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:
description: This is the documentation for the RESTful API gateway.
version: "1.0.0"
# Import security definitions from seperate file
securityDefinitions:
JwtBearerAuth:
type: apiKey
name: Authorization
in: header
x-apikeyInfoFunc: "services.token_service.verifyToken"
$ref: './security.yml#securityDefinitions'
consumes:
- "application/json"
......@@ -18,12 +15,14 @@ produces:
basePath: "/api"
# Paths supported by the server application
paths:
/secret:
get:
security:
- JwtBearerAuth: []
- JwtRegular: []
operationId: "rest.user.secret"
tags:
- "User"
......@@ -74,6 +73,8 @@ paths:
description: "Wrong credentials"
/users/username/{username}:
delete:
security:
- JwtAdmin: []
operationId: "rest.user.delete"
tags:
- "User"
......@@ -92,6 +93,8 @@ paths:
description: "User does not exist"
/users:
get:
security:
- JwtAdmin: []
operationId: "rest.user.all"
tags:
- "User"
......@@ -126,9 +129,9 @@ paths:
type: string
example: "secure_passw0rd"
responses:
200:
'200':
description: "User was added to the database"
400:
'400':
description: "User already exists"
/debug:
......@@ -145,7 +148,7 @@ paths:
schema:
type: object
responses:
200:
'200':
description: "Successful echo of request data"
/trace:
......@@ -163,9 +166,9 @@ paths:
schema:
$ref: "#/definitions/BlockchainTrace"
responses:
201:
'201':
description: "Successfully added"
400:
'400':
description: "Invalid input"
definitions:
......
......@@ -23,6 +23,10 @@ class Repository(MongoRepositoryBase):
def add(self, user : User):
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):
collection = self._database[self._user_collection]
collection.delete_many({"username": username})
......
# add modules folder to interpreter path
import sys
import os
import prance
from pathlib import Path
from typing import Dict, Any
modules_path = '../../modules/'
if os.path.exists(modules_path):
sys.path.insert(1, modules_path)
......@@ -14,9 +18,18 @@ LOGGER = logging.getLogger(__name__)
#################################
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
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'])
def api_root():
......
......@@ -38,6 +38,8 @@ def authenticate():
try:
user = UserService.get_by_credentials(username, data["password"])
TokenService.successful_authentication(user)
return {"token": TokenService.generate_token(user)}
except ValueError as e:
# return 400 if the user does not exist or the password is wrong
......@@ -62,8 +64,8 @@ def add():
'''
data = request.json
if not "role" in data:
data["role"] = "u"
# overwrite possibly existing role with "regular user"
data["role"] = "u"
username = data["username"]
......
......@@ -9,17 +9,40 @@ from typing import Dict
SIGNING_KEY = "yteNrMy6142WKwp8fKfrHkS5nlFpxtHgOXJh1ZPsOrV_gTcsO9eMY7aB7HUzRbTRO9dmZhCl3FdPtuvMe3K8aBA_wc2MmHRo8IkUIGmvUJGsAxKFClN_6oNW5fEvoeVKiL1krA-qjWbR_em-WksePgPoTsySW7QbKdi4f7cwuyK2_JZ2fQj9hDKlfJ2GzMXkKiWcfyCTr30yC6BviAFeRDD_Bpvg6znsrXr53Tq66hnwDwQ6QU7aHVu-bERblKZTYuvkSxsov6yRMEVWQoiuBITsQtIOcgSWK4Dy3BjSbqoIcKw3WG-s3wx1lTen19QbEu8vJC64e0iGeGDWT6vbtg"
TOKEN_VALIDITY_IN_DAYS = 1
# needed by connexion, do not remove!
def verifyToken(token, **kwargs) -> Dict:
def verifyTokenRegular(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 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}
class TokenService:
@staticmethod
def successful_authentication(user: User):
user.last_login = str(datetime.now())
UserService.update(user)
@staticmethod
def generate_token(user: User) -> str:
'''
......
......@@ -7,9 +7,16 @@ import bcrypt
class UserService:
_repository = Repository()
# u ... regular user
# a ... admin user
_valid_roles = ["u", "a"]
@staticmethod
def update(user: User):
UserService._repository.update(user)
@staticmethod
def get_by_username(username):
def get_by_username(username: str):
'''
fetches the given user from the database
throws a ValueError if the user does not exist
......@@ -69,6 +76,10 @@ class UserService:
password - Required : passphrase used to authenticate later, raw plaintext
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)
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