]>
git.ipfire.org Git - ipfire.org.git/blob - src/backend/accounts.py
63c99a6dd6e65bc1c47193a9e2902850cf0dcb19
16 import tornado
.httpclient
21 from . import countries
23 from .decorators
import *
24 from .misc
import Object
26 # Set the client keytab name
27 os
.environ
["KRB5_CLIENT_KTNAME"] = "/etc/ipfire.org/ldap.keytab"
29 class Accounts(Object
):
31 self
.search_base
= self
.settings
.get("ldap_search_base")
34 # Only return developers (group with ID 1000)
35 accounts
= self
._search
("(&(objectClass=posixAccount)(gidNumber=1000))")
37 return iter(sorted(accounts
))
41 # Connect to LDAP server
42 ldap_uri
= self
.settings
.get("ldap_uri")
44 logging
.debug("Connecting to LDAP server: %s" % ldap_uri
)
46 # Connect to the LDAP server
47 return ldap
.ldapobject
.ReconnectLDAPObject(ldap_uri
,
48 retry_max
=10, retry_delay
=3)
50 def _authenticate(self
):
51 # Authenticate against LDAP server using Kerberos
52 self
.ldap
.sasl_gssapi_bind_s()
55 logging
.info("Testing LDAP connection...")
59 logging
.info("Successfully authenticated as %s" % self
.ldap
.whoami_s())
61 def _query(self
, query
, attrlist
=None, limit
=0, search_base
=None):
62 logging
.debug("Performing LDAP query (%s): %s" \
63 % (search_base
or self
.search_base
, query
))
67 results
= self
.ldap
.search_ext_s(search_base
or self
.search_base
,
68 ldap
.SCOPE_SUBTREE
, query
, attrlist
=attrlist
, sizelimit
=limit
)
70 # Log time it took to perform the query
71 logging
.debug("Query took %.2fms" % ((time
.time() - t
) * 1000.0))
75 def _search(self
, query
, attrlist
=None, limit
=0):
77 for dn
, attrs
in self
._query
(query
, attrlist
=["dn"], limit
=limit
):
78 account
= self
.get_by_dn(dn
)
79 accounts
.append(account
)
83 def _get_attrs(self
, dn
):
85 Fetches all attributes for the given distinguished name
87 results
= self
._query
("(objectClass=*)", search_base
=dn
, limit
=1,
88 attrlist
=("*", "createTimestamp", "modifyTimestamp"))
90 for dn
, attrs
in results
:
93 def get_by_dn(self
, dn
):
94 attrs
= self
.memcache
.get("accounts:%s:attrs" % dn
)
96 attrs
= self
._get
_attrs
(dn
)
99 # Cache all attributes for 5 min
100 self
.memcache
.set("accounts:%s:attrs" % dn
, attrs
, 300)
102 return Account(self
.backend
, dn
, attrs
)
104 def get_created_after(self
, ts
):
105 t
= ts
.strftime("%Y%m%d%H%M%SZ")
107 return self
._search
("(&(objectClass=person)(createTimestamp>=%s))" % t
)
109 def search(self
, query
):
110 # Search for exact matches
111 accounts
= self
._search
(
112 "(&(objectClass=person)(|(uid=%s)(mail=%s)(displayName=%s)(sipAuthenticationUser=%s)(telephoneNumber=%s)(homePhone=%s)(mobile=%s)))" \
113 % (query
, query
, query
, query
, query
, query
, query
))
115 # Find accounts by name
117 for account
in self
._search
("(&(objectClass=person)(|(cn=*%s*)(uid=*%s*)(displayName=*%s*)))" % (query
, query
, query
)):
118 if not account
in accounts
:
119 accounts
.append(account
)
121 return sorted(accounts
)
123 def _search_one(self
, query
):
124 results
= self
._search
(query
, limit
=1)
126 for result
in results
:
129 def uid_is_valid(self
, uid
):
130 # UID must be at least four characters
134 # https://unix.stackexchange.com/questions/157426/what-is-the-regex-to-validate-linux-users
135 m
= re
.match(r
"^[a-z_]([a-z0-9_-]{0,31}|[a-z0-9_-]{0,30}\$)$", uid
)
141 def uid_exists(self
, uid
):
142 if self
.get_by_uid(uid
):
145 res
= self
.db
.get("SELECT 1 FROM account_activations \
146 WHERE uid = %s AND expires_at > NOW()", uid
)
151 # Account with uid does not exist, yet
154 def get_by_uid(self
, uid
):
155 return self
._search
_one
("(&(objectClass=person)(uid=%s))" % uid
)
157 def get_by_mail(self
, mail
):
158 return self
._search
_one
("(&(objectClass=inetOrgPerson)(mail=%s))" % mail
)
160 def find_account(self
, s
):
161 account
= self
.get_by_uid(s
)
165 return self
.get_by_mail(s
)
167 def get_by_sip_id(self
, sip_id
):
171 return self
._search
_one
(
172 "(|(&(objectClass=sipUser)(sipAuthenticationUser=%s))(&(objectClass=sipRoutingObject)(sipLocalAddress=%s)))" \
175 def get_by_phone_number(self
, number
):
179 return self
._search
_one
(
180 "(&(objectClass=inetOrgPerson)(|(sipAuthenticationUser=%s)(telephoneNumber=%s)(homePhone=%s)(mobile=%s)))" \
181 % (number
, number
, number
, number
))
183 @tornado.gen
.coroutine
184 def check_spam(self
, uid
, email
, address
):
185 sfs
= StopForumSpam(self
.backend
, uid
, email
, address
)
188 score
= yield sfs
.check()
192 def auth(self
, username
, password
):
194 account
= self
.backend
.accounts
.find_account(username
)
197 if account
and account
.check_password(password
):
202 def register(self
, uid
, email
, first_name
, last_name
, country_code
=None):
203 # Convert all uids to lowercase
206 # Check if UID is valid
207 if not self
.uid_is_valid(uid
):
208 raise ValueError("UID is invalid: %s" % uid
)
210 # Check if UID is unique
211 if self
.uid_exists(uid
):
212 raise ValueError("UID exists: %s" % uid
)
214 # Generate a random activation code
215 activation_code
= util
.random_string(36)
217 # Create an entry in our database until the user
218 # has activated the account
219 self
.db
.execute("INSERT INTO account_activations(uid, activation_code, \
220 email, first_name, last_name, country_code) VALUES(%s, %s, %s, %s, %s, %s)",
221 uid
, activation_code
, email
, first_name
, last_name
, country_code
)
223 # Send an account activation email
224 self
.backend
.messages
.send_template("auth/messages/register",
225 recipients
=[email
], priority
=100, uid
=uid
,
226 activation_code
=activation_code
, email
=email
,
227 first_name
=first_name
, last_name
=last_name
)
229 def activate(self
, uid
, activation_code
):
230 res
= self
.db
.get("DELETE FROM account_activations \
231 WHERE uid = %s AND activation_code = %s AND expires_at > NOW() \
232 RETURNING *", uid
, activation_code
)
234 # Return nothing when account was not found
238 # Return the account if it has already been created
239 account
= self
.get_by_uid(uid
)
243 # Create a new account on the LDAP database
244 account
= self
.create(uid
, res
.email
,
245 first_name
=res
.first_name
, last_name
=res
.last_name
,
246 country_code
=res
.country_code
)
248 # Send email about account registration
249 self
.backend
.messages
.send_template("people/messages/new-account",
250 recipients
=["moderators@ipfire.org"], account
=account
)
254 def create(self
, uid
, email
, first_name
, last_name
, country_code
=None):
255 cn
= "%s %s" % (first_name
, last_name
)
259 "objectClass" : [b
"top", b
"person", b
"inetOrgPerson"],
260 "mail" : email
.encode(),
264 "sn" : last_name
.encode(),
265 "givenName" : first_name
.encode(),
268 logging
.info("Creating new account: %s: %s" % (uid
, account
))
271 dn
= "uid=%s,ou=People,dc=ipfire,dc=org" % uid
273 # Create account on LDAP
274 self
.accounts
._authenticate
()
275 self
.ldap
.add_s(dn
, ldap
.modlist
.addModlist(account
))
278 account
= self
.get_by_dn(dn
)
280 # Optionally set country code
282 account
.country_code
= country_code
289 def create_session(self
, account
, host
):
290 session_id
= util
.random_string(64)
292 res
= self
.db
.get("INSERT INTO sessions(host, uid, session_id) VALUES(%s, %s, %s) \
293 RETURNING session_id, time_expires", host
, account
.uid
, session_id
)
295 # Session could not be created
299 logging
.info("Created session %s for %s which expires %s" \
300 % (res
.session_id
, account
, res
.time_expires
))
301 return res
.session_id
, res
.time_expires
303 def destroy_session(self
, session_id
, host
):
304 logging
.info("Destroying session %s" % session_id
)
306 self
.db
.execute("DELETE FROM sessions \
307 WHERE session_id = %s AND host = %s", session_id
, host
)
309 def get_by_session(self
, session_id
, host
):
310 logging
.debug("Looking up session %s" % session_id
)
312 res
= self
.db
.get("SELECT uid FROM sessions WHERE session_id = %s \
313 AND host = %s AND NOW() BETWEEN time_created AND time_expires",
316 # Session does not exist or has expired
320 # Update the session expiration time
321 self
.db
.execute("UPDATE sessions SET time_expires = NOW() + INTERVAL '14 days' \
322 WHERE session_id = %s AND host = %s", session_id
, host
)
324 return self
.get_by_uid(res
.uid
)
327 # Cleanup expired sessions
328 self
.db
.execute("DELETE FROM sessions WHERE time_expires <= NOW()")
330 # Cleanup expired account activations
331 self
.db
.execute("DELETE FROM account_activations WHERE expires_at <= NOW()")
335 def decode_discourse_payload(self
, payload
, signature
):
337 calculated_signature
= self
.sign_discourse_payload(payload
)
339 if not hmac
.compare_digest(signature
, calculated_signature
):
340 raise ValueError("Invalid signature: %s" % signature
)
342 # Decode the query string
343 qs
= base64
.b64decode(payload
).decode()
345 # Parse the query string
347 for key
, val
in urllib
.parse
.parse_qsl(qs
):
352 def encode_discourse_payload(self
, **args
):
353 # Encode the arguments into an URL-formatted string
354 qs
= urllib
.parse
.urlencode(args
).encode()
357 return base64
.b64encode(qs
).decode()
359 def sign_discourse_payload(self
, payload
, secret
=None):
361 secret
= self
.settings
.get("discourse_sso_secret")
363 # Calculate a HMAC using SHA256
364 h
= hmac
.new(secret
.encode(),
365 msg
=payload
.encode(), digestmod
="sha256")
370 class Account(Object
):
371 def __init__(self
, backend
, dn
, attrs
=None):
372 Object
.__init
__(self
, backend
)
375 self
.attributes
= attrs
or {}
384 return "<%s %s>" % (self
.__class
__.__name
__, self
.dn
)
386 def __eq__(self
, other
):
387 if isinstance(other
, self
.__class
__):
388 return self
.dn
== other
.dn
390 def __lt__(self
, other
):
391 if isinstance(other
, self
.__class
__):
392 return self
.name
< other
.name
396 return self
.accounts
.ldap
398 def _exists(self
, key
):
407 for value
in self
.attributes
.get(key
, []):
410 def _get_bytes(self
, key
, default
=None):
411 for value
in self
._get
(key
):
416 def _get_strings(self
, key
):
417 for value
in self
._get
(key
):
420 def _get_string(self
, key
, default
=None):
421 for value
in self
._get
_strings
(key
):
426 def _get_phone_numbers(self
, key
):
427 for value
in self
._get
_strings
(key
):
428 yield phonenumbers
.parse(value
, None)
430 def _get_timestamp(self
, key
):
431 value
= self
._get
_string
(key
)
433 # Parse the timestamp value and returns a datetime object
435 return datetime
.datetime
.strptime(value
, "%Y%m%d%H%M%SZ")
437 def _modify(self
, modlist
):
438 logging
.debug("Modifying %s: %s" % (self
.dn
, modlist
))
440 # Authenticate before performing any write operations
441 self
.accounts
._authenticate
()
443 # Run modify operation
444 self
.ldap
.modify_s(self
.dn
, modlist
)
446 # Delete cached attributes
447 self
.memcache
.delete("accounts:%s:attrs" % self
.dn
)
449 def _set(self
, key
, values
):
450 current
= self
._get
(key
)
452 # Don't do anything if nothing has changed
453 if list(current
) == values
:
456 # Remove all old values and add all new ones
459 if self
._exists
(key
):
460 modlist
.append((ldap
.MOD_DELETE
, key
, None))
464 modlist
.append((ldap
.MOD_ADD
, key
, values
))
466 # Run modify operation
467 self
._modify
(modlist
)
470 self
.attributes
.update({ key
: values
})
472 def _set_bytes(self
, key
, values
):
473 return self
._set
(key
, values
)
475 def _set_strings(self
, key
, values
):
476 return self
._set
(key
, [e
.encode() for e
in values
if e
])
478 def _set_string(self
, key
, value
):
479 return self
._set
_strings
(key
, [value
,])
481 def _add(self
, key
, values
):
483 (ldap
.MOD_ADD
, key
, values
),
486 self
._modify
(modlist
)
488 def _add_strings(self
, key
, values
):
489 return self
._add
(key
, [e
.encode() for e
in values
])
491 def _add_string(self
, key
, value
):
492 return self
._add
_strings
(key
, [value
,])
494 def _delete(self
, key
, values
):
496 (ldap
.MOD_DELETE
, key
, values
),
499 self
._modify
(modlist
)
501 def _delete_strings(self
, key
, values
):
502 return self
._delete
(key
, [e
.encode() for e
in values
])
504 def _delete_string(self
, key
, value
):
505 return self
._delete
_strings
(key
, [value
,])
508 def kerberos_attributes(self
):
509 res
= self
.backend
.accounts
._query
(
510 "(&(objectClass=krbPrincipal)(krbPrincipalName=%s@IPFIRE.ORG))" % self
.uid
,
512 "krbLastSuccessfulAuth",
513 "krbLastPasswordChange",
515 "krbLoginFailedCount",
518 search_base
="cn=krb5,%s" % self
.backend
.accounts
.search_base
)
520 for dn
, attrs
in res
:
521 return { key
: attrs
[key
][0] for key
in attrs
}
527 return datetime
.datetime
.strptime(s
.decode(), "%Y%m%d%H%M%SZ")
530 def last_successful_authentication(self
):
532 s
= self
.kerberos_attributes
["krbLastSuccessfulAuth"]
536 return self
._parse
_date
(s
)
539 def last_failed_authentication(self
):
541 s
= self
.kerberos_attributes
["krbLastFailedAuth"]
545 return self
._parse
_date
(s
)
548 def failed_login_count(self
):
550 count
= self
.kerberos_attributes
["krbLoginFailedCount"].decode()
559 def passwd(self
, password
):
563 # The new password must have a score of 3 or better
564 quality
= self
.check_password_quality(password
)
565 if quality
["score"] < 3:
566 raise ValueError("Password too weak")
568 self
.accounts
._authenticate
()
569 self
.ldap
.passwd_s(self
.dn
, None, password
)
571 def check_password(self
, password
):
573 Bind to the server with given credentials and return
574 true if password is corrent and false if not.
576 Raises exceptions from the server on any other errors.
581 logging
.debug("Checking credentials for %s" % self
.dn
)
583 # Create a new LDAP connection
584 ldap_uri
= self
.backend
.settings
.get("ldap_uri")
585 conn
= ldap
.initialize(ldap_uri
)
588 conn
.simple_bind_s(self
.dn
, password
.encode("utf-8"))
589 except ldap
.INVALID_CREDENTIALS
:
590 logging
.debug("Account credentials are invalid for %s" % self
)
593 logging
.info("Successfully authenticated %s" % self
)
597 def check_password_quality(self
, password
):
599 Passwords are passed through zxcvbn to make sure
600 that they are strong enough.
602 return zxcvbn
.zxcvbn(password
, user_inputs
=(
603 self
.first_name
, self
.last_name
,
607 return self
.is_member_of_group("sudo")
610 return self
.is_member_of_group("staff")
612 def is_moderator(self
):
613 return self
.is_member_of_group("moderators")
616 return "posixAccount" in self
.classes
619 return "postfixMailUser" in self
.classes
622 return "sipUser" in self
.classes
or "sipRoutingObject" in self
.classes
624 def can_be_managed_by(self
, account
):
626 Returns True if account is allowed to manage this account
628 # Admins can manage all accounts
629 if account
.is_admin():
632 # Users can manage themselves
633 return self
== account
637 return self
._get
_strings
("objectClass")
641 return self
._get
_string
("uid")
645 return self
._get
_string
("cn")
649 def get_nickname(self
):
650 return self
._get
_string
("displayName")
652 def set_nickname(self
, nickname
):
653 self
._set
_string
("displayName", nickname
)
655 nickname
= property(get_nickname
, set_nickname
)
659 def get_first_name(self
):
660 return self
._get
_string
("givenName")
662 def set_first_name(self
, first_name
):
663 self
._set
_string
("givenName", first_name
)
666 self
._set
_string
("cn", "%s %s" % (first_name
, self
.last_name
))
668 first_name
= property(get_first_name
, set_first_name
)
672 def get_last_name(self
):
673 return self
._get
_string
("sn")
675 def set_last_name(self
, last_name
):
676 self
._set
_string
("sn", last_name
)
679 self
._set
_string
("cn", "%s %s" % (self
.first_name
, last_name
))
681 last_name
= property(get_last_name
, set_last_name
)
685 return self
.backend
.groups
._get
_groups
("(| \
686 (&(objectClass=groupOfNames)(member=%s)) \
687 (&(objectClass=posixGroup)(memberUid=%s)) \
688 )" % (self
.dn
, self
.uid
))
690 def is_member_of_group(self
, gid
):
692 Returns True if this account is a member of this group
694 return gid
in (g
.gid
for g
in self
.groups
)
696 # Created/Modified at
699 def created_at(self
):
700 return self
._get
_timestamp
("createTimestamp")
703 def modified_at(self
):
704 return self
._get
_timestamp
("modifyTimestamp")
713 address
+= self
.street
.splitlines()
715 if self
.postal_code
and self
.city
:
716 if self
.country_code
in ("AT", "DE"):
717 address
.append("%s %s" % (self
.postal_code
, self
.city
))
719 address
.append("%s, %s" % (self
.city
, self
.postal_code
))
721 address
.append(self
.city
or self
.postal_code
)
723 if self
.country_name
:
724 address
.append(self
.country_name
)
728 def get_street(self
):
729 return self
._get
_string
("street") or self
._get
_string
("homePostalAddress")
731 def set_street(self
, street
):
732 self
._set
_string
("street", street
)
734 street
= property(get_street
, set_street
)
737 return self
._get
_string
("l") or ""
739 def set_city(self
, city
):
740 self
._set
_string
("l", city
)
742 city
= property(get_city
, set_city
)
744 def get_postal_code(self
):
745 return self
._get
_string
("postalCode") or ""
747 def set_postal_code(self
, postal_code
):
748 self
._set
_string
("postalCode", postal_code
)
750 postal_code
= property(get_postal_code
, set_postal_code
)
752 # XXX This should be c
753 def get_country_code(self
):
754 return self
._get
_string
("st")
756 def set_country_code(self
, country_code
):
757 self
._set
_string
("st", country_code
)
759 country_code
= property(get_country_code
, set_country_code
)
762 def country_name(self
):
763 if self
.country_code
:
764 return countries
.get_name(self
.country_code
)
768 return self
._get
_string
("mail")
770 # Mail Routing Address
772 def get_mail_routing_address(self
):
773 return self
._get
_string
("mailRoutingAddress", None)
775 def set_mail_routing_address(self
, address
):
776 self
._set
_string
("mailRoutingAddress", address
or None)
778 mail_routing_address
= property(get_mail_routing_address
, set_mail_routing_address
)
782 if "sipUser" in self
.classes
:
783 return self
._get
_string
("sipAuthenticationUser")
785 if "sipRoutingObject" in self
.classes
:
786 return self
._get
_string
("sipLocalAddress")
789 def sip_password(self
):
790 return self
._get
_string
("sipPassword")
793 def _generate_sip_password():
794 return util
.random_string(8)
798 return "%s@ipfire.org" % self
.sip_id
800 def uses_sip_forwarding(self
):
801 if self
.sip_routing_address
:
808 def get_sip_routing_address(self
):
809 if "sipRoutingObject" in self
.classes
:
810 return self
._get
_string
("sipRoutingAddress")
812 def set_sip_routing_address(self
, address
):
816 # Don't do anything if nothing has changed
817 if self
.get_sip_routing_address() == address
:
821 # This is no longer a SIP user any more
824 (ldap
.MOD_DELETE
, "objectClass", b
"sipUser"),
825 (ldap
.MOD_DELETE
, "sipAuthenticationUser", None),
826 (ldap
.MOD_DELETE
, "sipPassword", None),
828 except ldap
.NO_SUCH_ATTRIBUTE
:
831 # Set new routing object
834 (ldap
.MOD_ADD
, "objectClass", b
"sipRoutingObject"),
835 (ldap
.MOD_ADD
, "sipLocalAddress", self
.sip_id
.encode()),
836 (ldap
.MOD_ADD
, "sipRoutingAddress", address
.encode()),
839 # If this is a change, we cannot add this again
840 except ldap
.TYPE_OR_VALUE_EXISTS
:
841 self
._set
_string
("sipRoutingAddress", address
)
845 (ldap
.MOD_DELETE
, "objectClass", b
"sipRoutingObject"),
846 (ldap
.MOD_DELETE
, "sipLocalAddress", None),
847 (ldap
.MOD_DELETE
, "sipRoutingAddress", None),
849 except ldap
.NO_SUCH_ATTRIBUTE
:
853 (ldap
.MOD_ADD
, "objectClass", b
"sipUser"),
854 (ldap
.MOD_ADD
, "sipAuthenticationUser", self
.sip_id
.encode()),
855 (ldap
.MOD_ADD
, "sipPassword", self
._generate
_sip
_password
().encode()),
858 # XXX Cache is invalid here
860 sip_routing_address
= property(get_sip_routing_address
, set_sip_routing_address
)
863 def sip_registrations(self
):
864 sip_registrations
= []
866 for reg
in self
.backend
.talk
.freeswitch
.get_sip_registrations(self
.sip_url
):
869 sip_registrations
.append(reg
)
871 return sip_registrations
874 def sip_channels(self
):
875 return self
.backend
.talk
.freeswitch
.get_sip_channels(self
)
877 def get_cdr(self
, date
=None, limit
=None):
878 return self
.backend
.talk
.freeswitch
.get_cdr_by_account(self
, date
=date
, limit
=limit
)
883 def phone_number(self
):
885 Returns the IPFire phone number
888 return phonenumbers
.parse("+4923636035%s" % self
.sip_id
)
891 def fax_number(self
):
893 return phonenumbers
.parse("+49236360359%s" % self
.sip_id
)
895 def get_phone_numbers(self
):
898 for field
in ("telephoneNumber", "homePhone", "mobile"):
899 for number
in self
._get
_phone
_numbers
(field
):
904 def set_phone_numbers(self
, phone_numbers
):
905 # Sort phone numbers by landline and mobile
906 _landline_numbers
= []
909 for number
in phone_numbers
:
911 number
= phonenumbers
.parse(number
, None)
912 except phonenumbers
.phonenumberutil
.NumberParseException
:
915 # Convert to string (in E.164 format)
916 s
= phonenumbers
.format_number(number
, phonenumbers
.PhoneNumberFormat
.E164
)
918 # Separate mobile numbers
919 if phonenumbers
.number_type(number
) == phonenumbers
.PhoneNumberType
.MOBILE
:
920 _mobile_numbers
.append(s
)
922 _landline_numbers
.append(s
)
925 self
._set
_strings
("telephoneNumber", _landline_numbers
)
926 self
._set
_strings
("mobile", _mobile_numbers
)
928 phone_numbers
= property(get_phone_numbers
, set_phone_numbers
)
931 def _all_telephone_numbers(self
):
932 ret
= [ self
.sip_id
, ]
934 if self
.phone_number
:
935 s
= phonenumbers
.format_number(self
.phone_number
, phonenumbers
.PhoneNumberFormat
.E164
)
938 for number
in self
.phone_numbers
:
939 s
= phonenumbers
.format_number(number
, phonenumbers
.PhoneNumberFormat
.E164
)
946 def get_description(self
):
947 return self
._get
_string
("description")
949 def set_description(self
, description
):
950 self
._set
_string
("description", description
)
952 description
= property(get_description
, set_description
)
956 def has_avatar(self
):
957 has_avatar
= self
.memcache
.get("accounts:%s:has-avatar" % self
.uid
)
958 if has_avatar
is None:
959 has_avatar
= True if self
.get_avatar() else False
961 # Cache avatar status for up to 24 hours
962 self
.memcache
.set("accounts:%s:has-avatar" % self
.uid
, has_avatar
, 3600 * 24)
966 def avatar_url(self
, size
=None):
967 url
= "https://people.ipfire.org/users/%s.jpg?h=%s" % (self
.uid
, self
.avatar_hash
)
970 url
+= "&size=%s" % size
974 def get_avatar(self
, size
=None):
975 photo
= self
._get
_bytes
("jpegPhoto")
977 # Exit if no avatar is available
981 # Return the raw image if no size was requested
985 # Try to retrieve something from the cache
986 avatar
= self
.memcache
.get("accounts:%s:avatar:%s" % (self
.dn
, size
))
990 # Generate a new thumbnail
991 avatar
= util
.generate_thumbnail(photo
, size
, square
=True)
993 # Save to cache for 15m
994 self
.memcache
.set("accounts:%s:avatar:%s" % (self
.dn
, size
), avatar
, 900)
999 def avatar_hash(self
):
1000 hash = self
.memcache
.get("accounts:%s:avatar-hash" % self
.dn
)
1002 h
= hashlib
.new("md5")
1003 h
.update(self
.get_avatar() or b
"")
1004 hash = h
.hexdigest()[:7]
1006 self
.memcache
.set("accounts:%s:avatar-hash" % self
.dn
, hash, 86400)
1010 def upload_avatar(self
, avatar
):
1011 self
._set
("jpegPhoto", avatar
)
1013 # Delete cached avatar status
1014 self
.memcache
.delete("accounts:%s:has-avatar" % self
.uid
)
1016 # Delete avatar hash
1017 self
.memcache
.delete("accounts:%s:avatar-hash" % self
.uid
)
1020 class StopForumSpam(Object
):
1021 def init(self
, uid
, email
, address
):
1022 self
.uid
, self
.email
, self
.address
= uid
, email
, address
1024 @tornado.gen
.coroutine
1025 def send_request(self
, **kwargs
):
1029 arguments
.update(kwargs
)
1032 request
= tornado
.httpclient
.HTTPRequest(
1033 "https://api.stopforumspam.org/api", method
="POST")
1034 request
.body
= urllib
.parse
.urlencode(arguments
)
1037 response
= yield self
.backend
.http_client
.fetch(request
)
1039 # Decode the JSON response
1040 return json
.loads(response
.body
.decode())
1042 @tornado.gen
.coroutine
1043 def check_address(self
):
1044 response
= yield self
.send_request(ip
=self
.address
)
1047 confidence
= response
["ip"]["confidence"]
1051 logging
.debug("Confidence for %s: %s" % (self
.address
, confidence
))
1055 @tornado.gen
.coroutine
1056 def check_username(self
):
1057 response
= yield self
.send_request(username
=self
.uid
)
1060 confidence
= response
["username"]["confidence"]
1064 logging
.debug("Confidence for %s: %s" % (self
.uid
, confidence
))
1068 @tornado.gen
.coroutine
1069 def check_email(self
):
1070 response
= yield self
.send_request(email
=self
.email
)
1073 confidence
= response
["email"]["confidence"]
1077 logging
.debug("Confidence for %s: %s" % (self
.email
, confidence
))
1081 @tornado.gen
.coroutine
1082 def check(self
, threshold
=95):
1084 This function tries to detect if we have a spammer.
1086 To honour the privacy of our users, we only send the IP
1087 address and username and if those are on the database, we
1088 will send the email address as well.
1090 confidences
= yield [self
.check_address(), self
.check_username()]
1092 if any((c
< threshold
for c
in confidences
)):
1093 confidences
+= yield [self
.check_email()]
1095 # Build a score based on the lowest confidence
1096 return 100 - min(confidences
)
1099 class Groups(Object
):
1101 "cn=LDAP Read Only,ou=Group,dc=ipfire,dc=org",
1102 "cn=LDAP Read Write,ou=Group,dc=ipfire,dc=org",
1104 # Everyone is a member of people
1105 "cn=people,ou=Group,dc=ipfire,dc=org",
1109 def search_base(self
):
1110 return "ou=Group,%s" % self
.backend
.accounts
.search_base
1112 def _query(self
, *args
, **kwargs
):
1114 "search_base" : self
.backend
.groups
.search_base
,
1117 return self
.backend
.accounts
._query
(*args
, **kwargs
)
1120 groups
= self
.get_all()
1124 def _get_groups(self
, query
, **kwargs
):
1125 res
= self
._query
(query
, **kwargs
)
1128 for dn
, attrs
in res
:
1129 # Skip any hidden groups
1130 if dn
in self
.hidden_groups
:
1133 g
= Group(self
.backend
, dn
, attrs
)
1136 return sorted(groups
)
1138 def _get_group(self
, query
, **kwargs
):
1143 groups
= self
._get
_groups
(query
, **kwargs
)
1148 return self
._get
_groups
(
1149 "(|(objectClass=posixGroup)(objectClass=groupOfNames))",
1152 def get_by_gid(self
, gid
):
1153 return self
._get
_group
(
1154 "(&(|(objectClass=posixGroup)(objectClass=groupOfNames))(cn=%s))" % gid
,
1158 class Group(Object
):
1159 def init(self
, dn
, attrs
=None):
1162 self
.attributes
= attrs
or {}
1165 if self
.description
:
1166 return "<%s %s (%s)>" % (
1167 self
.__class
__.__name
__,
1172 return "<%s %s>" % (self
.__class
__.__name
__, self
.gid
)
1175 return self
.description
or self
.gid
1177 def __eq__(self
, other
):
1178 if isinstance(other
, self
.__class
__):
1179 return self
.gid
== other
.gid
1181 def __lt__(self
, other
):
1182 if isinstance(other
, self
.__class
__):
1183 return (self
.description
or self
.gid
) < (other
.description
or other
.gid
)
1190 Returns the number of members in this group
1194 for attr
in ("member", "memberUid"):
1195 a
= self
.attributes
.get(attr
, None)
1202 return iter(self
.members
)
1207 gid
= self
.attributes
["cn"][0]
1214 def description(self
):
1216 description
= self
.attributes
["description"][0]
1220 return description
.decode()
1225 email
= self
.attributes
["mail"][0]
1229 return email
.decode()
1235 # Get all members by DN
1236 for dn
in self
.attributes
.get("member", []):
1237 member
= self
.backend
.accounts
.get_by_dn(dn
.decode())
1239 members
.append(member
)
1241 # Get all meembers by UID
1242 for uid
in self
.attributes
.get("memberUid", []):
1243 member
= self
.backend
.accounts
.get_by_uid(uid
.decode())
1245 members
.append(member
)
1247 return sorted(members
)
1249 if __name__
== "__main__":