core.user.EmailMessageCollection.on_post()   F
last analyzed

Complexity

Conditions 24

Size

Total Lines 128
Code Lines 99

Duplication

Lines 24
Ratio 18.75 %

Importance

Changes 0
Metric Value
eloc 99
dl 24
loc 128
rs 0
c 0
b 0
f 0
cc 24
nop 2

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.EmailMessageCollection.on_post() 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
import random
7
import falcon
8
import mysql.connector
9
import simplejson as json
10
from core.useractivity import user_logger, write_log, admin_control
11
import config
12
13
14
class UserCollection:
15
    def __init__(self):
16
        """Initializes Class"""
17
        pass
18
19
    @staticmethod
20
    def on_options(req, resp):
21
        _ = req
22
        resp.status = falcon.HTTP_200
23
24
    @staticmethod
25
    def on_get(req, resp):
26
        admin_control(req)
27
        cnx = mysql.connector.connect(**config.myems_user_db)
28
        cursor = cnx.cursor()
29
        query = (" SELECT u.id, u.name, u.display_name, u.uuid, "
30
                 "        u.email, u.is_admin, u.is_read_only, p.id, p.name, "
31
                 "        u.account_expiration_datetime_utc, u.password_expiration_datetime_utc, u.failed_login_count "
32
                 " FROM tbl_users u "
33
                 " LEFT JOIN tbl_privileges p ON u.privilege_id = p.id "
34
                 " ORDER BY u.name ")
35
        cursor.execute(query)
36
        rows = cursor.fetchall()
37
        cursor.close()
38
        cnx.close()
39
40
        timezone_offset = int(config.utc_offset[1:3]) * 60 + int(config.utc_offset[4:6])
41
        if config.utc_offset[0] == '-':
42
            timezone_offset = -timezone_offset
43
44
        result = list()
45
        if rows is not None and len(rows) > 0:
46
            for row in rows:
47
                meta_result = {"id": row[0],
48
                               "name": row[1],
49
                               "display_name": row[2],
50
                               "uuid": row[3],
51
                               "email": row[4],
52
                               "is_admin": True if row[5] else False,
53
                               "is_read_only": (True if row[6] else False) if row[5] else None,
54
                               "privilege": {
55
                                   "id": row[7],
56
                                   "name": row[8]} if row[7] is not None else None,
57
                               "account_expiration_datetime":
58
                                   (row[9].replace(tzinfo=timezone.utc) +
59
                                    timedelta(minutes=timezone_offset)).isoformat()[0:19],
60
                               "password_expiration_datetime":
61
                                   (row[10].replace(tzinfo=timezone.utc) +
62
                                    timedelta(minutes=timezone_offset)).isoformat()[0:19],
63
                               "is_locked": True if row[11] >= config.maximum_failed_login_count 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
        admin_control(req)
72
        # todo: add user log
73
        try:
74
            raw_json = req.stream.read().decode('utf-8')
75
            new_values = json.loads(raw_json)
76
        except Exception as ex:
77
            print(str(ex))
78
            raise falcon.HTTPError(status=falcon.HTTP_400,
79
                                   title='API.BAD_REQUEST',
80
                                   description='API.FAILED_TO_READ_REQUEST_STREAM')
81
82
        if 'name' not in new_values['data'].keys() or \
83
                not isinstance(new_values['data']['name'], str) or \
84
                len(str.strip(new_values['data']['name'])) == 0:
85
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
86
                                   description='API.INVALID_USER_NAME')
87
        name = str.strip(new_values['data']['name'])
88
89
        if 'display_name' not in new_values['data'].keys() or \
90
                not isinstance(new_values['data']['display_name'], str) or \
91
                len(str.strip(new_values['data']['display_name'])) == 0:
92
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
93
                                   description='API.INVALID_DISPLAY_NAME')
94
        display_name = str.strip(new_values['data']['display_name'])
95
96
        if 'email' not in new_values['data'].keys() or \
97
                not isinstance(new_values['data']['email'], str) or \
98
                len(str.strip(new_values['data']['email'])) == 0:
99
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
100
                                   description='API.INVALID_EMAIL')
101
        email = str.lower(str.strip(new_values['data']['email']))
102
103
        match = re.match(r'^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,4})$', email)
104
        if match is None:
105
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
106
                                   description='API.INVALID_EMAIL')
107
108
        if 'password' not in new_values['data'].keys() or \
109
                not isinstance(new_values['data']['password'], str) or \
110
                len(str.strip(new_values['data']['password'])) == 0:
111
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
112
                                   description='API.INVALID_PASSWORD')
113
114
        if len(str.strip(new_values['data']['password'])) > 100:
115
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
116
                                   description='API.PASSWORD_LENGTH_CANNOT_EXCEED_100_CHARACTERS')
117
118
        if 'is_admin' not in new_values['data'].keys() or \
119
                not isinstance(new_values['data']['is_admin'], bool):
120
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
121
                                   description='API.INVALID_IS_ADMIN_VALUE')
122
        is_admin = new_values['data']['is_admin']
123
124
        is_read_only = False
125
126
        if is_admin:
127
            if 'is_read_only' not in new_values['data'].keys() or \
128
                   not isinstance(new_values['data']['is_read_only'], bool):
129
                raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
130
                                       description='API.INVALID_IS_READ_ONLY_VALUE')
131
            is_read_only = new_values['data']['is_read_only']
132
133
        if 'privilege_id' in new_values['data'].keys():
134
            if not isinstance(new_values['data']['privilege_id'], int) or \
135
                    new_values['data']['privilege_id'] <= 0:
136
                raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
137
                                       description='API.INVALID_PRIVILEGE_ID')
138
            privilege_id = new_values['data']['privilege_id']
139
        else:
140
            privilege_id = None
141
142
        timezone_offset = int(config.utc_offset[1:3]) * 60 + int(config.utc_offset[4:6])
143
        if config.utc_offset[0] == '-':
144
            timezone_offset = -timezone_offset
145
146
        account_expiration_datetime = datetime.strptime(new_values['data']['account_expiration_datetime'],
147
                                                        '%Y-%m-%dT%H:%M:%S')
148
        account_expiration_datetime = account_expiration_datetime.replace(tzinfo=timezone.utc)
149
        account_expiration_datetime -= timedelta(minutes=timezone_offset)
150
151
        password_expiration_datetime = datetime.strptime(new_values['data']['password_expiration_datetime'],
152
                                                         '%Y-%m-%dT%H:%M:%S')
153
        password_expiration_datetime = password_expiration_datetime.replace(tzinfo=timezone.utc)
154
        password_expiration_datetime -= timedelta(minutes=timezone_offset)
155
156
        cnx = mysql.connector.connect(**config.myems_user_db)
157
        cursor = cnx.cursor()
158
159
        cursor.execute(" SELECT name "
160
                       " FROM tbl_users "
161
                       " WHERE name = %s ", (name,))
162
        if cursor.fetchone() is not None:
163
            cursor.close()
164
            cnx.close()
165
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
166
                                   description='API.USER_NAME_IS_ALREADY_IN_USE')
167
168
        cursor.execute(" SELECT name "
169
                       " FROM tbl_users "
170
                       " WHERE email = %s ", (email,))
171
        if cursor.fetchone() is not None:
172
            cursor.close()
173
            cnx.close()
174
            raise falcon.HTTPError(status=falcon.HTTP_404, title='API.BAD_REQUEST',
175
                                   description='API.EMAIL_IS_ALREADY_IN_USE')
176
177
        if privilege_id is not None:
178
            cursor.execute(" SELECT name "
179
                           " FROM tbl_privileges "
180
                           " WHERE id = %s ",
181
                           (privilege_id,))
182
            if cursor.fetchone() is None:
183
                cursor.close()
184
                cnx.close()
185
                raise falcon.HTTPError(status=falcon.HTTP_404, title='API.NOT_FOUND',
186
                                       description='API.PRIVILEGE_NOT_FOUND')
187
188
        add_row = (" INSERT INTO tbl_users "
189
                   "     (name, uuid, display_name, email, salt, password, is_admin, is_read_only, privilege_id, "
190
                   "      account_expiration_datetime_utc, password_expiration_datetime_utc, failed_login_count) "
191
                   " VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) ")
192
193
        salt = uuid.uuid4().hex
194
        password = new_values['data']['password']
195
        hashed_password = hashlib.sha512(salt.encode() + password.encode()).hexdigest()
196
197
        cursor.execute(add_row, (name,
198
                                 str(uuid.uuid4()),
199
                                 display_name,
200
                                 email,
201
                                 salt,
202
                                 hashed_password,
203
                                 is_admin,
204
                                 is_read_only,
205
                                 privilege_id,
206
                                 account_expiration_datetime,
207
                                 password_expiration_datetime,
208
                                 0))
209
        new_id = cursor.lastrowid
210
        cnx.commit()
211
        cursor.close()
212
        cnx.close()
213
214
        resp.status = falcon.HTTP_201
215
        resp.location = '/users/' + str(new_id)
216
217
218
class UserItem:
219
    def __init__(self):
220
        """Initializes Class"""
221
        pass
222
223
    @staticmethod
224
    def on_options(req, resp, id_):
225
        _ = req
226
        resp.status = falcon.HTTP_200
227
        _ = id_
228
229
    @staticmethod
230
    def on_get(req, resp, id_):
231
        admin_control(req)
232
        if not id_.isdigit() or int(id_) <= 0:
233
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
234
                                   description='API.INVALID_USER_ID')
235
236
        cnx = mysql.connector.connect(**config.myems_user_db)
237
        cursor = cnx.cursor()
238
239
        query = (" SELECT u.id, u.name, u.display_name, u.uuid, "
240
                 "        u.email, u.is_admin, u.is_read_only, p.id, p.name, "
241
                 "        u.account_expiration_datetime_utc, u.password_expiration_datetime_utc,"
242
                 "        u.failed_login_count "
243
                 " FROM tbl_users u "
244
                 " LEFT JOIN tbl_privileges p ON u.privilege_id = p.id "
245
                 " WHERE u.id = %s ")
246
        cursor.execute(query, (id_,))
247
        row = cursor.fetchone()
248
        cursor.close()
249
        cnx.close()
250
251
        if row is None:
252
            raise falcon.HTTPError(status=falcon.HTTP_404, title='API.NOT_FOUND',
253
                                   description='API.USER_NOT_FOUND')
254
        timezone_offset = int(config.utc_offset[1:3]) * 60 + int(config.utc_offset[4:6])
255
        if config.utc_offset[0] == '-':
256
            timezone_offset = -timezone_offset
