Skip to content

Commit dd1b778

Browse files
authored
Merge pull request #3096 from jeff1evesque/feature-2869
#2869: Determine programmatic user login validation
2 parents b7341ab + ffecdf3 commit dd1b778

File tree

13 files changed

+291
-54
lines changed

13 files changed

+291
-54
lines changed

brain/load_data.py

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,13 @@
88
'''
99

1010
import json
11+
from flask import current_app, session
1112
from brain.session.data_append import DataAppend
1213
from brain.session.data_new import DataNew
1314
from brain.session.model_generate import ModelGenerate
1415
from brain.session.model_predict import ModelPredict
1516
from brain.database.session import Session
17+
from brain.database.account import Account
1618

1719

1820
class Load_Data(object):
@@ -29,7 +31,7 @@ class Load_Data(object):
2931
3032
'''
3133

32-
def __init__(self, data):
34+
def __init__(self, data, username=None):
3335
'''
3436
3537
This constructor is responsible for defining class variables.
@@ -45,6 +47,22 @@ def __init__(self, data):
4547
]
4648
self.list_error = []
4749

50+
# flask session user
51+
if 'uid' in session and session['uid']:
52+
self.uid = session['uid']
53+
54+
# flask jwt-token user
55+
elif username:
56+
uid = Account().get_uid(username)
57+
if uid['result']:
58+
self.uid = uid['result']
59+
else:
60+
self.uid = current_app.config.get('USER_ID')
61+
62+
# unauthenticated user
63+
else:
64+
self.uid = current_app.config.get('USER_ID')
65+
4866
def load_data_new(self):
4967
'''
5068
@@ -54,7 +72,7 @@ def load_data_new(self):
5472
'''
5573

5674
# instantiate class
57-
session = DataNew(self.data)
75+
session = DataNew(self.data, self.uid)
5876

5977
# implement class methods
6078
if not session.validate_arg_none():
@@ -89,7 +107,7 @@ def load_data_append(self):
89107
'''
90108

91109
# instantiate class
92-
session = DataAppend(self.data)
110+
session = DataAppend(self.data, self.uid)
93111

94112
# define current session id
95113
collection = self.data['properties']['collection']

brain/session/base_data.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
import datetime
1414
from brain.session.base import Base
15-
from flask import current_app, session
15+
from flask import current_app
1616
from brain.session.data.dataset import dataset2dict
1717
from brain.database.dataset import Collection
1818
from brain.database.entity import Entity
@@ -32,7 +32,7 @@ class BaseData(Base):
3232
3333
'''
3434

35-
def __init__(self, premodel_data):
35+
def __init__(self, premodel_data, uid):
3636
'''
3737
3838
This constructor inherits additional class properties, from the
@@ -50,8 +50,8 @@ def __init__(self, premodel_data):
5050
self.model_type = premodel_data['properties']['model_type']
5151
self.premodel_data = premodel_data
5252

53-
if 'uid' in session:
54-
self.uid = session['uid']
53+
if uid:
54+
self.uid = uid
5555
self.max_collection = current_app.config.get('MAXCOL_AUTH')
5656
self.max_document = current_app.config.get('MAXDOC_AUTH')
5757

