Workout With Friends
Stay fit with a little motivation
 All Classes Namespaces Files Functions Variables Properties
auth.py
Go to the documentation of this file.
1 ##
2 #
3 # Auth related helpers used to login, logout, hash passwords, etc.
4 #
5 
6 from __future__ import unicode_literals
7 import bcrypt
8 import datetime
9 import random
10 import string
11 from pyramid.response import Response
12 from pyramid.security import authenticated_userid, forget, remember
13 from pyramid.threadlocal import get_current_request
14 from wowf.lib.mailer import send_mail
15 from wowf.lib.utils import current_timestamp, parse_timedelta
16 from wowf.models import Group, InviteToken, LoginToken, PasswordToken, User
17 
18 
19 LOGIN_GROUP = 'login'
20 DEFAULT_GROUPS = [LOGIN_GROUP]
21 
22 LOGIN_TOKEN_KEY = 'auth_token'
23 
24 LOGIN_TOKEN_LIFETIME = datetime.timedelta(days=30)
25 PASSWORD_TOKEN_LIFETIME = datetime.timedelta(hours=24)
26 INVITE_TOKEN_LIFETIME = datetime.timedelta(days=30)
27 
28 
29 class Auth(object):
30 
31  @staticmethod
32  ##
33  #
34  # Send invitations to the given emails on behalf of the user, if given,
35  # or on behalf of the system.
36  #
37  def send_invite(emails, user=None):
38  invite_token = InviteToken.create(user, INVITE_TOKEN_LIFETIME)
39  if user:
40  extra_headers = {'Reply-To': user.email}
41  else:
42  extra_headers = None
43  send_mail(subject="You've Been Invited To Join Workout With Friends", recipients=emails,
44  template='emails/invite.html',
45  variables=dict(user=user, token=invite_token.token,
46  age=parse_timedelta(INVITE_TOKEN_LIFETIME)),
47  extra_headers=extra_headers)
48 
49  @staticmethod
50  ##
51  #
52  # Check if the token is valid, and thus if the user is allowed to register.
53  #
54  def check_invite(token):
55  invite_token = InviteToken.get_by_token(token)
56  return invite_token and invite_token.is_valid()
57 
58  @staticmethod
59  ##
60  #
61  # Provide the user with the minimum privileges necessary to access the
62  # system.
63  #
64  # @param login Whether to return login headers
65  #
66  def register(user, login=True):
67  for group in DEFAULT_GROUPS:
68  user.groups.append(Group.get_by_name(group))
69  send_mail(subject='Welcome to Workout With Friends', recipients=user.email,
70  template='emails/welcome.html', variables=dict(user=user))
71  if login:
72  return Auth.login(user, remember_me=False)
73 
74  @staticmethod
75  ##
76  #
77  # Check the email/password combo, and whether the user has privileges
78  # necessary to access the system.
79  #
80  def check_login(email, password):
81  user = User.get_by_email(email)
82  if user and user.password == Auth.hash_password(password, user.password):
83  login_group = Group.get_by_name(LOGIN_GROUP)
84  return user and login_group in user.groups
85  return False
86 
87  @staticmethod
88  ##
89  #
90  # Start a new session for the user.
91  #
92  # Performs no authentication on the user, so authenticate the user
93  # proior to logging them in.
94  #
95  # @return Login response headers
96  #
97  def login(user, remember_me=False):
98  request = get_current_request()
99  response = Response()
100  response.headerlist.extend(remember(request, user.id))
101  if remember_me:
102  login_token = LoginToken.create(user, LOGIN_TOKEN_LIFETIME, request.user_agent)
103  response.set_cookie(LOGIN_TOKEN_KEY, login_token.token, max_age=LOGIN_TOKEN_LIFETIME)
104  if not user.is_active:
105  user.is_active = True
106  user.last_active_at = current_timestamp()
107  return response.headers
108 
109  @staticmethod
110  ##
111  #
112  # Log the user in based on the presence of a "remember me" token.
113  #
114  # Only works if the user chose to be remembered on last login, and a
115  # valid token exists.
116  #
117  # @return Response headers or None
118  #
119  def auto_login():
120  request = get_current_request()
121  token = request.cookies.get(LOGIN_TOKEN_KEY)
122  if token:
123  login_token = LoginToken.get_by_token(token)
124  if login_token and login_token.is_valid(request.user_agent):
125  return Auth.login(login_token.user, remember_me=False)
126 
127  @staticmethod
128  ##
129  #
130  # End the users session. Delete login token if one is found.
131  #
132  # @return Response headers
133  #
134  def logout():
135  request = get_current_request()
136  response = Response()
137  response.headerlist.extend(forget(request))
138  auth_token = request.cookies.get(LOGIN_TOKEN_KEY)
139  if auth_token:
140  response.delete_cookie(LOGIN_TOKEN_KEY)
141  token = LoginToken.get_by_token(auth_token)
142  if token:
143  token.delete()
144  return response.headers
145 
146  @staticmethod
147  ##
148  #
149  # Send an email to the user with unstructions on how to reset their
150  # password.
151  #
153  user = User.get_by_email(email)
154  password_token = PasswordToken.create(user, PASSWORD_TOKEN_LIFETIME)
155  send_mail(subject='Reset Password Request', recipients=user.email,
156  template='emails/request_password.html',
157  variables=dict(user=user, token=password_token.token,
158  age=parse_timedelta(PASSWORD_TOKEN_LIFETIME)))
159 
160  @staticmethod
161  ##
162  #
163  # Check the token, reset the password of the linked user, and email
164  # them a copy upon success.
165  #
166  # @return True or False
167  #
168  def reset_password(token):
169  is_success = False
170  password_token = PasswordToken.get_by_token(token)
171  if password_token and password_token.is_valid():
172  random_password = Auth.generate_random_password()
173  user = password_token.user
174  user.password = random_password
175  send_mail(subject='Temporary Password', recipients=user.email,
176  template='emails/temporary_password.html',
177  variables=dict(user=user, random_password=random_password))
178  password_token.delete()
179  is_success = True
180  return is_success
181 
182  @staticmethod
183  ##
184  #
185  # Hash the password.
186  #
187  # @param check_password Current password hash, used as salt
188  #
189  def hash_password(password, check_password=None):
190  if isinstance(password, unicode):
191  password = password.encode('utf-8')
192  if check_password is None:
193  # Creating a new password
194  salt = bcrypt.gensalt(12)
195  else:
196  # Checking the current password
197  salt = check_password
198  return bcrypt.hashpw(password, salt)
199 
200  @staticmethod
201  ##
202  #
203  # Returns a random password using the given characters.
204  #
205  # @param chars A sequence of acceptable characters
206  #
207  def generate_random_password(length=8, chars=None):
208  chars = chars or string.letters + string.digits + '!@#$*?'
209  return ''.join(random.sample(chars, length))
210 
211  @staticmethod
212  ##
213  #
214  # Get the currently logged in user.
215  #
216  # @return User instance or None
217  #
219  request = get_current_request()
220  user_id = authenticated_userid(request)
221  if user_id:
222  return User.get_by_id(user_id)
223