257
258
        result = {"id": row[0],
259
                  "name": row[1],
260
                  "display_name": row[2],
261
                  "uuid": row[3],
262
                  "email": row[4],
263
                  "is_admin": True if row[5] else False,
264
                  "is_read_only": (True if row[6] else False) if row[5] else None,
265
                  "privilege": {
266
                      "id": row[7],
267
                      "name": row[8]} if row[7] is not None else None,
268
                  "account_expiration_datetime":
269
                      (row[9].replace(tzinfo=timezone.utc) +
270
                       timedelta(minutes=timezone_offset)).isoformat()[0:19],
271
                  "password_expiration_datetime":
272
                      (row[10].replace(tzinfo=timezone.utc) +
273
                       timedelta(minutes=timezone_offset)).isoformat()[0:19],
274
                  "is_locked": True if row[11] >= config.maximum_failed_login_count else False}
275
        resp.text = json.dumps(result)
276
277
    @staticmethod
278
    @user_logger
279
    def on_delete(req, resp, id_):
280
        admin_control(req)
281
        if not id_.isdigit() or int(id_) <= 0:
282
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
283
                                   description='API.INVALID_USER_ID')
284
285
        cnx_user_db = mysql.connector.connect(**config.myems_user_db)
286
        cursor_user_db = cnx_user_db.cursor()
287
        user_uuid = None
288
        cursor_user_db.execute(" SELECT uuid "
289
                               " FROM tbl_users "
290
                               " WHERE id = %s ", (id_,))
291
        row = cursor_user_db.fetchone()
292
        if row is None:
293
            cursor_user_db.close()
294
            cnx_user_db.close()
295
            raise falcon.HTTPError(status=falcon.HTTP_404, title='API.NOT_FOUND',
296
                                   description='API.USER_NOT_FOUND')
297
        else:
298
            user_uuid = row[0]
299
300
        cnx_system_db = mysql.connector.connect(**config.myems_system_db)
301
        cursor_system_db = cnx_system_db.cursor()
302
303
        # check if this user is being used by energy storage power stations
304
        cursor_system_db.execute(" DELETE FROM tbl_energy_storage_power_stations_users WHERE user_id = %s ", (id_,))
305
        cnx_system_db.commit()
306
307
        # check if this user is being used by microgrids
308
        cursor_system_db.execute(" DELETE FROM tbl_microgrids_users WHERE user_id = %s ", (id_,))
309
        cnx_system_db.commit()
310
311
        cursor_user_db.execute(" DELETE FROM tbl_sessions WHERE user_uuid = %s ", (user_uuid,))
312
        cnx_user_db.commit()
313
314
        cursor_user_db.execute(" DELETE FROM tbl_logs WHERE user_uuid = %s ", (user_uuid,))
315
        cnx_user_db.commit()
316
317
        cursor_user_db.execute(" DELETE FROM tbl_notifications WHERE user_id = %s ", (id_,))
318
        cnx_user_db.commit()
319
320
        cursor_user_db.execute(" DELETE FROM tbl_users WHERE id = %s ", (id_,))
321
        cnx_user_db.commit()
322
323
        cursor_user_db.close()
324
        cnx_user_db.close()
325
        cursor_system_db.close()
326
        cnx_system_db.close()
327
        resp.status = falcon.HTTP_204
328
329
    @staticmethod
330
    @user_logger
331
    def on_put(req, resp, id_):
332
        """Handles PUT requests"""
333
        admin_control(req)
334
        try:
335
            raw_json = req.stream.read().decode('utf-8')
336
            new_values = json.loads(raw_json)
337
        except Exception as ex:
338
            print(str(ex))
339
            raise falcon.HTTPError(status=falcon.HTTP_400,
340
                                   title='API.BAD_REQUEST',
341
                                   description='API.FAILED_TO_READ_REQUEST_STREAM')
342
343
        if not id_.isdigit() or int(id_) <= 0:
344
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
345
                                   description='API.INVALID_USER_ID')
346
347
        if 'name' not in new_values['data'].keys() or \
348
                not isinstance(new_values['data']['name'], str) or \
349
                len(str.strip(new_values['data']['name'])) == 0:
350
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
351
                                   description='API.INVALID_USER_NAME')
352
        name = str.strip(new_values['data']['name'])
353
354
        if 'display_name' not in new_values['data'].keys() or \
355
                not isinstance(new_values['data']['display_name'], str) or \
356
                len(str.strip(new_values['data']['display_name'])) == 0:
357
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
358
                                   description='API.INVALID_DISPLAY_NAME')
359
        display_name = str.strip(new_values['data']['display_name'])
360
361
        if 'email' not in new_values['data'].keys() or \
362
                not isinstance(new_values['data']['email'], str) or \
363
                len(str.strip(new_values['data']['email'])) == 0:
364
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
365
                                   description='API.INVALID_EMAIL')
366
        email = str.lower(str.strip(new_values['data']['email']))
367
368
        match = re.match(r'^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,4})$', email)
369
        if match is None:
370
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
371
                                   description='API.INVALID_EMAIL')
372
373
        if 'is_admin' not in new_values['data'].keys() or \
374
                not isinstance(new_values['data']['is_admin'], bool):
375
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
376
                                   description='API.INVALID_IS_ADMIN_VALUE')
377
        is_admin = new_values['data']['is_admin']
378
379
        is_read_only = False
380
381
        if is_admin:
382
            if 'is_read_only' not in new_values['data'].keys() or \
383
                   not isinstance(new_values['data']['is_read_only'], bool):
384
                raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
385
                                       description='API.INVALID_IS_READ_ONLY_VALUE')
386
            is_read_only = new_values['data']['is_read_only']
387
388
        if 'privilege_id' in new_values['data'].keys():
389
            if not isinstance(new_values['data']['privilege_id'], int) or \
390
                    new_values['data']['privilege_id'] <= 0:
391
                raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
392
                                       description='API.INVALID_PRIVILEGE_ID')
393
            privilege_id = new_values['data']['privilege_id']
394
        else:
395
            privilege_id = None
396
397
        timezone_offset = int(config.utc_offset[1:3]) * 60 + int(config.utc_offset[4:6])
398
        if config.utc_offset[0] == '-':
399
            timezone_offset = -timezone_offset
400
401
        account_expiration_datetime = datetime.strptime(new_values['data']['account_expiration_datetime'],
402
                                                        '%Y-%m-%dT%H:%M:%S')
403
        account_expiration_datetime = account_expiration_datetime.replace(tzinfo=timezone.utc)
404
        account_expiration_datetime -= timedelta(minutes=timezone_offset)
405
406
        password_expiration_datetime = datetime.strptime(new_values['data']['password_expiration_datetime'],
407
                                                         '%Y-%m-%dT%H:%M:%S')
408
        password_expiration_datetime = password_expiration_datetime.replace(tzinfo=timezone.utc)
409
        password_expiration_datetime -= timedelta(minutes=timezone_offset)
410
411
        cnx = mysql.connector.connect(**config.myems_user_db)
412
        cursor = cnx.cursor()
413
414
        cursor.execute(" SELECT name "
415
                       " FROM tbl_users "
416
                       " WHERE id = %s ", (id_,))
417
        if cursor.fetchone() is None:
418
            cursor.close()
419
            cnx.close()
420
            raise falcon.HTTPError(status=falcon.HTTP_404, title='API.NOT_FOUND',
421
                                   description='API.USER_NOT_FOUND')
422
423
        cursor.execute(" SELECT name "
424
                       " FROM tbl_users "
425
                       " WHERE name = %s AND id != %s ", (name, id_))
426
        if cursor.fetchone() is not None:
427
            cursor.close()
428
            cnx.close()
429
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
430
                                   description='API.USER_NAME_IS_ALREADY_IN_USE')
431
432
        cursor.execute(" SELECT name "
433
                       " FROM tbl_users "
434
                       " WHERE email = %s AND id != %s ", (email, id_))
435
        if cursor.fetchone() is not None:
436
            cursor.close()
437
            cnx.close()
438
            raise falcon.HTTPError(status=falcon.HTTP_404, title='API.BAD_REQUEST',
439
                                   description='API.EMAIL_IS_ALREADY_IN_USE')
440
441
        if privilege_id is not None:
442
            cursor.execute(" SELECT name "
443
                           " FROM tbl_privileges "
444
                           " WHERE id = %s ",
445
                           (privilege_id,))
446
            if cursor.fetchone() is None:
447
                cursor.close()
448
                cnx.close()
449
                raise falcon.HTTPError(status=falcon.HTTP_404, title='API.NOT_FOUND',
450
                                       description='API.PRIVILEGE_NOT_FOUND')
451
452
        update_row = (" UPDATE tbl_users "
453
                      " SET name = %s, display_name = %s, email = %s, "
454
                      "     is_admin = %s, is_read_only = %s, privilege_id = %s,"
455
                      "     account_expiration_datetime_utc = %s, "
456
                      "     password_expiration_datetime_utc = %s "
457
                      " WHERE id = %s ")
458
        cursor.execute(update_row, (name,
459
                                    display_name,
460
                                    email,
461
                                    is_admin,
462
                                    is_read_only,
463
                                    privilege_id,
464
                                    account_expiration_datetime,
465
                                    password_expiration_datetime,
466
                                    id_,))
467
        cnx.commit()
468
469
        cursor.close()
470
        cnx.close()
471
472
        resp.status = falcon.HTTP_200
473
474
475
class UserLogin:
476
    def __init__(self):
477
        """Initializes Class"""
478
        pass
479
480
    @staticmethod
481
    def on_options(req, resp):
482
        _ = req
483
        resp.status = falcon.HTTP_200
484
485
    @staticmethod
486
    def on_put(req, resp):
487
        """Handles PUT requests"""
488
        try:
489
            raw_json = req.stream.read().decode('utf-8')
490
            new_values = json.loads(raw_json)
491
        except Exception as ex:
492
            print(str(ex))
493
            raise falcon.HTTPError(status=falcon.HTTP_400,
494
                                   title='API.BAD_REQUEST',
495
                                   description='API.FAILED_TO_READ_REQUEST_STREAM')
496
497
        if not isinstance(new_values['data']['password'], str) or \
498
                len(str.strip(new_values['data']['password'])) == 0:
499
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
500
                                   description='API.INVALID_PASSWORD')
501
502
        if len(str.strip(new_values['data']['password'])) > 100:
503
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
504
                                   description='API.PASSWORD_LENGTH_CANNOT_EXCEED_100_CHARACTERS')
505
506
        cnx = mysql.connector.connect(**config.myems_user_db)
507
        cursor = cnx.cursor()
508
509
        if 'name' in new_values['data']:
510
511
            if not isinstance(new_values['data']['name'], str) or \
512
                    len(str.strip(new_values['data']['name'])) == 0:
513
                raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
514
                                       description='API.INVALID_USER_NAME')
515
516
            query = (" SELECT id, name, uuid, display_name, email, salt, password, is_admin, is_read_only, "
517
                     "        account_expiration_datetime_utc, password_expiration_datetime_utc, failed_login_count "
518
                     " FROM tbl_users "
519
                     " WHERE name = %s ")
