]>
git.ipfire.org Git - ipfire.org.git/blob - src/backend/accounts.py
13 import tornado
.httpclient
18 from . import countries
20 from .decorators
import *
21 from .misc
import Object
23 # Set the client keytab name
24 os
.environ
["KRB5_CLIENT_KTNAME"] = "/etc/ipfire.org/ldap.keytab"
26 class Accounts(Object
):
28 self
.search_base
= self
.settings
.get("ldap_search_base")
31 # Only return developers (group with ID 1000)
32 accounts
= self
._search
("(&(objectClass=posixAccount)(gidNumber=1000))")
34 return iter(sorted(accounts
))
38 # Connect to LDAP server
39 ldap_uri
= self
.settings
.get("ldap_uri")
41 logging
.debug("Connecting to LDAP server: %s" % ldap_uri
)
43 # Connect to the LDAP server
44 return ldap
.ldapobject
.ReconnectLDAPObject(ldap_uri
,
45 retry_max
=10, retry_delay
=3)
47 def _authenticate(self
):
48 # Authenticate against LDAP server using Kerberos
49 self
.ldap
.sasl_gssapi_bind_s()
52 logging
.info("Testing LDAP connection...")
56 logging
.info("Successfully authenticated as %s" % self
.ldap
.whoami_s())
58 def _query(self
, query
, attrlist
=None, limit
=0, search_base
=None):
59 logging
.debug("Performing LDAP query (%s): %s" \
60 % (search_base
or self
.search_base
, query
))
64 results
= self
.ldap
.search_ext_s(search_base
or self
.search_base
,
65 ldap
.SCOPE_SUBTREE
, query
, attrlist
=attrlist
, sizelimit
=limit
)
67 # Log time it took to perform the query
68 logging
.debug("Query took %.2fms" % ((time
.time() - t
) * 1000.0))
72 def _search(self
, query
, attrlist
=None, limit
=0):
74 for dn
, attrs
in self
._query
(query
, attrlist
=["dn"], limit
=limit
):
75 account
= self
.get_by_dn(dn
)
76 accounts
.append(account
)
80 def _get_attrs(self
, dn
):
82 Fetches all attributes for the given distinguished name
84 results
= self
._query
("(objectClass=*)", search_base
=dn
, limit
=1,
85 attrlist
=("*", "createTimestamp", "modifyTimestamp"))
87 for dn
, attrs
in results
:
90 def get_by_dn(self
, dn
):
91 attrs
= self
.memcache
.get("accounts:%s:attrs" % dn
)
93 attrs
= self
._get
_attrs
(dn
)
96 # Cache all attributes for 5 min
97 self
.memcache
.set("accounts:%s:attrs" % dn
, attrs
, 300)
99 return Account(self
.backend
, dn
, attrs
)
101 def get_created_after(self
, ts
):
102 t
= ts
.strftime("%Y%m%d%H%M%SZ")
104 return self
._search
("(&(objectClass=person)(createTimestamp>=%s))" % t
)
106 def search(self
, query
):
107 # Search for exact matches
108 accounts
= self
._search
(
109 "(&(objectClass=person)(|(uid=%s)(mail=%s)(sipAuthenticationUser=%s)(telephoneNumber=%s)(homePhone=%s)(mobile=%s)))" \
110 % (query
, query
, query
, query
, query
, query
))
112 # Find accounts by name
114 for account
in self
._search
("(&(objectClass=person)(|(cn=*%s*)(uid=*%s*)))" % (query
, query
)):
115 if not account
in accounts
:
116 accounts
.append(account
)
118 return sorted(accounts
)
120 def _search_one(self
, query
):
121 results
= self
._search
(query
, limit
=1)
123 for result
in results
:
126 def uid_exists(self
, uid
):
127 if self
.get_by_uid(uid
):
130 res
= self
.db
.get("SELECT 1 FROM account_activations \
131 WHERE uid = %s AND expires_at > NOW()", uid
)
136 # Account with uid does not exist, yet
139 def get_by_uid(self
, uid
):
140 return self
._search
_one
("(&(objectClass=person)(uid=%s))" % uid
)
142 def get_by_mail(self
, mail
):
143 return self
._search
_one
("(&(objectClass=inetOrgPerson)(mail=%s))" % mail
)
145 def find_account(self
, s
):
146 account
= self
.get_by_uid(s
)
150 return self
.get_by_mail(s
)
152 def get_by_sip_id(self
, sip_id
):
156 return self
._search
_one
(
157 "(|(&(objectClass=sipUser)(sipAuthenticationUser=%s))(&(objectClass=sipRoutingObject)(sipLocalAddress=%s)))" \
160 def get_by_phone_number(self
, number
):
164 return self
._search
_one
(
165 "(&(objectClass=inetOrgPerson)(|(sipAuthenticationUser=%s)(telephoneNumber=%s)(homePhone=%s)(mobile=%s)))" \
166 % (number
, number
, number
, number
))
168 @tornado.gen
.coroutine
169 def check_spam(self
, uid
, email
, address
):
170 sfs
= StopForumSpam(self
.backend
, uid
, email
, address
)
173 score
= yield sfs
.check()
179 def register(self
, uid
, email
, first_name
, last_name
):
180 # Convert all uids to lowercase
183 # Check if UID is unique
184 if self
.uid_exists(uid
):
185 raise ValueError("UID exists: %s" % uid
)
187 # Generate a random activation code
188 activation_code
= util
.random_string(36)
190 # Create an entry in our database until the user
191 # has activated the account
192 self
.db
.execute("INSERT INTO account_activations(uid, activation_code, \
193 email, first_name, last_name) VALUES(%s, %s, %s, %s, %s)",
194 uid
, activation_code
, email
, first_name
, last_name
)
196 # Send an account activation email
197 self
.backend
.messages
.send_template("auth/messages/register",
198 recipients
=[email
], priority
=100, uid
=uid
,
199 activation_code
=activation_code
, email
=email
,
200 first_name
=first_name
, last_name
=last_name
)
202 def activate(self
, uid
, activation_code
):
203 res
= self
.db
.get("DELETE FROM account_activations \
204 WHERE uid = %s AND activation_code = %s AND expires_at > NOW() \
205 RETURNING *", uid
, activation_code
)
207 # Return nothing when account was not found
211 # Create a new account on the LDAP database
212 return self
.create(uid
, res
.email
,
213 first_name
=res
.first_name
, last_name
=res
.last_name
)
215 def create(self
, uid
, email
, first_name
, last_name
):
216 cn
= "%s %s" % (first_name
, last_name
)
220 "objectClass" : [b
"top", b
"person", b
"inetOrgPerson"],
221 "mail" : email
.encode(),
225 "sn" : last_name
.encode(),
226 "givenName" : first_name
.encode(),
229 logging
.info("Creating new account: %s: %s" % (uid
, account
))
232 dn
= "uid=%s,ou=People,dc=ipfire,dc=org" % uid
234 # Create account on LDAP
235 self
.accounts
._authenticate
()
236 self
.ldap
.add_s(dn
, ldap
.modlist
.addModlist(account
))
239 return self
.get_by_dn(dn
)
243 def create_session(self
, account
, host
):
244 res
= self
.db
.get("INSERT INTO sessions(host, uid) VALUES(%s, %s) \
245 RETURNING session_id, time_expires", host
, account
.uid
)
247 # Session could not be created
251 logging
.info("Created session %s for %s which expires %s" \
252 % (res
.session_id
, account
, res
.time_expires
))
253 return res
.session_id
, res
.time_expires
255 def destroy_session(self
, session_id
, host
):
256 logging
.info("Destroying session %s" % session_id
)
258 self
.db
.execute("DELETE FROM sessions \
259 WHERE session_id = %s AND host = %s", session_id
, host
)
261 def get_by_session(self
, session_id
, host
):
262 logging
.debug("Looking up session %s" % session_id
)
264 res
= self
.db
.get("SELECT uid FROM sessions WHERE session_id = %s \
265 AND host = %s AND NOW() BETWEEN time_created AND time_expires",
268 # Session does not exist or has expired
272 # Update the session expiration time
273 self
.db
.execute("UPDATE sessions SET time_expires = NOW() + INTERVAL '14 days' \
274 WHERE session_id = %s AND host = %s", session_id
, host
)
276 return self
.get_by_uid(res
.uid
)
279 # Cleanup expired sessions
280 self
.db
.execute("DELETE FROM sessions WHERE time_expires <= NOW()")
282 # Cleanup expired account activations
283 self
.db
.execute("DELETE FROM account_activations WHERE expires_at <= NOW()")
286 class Account(Object
):
287 def __init__(self
, backend
, dn
, attrs
=None):
288 Object
.__init
__(self
, backend
)
291 self
.attributes
= attrs
or {}
300 return "<%s %s>" % (self
.__class
__.__name
__, self
.dn
)
302 def __eq__(self
, other
):
303 if isinstance(other
, self
.__class
__):
304 return self
.dn
== other
.dn
306 def __lt__(self
, other
):
307 if isinstance(other
, self
.__class
__):
308 return self
.name
< other
.name
312 return self
.accounts
.ldap
314 def _exists(self
, key
):
323 for value
in self
.attributes
.get(key
, []):
326 def _get_bytes(self
, key
, default
=None):
327 for value
in self
._get
(key
):
332 def _get_strings(self
, key
):
333 for value
in self
._get
(key
):
336 def _get_string(self
, key
, default
=None):
337 for value
in self
._get
_strings
(key
):
342 def _get_phone_numbers(self
, key
):
343 for value
in self
._get
_strings
(key
):
344 yield phonenumbers
.parse(value
, None)
346 def _get_timestamp(self
, key
):
347 value
= self
._get
_string
(key
)
349 # Parse the timestamp value and returns a datetime object
351 return datetime
.datetime
.strptime(value
, "%Y%m%d%H%M%SZ")
353 def _modify(self
, modlist
):
354 logging
.debug("Modifying %s: %s" % (self
.dn
, modlist
))
356 # Authenticate before performing any write operations
357 self
.accounts
._authenticate
()
359 # Run modify operation
360 self
.ldap
.modify_s(self
.dn
, modlist
)
362 # Delete cached attributes
363 self
.memcache
.delete("accounts:%s:attrs" % self
.dn
)
365 def _set(self
, key
, values
):
366 current
= self
._get
(key
)
368 # Don't do anything if nothing has changed
369 if list(current
) == values
:
372 # Remove all old values and add all new ones
375 if self
._exists
(key
):
376 modlist
.append((ldap
.MOD_DELETE
, key
, None))
380 modlist
.append((ldap
.MOD_ADD
, key
, values
))
382 # Run modify operation
383 self
._modify
(modlist
)
386 self
.attributes
.update({ key
: values
})
388 def _set_bytes(self
, key
, values
):
389 return self
._set
(key
, values
)
391 def _set_strings(self
, key
, values
):
392 return self
._set
(key
, [e
.encode() for e
in values
if e
])
394 def _set_string(self
, key
, value
):
395 return self
._set
_strings
(key
, [value
,])
397 def _add(self
, key
, values
):
399 (ldap
.MOD_ADD
, key
, values
),
402 self
._modify
(modlist
)
404 def _add_strings(self
, key
, values
):
405 return self
._add
(key
, [e
.encode() for e
in values
])
407 def _add_string(self
, key
, value
):
408 return self
._add
_strings
(key
, [value
,])
410 def _delete(self
, key
, values
):
412 (ldap
.MOD_DELETE
, key
, values
),
415 self
._modify
(modlist
)
417 def _delete_strings(self
, key
, values
):
418 return self
._delete
(key
, [e
.encode() for e
in values
])
420 def _delete_string(self
, key
, value
):
421 return self
._delete
_strings
(key
, [value
,])
423 def passwd(self
, password
):
427 # The new password must have a score of 3 or better
428 quality
= self
.check_password_quality(password
)
429 if quality
["score"] < 3:
430 raise ValueError("Password too weak")
432 self
.accounts
._authenticate
()
433 self
.ldap
.passwd_s(self
.dn
, None, password
)
435 def check_password(self
, password
):
437 Bind to the server with given credentials and return
438 true if password is corrent and false if not.
440 Raises exceptions from the server on any other errors.
445 logging
.debug("Checking credentials for %s" % self
.dn
)
447 # Create a new LDAP connection
448 ldap_uri
= self
.backend
.settings
.get("ldap_uri")
449 conn
= ldap
.initialize(ldap_uri
)
452 conn
.simple_bind_s(self
.dn
, password
.encode("utf-8"))
453 except ldap
.INVALID_CREDENTIALS
:
454 logging
.debug("Account credentials are invalid for %s" % self
)
457 logging
.info("Successfully authenticated %s" % self
)
461 def check_password_quality(self
, password
):
463 Passwords are passed through zxcvbn to make sure
464 that they are strong enough.
466 return zxcvbn
.zxcvbn(password
, user_inputs
=(
467 self
.first_name
, self
.last_name
,
471 return "wheel" in self
.groups
474 return "staff" in self
.groups
477 return "posixAccount" in self
.classes
480 return "postfixMailUser" in self
.classes
483 return "sipUser" in self
.classes
or "sipRoutingObject" in self
.classes
485 def can_be_managed_by(self
, account
):
487 Returns True if account is allowed to manage this account
489 # Admins can manage all accounts
490 if account
.is_admin():
493 # Users can manage themselves
494 return self
== account
498 return self
._get
_strings
("objectClass")
502 return self
._get
_string
("uid")
506 return self
._get
_string
("cn")
510 def get_nickname(self
):
511 return self
._get
_string
("displayName")
513 def set_nickname(self
, nickname
):
514 self
._set
_string
("displayName", nickname
)
516 nickname
= property(get_nickname
, set_nickname
)
520 def get_first_name(self
):
521 return self
._get
_string
("givenName")
523 def set_first_name(self
, first_name
):
524 self
._set
_string
("givenName", first_name
)
527 self
._set
_string
("cn", "%s %s" % (first_name
, self
.last_name
))
529 first_name
= property(get_first_name
, set_first_name
)
533 def get_last_name(self
):
534 return self
._get
_string
("sn")
536 def set_last_name(self
, last_name
):
537 self
._set
_string
("sn", last_name
)
540 self
._set
_string
("cn", "%s %s" % (self
.first_name
, last_name
))
542 last_name
= property(get_last_name
, set_last_name
)
546 groups
= self
.memcache
.get("accounts:%s:groups" % self
.dn
)
550 # Fetch groups from LDAP
551 groups
= self
._get
_groups
()
553 # Cache groups for 5 min
554 self
.memcache
.set("accounts:%s:groups" % self
.dn
, groups
, 300)
558 def _get_groups(self
):
561 res
= self
.accounts
._query
("(&(objectClass=posixGroup) \
562 (memberUid=%s))" % self
.uid
, ["cn"])
564 for dn
, attrs
in res
:
565 cns
= attrs
.get("cn")
567 groups
.append(cns
[0].decode())
571 # Created/Modified at
574 def created_at(self
):
575 return self
._get
_timestamp
("createTimestamp")
578 def modified_at(self
):
579 return self
._get
_timestamp
("modifyTimestamp")
588 address
+= self
.street
.splitlines()
590 if self
.postal_code
and self
.city
:
591 if self
.country_code
in ("AT", "DE"):
592 address
.append("%s %s" % (self
.postal_code
, self
.city
))
594 address
.append("%s, %s" % (self
.city
, self
.postal_code
))
596 address
.append(self
.city
or self
.postal_code
)
598 if self
.country_name
:
599 address
.append(self
.country_name
)
603 def get_street(self
):
604 return self
._get
_string
("street") or self
._get
_string
("homePostalAddress")
606 def set_street(self
, street
):
607 self
._set
_string
("street", street
)
609 street
= property(get_street
, set_street
)
612 return self
._get
_string
("l") or ""
614 def set_city(self
, city
):
615 self
._set
_string
("l", city
)
617 city
= property(get_city
, set_city
)
619 def get_postal_code(self
):
620 return self
._get
_string
("postalCode") or ""
622 def set_postal_code(self
, postal_code
):
623 self
._set
_string
("postalCode", postal_code
)
625 postal_code
= property(get_postal_code
, set_postal_code
)
627 # XXX This should be c
628 def get_country_code(self
):
629 return self
._get
_string
("st")
631 def set_country_code(self
, country_code
):
632 self
._set
_string
("st", country_code
)
634 country_code
= property(get_country_code
, set_country_code
)
637 def country_name(self
):
638 if self
.country_code
:
639 return countries
.get_name(self
.country_code
)
643 return self
._get
_string
("mail")
645 # Mail Routing Address
647 def get_mail_routing_address(self
):
648 return self
._get
_string
("mailRoutingAddress", None)
650 def set_mail_routing_address(self
, address
):
651 self
._set
_string
("mailRoutingAddress", address
or None)
653 mail_routing_address
= property(get_mail_routing_address
, set_mail_routing_address
)
657 if "sipUser" in self
.classes
:
658 return self
._get
_string
("sipAuthenticationUser")
660 if "sipRoutingObject" in self
.classes
:
661 return self
._get
_string
("sipLocalAddress")
664 def sip_password(self
):
665 return self
._get
_string
("sipPassword")
668 def _generate_sip_password():
669 return util
.random_string(8)
673 return "%s@ipfire.org" % self
.sip_id
675 def uses_sip_forwarding(self
):
676 if self
.sip_routing_address
:
683 def get_sip_routing_address(self
):
684 if "sipRoutingObject" in self
.classes
:
685 return self
._get
_string
("sipRoutingAddress")
687 def set_sip_routing_address(self
, address
):
691 # Don't do anything if nothing has changed
692 if self
.get_sip_routing_address() == address
:
696 # This is no longer a SIP user any more
699 (ldap
.MOD_DELETE
, "objectClass", b
"sipUser"),
700 (ldap
.MOD_DELETE
, "sipAuthenticationUser", None),
701 (ldap
.MOD_DELETE
, "sipPassword", None),
703 except ldap
.NO_SUCH_ATTRIBUTE
:
706 # Set new routing object
709 (ldap
.MOD_ADD
, "objectClass", b
"sipRoutingObject"),
710 (ldap
.MOD_ADD
, "sipLocalAddress", self
.sip_id
.encode()),
711 (ldap
.MOD_ADD
, "sipRoutingAddress", address
.encode()),
714 # If this is a change, we cannot add this again
715 except ldap
.TYPE_OR_VALUE_EXISTS
:
716 self
._set
_string
("sipRoutingAddress", address
)
720 (ldap
.MOD_DELETE
, "objectClass", b
"sipRoutingObject"),
721 (ldap
.MOD_DELETE
, "sipLocalAddress", None),
722 (ldap
.MOD_DELETE
, "sipRoutingAddress", None),
724 except ldap
.NO_SUCH_ATTRIBUTE
:
728 (ldap
.MOD_ADD
, "objectClass", b
"sipUser"),
729 (ldap
.MOD_ADD
, "sipAuthenticationUser", self
.sip_id
.encode()),
730 (ldap
.MOD_ADD
, "sipPassword", self
._generate
_sip
_password
().encode()),
733 # XXX Cache is invalid here
735 sip_routing_address
= property(get_sip_routing_address
, set_sip_routing_address
)
738 def sip_registrations(self
):
739 sip_registrations
= []
741 for reg
in self
.backend
.talk
.freeswitch
.get_sip_registrations(self
.sip_url
):
744 sip_registrations
.append(reg
)
746 return sip_registrations
749 def sip_channels(self
):
750 return self
.backend
.talk
.freeswitch
.get_sip_channels(self
)
752 def get_cdr(self
, date
=None, limit
=None):
753 return self
.backend
.talk
.freeswitch
.get_cdr_by_account(self
, date
=date
, limit
=limit
)
758 def phone_number(self
):
760 Returns the IPFire phone number
763 return phonenumbers
.parse("+4923636035%s" % self
.sip_id
)
766 def fax_number(self
):
768 return phonenumbers
.parse("+49236360359%s" % self
.sip_id
)
770 def get_phone_numbers(self
):
773 for field
in ("telephoneNumber", "homePhone", "mobile"):
774 for number
in self
._get
_phone
_numbers
(field
):
779 def set_phone_numbers(self
, phone_numbers
):
780 # Sort phone numbers by landline and mobile
781 _landline_numbers
= []
784 for number
in phone_numbers
:
786 number
= phonenumbers
.parse(number
, None)
787 except phonenumbers
.phonenumberutil
.NumberParseException
:
790 # Convert to string (in E.164 format)
791 s
= phonenumbers
.format_number(number
, phonenumbers
.PhoneNumberFormat
.E164
)
793 # Separate mobile numbers
794 if phonenumbers
.number_type(number
) == phonenumbers
.PhoneNumberType
.MOBILE
:
795 _mobile_numbers
.append(s
)
797 _landline_numbers
.append(s
)
800 self
._set
_strings
("telephoneNumber", _landline_numbers
)
801 self
._set
_strings
("mobile", _mobile_numbers
)
803 phone_numbers
= property(get_phone_numbers
, set_phone_numbers
)
806 def _all_telephone_numbers(self
):
807 ret
= [ self
.sip_id
, ]
809 if self
.phone_number
:
810 s
= phonenumbers
.format_number(self
.phone_number
, phonenumbers
.PhoneNumberFormat
.E164
)
813 for number
in self
.phone_numbers
:
814 s
= phonenumbers
.format_number(number
, phonenumbers
.PhoneNumberFormat
.E164
)
819 def avatar_url(self
, size
=None):
820 if self
.backend
.debug
:
821 hostname
= "http://people.dev.ipfire.org"
823 hostname
= "https://people.ipfire.org"
825 url
= "%s/users/%s.jpg" % (hostname
, self
.uid
)
828 url
+= "?size=%s" % size
832 def get_avatar(self
, size
=None):
833 photo
= self
._get
_bytes
("jpegPhoto")
835 # Exit if no avatar is available
839 # Return the raw image if no size was requested
843 # Try to retrieve something from the cache
844 avatar
= self
.memcache
.get("accounts:%s:avatar:%s" % (self
.dn
, size
))
848 # Generate a new thumbnail
849 avatar
= util
.generate_thumbnail(photo
, size
, square
=True)
851 # Save to cache for 15m
852 self
.memcache
.set("accounts:%s:avatar:%s" % (self
.dn
, size
), avatar
, 900)
856 def upload_avatar(self
, avatar
):
857 self
._set
("jpegPhoto", avatar
)
865 for key
in self
._get
_strings
("sshPublicKey"):
866 s
= sshpubkeys
.SSHKey()
870 except (sshpubkeys
.InvalidKeyError
, NotImplementedError) as e
:
871 logging
.warning("Could not parse SSH key %s: %s" % (key
, e
))
878 def get_ssh_key_by_hash_sha256(self
, hash_sha256
):
879 for key
in self
.ssh_keys
:
880 if not key
.hash_sha256() == hash_sha256
:
885 def add_ssh_key(self
, key
):
886 k
= sshpubkeys
.SSHKey()
888 # Try to parse the key
891 # Check for types and sufficient sizes
892 if k
.key_type
== b
"ssh-rsa":
894 raise sshpubkeys
.TooShortKeyError("RSA keys cannot be smaller than 4096 bits")
896 elif k
.key_type
== b
"ssh-dss":
897 raise sshpubkeys
.InvalidKeyError("DSA keys are not supported")
899 # Ignore any duplicates
900 if key
in (k
.keydata
for k
in self
.ssh_keys
):
901 logging
.debug("SSH Key has already been added for %s: %s" % (self
, key
))
904 # Prepare transaction
907 # Add object class if user is not in it, yet
908 if not "ldapPublicKey" in self
.classes
:
909 modlist
.append((ldap
.MOD_ADD
, "objectClass", b
"ldapPublicKey"))
912 modlist
.append((ldap
.MOD_ADD
, "sshPublicKey", key
.encode()))
915 self
._modify
(modlist
)
918 self
.ssh_keys
.append(k
)
920 def delete_ssh_key(self
, key
):
921 if not key
in (k
.keydata
for k
in self
.ssh_keys
):
924 # Delete key from LDAP
925 if len(self
.ssh_keys
) > 1:
926 self
._delete
_string
("sshPublicKey", key
)
929 (ldap
.MOD_DELETE
, "objectClass", b
"ldapPublicKey"),
930 (ldap
.MOD_DELETE
, "sshPublicKey", key
.encode()),
934 class StopForumSpam(Object
):
935 def init(self
, uid
, email
, address
):
936 self
.uid
, self
.email
, self
.address
= uid
, email
, address
938 @tornado.gen
.coroutine
939 def send_request(self
, **kwargs
):
943 arguments
.update(kwargs
)
946 request
= tornado
.httpclient
.HTTPRequest(
947 "https://api.stopforumspam.org/api", method
="POST")
948 request
.body
= urllib
.parse
.urlencode(arguments
)
951 response
= yield self
.backend
.http_client
.fetch(request
)
953 # Decode the JSON response
954 return json
.loads(response
.body
.decode())
956 @tornado.gen
.coroutine
957 def check_address(self
):
958 response
= yield self
.send_request(ip
=self
.address
)
961 confidence
= response
["ip"]["confidence"]
965 logging
.debug("Confidence for %s: %s" % (self
.address
, confidence
))
969 @tornado.gen
.coroutine
970 def check_username(self
):
971 response
= yield self
.send_request(username
=self
.uid
)
974 confidence
= response
["username"]["confidence"]
978 logging
.debug("Confidence for %s: %s" % (self
.uid
, confidence
))
982 @tornado.gen
.coroutine
983 def check_email(self
):
984 response
= yield self
.send_request(email
=self
.email
)
987 confidence
= response
["email"]["confidence"]
991 logging
.debug("Confidence for %s: %s" % (self
.email
, confidence
))
995 @tornado.gen
.coroutine
996 def check(self
, threshold
=95):
998 This function tries to detect if we have a spammer.
1000 To honour the privacy of our users, we only send the IP
1001 address and username and if those are on the database, we
1002 will send the email address as well.
1004 confidences
= yield [self
.check_address(), self
.check_username()]
1006 if any((c
< threshold
for c
in confidences
)):
1007 confidences
+= yield [self
.check_email()]
1009 # Build a score based on the lowest confidence
1010 return 100 - min(confidences
)
1013 if __name__
== "__main__":