Commit 2cfcbf6a authored by Manuel's avatar Manuel

restGateway: extracted security definition, implemented "role system"

- extracted security definition from swagger.yml into security.yml
- added seperate functions to verify tokens for a constant role
- added security definitions to protected endpoints
parent 9e4844c1
securityDefinitions:
JwtRegular:
type: apiKey
name: Authorization
in: header
x-apikeyInfoFunc: "services.token_service.verifyTokenRegular"
JwtAdmin:
type: apiKey
name: Authorization
in: header
x-apikeyInfoFunc: "services.token_service.verifyTokenAdmin"
...@@ -4,12 +4,9 @@ info: ...@@ -4,12 +4,9 @@ info:
description: This is the documentation for the RESTful API gateway. description: This is the documentation for the RESTful API gateway.
version: "1.0.0" version: "1.0.0"
# Import security definitions from seperate file
securityDefinitions: securityDefinitions:
JwtBearerAuth: $ref: './security.yml#securityDefinitions'
type: apiKey
name: Authorization
in: header
x-apikeyInfoFunc: "services.token_service.verifyToken"
consumes: consumes:
- "application/json" - "application/json"
...@@ -18,12 +15,14 @@ produces: ...@@ -18,12 +15,14 @@ produces:
basePath: "/api" basePath: "/api"
# Paths supported by the server application # Paths supported by the server application
paths: paths:
/secret: /secret:
get: get:
security: security:
- JwtBearerAuth: [] - JwtRegular: []
operationId: "rest.user.secret" operationId: "rest.user.secret"
tags: tags:
- "User" - "User"
...@@ -74,6 +73,8 @@ paths: ...@@ -74,6 +73,8 @@ paths:
description: "Wrong credentials" description: "Wrong credentials"
/users/username/{username}: /users/username/{username}:
delete: delete:
security:
- JwtAdmin: []
operationId: "rest.user.delete" operationId: "rest.user.delete"
tags: tags:
- "User" - "User"
...@@ -92,6 +93,8 @@ paths: ...@@ -92,6 +93,8 @@ paths:
description: "User does not exist" description: "User does not exist"
/users: /users:
get: get:
security:
- JwtAdmin: []
operationId: "rest.user.all" operationId: "rest.user.all"
tags: tags:
- "User" - "User"
...@@ -126,9 +129,9 @@ paths: ...@@ -126,9 +129,9 @@ paths:
type: string type: string
example: "secure_passw0rd" example: "secure_passw0rd"
responses: responses:
200: '200':
description: "User was added to the database" description: "User was added to the database"
400: '400':
description: "User already exists" description: "User already exists"
/debug: /debug:
...@@ -145,7 +148,7 @@ paths: ...@@ -145,7 +148,7 @@ paths:
schema: schema:
type: object type: object
responses: responses:
200: '200':
description: "Successful echo of request data" description: "Successful echo of request data"
/trace: /trace:
...@@ -163,9 +166,9 @@ paths: ...@@ -163,9 +166,9 @@ paths:
schema: schema:
$ref: "#/definitions/BlockchainTrace" $ref: "#/definitions/BlockchainTrace"
responses: responses:
201: '201':
description: "Successfully added" description: "Successfully added"
400: '400':
description: "Invalid input" description: "Invalid input"
definitions: definitions:
......
...@@ -23,6 +23,10 @@ class Repository(MongoRepositoryBase): ...@@ -23,6 +23,10 @@ class Repository(MongoRepositoryBase):
def add(self, user : User): def add(self, user : User):
super().insert_entry(self._user_collection, user.to_serializable_dict()) super().insert_entry(self._user_collection, user.to_serializable_dict())
def update(self, user: User):
collection = self._database[self._user_collection]
collection.update_one({"username":user.username}, {"$set": user.to_serializable_dict()})
def delete_all_with_username(self, username: str): def delete_all_with_username(self, username: str):
collection = self._database[self._user_collection] collection = self._database[self._user_collection]
collection.delete_many({"username": username}) collection.delete_many({"username": username})
......
# add modules folder to interpreter path # add modules folder to interpreter path
import sys import sys
import os import os
import prance
from pathlib import Path
from typing import Dict, Any
modules_path = '../../modules/' modules_path = '../../modules/'
if os.path.exists(modules_path): if os.path.exists(modules_path):
sys.path.insert(1, modules_path) sys.path.insert(1, modules_path)
...@@ -14,9 +18,18 @@ LOGGER = logging.getLogger(__name__) ...@@ -14,9 +18,18 @@ LOGGER = logging.getLogger(__name__)
################################# #################################
import connexion import connexion
def get_bundled_specs(main_file: Path) -> Dict[str, Any]:
parser = prance.ResolvingParser(str(main_file.absolute()),
lazy = True, backend = 'openapi-spec-validator')
parser.parse()
return parser.specification
# load swagger config # load swagger config
app = connexion.App(__name__, specification_dir='configs/') app = connexion.App(__name__, specification_dir='configs/')
app.add_api('swagger.yml') # app.add_api('swagger.yml')
app.add_api(get_bundled_specs(Path("configs/swagger.yml")),
resolver = connexion.RestyResolver("cms_rest_api"))
@app.route('/', methods=['GET']) @app.route('/', methods=['GET'])
def api_root(): def api_root():
......
...@@ -38,6 +38,8 @@ def authenticate(): ...@@ -38,6 +38,8 @@ def authenticate():
try: try:
user = UserService.get_by_credentials(username, data["password"]) user = UserService.get_by_credentials(username, data["password"])
TokenService.successful_authentication(user)
return {"token": TokenService.generate_token(user)} return {"token": TokenService.generate_token(user)}
except ValueError as e: except ValueError as e:
# return 400 if the user does not exist or the password is wrong # return 400 if the user does not exist or the password is wrong
...@@ -62,7 +64,7 @@ def add(): ...@@ -62,7 +64,7 @@ def add():
''' '''
data = request.json data = request.json
if not "role" in data: # overwrite possibly existing role with "regular user"
data["role"] = "u" data["role"] = "u"
username = data["username"] username = data["username"]
......
...@@ -9,17 +9,40 @@ from typing import Dict ...@@ -9,17 +9,40 @@ from typing import Dict
SIGNING_KEY = "yteNrMy6142WKwp8fKfrHkS5nlFpxtHgOXJh1ZPsOrV_gTcsO9eMY7aB7HUzRbTRO9dmZhCl3FdPtuvMe3K8aBA_wc2MmHRo8IkUIGmvUJGsAxKFClN_6oNW5fEvoeVKiL1krA-qjWbR_em-WksePgPoTsySW7QbKdi4f7cwuyK2_JZ2fQj9hDKlfJ2GzMXkKiWcfyCTr30yC6BviAFeRDD_Bpvg6znsrXr53Tq66hnwDwQ6QU7aHVu-bERblKZTYuvkSxsov6yRMEVWQoiuBITsQtIOcgSWK4Dy3BjSbqoIcKw3WG-s3wx1lTen19QbEu8vJC64e0iGeGDWT6vbtg" SIGNING_KEY = "yteNrMy6142WKwp8fKfrHkS5nlFpxtHgOXJh1ZPsOrV_gTcsO9eMY7aB7HUzRbTRO9dmZhCl3FdPtuvMe3K8aBA_wc2MmHRo8IkUIGmvUJGsAxKFClN_6oNW5fEvoeVKiL1krA-qjWbR_em-WksePgPoTsySW7QbKdi4f7cwuyK2_JZ2fQj9hDKlfJ2GzMXkKiWcfyCTr30yC6BviAFeRDD_Bpvg6znsrXr53Tq66hnwDwQ6QU7aHVu-bERblKZTYuvkSxsov6yRMEVWQoiuBITsQtIOcgSWK4Dy3BjSbqoIcKw3WG-s3wx1lTen19QbEu8vJC64e0iGeGDWT6vbtg"
TOKEN_VALIDITY_IN_DAYS = 1 TOKEN_VALIDITY_IN_DAYS = 1
# needed by connexion, do not remove!
def verifyToken(token, **kwargs) -> Dict: def verifyTokenRegular(token, required_scopes) -> Dict:
try: try:
user = TokenService.verify(token) user = TokenService.verify(token)
except ValueError as e: except ValueError as e:
print(f'ERROR DURING TOKEN VALIDATION: {str(e)}') print(f'ERROR DURING TOKEN VALIDATION: {str(e)}')
return None return None
if not user.role in UserService._valid_roles:
return None
TokenService.successful_authentication(user)
return {"sub": user.username}
def verifyTokenAdmin(token, required_scopes) -> Dict:
try:
user = TokenService.verify(token)
except ValueError as e:
print(f'ERROR DURING TOKEN VALIDATION: {str(e)}')
return None
if not user.role == "a":
print(f"Required Role:'a', Provided Role: '{user.role}'")
return None
TokenService.successful_authentication(user)
return {"sub": user.username} return {"sub": user.username}
class TokenService: class TokenService:
@staticmethod
def successful_authentication(user: User):
user.last_login = str(datetime.now())
UserService.update(user)
@staticmethod @staticmethod
def generate_token(user: User) -> str: def generate_token(user: User) -> str:
''' '''
......
...@@ -7,9 +7,16 @@ import bcrypt ...@@ -7,9 +7,16 @@ import bcrypt
class UserService: class UserService:
_repository = Repository() _repository = Repository()
# u ... regular user
# a ... admin user
_valid_roles = ["u", "a"]
@staticmethod @staticmethod
def get_by_username(username): def update(user: User):
UserService._repository.update(user)
@staticmethod
def get_by_username(username: str):
''' '''
fetches the given user from the database fetches the given user from the database
throws a ValueError if the user does not exist throws a ValueError if the user does not exist
...@@ -69,6 +76,10 @@ class UserService: ...@@ -69,6 +76,10 @@ class UserService:
password - Required : passphrase used to authenticate later, raw plaintext password - Required : passphrase used to authenticate later, raw plaintext
role - Optional : user type, one of the following: [u=regular user (default)] role - Optional : user type, one of the following: [u=regular user (default)]
''' '''
if role not in UserService._valid_roles:
raise ValueError(f'Role "{role}" is invalid. Must be one of {UserService._valid_roles}')
reference_users = UserService._repository.one_by_username(username) reference_users = UserService._repository.one_by_username(username)
if len(reference_users) > 0: if len(reference_users) > 0:
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment