Passed
Push — master ( 08fcc0...b4637d )
by Guangyu
07:24 queued 12s
created

core.user.ChangePassword.__init__()   A

Complexity

Conditions 1

Size

Total Lines 4
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 3
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
nop 0
1
import falcon
2
import simplejson as json
3
import mysql.connector
4
import config
5
import uuid
6
import hashlib
7
import re
8
import os
9
from datetime import datetime, timedelta, timezone
10
from core.useractivity import user_logger, write_log, access_control
11
12
13
class UserCollection:
14
    @staticmethod
15
    def __init__():
16
        """Initializes Class"""
17
        pass
18
19
    @staticmethod
20
    def on_options(req, resp):
21
        resp.status = falcon.HTTP_200
22
23
    @staticmethod
24
    def on_get(req, resp):
25
        access_control(req)
26
        cnx = mysql.connector.connect(**config.myems_user_db)
27
        cursor = cnx.cursor()
28
        query = (" SELECT u.id, u.name, u.display_name, u.uuid, "
29
                 "        u.email, u.is_admin, p.id, p.name, "
30
                 "        u.account_expiration_datetime_utc, u.password_expiration_datetime_utc "
31
                 " FROM tbl_users u "
32
                 " LEFT JOIN tbl_privileges p ON u.privilege_id = p.id "
33
                 " ORDER BY u.name ")
34
        cursor.execute(query)
35
        rows = cursor.fetchall()
36
        cursor.close()
37
        cnx.disconnect()
38
39
        timezone_offset = int(config.utc_offset[1:3]) * 60 + int(config.utc_offset[4:6])
40
        if config.utc_offset[0] == '-':
41
            timezone_offset = -timezone_offset
42
43
        result = list()
44
        if rows is not None and len(rows) > 0:
45
            for row in rows:
46
                account_expiration_datetime_local = row[8].replace(tzinfo=timezone.utc) + \
47
                    timedelta(minutes=timezone_offset)
48
                password_expiration_datetime_local = row[9].replace(tzinfo=timezone.utc) + \
49
                    timedelta(minutes=timezone_offset)
50
                meta_result = {"id": row[0],
51
                               "name": row[1],
52
                               "display_name": row[2],
53
                               "uuid": row[3],
54
                               "email": row[4],
55
                               "is_admin": True if row[5] else False,
56
                               "privilege": {
57
                                   "id": row[6],
58
                                   "name": row[7]} if row[6] is not None else None,
59
                               "account_expiration_datetime":
60
                                   account_expiration_datetime_local.strftime('%Y-%m-%dT%H:%M:%S'),
61
                               "password_expiration_datetime":
62
                                   password_expiration_datetime_local.strftime('%Y-%m-%dT%H:%M:%S')}
63
                result.append(meta_result)
64
65
        resp.text = json.dumps(result)
66
67
    @staticmethod
68
    def on_post(req, resp):
69
        """Handles POST requests"""
70
        access_control(req)
71
        # todo: add user log
72
        try:
73
            raw_json = req.stream.read().decode('utf-8')
74
        except Exception as ex:
75
            raise falcon.HTTPError(falcon.HTTP_400, title='API.EXCEPTION', description=ex)
76
77
        new_values = json.loads(raw_json)
78
79
        if 'name' not in new_values['data'].keys() or \
80
                not isinstance(new_values['data']['name'], str) or \
81
                len(str.strip(new_values['data']['name'])) == 0:
82
            raise falcon.HTTPError(falcon.HTTP_400, title='API.BAD_REQUEST',
83
                                   description='API.INVALID_USER_NAME')
84
        name = str.strip(new_values['data']['name'])
85
86
        if 'display_name' not in new_values['data'].keys() or \
87
                not isinstance(new_values['data']['display_name'], str) or \
88
                len(str.strip(new_values['data']['display_name'])) == 0:
89
            raise falcon.HTTPError(falcon.HTTP_400, title='API.BAD_REQUEST',
90
                                   description='API.INVALID_DISPLAY_NAME')
91
        display_name = str.strip(new_values['data']['display_name'])
92
93
        if 'email' not in new_values['data'].keys() or \
94
                not isinstance(new_values['data']['email'], str) or \
95
                len(str.strip(new_values['data']['email'])) == 0:
96
            raise falcon.HTTPError(falcon.HTTP_400, title='API.BAD_REQUEST',
97
                                   description='API.INVALID_EMAIL')
98
        email = str.lower(str.strip(new_values['data']['email']))
99
100
        match = re.match(r'^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,4})$', email)
101
        if match is None:
102
            raise falcon.HTTPError(falcon.HTTP_400, title='API.BAD_REQUEST',
103
                                   description='API.INVALID_EMAIL')
104
105
        if 'is_admin' not in new_values['data'].keys() or \
106
                not isinstance(new_values['data']['is_admin'], bool):
