|
15 | 15 | from enum import Enum
|
16 | 16 | import json
|
17 | 17 | from functools import wraps
|
| 18 | +from hashlib import md5 |
18 | 19 | from django.conf.urls import url as re_path
|
19 |
| -from django.contrib.auth.hashers import check_password |
| 20 | +from django.contrib.auth.hashers import check_password as django_check_password |
| 21 | +from django.contrib.auth.hashers import is_password_usable |
20 | 22 | from django.db.models import Q
|
21 | 23 | from django.http import JsonResponse
|
22 | 24 | from django.views.decorators.csrf import csrf_exempt
|
|
29 | 31 | )
|
30 | 32 | from pycon.settings import MATRIX_AUTH_API_DEBUG as DEBUG
|
31 | 33 | from pycon.settings import MATRIX_AUTH_API_ALLOWED_IPS as ALLOWED_IPS
|
| 34 | +from pycon.settings import SECRET_KEY |
32 | 35 |
|
33 | 36 |
|
34 | 37 | # Error Codes
|
@@ -113,6 +116,57 @@ def wrapper(request, *args, **kwargs):
|
113 | 116 | return wrapper
|
114 | 117 |
|
115 | 118 |
|
| 119 | +def check_user_password(user, password): |
| 120 | + # Two options: either our User has a valid password, in which case we do |
| 121 | + # check it, or not, in which case we check it against the generated passwd. |
| 122 | + if not is_password_usable(user.password): |
| 123 | + return password == generate_matrix_password(user) |
| 124 | + return django_check_password(password, user.password) |
| 125 | + |
| 126 | + |
| 127 | +def get_assigned_tickets(user, conference): |
| 128 | + return Ticket.objects.filter( |
| 129 | + Q(fare__conference=conference.code) |
| 130 | + & Q(frozen=False) # i.e. the ticket was not cancelled |
| 131 | + & Q(orderitem__order___complete=True) # i.e. they paid |
| 132 | + & Q(user=user) # i.e. assigned to user |
| 133 | + ) |
| 134 | + |
| 135 | + |
| 136 | +def is_speaker(user, conference): |
| 137 | + # A speaker is a user with at least one accepted talk in the current |
| 138 | + # conference. |
| 139 | + try: |
| 140 | + speaker = user.speaker |
| 141 | + except Speaker.DoesNotExist: |
| 142 | + return False |
| 143 | + return TalkSpeaker.objects.filter( |
| 144 | + speaker=speaker, |
| 145 | + talk__conference=conference.code, |
| 146 | + talk__status='accepted' |
| 147 | + ).count() > 0 |
| 148 | + |
| 149 | + |
| 150 | +def generate_matrix_password(user): |
| 151 | + """ |
| 152 | + Create a temporary password for `user` to that they can login into our |
| 153 | + matrix chat server using their email address and that password. This is |
| 154 | + only needed for social auth users since they do not have a valid password |
| 155 | + in our database. |
| 156 | +
|
| 157 | + The generated passowrd is not stored anywhere. |
| 158 | + """ |
| 159 | + def n_base_b(n, b, nums='0123456789abcdefghijklmnopqrstuvwxyz'): |
| 160 | + """Return `n` in base `b`.""" |
| 161 | + |
| 162 | + return ((n == 0) and nums[0]) or \ |
| 163 | + (n_base_b(n // b, b, nums).lstrip(nums[0]) + nums[n % b]) |
| 164 | + |
| 165 | + encoded = md5(str(user.email + SECRET_KEY).encode()).hexdigest() |
| 166 | + n = int(encoded, 16) |
| 167 | + return n_base_b(n, 36) |
| 168 | + |
| 169 | + |
116 | 170 | @csrf_exempt
|
117 | 171 | @ensure_post
|
118 | 172 | @ensure_https_in_ops
|
@@ -171,44 +225,22 @@ def isauth(request):
|
171 | 225 | return _error(ApiError.AUTH_ERROR, 'unknown user')
|
172 | 226 |
|
173 | 227 | # Is the password OK?
|
174 |
| - if not check_password(data['password'], profile.user.password): |
| 228 | + if not check_user_password(profile.user, data['password']): |
175 | 229 | return _error(ApiError.AUTH_ERROR, 'authentication error')
|
176 | 230 |
|
177 |
| - # Get the tickets **assigned** to the user |
178 | 231 | conference = Conference.objects.current()
|
179 |
| - |
180 |
| - tickets = Ticket.objects.filter( |
181 |
| - Q(fare__conference=conference.code) |
182 |
| - & Q(frozen=False) # i.e. the ticket was not cancelled |
183 |
| - & Q(orderitem__order___complete=True) # i.e. they paid |
184 |
| - & Q(user=profile.user) # i.e. assigned to user |
185 |
| - ) |
186 |
| - |
187 |
| - # A speaker is a user with at least one accepted talk in the current |
188 |
| - # conference. |
189 |
| - try: |
190 |
| - speaker = profile.user.speaker |
191 |
| - except Speaker.DoesNotExist: |
192 |
| - is_speaker = False |
193 |
| - else: |
194 |
| - is_speaker = TalkSpeaker.objects.filter( |
195 |
| - speaker=speaker, |
196 |
| - talk__conference=conference.code, |
197 |
| - talk__status='accepted' |
198 |
| - ).count() > 0 |
199 |
| - |
200 | 232 | payload = {
|
201 | 233 | "username": profile.user.username,
|
202 | 234 | "first_name": profile.user.first_name,
|
203 | 235 | "last_name": profile.user.last_name,
|
204 | 236 | "email": profile.user.email,
|
205 | 237 | "is_staff": profile.user.is_staff,
|
206 |
| - "is_speaker": is_speaker, |
| 238 | + "is_speaker": is_speaker(profile.user, conference), |
207 | 239 | "is_active": profile.user.is_active,
|
208 | 240 | "is_minor": profile.is_minor,
|
209 | 241 | "tickets": [
|
210 | 242 | {"fare_name": t.fare.name, "fare_code": t.fare.code}
|
211 |
| - for t in tickets |
| 243 | + for t in get_assigned_tickets(profile.user, conference) |
212 | 244 | ]
|
213 | 245 | }
|
214 | 246 |
|
|
0 commit comments