520
            cursor.execute(query, (str.strip(new_values['data']['name']).lower(),))
521
            row = cursor.fetchone()
522
            if row is None:
523
                cursor.close()
524
                cnx.close()
525
                raise falcon.HTTPError(status=falcon.HTTP_404, title='API.ERROR', description='API.USER_NOT_FOUND')
526
527
            result = {"id": row[0],
528
                      "name": row[1],
529
                      "uuid": row[2],
530
                      "display_name": row[3],
531
                      "email": row[4],
532
                      "salt": row[5],
533
                      "password": row[6],
534
                      "is_admin": True if row[7] else False,
535
                      "is_read_only": (True if row[8] else False) if row[7] else None,
536
                      "account_expiration_datetime_utc": row[9],
537
                      "password_expiration_datetime_utc": row[10],
538
                      "failed_login_count": row[11]}
539
540
        elif 'email' in new_values['data']:
541
            if not isinstance(new_values['data']['email'], str) or \
542
                    len(str.strip(new_values['data']['email'])) == 0:
543
                raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
544
                                       description='API.INVALID_EMAIL')
545
546
            query = (" SELECT id, name, uuid, display_name, email, salt, password, is_admin, is_read_only, "
547
                     "        account_expiration_datetime_utc, password_expiration_datetime_utc,failed_login_count "
548
                     " FROM tbl_users "
549
                     " WHERE email = %s ")
550
            cursor.execute(query, (str.strip(new_values['data']['email']).lower(),))
551
            row = cursor.fetchone()
552
            if row is None:
553
                cursor.close()
554
                cnx.close()
555
                raise falcon.HTTPError(status=falcon.HTTP_404, title='API.ERROR', description='API.USER_NOT_FOUND')
556
557
            result = {"id": row[0],
558
                      "name": row[1],
559
                      "uuid": row[2],
560
                      "display_name": row[3],
561
                      "email": row[4],
562
                      "salt": row[5],
563
                      "password": row[6],
564
                      "is_admin": True if row[7] else False,
565
                      "is_read_only": (True if row[8] else False) if row[7] else None,
566
                      "account_expiration_datetime_utc": row[9],
567
                      "password_expiration_datetime_utc": row[10],
568
                      "failed_login_count": row[11]}
569
570
        else:
571
            cursor.close()
572
            cnx.close()
573
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
574
                                   description='API.INVALID_USER_NAME_OR_EMAIL')
575
576
        failed_login_count = result['failed_login_count']
577
578
        if failed_login_count >= config.maximum_failed_login_count:
579
            cursor.close()
580
            cnx.close()
581
            raise falcon.HTTPError(status=falcon.HTTP_400,
582
                                   title='API.BAD_REQUEST',
583
                                   description='API.USER_ACCOUNT_HAS_BEEN_LOCKED')
584
585
        salt = result['salt']
586
        password = str.strip(new_values['data']['password'])
587
        hashed_password = hashlib.sha512(salt.encode() + password.encode()).hexdigest()
588
589
        if hashed_password != result['password']:
590
            update_failed_login_count = (" UPDATE tbl_users "
591
                                         " SET failed_login_count = %s "
592
                                         " WHERE uuid = %s ")
593
            user_uuid = result['uuid']
594
            cursor.execute(update_failed_login_count, (failed_login_count + 1, user_uuid))
595
            cnx.commit()
596
            cursor.close()
597
            cnx.close()
598
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', description='API.INVALID_PASSWORD')
599
600
        if failed_login_count != 0:
601
            update_failed_login_count = (" UPDATE tbl_users "
602
                                         " SET failed_login_count = 0 "
603
                                         " WHERE uuid = %s ")
604
            user_uuid = result['uuid']
605
            cursor.execute(update_failed_login_count, (user_uuid, ))
606
            cnx.commit()
607
608
        if result['account_expiration_datetime_utc'] <= datetime.utcnow():
609
            cursor.close()
610
            cnx.close()
611
            raise falcon.HTTPError(status=falcon.HTTP_400,
612
                                   title='API.BAD_REQUEST',
613
                                   description='API.USER_ACCOUNT_HAS_EXPIRED')
614
615
        if result['password_expiration_datetime_utc'] <= datetime.utcnow():
616
            cursor.close()
617
            cnx.close()
618
            raise falcon.HTTPError(status=falcon.HTTP_400,
619
                                   title='API.BAD_REQUEST',
620
                                   description='API.USER_PASSWORD_HAS_EXPIRED')
621
622
        add_session = (" INSERT INTO tbl_sessions "
623
                       "             (user_uuid, token, utc_expires) "
624
                       " VALUES (%s, %s, %s) ")
625
        user_uuid = result['uuid']
626
        token = hashlib.sha512(os.urandom(24)).hexdigest()
627
        utc_expires = datetime.utcnow() + timedelta(seconds=config.session_expires_in_seconds)
628
        cursor.execute(add_session, (user_uuid, token, utc_expires))
629
        cnx.commit()
630
        cursor.close()
631
        cnx.close()
632
        del result['salt']
633
        del result['password']
634
635
        timezone_offset = int(config.utc_offset[1:3]) * 60 + int(config.utc_offset[4:6])
636
        if config.utc_offset[0] == '-':
637
            timezone_offset = -timezone_offset
638
639
        result['account_expiration_datetime'] = \
640
            (result['account_expiration_datetime_utc'].replace(tzinfo=timezone.utc) +
641
             timedelta(minutes=timezone_offset)).isoformat()[0:19]
642
        del result['account_expiration_datetime_utc']
643
644
        result['password_expiration_datetime'] = \
645
            (result['password_expiration_datetime_utc'].replace(tzinfo=timezone.utc) +
646
             timedelta(minutes=timezone_offset)).isoformat()[0:19]
647
        del result['password_expiration_datetime_utc']
648
649
        result['token'] = token
650
651
        resp.text = json.dumps(result)
652
        resp.status = falcon.HTTP_200
653
        write_log(user_uuid=user_uuid, request_method='PUT', resource_type='UserLogin',
654
                  resource_id=None, request_body=None)
655
656
657
class UserLogout:
658
    def __init__(self):
659
        """Initializes Class"""
660
        pass
661
662
    @staticmethod
663
    def on_options(req, resp):
664
        _ = req
665
        resp.status = falcon.HTTP_200
666
667
    @staticmethod
668
    @user_logger
669
    def on_put(req, resp):
670
        """Handles PUT requests"""
671
672
        if 'USER-UUID' not in req.headers or \
673
                not isinstance(req.headers['USER-UUID'], str) or \
674
                len(str.strip(req.headers['USER-UUID'])) == 0:
675
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
676
                                   description='API.INVALID_USER_UUID')
677
        user_uuid = str.strip(req.headers['USER-UUID'])
678
679
        if 'TOKEN' not in req.headers or \
680
                not isinstance(req.headers['TOKEN'], str) or \
681
                len(str.strip(req.headers['TOKEN'])) == 0:
682
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
683
                                   description='API.INVALID_TOKEN')
684
        token = str.strip(req.headers['TOKEN'])
685
686
        cnx = mysql.connector.connect(**config.myems_user_db)
687
        cursor = cnx.cursor()
688
        query = (" DELETE FROM tbl_sessions "
689
                 " WHERE user_uuid = %s AND token = %s ")
690
        cursor.execute(query, (user_uuid, token,))
691
        rowcount = cursor.rowcount
692
        cnx.commit()
693
        cursor.close()
694
        cnx.close()
695
        if rowcount is None or rowcount == 0:
696
            raise falcon.HTTPError(status=falcon.HTTP_404, title='API.NOT_FOUND',
697
                                   description='API.USER_SESSION_NOT_FOUND')
698
        resp.text = json.dumps("OK")
699
        resp.status = falcon.HTTP_200
700
701
702
class ChangePassword:
703
    def __init__(self):
704
        """Initializes Class"""
705
        pass
706
707
    @staticmethod
708
    def on_options(req, resp):
709
        _ = req
710
        resp.status = falcon.HTTP_200
711
712
    @staticmethod
713
    def on_put(req, resp):
714
        """Handles PUT requests"""
715
        if 'USER-UUID' not in req.headers or \
716
                not isinstance(req.headers['USER-UUID'], str) or \
717
                len(str.strip(req.headers['USER-UUID'])) == 0:
718
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
719
                                   description='API.INVALID_USER_UUID')
720
        user_uuid = str.strip(req.headers['USER-UUID'])
721
722
        if 'TOKEN' not in req.headers or \
723
                not isinstance(req.headers['TOKEN'], str) or \
724
                len(str.strip(req.headers['TOKEN'])) == 0:
725
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
726
                                   description='API.INVALID_TOKEN')
727
        token = str.strip(req.headers['TOKEN'])
728
729
        try:
730
            raw_json = req.stream.read().decode('utf-8')
731
            new_values = json.loads(raw_json)
732
        except Exception as ex:
733
            print(str(ex))
734
            raise falcon.HTTPError(status=falcon.HTTP_400,
735
                                   title='API.BAD_REQUEST',
736
                                   description='API.FAILED_TO_READ_REQUEST_STREAM')
737
738
        if 'old_password' not in new_values['data'] or \
739
                not isinstance(new_values['data']['old_password'], str) or \
740
                len(str.strip(new_values['data']['old_password'])) == 0:
741
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
742
                                   description='API.INVALID_OLD_PASSWORD')
743
        old_password = str.strip(new_values['data']['old_password'])
744
745
        if len(str.strip(new_values['data']['old_password'])) > 100:
746
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
747
                                   description='API.OLD_PASSWORD_LENGTH_CANNOT_EXCEED_100_CHARACTERS')
748
749
        if 'new_password' not in new_values['data'] or \
750
                not isinstance(new_values['data']['new_password'], str) or \
751
                len(str.strip(new_values['data']['new_password'])) == 0:
752
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
753
                                   description='API.INVALID_NEW_PASSWORD')
754
        new_password = str.strip(new_values['data']['new_password'])
755
756
        if len(str.strip(new_values['data']['new_password'])) > 100:
757
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
758
                                   description='API.NEW_PASSWORD_LENGTH_CANNOT_EXCEED_100_CHARACTERS')
759
760
        # Verify User Session
761
762
        cnx = mysql.connector.connect(**config.myems_user_db)
763
        cursor = cnx.cursor()
764
        query = (" SELECT utc_expires "
765
                 " FROM tbl_sessions "
766
                 " WHERE user_uuid = %s AND token = %s")
767
        cursor.execute(query, (user_uuid, token,))
768
        row = cursor.fetchone()
769
770
        if row is None:
771
            cursor.close()
772
            cnx.close()
773
            raise falcon.HTTPError(status=falcon.HTTP_404, title='API.NOT_FOUND',
774
                                   description='API.USER_SESSION_NOT_FOUND')