107
            raise falcon.HTTPError(falcon.HTTP_400, title='API.BAD_REQUEST',
108
                                   description='API.INVALID_IS_ADMIN_VALUE')
109
        is_admin = new_values['data']['is_admin']
110
111
        if 'privilege_id' in new_values['data'].keys():
112
            if not isinstance(new_values['data']['privilege_id'], int) or \
113
                    new_values['data']['privilege_id'] <= 0:
114
                raise falcon.HTTPError(falcon.HTTP_400, title='API.BAD_REQUEST',
115
                                       description='API.INVALID_PRIVILEGE_ID')
116
            privilege_id = new_values['data']['privilege_id']
117
        else:
118
            privilege_id = None
119
120
        timezone_offset = int(config.utc_offset[1:3]) * 60 + int(config.utc_offset[4:6])
121
        if config.utc_offset[0] == '-':
122
            timezone_offset = -timezone_offset
123
124
        account_expiration_datetime = datetime.strptime(new_values['data']['account_expiration_datetime'],
125
                                                        '%Y-%m-%dT%H:%M:%S')
126
        account_expiration_datetime = account_expiration_datetime.replace(tzinfo=timezone.utc)
127
        account_expiration_datetime -= timedelta(minutes=timezone_offset)
128
129
        password_expiration_datetime = datetime.strptime(new_values['data']['password_expiration_datetime'],
130
                                                         '%Y-%m-%dT%H:%M:%S')
131
        password_expiration_datetime = password_expiration_datetime.replace(tzinfo=timezone.utc)
132
        password_expiration_datetime -= timedelta(minutes=timezone_offset)
133
134
        cnx = mysql.connector.connect(**config.myems_user_db)
135
        cursor = cnx.cursor()
136
137
        cursor.execute(" SELECT name "
138
                       " FROM tbl_users "
139
                       " WHERE name = %s ", (name,))
140
        if cursor.fetchone() is not None:
141
            cursor.close()
142
            cnx.disconnect()
143
            raise falcon.HTTPError(falcon.HTTP_404, title='API.BAD_REQUEST',
144
                                   description='API.USER_NAME_IS_ALREADY_IN_USE')
145
146
        cursor.execute(" SELECT name "
147
                       " FROM tbl_users "
148
                       " WHERE email = %s ", (email,))
149
        if cursor.fetchone() is not None:
150
            cursor.close()
151
            cnx.disconnect()
152
            raise falcon.HTTPError(falcon.HTTP_404, title='API.BAD_REQUEST',
153
                                   description='API.EMAIL_IS_ALREADY_IN_USE')
154
155
        if privilege_id is not None:
156
            cursor.execute(" SELECT name "
157
                           " FROM tbl_privileges "
158
                           " WHERE id = %s ",
159
                           (privilege_id,))
160
            if cursor.fetchone() is None:
161
                cursor.close()
162
                cnx.disconnect()
163
                raise falcon.HTTPError(falcon.HTTP_404, title='API.NOT_FOUND',
164
                                       description='API.PRIVILEGE_NOT_FOUND')
165
166
        add_row = (" INSERT INTO tbl_users "
167
                   "     (name, uuid, display_name, email, salt, password, is_admin, privilege_id, "
168
                   "      account_expiration_datetime_utc, password_expiration_datetime_utc) "
169
                   " VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s) ")
170
171
        salt = uuid.uuid4().hex
172
        password = new_values['data']['password']
173
        hashed_password = hashlib.sha512(salt.encode() + password.encode()).hexdigest()
174
175
        cursor.execute(add_row, (name,
176
                                 str(uuid.uuid4()),
177
                                 display_name,
178
                                 email,
179
                                 salt,
180
                                 hashed_password,
181
                                 is_admin,
182
                                 privilege_id,
183
                                 account_expiration_datetime,
184
                                 password_expiration_datetime))
185
        new_id = cursor.lastrowid
186
        cnx.commit()
187
        cursor.close()
188
        cnx.disconnect()
189
190
        resp.status = falcon.HTTP_201
191
        resp.location = '/users/' + str(new_id)
192
193
194
class UserItem:
195
    @staticmethod
196
    def __init__():
197
        """Initializes Class"""
198
        pass
199
200
    @staticmethod
201
    def on_options(req, resp, id_):
202
        resp.status = falcon.HTTP_200
203
204
    @staticmethod
205
    def on_get(req, resp, id_):
206
        access_control(req)
207
        if not id_.isdigit() or int(id_) <= 0:
208
            raise falcon.HTTPError(falcon.HTTP_400, title='API.BAD_REQUEST',
209
                                   description='API.INVALID_USER_ID')
210
211
        cnx = mysql.connector.connect(**config.myems_user_db)
212
        cursor = cnx.cursor()
213
214
        query = (" SELECT u.id, u.name, u.display_name, u.uuid, "
215
                 "        u.email, u.is_admin, p.id, p.name, "
216
                 "        u.account_expiration_datetime_utc, u.password_expiration_datetime_utc "
217
                 " FROM tbl_users u "
218
                 " LEFT JOIN tbl_privileges p ON u.privilege_id = p.id "
219
                 " WHERE u.id =%s ")
220
        cursor.execute(query, (id_,))
221
        row = cursor.fetchone()
222
        cursor.close()
223
        cnx.disconnect()
224
225
        if row is None:
226
            raise falcon.HTTPError(falcon.HTTP_404, title='API.NOT_FOUND',
227
                                   description='API.USER_NOT_FOUND')
228
        timezone_offset = int(config.utc_offset[1:3]) * 60 + int(config.utc_offset[4:6])
229
        if config.utc_offset[0] == '-':
230
            timezone_offset = -timezone_offset
231
232
        account_expiration_datetime_local = row[8].replace(tzinfo=timezone.utc) + timedelta(minutes=timezone_offset)
233
        password_expiration_datetime_local = row[9].replace(tzinfo=timezone.utc) + timedelta(minutes=timezone_offset)
234
235
        result = {"id": row[0],
236
                  "name": row[1],
237
                  "display_name": row[2],
238
                  "uuid": row[3],
239
                  "email": row[4],
240
                  "is_admin": True if row[5] else False,
241
                  "privilege": {
242
                      "id": row[6],
243
                      "name": row[7]} if row[6] is not None else None,
244
                  "account_expiration_datetime": account_expiration_datetime_local.strftime('%Y-%m-%dT%H:%M:%S'),
245
                  "password_expiration_datetime": password_expiration_datetime_local.strftime('%Y-%m-%dT%H:%M:%S')}
246
        resp.text = json.dumps(result)
247
248
    @staticmethod
249
    @user_logger
250
    def on_delete(req, resp, id_):
251
        access_control(req)
252
        if not id_.isdigit() or int(id_) <= 0:
253
            raise falcon.HTTPError(falcon.HTTP_400, title='API.BAD_REQUEST',
254
                                   description='API.INVALID_USER_ID')
255
256
        cnx = mysql.connector.connect(**config.myems_user_db)
257
        cursor = cnx.cursor()
258
259
        cursor.execute(" SELECT name "
260
                       " FROM tbl_users "
261
                       " WHERE id = %s ", (id_,))
262
        if cursor.fetchone() is None:
263
            cursor.close()
264
            cnx.disconnect()
265
            raise falcon.HTTPError(falcon.HTTP_404, title='API.NOT_FOUND',
266
                                   description='API.USER_NOT_FOUND')
267
268
        # TODO: delete associated objects
269
        cursor.execute(" DELETE FROM tbl_users WHERE id = %s ", (id_,))
270
        cnx.commit()
271
272
        cursor.close()
273
        cnx.disconnect()
274
275
        resp.status = falcon.HTTP_204
276
277
    @staticmethod
278
    @user_logger
279
    def on_put(req, resp, id_):
280
        """Handles PUT requests"""
281
        access_control(req)
282
        try:
283
            raw_json = req.stream.read().decode('utf-8')
284
        except Exception as ex:
285
            raise falcon.HTTPError(falcon.HTTP_400, title='API.EXCEPTION', description=ex)
286
287
        if not id_.isdigit() or int(id_) <= 0:
288
            raise falcon.HTTPError(falcon.HTTP_400, title='API.BAD_REQUEST',
289
                                   description='API.INVALID_USER_ID')
290
291
        new_values = json.loads(raw_json)
292
293
        if 'name' not in new_values['data'].keys() or \
294
                not isinstance(new_values['data']['name'], str) or \
295
                len(str.strip(new_values['data']['name'])) == 0:
296
            raise falcon.HTTPError(falcon.HTTP_400, title='API.BAD_REQUEST',
297
                                   description='API.INVALID_USER_NAME')
298
        name = str.strip(new_values['data']['name'])
299
300
        if 'display_name' not in new_values['data'].keys() or \
301
                not isinstance(new_values['data']['display_name'], str) or \
302
                len(str.strip(new_values['data']['display_name'])) == 0:
303
            raise falcon.HTTPError(falcon.HTTP_400, title='API.BAD_REQUEST',
304
                                   description='API.INVALID_DISPLAY_NAME')
305
        display_name = str.strip(new_values['data']['display_name'])
306
307
        if 'email' not in new_values['data'].keys() or \
308
                not isinstance(new_values['data']['email'], str) or \
309
                len(str.strip(new_values['data']['email'])) == 0:
310
            raise falcon.HTTPError(falcon.HTTP_400, title='API.BAD_REQUEST',
311
                                   description='API.INVALID_EMAIL')
