]>
git.ipfire.org Git - ipfire.org.git/blob - src/backend/accounts.py
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}$", 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 # Invite newly registered users to newsletter
249 self
.backend
.messages
.send_template(
250 "newsletter/subscribe", address
="%s <%s>" % (account
, account
.email
))
252 # Send email about account registration
253 self
.backend
.messages
.send_template("people/messages/new-account",
254 recipients
=["moderators@ipfire.org"], account
=account
)
258 def create(self
, uid
, email
, first_name
, last_name
, country_code
=None):
259 cn
= "%s %s" % (first_name
, last_name
)
263 "objectClass" : [b
"top", b
"person", b
"inetOrgPerson"],
264 "mail" : email
.encode(),
268 "sn" : last_name
.encode(),
269 "givenName" : first_name
.encode(),
272 logging
.info("Creating new account: %s: %s" % (uid
, account
))
275 dn
= "uid=%s,ou=People,dc=ipfire,dc=org" % uid
277 # Create account on LDAP
278 self
.accounts
._authenticate
()
279 self
.ldap
.add_s(dn
, ldap
.modlist
.addModlist(account
))
282 account
= self
.get_by_dn(dn
)
284 # Optionally set country code
286 account
.country_code
= country_code
293 def create_session(self
, account
, host
):
294 session_id
= util
.random_string(64)
296 res
= self
.db
.get("INSERT INTO sessions(host, uid, session_id) VALUES(%s, %s, %s) \
297 RETURNING session_id, time_expires", host
, account
.uid
, session_id
)
299 # Session could not be created
303 logging
.info("Created session %s for %s which expires %s" \
304 % (res
.session_id
, account
, res
.time_expires
))
305 return res
.session_id
, res
.time_expires
307 def destroy_session(self
, session_id
, host
):
308 logging
.info("Destroying session %s" % session_id
)
310 self
.db
.execute("DELETE FROM sessions \
311 WHERE session_id = %s AND host = %s", session_id
, host
)
313 def get_by_session(self
, session_id
, host
):
314 logging
.debug("Looking up session %s" % session_id
)
316 res
= self
.db
.get("SELECT uid FROM sessions WHERE session_id = %s \
317 AND host = %s AND NOW() BETWEEN time_created AND time_expires",
320 # Session does not exist or has expired
324 # Update the session expiration time
325 self
.db
.execute("UPDATE sessions SET time_expires = NOW() + INTERVAL '14 days' \
326 WHERE session_id = %s AND host = %s", session_id
, host
)
328 return self
.get_by_uid(res
.uid
)
331 # Cleanup expired sessions
332 self
.db
.execute("DELETE FROM sessions WHERE time_expires <= NOW()")
334 # Cleanup expired account activations
335 self
.db
.execute("DELETE FROM account_activations WHERE expires_at <= NOW()")
339 def decode_discourse_payload(self
, payload
, signature
):
341 calculated_signature
= self
.sign_discourse_payload(payload
)
343 if not hmac
.compare_digest(signature
, calculated_signature
):
344 raise ValueError("Invalid signature: %s" % signature
)
346 # Decode the query string
347 qs
= base64
.b64decode(payload
).decode()
349 # Parse the query string
351 for key
, val
in urllib
.parse
.parse_qsl(qs
):
356 def encode_discourse_payload(self
, **args
):
357 # Encode the arguments into an URL-formatted string
358 qs
= urllib
.parse
.urlencode(args
).encode()
361 return base64
.b64encode(qs
).decode()
363 def sign_discourse_payload(self
, payload
, secret
=None):
365 secret
= self
.settings
.get("discourse_sso_secret")
367 # Calculate a HMAC using SHA256
368 h
= hmac
.new(secret
.encode(),
369 msg
=payload
.encode(), digestmod
="sha256")
374 class Account(Object
):
375 def __init__(self
, backend
, dn
, attrs
=None):
376 Object
.__init
__(self
, backend
)
379 self
.attributes
= attrs
or {}
388 return "<%s %s>" % (self
.__class
__.__name
__, self
.dn
)
390 def __eq__(self
, other
):
391 if isinstance(other
, self
.__class
__):
392 return self
.dn
== other
.dn
394 def __lt__(self
, other
):
395 if isinstance(other
, self
.__class
__):
396 return self
.name
< other
.name
400 return self
.accounts
.ldap
402 def _exists(self
, key
):
411 for value
in self
.attributes
.get(key
, []):
414 def _get_bytes(self
, key
, default
=None):
415 for value
in self
._get
(key
):
420 def _get_strings(self
, key
):
421 for value
in self
._get
(key
):
424 def _get_string(self
, key
, default
=None):
425 for value
in self
._get
_strings
(key
):
430 def _get_phone_numbers(self
, key
):
431 for value
in self
._get
_strings
(key
):
432 yield phonenumbers
.parse(value
, None)
434 def _get_timestamp(self
, key
):
435 value
= self
._get
_string
(key
)
437 # Parse the timestamp value and returns a datetime object
439 return datetime
.datetime
.strptime(value
, "%Y%m%d%H%M%SZ")
441 def _modify(self
, modlist
):
442 logging
.debug("Modifying %s: %s" % (self
.dn
, modlist
))
444 # Authenticate before performing any write operations
445 self
.accounts
._authenticate
()
447 # Run modify operation
448 self
.ldap
.modify_s(self
.dn
, modlist
)
450 # Delete cached attributes
451 self
.memcache
.delete("accounts:%s:attrs" % self
.dn
)
453 def _set(self
, key
, values
):
454 current
= self
._get
(key
)
456 # Don't do anything if nothing has changed
457 if list(current
) == values
:
460 # Remove all old values and add all new ones
463 if self
._exists
(key
):
464 modlist
.append((ldap
.MOD_DELETE
, key
, None))
468 modlist
.append((ldap
.MOD_ADD
, key
, values
))
470 # Run modify operation
471 self
._modify
(modlist
)
474 self
.attributes
.update({ key
: values
})
476 def _set_bytes(self
, key
, values
):
477 return self
._set
(key
, values
)
479 def _set_strings(self
, key
, values
):
480 return self
._set
(key
, [e
.encode() for e
in values
if e
])
482 def _set_string(self
, key
, value
):
483 return self
._set
_strings
(key
, [value
,])
485 def _add(self
, key
, values
):
487 (ldap
.MOD_ADD
, key
, values
),
490 self
._modify
(modlist
)
492 def _add_strings(self
, key
, values
):
493 return self
._add
(key
, [e
.encode() for e
in values
])
495 def _add_string(self
, key
, value
):
496 return self
._add
_strings
(key
, [value
,])
498 def _delete(self
, key
, values
):
500 (ldap
.MOD_DELETE
, key
, values
),
503 self
._modify
(modlist
)
505 def _delete_strings(self
, key
, values
):
506 return self
._delete
(key
, [e
.encode() for e
in values
])
508 def _delete_string(self
, key
, value
):
509 return self
._delete
_strings
(key
, [value
,])
512 def kerberos_attributes(self
):
513 res
= self
.backend
.accounts
._query
(
514 "(&(objectClass=krbPrincipal)(krbPrincipalName=%s@IPFIRE.ORG))" % self
.uid
,
516 "krbLastSuccessfulAuth",
517 "krbLastPasswordChange",
519 "krbLoginFailedCount",
522 search_base
="cn=krb5,%s" % self
.backend
.accounts
.search_base
)
524 for dn
, attrs
in res
:
525 return { key
: attrs
[key
][0] for key
in attrs
}
531 return datetime
.datetime
.strptime(s
.decode(), "%Y%m%d%H%M%SZ")
534 def last_successful_authentication(self
):
536 s
= self
.kerberos_attributes
["krbLastSuccessfulAuth"]
540 return self
._parse
_date
(s
)
543 def last_failed_authentication(self
):
545 s
= self
.kerberos_attributes
["krbLastFailedAuth"]
549 return self
._parse
_date
(s
)
552 def failed_login_count(self
):
554 count
= self
.kerberos_attributes
["krbLoginFailedCount"].decode()
563 def passwd(self
, password
):
567 # The new password must have a score of 3 or better
568 quality
= self
.check_password_quality(password
)
569 if quality
["score"] < 3:
570 raise ValueError("Password too weak")
572 self
.accounts
._authenticate
()
573 self
.ldap
.passwd_s(self
.dn
, None, password
)
575 def check_password(self
, password
):
577 Bind to the server with given credentials and return
578 true if password is corrent and false if not.
580 Raises exceptions from the server on any other errors.
585 logging
.debug("Checking credentials for %s" % self
.dn
)
587 # Create a new LDAP connection
588 ldap_uri
= self
.backend
.settings
.get("ldap_uri")
589 conn
= ldap
.initialize(ldap_uri
)
592 conn
.simple_bind_s(self
.dn
, password
.encode("utf-8"))
593 except ldap
.INVALID_CREDENTIALS
:
594 logging
.debug("Account credentials are invalid for %s" % self
)
597 logging
.info("Successfully authenticated %s" % self
)
601 def check_password_quality(self
, password
):
603 Passwords are passed through zxcvbn to make sure
604 that they are strong enough.
606 return zxcvbn
.zxcvbn(password
, user_inputs
=(
607 self
.first_name
, self
.last_name
,
610 def request_password_reset(self
, address
=None):
611 reset_code
= util
.random_string(64)
613 self
.db
.execute("INSERT INTO account_password_resets(uid, reset_code, address) \
614 VALUES(%s, %s, %s)", self
.uid
, reset_code
, address
)
616 # Send a password reset email
617 self
.backend
.messages
.send_template("auth/messages/password-reset",
618 recipients
=[self
.email
], priority
=100, account
=self
, reset_code
=reset_code
)
620 def reset_password(self
, reset_code
, new_password
):
621 # Delete the reset token
622 res
= self
.db
.query("DELETE FROM account_password_resets \
623 WHERE uid = %s AND reset_code = %s AND expires_at >= NOW() \
624 RETURNING *", self
.uid
, reset_code
)
626 # The reset code was invalid
628 raise ValueError("Invalid password reset token for %s: %s" % (self
, reset_code
))
630 # Perform password change
631 return self
.passwd(new_password
)
634 return self
.is_member_of_group("sudo")
637 return self
.is_member_of_group("staff")
639 def is_moderator(self
):
640 return self
.is_member_of_group("moderators")
643 return "posixAccount" in self
.classes
646 return "postfixMailUser" in self
.classes
649 return "sipUser" in self
.classes
or "sipRoutingObject" in self
.classes
651 def can_be_managed_by(self
, account
):
653 Returns True if account is allowed to manage this account
655 # Admins can manage all accounts
656 if account
.is_admin():
659 # Users can manage themselves
660 return self
== account
664 return self
._get
_strings
("objectClass")
668 return self
._get
_string
("uid")
672 return self
._get
_string
("cn")
676 def get_nickname(self
):
677 return self
._get
_string
("displayName")
679 def set_nickname(self
, nickname
):
680 self
._set
_string
("displayName", nickname
)
682 nickname
= property(get_nickname
, set_nickname
)
686 def get_first_name(self
):
687 return self
._get
_string
("givenName")
689 def set_first_name(self
, first_name
):
690 self
._set
_string
("givenName", first_name
)
693 self
._set
_string
("cn", "%s %s" % (first_name
, self
.last_name
))
695 first_name
= property(get_first_name
, set_first_name
)
699 def get_last_name(self
):
700 return self
._get
_string
("sn")
702 def set_last_name(self
, last_name
):
703 self
._set
_string
("sn", last_name
)
706 self
._set
_string
("cn", "%s %s" % (self
.first_name
, last_name
))
708 last_name
= property(get_last_name
, set_last_name
)
712 return self
.backend
.groups
._get
_groups
("(| \
713 (&(objectClass=groupOfNames)(member=%s)) \
714 (&(objectClass=posixGroup)(memberUid=%s)) \
715 )" % (self
.dn
, self
.uid
))
717 def is_member_of_group(self
, gid
):
719 Returns True if this account is a member of this group
721 return gid
in (g
.gid
for g
in self
.groups
)
723 # Created/Modified at
726 def created_at(self
):
727 return self
._get
_timestamp
("createTimestamp")
730 def modified_at(self
):
731 return self
._get
_timestamp
("modifyTimestamp")
740 address
+= self
.street
.splitlines()
742 if self
.postal_code
and self
.city
:
743 if self
.country_code
in ("AT", "DE"):
744 address
.append("%s %s" % (self
.postal_code
, self
.city
))
746 address
.append("%s, %s" % (self
.city
, self
.postal_code
))
748 address
.append(self
.city
or self
.postal_code
)
750 if self
.country_name
:
751 address
.append(self
.country_name
)
755 def get_street(self
):
756 return self
._get
_string
("street") or self
._get
_string
("homePostalAddress")
758 def set_street(self
, street
):
759 self
._set
_string
("street", street
)
761 street
= property(get_street
, set_street
)
764 return self
._get
_string
("l") or ""
766 def set_city(self
, city
):
767 self
._set
_string
("l", city
)
769 city
= property(get_city
, set_city
)
771 def get_postal_code(self
):
772 return self
._get
_string
("postalCode") or ""
774 def set_postal_code(self
, postal_code
):
775 self
._set
_string
("postalCode", postal_code
)
777 postal_code
= property(get_postal_code
, set_postal_code
)
779 # XXX This should be c
780 def get_country_code(self
):
781 return self
._get
_string
("st")
783 def set_country_code(self
, country_code
):
784 self
._set
_string
("st", country_code
)
786 country_code
= property(get_country_code
, set_country_code
)
789 def country_name(self
):
790 if self
.country_code
:
791 return countries
.get_name(self
.country_code
)
795 return self
._get
_string
("mail")
797 # Mail Routing Address
799 def get_mail_routing_address(self
):
800 return self
._get
_string
("mailRoutingAddress", None)
802 def set_mail_routing_address(self
, address
):
803 self
._set
_string
("mailRoutingAddress", address
or None)
805 mail_routing_address
= property(get_mail_routing_address
, set_mail_routing_address
)
809 if "sipUser" in self
.classes
:
810 return self
._get
_string
("sipAuthenticationUser")
812 if "sipRoutingObject" in self
.classes
:
813 return self
._get
_string
("sipLocalAddress")
816 def sip_password(self
):
817 return self
._get
_string
("sipPassword")
820 def _generate_sip_password():
821 return util
.random_string(8)
825 return "%s@ipfire.org" % self
.sip_id
827 def uses_sip_forwarding(self
):
828 if self
.sip_routing_address
:
835 def get_sip_routing_address(self
):
836 if "sipRoutingObject" in self
.classes
:
837 return self
._get
_string
("sipRoutingAddress")
839 def set_sip_routing_address(self
, address
):
843 # Don't do anything if nothing has changed
844 if self
.get_sip_routing_address() == address
:
848 # This is no longer a SIP user any more
851 (ldap
.MOD_DELETE
, "objectClass", b
"sipUser"),
852 (ldap
.MOD_DELETE
, "sipAuthenticationUser", None),
853 (ldap
.MOD_DELETE
, "sipPassword", None),
855 except ldap
.NO_SUCH_ATTRIBUTE
:
858 # Set new routing object
861 (ldap
.MOD_ADD
, "objectClass", b
"sipRoutingObject"),
862 (ldap
.MOD_ADD
, "sipLocalAddress", self
.sip_id
.encode()),
863 (ldap
.MOD_ADD
, "sipRoutingAddress", address
.encode()),
866 # If this is a change, we cannot add this again
867 except ldap
.TYPE_OR_VALUE_EXISTS
:
868 self
._set
_string
("sipRoutingAddress", address
)
872 (ldap
.MOD_DELETE
, "objectClass", b
"sipRoutingObject"),
873 (ldap
.MOD_DELETE
, "sipLocalAddress", None),
874 (ldap
.MOD_DELETE
, "sipRoutingAddress", None),
876 except ldap
.NO_SUCH_ATTRIBUTE
:
880 (ldap
.MOD_ADD
, "objectClass", b
"sipUser"),
881 (ldap
.MOD_ADD
, "sipAuthenticationUser", self
.sip_id
.encode()),
882 (ldap
.MOD_ADD
, "sipPassword", self
._generate
_sip
_password
().encode()),
885 # XXX Cache is invalid here
887 sip_routing_address
= property(get_sip_routing_address
, set_sip_routing_address
)
890 def sip_registrations(self
):
891 sip_registrations
= []
893 for reg
in self
.backend
.talk
.freeswitch
.get_sip_registrations(self
.sip_url
):
896 sip_registrations
.append(reg
)
898 return sip_registrations
901 def sip_channels(self
):
902 return self
.backend
.talk
.freeswitch
.get_sip_channels(self
)
904 def get_cdr(self
, date
=None, limit
=None):
905 return self
.backend
.talk
.freeswitch
.get_cdr_by_account(self
, date
=date
, limit
=limit
)
910 def phone_number(self
):
912 Returns the IPFire phone number
915 return phonenumbers
.parse("+4923636035%s" % self
.sip_id
)
918 def fax_number(self
):
920 return phonenumbers
.parse("+49236360359%s" % self
.sip_id
)
922 def get_phone_numbers(self
):
925 for field
in ("telephoneNumber", "homePhone", "mobile"):
926 for number
in self
._get
_phone
_numbers
(field
):
931 def set_phone_numbers(self
, phone_numbers
):
932 # Sort phone numbers by landline and mobile
933 _landline_numbers
= []
936 for number
in phone_numbers
:
938 number
= phonenumbers
.parse(number
, None)
939 except phonenumbers
.phonenumberutil
.NumberParseException
:
942 # Convert to string (in E.164 format)
943 s
= phonenumbers
.format_number(number
, phonenumbers
.PhoneNumberFormat
.E164
)
945 # Separate mobile numbers
946 if phonenumbers
.number_type(number
) == phonenumbers
.PhoneNumberType
.MOBILE
:
947 _mobile_numbers
.append(s
)
949 _landline_numbers
.append(s
)
952 self
._set
_strings
("telephoneNumber", _landline_numbers
)
953 self
._set
_strings
("mobile", _mobile_numbers
)
955 phone_numbers
= property(get_phone_numbers
, set_phone_numbers
)
958 def _all_telephone_numbers(self
):
959 ret
= [ self
.sip_id
, ]
961 if self
.phone_number
:
962 s
= phonenumbers
.format_number(self
.phone_number
, phonenumbers
.PhoneNumberFormat
.E164
)
965 for number
in self
.phone_numbers
:
966 s
= phonenumbers
.format_number(number
, phonenumbers
.PhoneNumberFormat
.E164
)
973 def get_description(self
):
974 return self
._get
_string
("description")
976 def set_description(self
, description
):
977 self
._set
_string
("description", description
)
979 description
= property(get_description
, set_description
)
983 def has_avatar(self
):
984 has_avatar
= self
.memcache
.get("accounts:%s:has-avatar" % self
.uid
)
985 if has_avatar
is None:
986 has_avatar
= True if self
.get_avatar() else False
988 # Cache avatar status for up to 24 hours
989 self
.memcache
.set("accounts:%s:has-avatar" % self
.uid
, has_avatar
, 3600 * 24)
993 def avatar_url(self
, size
=None):
994 url
= "https://people.ipfire.org/users/%s.jpg?h=%s" % (self
.uid
, self
.avatar_hash
)
997 url
+= "&size=%s" % size
1001 def get_avatar(self
, size
=None):
1002 photo
= self
._get
_bytes
("jpegPhoto")
1004 # Exit if no avatar is available
1008 # Return the raw image if no size was requested
1012 # Try to retrieve something from the cache
1013 avatar
= self
.memcache
.get("accounts:%s:avatar:%s" % (self
.dn
, size
))
1017 # Generate a new thumbnail
1018 avatar
= util
.generate_thumbnail(photo
, size
, square
=True)
1020 # Save to cache for 15m
1021 self
.memcache
.set("accounts:%s:avatar:%s" % (self
.dn
, size
), avatar
, 900)
1026 def avatar_hash(self
):
1027 hash = self
.memcache
.get("accounts:%s:avatar-hash" % self
.dn
)
1029 h
= hashlib
.new("md5")
1030 h
.update(self
.get_avatar() or b
"")
1031 hash = h
.hexdigest()[:7]
1033 self
.memcache
.set("accounts:%s:avatar-hash" % self
.dn
, hash, 86400)
1037 def upload_avatar(self
, avatar
):
1038 self
._set
("jpegPhoto", avatar
)
1040 # Delete cached avatar status
1041 self
.memcache
.delete("accounts:%s:has-avatar" % self
.dn
)
1043 # Delete avatar hash
1044 self
.memcache
.delete("accounts:%s:avatar-hash" % self
.dn
)
1047 class StopForumSpam(Object
):
1048 def init(self
, uid
, email
, address
):
1049 self
.uid
, self
.email
, self
.address
= uid
, email
, address
1051 @tornado.gen
.coroutine
1052 def send_request(self
, **kwargs
):
1056 arguments
.update(kwargs
)
1059 request
= tornado
.httpclient
.HTTPRequest(
1060 "https://api.stopforumspam.org/api", method
="POST")
1061 request
.body
= urllib
.parse
.urlencode(arguments
)
1064 response
= yield self
.backend
.http_client
.fetch(request
)
1066 # Decode the JSON response
1067 return json
.loads(response
.body
.decode())
1069 @tornado.gen
.coroutine
1070 def check_address(self
):
1071 response
= yield self
.send_request(ip
=self
.address
)
1074 confidence
= response
["ip"]["confidence"]
1078 logging
.debug("Confidence for %s: %s" % (self
.address
, confidence
))
1082 @tornado.gen
.coroutine
1083 def check_username(self
):
1084 response
= yield self
.send_request(username
=self
.uid
)
1087 confidence
= response
["username"]["confidence"]
1091 logging
.debug("Confidence for %s: %s" % (self
.uid
, confidence
))
1095 @tornado.gen
.coroutine
1096 def check_email(self
):
1097 response
= yield self
.send_request(email
=self
.email
)
1100 confidence
= response
["email"]["confidence"]
1104 logging
.debug("Confidence for %s: %s" % (self
.email
, confidence
))
1108 @tornado.gen
.coroutine
1109 def check(self
, threshold
=95):
1111 This function tries to detect if we have a spammer.
1113 To honour the privacy of our users, we only send the IP
1114 address and username and if those are on the database, we
1115 will send the email address as well.
1117 confidences
= yield [self
.check_address(), self
.check_username()]
1119 if any((c
< threshold
for c
in confidences
)):
1120 confidences
+= yield [self
.check_email()]
1122 # Build a score based on the lowest confidence
1123 return 100 - min(confidences
)
1126 class Groups(Object
):
1128 "cn=LDAP Read Only,ou=Group,dc=ipfire,dc=org",
1129 "cn=LDAP Read Write,ou=Group,dc=ipfire,dc=org",
1131 # Everyone is a member of people
1132 "cn=people,ou=Group,dc=ipfire,dc=org",
1136 def search_base(self
):
1137 return "ou=Group,%s" % self
.backend
.accounts
.search_base
1139 def _query(self
, *args
, **kwargs
):
1141 "search_base" : self
.backend
.groups
.search_base
,
1144 return self
.backend
.accounts
._query
(*args
, **kwargs
)
1147 groups
= self
.get_all()
1151 def _get_groups(self
, query
, **kwargs
):
1152 res
= self
._query
(query
, **kwargs
)
1155 for dn
, attrs
in res
:
1156 # Skip any hidden groups
1157 if dn
in self
.hidden_groups
:
1160 g
= Group(self
.backend
, dn
, attrs
)
1163 return sorted(groups
)
1165 def _get_group(self
, query
, **kwargs
):
1170 groups
= self
._get
_groups
(query
, **kwargs
)
1175 return self
._get
_groups
(
1176 "(|(objectClass=posixGroup)(objectClass=groupOfNames))",
1179 def get_by_gid(self
, gid
):
1180 return self
._get
_group
(
1181 "(&(|(objectClass=posixGroup)(objectClass=groupOfNames))(cn=%s))" % gid
,
1185 class Group(Object
):
1186 def init(self
, dn
, attrs
=None):
1189 self
.attributes
= attrs
or {}
1192 if self
.description
:
1193 return "<%s %s (%s)>" % (
1194 self
.__class
__.__name
__,
1199 return "<%s %s>" % (self
.__class
__.__name
__, self
.gid
)
1202 return self
.description
or self
.gid
1204 def __eq__(self
, other
):
1205 if isinstance(other
, self
.__class
__):
1206 return self
.gid
== other
.gid
1208 def __lt__(self
, other
):
1209 if isinstance(other
, self
.__class
__):
1210 return (self
.description
or self
.gid
) < (other
.description
or other
.gid
)
1217 Returns the number of members in this group
1221 for attr
in ("member", "memberUid"):
1222 a
= self
.attributes
.get(attr
, None)
1229 return iter(self
.members
)
1234 gid
= self
.attributes
["cn"][0]
1241 def description(self
):
1243 description
= self
.attributes
["description"][0]
1247 return description
.decode()
1252 email
= self
.attributes
["mail"][0]
1256 return email
.decode()
1262 # Get all members by DN
1263 for dn
in self
.attributes
.get("member", []):
1264 member
= self
.backend
.accounts
.get_by_dn(dn
.decode())
1266 members
.append(member
)
1268 # Get all meembers by UID
1269 for uid
in self
.attributes
.get("memberUid", []):
1270 member
= self
.backend
.accounts
.get_by_uid(uid
.decode())
1272 members
.append(member
)
1274 return sorted(members
)
1276 if __name__
== "__main__":