775
        else:
776
            utc_expires = row[0]
777
            if datetime.utcnow() > utc_expires:
778
                cursor.close()
779
                cnx.close()
780
                raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
781
                                       description='API.USER_SESSION_TIMEOUT')
782
783
        query = (" SELECT salt, password "
784
                 " FROM tbl_users "
785
                 " WHERE uuid = %s ")
786
        cursor.execute(query, (user_uuid,))
787
        row = cursor.fetchone()
788
        if row is None:
789
            cursor.close()
790
            cnx.close()
791
            raise falcon.HTTPError(status=falcon.HTTP_404, title='API.NOT_FOUND', description='API.USER_NOT_FOUND')
792
793
        result = {'salt': row[0], 'password': row[1]}
794
795
        # verify old password
796
        salt = result['salt']
797
        hashed_password = hashlib.sha512(salt.encode() + old_password.encode()).hexdigest()
798
799
        if hashed_password != result['password']:
800
            cursor.close()
801
            cnx.close()
802
            raise falcon.HTTPError(status=falcon.HTTP_400,
803
                                   title='API.BAD_REQUEST',
804
                                   description='API.INVALID_OLD_PASSWORD')
805
806
        # Update User password
807
        salt = uuid.uuid4().hex
808
        hashed_password = hashlib.sha512(salt.encode() + new_password.encode()).hexdigest()
809
810
        update_user = (" UPDATE tbl_users "
811
                       " SET salt = %s, password = %s "
812
                       " WHERE uuid = %s ")
813
        cursor.execute(update_user, (salt, hashed_password, user_uuid,))
814
        cnx.commit()
815
816
        # Refresh User session
817
        update_session = (" UPDATE tbl_sessions "
818
                          " SET utc_expires = %s "
819
                          " WHERE user_uuid = %s AND token = %s ")
820
        utc_expires = datetime.utcnow() + timedelta(seconds=1000 * 60 * 60 * 8)
821
        cursor.execute(update_session, (utc_expires, user_uuid, token, ))
822
        cnx.commit()
823
824
        cursor.close()
825
        cnx.close()
826
        resp.text = json.dumps("OK")
827
        resp.status = falcon.HTTP_200
828
        write_log(user_uuid=user_uuid, request_method='PUT', resource_type='ChangePassword',
829
                  resource_id=None, request_body=None)
830
831
832
class ResetPassword:
833
    def __init__(self):
834
        """Initializes Class"""
835
        pass
836
837
    @staticmethod
838
    def on_options(req, resp):
839
        _ = req
840
        resp.status = falcon.HTTP_200
841
842
    @staticmethod
843
    def on_put(req, resp):
844
        """Handles PUT requests"""
845
        if 'USER-UUID' not in req.headers or \
846
                not isinstance(req.headers['USER-UUID'], str) or \
847
                len(str.strip(req.headers['USER-UUID'])) == 0:
848
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
849
                                   description='API.INVALID_USER_UUID')
850
        admin_user_uuid = str.strip(req.headers['USER-UUID'])
851
852
        if 'TOKEN' not in req.headers or \
853
                not isinstance(req.headers['TOKEN'], str) or \
854
                len(str.strip(req.headers['TOKEN'])) == 0:
855
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
856
                                   description='API.INVALID_TOKEN')
857
        admin_token = str.strip(req.headers['TOKEN'])
858
859
        try:
860
            raw_json = req.stream.read().decode('utf-8')
861
            new_values = json.loads(raw_json)
862
        except Exception as ex:
863
            print(str(ex))
864
            raise falcon.HTTPError(status=falcon.HTTP_400,
865
                                   title='API.BAD_REQUEST',
866
                                   description='API.FAILED_TO_READ_REQUEST_STREAM')
867
868
        if 'name' not in new_values['data'] or \
869
                not isinstance(new_values['data']['name'], str) or \
870
                len(str.strip(new_values['data']['name'])) == 0:
871
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
872
                                   description='API.INVALID_USER_NAME')
873
        user_name = str.strip(new_values['data']['name'])
874
875
        if 'password' not in new_values['data'] or \
876
                not isinstance(new_values['data']['password'], str) or \
877
                len(str.strip(new_values['data']['password'])) == 0:
878
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
879
                                   description='API.INVALID_PASSWORD')
880
881
        if len(str.strip(new_values['data']['password'])) > 100:
882
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
883
                                   description='API.PASSWORD_LENGTH_CANNOT_EXCEED_100_CHARACTERS')
884
885
        new_password = str.strip(new_values['data']['password'])
886
887
        # Verify Administrator
888
        cnx = mysql.connector.connect(**config.myems_user_db)
889
        cursor = cnx.cursor()
890
        query = (" SELECT utc_expires "
891
                 " FROM tbl_sessions "
892
                 " WHERE user_uuid = %s AND token = %s")
893
        cursor.execute(query, (admin_user_uuid, admin_token,))
894
        row = cursor.fetchone()
895
896
        if row is None:
897
            cursor.close()
898
            cnx.close()
899
            raise falcon.HTTPError(status=falcon.HTTP_404, title='API.NOT_FOUND',
900
                                   description='API.ADMINISTRATOR_SESSION_NOT_FOUND')
901
        else:
902
            utc_expires = row[0]
903
            if datetime.utcnow() > utc_expires:
904
                cursor.close()
905
                cnx.close()
906
                raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
907
                                       description='API.ADMINISTRATOR_SESSION_TIMEOUT')
908
909
        query = (" SELECT name "
910
                 " FROM tbl_users "
911
                 " WHERE uuid = %s AND is_admin = 1 ")
912
        cursor.execute(query, (admin_user_uuid,))
913
        row = cursor.fetchone()
914
        if row is None:
915
            cursor.close()
916
            cnx.close()
917
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', description='API.INVALID_PRIVILEGE')
918
919
        salt = uuid.uuid4().hex
920
        hashed_password = hashlib.sha512(salt.encode() + new_password.encode()).hexdigest()
921
922
        update_user = (" UPDATE tbl_users "
923
                       " SET salt = %s, password = %s "
924
                       " WHERE name = %s ")
925
        cursor.execute(update_user, (salt, hashed_password, user_name,))
926
        cnx.commit()
927
928
        query = (" SELECT id "
929
                 " FROM tbl_users "
930
                 " WHERE name = %s ")
931
        cursor.execute(query, (user_name,))
932
        row = cursor.fetchone()
933
        if row is None:
934
            cursor.close()
935
            cnx.close()
936
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', description='API.INVALID_USERNAME')
937
938
        user_id = row[0]
939
940
        # Refresh administrator session
941
        update_session = (" UPDATE tbl_sessions "
942
                          " SET utc_expires = %s "
943
                          " WHERE user_uuid = %s and token = %s ")
944
        utc_expires = datetime.utcnow() + timedelta(seconds=1000 * 60 * 60 * 8)
945
        cursor.execute(update_session, (utc_expires, admin_user_uuid, admin_token, ))
946
        cnx.commit()
947
948
        cursor.close()
949
        cnx.close()
950
        resp.text = json.dumps("OK")
951
        resp.status = falcon.HTTP_200
952
        write_log(user_uuid=admin_user_uuid, request_method='PUT', resource_type='ResetPassword',
953
                  resource_id=user_id, request_body=None)
954
955
956
class Unlock:
957
    def __init__(self):
958
        """Initializes Class"""
959
        pass
960
961
    @staticmethod
962
    def on_options(req, resp, id_):
963
        _ = req
964
        resp.status = falcon.HTTP_200
965
        _ = id_
966
967
    @staticmethod
968
    def on_put(req, resp, id_):
969
        """Handles PUT requests"""
970
        if 'USER-UUID' not in req.headers or \
971
                not isinstance(req.headers['USER-UUID'], str) or \
972
                len(str.strip(req.headers['USER-UUID'])) == 0:
973
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
974
                                   description='API.INVALID_USER_UUID')
975
        admin_user_uuid = str.strip(req.headers['USER-UUID'])
976
977
        if not id_.isdigit() or int(id_) <= 0:
978
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
979
                                   description='API.INVALID_USER_ID')
980
981
        cnx = mysql.connector.connect(**config.myems_user_db)
982
        cursor = cnx.cursor()
983
984
        query = (" SELECT failed_login_count "
985
                 " FROM tbl_users "
986
                 " WHERE id = %s ")
987
        cursor.execute(query, (id_,))
988
        row = cursor.fetchone()
989
        if row is None:
990
            cursor.close()
991
            cnx.close()
992
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST', description='API.INVALID_Id')
993
994
        failed_login_count = row[0]
995
        if failed_login_count < config.maximum_failed_login_count:
996
            cursor.close()
997
            cnx.close()
998
            raise falcon.HTTPError(status=falcon.HTTP_400,
999
                                   title='API.BAD_REQUEST',
1000
                                   description='API.USER_ACCOUNT_IS_NOT_LOCKED')
1001
1002
        update_user = (" UPDATE tbl_users "
1003
                       " SET failed_login_count = 0"
1004
                       " WHERE id = %s ")
1005
        cursor.execute(update_user, (id_, ))
1006
        cnx.commit()
1007
1008
        query = (" SELECT failed_login_count "
1009
                 " FROM tbl_users "
1010
                 " WHERE id = %s ")
1011
        cursor.execute(query, (id_,))
1012
        row = cursor.fetchone()
1013
        if row is None or row[0] != 0:
1014
            cursor.close()
1015
            cnx.close()
1016
            raise falcon.HTTPError(status=falcon.HTTP_400,
1017
                                   title='API.BAD_REQUEST',
1018
                                   description='API.ACCOUNT_UNLOCK_FAILED')
1019
1020
        cursor.close()
1021
        cnx.close()
1022
        resp.text = json.dumps("OK")
1023
        resp.status = falcon.HTTP_200
1024
        write_log(user_uuid=admin_user_uuid, request_method='PUT', resource_type='UnlockUser',
1025
                  resource_id=id_, request_body=None)
1026
1027
1028
class ForgotPassword:
1029
    def __init__(self):
1030
        """Initializes Class"""
1031
        pass
1032
1033
    @staticmethod
1034
    def on_options(req, resp):
1035
        _ = req
1036
        resp.status = falcon.HTTP_200
1037
1038
    @staticmethod
1039
    def on_put(req, resp):
1040
        """Handles PUT requests"""
1041
        try:
1042
            raw_json = req.stream.read().decode('utf-8')
1043
            new_values = json.loads(raw_json)
1044
        except Exception as ex:
1045
            print(str(ex))
1046
            raise falcon.HTTPError(status=falcon.HTTP_400,
1047
                                   title='API.BAD_REQUEST',
1048
                                   description='API.FAILED_TO_READ_REQUEST_STREAM')
1049
1050
        if 'verification_code' not in new_values['data'].keys() or \
