Passed
Push — master ( cb176a...4b8481 )
by
unknown
13:30
created

core.useractivity.admin_control()   C

Complexity

Conditions 10

Size

Total Lines 67
Code Lines 38

Duplication

Lines 53
Ratio 79.1 %

Importance

Changes 0
Metric Value
eloc 38
dl 53
loc 67
rs 5.9999
c 0
b 0
f 0
cc 10
nop 1

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

Complexity

Complex classes like core.useractivity.admin_control() 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 os
2
import uuid
3
from datetime import datetime
4
from functools import wraps
5
import falcon
6
import mysql.connector
7
import simplejson as json
8
from gunicorn.http.body import Body
9
import config
10
11
12 View Code Duplication
def admin_control(req):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
13
    """
14
    Check administrator privilege in request headers to protect resources from invalid access.
15
16
    This function validates that the request contains valid administrator credentials
17
    by checking the USER-UUID and TOKEN headers against the user database sessions.
18
    It ensures that only authenticated administrators can access protected resources.
19
20
    Args:
21
        req: HTTP request object containing headers with USER-UUID and TOKEN
22
23
    Raises:
24
        falcon.HTTPError: If invalid credentials or expired session
25
26
    Returns:
27
        None: If validation passes
28
    """
29
    # Validate USER-UUID header
30
    if 'USER-UUID' not in req.headers or \
31
            not isinstance(req.headers['USER-UUID'], str) or \
32
            len(str.strip(req.headers['USER-UUID'])) == 0:
33
        raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
34
                               description='API.INVALID_USER_UUID')
35
    admin_user_uuid = str.strip(req.headers['USER-UUID'])
36
37
    # Validate TOKEN header
38
    if 'TOKEN' not in req.headers or \
39
            not isinstance(req.headers['TOKEN'], str) or \
40
            len(str.strip(req.headers['TOKEN'])) == 0:
41
        raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
42
                               description='API.INVALID_TOKEN')
43
    admin_token = str.strip(req.headers['TOKEN'])
44
45
    # Check administrator session in user database
46
    cnx = mysql.connector.connect(**config.myems_user_db)
47
    cursor = cnx.cursor()
48
    query = (" SELECT utc_expires "
49
             " FROM tbl_sessions "
50
             " WHERE user_uuid = %s AND token = %s")
51
    cursor.execute(query, (admin_user_uuid, admin_token,))
52
    row = cursor.fetchone()
53
54
    if row is None:
55
        cursor.close()
56
        cnx.close()
57
        raise falcon.HTTPError(status=falcon.HTTP_404, title='API.NOT_FOUND',
58
                               description='API.ADMINISTRATOR_SESSION_NOT_FOUND')
59
    else:
60
        utc_expires = row[0]
61
        # Check if session has expired
62
        if datetime.utcnow() > utc_expires:
63
            cursor.close()
64
            cnx.close()
65
            raise falcon.HTTPError(status=falcon.HTTP_400, title='API.BAD_REQUEST',
66
                                   description='API.ADMINISTRATOR_SESSION_TIMEOUT')
67
    # check administrator privilege
68
    query = (" SELECT name "
69
             " FROM tbl_users "
70
             " WHERE uuid = %s AND is_admin = 1 AND is_read_only = 0 ")
71
    cursor.execute(query, (admin_user_uuid,))
72
    row = cursor.fetchone()
73
    cursor.close()
74
    cnx.close()
75
    if row is None:
76
        raise falcon.HTTPError(status=falcon.HTTP_400,
77
                               title='API.BAD_REQUEST',
78
                               description='API.INVALID_PRIVILEGE')
79
80
81 View Code Duplication
def access_control(req):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
82
    """
83
        Check user privilege in request headers to protect resources from invalid access
84
        :param req: HTTP request
85
        :return: HTTPError if invalid else None
86
        """
87
    if 'USER-UUID' not in req.headers or \
88
            not isinstance(req.headers['USER-UUID'], str) or \
89
            len(str.strip(req.headers['USER-UUID'])) == 0:
90
        raise falcon.HTTPError(status=falcon.HTTP_400,
91
                               title='API.BAD_REQUEST',
92
                               description='API.INVALID_USER_UUID')