312
        email = str.lower(str.strip(new_values['data']['email']))
313
314
        match = re.match(r'^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,4})$', email)
315
        if match is None:
316
            raise falcon.HTTPError(falcon.HTTP_400, title='API.BAD_REQUEST',
317
                                   description='API.INVALID_EMAIL')
318
319
        if 'is_admin' not in new_values['data'].keys() or \
320
                not isinstance(new_values['data']['is_admin'], bool):
321
            raise falcon.HTTPError(falcon.HTTP_400, title='API.BAD_REQUEST',
322
                                   description='API.INVALID_IS_ADMIN_VALUE')
323
        is_admin = new_values['data']['is_admin']
324
325
        if 'privilege_id' in new_values['data'].keys():
326
            if not isinstance(new_values['data']['privilege_id'], int) or \
327
                    new_values['data']['privilege_id'] <= 0:
328
                raise falcon.HTTPError(falcon.HTTP_400, title='API.BAD_REQUEST',
329
                                       description='API.INVALID_PRIVILEGE_ID')
330
            privilege_id = new_values['data']['privilege_id']
331
        else:
332
            privilege_id = None
333
334
        timezone_offset = int(config.utc_offset[1:3]) * 60 + int(config.utc_offset[4:6])
335
        if config.utc_offset[0] == '-':
336
            timezone_offset = -timezone_offset
337
338
        account_expiration_datetime = datetime.strptime(new_values['data']['account_expiration_datetime'],
339
                                                        '%Y-%m-%dT%H:%M:%S')
340
        account_expiration_datetime = account_expiration_datetime.replace(tzinfo=timezone.utc)
341
        account_expiration_datetime -= timedelta(minutes=timezone_offset)
342
343
        password_expiration_datetime = datetime.strptime(new_values['data']['password_expiration_datetime'],
344
                                                         '%Y-%m-%dT%H:%M:%S')
345
        password_expiration_datetime = password_expiration_datetime.replace(tzinfo=timezone.utc)
346
        password_expiration_datetime -= timedelta(minutes=timezone_offset)
347
348
        cnx = mysql.connector.connect(**config.myems_user_db)
349
        cursor = cnx.cursor()
350
351
        cursor.execute(" SELECT name "
352
                       " FROM tbl_users "
353
                       " WHERE id = %s ", (id_,))
354
        if cursor.fetchone() is None:
355
            cursor.close()
356
            cnx.disconnect()
357
            raise falcon.HTTPError(falcon.HTTP_404, title='API.NOT_FOUND',
358
                                   description='API.USER_NOT_FOUND')
359
360
        cursor.execute(" SELECT name "
361
                       " FROM tbl_users "
362
                       " WHERE name = %s AND id != %s ", (name, id_))
363
        if cursor.fetchone() is not None:
364
            cursor.close()
365
            cnx.disconnect()
366
            raise falcon.HTTPError(falcon.HTTP_404, title='API.BAD_REQUEST',
367
                                   description='API.USER_NAME_IS_ALREADY_IN_USE')
368
369
        cursor.execute(" SELECT name "
370
                       " FROM tbl_users "
371
                       " WHERE email = %s AND id != %s ", (email, id_))
372
        if cursor.fetchone() is not None:
373
            cursor.close()
374
            cnx.disconnect()
375
            raise falcon.HTTPError(falcon.HTTP_404, title='API.BAD_REQUEST',
376
                                   description='API.EMAIL_IS_ALREADY_IN_USE')
377
378
        if privilege_id is not None:
379
            cursor.execute(" SELECT name "
380
                           " FROM tbl_privileges "
381
                           " WHERE id = %s ",
382
                           (privilege_id,))
383
            if cursor.fetchone() is None:
384
                cursor.close()
385
                cnx.disconnect()
386
                raise falcon.HTTPError(falcon.HTTP_404, title='API.NOT_FOUND',
387
                                       description='API.PRIVILEGE_NOT_FOUND')
388
389
        update_row = (" UPDATE tbl_users "
390
                      " SET name = %s, display_name = %s, email = %s, "
391
                      "     is_admin = %s, privilege_id = %s,"
392
                      "     account_expiration_datetime_utc = %s, "
393
                      "     password_expiration_datetime_utc = %s "
394
                      " WHERE id = %s ")
395
        cursor.execute(update_row, (name,
396
                                    display_name,
397
                                    email,
398
                                    is_admin,
399
                                    privilege_id,
400
                                    account_expiration_datetime,
401
                                    password_expiration_datetime,
402
                                    id_,))
403
        cnx.commit()
404
405
        cursor.close()
406
        cnx.disconnect()
407
408
        resp.status = falcon.HTTP_200
409
410
411
class UserLogin:
412
    @staticmethod
413
    def __init__():
414
        """Initializes Class"""
415
        pass
416
417
    @staticmethod