1051
                not isinstance(new_values['data']['verification_code'], str) or \
1052
                len(str.strip(new_values['data']['verification_code'])) == 0:
1053
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1054
                                   description='API.INVALID_VERIFICATION_CODE')
1055
        verification_code = str.strip(new_values['data']['verification_code'])
1056
1057
        if 'password' not in new_values['data'].keys() or \
1058
                not isinstance(new_values['data']['password'], str) or \
1059
                len(str.strip(new_values['data']['password'])) == 0:
1060
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1061
                                   description='API.INVALID_PASSWORD')
1062
        password = str.strip(new_values['data']['password'])
1063
1064
        cnx = mysql.connector.connect(**config.myems_user_db)
1065
        cursor = cnx.cursor()
1066
1067
        if 'email' in new_values['data']:
1068
            if not isinstance(new_values['data']['email'], str) or \
1069
                    len(str.strip(new_values['data']['email'])) == 0:
1070
                raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1071
                                       description='API.INVALID_EMAIL')
1072
1073
            email = str.strip(new_values['data']['email']).lower()
1074
1075
            query = (" SELECT recipient_email, verification_code, expires_datetime_utc"
1076
                     " FROM tbl_verification_codes "
1077
                     " WHERE recipient_email = %s and verification_code = %s"
1078
                     " ORDER BY created_datetime_utc DESC")
1079
            cursor.execute(query, (email, verification_code))
1080
            row = cursor.fetchone()
1081
1082
            if row is None:
1083
                cursor.close()
1084
                cnx.close()
1085
                raise falcon.HTTPError(status=falcon.HTTP_404, title='API.ERROR',
1086
                                       description='API.INVALID_VERIFICATION_CODE')
1087
            else:
1088
                if datetime.utcnow() > row[2]:
1089
                    cursor.close()
1090
                    cnx.close()
1091
                    raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1092
                                           description='API.ADMINISTRATOR_SESSION_TIMEOUT')
1093
        else:
1094
            cursor.close()
1095
            cnx.close()
1096
            raise falcon.HTTPError(status=falcon.HTTP_400,
1097
                                   title='API.BAD_REQUEST',
1098
                                   description='API.INVALID_EMAIL')
1099
1100
        cursor.execute(" SELECT salt, uuid, id "
1101
                       " FROM tbl_users "
1102
                       " WHERE email = %s",
1103
                       (email,))
1104
        row = cursor.fetchone()
1105
        if row is None:
1106
            cursor.close()
1107
            cnx.close()
1108
            raise falcon.HTTPError(status=falcon.HTTP_404, title='API.ERROR', description='API.USER_NOT_FOUND')
1109
        else:
1110
            result = {"salt": row[0], "uuid": row[1], "id": row[2]}
1111
1112
        hashed_password = hashlib.sha512(result['salt'].encode() + password.encode()).hexdigest()
1113
        cursor.execute(" SELECT uuid, id "
1114
                       " FROM tbl_users "
1115
                       " WHERE email = %s and password = %s",
1116
                       (email, hashed_password))
1117
        row = cursor.fetchone()
1118
        if row is not None:
1119
            cursor.close()
1120
            cnx.close()
1121
            raise falcon.HTTPError(status=falcon.HTTP_404, title='API.ERROR', description='API.PASSWORDS_MATCH')
1122
1123
        cursor.execute(" UPDATE tbl_users "
1124
                       " SET password = %s "
1125
                       " Where email = %s",
1126
                       (hashed_password, email))
1127
        cnx.commit()
1128
1129
        cursor.execute(" DELETE FROM tbl_verification_codes "
1130
                       " WHERE recipient_email = %s ",
1131
                       (email,))
1132
        cnx.commit()
1133
        cursor.close()
1134
        cnx.close()
1135
1136
        resp.status = falcon.HTTP_200
1137
        write_log(user_uuid=result['uuid'], request_method='PUT', resource_type='ForgotPassword',
1138
                  resource_id=result['id'], request_body=None)
1139
1140
1141
class EmailMessageCollection:
1142
    def __init__(self):
1143
        """"Initializes EmailMessageCollection"""
1144
        pass
1145
1146
    @staticmethod
1147
    def on_options(req, resp):
1148
        _ = req
1149
        resp.status = falcon.HTTP_200
1150
1151 View Code Duplication
    @staticmethod
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
1152
    def on_get(req, resp):
1153
        admin_control(req)
1154
        start_datetime_local = req.params.get('startdatetime')
1155
        end_datetime_local = req.params.get('enddatetime')
1156
1157
        timezone_offset = int(config.utc_offset[1:3]) * 60 + int(config.utc_offset[4:6])
1158
        if config.utc_offset[0] == '-':
1159
            timezone_offset = -timezone_offset
1160
1161
        if start_datetime_local is None:
1162
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1163
                                   description="API.INVALID_START_DATETIME_FORMAT")
1164
        else:
1165
            start_datetime_local = str.strip(start_datetime_local)
1166
            try:
1167
                start_datetime_utc = datetime.strptime(start_datetime_local,
1168
                                                       '%Y-%m-%dT%H:%M:%S').replace(tzinfo=timezone.utc) - \
1169
                                     timedelta(minutes=timezone_offset)
1170
            except ValueError:
1171
                raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1172
                                       description="API.INVALID_START_DATETIME_FORMAT")
1173
1174
        if end_datetime_local is None:
1175
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1176
                                   description="API.INVALID_END_DATETIME_FORMAT")
1177
        else:
1178
            end_datetime_local = str.strip(end_datetime_local)
1179
            try:
1180
                end_datetime_utc = datetime.strptime(end_datetime_local,
1181
                                                     '%Y-%m-%dT%H:%M:%S').replace(tzinfo=timezone.utc) - \
1182
                                   timedelta(minutes=timezone_offset)
1183
            except ValueError:
1184
                raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1185
                                       description="API.INVALID_END_DATETIME_FORMAT")
1186
1187
        if start_datetime_utc >= end_datetime_utc:
1188
            raise falcon.HTTPError(status=falcon.HTTP_400,
1189
                                   title='API.BAD_REQUEST',
1190
                                   description='API.START_DATETIME_MUST_BE_EARLIER_THAN_END_DATETIME')
1191
1192
        cnx = mysql.connector.connect(**config.myems_user_db)
1193
        cursor = cnx.cursor()
1194
1195
        query = (" SELECT id, recipient_name, recipient_email, "
1196
                 "        subject, message, created_datetime_utc, "
1197
                 "        scheduled_datetime_utc, status "
1198
                 " FROM tbl_email_messages "
1199
                 " WHERE created_datetime_utc >= %s AND created_datetime_utc < %s "
1200
                 " ORDER BY created_datetime_utc ")
1201
        cursor.execute(query, (start_datetime_utc, end_datetime_utc))
1202
        rows = cursor.fetchall()
1203
1204
        if cursor:
1205
            cursor.close()
1206
        if cnx:
1207
            cnx.close()
1208
1209
        result = list()
1210
        if rows is not None and len(rows) > 0:
1211
            for row in rows:
1212
                meta_result = {"id": row[0],
1213
                               "recipient_name": row[1],
1214
                               "recipient_email": row[2],
1215
                               "subject": row[3],
1216
                               "message": row[4].replace("<br>", ""),
1217
                               "created_datetime": row[5].timestamp() * 1000 if isinstance(row[5], datetime) else None,
1218
                               "scheduled_datetime":
1219
                                   row[6].timestamp() * 1000 if isinstance(row[6], datetime) else None,
1220
                               "status": row[7]}
1221
                result.append(meta_result)
1222
1223
        resp.text = json.dumps(result)
1224
1225
    @staticmethod
1226
    def on_post(req, resp):
1227
        try:
1228
            raw_json = req.stream.read().decode('utf-8')
1229
            new_values = json.loads(raw_json)
1230
        except Exception as ex:
1231
            print(str(ex))
1232
            raise falcon.HTTPError(status=falcon.HTTP_400,
1233
                                   title='API.BAD_REQUEST',
1234
                                   description='API.FAILED_TO_READ_REQUEST_STREAM')
1235
1236
        expires_datetime_utc = datetime.utcnow() + timedelta(seconds=60 * 60 * 1)
1237
1238
        if 'recipient_email' not in new_values['data'].keys() or \
1239
                not isinstance(new_values['data']['recipient_email'], str) or \
1240
                len(str.strip(new_values['data']['recipient_email'])) == 0:
1241
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1242
                                   description='API.INVALID_RECIPIENT_EMAIL')
1243
        recipient_email = str.strip(new_values['data']['recipient_email'])
1244
        match = re.match(r'^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,4})$', recipient_email)
1245
        if match is None:
1246
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1247
                                   description='API.INVALID_EMAIL')
1248
1249
        if 'subject' not in new_values['data'].keys() or \
1250
                not isinstance(new_values['data']['subject'], str) or \
1251
                len(str.strip(new_values['data']['subject'])) == 0:
1252
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1253
                                   description='API.INVALID_SUBJECT_VALUE')
1254
        subject = str.strip(new_values['data']['subject'])
1255
1256
        if 'message' not in new_values['data'].keys() or \
1257
                not isinstance(new_values['data']['message'], str) or \
1258
                len(str.strip(new_values['data']['message'])) == 0:
1259
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1260
                                   description='API.INVALID_MESSAGE_VALUE')
1261
1262
        verification_code = str(random.randint(100000, 999999))
1263
        message = str.strip(new_values['data']['message'])
1264
        message = re.sub(r'{verification_code}', verification_code, message)
1265
1266
        if 'created_datetime' not in new_values['data'].keys() or \
1267
                not isinstance(new_values['data']['created_datetime'], str) or \
1268
                len(str.strip(new_values['data']['created_datetime'])) == 0:
1269
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1270
                                   description='API.INVALID_CREATED_DATETIME')
1271
        created_datetime_local = str.strip(new_values['data']['created_datetime'])
1272
1273
        if 'scheduled_datetime' not in new_values['data'].keys() or \
1274
                not isinstance(new_values['data']['scheduled_datetime'], str) or \
1275
                len(str.strip(new_values['data']['scheduled_datetime'])) == 0:
1276
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1277
                                   description='API.INVALID_SCHEDULED_DATETIME')
1278
        scheduled_datetime_local = str.strip(new_values['data']['scheduled_datetime'])
1279
1280
        timezone_offset = int(config.utc_offset[1:3]) * 60 + int(config.utc_offset[4:6])
1281
        if config.utc_offset[0] == '-':
1282
            timezone_offset = -timezone_offset
1283
1284 View Code Duplication
        if created_datetime_local is None:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
1285
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1286
                                   description="API.INVALID_CREATED_DATETIME")
1287
        else:
1288
            created_datetime_local = str.strip(created_datetime_local)
1289
            try:
