]>
git.ipfire.org Git - ipfire.org.git/blob - src/backend/accounts.py
c310cf5300f8de86b9525016c2fb3890cf87470f
15 import tornado
.httpclient
20 from . import countries
22 from .decorators
import *
23 from .misc
import Object
25 # Set the client keytab name
26 os
.environ
["KRB5_CLIENT_KTNAME"] = "/etc/ipfire.org/ldap.keytab"
28 class Accounts(Object
):
30 self
.search_base
= self
.settings
.get("ldap_search_base")
33 # Only return developers (group with ID 1000)
34 accounts
= self
._search
("(&(objectClass=posixAccount)(gidNumber=1000))")
36 return iter(sorted(accounts
))
40 # Connect to LDAP server
41 ldap_uri
= self
.settings
.get("ldap_uri")
43 logging
.debug("Connecting to LDAP server: %s" % ldap_uri
)
45 # Connect to the LDAP server
46 return ldap
.ldapobject
.ReconnectLDAPObject(ldap_uri
,
47 retry_max
=10, retry_delay
=3)
49 def _authenticate(self
):
50 # Authenticate against LDAP server using Kerberos
51 self
.ldap
.sasl_gssapi_bind_s()
54 logging
.info("Testing LDAP connection...")
58 logging
.info("Successfully authenticated as %s" % self
.ldap
.whoami_s())
60 def _query(self
, query
, attrlist
=None, limit
=0, search_base
=None):
61 logging
.debug("Performing LDAP query (%s): %s" \
62 % (search_base
or self
.search_base
, query
))
66 results
= self
.ldap
.search_ext_s(search_base
or self
.search_base
,
67 ldap
.SCOPE_SUBTREE
, query
, attrlist
=attrlist
, sizelimit
=limit
)
69 # Log time it took to perform the query
70 logging
.debug("Query took %.2fms" % ((time
.time() - t
) * 1000.0))
74 def _search(self
, query
, attrlist
=None, limit
=0):
76 for dn
, attrs
in self
._query
(query
, attrlist
=["dn"], limit
=limit
):
77 account
= self
.get_by_dn(dn
)
78 accounts
.append(account
)
82 def _get_attrs(self
, dn
):
84 Fetches all attributes for the given distinguished name
86 results
= self
._query
("(objectClass=*)", search_base
=dn
, limit
=1,
87 attrlist
=("*", "createTimestamp", "modifyTimestamp"))
89 for dn
, attrs
in results
:
92 def get_by_dn(self
, dn
):
93 attrs
= self
.memcache
.get("accounts:%s:attrs" % dn
)
95 attrs
= self
._get
_attrs
(dn
)
98 # Cache all attributes for 5 min
99 self
.memcache
.set("accounts:%s:attrs" % dn
, attrs
, 300)
101 return Account(self
.backend
, dn
, attrs
)
103 def get_created_after(self
, ts
):
104 t
= ts
.strftime("%Y%m%d%H%M%SZ")
106 return self
._search
("(&(objectClass=person)(createTimestamp>=%s))" % t
)
108 def search(self
, query
):
109 # Search for exact matches
110 accounts
= self
._search
(
111 "(&(objectClass=person)(|(uid=%s)(mail=%s)(sipAuthenticationUser=%s)(telephoneNumber=%s)(homePhone=%s)(mobile=%s)))" \
112 % (query
, query
, query
, query
, query
, query
))
114 # Find accounts by name
116 for account
in self
._search
("(&(objectClass=person)(|(cn=*%s*)(uid=*%s*)))" % (query
, query
)):
117 if not account
in accounts
:
118 accounts
.append(account
)
120 return sorted(accounts
)
122 def _search_one(self
, query
):
123 results
= self
._search
(query
, limit
=1)
125 for result
in results
:
128 def uid_exists(self
, uid
):
129 if self
.get_by_uid(uid
):
132 res
= self
.db
.get("SELECT 1 FROM account_activations \
133 WHERE uid = %s AND expires_at > NOW()", uid
)
138 # Account with uid does not exist, yet
141 def get_by_uid(self
, uid
):
142 return self
._search
_one
("(&(objectClass=person)(uid=%s))" % uid
)
144 def get_by_mail(self
, mail
):
145 return self
._search
_one
("(&(objectClass=inetOrgPerson)(mail=%s))" % mail
)
147 def find_account(self
, s
):
148 account
= self
.get_by_uid(s
)
152 return self
.get_by_mail(s
)
154 def get_by_sip_id(self
, sip_id
):
158 return self
._search
_one
(
159 "(|(&(objectClass=sipUser)(sipAuthenticationUser=%s))(&(objectClass=sipRoutingObject)(sipLocalAddress=%s)))" \
162 def get_by_phone_number(self
, number
):
166 return self
._search
_one
(
167 "(&(objectClass=inetOrgPerson)(|(sipAuthenticationUser=%s)(telephoneNumber=%s)(homePhone=%s)(mobile=%s)))" \
168 % (number
, number
, number
, number
))
170 @tornado.gen
.coroutine
171 def check_spam(self
, uid
, email
, address
):
172 sfs
= StopForumSpam(self
.backend
, uid
, email
, address
)
175 score
= yield sfs
.check()
179 def auth(self
, username
, password
):
181 account
= self
.backend
.accounts
.find_account(username
)
184 if account
and account
.check_password(password
):
189 def register(self
, uid
, email
, first_name
, last_name
, country_code
=None):
190 # Convert all uids to lowercase
193 # Check if UID is unique
194 if self
.uid_exists(uid
):
195 raise ValueError("UID exists: %s" % uid
)
197 # Generate a random activation code
198 activation_code
= util
.random_string(36)
200 # Create an entry in our database until the user
201 # has activated the account
202 self
.db
.execute("INSERT INTO account_activations(uid, activation_code, \
203 email, first_name, last_name, country_code) VALUES(%s, %s, %s, %s, %s, %s)",
204 uid
, activation_code
, email
, first_name
, last_name
, country_code
)
206 # Send an account activation email
207 self
.backend
.messages
.send_template("auth/messages/register",
208 recipients
=[email
], priority
=100, uid
=uid
,
209 activation_code
=activation_code
, email
=email
,
210 first_name
=first_name
, last_name
=last_name
)
212 def activate(self
, uid
, activation_code
):
213 res
= self
.db
.get("DELETE FROM account_activations \
214 WHERE uid = %s AND activation_code = %s AND expires_at > NOW() \
215 RETURNING *", uid
, activation_code
)
217 # Return nothing when account was not found
221 # Return the account if it has already been created
222 account
= self
.get_by_uid(uid
)
226 # Create a new account on the LDAP database
227 account
= self
.create(uid
, res
.email
,
228 first_name
=res
.first_name
, last_name
=res
.last_name
,
229 country_code
=res
.country_code
)
231 # Send email about account registration
232 self
.backend
.messages
.send_template("people/messages/new-account",
233 recipients
=["admin@ipfire.org"], account
=account
)
237 def create(self
, uid
, email
, first_name
, last_name
, country_code
=None):
238 cn
= "%s %s" % (first_name
, last_name
)
242 "objectClass" : [b
"top", b
"person", b
"inetOrgPerson"],
243 "mail" : email
.encode(),
247 "sn" : last_name
.encode(),
248 "givenName" : first_name
.encode(),
251 logging
.info("Creating new account: %s: %s" % (uid
, account
))
254 dn
= "uid=%s,ou=People,dc=ipfire,dc=org" % uid
256 # Create account on LDAP
257 self
.accounts
._authenticate
()
258 self
.ldap
.add_s(dn
, ldap
.modlist
.addModlist(account
))
261 account
= self
.get_by_dn(dn
)
263 # Optionally set country code
265 account
.country_code
= country_code
272 def create_session(self
, account
, host
):
273 res
= self
.db
.get("INSERT INTO sessions(host, uid) VALUES(%s, %s) \
274 RETURNING session_id, time_expires", host
, account
.uid
)
276 # Session could not be created
280 logging
.info("Created session %s for %s which expires %s" \
281 % (res
.session_id
, account
, res
.time_expires
))
282 return res
.session_id
, res
.time_expires
284 def destroy_session(self
, session_id
, host
):
285 logging
.info("Destroying session %s" % session_id
)
287 self
.db
.execute("DELETE FROM sessions \
288 WHERE session_id = %s AND host = %s", session_id
, host
)
290 def get_by_session(self
, session_id
, host
):
291 logging
.debug("Looking up session %s" % session_id
)
293 res
= self
.db
.get("SELECT uid FROM sessions WHERE session_id = %s \
294 AND host = %s AND NOW() BETWEEN time_created AND time_expires",
297 # Session does not exist or has expired
301 # Update the session expiration time
302 self
.db
.execute("UPDATE sessions SET time_expires = NOW() + INTERVAL '14 days' \
303 WHERE session_id = %s AND host = %s", session_id
, host
)
305 return self
.get_by_uid(res
.uid
)
308 # Cleanup expired sessions
309 self
.db
.execute("DELETE FROM sessions WHERE time_expires <= NOW()")
311 # Cleanup expired account activations
312 self
.db
.execute("DELETE FROM account_activations WHERE expires_at <= NOW()")
316 def decode_discourse_payload(self
, payload
, signature
):
318 calculated_signature
= self
.sign_discourse_payload(payload
)
320 if not hmac
.compare_digest(signature
, calculated_signature
):
321 raise ValueError("Invalid signature: %s" % signature
)
323 # Decode the query string
324 qs
= base64
.b64decode(payload
).decode()
326 # Parse the query string
328 for key
, val
in urllib
.parse
.parse_qsl(qs
):
333 def encode_discourse_payload(self
, **args
):
334 # Encode the arguments into an URL-formatted string
335 qs
= urllib
.parse
.urlencode(args
).encode()
338 return base64
.b64encode(qs
).decode()
340 def sign_discourse_payload(self
, payload
, secret
=None):
342 secret
= self
.settings
.get("discourse_sso_secret")
344 # Calculate a HMAC using SHA256
345 h
= hmac
.new(secret
.encode(),
346 msg
=payload
.encode(), digestmod
="sha256")
351 class Account(Object
):
352 def __init__(self
, backend
, dn
, attrs
=None):
353 Object
.__init
__(self
, backend
)
356 self
.attributes
= attrs
or {}
365 return "<%s %s>" % (self
.__class
__.__name
__, self
.dn
)
367 def __eq__(self
, other
):
368 if isinstance(other
, self
.__class
__):
369 return self
.dn
== other
.dn
371 def __lt__(self
, other
):
372 if isinstance(other
, self
.__class
__):
373 return self
.name
< other
.name
377 return self
.accounts
.ldap
379 def _exists(self
, key
):
388 for value
in self
.attributes
.get(key
, []):
391 def _get_bytes(self
, key
, default
=None):
392 for value
in self
._get
(key
):
397 def _get_strings(self
, key
):
398 for value
in self
._get
(key
):
401 def _get_string(self
, key
, default
=None):
402 for value
in self
._get
_strings
(key
):
407 def _get_phone_numbers(self
, key
):
408 for value
in self
._get
_strings
(key
):
409 yield phonenumbers
.parse(value
, None)
411 def _get_timestamp(self
, key
):
412 value
= self
._get
_string
(key
)
414 # Parse the timestamp value and returns a datetime object
416 return datetime
.datetime
.strptime(value
, "%Y%m%d%H%M%SZ")
418 def _modify(self
, modlist
):
419 logging
.debug("Modifying %s: %s" % (self
.dn
, modlist
))
421 # Authenticate before performing any write operations
422 self
.accounts
._authenticate
()
424 # Run modify operation
425 self
.ldap
.modify_s(self
.dn
, modlist
)
427 # Delete cached attributes
428 self
.memcache
.delete("accounts:%s:attrs" % self
.dn
)
430 def _set(self
, key
, values
):
431 current
= self
._get
(key
)
433 # Don't do anything if nothing has changed
434 if list(current
) == values
:
437 # Remove all old values and add all new ones
440 if self
._exists
(key
):
441 modlist
.append((ldap
.MOD_DELETE
, key
, None))
445 modlist
.append((ldap
.MOD_ADD
, key
, values
))
447 # Run modify operation
448 self
._modify
(modlist
)
451 self
.attributes
.update({ key
: values
})
453 def _set_bytes(self
, key
, values
):
454 return self
._set
(key
, values
)
456 def _set_strings(self
, key
, values
):
457 return self
._set
(key
, [e
.encode() for e
in values
if e
])
459 def _set_string(self
, key
, value
):
460 return self
._set
_strings
(key
, [value
,])
462 def _add(self
, key
, values
):
464 (ldap
.MOD_ADD
, key
, values
),
467 self
._modify
(modlist
)
469 def _add_strings(self
, key
, values
):
470 return self
._add
(key
, [e
.encode() for e
in values
])
472 def _add_string(self
, key
, value
):
473 return self
._add
_strings
(key
, [value
,])
475 def _delete(self
, key
, values
):
477 (ldap
.MOD_DELETE
, key
, values
),
480 self
._modify
(modlist
)
482 def _delete_strings(self
, key
, values
):
483 return self
._delete
(key
, [e
.encode() for e
in values
])
485 def _delete_string(self
, key
, value
):
486 return self
._delete
_strings
(key
, [value
,])
489 def kerberos_attributes(self
):
490 res
= self
.backend
.accounts
._query
(
491 "(&(objectClass=krbPrincipal)(krbPrincipalName=%s@IPFIRE.ORG))" % self
.uid
,
493 "krbLastSuccessfulAuth",
494 "krbLastPasswordChange",
496 "krbLoginFailedCount",
499 search_base
="cn=krb5,%s" % self
.backend
.accounts
.search_base
)
501 for dn
, attrs
in res
:
502 return { key
: attrs
[key
][0] for key
in attrs
}
508 return datetime
.datetime
.strptime(s
.decode(), "%Y%m%d%H%M%SZ")
511 def last_successful_authentication(self
):
513 s
= self
.kerberos_attributes
["krbLastSuccessfulAuth"]
517 return self
._parse
_date
(s
)
520 def last_failed_authentication(self
):
522 s
= self
.kerberos_attributes
["krbLastFailedAuth"]
526 return self
._parse
_date
(s
)
529 def failed_login_count(self
):
531 count
= self
.kerberos_attributes
["krbLoginFailedCount"].decode()
540 def passwd(self
, password
):
544 # The new password must have a score of 3 or better
545 quality
= self
.check_password_quality(password
)
546 if quality
["score"] < 3:
547 raise ValueError("Password too weak")
549 self
.accounts
._authenticate
()
550 self
.ldap
.passwd_s(self
.dn
, None, password
)
552 def check_password(self
, password
):
554 Bind to the server with given credentials and return
555 true if password is corrent and false if not.
557 Raises exceptions from the server on any other errors.
562 logging
.debug("Checking credentials for %s" % self
.dn
)
564 # Create a new LDAP connection
565 ldap_uri
= self
.backend
.settings
.get("ldap_uri")
566 conn
= ldap
.initialize(ldap_uri
)
569 conn
.simple_bind_s(self
.dn
, password
.encode("utf-8"))
570 except ldap
.INVALID_CREDENTIALS
:
571 logging
.debug("Account credentials are invalid for %s" % self
)
574 logging
.info("Successfully authenticated %s" % self
)
578 def check_password_quality(self
, password
):
580 Passwords are passed through zxcvbn to make sure
581 that they are strong enough.
583 return zxcvbn
.zxcvbn(password
, user_inputs
=(
584 self
.first_name
, self
.last_name
,
588 return "sudo" in self
.groups
591 return "staff" in self
.groups
594 return "posixAccount" in self
.classes
597 return "postfixMailUser" in self
.classes
600 return "sipUser" in self
.classes
or "sipRoutingObject" in self
.classes
602 def can_be_managed_by(self
, account
):
604 Returns True if account is allowed to manage this account
606 # Admins can manage all accounts
607 if account
.is_admin():
610 # Users can manage themselves
611 return self
== account
615 return self
._get
_strings
("objectClass")
619 return self
._get
_string
("uid")
623 return self
._get
_string
("cn")
627 def get_nickname(self
):
628 return self
._get
_string
("displayName")
630 def set_nickname(self
, nickname
):
631 self
._set
_string
("displayName", nickname
)
633 nickname
= property(get_nickname
, set_nickname
)
637 def get_first_name(self
):
638 return self
._get
_string
("givenName")
640 def set_first_name(self
, first_name
):
641 self
._set
_string
("givenName", first_name
)
644 self
._set
_string
("cn", "%s %s" % (first_name
, self
.last_name
))
646 first_name
= property(get_first_name
, set_first_name
)
650 def get_last_name(self
):
651 return self
._get
_string
("sn")
653 def set_last_name(self
, last_name
):
654 self
._set
_string
("sn", last_name
)
657 self
._set
_string
("cn", "%s %s" % (self
.first_name
, last_name
))
659 last_name
= property(get_last_name
, set_last_name
)
663 groups
= self
.memcache
.get("accounts:%s:groups" % self
.dn
)
667 # Fetch groups from LDAP
668 groups
= self
._get
_groups
()
670 # Cache groups for 5 min
671 self
.memcache
.set("accounts:%s:groups" % self
.dn
, groups
, 300)
675 def _get_groups(self
):
678 res
= self
.accounts
._query
("(&(objectClass=posixGroup) \
679 (memberUid=%s))" % self
.uid
, ["cn"])
681 for dn
, attrs
in res
:
682 cns
= attrs
.get("cn")
684 groups
.append(cns
[0].decode())
688 # Created/Modified at
691 def created_at(self
):
692 return self
._get
_timestamp
("createTimestamp")
695 def modified_at(self
):
696 return self
._get
_timestamp
("modifyTimestamp")
705 address
+= self
.street
.splitlines()
707 if self
.postal_code
and self
.city
:
708 if self
.country_code
in ("AT", "DE"):
709 address
.append("%s %s" % (self
.postal_code
, self
.city
))
711 address
.append("%s, %s" % (self
.city
, self
.postal_code
))
713 address
.append(self
.city
or self
.postal_code
)
715 if self
.country_name
:
716 address
.append(self
.country_name
)
720 def get_street(self
):
721 return self
._get
_string
("street") or self
._get
_string
("homePostalAddress")
723 def set_street(self
, street
):
724 self
._set
_string
("street", street
)
726 street
= property(get_street
, set_street
)
729 return self
._get
_string
("l") or ""
731 def set_city(self
, city
):
732 self
._set
_string
("l", city
)
734 city
= property(get_city
, set_city
)
736 def get_postal_code(self
):
737 return self
._get
_string
("postalCode") or ""
739 def set_postal_code(self
, postal_code
):
740 self
._set
_string
("postalCode", postal_code
)
742 postal_code
= property(get_postal_code
, set_postal_code
)
744 # XXX This should be c
745 def get_country_code(self
):
746 return self
._get
_string
("st")
748 def set_country_code(self
, country_code
):
749 self
._set
_string
("st", country_code
)
751 country_code
= property(get_country_code
, set_country_code
)
754 def country_name(self
):
755 if self
.country_code
:
756 return countries
.get_name(self
.country_code
)
760 return self
._get
_string
("mail")
762 # Mail Routing Address
764 def get_mail_routing_address(self
):
765 return self
._get
_string
("mailRoutingAddress", None)
767 def set_mail_routing_address(self
, address
):
768 self
._set
_string
("mailRoutingAddress", address
or None)
770 mail_routing_address
= property(get_mail_routing_address
, set_mail_routing_address
)
774 if "sipUser" in self
.classes
:
775 return self
._get
_string
("sipAuthenticationUser")
777 if "sipRoutingObject" in self
.classes
:
778 return self
._get
_string
("sipLocalAddress")
781 def sip_password(self
):
782 return self
._get
_string
("sipPassword")
785 def _generate_sip_password():
786 return util
.random_string(8)
790 return "%s@ipfire.org" % self
.sip_id
792 def uses_sip_forwarding(self
):
793 if self
.sip_routing_address
:
800 def get_sip_routing_address(self
):
801 if "sipRoutingObject" in self
.classes
:
802 return self
._get
_string
("sipRoutingAddress")
804 def set_sip_routing_address(self
, address
):
808 # Don't do anything if nothing has changed
809 if self
.get_sip_routing_address() == address
:
813 # This is no longer a SIP user any more
816 (ldap
.MOD_DELETE
, "objectClass", b
"sipUser"),
817 (ldap
.MOD_DELETE
, "sipAuthenticationUser", None),
818 (ldap
.MOD_DELETE
, "sipPassword", None),
820 except ldap
.NO_SUCH_ATTRIBUTE
:
823 # Set new routing object
826 (ldap
.MOD_ADD
, "objectClass", b
"sipRoutingObject"),
827 (ldap
.MOD_ADD
, "sipLocalAddress", self
.sip_id
.encode()),
828 (ldap
.MOD_ADD
, "sipRoutingAddress", address
.encode()),
831 # If this is a change, we cannot add this again
832 except ldap
.TYPE_OR_VALUE_EXISTS
:
833 self
._set
_string
("sipRoutingAddress", address
)
837 (ldap
.MOD_DELETE
, "objectClass", b
"sipRoutingObject"),
838 (ldap
.MOD_DELETE
, "sipLocalAddress", None),
839 (ldap
.MOD_DELETE
, "sipRoutingAddress", None),
841 except ldap
.NO_SUCH_ATTRIBUTE
:
845 (ldap
.MOD_ADD
, "objectClass", b
"sipUser"),
846 (ldap
.MOD_ADD
, "sipAuthenticationUser", self
.sip_id
.encode()),
847 (ldap
.MOD_ADD
, "sipPassword", self
._generate
_sip
_password
().encode()),
850 # XXX Cache is invalid here
852 sip_routing_address
= property(get_sip_routing_address
, set_sip_routing_address
)
855 def sip_registrations(self
):
856 sip_registrations
= []
858 for reg
in self
.backend
.talk
.freeswitch
.get_sip_registrations(self
.sip_url
):
861 sip_registrations
.append(reg
)
863 return sip_registrations
866 def sip_channels(self
):
867 return self
.backend
.talk
.freeswitch
.get_sip_channels(self
)
869 def get_cdr(self
, date
=None, limit
=None):
870 return self
.backend
.talk
.freeswitch
.get_cdr_by_account(self
, date
=date
, limit
=limit
)
875 def phone_number(self
):
877 Returns the IPFire phone number
880 return phonenumbers
.parse("+4923636035%s" % self
.sip_id
)
883 def fax_number(self
):
885 return phonenumbers
.parse("+49236360359%s" % self
.sip_id
)
887 def get_phone_numbers(self
):
890 for field
in ("telephoneNumber", "homePhone", "mobile"):
891 for number
in self
._get
_phone
_numbers
(field
):
896 def set_phone_numbers(self
, phone_numbers
):
897 # Sort phone numbers by landline and mobile
898 _landline_numbers
= []
901 for number
in phone_numbers
:
903 number
= phonenumbers
.parse(number
, None)
904 except phonenumbers
.phonenumberutil
.NumberParseException
:
907 # Convert to string (in E.164 format)
908 s
= phonenumbers
.format_number(number
, phonenumbers
.PhoneNumberFormat
.E164
)
910 # Separate mobile numbers
911 if phonenumbers
.number_type(number
) == phonenumbers
.PhoneNumberType
.MOBILE
:
912 _mobile_numbers
.append(s
)
914 _landline_numbers
.append(s
)
917 self
._set
_strings
("telephoneNumber", _landline_numbers
)
918 self
._set
_strings
("mobile", _mobile_numbers
)
920 phone_numbers
= property(get_phone_numbers
, set_phone_numbers
)
923 def _all_telephone_numbers(self
):
924 ret
= [ self
.sip_id
, ]
926 if self
.phone_number
:
927 s
= phonenumbers
.format_number(self
.phone_number
, phonenumbers
.PhoneNumberFormat
.E164
)
930 for number
in self
.phone_numbers
:
931 s
= phonenumbers
.format_number(number
, phonenumbers
.PhoneNumberFormat
.E164
)
936 def avatar_url(self
, size
=None):
937 url
= "https://people.ipfire.org/users/%s.jpg" % self
.uid
940 url
+= "?size=%s" % size
944 def get_avatar(self
, size
=None):
945 photo
= self
._get
_bytes
("jpegPhoto")
947 # Exit if no avatar is available
951 # Return the raw image if no size was requested
955 # Try to retrieve something from the cache
956 avatar
= self
.memcache
.get("accounts:%s:avatar:%s" % (self
.dn
, size
))
960 # Generate a new thumbnail
961 avatar
= util
.generate_thumbnail(photo
, size
, square
=True)
963 # Save to cache for 15m
964 self
.memcache
.set("accounts:%s:avatar:%s" % (self
.dn
, size
), avatar
, 900)
968 def upload_avatar(self
, avatar
):
969 self
._set
("jpegPhoto", avatar
)
977 for key
in self
._get
_strings
("sshPublicKey"):
978 s
= sshpubkeys
.SSHKey()
982 except (sshpubkeys
.InvalidKeyError
, NotImplementedError) as e
:
983 logging
.warning("Could not parse SSH key %s: %s" % (key
, e
))
990 def get_ssh_key_by_hash_sha256(self
, hash_sha256
):
991 for key
in self
.ssh_keys
:
992 if not key
.hash_sha256() == hash_sha256
:
997 def add_ssh_key(self
, key
):
998 k
= sshpubkeys
.SSHKey()
1000 # Try to parse the key
1003 # Check for types and sufficient sizes
1004 if k
.key_type
== b
"ssh-rsa":
1006 raise sshpubkeys
.TooShortKeyError("RSA keys cannot be smaller than 4096 bits")
1008 elif k
.key_type
== b
"ssh-dss":
1009 raise sshpubkeys
.InvalidKeyError("DSA keys are not supported")
1011 # Ignore any duplicates
1012 if key
in (k
.keydata
for k
in self
.ssh_keys
):
1013 logging
.debug("SSH Key has already been added for %s: %s" % (self
, key
))
1016 # Prepare transaction
1019 # Add object class if user is not in it, yet
1020 if not "ldapPublicKey" in self
.classes
:
1021 modlist
.append((ldap
.MOD_ADD
, "objectClass", b
"ldapPublicKey"))
1024 modlist
.append((ldap
.MOD_ADD
, "sshPublicKey", key
.encode()))
1027 self
._modify
(modlist
)
1030 self
.ssh_keys
.append(k
)
1032 def delete_ssh_key(self
, key
):
1033 if not key
in (k
.keydata
for k
in self
.ssh_keys
):
1036 # Delete key from LDAP
1037 if len(self
.ssh_keys
) > 1:
1038 self
._delete
_string
("sshPublicKey", key
)
1041 (ldap
.MOD_DELETE
, "objectClass", b
"ldapPublicKey"),
1042 (ldap
.MOD_DELETE
, "sshPublicKey", key
.encode()),
1046 class StopForumSpam(Object
):
1047 def init(self
, uid
, email
, address
):
1048 self
.uid
, self
.email
, self
.address
= uid
, email
, address
1050 @tornado.gen
.coroutine
1051 def send_request(self
, **kwargs
):
1055 arguments
.update(kwargs
)
1058 request
= tornado
.httpclient
.HTTPRequest(
1059 "https://api.stopforumspam.org/api", method
="POST")
1060 request
.body
= urllib
.parse
.urlencode(arguments
)
1063 response
= yield self
.backend
.http_client
.fetch(request
)
1065 # Decode the JSON response
1066 return json
.loads(response
.body
.decode())
1068 @tornado.gen
.coroutine
1069 def check_address(self
):
1070 response
= yield self
.send_request(ip
=self
.address
)
1073 confidence
= response
["ip"]["confidence"]
1077 logging
.debug("Confidence for %s: %s" % (self
.address
, confidence
))
1081 @tornado.gen
.coroutine
1082 def check_username(self
):
1083 response
= yield self
.send_request(username
=self
.uid
)
1086 confidence
= response
["username"]["confidence"]
1090 logging
.debug("Confidence for %s: %s" % (self
.uid
, confidence
))
1094 @tornado.gen
.coroutine
1095 def check_email(self
):
1096 response
= yield self
.send_request(email
=self
.email
)
1099 confidence
= response
["email"]["confidence"]
1103 logging
.debug("Confidence for %s: %s" % (self
.email
, confidence
))
1107 @tornado.gen
.coroutine
1108 def check(self
, threshold
=95):
1110 This function tries to detect if we have a spammer.
1112 To honour the privacy of our users, we only send the IP
1113 address and username and if those are on the database, we
1114 will send the email address as well.
1116 confidences
= yield [self
.check_address(), self
.check_username()]
1118 if any((c
< threshold
for c
in confidences
)):
1119 confidences
+= yield [self
.check_email()]
1121 # Build a score based on the lowest confidence
1122 return 100 - min(confidences
)
1125 if __name__
== "__main__":