Passed
Push — master ( be43ee...ad5689 )
by Guangyu
08:04 queued 13s
created

core.user.UserItem.on_get()   C

Complexity

Conditions 10

Size

Total Lines 48
Code Lines 37

Duplication

Lines 0
Ratio 0 %

Importance

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

How to fix   Complexity   

Complexity

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