1290
                created_datetime_utc = datetime.strptime(created_datetime_local,
1291
                                                         '%Y-%m-%dT%H:%M:%S').replace(tzinfo=timezone.utc) - \
1292
                                       timedelta(minutes=timezone_offset)
1293
            except ValueError:
1294
                raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1295
                                       description="API.INVALID_CREATED_DATETIME")
1296
1297 View Code Duplication
        if scheduled_datetime_local is None:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
1298
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1299
                                   description="API.INVALID_SCHEDULED_DATETIME")
1300
        else:
1301
            scheduled_datetime_local = str.strip(scheduled_datetime_local)
1302
            try:
1303
                scheduled_datetime_utc = datetime.strptime(scheduled_datetime_local,
1304
                                                           '%Y-%m-%dT%H:%M:%S').replace(tzinfo=timezone.utc) - \
1305
                                         timedelta(minutes=timezone_offset)
1306
            except ValueError:
1307
                raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1308
                                       description="API.INVALID_SCHEDULED_DATETIME")
1309
1310
        status = 'new'
1311
1312
        cnx = mysql.connector.connect(**config.myems_user_db)
1313
        cursor = cnx.cursor()
1314
1315
        cursor.execute(" DELETE FROM tbl_verification_codes "
1316
                       " WHERE recipient_email = %s ",
1317
                       (recipient_email,))
1318
        cnx.commit()
1319
1320
        cursor.execute(" select name "
1321
                       " from tbl_new_users "
1322
                       " where email = %s ", (recipient_email,))
1323
        row = cursor.fetchone()
1324
        if row is not None:
1325
            recipient_name = row[0]
1326
        else:
1327
            recipient_name = recipient_email.split('@')[0]
1328
1329
        add_verification_code = (" INSERT INTO tbl_verification_codes "
1330
                                 " (recipient_email, verification_code, created_datetime_utc, expires_datetime_utc) "
1331
                                 " VALUES (%s, %s, %s, %s) ")
1332
        cursor.execute(add_verification_code,
1333
                       (recipient_email, verification_code, created_datetime_utc, expires_datetime_utc))
1334
1335
        add_row = (" INSERT INTO tbl_email_messages "
1336
                   "             (recipient_name, recipient_email, subject, message, "
1337
                   "             created_datetime_utc, scheduled_datetime_utc, status) "
1338
                   " VALUES (%s, %s, %s, %s, %s, %s, %s) ")
1339
1340
        cursor.execute(add_row, (recipient_name,
1341
                                 recipient_email,
1342
                                 subject,
1343
                                 message,
1344
                                 created_datetime_utc,
1345
                                 scheduled_datetime_utc,
1346
                                 status))
1347
        cnx.commit()
1348
1349
        cursor.close()
1350
        cnx.close()
1351
1352
        resp.status = falcon.HTTP_201
1353
1354
1355
class EmailMessageItem:
1356
    def __init__(self):
1357
        """"Initializes EmailMessageItem"""
1358
        pass
1359
1360
    @staticmethod
1361
    def on_options(req, resp, id_):
1362
        _ = req
1363
        resp.status = falcon.HTTP_200
1364
        _ = id_
1365
1366 View Code Duplication
    @staticmethod
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
1367
    def on_get(req, resp, id_):
1368
        admin_control(req)
1369
        if not id_.isdigit() or int(id_) <= 0:
1370
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1371
                                   description='API.INVALID_EMAIL_MESSAGE_ID')
1372
1373
        cnx = mysql.connector.connect(**config.myems_user_db)
1374
        cursor = cnx.cursor()
1375
1376
        query = (" SELECT id, recipient_name, recipient_email, "
1377
                 "        subject, message, created_datetime_utc, "
1378
                 "        scheduled_datetime_utc, status "
1379
                 " FROM tbl_email_messages "
1380
                 " WHERE id = %s ")
1381
        cursor.execute(query, (id_,))
1382
        row = cursor.fetchone()
1383
1384
        if cursor:
1385
            cursor.close()
1386
        if cnx:
1387
            cnx.close()
1388
1389
        if row is None:
1390
            raise falcon.HTTPError(status=falcon.HTTP_404, title='API.NOT_FOUND',
1391
                                   description='API.EMAIL_MESSAGE_NOT_FOUND')
1392
1393
        result = {"id": row[0],
1394
                  "recipient_name": row[1],
1395
                  "recipient_email": row[2],
1396
                  "subject": row[3],
1397
                  "message": row[4].replace("<br>", ""),
1398
                  "created_datetime": row[5].timestamp() * 1000 if isinstance(row[5], datetime) else None,
1399
                  "scheduled_datetime": row[6].timestamp() * 1000 if isinstance(row[6], datetime) else None,
1400
                  "status": row[7]}
1401
1402
        resp.text = json.dumps(result)
1403
1404
    @staticmethod
1405
    @user_logger
1406
    def on_put(req, resp, id_):
1407
        """Handles PUT requests"""
1408
        admin_control(req)
1409
1410
        if not id_.isdigit() or int(id_) <= 0:
1411
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1412
                                   description='API.INVALID_EMAIL_MESSAGE_ID')
1413
1414
        try:
1415
            raw_json = req.stream.read().decode('utf-8')
1416
            new_values = json.loads(raw_json)
1417
        except Exception as ex:
1418
            print(str(ex))
1419
            raise falcon.HTTPError(status=falcon.HTTP_400,
1420
                                   title='API.BAD_REQUEST',
1421
                                   description='API.FAILED_TO_READ_REQUEST_STREAM')
1422
1423
        if 'recipient_name' not in new_values['data'].keys() or \
1424
                not isinstance(new_values['data']['recipient_name'], str) or \
1425
                len(str.strip(new_values['data']['recipient_name'])) == 0:
1426
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1427
                                   description='API.INVALID_RECIPIENT_NAME')
1428
        recipient_name = str.strip(new_values['data']['recipient_name'])
1429
1430
        if 'recipient_email' not in new_values['data'].keys() or \
1431
                not isinstance(new_values['data']['recipient_email'], str) or \
1432
                len(str.strip(new_values['data']['recipient_email'])) == 0:
1433
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1434
                                   description='API.INVALID_RECIPIENT_EMAIL')
1435
        recipient_email = str.strip(new_values['data']['recipient_email'])
1436
        match = re.match(r'^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,4})$', recipient_email)
1437
        if match is None:
1438
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1439
                                   description='API.INVALID_EMAIL')
1440
1441
        if 'subject' not in new_values['data'].keys() or \
1442
                not isinstance(new_values['data']['subject'], str) or \
1443
                len(str.strip(new_values['data']['subject'])) == 0:
1444
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1445
                                   description='API.INVALID_SUBJECT_VALUE')
1446
        subject = str.strip(new_values['data']['subject'])
1447
1448
        if 'message' not in new_values['data'].keys() or \
1449
                not isinstance(new_values['data']['message'], str) or \
1450
                len(str.strip(new_values['data']['message'])) == 0:
1451
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1452
                                   description='API.INVALID_MESSAGE_VALUE')
1453
        message = str.strip(new_values['data']['message'])
1454
1455
        if 'status' not in new_values['data'].keys() or \
1456
                not isinstance(new_values['data']['status'], str) or \
1457
                len(str.strip(new_values['data']['status'])) == 0 or \
1458
                str.strip(new_values['data']['status']) not in ('new', 'acknowledged', 'timeout'):
1459
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1460
                                   description='API.INVALID_STATUS')
1461
        status = str.strip(new_values['data']['status'])
1462
1463
        if 'created_datetime' not in new_values['data'].keys() or \
1464
                not isinstance(new_values['data']['created_datetime'], str) or \
1465
                len(str.strip(new_values['data']['created_datetime'])) == 0:
1466
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1467
                                   description='API.INVALID_CREATED_DATETIME')
1468
        created_datetime_local = str.strip(new_values['data']['created_datetime'])
1469
1470
        if 'scheduled_datetime' not in new_values['data'].keys() or \
1471
                not isinstance(new_values['data']['scheduled_datetime'], str) or \
1472
                len(str.strip(new_values['data']['scheduled_datetime'])) == 0:
1473
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1474
                                   description='API.INVALID_SCHEDULED_DATETIME')
1475
        scheduled_datetime_local = str.strip(new_values['data']['scheduled_datetime'])
1476
1477
        timezone_offset = int(config.utc_offset[1:3]) * 60 + int(config.utc_offset[4:6])
1478
        if config.utc_offset[0] == '-':
1479
            timezone_offset = -timezone_offset
1480
1481 View Code Duplication
        if created_datetime_local is None:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
1482
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1483
                                   description="API.INVALID_CREATED_DATETIME")
1484
        else:
1485
            created_datetime_local = str.strip(created_datetime_local)
1486
            try:
1487
                created_datetime_utc = datetime.strptime(created_datetime_local,
1488
                                                         '%Y-%m-%dT%H:%M:%S').replace(tzinfo=timezone.utc) - \
1489
                                       timedelta(minutes=timezone_offset)
1490
            except ValueError:
1491
                raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1492
                                       description="API.INVALID_CREATED_DATETIME")
1493
1494 View Code Duplication
        if scheduled_datetime_local is None:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
1495
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1496
                                   description="API.INVALID_SCHEDULED_DATETIME")
1497
        else:
1498
            scheduled_datetime_local = str.strip(scheduled_datetime_local)
1499
            try:
1500
                scheduled_datetime_utc = datetime.strptime(scheduled_datetime_local,
1501
                                                           '%Y-%m-%dT%H:%M:%S').replace(tzinfo=timezone.utc) - \
1502
                                         timedelta(minutes=timezone_offset)
1503
            except ValueError:
1504
                raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1505
                                       description="API.INVALID_SCHEDULED_DATETIME")
1506
1507
        cnx = mysql.connector.connect(**config.myems_user_db)
1508
        cursor = cnx.cursor()
1509
1510
        cursor.execute(" SELECT recipient_name "
1511
                       " FROM tbl_email_messages "
1512
                       " WHERE id = %s ", (id_,))
1513
1514
        if cursor.fetchone() is None:
1515
            cursor.close()
1516
            cnx.close()
1517
            raise falcon.HTTPError(status=falcon.HTTP_404, title='API.NOT_FOUND',
1518
                                   description='API.EMAIL_MESSAGE_NOT_FOUND')
1519
1520
        update_row = (" UPDATE tbl_email_messages "
1521
                      " SET recipient_name = %s, recipient_email = %s, subject = %s, message = %s,"
1522
                      " created_datetime_utc = %s, scheduled_datetime_utc = %s, status = %s"
1523
                      " WHERE id = %s ")
1524
1525
        cursor.execute(update_row, (recipient_name,
1526
                                    recipient_email,
1527
                                    subject,
1528
                                    message,
1529
                                    created_datetime_utc,
1530
                                    scheduled_datetime_utc,
1531
                                    status,
1532
                                    id_))