93
    user_uuid = str.strip(req.headers['USER-UUID'])
94
95
    if 'TOKEN' not in req.headers or \
96
            not isinstance(req.headers['TOKEN'], str) or \
97
            len(str.strip(req.headers['TOKEN'])) == 0:
98
        raise falcon.HTTPError(status=falcon.HTTP_400,
99
                               title='API.BAD_REQUEST',
100
                               description='API.INVALID_TOKEN')
101
    ordinary_token = str.strip(req.headers['TOKEN'])
102
103
    # Check user session
104
    cnx = mysql.connector.connect(**config.myems_user_db)
105
    cursor = cnx.cursor()
106
    query = (" SELECT utc_expires "
107
             " FROM tbl_sessions "
108
             " WHERE user_uuid = %s AND token = %s")
109
    cursor.execute(query, (user_uuid, ordinary_token,))
110
    row = cursor.fetchone()
111
112
    if row is None:
113
        cursor.close()
114
        cnx.close()
115
        raise falcon.HTTPError(status=falcon.HTTP_404,
116
                               title='API.NOT_FOUND',
117
                               description='API.USER_SESSION_NOT_FOUND')
118
    else:
119
        utc_expires = row[0]
120
        if datetime.utcnow() > utc_expires:
121
            cursor.close()
122
            cnx.close()
123
            raise falcon.HTTPError(status=falcon.HTTP_400,
124
                                   title='API.BAD_REQUEST',
125
                                   description='API.USER_SESSION_TIMEOUT')
126
    # todo: check user privilege
127
    query = (" SELECT name "
128
             " FROM tbl_users "
129
             " WHERE uuid = %s ")
130
    cursor.execute(query, (user_uuid,))
131
    row = cursor.fetchone()
132
    cursor.close()
133
    cnx.close()
134
    if row is None:
135
        raise falcon.HTTPError(status=falcon.HTTP_400,
136
                               title='API.BAD_REQUEST',
137
                               description='API.INVALID_PRIVILEGE')
138
139
140
def api_key_control(req):
141
    """
142
        Check API privilege in request headers to protect resources from invalid access
143
        :param req: HTTP request
144
        :return: HTTPError if invalid else None
145
    """
146
    api_key = str.strip(req.headers['API-KEY'])
147
    cnx = mysql.connector.connect(**config.myems_user_db)
148
    cursor = cnx.cursor()
149
    query = (" SELECT expires_datetime_utc "
150
             " FROM tbl_api_keys "
151
             " WHERE token = %s ")
152
    cursor.execute(query, (api_key,))
153
    row = cursor.fetchone()
154
    cursor.close()
155
    cnx.close()
156
    if row is None:
157
        raise falcon.HTTPError(status=falcon.HTTP_404,
158
                               title='API.NOT_FOUND',
159
                               description='API.API_KEY_NOT_FOUND')
160
    else:
161
        expires_datetime_utc = row[0]
162
        if datetime.utcnow() > expires_datetime_utc:
163
            raise falcon.HTTPError(status=falcon.HTTP_400,
164
                                   title='API.BAD_REQUEST',
165
                                   description='API.API_KEY_HAS_EXPIRED')
166
167
168
def write_log(user_uuid, request_method, resource_type, resource_id, request_body):
169
    """
170
    :param user_uuid: user_uuid
171
    :param request_method: 'POST', 'PUT', 'DELETE'
172
    :param resource_type: class_name
173
    :param resource_id: int
174
    :param request_body: json in raw string
175
    """
176
    cnx = None
177
    cursor = None
178
    try:
179
        cnx = mysql.connector.connect(**config.myems_user_db)
180
        cursor = cnx.cursor()
181
        add_row = (" INSERT INTO tbl_logs "
182
                   "    (user_uuid, request_datetime_utc, request_method, resource_type, resource_id, request_body) "
183
                   " VALUES (%s, %s, %s, %s, %s , %s) ")
184
        cursor.execute(add_row, (user_uuid,
185
                                 datetime.utcnow(),
186
                                 request_method,
187
                                 resource_type,
188
                                 resource_id if resource_id else None,
189
                                 request_body if request_body else None,
190
                                 ))
