]>
git.ipfire.org Git - ipfire.org.git/blob - src/backend/accounts.py
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
=["moderators@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 return self
.backend
.groups
._get
_groups
("(| \
685 (&(objectClass=groupOfNames)(member=%s)) \
686 (&(objectClass=posixGroup)(memberUid=%s)) \
687 )" % (self
.dn
, self
.uid
))
689 def is_member_of_group(self
, gid
):
691 Returns True if this account is a member of this group
693 return gid
in (g
.gid
for g
in self
.groups
)
695 # Created/Modified at
698 def created_at(self
):
699 return self
._get
_timestamp
("createTimestamp")
702 def modified_at(self
):
703 return self
._get
_timestamp
("modifyTimestamp")
712 address
+= self
.street
.splitlines()
714 if self
.postal_code
and self
.city
:
715 if self
.country_code
in ("AT", "DE"):
716 address
.append("%s %s" % (self
.postal_code
, self
.city
))
718 address
.append("%s, %s" % (self
.city
, self
.postal_code
))
720 address
.append(self
.city
or self
.postal_code
)
722 if self
.country_name
:
723 address
.append(self
.country_name
)
727 def get_street(self
):
728 return self
._get
_string
("street") or self
._get
_string
("homePostalAddress")
730 def set_street(self
, street
):
731 self
._set
_string
("street", street
)
733 street
= property(get_street
, set_street
)
736 return self
._get
_string
("l") or ""
738 def set_city(self
, city
):
739 self
._set
_string
("l", city
)
741 city
= property(get_city
, set_city
)
743 def get_postal_code(self
):
744 return self
._get
_string
("postalCode") or ""
746 def set_postal_code(self
, postal_code
):
747 self
._set
_string
("postalCode", postal_code
)
749 postal_code
= property(get_postal_code
, set_postal_code
)
751 # XXX This should be c
752 def get_country_code(self
):
753 return self
._get
_string
("st")
755 def set_country_code(self
, country_code
):
756 self
._set
_string
("st", country_code
)
758 country_code
= property(get_country_code
, set_country_code
)
761 def country_name(self
):
762 if self
.country_code
:
763 return countries
.get_name(self
.country_code
)
767 return self
._get
_string
("mail")
769 # Mail Routing Address
771 def get_mail_routing_address(self
):
772 return self
._get
_string
("mailRoutingAddress", None)
774 def set_mail_routing_address(self
, address
):
775 self
._set
_string
("mailRoutingAddress", address
or None)
777 mail_routing_address
= property(get_mail_routing_address
, set_mail_routing_address
)
781 if "sipUser" in self
.classes
:
782 return self
._get
_string
("sipAuthenticationUser")
784 if "sipRoutingObject" in self
.classes
:
785 return self
._get
_string
("sipLocalAddress")
788 def sip_password(self
):
789 return self
._get
_string
("sipPassword")
792 def _generate_sip_password():
793 return util
.random_string(8)
797 return "%s@ipfire.org" % self
.sip_id
799 def uses_sip_forwarding(self
):
800 if self
.sip_routing_address
:
807 def get_sip_routing_address(self
):
808 if "sipRoutingObject" in self
.classes
:
809 return self
._get
_string
("sipRoutingAddress")
811 def set_sip_routing_address(self
, address
):
815 # Don't do anything if nothing has changed
816 if self
.get_sip_routing_address() == address
:
820 # This is no longer a SIP user any more
823 (ldap
.MOD_DELETE
, "objectClass", b
"sipUser"),
824 (ldap
.MOD_DELETE
, "sipAuthenticationUser", None),
825 (ldap
.MOD_DELETE
, "sipPassword", None),
827 except ldap
.NO_SUCH_ATTRIBUTE
:
830 # Set new routing object
833 (ldap
.MOD_ADD
, "objectClass", b
"sipRoutingObject"),
834 (ldap
.MOD_ADD
, "sipLocalAddress", self
.sip_id
.encode()),
835 (ldap
.MOD_ADD
, "sipRoutingAddress", address
.encode()),
838 # If this is a change, we cannot add this again
839 except ldap
.TYPE_OR_VALUE_EXISTS
:
840 self
._set
_string
("sipRoutingAddress", address
)
844 (ldap
.MOD_DELETE
, "objectClass", b
"sipRoutingObject"),
845 (ldap
.MOD_DELETE
, "sipLocalAddress", None),
846 (ldap
.MOD_DELETE
, "sipRoutingAddress", None),
848 except ldap
.NO_SUCH_ATTRIBUTE
:
852 (ldap
.MOD_ADD
, "objectClass", b
"sipUser"),
853 (ldap
.MOD_ADD
, "sipAuthenticationUser", self
.sip_id
.encode()),
854 (ldap
.MOD_ADD
, "sipPassword", self
._generate
_sip
_password
().encode()),
857 # XXX Cache is invalid here
859 sip_routing_address
= property(get_sip_routing_address
, set_sip_routing_address
)
862 def sip_registrations(self
):
863 sip_registrations
= []
865 for reg
in self
.backend
.talk
.freeswitch
.get_sip_registrations(self
.sip_url
):
868 sip_registrations
.append(reg
)
870 return sip_registrations
873 def sip_channels(self
):
874 return self
.backend
.talk
.freeswitch
.get_sip_channels(self
)
876 def get_cdr(self
, date
=None, limit
=None):
877 return self
.backend
.talk
.freeswitch
.get_cdr_by_account(self
, date
=date
, limit
=limit
)
882 def phone_number(self
):
884 Returns the IPFire phone number
887 return phonenumbers
.parse("+4923636035%s" % self
.sip_id
)
890 def fax_number(self
):
892 return phonenumbers
.parse("+49236360359%s" % self
.sip_id
)
894 def get_phone_numbers(self
):
897 for field
in ("telephoneNumber", "homePhone", "mobile"):
898 for number
in self
._get
_phone
_numbers
(field
):
903 def set_phone_numbers(self
, phone_numbers
):
904 # Sort phone numbers by landline and mobile
905 _landline_numbers
= []
908 for number
in phone_numbers
:
910 number
= phonenumbers
.parse(number
, None)
911 except phonenumbers
.phonenumberutil
.NumberParseException
:
914 # Convert to string (in E.164 format)
915 s
= phonenumbers
.format_number(number
, phonenumbers
.PhoneNumberFormat
.E164
)
917 # Separate mobile numbers
918 if phonenumbers
.number_type(number
) == phonenumbers
.PhoneNumberType
.MOBILE
:
919 _mobile_numbers
.append(s
)
921 _landline_numbers
.append(s
)
924 self
._set
_strings
("telephoneNumber", _landline_numbers
)
925 self
._set
_strings
("mobile", _mobile_numbers
)
927 phone_numbers
= property(get_phone_numbers
, set_phone_numbers
)
930 def _all_telephone_numbers(self
):
931 ret
= [ self
.sip_id
, ]
933 if self
.phone_number
:
934 s
= phonenumbers
.format_number(self
.phone_number
, phonenumbers
.PhoneNumberFormat
.E164
)
937 for number
in self
.phone_numbers
:
938 s
= phonenumbers
.format_number(number
, phonenumbers
.PhoneNumberFormat
.E164
)
943 def has_avatar(self
):
944 has_avatar
= self
.memcache
.get("accounts:%s:has-avatar" % self
.uid
)
945 if has_avatar
is None:
946 has_avatar
= True if self
.get_avatar() else False
948 # Cache avatar status for up to 24 hours
949 self
.memcache
.set("accounts:%s:has-avatar" % self
.uid
, has_avatar
, 3600 * 24)
953 def avatar_url(self
, size
=None):
954 url
= "https://people.ipfire.org/users/%s.jpg" % self
.uid
957 url
+= "?size=%s" % size
961 def get_avatar(self
, size
=None):
962 photo
= self
._get
_bytes
("jpegPhoto")
964 # Exit if no avatar is available
968 # Return the raw image if no size was requested
972 # Try to retrieve something from the cache
973 avatar
= self
.memcache
.get("accounts:%s:avatar:%s" % (self
.dn
, size
))
977 # Generate a new thumbnail
978 avatar
= util
.generate_thumbnail(photo
, size
, square
=True)
980 # Save to cache for 15m
981 self
.memcache
.set("accounts:%s:avatar:%s" % (self
.dn
, size
), avatar
, 900)
985 def upload_avatar(self
, avatar
):
986 self
._set
("jpegPhoto", avatar
)
988 # Delete cached avatar status
989 self
.memcache
.delete("accounts:%s:has-avatar" % self
.uid
)
992 class StopForumSpam(Object
):
993 def init(self
, uid
, email
, address
):
994 self
.uid
, self
.email
, self
.address
= uid
, email
, address
996 @tornado.gen
.coroutine
997 def send_request(self
, **kwargs
):
1001 arguments
.update(kwargs
)
1004 request
= tornado
.httpclient
.HTTPRequest(
1005 "https://api.stopforumspam.org/api", method
="POST")
1006 request
.body
= urllib
.parse
.urlencode(arguments
)
1009 response
= yield self
.backend
.http_client
.fetch(request
)
1011 # Decode the JSON response
1012 return json
.loads(response
.body
.decode())
1014 @tornado.gen
.coroutine
1015 def check_address(self
):
1016 response
= yield self
.send_request(ip
=self
.address
)
1019 confidence
= response
["ip"]["confidence"]
1023 logging
.debug("Confidence for %s: %s" % (self
.address
, confidence
))
1027 @tornado.gen
.coroutine
1028 def check_username(self
):
1029 response
= yield self
.send_request(username
=self
.uid
)
1032 confidence
= response
["username"]["confidence"]
1036 logging
.debug("Confidence for %s: %s" % (self
.uid
, confidence
))
1040 @tornado.gen
.coroutine
1041 def check_email(self
):
1042 response
= yield self
.send_request(email
=self
.email
)
1045 confidence
= response
["email"]["confidence"]
1049 logging
.debug("Confidence for %s: %s" % (self
.email
, confidence
))
1053 @tornado.gen
.coroutine
1054 def check(self
, threshold
=95):
1056 This function tries to detect if we have a spammer.
1058 To honour the privacy of our users, we only send the IP
1059 address and username and if those are on the database, we
1060 will send the email address as well.
1062 confidences
= yield [self
.check_address(), self
.check_username()]
1064 if any((c
< threshold
for c
in confidences
)):
1065 confidences
+= yield [self
.check_email()]
1067 # Build a score based on the lowest confidence
1068 return 100 - min(confidences
)
1071 class Groups(Object
):
1073 "cn=LDAP Read Only,ou=Group,dc=ipfire,dc=org",
1074 "cn=LDAP Read Write,ou=Group,dc=ipfire,dc=org",
1078 def search_base(self
):
1079 return "ou=Group,%s" % self
.backend
.accounts
.search_base
1081 def _query(self
, *args
, **kwargs
):
1083 "search_base" : self
.backend
.groups
.search_base
,
1086 return self
.backend
.accounts
._query
(*args
, **kwargs
)
1089 groups
= self
.get_all()
1093 def _get_groups(self
, query
, **kwargs
):
1094 res
= self
._query
(query
, **kwargs
)
1097 for dn
, attrs
in res
:
1098 # Skip any hidden groups
1099 if dn
in self
.hidden_groups
:
1102 g
= Group(self
.backend
, dn
, attrs
)
1105 return sorted(groups
)
1108 return self
._get
_groups
(
1109 "(|(objectClass=posixGroup)(objectClass=groupOfNames))",
1113 class Group(Object
):
1114 def init(self
, dn
, attrs
=None):
1117 self
.attributes
= attrs
or {}
1120 if self
.description
:
1121 return "<%s %s (%s)>" % (
1122 self
.__class
__.__name
__,
1127 return "<%s %s>" % (self
.__class
__.__name
__, self
.gid
)
1130 return self
.description
or self
.gid
1132 def __eq__(self
, other
):
1133 if isinstance(other
, self
.__class
__):
1134 return self
.gid
== other
.gid
1136 def __lt__(self
, other
):
1137 if isinstance(other
, self
.__class
__):
1138 return (self
.description
or self
.gid
) < (other
.description
or other
.gid
)
1143 gid
= self
.attributes
["cn"][0]
1150 def description(self
):
1152 description
= self
.attributes
["description"][0]
1156 return description
.decode()
1159 if __name__
== "__main__":