1533
1534
        cnx.commit()
1535
        cursor.close()
1536
        cnx.close()
1537
1538
        resp.status = falcon.HTTP_200
1539
1540 View Code Duplication
    @staticmethod
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
1541
    @user_logger
1542
    def on_delete(req, resp, id_):
1543
        admin_control(req)
1544
        if not id_.isdigit() or int(id_) <= 0:
1545
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1546
                                   description='API.INVALID_EMAIL_MESSAGE_ID')
1547
1548
        cnx = mysql.connector.connect(**config.myems_user_db)
1549
        cursor = cnx.cursor()
1550
1551
        cursor.execute(" SELECT id "
1552
                       " FROM tbl_email_messages "
1553
                       " WHERE id = %s ", (id_,))
1554
        row = cursor.fetchone()
1555
1556
        if row is None:
1557
            if cursor:
1558
                cursor.close()
1559
            if cnx:
1560
                cnx.close()
1561
            raise falcon.HTTPError(status=falcon.HTTP_404, title='API.NOT_FOUND',
1562
                                   description='API.EMAIL_MESSAGE_NOT_FOUND')
1563
1564
        cursor.execute(" DELETE FROM tbl_email_messages WHERE id = %s ", (id_,))
1565
        cnx.commit()
1566
1567
        if cursor:
1568
            cursor.close()
1569
        if cnx:
1570
            cnx.close()
1571
1572
        resp.status = falcon.HTTP_204
1573
1574
1575
class NewUserCollection:
1576
    def __init__(self):
1577
        """Initializes Class"""
1578
        pass
1579
1580
    @staticmethod
1581
    def on_options(req, resp):
1582
        _ = req
1583
        resp.status = falcon.HTTP_200
1584
1585
    @staticmethod
1586
    def on_get(req, resp):
1587
        admin_control(req)
1588
        cnx = mysql.connector.connect(**config.myems_user_db)
1589
        cursor = cnx.cursor()
1590
        query = (" SELECT id, name, display_name, uuid, email "
1591
                 " FROM tbl_new_users "
1592
                 " ORDER BY name ")
1593
        cursor.execute(query)
1594
        rows = cursor.fetchall()
1595
        cursor.close()
1596
        cnx.close()
1597
1598
        result = list()
1599
        if rows is not None and len(rows) > 0:
1600
            for row in rows:
1601
                meta_result = {"id": row[0],
1602
                               "name": row[1],
1603
                               "display_name": row[2],
1604
                               "uuid": row[3],
1605
                               "email": row[4], }
1606
                result.append(meta_result)
1607
1608
        resp.text = json.dumps(result)
1609
1610
    @staticmethod
1611
    def on_post(req, resp):
1612
        try:
1613
            raw_json = req.stream.read().decode('utf-8')
1614
            new_values = json.loads(raw_json)
1615
        except Exception as ex:
1616
            print(str(ex))
1617
            raise falcon.HTTPError(status=falcon.HTTP_400,
1618
                                   title='API.BAD_REQUEST',
1619
                                   description='API.FAILED_TO_READ_REQUEST_STREAM')
1620
1621
        if 'name' not in new_values['data'].keys() or \
1622
                not isinstance(new_values['data']['name'], str) or \
1623
                len(str.strip(new_values['data']['name'])) == 0:
1624
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1625
                                   description='API.INVALID_USER_NAME')
1626
        name = str.strip(new_values['data']['name'])
1627
1628
        if 'display_name' not in new_values['data'].keys() or \
1629
                not isinstance(new_values['data']['display_name'], str) or \
1630
                len(str.strip(new_values['data']['display_name'])) == 0:
1631
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1632
                                   description='API.INVALID_DISPLAY_NAME')
1633
        display_name = str.strip(new_values['data']['display_name'])
1634
1635
        if 'email' not in new_values['data'].keys() or \
1636
                not isinstance(new_values['data']['email'], str) or \
1637
                len(str.strip(new_values['data']['email'])) == 0:
1638
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1639
                                   description='API.INVALID_EMAIL')
1640
        email = str.lower(str.strip(new_values['data']['email']))
1641
1642
        match = re.match(r'^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,4})$', email)
1643
        if match is None:
1644
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1645
                                   description='API.INVALID_EMAIL')
1646
1647
        if 'password' not in new_values['data'].keys() or \
1648
                not isinstance(new_values['data']['password'], str) or \
1649
                len(str.strip(new_values['data']['password'])) == 0:
1650
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1651
                                   description='API.INVALID_PASSWORD')
1652
1653
        if len(str.strip(new_values['data']['password'])) > 100:
1654
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1655
                                   description='API.PASSWORD_LENGTH_CANNOT_EXCEED_100_CHARACTERS')
1656
1657
        if 'verification_code' not in new_values['data'].keys() or \
1658
                not isinstance(new_values['data']['verification_code'], str) or \
1659
                len(str.strip(new_values['data']['verification_code'])) == 0:
1660
            raise falcon.HTTPError(status=falcon.HTTP_400,
1661
                                   title='API.BAD_REQUEST',
1662
                                   description='API.INVALID_VERIFICATION_CODE')
1663
        verification_code = str.strip(new_values['data']['verification_code'])
1664
1665
        cnx = mysql.connector.connect(**config.myems_user_db)
1666
        cursor = cnx.cursor()
1667
1668
        cursor.execute(" SELECT expires_datetime_utc "
1669
                       " FROM tbl_verification_codes "
1670
                       " WHERE recipient_email = %s and verification_code = %s ",
1671
                       (email, verification_code))
1672
        row = cursor.fetchone()
1673
        if row is not None:
1674
            expires_datetime_utc = row[0]
1675
            print(expires_datetime_utc)
1676
            print(datetime.utcnow())
1677
            if datetime.utcnow() > expires_datetime_utc:
1678
                cursor.close()
1679
                cnx.close()
1680
                raise falcon.HTTPError(status=falcon.HTTP_400,
1681
                                       title='API.BAD_REQUEST',
1682
                                       description='API.NEW_USER_SESSION_TIMEOUT')
1683
        else:
1684
            cursor.close()
1685
            cnx.close()
1686
            raise falcon.HTTPError(status=falcon.HTTP_404,
1687
                                   title='API.NOT_FOUND',
1688
                                   description='API.NEW_USER_SESSION_NOT_FOUND')
1689
1690
        cursor.execute(" SELECT name "
1691
                       " FROM tbl_new_users "
1692
                       " WHERE name = %s ", (name,))
1693
        if cursor.fetchone() is not None:
1694
            cursor.close()
1695
            cnx.close()
1696
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1697
                                   description='API.USER_NAME_IS_ALREADY_IN_USE')
1698
1699
        cursor.execute(" SELECT name "
1700
                       " FROM tbl_users "
1701
                       " WHERE name = %s ", (name,))
1702
        if cursor.fetchone() is not None:
1703
            cursor.close()
1704
            cnx.close()
1705
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1706
                                   description='API.USER_NAME_IS_ALREADY_IN_USE')
1707
1708
        cursor.execute(" SELECT name "
1709
                       " FROM tbl_new_users "
1710
                       " WHERE email = %s ", (email,))
1711
        if cursor.fetchone() is not None:
1712
            cursor.close()
1713
            cnx.close()
1714
            raise falcon.HTTPError(status=falcon.HTTP_404, title='API.BAD_REQUEST',
1715
                                   description='API.EMAIL_IS_ALREADY_IN_USE')
1716
1717
        cursor.execute(" SELECT name "
1718
                       " FROM tbl_users "
1719
                       " WHERE email = %s ", (email,))
1720
        if cursor.fetchone() is not None:
1721
            cursor.close()
1722
            cnx.close()
1723
            raise falcon.HTTPError(status=falcon.HTTP_404, title='API.BAD_REQUEST',
1724
                                   description='API.EMAIL_IS_ALREADY_IN_USE')
1725
1726
        add_row = (" INSERT INTO tbl_new_users "
1727
                   "     (name, uuid, display_name, email, salt, password) "
1728
                   " VALUES (%s, %s, %s, %s, %s, %s) ")
1729
1730
        salt = uuid.uuid4().hex
1731
        password = new_values['data']['password']
1732
        hashed_password = hashlib.sha512(salt.encode() + password.encode()).hexdigest()
1733
1734
        cursor.execute(add_row, (name,
1735
                                 str(uuid.uuid4()),
1736
                                 display_name,
1737
                                 email,
1738
                                 salt,
1739
                                 hashed_password))
1740
        new_id = cursor.lastrowid
1741
        cnx.commit()
1742
1743
        cursor.execute(" DELETE FROM tbl_verification_codes "
1744
                       " WHERE recipient_email = %s",
1745
                       (email,))
1746
        cnx.commit()
1747
1748
        cursor.close()
1749
        cnx.close()
1750
1751
        resp.status = falcon.HTTP_201
1752
        resp.location = '/users/newusers/' + str(new_id)
1753
1754
1755
class NewUserItem:
1756
    def __init__(self):
1757
        """Initializes Class"""
1758
        pass
1759
1760
    @staticmethod
1761
    def on_options(req, resp, id_):
1762
        _ = req
1763
        resp.status = falcon.HTTP_200
1764
        _ = id_
1765
1766
    @staticmethod
1767
    def on_get(req, resp, email):
1768
        _ = req
1769
        if not isinstance(email, str) or len(str.strip(email)) == 0:
1770
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1771
                                   description='API.INVALID_EMAIL')
1772
        email = str.lower(str.strip(email))
1773
1774
        cnx = mysql.connector.connect(**config.myems_user_db)
1775
        cursor = cnx.cursor()
1776
1777
        query = (" SELECT id, name, display_name, uuid, email "
1778
                 " FROM tbl_new_users "
1779
                 " WHERE id = %s ")
1780
        cursor.execute(query, (email,))
1781
        row = cursor.fetchone()
1782
        cursor.close()
1783
        cnx.close()
1784
1785
        if row is None:
1786
            raise falcon.HTTPError(status=falcon.HTTP_404, title='API.NOT_FOUND',
1787
                                   description='API.USER_NOT_FOUND')
1788
1789
        result = {"id": row[0],
1790
                  "name": row[1],
1791
                  "display_name": row[2],
1792
                  "uuid": row[3],
1793
                  "email": row[4]}
1794
        resp.text = json.dumps(result)
1795
1796
    @staticmethod
1797
    @user_logger
1798
    def on_delete(req, resp, id_):
1799
        admin_control(req)
1800
        if not id_.isdigit() or int(id_) <= 0:
1801
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1802
                                   description='API.INVALID_USER_ID')
1803
1804
        cnx = mysql.connector.connect(**config.myems_user_db)
1805
        cursor = cnx.cursor()
1806
1807
        cursor.execute(" SELECT name "
1808
                       " FROM tbl_new_users "
1809
                       " WHERE id = %s ", (id_,))