@@ -116,7 +116,6 @@ def save_premodel_dataset(self):
116116
):
117117
current_utc = datetime.datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S")
118118
self.premodel_data['properties']['datetime_saved'] = current_utc
119-
self.premodel_data['properties']['uid'] = self.uid
120119
document = {
121120
'properties': self.premodel_data['properties'],
122121
'dataset': self.dataset

brain/session/data_append.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ class DataAppend(BaseData):
2929
3030
'''
3131

32-
def __init__(self, premodel_data):
32+
def __init__(self, premodel_data, uid):
3333
'''
3434
3535
This constructor inherits additional class properties, from the
@@ -38,7 +38,7 @@ def __init__(self, premodel_data):
3838
'''
3939

4040
# superclass constructor
41-
BaseData.__init__(self, premodel_data)
41+
BaseData.__init__(self, premodel_data, uid)
4242

4343
# class variable
4444
if session.get('uid'):

brain/session/data_new.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ class DataNew(BaseData):
2727
2828
'''
2929

30-
def __init__(self, premodel_data):
30+
def __init__(self, premodel_data, uid):
3131
'''
3232
3333
This constructor inherits additional class properties, from the
@@ -36,7 +36,7 @@ def __init__(self, premodel_data):
3636
'''
3737

3838
# superclass constructor
39-
BaseData.__init__(self, premodel_data)
39+
BaseData.__init__(self, premodel_data, uid)
4040

4141
def save_entity(self, session_type, id_entity=None):
4242
'''
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
=====
2+
Login
3+
=====
4+
5+
Users can submit a ``/login`` request, for the programmatic-api:
6+
7+
.. code:: python
8+
9+
# request attributes
10+
username = 'jeff1evesque'
11+
password = 'password123'
12+
payload = {'user[login]': username, 'user[password]': password}
13+
14+
# login and get flask-jwt token
15+
login = client.post(
16+
'/login',
17+
headers={'Content-Type': 'application/json'},
18+
data=payload
19+
)
20+
token = login.json['access_token']
21+
22+
The returned ``token`` value, can be supplied on successive requests, to a rest
23+
endpoint, expecting the corresponding ``token`` value. For example, supplying
24+
a valid token, to the ``/load-data`` endpoint:
25+
26+
.. code:: python
27+
28+
# request attributes
29+
username = 'jeff1evesque'
30+
password = 'password123'
31+
payload = {'user[login]': username, 'user[password]': password}
32+
33+
# login and get flask-jwt token
34+
login = client.post(
35+
'/login',
36+
headers={'Content-Type': 'application/json'},
37+
data=payload
38+
)
39+
token = login.json['access_token']
40+
41+
# provide flask-jwt token
42+
login = client.post(
43+
'/load-data',
44+
headers={
45+
'Authorization': 'Bearer ' + token,
46+
'Content-Type': 'application/json'
47+
},
48+
data=payload
49+
)
50+
51+
The following sessions, can be implemented with the above token, or omitted as
52+
an anonymous user:
53+
54+
- `data-new <https://github.com/jeff1evesque/machine-learning/blob/master/doc/programmatic_interface/data/data_new.rst>`_
55+
- `data-append <https://github.com/jeff1evesque/machine-learning/blob/master/doc/programmatic_interface/data/data_new.rst>`_
56+
57+
The following unit tests, provides examples of the `flask-jwt <http://flask-jwt-extended.readthedocs.io/en/latest/>`_
58+
implementation:
59+
60+
- |pytest_6_user_session.py|_
61+
62+
.. |pytest_6_user_session.py| replace:: ``pytest_6_user_session.py``
63+
.. _pytest_6_user_session.py: https://github.com/jeff1evesque/machine-learning/tree/master/test/live_server/1_authentication/pytest_6_user_login.py
64+

doc/programmatic_interface/data/data_append.rst

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,24 @@ the ``data`` attribute, in a given ``POST`` request:
2828
2929
requests.post(endpoint_url, headers=headers, data=json_string_here)
3030
31+
To submit a ``/load-data`` request, as a valid authenticated user, a valid token
32+
must be suppied to the ``headers``:
33+
34+
.. code:: python
35+
36+
import requests
37+
38+
endpoint_url = 'https://localhost:8080/load-data'
39+
headers={
40+
'Authorization': 'Bearer ' + token,
41+
'Content-Type': 'application/json'
42+
},
43+
44+
requests.post(endpoint_url, headers=headers, data=json_string_here)
45+
46+
**Note:** more information, regarding how to obtain a valid ``token``, can be further
47+
reviewed, in the ``/login`` `documentation <https://github.com/jeff1evesque/machine-learning/tree/master/doc/programmatic_interface/authentication/login.rst>`_.
48+
3149
The following properties define the above ``data`` attribute:
3250

3351
- ``collection``: collection of dataset documents, used as a reference to add additional dataset documents into

doc/programmatic_interface/data/data_new.rst

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,24 @@ the ``data`` attribute, in a given ``POST`` request:
2828
2929
requests.post(endpoint_url, headers=headers, data=json_string_here)
3030
31+
To submit a ``/load-data`` request, as a valid authenticated user, a valid token
32+
must be suppied to the ``headers``:
33+
34+
.. code:: python
35+
36+
import requests
37+
38+
endpoint_url = 'https://localhost:8080/load-data'
39+
headers={
40+
'Authorization': 'Bearer ' + token,
41+
'Content-Type': 'application/json'
42+
},
43+
44+
requests.post(endpoint_url, headers=headers, data=json_string_here)
45+
46+
**Note:** more information, regarding how to obtain a valid ``token``, can be further
47+
reviewed, in the ``/login`` `documentation <https://github.com/jeff1evesque/machine-learning/tree/master/doc/programmatic_interface/authentication/login.rst>`_.
48+
3149
The following properties define the above ``data`` attribute:
3250

3351
- ``session_name``: title for the corresponding ``data_new`` session

factory.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from logging.handlers import RotatingFileHandler
1919
from brain.cache.session import RedisSessionInterface
2020
from interface.views import blueprint
21+
from flask_jwt_extended import JWTManager
2122

2223

2324
# application factory
@@ -80,6 +81,9 @@ def create_app(args={'prefix': '', 'settings': ''}):
8081
# register blueprint
8182
app.register_blueprint(blueprint)
8283

84+
# set the flask-jwt-extended extension
85+
JWTManager(app)
86+
8387
# local logger: used for this module
8488
root = general['root']
8589
LOG_PATH = root + webserver['flask']['log_path']

hiera/packages.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ development:
4343
pip:
4444
general:
4545
flask-script: '2.0.5'
46+
flask-jwt-extended: '3.3.4'
4647
jsonschema: '2.5.1'
4748
pytest-flask: '0.10.0'
4849
six: '1.5.2'

0 commit comments

Comments
 (0)