191
        cnx.commit()
192
    except InterfaceError as e:
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable InterfaceError does not seem to be defined.
Loading history...
193
        print("Failed to connect request")
194
    except OperationalError as e:      
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable OperationalError does not seem to be defined.
Loading history...
195
        print("Failed to SQL operate request")
196
    except ProgrammingError as e:
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable ProgrammingError does not seem to be defined.
Loading history...
197
        print("Failed to SQL request")
198
    except DataError as e:
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable DataError does not seem to be defined.
Loading history...
199
        print("Failed to SQL Data request")
200
    except Exception as e:
201
        print('write_log:' + str(e))
202
    finally:
203
        if cursor:
204
            cursor.close()
205
        if cnx:
206
            cnx.close()
207
208
209
def user_logger(func):
210
    """
211
    Decorator for logging user activities
212
    :param func: the decorated function
213
    :return: the decorator
214
    """
215
    @wraps(func)
216
    def logger(*args, **kwargs):
217
        qualified_name = func.__qualname__
218
        class_name = qualified_name.split(".")[0]
219
        func_name = qualified_name.split(".")[1]
220
221
        if func_name not in ("on_post", "on_put", "on_delete"):
222
            # do not log for other HTTP Methods
223
            func(*args, **kwargs)
224
            return
225
        req, resp = args
226
        headers = req.headers
227
        if headers is not None and 'USER-UUID' in headers.keys():
228
            user_uuid = headers['USER-UUID']
229
        else:
230
            # todo: deal with requests with NULL user_uuid
231
            print('user_logger: USER-UUID is NULL')
232
            # do not log for NULL user_uuid
233
            func(*args, **kwargs)
234
            return
235
236
        if func_name == "on_post":
237
            try:
238
                file_name = str(uuid.uuid4())
239
                with open(file_name, "wb") as fw:
240
                    reads = req.stream.read()
241
                    fw.write(reads)
242
                raw_json = reads.decode('utf-8')
243
                with open(file_name, "rb") as fr:
244
                    req.stream = Body(fr)
245
                    func(*args, **kwargs)
246
                    write_log(user_uuid=user_uuid, request_method='POST', resource_type=class_name,
247
                              resource_id=kwargs.get('id_'), request_body=raw_json)
248
                os.remove(file_name)
249
            except OSError as e:
250
                print("Failed to stream request")
251
            except UnicodeDecodeError as e:
252
                print("Failed to decode request")
253
            except Exception as e:
254
                if isinstance(e, falcon.HTTPError):
255
                    raise e
256
                else:
257
                    print('user_logger:' + str(e))
258
            return
259
        elif func_name == "on_put":
260
            try:
261
                file_name = str(uuid.uuid4())
262
263
                with open(file_name, "wb") as fw:
264
                    reads = req.stream.read()
265
                    fw.write(reads)
266
                raw_json = reads.decode('utf-8')
267
                with open(file_name, "rb") as fr:
268
                    req.stream = Body(fr)
269
                    func(*args, **kwargs)
270
                    write_log(user_uuid=user_uuid, request_method='PUT', resource_type=class_name,
271
                              resource_id=kwargs.get('id_'), request_body=raw_json)
272
                os.remove(file_name)
273
            except OSError as e:
274
                print("Failed to stream request")
275
            except UnicodeDecodeError as e:
276
                print("Failed to decode request")
277
            except Exception as e:
278
                if isinstance(e, falcon.HTTPError):
279
                    raise e
280
                else:
281
                    print('user_logger:' + str(e))
282
283
            return
284
        elif func_name == "on_delete":
285
            try:
286
                func(*args, **kwargs)
287
                write_log(user_uuid=user_uuid, request_method="DELETE", resource_type=class_name,
288
                          resource_id=kwargs.get('id_'), request_body=json.dumps(kwargs))
289
            except (TypeError, ValueError) as e:
290
                print("Failed to decode JSON")
291
            except Exception as e:
292
                if isinstance(e, falcon.HTTPError):
293
                    raise e
294
                else:
295
                    print('user_logger:' + str(e))
296
            return
297
298
    return logger
299