]>
git.ipfire.org Git - people/jschlag/pbs.git/blob - src/buildservice/users.py
15 # A list of possible random characters.
16 random_chars
= string
.ascii_letters
+ string
.digits
18 def generate_random_string(length
=16):
20 Return a string with random chararcters A-Za-z0-9 with given length.
22 return "".join([random
.choice(random_chars
) for i
in range(length
)])
25 def generate_password_hash(password
, salt
=None, algo
="sha512"):
27 This function creates a salted digest of the given password.
29 # Generate the salt (length = 16) of none was given.
31 salt
= generate_random_string(length
=16)
35 if not algo
in hashlib
.algorithms
:
36 raise Exception, "Unsupported password hash algorithm: %s" % algo
38 # Calculate the digest.
43 # Output string is of kind "<algo>$<salt>$<hash>".
44 return "$".join((algo
, salt
, h
.hexdigest()))
46 def check_password_hash(password
, password_hash
):
48 Check a plain-text password with the given digest.
50 # Handle plaintext passwords (plain$<password>).
51 if password_hash
.startswith("plain$"):
52 return password_hash
[6:] == password
55 algo
, salt
, digest
= password_hash
.split("$", 2)
57 logging
.warning("Unknown password hash: %s" % password_hash
)
60 # Re-generate the password hash and compare the result.
61 return password_hash
== generate_password_hash(password
, salt
=salt
, algo
=algo
)
63 def check_password_strength(password
):
67 # Empty passwords cannot be used.
68 if len(password
) == 0:
71 # Passwords with less than 6 characters are also too weak.
75 # Password with at least 8 characters are secure.
76 if len(password
) >= 8:
79 # 10 characters are even more secure.
80 if len(password
) >= 10:
83 # Digits in the password are good.
84 if re
.search("\d+", password
):
87 # Check for lowercase AND uppercase characters.
88 if re
.search("[a-z]", password
) and re
.search("[A-Z]", password
):
91 # Search for special characters.
92 if re
.search(".[!,@,#,$,%,^,&,*,?,_,~,-,(,)]", password
):
98 return accepted
, score
100 def maintainer_split(s
):
101 m
= re
.match(r
"(.*) <(.*)>", s
)
103 name
, email
= m
.groups()
105 name
, email
= None, None
109 class Users(base
.Object
):
110 def auth(self
, name
, password
):
111 # If either name or password is None, we don't check at all.
112 if None in (name
, password
):
115 # Search for the username in the database.
116 # The user must not be deleted and must be activated.
117 user
= self
.db
.get("SELECT id FROM users WHERE name = %s AND \
118 activated = 'Y' AND deleted = 'N'", name
)
123 # Get the whole User object from the database.
124 user
= self
.get_by_id(user
.id)
126 # If the user was not found or the password does not match,
128 if not user
or not user
.check_password(password
):
131 # Otherwise we return the User object.
134 def register(self
, name
, password
, email
, realname
, locale
=None):
135 return User
.new(self
.pakfire
, name
, password
, email
, realname
, locale
)
137 def name_is_used(self
, name
):
138 users
= self
.db
.query("SELECT id FROM users WHERE name = %s", name
)
145 def email_is_used(self
, email
):
146 users
= self
.db
.query("SELECT id FROM users_emails WHERE email = %s", email
)
154 users
= self
.db
.query("""SELECT id FROM users WHERE activated = 'Y' AND
155 deleted = 'N' ORDER BY name ASC""")
157 return [User(self
.pakfire
, u
.id) for u
in users
]
159 def get_by_id(self
, id):
160 return User(self
.pakfire
, id)
162 def get_by_name(self
, name
):
163 user
= self
.db
.get("SELECT id FROM users WHERE name = %s LIMIT 1", name
)
166 return User(self
.pakfire
, user
.id)
168 def get_by_email(self
, email
):
169 user
= self
.db
.get("SELECT user_id AS id FROM users_emails \
170 WHERE email = %s LIMIT 1", email
)
173 return User(self
.pakfire
, user
.id)
176 users
= self
.db
.get("SELECT COUNT(*) AS count FROM users \
177 WHERE activated = 'Y' AND deleted = 'N'")
182 def search(self
, pattern
, limit
=None):
183 pattern
= "%%%s%%" % pattern
185 query
= "SELECT id FROM users \
186 WHERE (name LIKE %s OR realname LIKE %s) AND activated = %s AND deleted = %s"
187 args
= [pattern
, pattern
, "Y", "N"]
194 for user
in self
.db
.query(query
, *args
):
195 user
= User(self
.pakfire
, user
.id)
200 def find_maintainer(self
, s
):
204 name
, email
= maintainer_split(s
)
208 user
= self
.db
.get("SELECT user_id FROM users_emails WHERE email = %s LIMIT 1", email
)
212 return self
.get_by_id(user
.user_id
)
215 class User(base
.Object
):
216 def __init__(self
, pakfire
, id):
217 base
.Object
.__init
__(self
, pakfire
)
226 return "<%s %s>" % (self
.__class
__.__name
__, self
.realname
)
231 def __cmp__(self
, other
):
235 if isinstance(other
, unicode):
236 return cmp(self
.email
, other
)
238 if self
.id == other
.id:
241 return cmp(self
.realname
, other
.realname
)
244 def new(cls
, pakfire
, name
, passphrase
, email
, realname
, locale
=None):
245 id = pakfire
.db
.execute("INSERT INTO users(name, passphrase, realname) \
246 VALUES(%s, %s, %s)", name
, generate_password_hash(passphrase
), realname
)
249 pakfire
.db
.execute("INSERT INTO users_emails(user_id, email, primary) \
250 VALUES(%s, %s, 'Y')", id, email
)
252 # Create row in permissions table.
253 pakfire
.db
.execute("INSERT INTO users_permissions(user_id) VALUES(%s)", id)
255 user
= cls(pakfire
, id)
257 # If we have a guessed locale, we save it (for sending emails).
261 user
.send_activation_mail()
267 if self
._data
is None:
268 self
._data
= self
.db
.get("SELECT * FROM users WHERE id = %s" % self
.id)
269 assert self
._data
, "User %s not found." % self
.id
274 self
.db
.execute("UPDATE users SET deleted = 'Y' WHERE id = %s", self
.id)
278 self
.db
.execute("UPDATE users SET activated = 'Y', activation_code = NULL \
279 WHERE id = %s", self
.id)
281 def check_password(self
, password
):
283 Compare the given password with the one stored in the database.
285 return check_password_hash(password
, self
.data
.passphrase
)
287 def set_passphrase(self
, passphrase
):
289 Update the passphrase the users uses to log on.
291 self
.db
.execute("UPDATE users SET passphrase = %s WHERE id = %s",
292 generate_password_hash(passphrase
), self
.id)
294 passphrase
= property(lambda x
: None, set_passphrase
)
297 def activation_code(self
):
298 return self
.data
.activation_code
300 def get_realname(self
):
301 if not self
.data
.realname
:
304 return self
.data
.realname
306 def set_realname(self
, realname
):
307 self
.db
.execute("UPDATE users SET realname = %s WHERE id = %s",
309 self
.data
["realname"] = realname
311 realname
= property(get_realname
, set_realname
)
315 return self
.data
.name
319 # Try to split the string into first and last name.
320 # If that is not successful, return the entire realname.
322 firstname
, rest
= self
.realname
.split(" ", 1)
329 if self
._emails
is None:
330 self
._emails
= self
.db
.query("SELECT * FROM users_emails WHERE user_id = %s", self
.id)
333 for email
in self
._emails
:
334 if not email
.primary
== "Y":
339 def set_email(self
, email
):
340 if email
== self
.email
:
343 self
.db
.execute("UPDATE users_emails SET email = %s \
344 WHERE user_id = %s AND primary = 'Y'", email
, self
.id)
346 self
.db
.execute("UPDATE users SET activated 'N' WHERE id = %s",
350 self
._data
= self
._emails
= None
352 # Inform the user, that he or she has to re-activate the account.
353 self
.send_activation_mail()
355 email
= property(get_email
, set_email
)
358 return self
.data
.state
360 def set_state(self
, state
):
361 self
.db
.execute("UPDATE users SET state = %s WHERE id = %s", state
,
363 self
.data
["state"] = state
365 state
= property(get_state
, set_state
)
367 def get_locale(self
):
368 return self
.data
.locale
or ""
370 def set_locale(self
, locale
):
371 self
.db
.execute("UPDATE users SET locale = %s WHERE id = %s", locale
,
373 self
.data
["locale"] = locale
375 locale
= property(get_locale
, set_locale
)
377 def get_timezone(self
, tz
=None):
379 tz
= self
.data
.timezone
or ""
382 tz
= pytz
.timezone(tz
)
383 except pytz
.UnknownTimeZoneError
:
384 tz
= pytz
.timezone("UTC")
388 def set_timezone(self
, timezone
):
389 if not timezone
is None:
390 tz
= self
.get_timezone(timezone
)
393 self
.db
.execute("UPDATE users SET timezone = %s WHERE id = %s",
396 timezone
= property(get_timezone
, set_timezone
)
400 return self
.data
.activated
== "Y"
403 def registered(self
):
404 return self
.data
.registered
406 def gravatar_icon(self
, size
=128):
408 gravatar_url
= "http://www.gravatar.com/avatar/" + \
409 hashlib
.md5(self
.email
.lower()).hexdigest() + "?"
410 gravatar_url
+= urllib
.urlencode({'d': "mm", 's': str(size
)})
415 return self
.state
== "admin"
418 return self
.state
== "tester"
422 if self
._perms
is None:
424 self
.db
.get("SELECT * FROM users_permissions WHERE user_id = %s", self
.id)
428 def has_perm(self
, perm
):
430 Returns True if the user has the requested permission.
432 # Admins have the permission for everything.
436 # Exception for voting. All testers are allowed to vote.
437 if perm
== "vote" and self
.is_tester():
440 # All others must be checked individually.
441 return self
.perms
.get(perm
, "N") == "Y"
443 def send_activation_mail(self
):
444 logging
.debug("Sending activation mail to %s" % self
.email
)
446 # Generate a random activation code.
447 source
= string
.ascii_letters
+ string
.digits
448 self
.data
["activation_code"] = "".join(random
.sample(source
* 20, 20))
449 self
.db
.execute("UPDATE users SET activation_code = %s WHERE id = %s",
450 self
.activation_code
, self
.id)
452 # Get the saved locale from the user.
453 locale
= tornado
.locale
.get(self
.locale
)
456 subject
= _("Account Activation")
458 message
= _("You, or somebody using your email address, has registered an account on the Pakfire Build Service.")
460 message
+= _("To activate your account, please click on the link below.")
462 message
+= " %(baseurl)s/user/%(name)s/activate?code=%(activation_code)s" \
463 % { "baseurl" : self
.settings
.get("baseurl"), "name" : self
.name
,
464 "activation_code" : self
.activation_code
, }
466 message
+= "Sincerely,\n The Pakfire Build Service"
468 self
.pakfire
.messages
.add("%s <%s>" % (self
.realname
, self
.email
), subject
, message
)
472 if __name__
== "__main__":
473 for password
in ("1234567890", "abcdefghij"):
474 digest
= generate_password_hash(password
)
476 print "%s %s" % (password
, digest
)
477 print " Matches? %s" % check_password_hash(password
, digest
)