]>
git.ipfire.org Git - ipfire.org.git/blob - src/backend/accounts.py
f8db50a7871fc8e784ac3cf657d12f9ba988205f
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_is_valid(self
, uid
):
129 # UID must be at least four characters
133 # https://unix.stackexchange.com/questions/157426/what-is-the-regex-to-validate-linux-users
134 m
= re
.match(r
"^[a-z_]([a-z0-9_-]{0,31}|[a-z0-9_-]{0,30}\$)$", uid
)
140 def uid_exists(self
, uid
):
141 if self
.get_by_uid(uid
):
144 res
= self
.db
.get("SELECT 1 FROM account_activations \
145 WHERE uid = %s AND expires_at > NOW()", uid
)
150 # Account with uid does not exist, yet
153 def get_by_uid(self
, uid
):
154 return self
._search
_one
("(&(objectClass=person)(uid=%s))" % uid
)
156 def get_by_mail(self
, mail
):
157 return self
._search
_one
("(&(objectClass=inetOrgPerson)(mail=%s))" % mail
)
159 def find_account(self
, s
):
160 account
= self
.get_by_uid(s
)
164 return self
.get_by_mail(s
)
166 def get_by_sip_id(self
, sip_id
):
170 return self
._search
_one
(
171 "(|(&(objectClass=sipUser)(sipAuthenticationUser=%s))(&(objectClass=sipRoutingObject)(sipLocalAddress=%s)))" \
174 def get_by_phone_number(self
, number
):
178 return self
._search
_one
(
179 "(&(objectClass=inetOrgPerson)(|(sipAuthenticationUser=%s)(telephoneNumber=%s)(homePhone=%s)(mobile=%s)))" \
180 % (number
, number
, number
, number
))
182 @tornado.gen
.coroutine
183 def check_spam(self
, uid
, email
, address
):
184 sfs
= StopForumSpam(self
.backend
, uid
, email
, address
)
187 score
= yield sfs
.check()
191 def auth(self
, username
, password
):
193 account
= self
.backend
.accounts
.find_account(username
)
196 if account
and account
.check_password(password
):
201 def register(self
, uid
, email
, first_name
, last_name
, country_code
=None):
202 # Convert all uids to lowercase
205 # Check if UID is valid
206 if not self
.uid_is_valid(uid
):
207 raise ValueError("UID is invalid: %s" % uid
)
209 # Check if UID is unique
210 if self
.uid_exists(uid
):
211 raise ValueError("UID exists: %s" % uid
)
213 # Generate a random activation code
214 activation_code
= util
.random_string(36)
216 # Create an entry in our database until the user
217 # has activated the account
218 self
.db
.execute("INSERT INTO account_activations(uid, activation_code, \
219 email, first_name, last_name, country_code) VALUES(%s, %s, %s, %s, %s, %s)",
220 uid
, activation_code
, email
, first_name
, last_name
, country_code
)
222 # Send an account activation email
223 self
.backend
.messages
.send_template("auth/messages/register",
224 recipients
=[email
], priority
=100, uid
=uid
,
225 activation_code
=activation_code
, email
=email
,
226 first_name
=first_name
, last_name
=last_name
)
228 def activate(self
, uid
, activation_code
):
229 res
= self
.db
.get("DELETE FROM account_activations \
230 WHERE uid = %s AND activation_code = %s AND expires_at > NOW() \
231 RETURNING *", uid
, activation_code
)
233 # Return nothing when account was not found
237 # Return the account if it has already been created
238 account
= self
.get_by_uid(uid
)
242 # Create a new account on the LDAP database
243 account
= self
.create(uid
, res
.email
,
244 first_name
=res
.first_name
, last_name
=res
.last_name
,
245 country_code
=res
.country_code
)
247 # Send email about account registration
248 self
.backend
.messages
.send_template("people/messages/new-account",
249 recipients
=["admin@ipfire.org"], account
=account
)
253 def create(self
, uid
, email
, first_name
, last_name
, country_code
=None):
254 cn
= "%s %s" % (first_name
, last_name
)
258 "objectClass" : [b
"top", b
"person", b
"inetOrgPerson"],
259 "mail" : email
.encode(),
263 "sn" : last_name
.encode(),
264 "givenName" : first_name
.encode(),
267 logging
.info("Creating new account: %s: %s" % (uid
, account
))
270 dn
= "uid=%s,ou=People,dc=ipfire,dc=org" % uid
272 # Create account on LDAP
273 self
.accounts
._authenticate
()
274 self
.ldap
.add_s(dn
, ldap
.modlist
.addModlist(account
))
277 account
= self
.get_by_dn(dn
)
279 # Optionally set country code
281 account
.country_code
= country_code
288 def create_session(self
, account
, host
):
289 session_id
= util
.random_string(64)
291 res
= self
.db
.get("INSERT INTO sessions(host, uid, session_id) VALUES(%s, %s, %s) \
292 RETURNING session_id, time_expires", host
, account
.uid
, session_id
)
294 # Session could not be created
298 logging
.info("Created session %s for %s which expires %s" \
299 % (res
.session_id
, account
, res
.time_expires
))
300 return res
.session_id
, res
.time_expires
302 def destroy_session(self
, session_id
, host
):
303 logging
.info("Destroying session %s" % session_id
)
305 self
.db
.execute("DELETE FROM sessions \
306 WHERE session_id = %s AND host = %s", session_id
, host
)
308 def get_by_session(self
, session_id
, host
):
309 logging
.debug("Looking up session %s" % session_id
)
311 res
= self
.db
.get("SELECT uid FROM sessions WHERE session_id = %s \
312 AND host = %s AND NOW() BETWEEN time_created AND time_expires",
315 # Session does not exist or has expired
319 # Update the session expiration time
320 self
.db
.execute("UPDATE sessions SET time_expires = NOW() + INTERVAL '14 days' \
321 WHERE session_id = %s AND host = %s", session_id
, host
)
323 return self
.get_by_uid(res
.uid
)
326 # Cleanup expired sessions
327 self
.db
.execute("DELETE FROM sessions WHERE time_expires <= NOW()")
329 # Cleanup expired account activations
330 self
.db
.execute("DELETE FROM account_activations WHERE expires_at <= NOW()")
334 def decode_discourse_payload(self
, payload
, signature
):
336 calculated_signature
= self
.sign_discourse_payload(payload
)
338 if not hmac
.compare_digest(signature
, calculated_signature
):
339 raise ValueError("Invalid signature: %s" % signature
)
341 # Decode the query string
342 qs
= base64
.b64decode(payload
).decode()
344 # Parse the query string
346 for key
, val
in urllib
.parse
.parse_qsl(qs
):
351 def encode_discourse_payload(self
, **args
):
352 # Encode the arguments into an URL-formatted string
353 qs
= urllib
.parse
.urlencode(args
).encode()
356 return base64
.b64encode(qs
).decode()
358 def sign_discourse_payload(self
, payload
, secret
=None):
360 secret
= self
.settings
.get("discourse_sso_secret")
362 # Calculate a HMAC using SHA256
363 h
= hmac
.new(secret
.encode(),
364 msg
=payload
.encode(), digestmod
="sha256")
369 class Account(Object
):
370 def __init__(self
, backend
, dn
, attrs
=None):
371 Object
.__init
__(self
, backend
)
374 self
.attributes
= attrs
or {}
383 return "<%s %s>" % (self
.__class
__.__name
__, self
.dn
)
385 def __eq__(self
, other
):
386 if isinstance(other
, self
.__class
__):
387 return self
.dn
== other
.dn
389 def __lt__(self
, other
):
390 if isinstance(other
, self
.__class
__):
391 return self
.name
< other
.name
395 return self
.accounts
.ldap
397 def _exists(self
, key
):
406 for value
in self
.attributes
.get(key
, []):
409 def _get_bytes(self
, key
, default
=None):
410 for value
in self
._get
(key
):
415 def _get_strings(self
, key
):
416 for value
in self
._get
(key
):
419 def _get_string(self
, key
, default
=None):
420 for value
in self
._get
_strings
(key
):
425 def _get_phone_numbers(self
, key
):
426 for value
in self
._get
_strings
(key
):
427 yield phonenumbers
.parse(value
, None)
429 def _get_timestamp(self
, key
):
430 value
= self
._get
_string
(key
)
432 # Parse the timestamp value and returns a datetime object
434 return datetime
.datetime
.strptime(value
, "%Y%m%d%H%M%SZ")
436 def _modify(self
, modlist
):
437 logging
.debug("Modifying %s: %s" % (self
.dn
, modlist
))
439 # Authenticate before performing any write operations
440 self
.accounts
._authenticate
()
442 # Run modify operation
443 self
.ldap
.modify_s(self
.dn
, modlist
)
445 # Delete cached attributes
446 self
.memcache
.delete("accounts:%s:attrs" % self
.dn
)
448 def _set(self
, key
, values
):
449 current
= self
._get
(key
)
451 # Don't do anything if nothing has changed
452 if list(current
) == values
:
455 # Remove all old values and add all new ones
458 if self
._exists
(key
):
459 modlist
.append((ldap
.MOD_DELETE
, key
, None))
463 modlist
.append((ldap
.MOD_ADD
, key
, values
))
465 # Run modify operation
466 self
._modify
(modlist
)
469 self
.attributes
.update({ key
: values
})
471 def _set_bytes(self
, key
, values
):
472 return self
._set
(key
, values
)
474 def _set_strings(self
, key
, values
):
475 return self
._set
(key
, [e
.encode() for e
in values
if e
])
477 def _set_string(self
, key
, value
):
478 return self
._set
_strings
(key
, [value
,])
480 def _add(self
, key
, values
):
482 (ldap
.MOD_ADD
, key
, values
),
485 self
._modify
(modlist
)
487 def _add_strings(self
, key
, values
):
488 return self
._add
(key
, [e
.encode() for e
in values
])
490 def _add_string(self
, key
, value
):
491 return self
._add
_strings
(key
, [value
,])
493 def _delete(self
, key
, values
):
495 (ldap
.MOD_DELETE
, key
, values
),
498 self
._modify
(modlist
)
500 def _delete_strings(self
, key
, values
):
501 return self
._delete
(key
, [e
.encode() for e
in values
])
503 def _delete_string(self
, key
, value
):
504 return self
._delete
_strings
(key
, [value
,])
507 def kerberos_attributes(self
):
508 res
= self
.backend
.accounts
._query
(
509 "(&(objectClass=krbPrincipal)(krbPrincipalName=%s@IPFIRE.ORG))" % self
.uid
,
511 "krbLastSuccessfulAuth",
512 "krbLastPasswordChange",
514 "krbLoginFailedCount",
517 search_base
="cn=krb5,%s" % self
.backend
.accounts
.search_base
)
519 for dn
, attrs
in res
:
520 return { key
: attrs
[key
][0] for key
in attrs
}
526 return datetime
.datetime
.strptime(s
.decode(), "%Y%m%d%H%M%SZ")
529 def last_successful_authentication(self
):
531 s
= self
.kerberos_attributes
["krbLastSuccessfulAuth"]
535 return self
._parse
_date
(s
)
538 def last_failed_authentication(self
):
540 s
= self
.kerberos_attributes
["krbLastFailedAuth"]
544 return self
._parse
_date
(s
)
547 def failed_login_count(self
):
549 count
= self
.kerberos_attributes
["krbLoginFailedCount"].decode()
558 def passwd(self
, password
):
562 # The new password must have a score of 3 or better
563 quality
= self
.check_password_quality(password
)
564 if quality
["score"] < 3:
565 raise ValueError("Password too weak")
567 self
.accounts
._authenticate
()
568 self
.ldap
.passwd_s(self
.dn
, None, password
)
570 def check_password(self
, password
):
572 Bind to the server with given credentials and return
573 true if password is corrent and false if not.
575 Raises exceptions from the server on any other errors.
580 logging
.debug("Checking credentials for %s" % self
.dn
)
582 # Create a new LDAP connection
583 ldap_uri
= self
.backend
.settings
.get("ldap_uri")
584 conn
= ldap
.initialize(ldap_uri
)
587 conn
.simple_bind_s(self
.dn
, password
.encode("utf-8"))
588 except ldap
.INVALID_CREDENTIALS
:
589 logging
.debug("Account credentials are invalid for %s" % self
)
592 logging
.info("Successfully authenticated %s" % self
)
596 def check_password_quality(self
, password
):
598 Passwords are passed through zxcvbn to make sure
599 that they are strong enough.
601 return zxcvbn
.zxcvbn(password
, user_inputs
=(
602 self
.first_name
, self
.last_name
,
606 return self
.is_member_of_group("sudo")
609 return self
.is_member_of_group("staff")
611 def is_moderator(self
):
612 return self
.is_member_of_group("moderators")
615 return "posixAccount" in self
.classes
618 return "postfixMailUser" in self
.classes
621 return "sipUser" in self
.classes
or "sipRoutingObject" in self
.classes
623 def can_be_managed_by(self
, account
):
625 Returns True if account is allowed to manage this account
627 # Admins can manage all accounts
628 if account
.is_admin():
631 # Users can manage themselves
632 return self
== account
636 return self
._get
_strings
("objectClass")
640 return self
._get
_string
("uid")
644 return self
._get
_string
("cn")
648 def get_nickname(self
):
649 return self
._get
_string
("displayName")
651 def set_nickname(self
, nickname
):
652 self
._set
_string
("displayName", nickname
)
654 nickname
= property(get_nickname
, set_nickname
)
658 def get_first_name(self
):
659 return self
._get
_string
("givenName")
661 def set_first_name(self
, first_name
):
662 self
._set
_string
("givenName", first_name
)
665 self
._set
_string
("cn", "%s %s" % (first_name
, self
.last_name
))
667 first_name
= property(get_first_name
, set_first_name
)
671 def get_last_name(self
):
672 return self
._get
_string
("sn")
674 def set_last_name(self
, last_name
):
675 self
._set
_string
("sn", last_name
)
678 self
._set
_string
("cn", "%s %s" % (self
.first_name
, last_name
))
680 last_name
= property(get_last_name
, set_last_name
)
684 groups
= self
.memcache
.get("accounts:%s:groups" % self
.dn
)
686 # Fetch groups from LDAP
687 groups
= self
._get
_groups
()
689 # Cache groups for 5 min
690 self
.memcache
.set("accounts:%s:groups" % self
.dn
, groups
, 300)
692 return sorted((Group(self
.backend
, gid
) for gid
in groups
))
694 def _get_groups(self
):
697 res
= self
.accounts
._query
("(&(objectClass=posixGroup) \
698 (memberUid=%s))" % self
.uid
, ["cn"])
700 for dn
, attrs
in res
:
701 cns
= attrs
.get("cn")
703 groups
.append(cns
[0].decode())
707 def is_member_of_group(self
, gid
):
709 Returns True if this account is a member of this group
711 return gid
in (g
.gid
for g
in self
.groups
)
713 # Created/Modified at
716 def created_at(self
):
717 return self
._get
_timestamp
("createTimestamp")
720 def modified_at(self
):
721 return self
._get
_timestamp
("modifyTimestamp")
730 address
+= self
.street
.splitlines()
732 if self
.postal_code
and self
.city
:
733 if self
.country_code
in ("AT", "DE"):
734 address
.append("%s %s" % (self
.postal_code
, self
.city
))
736 address
.append("%s, %s" % (self
.city
, self
.postal_code
))
738 address
.append(self
.city
or self
.postal_code
)
740 if self
.country_name
:
741 address
.append(self
.country_name
)
745 def get_street(self
):
746 return self
._get
_string
("street") or self
._get
_string
("homePostalAddress")
748 def set_street(self
, street
):
749 self
._set
_string
("street", street
)
751 street
= property(get_street
, set_street
)
754 return self
._get
_string
("l") or ""
756 def set_city(self
, city
):
757 self
._set
_string
("l", city
)
759 city
= property(get_city
, set_city
)
761 def get_postal_code(self
):
762 return self
._get
_string
("postalCode") or ""
764 def set_postal_code(self
, postal_code
):
765 self
._set
_string
("postalCode", postal_code
)
767 postal_code
= property(get_postal_code
, set_postal_code
)
769 # XXX This should be c
770 def get_country_code(self
):
771 return self
._get
_string
("st")
773 def set_country_code(self
, country_code
):
774 self
._set
_string
("st", country_code
)
776 country_code
= property(get_country_code
, set_country_code
)
779 def country_name(self
):
780 if self
.country_code
:
781 return countries
.get_name(self
.country_code
)
785 return self
._get
_string
("mail")
787 # Mail Routing Address
789 def get_mail_routing_address(self
):
790 return self
._get
_string
("mailRoutingAddress", None)
792 def set_mail_routing_address(self
, address
):
793 self
._set
_string
("mailRoutingAddress", address
or None)
795 mail_routing_address
= property(get_mail_routing_address
, set_mail_routing_address
)
799 if "sipUser" in self
.classes
:
800 return self
._get
_string
("sipAuthenticationUser")
802 if "sipRoutingObject" in self
.classes
:
803 return self
._get
_string
("sipLocalAddress")
806 def sip_password(self
):
807 return self
._get
_string
("sipPassword")
810 def _generate_sip_password():
811 return util
.random_string(8)
815 return "%s@ipfire.org" % self
.sip_id
817 def uses_sip_forwarding(self
):
818 if self
.sip_routing_address
:
825 def get_sip_routing_address(self
):
826 if "sipRoutingObject" in self
.classes
:
827 return self
._get
_string
("sipRoutingAddress")
829 def set_sip_routing_address(self
, address
):
833 # Don't do anything if nothing has changed
834 if self
.get_sip_routing_address() == address
:
838 # This is no longer a SIP user any more
841 (ldap
.MOD_DELETE
, "objectClass", b
"sipUser"),
842 (ldap
.MOD_DELETE
, "sipAuthenticationUser", None),
843 (ldap
.MOD_DELETE
, "sipPassword", None),
845 except ldap
.NO_SUCH_ATTRIBUTE
:
848 # Set new routing object
851 (ldap
.MOD_ADD
, "objectClass", b
"sipRoutingObject"),
852 (ldap
.MOD_ADD
, "sipLocalAddress", self
.sip_id
.encode()),
853 (ldap
.MOD_ADD
, "sipRoutingAddress", address
.encode()),
856 # If this is a change, we cannot add this again
857 except ldap
.TYPE_OR_VALUE_EXISTS
:
858 self
._set
_string
("sipRoutingAddress", address
)
862 (ldap
.MOD_DELETE
, "objectClass", b
"sipRoutingObject"),
863 (ldap
.MOD_DELETE
, "sipLocalAddress", None),
864 (ldap
.MOD_DELETE
, "sipRoutingAddress", None),
866 except ldap
.NO_SUCH_ATTRIBUTE
:
870 (ldap
.MOD_ADD
, "objectClass", b
"sipUser"),
871 (ldap
.MOD_ADD
, "sipAuthenticationUser", self
.sip_id
.encode()),
872 (ldap
.MOD_ADD
, "sipPassword", self
._generate
_sip
_password
().encode()),
875 # XXX Cache is invalid here
877 sip_routing_address
= property(get_sip_routing_address
, set_sip_routing_address
)
880 def sip_registrations(self
):
881 sip_registrations
= []
883 for reg
in self
.backend
.talk
.freeswitch
.get_sip_registrations(self
.sip_url
):
886 sip_registrations
.append(reg
)
888 return sip_registrations
891 def sip_channels(self
):
892 return self
.backend
.talk
.freeswitch
.get_sip_channels(self
)
894 def get_cdr(self
, date
=None, limit
=None):
895 return self
.backend
.talk
.freeswitch
.get_cdr_by_account(self
, date
=date
, limit
=limit
)
900 def phone_number(self
):
902 Returns the IPFire phone number
905 return phonenumbers
.parse("+4923636035%s" % self
.sip_id
)
908 def fax_number(self
):
910 return phonenumbers
.parse("+49236360359%s" % self
.sip_id
)
912 def get_phone_numbers(self
):
915 for field
in ("telephoneNumber", "homePhone", "mobile"):
916 for number
in self
._get
_phone
_numbers
(field
):
921 def set_phone_numbers(self
, phone_numbers
):
922 # Sort phone numbers by landline and mobile
923 _landline_numbers
= []
926 for number
in phone_numbers
:
928 number
= phonenumbers
.parse(number
, None)
929 except phonenumbers
.phonenumberutil
.NumberParseException
:
932 # Convert to string (in E.164 format)
933 s
= phonenumbers
.format_number(number
, phonenumbers
.PhoneNumberFormat
.E164
)
935 # Separate mobile numbers
936 if phonenumbers
.number_type(number
) == phonenumbers
.PhoneNumberType
.MOBILE
:
937 _mobile_numbers
.append(s
)
939 _landline_numbers
.append(s
)
942 self
._set
_strings
("telephoneNumber", _landline_numbers
)
943 self
._set
_strings
("mobile", _mobile_numbers
)
945 phone_numbers
= property(get_phone_numbers
, set_phone_numbers
)
948 def _all_telephone_numbers(self
):
949 ret
= [ self
.sip_id
, ]
951 if self
.phone_number
:
952 s
= phonenumbers
.format_number(self
.phone_number
, phonenumbers
.PhoneNumberFormat
.E164
)
955 for number
in self
.phone_numbers
:
956 s
= phonenumbers
.format_number(number
, phonenumbers
.PhoneNumberFormat
.E164
)
961 def avatar_url(self
, size
=None):
962 url
= "https://people.ipfire.org/users/%s.jpg" % self
.uid
965 url
+= "?size=%s" % size
969 def get_avatar(self
, size
=None):
970 photo
= self
._get
_bytes
("jpegPhoto")
972 # Exit if no avatar is available
976 # Return the raw image if no size was requested
980 # Try to retrieve something from the cache
981 avatar
= self
.memcache
.get("accounts:%s:avatar:%s" % (self
.dn
, size
))
985 # Generate a new thumbnail
986 avatar
= util
.generate_thumbnail(photo
, size
, square
=True)
988 # Save to cache for 15m
989 self
.memcache
.set("accounts:%s:avatar:%s" % (self
.dn
, size
), avatar
, 900)
993 def upload_avatar(self
, avatar
):
994 self
._set
("jpegPhoto", avatar
)
997 class StopForumSpam(Object
):
998 def init(self
, uid
, email
, address
):
999 self
.uid
, self
.email
, self
.address
= uid
, email
, address
1001 @tornado.gen
.coroutine
1002 def send_request(self
, **kwargs
):
1006 arguments
.update(kwargs
)
1009 request
= tornado
.httpclient
.HTTPRequest(
1010 "https://api.stopforumspam.org/api", method
="POST")
1011 request
.body
= urllib
.parse
.urlencode(arguments
)
1014 response
= yield self
.backend
.http_client
.fetch(request
)
1016 # Decode the JSON response
1017 return json
.loads(response
.body
.decode())
1019 @tornado.gen
.coroutine
1020 def check_address(self
):
1021 response
= yield self
.send_request(ip
=self
.address
)
1024 confidence
= response
["ip"]["confidence"]
1028 logging
.debug("Confidence for %s: %s" % (self
.address
, confidence
))
1032 @tornado.gen
.coroutine
1033 def check_username(self
):
1034 response
= yield self
.send_request(username
=self
.uid
)
1037 confidence
= response
["username"]["confidence"]
1041 logging
.debug("Confidence for %s: %s" % (self
.uid
, confidence
))
1045 @tornado.gen
.coroutine
1046 def check_email(self
):
1047 response
= yield self
.send_request(email
=self
.email
)
1050 confidence
= response
["email"]["confidence"]
1054 logging
.debug("Confidence for %s: %s" % (self
.email
, confidence
))
1058 @tornado.gen
.coroutine
1059 def check(self
, threshold
=95):
1061 This function tries to detect if we have a spammer.
1063 To honour the privacy of our users, we only send the IP
1064 address and username and if those are on the database, we
1065 will send the email address as well.
1067 confidences
= yield [self
.check_address(), self
.check_username()]
1069 if any((c
< threshold
for c
in confidences
)):
1070 confidences
+= yield [self
.check_email()]
1072 # Build a score based on the lowest confidence
1073 return 100 - min(confidences
)
1077 class Groups(Object
):
1079 def search_base(self
):
1080 return "ou=Group,%s" % self
.backend
.accounts
.search_base
1083 class Group(Object
):
1084 def init(self
, gid
):
1088 if self
.description
:
1089 return "<%s %s (%s)>" % (
1090 self
.__class
__.__name
__,
1095 return "<%s %s>" % (self
.__class
__.__name
__, self
.gid
)
1098 return self
.description
or self
.gid
1100 def __eq__(self
, other
):
1101 if isinstance(other
, self
.__class
__):
1102 return self
.gid
== other
.gid
1104 def __lt__(self
, other
):
1105 if isinstance(other
, self
.__class
__):
1106 return (self
.description
or self
.gid
) < (other
.description
or other
.gid
)
1109 def attributes(self
):
1110 attrs
= self
.memcache
.get("groups:%s:attrs" % self
.gid
)
1112 attrs
= self
._get
_attrs
()
1114 # Cache this for 5 mins
1115 self
.memcache
.set("groups:%s:attrs" % self
.gid
, attrs
, 300)
1119 def _get_attrs(self
):
1120 res
= self
.backend
.accounts
._query
(
1121 "(&(|(objectClass=posixGroup)(objectClass=groupOfNames))(cn=%s))" % self
.gid
,
1122 search_base
=self
.backend
.groups
.search_base
, limit
=1)
1124 for dn
, attrs
in res
:
1128 def description(self
):
1130 description
= self
.attributes
["description"][0]
1134 return description
.decode()
1137 if __name__
== "__main__":