418
    def on_options(req, resp):
419
        resp.status = falcon.HTTP_200
420
421
    @staticmethod
422
    def on_put(req, resp):
423
        """Handles PUT requests"""
424
        try:
425
            raw_json = req.stream.read().decode('utf-8')
426
            new_values = json.loads(raw_json)
427
        except Exception as ex:
428
            raise falcon.HTTPError(falcon.HTTP_400, title='API.EXCEPTION', description=ex)
429
430
        if not isinstance(new_values['data']['password'], str) or \
431
                len(str.strip(new_values['data']['password'])) == 0:
432
            raise falcon.HTTPError(falcon.HTTP_400, title='API.BAD_REQUEST',
433
                                   description='API.INVALID_PASSWORD')
434
435
        cnx = mysql.connector.connect(**config.myems_user_db)
436
        cursor = cnx.cursor()
437
        result = dict()
438
439
        if 'name' in new_values['data']:
440
441
            if not isinstance(new_values['data']['name'], str) or \
442
                    len(str.strip(new_values['data']['name'])) == 0:
443
                raise falcon.HTTPError(falcon.HTTP_400, title='API.BAD_REQUEST',
444
                                       description='API.INVALID_USER_NAME')
445
446
            query = (" SELECT id, name, uuid, display_name, email, salt, password, is_admin, "
447
                     "        account_expiration_datetime_utc, password_expiration_datetime_utc "
448
                     " FROM tbl_users "
449
                     " WHERE name = %s ")
450
            cursor.execute(query, (str.strip(new_values['data']['name']).lower(),))
451
            row = cursor.fetchone()
452
            if row is None:
453
                cursor.close()
454
                cnx.disconnect()
455
                raise falcon.HTTPError(falcon.HTTP_404, 'API.ERROR', 'API.USER_NOT_FOUND')
456
457
            result = {"id": row[0],
458
                      "name": row[1],
459
                      "uuid": row[2],
460
                      "display_name": row[3],
461
                      "email": row[4],
462
                      "salt": row[5],
463
                      "password": row[6],
464
                      "is_admin": True if row[7] else False,
465
                      "account_expiration_datetime_utc": row[8],
466
                      "password_expiration_datetime_utc": row[9]}
467
468
        elif 'email' in new_values['data']:
469
            if not isinstance(new_values['data']['email'], str) or \
470
                    len(str.strip(new_values['data']['email'])) == 0:
471
                raise falcon.HTTPError(falcon.HTTP_400, title='API.BAD_REQUEST',
472
                                       description='API.INVALID_EMAIL')
473
474
            query = (" SELECT id, name, uuid, display_name, email, salt, password, is_admin, "
475
                     "        account_expiration_datetime_utc, password_expiration_datetime_utc "
476
                     " FROM tbl_users "
477
                     " WHERE email = %s ")
478
            cursor.execute(query, (str.strip(new_values['data']['email']).lower(),))
479
            row = cursor.fetchone()
480
            if row is None:
481
                cursor.close()
482
                cnx.disconnect()
483
                raise falcon.HTTPError(falcon.HTTP_404, 'API.ERROR', 'API.USER_NOT_FOUND')
484
485
            result = {"id": row[0],
486
                      "name": row[1],
487
                      "uuid": row[2],
488
                      "display_name": row[3],
489
                      "email": row[4],
490
                      "salt": row[5],
491
                      "password": row[6],
492
                      "is_admin": True if row[7] else False,
493
                      "account_expiration_datetime_utc": row[8],
494
                      "password_expiration_datetime_utc": row[9]}
495
        else:
496
            cursor.close()
497
            cnx.disconnect()
498
            raise falcon.HTTPError(falcon.HTTP_400, title='API.BAD_REQUEST',
499
                                   description='API.INVALID_USER_NAME_OR_EMAIL')
500
501
        salt = result['salt']
502
        password = str.strip(new_values['data']['password'])
503
        hashed_password = hashlib.sha512(salt.encode() + password.encode()).hexdigest()
504
505
        if hashed_password != result['password']:
506
            cursor.close()
507
            cnx.disconnect()
508
            raise falcon.HTTPError(falcon.HTTP_400, 'API.BAD_REQUEST', 'API.INVALID_PASSWORD')
509
510
        if result['account_expiration_datetime_utc'] <= datetime.utcnow():
511
            cursor.close()
512
            cnx.disconnect()
513
            raise falcon.HTTPError(falcon.HTTP_400, 'API.BAD_REQUEST', 'API.USER_ACCOUNT_HAS_EXPIRED')
514
515
        if result['password_expiration_datetime_utc'] <= datetime.utcnow():
516
            cursor.close()
517
            cnx.disconnect()
518
            raise falcon.HTTPError(falcon.HTTP_400, 'API.BAD_REQUEST', 'API.USER_PASSWORD_HAS_EXPIRED')
