17 import tornado
.httpclient
22 from . import countries
24 from .decorators
import *
25 from .misc
import Object
29 # Set the client keytab name
30 os
.environ
["KRB5_CLIENT_KTNAME"] = "/etc/ipfire.org/ldap.keytab"
32 class LDAPObject(Object
):
33 def init(self
, dn
, attrs
=None):
36 self
.attributes
= attrs
or {}
38 def __eq__(self
, other
):
39 if isinstance(other
, self
.__class
__):
40 return self
.dn
== other
.dn
44 return self
.accounts
.ldap
46 def _exists(self
, key
):
55 for value
in self
.attributes
.get(key
, []):
58 def _get_bytes(self
, key
, default
=None):
59 for value
in self
._get
(key
):
64 def _get_strings(self
, key
):
65 for value
in self
._get
(key
):
68 def _get_string(self
, key
, default
=None):
69 for value
in self
._get
_strings
(key
):
74 def _get_phone_numbers(self
, key
):
75 for value
in self
._get
_strings
(key
):
76 yield phonenumbers
.parse(value
, None)
78 def _get_timestamp(self
, key
):
79 value
= self
._get
_string
(key
)
81 # Parse the timestamp value and returns a datetime object
83 return datetime
.datetime
.strptime(value
, "%Y%m%d%H%M%SZ")
85 def _modify(self
, modlist
):
86 logging
.debug("Modifying %s: %s" % (self
.dn
, modlist
))
88 # Authenticate before performing any write operations
89 self
.accounts
._authenticate
()
91 # Run modify operation
92 self
.ldap
.modify_s(self
.dn
, modlist
)
97 def _clear_cache(self
):
103 def _set(self
, key
, values
):
104 current
= self
._get
(key
)
106 # Don't do anything if nothing has changed
107 if list(current
) == values
:
110 # Remove all old values and add all new ones
113 if self
._exists
(key
):
114 modlist
.append((ldap
.MOD_DELETE
, key
, None))
118 modlist
.append((ldap
.MOD_ADD
, key
, values
))
120 # Run modify operation
121 self
._modify
(modlist
)
124 self
.attributes
.update({ key
: values
})
126 def _set_bytes(self
, key
, values
):
127 return self
._set
(key
, values
)
129 def _set_strings(self
, key
, values
):
130 return self
._set
(key
, [e
.encode() for e
in values
if e
])
132 def _set_string(self
, key
, value
):
133 return self
._set
_strings
(key
, [value
,])
135 def _add(self
, key
, values
):
137 (ldap
.MOD_ADD
, key
, values
),
140 self
._modify
(modlist
)
142 def _add_strings(self
, key
, values
):
143 return self
._add
(key
, [e
.encode() for e
in values
])
145 def _add_string(self
, key
, value
):
146 return self
._add
_strings
(key
, [value
,])
148 def _delete(self
, key
, values
):
150 (ldap
.MOD_DELETE
, key
, values
),
153 self
._modify
(modlist
)
155 def _delete_strings(self
, key
, values
):
156 return self
._delete
(key
, [e
.encode() for e
in values
])
158 def _delete_string(self
, key
, value
):
159 return self
._delete
_strings
(key
, [value
,])
162 def objectclasses(self
):
163 return self
._get
_strings
("objectClass")
167 return datetime
.datetime
.strptime(s
.decode(), "%Y%m%d%H%M%SZ")
170 class Accounts(Object
):
172 self
.search_base
= self
.settings
.get("ldap_search_base")
175 count
= self
.memcache
.get("accounts:count")
178 count
= self
._count
("(objectClass=person)")
180 self
.memcache
.set("accounts:count", count
, 300)
185 accounts
= self
._search
("(objectClass=person)")
187 return iter(sorted(accounts
))
191 # Connect to LDAP server
192 ldap_uri
= self
.settings
.get("ldap_uri")
194 logging
.debug("Connecting to LDAP server: %s" % ldap_uri
)
196 # Connect to the LDAP server
197 connection
= ldap
.ldapobject
.ReconnectLDAPObject(ldap_uri
,
198 trace_level
=2 if self
.backend
.debug
else 0,
199 retry_max
=10, retry_delay
=3)
201 # Set maximum timeout for operations
202 connection
.set_option(ldap
.OPT_TIMEOUT
, 10)
206 def _authenticate(self
):
207 # Authenticate against LDAP server using Kerberos
208 self
.ldap
.sasl_gssapi_bind_s()
211 logging
.info("Testing LDAP connection...")
215 logging
.info("Successfully authenticated as %s" % self
.ldap
.whoami_s())
217 def _query(self
, query
, attrlist
=None, limit
=0, search_base
=None):
218 logging
.debug("Performing LDAP query (%s): %s" \
219 % (search_base
or self
.search_base
, query
))
223 results
= self
.ldap
.search_ext_s(search_base
or self
.search_base
,
224 ldap
.SCOPE_SUBTREE
, query
, attrlist
=attrlist
, sizelimit
=limit
)
226 # Log time it took to perform the query
227 logging
.debug("Query took %.2fms" % ((time
.time() - t
) * 1000.0))
231 def _count(self
, query
):
232 res
= self
._query
(query
, attrlist
=["dn"], limit
=INT_MAX
)
236 def _search(self
, query
, attrlist
=None, limit
=0):
238 for dn
, attrs
in self
._query
(query
, attrlist
=["dn"], limit
=limit
):
239 account
= self
.get_by_dn(dn
)
240 accounts
.append(account
)
244 def _get_attrs(self
, dn
):
246 Fetches all attributes for the given distinguished name
248 results
= self
._query
("(objectClass=*)", search_base
=dn
, limit
=1,
249 attrlist
=("*", "createTimestamp", "modifyTimestamp"))
251 for dn
, attrs
in results
:
254 def get_by_dn(self
, dn
):
255 attrs
= self
.memcache
.get("accounts:%s:attrs" % dn
)
257 attrs
= self
._get
_attrs
(dn
)
260 # Cache all attributes for 5 min
261 self
.memcache
.set("accounts:%s:attrs" % dn
, attrs
, 300)
263 return Account(self
.backend
, dn
, attrs
)
267 return t
.strftime("%Y%m%d%H%M%SZ")
269 def get_created_after(self
, ts
):
270 return self
._search
("(&(objectClass=person)(createTimestamp>=%s))" % self
._format
_date
(ts
))
272 def count_created_after(self
, ts
):
273 return self
._count
("(&(objectClass=person)(createTimestamp>=%s))" % self
._format
_date
(ts
))
275 def search(self
, query
):
276 accounts
= self
._search
("(&(objectClass=person)(|(cn=*%s*)(uid=*%s*)(displayName=*%s*)(mail=*%s*)))" \
277 % (query
, query
, query
, query
))
279 return sorted(accounts
)
281 def _search_one(self
, query
):
282 results
= self
._search
(query
, limit
=1)
284 for result
in results
:
287 def uid_is_valid(self
, uid
):
288 # https://unix.stackexchange.com/questions/157426/what-is-the-regex-to-validate-linux-users
289 m
= re
.match(r
"^[a-z_][a-z0-9_-]{3,31}$", uid
)
295 def uid_exists(self
, uid
):
296 if self
.get_by_uid(uid
):
299 res
= self
.db
.get("SELECT 1 FROM account_activations \
300 WHERE uid = %s AND expires_at > NOW()", uid
)
305 # Account with uid does not exist, yet
308 def mail_is_valid(self
, mail
):
309 username
, delim
, domain
= mail
.partition("@")
311 # There must be an @ and a domain part
315 # The domain cannot end on a dot
316 if domain
.endswith("."):
319 # The domain should at least have one dot to fully qualified
320 if not "." in domain
:
323 # Looks like a valid email address
326 def mail_is_blacklisted(self
, mail
):
327 username
, delim
, domain
= mail
.partition("@")
330 return self
.domain_is_blacklisted(domain
)
332 def domain_is_blacklisted(self
, domain
):
333 res
= self
.db
.get("SELECT TRUE AS found FROM blacklisted_domains \
334 WHERE domain = %s", domain
)
336 if res
and res
.found
:
341 def get_by_uid(self
, uid
):
342 return self
._search
_one
("(&(objectClass=person)(uid=%s))" % uid
)
344 def get_by_mail(self
, mail
):
345 return self
._search
_one
("(&(objectClass=inetOrgPerson)(mail=%s))" % mail
)
347 def find_account(self
, s
):
348 account
= self
.get_by_uid(s
)
352 return self
.get_by_mail(s
)
354 def get_by_sip_id(self
, sip_id
):
358 return self
._search
_one
(
359 "(|(&(objectClass=sipUser)(sipAuthenticationUser=%s))(&(objectClass=sipRoutingObject)(sipLocalAddress=%s)))" \
362 def get_by_phone_number(self
, number
):
366 return self
._search
_one
(
367 "(&(objectClass=inetOrgPerson)(|(sipAuthenticationUser=%s)(telephoneNumber=%s)(homePhone=%s)(mobile=%s)))" \
368 % (number
, number
, number
, number
))
371 def pending_registrations(self
):
372 res
= self
.db
.get("SELECT COUNT(*) AS c FROM account_activations")
376 async def check_spam(self
, email
, address
):
377 sfs
= StopForumSpam(self
.backend
, email
, address
)
380 score
= await sfs
.check()
384 def auth(self
, username
, password
):
386 account
= self
.backend
.accounts
.find_account(username
)
389 if account
and account
.check_password(password
):
394 def register(self
, uid
, email
, first_name
, last_name
, country_code
=None):
395 # Convert all uids to lowercase
398 # Check if UID is valid
399 if not self
.uid_is_valid(uid
):
400 raise ValueError("UID is invalid: %s" % uid
)
402 # Check if UID is unique
403 if self
.uid_exists(uid
):
404 raise ValueError("UID exists: %s" % uid
)
406 # Check if the email address is valid
407 if not self
.mail_is_valid(email
):
408 raise ValueError("Email is invalid: %s" % email
)
410 # Check if the email address is blacklisted
411 if self
.mail_is_blacklisted(email
):
412 raise ValueError("Email is blacklisted: %s" % email
)
414 # Generate a random activation code
415 activation_code
= util
.random_string(36)
417 # Create an entry in our database until the user
418 # has activated the account
419 self
.db
.execute("INSERT INTO account_activations(uid, activation_code, \
420 email, first_name, last_name, country_code) VALUES(%s, %s, %s, %s, %s, %s)",
421 uid
, activation_code
, email
, first_name
, last_name
, country_code
)
423 # Send an account activation email
424 self
.backend
.messages
.send_template("auth/messages/register",
425 recipients
=[email
], priority
=100, uid
=uid
,
426 activation_code
=activation_code
, email
=email
,
427 first_name
=first_name
, last_name
=last_name
)
429 def activate(self
, uid
, activation_code
):
430 res
= self
.db
.get("DELETE FROM account_activations \
431 WHERE uid = %s AND activation_code = %s AND expires_at > NOW() \
432 RETURNING *", uid
, activation_code
)
434 # Return nothing when account was not found
438 # Return the account if it has already been created
439 account
= self
.get_by_uid(uid
)
443 # Create a new account on the LDAP database
444 account
= self
.create(uid
, res
.email
,
445 first_name
=res
.first_name
, last_name
=res
.last_name
,
446 country_code
=res
.country_code
)
448 # Non-EU users do not need to consent to promo emails
449 if account
.country_code
and not account
.country_code
in countries
.EU_COUNTRIES
:
450 account
.consents_to_promotional_emails
= True
452 # Send email about account registration
453 self
.backend
.messages
.send_template("people/messages/new-account",
454 recipients
=["moderators@ipfire.org"], account
=account
)
456 # Launch drip campaigns
457 for campaign
in ("signup", "christmas"):
458 self
.backend
.campaigns
.launch(campaign
, account
)
462 def create(self
, uid
, email
, first_name
, last_name
, country_code
=None):
463 cn
= "%s %s" % (first_name
, last_name
)
467 "objectClass" : [b
"top", b
"person", b
"inetOrgPerson"],
468 "mail" : email
.encode(),
472 "sn" : last_name
.encode(),
473 "givenName" : first_name
.encode(),
476 logging
.info("Creating new account: %s: %s" % (uid
, account
))
479 dn
= "uid=%s,ou=People,dc=ipfire,dc=org" % uid
481 # Create account on LDAP
482 self
.accounts
._authenticate
()
483 self
.ldap
.add_s(dn
, ldap
.modlist
.addModlist(account
))
486 account
= self
.get_by_dn(dn
)
488 # Optionally set country code
490 account
.country_code
= country_code
497 def create_session(self
, account
, host
):
498 session_id
= util
.random_string(64)
500 res
= self
.db
.get("INSERT INTO sessions(host, uid, session_id) VALUES(%s, %s, %s) \
501 RETURNING session_id, time_expires", host
, account
.uid
, session_id
)
503 # Session could not be created
507 logging
.info("Created session %s for %s which expires %s" \
508 % (res
.session_id
, account
, res
.time_expires
))
509 return res
.session_id
, res
.time_expires
511 def destroy_session(self
, session_id
, host
):
512 logging
.info("Destroying session %s" % session_id
)
514 self
.db
.execute("DELETE FROM sessions \
515 WHERE session_id = %s AND host = %s", session_id
, host
)
517 def get_by_session(self
, session_id
, host
):
518 logging
.debug("Looking up session %s" % session_id
)
520 res
= self
.db
.get("SELECT uid FROM sessions WHERE session_id = %s \
521 AND host = %s AND NOW() BETWEEN time_created AND time_expires",
524 # Session does not exist or has expired
528 # Update the session expiration time
529 self
.db
.execute("UPDATE sessions SET time_expires = NOW() + INTERVAL '14 days' \
530 WHERE session_id = %s AND host = %s", session_id
, host
)
532 return self
.get_by_uid(res
.uid
)
535 # Cleanup expired sessions
536 self
.db
.execute("DELETE FROM sessions WHERE time_expires <= NOW()")
538 # Cleanup expired account activations
539 self
.db
.execute("DELETE FROM account_activations WHERE expires_at <= NOW()")
541 # Cleanup expired account password resets
542 self
.db
.execute("DELETE FROM account_password_resets WHERE expires_at <= NOW()")
546 def decode_discourse_payload(self
, payload
, signature
):
548 calculated_signature
= self
.sign_discourse_payload(payload
)
550 if not hmac
.compare_digest(signature
, calculated_signature
):
551 raise ValueError("Invalid signature: %s" % signature
)
553 # Decode the query string
554 qs
= base64
.b64decode(payload
).decode()
556 # Parse the query string
558 for key
, val
in urllib
.parse
.parse_qsl(qs
):
563 def encode_discourse_payload(self
, **args
):
564 # Encode the arguments into an URL-formatted string
565 qs
= urllib
.parse
.urlencode(args
).encode()
568 return base64
.b64encode(qs
).decode()
570 def sign_discourse_payload(self
, payload
, secret
=None):
572 secret
= self
.settings
.get("discourse_sso_secret")
574 # Calculate a HMAC using SHA256
575 h
= hmac
.new(secret
.encode(),
576 msg
=payload
.encode(), digestmod
="sha256")
584 for country
in iso3166
.countries
:
585 count
= self
._count
("(&(objectClass=person)(st=%s))" % country
.alpha2
)
593 class Account(LDAPObject
):
601 return "<%s %s>" % (self
.__class
__.__name
__, self
.dn
)
603 def __lt__(self
, other
):
604 if isinstance(other
, self
.__class
__):
605 return self
.name
< other
.name
607 def _clear_cache(self
):
608 # Delete cached attributes
609 self
.memcache
.delete("accounts:%s:attrs" % self
.dn
)
612 def kerberos_attributes(self
):
613 res
= self
.backend
.accounts
._query
(
614 "(&(objectClass=krbPrincipal)(krbPrincipalName=%s@IPFIRE.ORG))" % self
.uid
,
616 "krbLastSuccessfulAuth",
617 "krbLastPasswordChange",
619 "krbLoginFailedCount",
622 search_base
="cn=krb5,%s" % self
.backend
.accounts
.search_base
)
624 for dn
, attrs
in res
:
625 return { key
: attrs
[key
][0] for key
in attrs
}
630 def last_successful_authentication(self
):
632 s
= self
.kerberos_attributes
["krbLastSuccessfulAuth"]
636 return self
._parse
_date
(s
)
639 def last_failed_authentication(self
):
641 s
= self
.kerberos_attributes
["krbLastFailedAuth"]
645 return self
._parse
_date
(s
)
648 def failed_login_count(self
):
650 count
= self
.kerberos_attributes
["krbLoginFailedCount"].decode()
659 def passwd(self
, password
):
663 # The new password must have a score of 3 or better
664 quality
= self
.check_password_quality(password
)
665 if quality
["score"] < 3:
666 raise ValueError("Password too weak")
668 self
.accounts
._authenticate
()
669 self
.ldap
.passwd_s(self
.dn
, None, password
)
671 def check_password(self
, password
):
673 Bind to the server with given credentials and return
674 true if password is corrent and false if not.
676 Raises exceptions from the server on any other errors.
681 logging
.debug("Checking credentials for %s" % self
.dn
)
683 # Create a new LDAP connection
684 ldap_uri
= self
.backend
.settings
.get("ldap_uri")
685 conn
= ldap
.initialize(ldap_uri
)
688 conn
.simple_bind_s(self
.dn
, password
.encode("utf-8"))
689 except ldap
.INVALID_CREDENTIALS
:
690 logging
.debug("Account credentials are invalid for %s" % self
)
693 logging
.info("Successfully authenticated %s" % self
)
697 def check_password_quality(self
, password
):
699 Passwords are passed through zxcvbn to make sure
700 that they are strong enough.
702 return zxcvbn
.zxcvbn(password
, user_inputs
=(
703 self
.first_name
, self
.last_name
,
706 def request_password_reset(self
, address
=None):
707 reset_code
= util
.random_string(64)
709 self
.db
.execute("INSERT INTO account_password_resets(uid, reset_code, address) \
710 VALUES(%s, %s, %s)", self
.uid
, reset_code
, address
)
712 # Send a password reset email
713 self
.backend
.messages
.send_template("auth/messages/password-reset",
714 recipients
=[self
.email
], priority
=100, account
=self
, reset_code
=reset_code
)
716 def reset_password(self
, reset_code
, new_password
):
717 # Delete the reset token
718 res
= self
.db
.query("DELETE FROM account_password_resets \
719 WHERE uid = %s AND reset_code = %s AND expires_at >= NOW() \
720 RETURNING *", self
.uid
, reset_code
)
722 # The reset code was invalid
724 raise ValueError("Invalid password reset token for %s: %s" % (self
, reset_code
))
726 # Perform password change
727 return self
.passwd(new_password
)
730 return self
.is_member_of_group("sudo")
733 return self
.is_member_of_group("staff")
735 def is_moderator(self
):
736 return self
.is_member_of_group("moderators")
739 return "posixAccount" in self
.classes
742 return "postfixMailUser" in self
.classes
745 return "sipUser" in self
.classes
or "sipRoutingObject" in self
.classes
747 def can_be_managed_by(self
, account
):
749 Returns True if account is allowed to manage this account
751 # Admins can manage all accounts
752 if account
.is_admin():
755 # Users can manage themselves
756 return self
== account
760 return self
._get
_strings
("objectClass")
764 return self
._get
_string
("uid")
768 return self
._get
_string
("cn")
772 def get_nickname(self
):
773 return self
._get
_string
("displayName")
775 def set_nickname(self
, nickname
):
776 self
._set
_string
("displayName", nickname
)
778 nickname
= property(get_nickname
, set_nickname
)
782 def get_first_name(self
):
783 return self
._get
_string
("givenName")
785 def set_first_name(self
, first_name
):
786 self
._set
_string
("givenName", first_name
)
789 self
._set
_string
("cn", "%s %s" % (first_name
, self
.last_name
))
791 first_name
= property(get_first_name
, set_first_name
)
795 def get_last_name(self
):
796 return self
._get
_string
("sn")
798 def set_last_name(self
, last_name
):
799 self
._set
_string
("sn", last_name
)
802 self
._set
_string
("cn", "%s %s" % (self
.first_name
, last_name
))
804 last_name
= property(get_last_name
, set_last_name
)
808 return self
.backend
.groups
._get
_groups
("(| \
809 (&(objectClass=groupOfNames)(member=%s)) \
810 (&(objectClass=posixGroup)(memberUid=%s)) \
811 )" % (self
.dn
, self
.uid
))
813 def is_member_of_group(self
, gid
):
815 Returns True if this account is a member of this group
817 return gid
in (g
.gid
for g
in self
.groups
)
819 # Created/Modified at
822 def created_at(self
):
823 return self
._get
_timestamp
("createTimestamp")
826 def modified_at(self
):
827 return self
._get
_timestamp
("modifyTimestamp")
836 address
+= self
.street
.splitlines()
838 if self
.postal_code
and self
.city
:
839 if self
.country_code
in ("AT", "DE"):
840 address
.append("%s %s" % (self
.postal_code
, self
.city
))
842 address
.append("%s, %s" % (self
.city
, self
.postal_code
))
844 address
.append(self
.city
or self
.postal_code
)
846 if self
.country_name
:
847 address
.append(self
.country_name
)
851 def get_street(self
):
852 return self
._get
_string
("street") or self
._get
_string
("homePostalAddress")
854 def set_street(self
, street
):
855 self
._set
_string
("street", street
)
857 street
= property(get_street
, set_street
)
860 return self
._get
_string
("l") or ""
862 def set_city(self
, city
):
863 self
._set
_string
("l", city
)
865 city
= property(get_city
, set_city
)
867 def get_postal_code(self
):
868 return self
._get
_string
("postalCode") or ""
870 def set_postal_code(self
, postal_code
):
871 self
._set
_string
("postalCode", postal_code
)
873 postal_code
= property(get_postal_code
, set_postal_code
)
875 # XXX This should be c
876 def get_country_code(self
):
877 return self
._get
_string
("st")
879 def set_country_code(self
, country_code
):
880 self
._set
_string
("st", country_code
)
882 country_code
= property(get_country_code
, set_country_code
)
885 def country_name(self
):
886 if self
.country_code
:
887 return countries
.get_name(self
.country_code
)
891 return self
._get
_string
("mail")
895 return "%s <%s>" % (self
, self
.email
)
897 # Mail Routing Address
899 def get_mail_routing_address(self
):
900 return self
._get
_string
("mailRoutingAddress", None)
902 def set_mail_routing_address(self
, address
):
903 self
._set
_string
("mailRoutingAddress", address
or None)
905 mail_routing_address
= property(get_mail_routing_address
, set_mail_routing_address
)
909 if "sipUser" in self
.classes
:
910 return self
._get
_string
("sipAuthenticationUser")
912 if "sipRoutingObject" in self
.classes
:
913 return self
._get
_string
("sipLocalAddress")
916 def sip_password(self
):
917 return self
._get
_string
("sipPassword")
920 def _generate_sip_password():
921 return util
.random_string(8)
925 return "%s@ipfire.org" % self
.sip_id
928 def agent_status(self
):
929 return self
.backend
.talk
.freeswitch
.get_agent_status(self
)
931 def uses_sip_forwarding(self
):
932 if self
.sip_routing_address
:
939 def get_sip_routing_address(self
):
940 if "sipRoutingObject" in self
.classes
:
941 return self
._get
_string
("sipRoutingAddress")
943 def set_sip_routing_address(self
, address
):
947 # Don't do anything if nothing has changed
948 if self
.get_sip_routing_address() == address
:
952 # This is no longer a SIP user any more
955 (ldap
.MOD_DELETE
, "objectClass", b
"sipUser"),
956 (ldap
.MOD_DELETE
, "sipAuthenticationUser", None),
957 (ldap
.MOD_DELETE
, "sipPassword", None),
959 except ldap
.NO_SUCH_ATTRIBUTE
:
962 # Set new routing object
965 (ldap
.MOD_ADD
, "objectClass", b
"sipRoutingObject"),
966 (ldap
.MOD_ADD
, "sipLocalAddress", self
.sip_id
.encode()),
967 (ldap
.MOD_ADD
, "sipRoutingAddress", address
.encode()),
970 # If this is a change, we cannot add this again
971 except ldap
.TYPE_OR_VALUE_EXISTS
:
972 self
._set
_string
("sipRoutingAddress", address
)
976 (ldap
.MOD_DELETE
, "objectClass", b
"sipRoutingObject"),
977 (ldap
.MOD_DELETE
, "sipLocalAddress", None),
978 (ldap
.MOD_DELETE
, "sipRoutingAddress", None),
980 except ldap
.NO_SUCH_ATTRIBUTE
:
984 (ldap
.MOD_ADD
, "objectClass", b
"sipUser"),
985 (ldap
.MOD_ADD
, "sipAuthenticationUser", self
.sip_id
.encode()),
986 (ldap
.MOD_ADD
, "sipPassword", self
._generate
_sip
_password
().encode()),
989 # XXX Cache is invalid here
991 sip_routing_address
= property(get_sip_routing_address
, set_sip_routing_address
)
994 def sip_registrations(self
):
995 sip_registrations
= []
997 for reg
in self
.backend
.talk
.freeswitch
.get_sip_registrations(self
.sip_url
):
1000 sip_registrations
.append(reg
)
1002 return sip_registrations
1005 def sip_channels(self
):
1006 return self
.backend
.talk
.freeswitch
.get_sip_channels(self
)
1008 def get_cdr(self
, date
=None, limit
=None):
1009 return self
.backend
.talk
.freeswitch
.get_cdr_by_account(self
, date
=date
, limit
=limit
)
1014 def phone_number(self
):
1016 Returns the IPFire phone number
1019 return phonenumbers
.parse("+4923636035%s" % self
.sip_id
)
1022 def fax_number(self
):
1024 return phonenumbers
.parse("+49236360359%s" % self
.sip_id
)
1026 def get_phone_numbers(self
):
1029 for field
in ("telephoneNumber", "homePhone", "mobile"):
1030 for number
in self
._get
_phone
_numbers
(field
):
1035 def set_phone_numbers(self
, phone_numbers
):
1036 # Sort phone numbers by landline and mobile
1037 _landline_numbers
= []
1038 _mobile_numbers
= []
1040 for number
in phone_numbers
:
1042 number
= phonenumbers
.parse(number
, None)
1043 except phonenumbers
.phonenumberutil
.NumberParseException
:
1046 # Convert to string (in E.164 format)
1047 s
= phonenumbers
.format_number(number
, phonenumbers
.PhoneNumberFormat
.E164
)
1049 # Separate mobile numbers
1050 if phonenumbers
.number_type(number
) == phonenumbers
.PhoneNumberType
.MOBILE
:
1051 _mobile_numbers
.append(s
)
1053 _landline_numbers
.append(s
)
1056 self
._set
_strings
("telephoneNumber", _landline_numbers
)
1057 self
._set
_strings
("mobile", _mobile_numbers
)
1059 phone_numbers
= property(get_phone_numbers
, set_phone_numbers
)
1062 def _all_telephone_numbers(self
):
1063 ret
= [ self
.sip_id
, ]
1065 if self
.phone_number
:
1066 s
= phonenumbers
.format_number(self
.phone_number
, phonenumbers
.PhoneNumberFormat
.E164
)
1069 for number
in self
.phone_numbers
:
1070 s
= phonenumbers
.format_number(number
, phonenumbers
.PhoneNumberFormat
.E164
)
1077 def get_description(self
):
1078 return self
._get
_string
("description")
1080 def set_description(self
, description
):
1081 self
._set
_string
("description", description
)
1083 description
= property(get_description
, set_description
)
1087 def has_avatar(self
):
1088 has_avatar
= self
.memcache
.get("accounts:%s:has-avatar" % self
.uid
)
1089 if has_avatar
is None:
1090 has_avatar
= True if self
.get_avatar() else False
1092 # Cache avatar status for up to 24 hours
1093 self
.memcache
.set("accounts:%s:has-avatar" % self
.uid
, has_avatar
, 3600 * 24)
1097 def avatar_url(self
, size
=None):
1098 url
= "https://people.ipfire.org/users/%s.jpg?h=%s" % (self
.uid
, self
.avatar_hash
)
1101 url
+= "&size=%s" % size
1105 def get_avatar(self
, size
=None):
1106 photo
= self
._get
_bytes
("jpegPhoto")
1108 # Exit if no avatar is available
1112 # Return the raw image if no size was requested
1116 # Try to retrieve something from the cache
1117 avatar
= self
.memcache
.get("accounts:%s:avatar:%s" % (self
.dn
, size
))
1121 # Generate a new thumbnail
1122 avatar
= util
.generate_thumbnail(photo
, size
, square
=True)
1124 # Save to cache for 15m
1125 self
.memcache
.set("accounts:%s:avatar:%s" % (self
.dn
, size
), avatar
, 900)
1130 def avatar_hash(self
):
1131 hash = self
.memcache
.get("accounts:%s:avatar-hash" % self
.dn
)
1133 h
= hashlib
.new("md5")
1134 h
.update(self
.get_avatar() or b
"")
1135 hash = h
.hexdigest()[:7]
1137 self
.memcache
.set("accounts:%s:avatar-hash" % self
.dn
, hash, 86400)
1141 def upload_avatar(self
, avatar
):
1142 self
._set
("jpegPhoto", avatar
)
1144 # Delete cached avatar status
1145 self
.memcache
.delete("accounts:%s:has-avatar" % self
.dn
)
1147 # Delete avatar hash
1148 self
.memcache
.delete("accounts:%s:avatar-hash" % self
.dn
)
1150 # Consent to promotional emails
1152 def get_consents_to_promotional_emails(self
):
1153 return self
.is_member_of_group("promotional-consent")
1155 def set_contents_to_promotional_emails(self
, value
):
1156 group
= self
.backend
.groups
.get_by_gid("promotional-consent")
1157 assert group
, "Could not find group: promotional-consent"
1160 group
.add_member(self
)
1162 group
.del_member(self
)
1164 consents_to_promotional_emails
= property(
1165 get_consents_to_promotional_emails
,
1166 set_contents_to_promotional_emails
,
1170 class StopForumSpam(Object
):
1171 def init(self
, email
, address
):
1172 self
.email
, self
.address
= email
, address
1174 async def send_request(self
, **kwargs
):
1178 arguments
.update(kwargs
)
1181 request
= tornado
.httpclient
.HTTPRequest(
1182 "https://api.stopforumspam.org/api", method
="POST",
1183 connect_timeout
=2, request_timeout
=5)
1184 request
.body
= urllib
.parse
.urlencode(arguments
)
1187 response
= await self
.backend
.http_client
.fetch(request
)
1189 # Decode the JSON response
1190 return json
.loads(response
.body
.decode())
1192 async def check_address(self
):
1193 response
= await self
.send_request(ip
=self
.address
)
1196 confidence
= response
["ip"]["confidence"]
1200 logging
.debug("Confidence for %s: %s" % (self
.address
, confidence
))
1204 async def check_email(self
):
1205 response
= await self
.send_request(email
=self
.email
)
1208 confidence
= response
["email"]["confidence"]
1212 logging
.debug("Confidence for %s: %s" % (self
.email
, confidence
))
1216 async def check(self
, threshold
=95):
1218 This function tries to detect if we have a spammer.
1220 To honour the privacy of our users, we only send the IP
1221 address and username and if those are on the database, we
1222 will send the email address as well.
1224 confidences
= [await self
.check_address(), await self
.check_email()]
1226 # Build a score based on the lowest confidence
1227 return 100 - min(confidences
)
1230 class Groups(Object
):
1232 "cn=LDAP Read Only,ou=Group,dc=ipfire,dc=org",
1233 "cn=LDAP Read Write,ou=Group,dc=ipfire,dc=org",
1235 # Everyone is a member of people
1236 "cn=people,ou=Group,dc=ipfire,dc=org",
1240 def search_base(self
):
1241 return "ou=Group,%s" % self
.backend
.accounts
.search_base
1243 def _query(self
, *args
, **kwargs
):
1245 "search_base" : self
.backend
.groups
.search_base
,
1248 return self
.backend
.accounts
._query
(*args
, **kwargs
)
1251 groups
= self
.get_all()
1255 def _get_groups(self
, query
, **kwargs
):
1256 res
= self
._query
(query
, **kwargs
)
1259 for dn
, attrs
in res
:
1260 # Skip any hidden groups
1261 if dn
in self
.hidden_groups
:
1264 g
= Group(self
.backend
, dn
, attrs
)
1267 return sorted(groups
)
1269 def _get_group(self
, query
, **kwargs
):
1274 groups
= self
._get
_groups
(query
, **kwargs
)
1279 return self
._get
_groups
(
1280 "(|(objectClass=posixGroup)(objectClass=groupOfNames))",
1283 def get_by_gid(self
, gid
):
1284 return self
._get
_group
(
1285 "(&(|(objectClass=posixGroup)(objectClass=groupOfNames))(cn=%s))" % gid
,
1289 class Group(LDAPObject
):
1291 if self
.description
:
1292 return "<%s %s (%s)>" % (
1293 self
.__class
__.__name
__,
1298 return "<%s %s>" % (self
.__class
__.__name
__, self
.gid
)
1301 return self
.description
or self
.gid
1303 def __lt__(self
, other
):
1304 if isinstance(other
, self
.__class
__):
1305 return (self
.description
or self
.gid
) < (other
.description
or other
.gid
)
1312 Returns the number of members in this group
1316 for attr
in ("member", "memberUid"):
1317 a
= self
.attributes
.get(attr
, None)
1324 return iter(self
.members
)
1328 return self
._get
_string
("cn")
1331 def description(self
):
1332 return self
._get
_string
("description")
1336 return self
._get
_string
("mail")
1342 # Get all members by DN
1343 for dn
in self
._get
_strings
("member"):
1344 member
= self
.backend
.accounts
.get_by_dn(dn
)
1346 members
.append(member
)
1348 # Get all members by UID
1349 for uid
in self
._get
_strings
("memberUid"):
1350 member
= self
.backend
.accounts
.get_by_uid(uid
)
1352 members
.append(member
)
1354 return sorted(members
)
1356 def add_member(self
, account
):
1358 Adds a member to this group
1360 # Do nothing if this user is already in the group
1361 if account
.is_member_of_group(self
.gid
):
1364 if "posixGroup" in self
.objectclasses
:
1365 self
._add
_string
("memberUid", account
.uid
)
1367 self
._add
_string
("member", account
.dn
)
1369 # Append to cached list of members
1370 self
.members
.append(account
)
1373 def del_member(self
, account
):
1375 Removes a member from a group
1377 # Do nothing if this user is not in the group
1378 if not account
.is_member_of_group(self
.gid
):
1381 if "posixGroup" in self
.objectclasses
:
1382 self
._delete
_string
("memberUid", account
.uid
)
1384 self
._delete
_string
("member", account
.dn
)
1387 if __name__
== "__main__":