Passed
Push — master ( 99bc30...abf832 )
by Guangyu
08:06 queued 12s
created

core.user.Unlock.on_put()   C

Complexity

Conditions 10

Size

Total Lines 57
Code Lines 42

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 42
dl 0
loc 57
rs 5.9999
c 0
b 0
f 0
cc 10
nop 3

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like core.user.Unlock.on_put() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

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