519
520
        add_session = (" INSERT INTO tbl_sessions "
521
                       "             (user_uuid, token, utc_expires) "
522
                       " VALUES (%s, %s, %s) ")
523
        user_uuid = result['uuid']
524
        token = hashlib.sha512(os.urandom(24)).hexdigest()
525
        utc_expires = datetime.utcnow() + timedelta(seconds=60 * 60 * 8)
526
        cursor.execute(add_session, (user_uuid, token, utc_expires))
527
        cnx.commit()
528
        cursor.close()
529
        cnx.disconnect()
530
        del result['salt']
531
        del result['password']
532
533
        timezone_offset = int(config.utc_offset[1:3]) * 60 + int(config.utc_offset[4:6])
534
        if config.utc_offset[0] == '-':
535
            timezone_offset = -timezone_offset
536
537
        result['account_expiration_datetime'] = \
538
            (result['account_expiration_datetime_utc'].replace(tzinfo=timezone.utc) +
539
             timedelta(minutes=timezone_offset)).strftime('%Y-%m-%dT%H:%M:%S')
540
        del result['account_expiration_datetime_utc']
541
542
        result['password_expiration_datetime'] = \
543
            (result['password_expiration_datetime_utc'].replace(tzinfo=timezone.utc) +
544
             timedelta(minutes=timezone_offset)).strftime('%Y-%m-%dT%H:%M:%S')
545
        del result['password_expiration_datetime_utc']
546
547
        result['token'] = token
548
549
        resp.text = json.dumps(result)
550
        resp.status = falcon.HTTP_200
551
        write_log(user_uuid=user_uuid, request_method='PUT', resource_type='UserLogin',
552
                  resource_id=None, request_body=None)
553
554
555
class UserLogout:
556
    @staticmethod
557
    def __init__():
558
        """Initializes Class"""
559
        pass
560
561
    @staticmethod
562
    def on_options(req, resp):
563
        resp.status = falcon.HTTP_200
564
565
    @staticmethod
566
    @user_logger
567
    def on_put(req, resp):
568
        """Handles PUT requests"""
569
570
        if 'USER-UUID' not in req.headers or \
571
                not isinstance(req.headers['USER-UUID'], str) or \
572
                len(str.strip(req.headers['USER-UUID'])) == 0:
573
            raise falcon.HTTPError(falcon.HTTP_400, title='API.BAD_REQUEST',
574
                                   description='API.INVALID_USER_UUID')
575
        user_uuid = str.strip(req.headers['USER-UUID'])
576
577
        if 'TOKEN' not in req.headers or \
578
                not isinstance(req.headers['TOKEN'], str) or \
579
                len(str.strip(req.headers['TOKEN'])) == 0:
580
            raise falcon.HTTPError(falcon.HTTP_400, title='API.BAD_REQUEST',
581
                                   description='API.INVALID_TOKEN')
582
        token = str.strip(req.headers['TOKEN'])
583
584
        cnx = mysql.connector.connect(**config.myems_user_db)
585
        cursor = cnx.cursor()
586
        query = (" DELETE FROM tbl_sessions "
587
                 " WHERE user_uuid = %s AND token = %s ")
588
        cursor.execute(query, (user_uuid, token,))
589
        rowcount = cursor.rowcount
590
        cnx.commit()
591
        cursor.close()
592
        cnx.disconnect()
593
        if rowcount is None or rowcount == 0:
594
            raise falcon.HTTPError(falcon.HTTP_404, title='API.NOT_FOUND',
595
                                   description='API.USER_SESSION_NOT_FOUND')
596
        resp.text = json.dumps("OK")
597
        resp.status = falcon.HTTP_200
598
599
600
class ChangePassword:
601
    @staticmethod
602
    def __init__():
603
        """Initializes Class"""
604
        pass
605
606
    @staticmethod
607
    def on_options(req, resp):
608
        resp.status = falcon.HTTP_200
609
610
    @staticmethod
611
    def on_put(req, resp):
612
        """Handles PUT requests"""
613
        if 'USER-UUID' not in req.headers or \
614
                not isinstance(req.headers['USER-UUID'], str) or \
615
                len(str.strip(req.headers['USER-UUID'])) == 0:
616
            raise falcon.HTTPError(falcon.HTTP_400, title='API.BAD_REQUEST',
617
                                   description='API.INVALID_USER_UUID')
618
        user_uuid = str.strip(req.headers['USER-UUID'])
619
620
        if 'TOKEN' not in req.headers or \
621
                not isinstance(req.headers['TOKEN'], str) or \
622
                len(str.strip(req.headers['TOKEN'])) == 0:
623
            raise falcon.HTTPError(falcon.HTTP_400, title='API.BAD_REQUEST',
624
                                   description='API.INVALID_TOKEN')