1810
        if cursor.fetchone() is None:
1811
            cursor.close()
1812
            cnx.close()
1813
            raise falcon.HTTPError(status=falcon.HTTP_404, title='API.NOT_FOUND',
1814
                                   description='API.USER_NOT_FOUND')
1815
1816
        # TODO: delete associated objects
1817
        cursor.execute(" DELETE FROM tbl_new_users WHERE id = %s ", (id_,))
1818
        cnx.commit()
1819
1820
        cursor.close()
1821
        cnx.close()
1822
1823
        resp.status = falcon.HTTP_204
1824
1825
    @staticmethod
1826
    @user_logger
1827
    def on_put(req, resp, id_):
1828
        """Handles PUT requests"""
1829
        admin_control(req)
1830
1831
        try:
1832
            raw_json = req.stream.read().decode('utf-8')
1833
            new_values = json.loads(raw_json)
1834
        except Exception as ex:
1835
            print(str(ex))
1836
            raise falcon.HTTPError(status=falcon.HTTP_400,
1837
                                   title='API.BAD_REQUEST',
1838
                                   description='API.FAILED_TO_READ_REQUEST_STREAM')
1839
1840
        if not id_.isdigit() or int(id_) <= 0:
1841
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1842
                                   description='API.INVALID_USER_ID')
1843
1844
        if 'name' not in new_values['data'].keys() or \
1845
                not isinstance(new_values['data']['name'], str) or \
1846
                len(str.strip(new_values['data']['name'])) == 0:
1847
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1848
                                   description='API.INVALID_USER_NAME')
1849
        name = str.strip(new_values['data']['name'])
1850
1851
        if 'display_name' not in new_values['data'].keys() or \
1852
                not isinstance(new_values['data']['display_name'], str) or \
1853
                len(str.strip(new_values['data']['display_name'])) == 0:
1854
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1855
                                   description='API.INVALID_DISPLAY_NAME')
1856
        display_name = str.strip(new_values['data']['display_name'])
1857
1858
        if 'email' not in new_values['data'].keys() or \
1859
                not isinstance(new_values['data']['email'], str) or \
1860
                len(str.strip(new_values['data']['email'])) == 0:
1861
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1862
                                   description='API.INVALID_EMAIL')
1863
        email = str.lower(str.strip(new_values['data']['email']))
1864
1865
        match = re.match(r'^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,4})$', email)
1866
        if match is None:
1867
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1868
                                   description='API.INVALID_EMAIL')
1869
1870
        cnx = mysql.connector.connect(**config.myems_user_db)
1871
        cursor = cnx.cursor()
1872
1873
        cursor.execute(" SELECT name "
1874
                       " FROM tbl_new_users "
1875
                       " WHERE id = %s ", (id_,))
1876
        if cursor.fetchone() is None:
1877
            cursor.close()
1878
            cnx.close()
1879
            raise falcon.HTTPError(status=falcon.HTTP_404, title='API.NOT_FOUND',
1880
                                   description='API.USER_NOT_FOUND')
1881
1882
        cursor.execute(" SELECT name "
1883
                       " FROM tbl_users "
1884
                       " WHERE name = %s ", (name,))
1885
        if cursor.fetchone() is not None:
1886
            cursor.close()
1887
            cnx.close()
1888
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1889
                                   description='API.USER_NAME_IS_ALREADY_IN_USE')
1890
1891
        cursor.execute(" SELECT name "
1892
                       " FROM tbl_new_users "
1893
                       " WHERE name = %s AND id != %s ", (name, id_))
1894
        if cursor.fetchone() is not None:
1895
            cursor.close()
1896
            cnx.close()
1897
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1898
                                   description='API.USER_NAME_IS_ALREADY_IN_USE')
1899
1900
        cursor.execute(" SELECT name "
1901
                       " FROM tbl_users "
1902
                       " WHERE email = %s ", (email, ))
1903
        if cursor.fetchone() is not None:
1904
            cursor.close()
1905
            cnx.close()
1906
            raise falcon.HTTPError(status=falcon.HTTP_404, title='API.BAD_REQUEST',
1907
                                   description='API.EMAIL_IS_ALREADY_IN_USE')
1908
1909
        cursor.execute(" SELECT name "
1910
                       " FROM tbl_new_users "
1911
                       " WHERE email = %s AND id != %s ", (email, id_))
1912
        if cursor.fetchone() is not None:
1913
            cursor.close()
1914
            cnx.close()
1915
            raise falcon.HTTPError(status=falcon.HTTP_404, title='API.BAD_REQUEST',
1916
                                   description='API.EMAIL_IS_ALREADY_IN_USE')
1917
1918
        update_row = (" UPDATE tbl_new_users "
1919
                      " SET name = %s, display_name = %s, email = %s "
1920
                      " WHERE id = %s ")
1921
        cursor.execute(update_row, (name,
1922
                                    display_name,
1923
                                    email,
1924
                                    id_,))
1925
        cnx.commit()
1926
1927
        cursor.close()
1928
        cnx.close()
1929
1930
        resp.status = falcon.HTTP_200
1931
1932
1933
class NewUserApprove:
1934
    def __init__(self):
1935
        """Initializes Class"""
1936
        pass
1937
1938
    @staticmethod
1939
    def on_options(req, resp, id_):
1940
        _ = req
1941
        resp.status = falcon.HTTP_200
1942
        _ = id_
1943
1944
    @staticmethod
1945
    @user_logger
1946
    def on_put(req, resp, id_):
1947
        """Handles POST requests"""
1948
        admin_control(req)
1949
1950
        try:
1951
            raw_json = req.stream.read().decode('utf-8')
1952
            new_values = json.loads(raw_json)
1953
        except Exception as ex:
1954
            print(str(ex))
1955
            raise falcon.HTTPError(status=falcon.HTTP_400,
1956
                                   title='API.BAD_REQUEST',
1957
                                   description='API.FAILED_TO_READ_REQUEST_STREAM')
1958
1959
        if not id_.isdigit() or int(id_) <= 0:
1960
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1961
                                   description='API.INVALID_USER_ID')
1962
1963
        if 'is_admin' not in new_values['data'].keys() or \
1964
                not isinstance(new_values['data']['is_admin'], bool):
1965
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1966
                                   description='API.INVALID_IS_ADMIN_VALUE')
1967
        is_admin = new_values['data']['is_admin']
1968
1969
        is_read_only = False
1970
1971
        if is_admin:
1972
            if 'is_read_only' not in new_values['data'].keys() or \
1973
                    not isinstance(new_values['data']['is_read_only'], bool):
1974
                raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1975
                                       description='API.INVALID_IS_READ_ONLY_VALUE')
1976
            is_read_only = new_values['data']['is_read_only']
1977
1978
        if 'privilege_id' in new_values['data'].keys():
1979
            if not isinstance(new_values['data']['privilege_id'], int) or \
1980
                    new_values['data']['privilege_id'] <= 0:
1981
                raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
1982
                                       description='API.INVALID_PRIVILEGE_ID')
1983
            privilege_id = new_values['data']['privilege_id']
1984
        else:
1985
            privilege_id = None
1986
1987
        timezone_offset = int(config.utc_offset[1:3]) * 60 + int(config.utc_offset[4:6])
1988
        if config.utc_offset[0] == '-':
1989
            timezone_offset = -timezone_offset
1990
1991
        account_expiration_datetime = datetime.strptime(new_values['data']['account_expiration_datetime'],
1992
                                                        '%Y-%m-%dT%H:%M:%S')
1993
        account_expiration_datetime = account_expiration_datetime.replace(tzinfo=timezone.utc)
1994
        account_expiration_datetime -= timedelta(minutes=timezone_offset)
1995
1996
        password_expiration_datetime = datetime.strptime(new_values['data']['password_expiration_datetime'],
1997
                                                         '%Y-%m-%dT%H:%M:%S')
1998
        password_expiration_datetime = password_expiration_datetime.replace(tzinfo=timezone.utc)
1999
        password_expiration_datetime -= timedelta(minutes=timezone_offset)
2000
2001
        cnx = mysql.connector.connect(**config.myems_user_db)
2002
        cursor = cnx.cursor()
2003
2004
        cursor.execute(" SELECT name "
2005
                       " FROM tbl_new_users "
2006
                       " WHERE id = %s ", (id_,))
2007
        if cursor.fetchone() is None:
2008
            cursor.close()
2009
            cnx.close()
2010
            raise falcon.HTTPError(status=falcon.HTTP_404, title='API.NOT_FOUND',
2011
                                   description='API.USER_NOT_FOUND')
2012
2013
        if privilege_id is not None:
2014
            cursor.execute(" SELECT name "
2015
                           " FROM tbl_privileges "
2016
                           " WHERE id = %s ",
2017
                           (privilege_id,))
2018
            if cursor.fetchone() is None:
2019
                cursor.close()
2020
                cnx.close()
2021
                raise falcon.HTTPError(status=falcon.HTTP_404, title='API.NOT_FOUND',
2022
                                       description='API.PRIVILEGE_NOT_FOUND')
2023
2024
        cursor.execute(" SELECT name, uuid, display_name, email, salt, password"
2025
                       " FROM tbl_new_users "
2026
                       " WHERE id = %s ", (id_,))
2027
        row = cursor.fetchone()
2028
        if row is None:
2029
            cursor.close()
2030
            cnx.close()
2031
            raise falcon.HTTPError(status=falcon.HTTP_404, title='API.NOT_FOUND',
2032
                                   description='API.USER_NOT_FOUND')
2033
        else:
2034
            name = row[0]
2035
            user_uuid = row[1]
2036
            display_name = row[2]
2037
            email = row[3]
2038
            salt = row[4]
2039
            passowrd = row[5]
2040
2041
        add_row = (" INSERT INTO tbl_users "
2042
                   "     (name, uuid, display_name, email, salt, password, is_admin, is_read_only, privilege_id, "
2043
                   "      account_expiration_datetime_utc, password_expiration_datetime_utc, failed_login_count) "
2044
                   " VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) ")
2045
2046
        cursor.execute(add_row, (name,
2047
                                 user_uuid,
2048
                                 display_name,
2049
                                 email,
2050
                                 salt,
2051
                                 passowrd,
2052
                                 is_admin,
2053
                                 is_read_only,
2054
                                 privilege_id,
2055
                                 account_expiration_datetime,
2056
                                 password_expiration_datetime,
2057
                                 0))
2058
        new_id = cursor.lastrowid
2059
        cnx.commit()
2060
2061
        cursor.execute(" DELETE FROM tbl_new_users WHERE id = %s", (id_,))
2062
        cnx.commit()
2063
        cursor.close()
2064
        cnx.close()
2065
2066
        resp.status = falcon.HTTP_201
2067
        resp.location = '/users/' + str(new_id)
2068
2069