Skip to content

Commit

Permalink
Merge cec6658 into e5c6a4e
Browse files Browse the repository at this point in the history
  • Loading branch information
dwendland authored Jun 1, 2023
2 parents e5c6a4e + cec6658 commit cea16b1
Show file tree
Hide file tree
Showing 11 changed files with 419 additions and 32 deletions.
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# activation-service
Service allowing to activate services and create access rights during the acquisition step via:
* creating policies in an iSHARE authorisation registry
* creating entries at a trusted issuer list (TBD)
* creating entries at a trusted issuer list

It is based on Python Flask using gunicorn. The service requires to store data in an SQL database.
It ca be configured to use external databases (e.g., MySQL, PostgreSQL) or SQLite.
It can be configured to use external databases (e.g., MySQL, PostgreSQL) or SQLite.


## Preparation
Expand Down Expand Up @@ -87,6 +87,7 @@ and [Artifacthub](https://artifacthub.io/packages/helm/i4trust/activation-servic
* `/health`: Get health output of web server
* `/token`: Forwards a token request to the `/token` endpoint at the locally configured authorisation registry (iSHARE flow)
* `/createpolicy`: Activates the service by creating a policy at the locally configured authorisation registry (iSHARE flow)
* `/issuer`: Activates the service by creating an entry at a trusted issuers list. If no Authorization header is provided, it will return a redirect to the verifier for obtaining a JWT access token (Trusted-Issuers-List flow)


## Extend
Expand Down
2 changes: 2 additions & 0 deletions api/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from .errors import errors
from .token import token_endpoint
from .createpolicy import createpolicy_endpoint
from .issuer import issuer_endpoint

app = Flask(__name__)

Expand All @@ -12,6 +13,7 @@
# Register routes
app.register_blueprint(createpolicy_endpoint)
app.register_blueprint(token_endpoint)
app.register_blueprint(issuer_endpoint)

# Default 500 error handler
@app.errorhandler(500)
Expand Down
11 changes: 11 additions & 0 deletions api/exceptions/as_exception.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
class ActivationServiceException(Exception):

def __init__(self, message, internal_msg, status_code):
super().__init__(message)

self.status_code = status_code
self.internal_msg = internal_msg
self.public_msg = message

if not internal_msg:
self.internal_msg = message
12 changes: 3 additions & 9 deletions api/exceptions/create_policy_exception.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
class CreatePolicyException(Exception):
from api.exceptions.as_exception import ActivationServiceException

def __init__(self, message, internal_msg, status_code):
super().__init__(message)

self.status_code = status_code
self.internal_msg = internal_msg
self.public_msg = message
class CreatePolicyException(ActivationServiceException):

if not internal_msg:
self.internal_msg = message
pass
13 changes: 3 additions & 10 deletions api/exceptions/database_exception.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,5 @@
class DatabaseException(Exception):
from api.exceptions.as_exception import ActivationServiceException

def __init__(self, message, internal_msg, status_code):
super().__init__(message)

self.status_code = status_code
self.internal_msg = internal_msg
self.public_msg = message

if not internal_msg:
self.internal_msg = message
class DatabaseException(ActivationServiceException):

pass
5 changes: 5 additions & 0 deletions api/exceptions/issuer_exception.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from api.exceptions.as_exception import ActivationServiceException

class IssuerException(ActivationServiceException):

pass
12 changes: 3 additions & 9 deletions api/exceptions/token_exception.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
class TokenException(Exception):
from api.exceptions.as_exception import ActivationServiceException

def __init__(self, message, internal_msg, status_code):
super().__init__(message)

self.status_code = status_code
self.internal_msg = internal_msg
self.public_msg = message
class TokenException(ActivationServiceException):

if not internal_msg:
self.internal_msg = message
pass
114 changes: 114 additions & 0 deletions api/issuer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
from flask import Blueprint, Response, current_app, abort, request, redirect
from api.util.issuer_handler import extract_access_token, get_samedevice_redirect_url
from api.util.issuer_handler import decode_token_with_jwk, forward_til_request
from api.util.issuer_handler import check_create_role, check_update_role, check_delete_role
import time

from api.exceptions.issuer_exception import IssuerException
from api.exceptions.database_exception import DatabaseException

# Blueprint
issuer_endpoint = Blueprint("issuer_endpoint", __name__)

# POST /issuer
@issuer_endpoint.route("/issuer", methods = ['POST','PUT','DELETE'])
def index():
current_app.logger.debug('Received request at /issuer endpoint (HTTP Method: {})'.format(request.method))

# Load config
conf = current_app.config['as']

# Check for access token JWT in request header
request_token = None
try:
request_token = extract_access_token(request)
except IssuerException as iex:
current_app.logger.debug("Error when extracting access token JWT from authorization header: {}. Returning status {}.".format(iex.internal_msg, iex.status_code))
abort(iex.status_code, iex.public_msg)

if not request_token:
current_app.logger.debug("... no access token JWT in 'Authorization' header, returning 302 redirect")

redirect_url = None
try:
redirect_url = get_samedevice_redirect_url(conf)
except IssuerException as rie:
current_app.logger.error("Error when generating redirect URL: {}. Returning status {}.".format(rie.internal_msg, rie.status_code))
abort(rie.status_code, rie.public_msg)

# Return redirect
current_app.logger.debug("... returning redirect to: {}".format(redirect_url))
return redirect(redirect_url, 302)

# Received JWT in Authorization header
current_app.logger.debug("...received access token JWT in incoming request: {}".format(request_token))

# Validate JWT with verifier JWKS
payload = None
try:
current_app.logger.debug("... validating and decoding JWT using JWKS ...")
payload = decode_token_with_jwk(request_token, conf)
except IssuerException as die:
# TODO: catch expired token exception, to send redirect as well
current_app.logger.debug("Error when validating/decoding: {}. Returning status {}.".format(die.internal_msg, die.status_code))
abort(die.status_code, die.public_msg)
current_app.logger.debug("... decoded token payload: {}".format(payload))

# Check TIL access depending on HTTP method
if request.method == 'POST':
# POST: Create issuer flow

# Check for 'Create Issuer' role
try:
current_app.logger.debug("... checking for necessary role to create issuer")
if not check_create_role(payload, conf):
current_app.logger.debug("Required role was not found in JWT credential. Returning status 401.")
current_app.logger.debug("... required role '{}' for target DID '{}'".format(conf['issuer']['roles']['createRole'], conf['issuer']['providerId']))
abort(401, "Issued roles do not allow to create an issuer")
except IssuerException as cie:
current_app.logger.debug("Error when checking for required role: {}. Returning status {}.".format(cie.internal_msg, cie.status_code))
abort(cie.status_code, cie.public_msg)

elif request.method == 'PUT':
# PUT: Update issuer flow

# Check for 'Update Issuer' role
try:
current_app.logger.debug("... checking for necessary role to update issuer")
if not check_update_role(payload, conf):
current_app.logger.debug("Required role was not found in JWT credential. Returning status 401.")
current_app.logger.debug("... required role '{}' for target DID '{}'".format(conf['issuer']['roles']['updateRole'], conf['issuer']['providerId']))
abort(401, "Issued roles do not allow to update an issuer")
except IssuerException as pie:
current_app.logger.debug("Error when checking for required role: {}. Returning status {}.".format(pie.internal_msg, pie.status_code))
abort(pie.status_code, pie.public_msg)

elif request.method == 'DELETE':
# DELETE: Delete issuer flow

# Check for 'Delete Issuer' role
try:
current_app.logger.debug("... checking for necessary role to delete issuer")
if not check_delete_role(payload, conf):
current_app.logger.debug("Required role was not found in JWT credential. Returning status 401.")
current_app.logger.debug("... required role '{}' for target DID '{}'".format(conf['issuer']['roles']['deleteRole'], conf['issuer']['providerId']))
abort(401, "Issued roles do not allow to delete an issuer")
except IssuerException as delie:
current_app.logger.debug("Error when checking for required role: {}. Returning status {}.".format(delie.internal_msg, delie.status_code))
abort(delie.status_code, delie.public_msg)

else:
# should not happen
abort(500, "Invalid HTTP method")

# Forward request to TIL
current_app.logger.debug("... access granted!")
try:
current_app.logger.debug("... forwarding request to TIL")
res = forward_til_request(request, conf)
current_app.logger.debug("... received TIL response (code: {}): {}".format(res.status_code, res.content))
current_app.logger.debug("... returning response to sender!")
return res.content, res.status_code, res.headers.items()
except IssuerException as fie:
current_app.logger.error("Error when forwarding request to TIL: {}. Returning status {}.".format(fie.internal_msg, fie.status_code))
abort(fie.status_code, fie.public_msg)
Loading

0 comments on commit cea16b1

Please sign in to comment.