625
        token = str.strip(req.headers['TOKEN'])
626
627
        try:
628
            raw_json = req.stream.read().decode('utf-8')
629
            new_values = json.loads(raw_json)
630
        except Exception as ex:
631
            raise falcon.HTTPError(falcon.HTTP_400, 'API.ERROR', ex.args)
632
633
        if 'old_password' not in new_values['data'] or \
634
                not isinstance(new_values['data']['old_password'], str) or \
635
                len(str.strip(new_values['data']['old_password'])) == 0:
636
            raise falcon.HTTPError(falcon.HTTP_400, title='API.BAD_REQUEST',
637
                                   description='API.INVALID_OLD_PASSWORD')
638
        old_password = str.strip(new_values['data']['old_password'])
639
640
        if 'new_password' not in new_values['data'] or \
641
                not isinstance(new_values['data']['new_password'], str) or \
642
                len(str.strip(new_values['data']['new_password'])) == 0:
643
            raise falcon.HTTPError(falcon.HTTP_400, title='API.BAD_REQUEST',
644
                                   description='API.INVALID_NEW_PASSWORD')
645
        new_password = str.strip(new_values['data']['new_password'])
646
647
        # Verify User Session
648
649
        cnx = mysql.connector.connect(**config.myems_user_db)
650
        cursor = cnx.cursor()
651
        query = (" SELECT utc_expires "
652
                 " FROM tbl_sessions "
653
                 " WHERE user_uuid = %s AND token = %s")
654
        cursor.execute(query, (user_uuid, token,))
655
        row = cursor.fetchone()
656
657 View Code Duplication
        if row is None:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
658
            cursor.close()
659
            cnx.disconnect()
660
            raise falcon.HTTPError(falcon.HTTP_404, title='API.NOT_FOUND',
661
                                   description='API.USER_SESSION_NOT_FOUND')
662
        else:
663
            utc_expires = row[0]
664
            if datetime.utcnow() > utc_expires:
665
                cursor.close()
666
                cnx.disconnect()
667
                raise falcon.HTTPError(falcon.HTTP_400, title='API.BAD_REQUEST',
668
                                       description='API.USER_SESSION_TIMEOUT')
669
670
        query = (" SELECT salt, password "
671
                 " FROM tbl_users "
672
                 " WHERE uuid = %s ")
673
        cursor.execute(query, (user_uuid,))
674
        row = cursor.fetchone()
675
        if row is None:
676
            cursor.close()
677
            cnx.disconnect()
678
            raise falcon.HTTPError(falcon.HTTP_404, 'API.NOT_FOUND', 'API.USER_NOT_FOUND')
679
680
        result = {'salt': row[0], 'password': row[1]}
681
682
        # verify old password
683
        salt = result['salt']
684
        hashed_password = hashlib.sha512(salt.encode() + old_password.encode()).hexdigest()
685
686
        if hashed_password != result['password']:
687
            cursor.close()
688
            cnx.disconnect()
689
            raise falcon.HTTPError(falcon.HTTP_400, 'API.BAD_REQUEST', 'API.INVALID_OLD_PASSWORD')
690
691
        # Update User password
692
        salt = uuid.uuid4().hex
693
        hashed_password = hashlib.sha512(salt.encode() + new_password.encode()).hexdigest()
694
695
        update_user = (" UPDATE tbl_users "
696
                       " SET salt = %s, password = %s "
697
                       " WHERE uuid = %s ")
698
        cursor.execute(update_user, (salt, hashed_password, user_uuid,))
699
        cnx.commit()
700
701
        # Refresh User session
702
        update_session = (" UPDATE tbl_sessions "
703
                          " SET utc_expires = %s "
704
                          " WHERE user_uuid = %s AND token = %s ")
705
        utc_expires = datetime.utcnow() + timedelta(seconds=1000 * 60 * 60 * 8)
706
        cursor.execute(update_session, (utc_expires, user_uuid, token, ))
707
        cnx.commit()
708
709
        cursor.close()
710
        cnx.disconnect()
711
        resp.text = json.dumps("OK")
712
        resp.status = falcon.HTTP_200
713
        write_log(user_uuid=user_uuid, request_method='PUT', resource_type='ChangePassword',
714
                  resource_id=None, request_body=None)
715
716
717
class ResetPassword:
718
    @staticmethod
719
    def __init__():
720
        """Initializes Class"""
721
        pass
722
723
    @staticmethod
724
    def on_options(req, resp):
725
        resp.status = falcon.HTTP_200
726
727
    @staticmethod
728
    def on_put(req, resp):
729
        """Handles PUT requests"""
730
        if 'USER-UUID' not in req.headers or \
731
                not isinstance(req.headers['USER-UUID'], str) or \
732
                len(str.strip(req.headers['USER-UUID'])) == 0:
