diff --git a/app/config/development.default.py b/app/config/development.default.py index 9f3058ea9fc570a81e91279fe26a97107aeff6ff..7b4be41032e2d6152c34291609849e20d899f944 100644 --- a/app/config/development.default.py +++ b/app/config/development.default.py @@ -8,11 +8,3 @@ DEBUG = True HEADER_APP_KEY = 'x-blocpower-app-key' HEADER_APP_SECRET = 'x-blocpower-app-secret' - -# Auth0 Authentication -AUTH0_AUTH_HEADER = 'x-blocpower-auth0-token' -AUTH0_DOMAIN = os.environ['AUTH0_DOMAIN'] -AUTH0_AUDIENCE = os.environ['AUTH0_AUDIENCE'] -AUTH0_CLAIMS_NAMESPACE = os.environ['AUTH0_CLAIMS_NAMESPACE'] -AUTH0_CLIENT_ID = os.environ['AUTH0_CLIENT_ID'] -AUTH0_CLIENT_SECRET = os.environ['AUTH0_CLIENT_SECRET'] diff --git a/app/config/production.default.py b/app/config/production.default.py index 36d39fce9c802c4338affb9b152bfd614d4c7988..e57609219b4a9275ae1b8be4a320b5a3f341038a 100644 --- a/app/config/production.default.py +++ b/app/config/production.default.py @@ -7,11 +7,3 @@ DEBUG = False HEADER_APP_KEY = 'x-blocpower-app-key' HEADER_APP_SECRET = 'x-blocpower-app-secret' - -# Auth0 Authentication -AUTH0_AUTH_HEADER = 'x-blocpower-auth0-token' -AUTH0_DOMAIN = os.environ['AUTH0_DOMAIN'] -AUTH0_AUDIENCE = os.environ['AUTH0_AUDIENCE'] -AUTH0_CLAIMS_NAMESPACE = os.environ['AUTH0_CLAIMS_NAMESPACE'] -AUTH0_CLIENT_ID = os.environ['AUTH0_CLIENT_ID'] -AUTH0_CLIENT_SECRET = os.environ['AUTH0_CLIENT_SECRET'] diff --git a/app/config/staging.default.py b/app/config/staging.default.py index 9f3058ea9fc570a81e91279fe26a97107aeff6ff..7b4be41032e2d6152c34291609849e20d899f944 100644 --- a/app/config/staging.default.py +++ b/app/config/staging.default.py @@ -8,11 +8,3 @@ DEBUG = True HEADER_APP_KEY = 'x-blocpower-app-key' HEADER_APP_SECRET = 'x-blocpower-app-secret' - -# Auth0 Authentication -AUTH0_AUTH_HEADER = 'x-blocpower-auth0-token' -AUTH0_DOMAIN = os.environ['AUTH0_DOMAIN'] -AUTH0_AUDIENCE = os.environ['AUTH0_AUDIENCE'] -AUTH0_CLAIMS_NAMESPACE = os.environ['AUTH0_CLAIMS_NAMESPACE'] -AUTH0_CLIENT_ID = os.environ['AUTH0_CLIENT_ID'] -AUTH0_CLIENT_SECRET = os.environ['AUTH0_CLIENT_SECRET'] diff --git a/app/permissions/auth.py b/app/permissions/auth.py index 107a77f2bec650fd45f10479ea84f6ceb0a7b9c9..2b70882a8c8f509c2ea26a25509ed304ceb893ec 100644 --- a/app/permissions/auth.py +++ b/app/permissions/auth.py @@ -1,4 +1,6 @@ """Permissions to check authentication.""" +import json +import requests from flask import current_app, g from werkzeug.exceptions import Unauthorized from jose import jwt @@ -12,8 +14,7 @@ from .application import app_need class AuthNeed(Permission): """Check if the access token is valid.""" - def __init__(self, required_permissions=[]): - self.required_permissions = required_permissions + def __init__(self): super().__init__() def is_met(self): @@ -25,6 +26,7 @@ class AuthNeed(Permission): AUTH0_DOMAIN = current_app.config['AUTH0_DOMAIN'] API_AUDIENCE = current_app.config['AUTH0_AUDIENCE'] ALGORITHMS = ["RS256"] + r = requests.get("https://"+AUTH0_DOMAIN+"/.well-known/jwks.json") jwks = json.loads(r.text) unverified_header = jwt.get_unverified_header(auth0_token) @@ -48,8 +50,7 @@ class AuthNeed(Permission): issuer="https://"+AUTH0_DOMAIN+"/" ) g.sub = payload['sub'] - # For now we will print and return unauthorized. In the future - # we will log these errors and the requester + except jwt.ExpiredSignatureError: current_app.logger.info('Token is expired') return False @@ -60,22 +61,8 @@ class AuthNeed(Permission): current_app.logger.info('Invalid header. Unable to parse the token') return False - # Check permissions - # The self.bool_ variable is a boolean if no value is passed in - if self.required_permissions: - CLAIMS_NAMESPACE = current_app.config['AUTH0_CLAIMS_NAMESPACE'] - permission_key = '{}permissions'.format(CLAIMS_NAMESPACE) - if permission_key not in payload: - return False - actual_permissions = payload[permission_key] - for permission in self.required_permissions: - if permission not in actual_permissions: - return False return True return False auth_need = AuthNeed() standard_login_need = app_need & auth_need -# An example permission need decorator. This decorator requires that the user -# has the 'view::developer' permission -developer_view_need = app_need & AuthNeed(['view::developer']) diff --git a/app/permissions/authorization.py b/app/permissions/authorization.py new file mode 100644 index 0000000000000000000000000000000000000000..fcb5a1c706731ad4d587a2912edc202a141c0e43 --- /dev/null +++ b/app/permissions/authorization.py @@ -0,0 +1,47 @@ +"""Authorization decorator""" +from functools import wraps +from flask import current_app, g, request +from werkzeug.exceptions import Unauthorized +from ..lib.service import services + +CRUD_TO_REST = { + 'POST': 'create', + 'GET': 'read', + 'PUT': 'update', # TODO: if no id 'create' + 'PATCH': 'update', + 'DELETE': 'delete', +} + + +def secured(f): + @wraps(f) + def decorated_function(*args, **kwargs): + if g.sub is not None: + current_app.logger.info('{} accessing {}'.format(g.sub, request.endpoint)) + + action = CRUD_TO_REST[request.method] + resource = request.endpoint.split(':').pop(0) + if resource.endswith('View'): + resource = resource[:-4] + + from flask import session + auth0_header = current_app.config.get('AUTH0_AUTH_HEADER') + headers = {} + headers[auth0_header] = request.headers.get(auth0_header) + params = {'permissions': 'true'} + + response = services.user.get('/user/{}'.format(g.sub), params=params, headers=headers) + if not response.status_code == 200: + raise Unauthorized + + data = response.json() + + permissions = data['data'][g.sub]['permissions'] + action_resource = '{action}::{resource}'.format(action=action, resource=resource) + if action_resource not in permissions: + raise Unauthorized + + else: + raise Unauthorized + return f(*args, **kwargs) + return decorated_function diff --git a/app/views/base.py b/app/views/base.py index 4700da8cccec2b0ca3e7b047f29277ac4361303d..bf038245d77c430e118f33ad6f0197f95a839728 100644 --- a/app/views/base.py +++ b/app/views/base.py @@ -1,8 +1,9 @@ from bpvalve.flask.views import UnprotectedRestView from app.permissions.auth import standard_login_need +from ..permissions.authorization import secured class RestView(UnprotectedRestView): """A view wrapper for RESTful controllers that _does_ offer API protection.""" - decorators = (standard_login_need,) + decorators = (secured, standard_login_need) diff --git a/requirements.txt b/requirements.txt index 32077957e7c670fdb47dd3d22e5e901c5b5fc648..c3990ad8506d57cf09f46e1d7c4bebf57f1d1862 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,8 +2,8 @@ arrow==0.7.0 bcrypt==2.0.0 cffi==1.5.2 blessed==1.9.5 -botocore==1.3.28 -git+ssh://git@github.com/Blocp/bpvalve.git@v1.1.1 +botocore==1.5.48 +git+ssh://git@github.com/Blocp/bpvalve.git@v1.2.0 cement==2.4.0 colorama==0.3.3 docker-py==1.1.0