Passed
Push — master ( a7685a...a3b41e )
by
unknown
10:46
created

core.user.UserCollection.on_get()   D

Complexity

Conditions 12

Size

Total Lines 58
Code Lines 44

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 44
dl 0
loc 58
rs 4.8
c 0
b 0
f 0
cc 12
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.UserCollection.on_get() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

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

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