733
            raise falcon.HTTPError(falcon.HTTP_400, title='API.BAD_REQUEST',
734
                                   description='API.INVALID_USER_UUID')
735
        admin_user_uuid = str.strip(req.headers['USER-UUID'])
736
737
        if 'TOKEN' not in req.headers or \
738
                not isinstance(req.headers['TOKEN'], str) or \
739
                len(str.strip(req.headers['TOKEN'])) == 0:
740
            raise falcon.HTTPError(falcon.HTTP_400, title='API.BAD_REQUEST',
741
                                   description='API.INVALID_TOKEN')
742
        admin_token = str.strip(req.headers['TOKEN'])
743
744
        try:
745
            raw_json = req.stream.read().decode('utf-8')
746
            new_values = json.loads(raw_json)
747
        except Exception as ex:
748
            raise falcon.HTTPError(falcon.HTTP_400, 'API.ERROR', ex.args)
749
750
        if 'name' not in new_values['data'] or \
751
                not isinstance(new_values['data']['name'], str) or \
752
                len(str.strip(new_values['data']['name'])) == 0:
753
            raise falcon.HTTPError(falcon.HTTP_400, title='API.BAD_REQUEST',
754
                                   description='API.INVALID_USER_NAME')
755
        user_name = str.strip(new_values['data']['name'])
756
757
        if 'password' not in new_values['data'] or \
758
                not isinstance(new_values['data']['password'], str) or \
759
                len(str.strip(new_values['data']['password'])) == 0:
760
            raise falcon.HTTPError(falcon.HTTP_400, title='API.BAD_REQUEST',
761
                                   description='API.INVALID_PASSWORD')
762
        new_password = str.strip(new_values['data']['password'])
763
764
        # Verify Administrator
765
        cnx = mysql.connector.connect(**config.myems_user_db)
766
        cursor = cnx.cursor()
767
        query = (" SELECT utc_expires "
768
                 " FROM tbl_sessions "
769
                 " WHERE user_uuid = %s AND token = %s")
770
        cursor.execute(query, (admin_user_uuid, admin_token,))
771
        row = cursor.fetchone()
772
773 View Code Duplication
        if row is None:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
774
            cursor.close()
775
            cnx.disconnect()
776
            raise falcon.HTTPError(falcon.HTTP_404, title='API.NOT_FOUND',
777
                                   description='API.ADMINISTRATOR_SESSION_NOT_FOUND')
778
        else:
779
            utc_expires = row[0]
780
            if datetime.utcnow() > utc_expires:
781
                cursor.close()
782
                cnx.disconnect()
783
                raise falcon.HTTPError(falcon.HTTP_400, title='API.BAD_REQUEST',
784
                                       description='API.ADMINISTRATOR_SESSION_TIMEOUT')
785
786
        query = (" SELECT name "
787
                 " FROM tbl_users "
788
                 " WHERE uuid = %s AND is_admin = true ")
789
        cursor.execute(query, (admin_user_uuid,))
790
        row = cursor.fetchone()
791
        if row is None:
792
            cursor.close()
793
            cnx.disconnect()
794
            raise falcon.HTTPError(falcon.HTTP_400, 'API.BAD_REQUEST', 'API.INVALID_PRIVILEGE')
795
796
        salt = uuid.uuid4().hex
797
        hashed_password = hashlib.sha512(salt.encode() + new_password.encode()).hexdigest()
798
799
        update_user = (" UPDATE tbl_users "
800
                       " SET salt = %s, password = %s "
801
                       " WHERE name = %s ")
802
        cursor.execute(update_user, (salt, hashed_password, user_name,))
803
        cnx.commit()
804
805
        query = (" SELECT id "
806
                 " FROM tbl_users "
807
                 " WHERE name = %s ")
808
        cursor.execute(query, (user_name,))
809
        row = cursor.fetchone()
810
        if row is None:
811
            cursor.close()
812
            cnx.disconnect()
813
            raise falcon.HTTPError(falcon.HTTP_400, 'API.BAD_REQUEST', 'API.INVALID_USERNAME')
814
815
        user_id = row[0]
816
817
        # Refresh administrator session
818
        update_session = (" UPDATE tbl_sessions "
819
                          " SET utc_expires = %s "
820
                          " WHERE user_uuid = %s and token = %s ")
821
        utc_expires = datetime.utcnow() + timedelta(seconds=1000 * 60 * 60 * 8)
822
        cursor.execute(update_session, (utc_expires, admin_user_uuid, admin_token, ))
823
        cnx.commit()
824
825
        cursor.close()
826
        cnx.disconnect()
827
        resp.text = json.dumps("OK")
828
        resp.status = falcon.HTTP_200
829
        write_log(user_uuid=admin_user_uuid, request_method='PUT', resource_type='ResetPassword',
830
                  resource_id=user_id, request_body=None)
831