core.user   F
last analyzed

Complexity

Total Complexity 367

Size/Duplication

Total Lines 2036
Duplicated Lines 9.38 %

Importance

Changes 0
Metric Value
wmc 367
eloc 1544
dl 191
loc 2036
rs 0.8
c 0
b 0
f 0

48 Methods

Rating   Name   Duplication   Size   Complexity  
A UserCollection.on_options() 0 3 1
A UserCollection.__init__() 0 4 1
A ResetPassword.__init__() 0 4 1
A ResetPassword.on_options() 0 3 1
A Unlock.on_options() 0 3 1
A Unlock.__init__() 0 4 1
A NewUserCollection.on_options() 0 3 1
A NewUserItem.__init__() 0 4 1
A UserItem.__init__() 0 4 1
A NewUserItem.on_delete() 0 28 4
F UserCollection.on_post() 0 147 29
A NewUserItem.on_get() 0 28 4
A EmailMessageItem.on_options() 0 3 1
B EmailMessageItem.on_delete() 33 33 8
C UserCollection.on_get() 0 41 10
F NewUserApprove.on_put() 0 123 17
F ForgotPassword.on_put() 0 100 15
A NewUserApprove.__init__() 0 4 1
A EmailMessageItem.__init__() 0 4 1
A UserItem.on_options() 0 3 1
C Unlock.on_put() 0 59 10
A EmailMessageCollection.on_options() 0 3 1
A UserLogin.on_options() 0 3 1
B EmailMessageItem.on_get() 37 37 8
A ChangePassword.on_options() 0 3 1
F ResetPassword.on_put() 0 111 19
F EmailMessageCollection.on_get() 73 73 14
F UserItem.on_put() 0 143 28
F ChangePassword.on_put() 0 117 20
A UserLogout.__init__() 0 4 1
A NewUserApprove.on_options() 0 3 1
A ChangePassword.__init__() 0 4 1
F UserLogin.on_put() 0 169 25
B UserItem.on_delete() 0 40 6
F EmailMessageItem.on_put() 24 134 33
A UserLogout.on_options() 0 3 1
A NewUserCollection.on_get() 0 24 4
F NewUserItem.on_put() 0 105 19
A UserLogin.__init__() 0 4 1
A NewUserItem.on_options() 0 3 1
A ForgotPassword.__init__() 0 4 1
C UserItem.on_get() 0 45 10
F EmailMessageCollection.on_post() 24 127 24
F NewUserCollection.on_post() 0 142 25
A NewUserCollection.__init__() 0 4 1
A EmailMessageCollection.__init__() 0 4 1
A ForgotPassword.on_options() 0 3 1
C UserLogout.on_put() 0 33 9

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complexity

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

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