Skip to content

Commit

Permalink
Merge pull request #28 from GeographicaGS/robgc/v0.2.0
Browse files Browse the repository at this point in the history
v0.2.0: Profile checking and updated dependencies
  • Loading branch information
josemazo authored Nov 19, 2018
2 parents 6c0cb24 + 8012e4d commit a4ed9c2
Show file tree
Hide file tree
Showing 7 changed files with 98 additions and 15 deletions.
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,23 @@ pip install -e git+https://github.com/GeographicaGS/Longitude#egg=longitude

You need to be part of Geographica's developer team to be able to accomplish this task.


Start docker
```
docker-compose run --rm python bash
```

Install needed dependencies
```
pip install -r requirements.txt
```

Install twine
```
pip install twine
```

Set vertion at setup.py
Set version at setup.py

Upload:
```
Expand Down
90 changes: 80 additions & 10 deletions longitude/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@

from functools import wraps
from flask import Blueprint, jsonify, request
from flask_jwt_extended import JWTManager, jwt_required, create_access_token, get_jwt_identity
from flask_jwt_extended import (
JWTManager, jwt_required, create_access_token, get_jwt_identity
)
from .config import cfg
from .models.user_model import UserModel
from dateutil.parser import parse
Expand All @@ -21,7 +23,6 @@ def ini(app):

log.info(cfg['AUTH_TOKEN_EXPIRATION'])


JWTManager(app)
app.config['JWT_TOKEN_LOCATION'] = ['headers', 'query_string']
app.config['JWT_ACCESS_TOKEN_EXPIRES'] = EXPIRATION_DELTA
Expand All @@ -35,9 +36,23 @@ def ini(app):
routes = Blueprint('auth', __name__)


def auth():
"""
AUTH decorator. It checks for a valid token and validates this token against the DB
def auth(allow_profiles=None, disallow_profiles=None):
"""AUTH decorator.
It checks for a valid token and validates this token against the DB.
With AUTH_USE_PROFILES env var activated it will aditionally check if
the user has a allowed or disallowed profile for the endpoint.
You can only specify a set of allowed profiles or a set of disallowed
profiles.
:param allow_profiles: A list of profiles/roles that can access \
the endpoint, defaults to None.
:param allow_profiles: list[str], optional.
:param disallow_profiles: A list of profiles/roles that cannot access \
the endpoint, defaults to None
:param disallow_profiles: list[str], optional.
:raises ValueError: If both allowed and disallowed profiles are specified.
"""

def decorator(func):
Expand All @@ -58,7 +73,9 @@ def check_token(*args, **kwargs):
token = request.args.get('jwt')

if not token:
return jsonify({'msg': 'You must provide an authorization header (token)'}), 401
return jsonify({
'msg': 'You must provide an authorization header (token)'
}), 401

if cfg['AUTH_TOKEN_DOBLE_CHECK']:
user_model = UserModel({
Expand All @@ -70,6 +87,29 @@ def check_token(*args, **kwargs):
if not valid:
return jsonify({'msg': 'No valid token'}), 403

if cfg['AUTH_USE_PROFILES']:
allow_type_prfs = check_auth_profiles(allow_profiles)
disallow_type_prfs = check_auth_profiles(disallow_profiles)
user_prof = user_data['profile']

err_response = jsonify(
{
'msg': 'You are not authorized to request '
'this information'
}
), 403

if (allow_type_prfs is list and
disallow_type_prfs is list):
raise ValueError('You can only specify allowed profiles '
'or disallowed profiles')
elif allow_type_prfs is list:
if user_prof not in allow_profiles:
return err_response
elif disallow_type_prfs is list:
if user_prof in disallow_profiles:
return err_response

request.user = user_data
request.token = token

Expand All @@ -79,10 +119,35 @@ def check_token(*args, **kwargs):

return decorator


def check_login_fields(fields_list, user_data, username):
check_list = [True for x in fields_list if user_data[x] == username]
return True if check_list else False


def check_auth_profiles(profiles):
correct = True
prof_type = None

if type(profiles) is list:
prof_type = list
if len(profiles):
for element in profiles:
if type(element) is not str:
correct = False
break
prof_type = list if correct else None
elif profiles is None:
pass
else:
correct = False

if not correct:
raise ValueError('Profiles parameter is expected to be a list[str]')
else:
return prof_type


@routes.route('/token', methods=['GET'])
def get_token():
"""
Expand All @@ -104,8 +169,10 @@ def get_token():

login_fields = cfg['AUTH_LOGIN_FIELDS'].split(',')

if not user_data or not check_login_fields(login_fields, user_data, username) or not bcrypt.checkpw(password.encode('utf8'),
user_data['password'].encode('utf-8')):
if (not user_data or
not check_login_fields(login_fields, user_data, username) or
not bcrypt.checkpw(password.encode('utf8'),
user_data['password'].encode('utf-8'))):
return jsonify({'msg': 'Bad username or password'}), 401

if cfg['AUTH_ACCOUNT_EXPIRATION_FIELD']:
Expand All @@ -117,8 +184,11 @@ def get_token():
user_model.update_last_access(user_data['id'])

if cfg['EXTRA_JWT_IDENTITY_FIELDS']:
extra_jwt_identity_fields = {key.replace('JWT_IDENTITY_', '').lower(): value for (key, value) in
cfg['EXTRA_JWT_IDENTITY_FIELDS'].items()}
extra_jwt_identity_fields = {
key.replace('JWT_IDENTITY_', '').lower(): value
for (key, value) in
cfg['EXTRA_JWT_IDENTITY_FIELDS'].items()
}
user_data.update(extra_jwt_identity_fields)

del user_data['password']
Expand Down
3 changes: 2 additions & 1 deletion longitude/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
'AUTH_LAST_ACCESS_FIELD': os.environ.get('AUTH_LAST_ACCESS_FIELD', None),
'AUTH_ACCOUNT_EXPIRATION_FIELD': os.environ.get('AUTH_ACCOUNT_EXPIRATION_FIELD', None),
'AUTH_UPDATE_LAST_ACCESS': bool(int(os.environ.get('AUTH_UPDATE_LAST_ACCESS', 0))),
'AUTH_HEADER_NAME': os.environ.get('AUTH_HEADER_NAME', 'Authorization')
'AUTH_HEADER_NAME': os.environ.get('AUTH_HEADER_NAME', 'Authorization'),
'AUTH_USE_PROFILES': bool(int(os.environ.get('AUTH_USE_PROFILES', 0)))
}


Expand Down
4 changes: 4 additions & 0 deletions longitude/models/user/abstract_user_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,7 @@ def check_user_token(self, token):
@abstractmethod
def delete_user_token(self, user_id):
pass

@abstractmethod
def update_last_access(self, user_id):
pass
1 change: 1 addition & 0 deletions longitude/models/user/postgres_user_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"""
from longitude.models.base_models import PostgresModel
from longitude.models.user import AbstractUserModel
from longitude.models.utils import SQLTrustedString


class PostgresUserModel(AbstractUserModel, PostgresModel):
Expand Down
4 changes: 2 additions & 2 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ certifi==2017.11.5
cffi==1.11.5
chardet==3.0.4
click==6.7
Flask==0.12.2
Flask==1.0.2
Flask-JWT-Extended==3.12.0
future==0.15.2
idna==2.6
Expand All @@ -18,7 +18,7 @@ PyJWT==1.6.1
pyrestcli==0.6.4
python-dateutil==2.5.3
redis==2.10.6
requests==2.18.4
requests==2.20.1
requests-toolbelt==0.8.0
six==1.11.0
tqdm==4.19.4
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
setup(
name='geographica-longitude',

version='0.1.31',
version='0.2.0',

description='Longitude',
long_description=long_description,
Expand Down

0 comments on commit a4ed9c2

Please sign in to comment.