Skip to content

Commit

Permalink
Merge pull request #59 from BoostV/support-authentication
Browse files Browse the repository at this point in the history
Support authentication
  • Loading branch information
langdal authored Mar 14, 2023
2 parents 695aeb6 + 30d4792 commit 0305a8f
Show file tree
Hide file tree
Showing 7 changed files with 106 additions and 10 deletions.
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,21 @@ Two specific origins:

CORS_ORIGIN="(https://prod.brownie.projects.alexandra.dk|https://prod.cake.projects.alexandra.dk)" python -m optimizerapi.server

# Using authentication

API endpoints can be protected by either a static API key or using a Keycloak OIDC server.
The static API key is configured by the environment variable `AUTH_API_KEY`

Keycloak is configured using the following environement variables


|Name |Description |
|-------------------|-----------------------------------|
|AUTH_SERVER |Base url of your Keycloak server |
|AUTH_REALM_NAME |OAuth realm name |
|AUTH_CLIENT_ID |Client ID |
|AUTH_CLIENT_SECRET |Client secret |

# Adding or updating dependencies

When adding a new dependency, you should manually add it to `requirements.txt` and then run the following commands:
Expand Down
54 changes: 54 additions & 0 deletions optimizerapi/auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
"""Authenitcation module
This module will verify tokens provided bt a Keycloak OpenID server
"""
import os
from keycloak import KeycloakOpenID

AUTH_API_KEY = os.getenv("AUTH_API_KEY", 'none')
AUTH_SERVER = os.getenv("AUTH_SERVER", None)
AUTH_CLIENT_ID = os.getenv("AUTH_CLIENT_ID", None)
AUTH_CLIENT_SECRET = os.getenv("AUTH_CLIENT_SECRET", None)
AUTH_REALM_NAME = os.getenv("AUTH_REALM_NAME", None)

keycloak_openid = KeycloakOpenID(server_url=AUTH_SERVER,
realm_name=AUTH_REALM_NAME,
client_id=AUTH_CLIENT_ID,
client_secret_key=AUTH_CLIENT_SECRET
)


def token_info(access_token) -> dict:
"""Verify token with authentication server
Returns
-------
dict
a dictionary containing sub and scope
None in case of invalid token
"""
print(access_token)
if not AUTH_SERVER:
return {'scope': []}
token = access_token
token_data = keycloak_openid.introspect(token)
if 'active' in token_data and token_data['active']:
print('OK')
return token_data
print('NOT OK')
print(token_data)
return None


def apikey_handler(access_token) -> dict:
"""Verify API key based on environment variable
Returns
-------
dict
a dictionary containing sub and scope
None in case of invalid token
"""
if not AUTH_SERVER and AUTH_API_KEY == access_token:
return {'scope': []}
return None
16 changes: 16 additions & 0 deletions optimizerapi/openapi/specification.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ servers:
paths:
/optimizer:
post:
security:
- oauth2: []
- apikey: []
description: Run optimizer with the specified parameters
operationId: optimizerapi.optimizer.run
responses:
Expand Down Expand Up @@ -113,6 +116,19 @@ paths:
}

components:
securitySchemes:
apikey:
x-apikeyInfoFunc: optimizerapi.auth.apikey_handler
type: apiKey
name: apikey
in: query
oauth2:
type: oauth2
x-tokenInfoFunc: optimizerapi.auth.token_info
flows:
implicit:
authorizationUrl: https://keycloak.browniebee.projects.alexandra.dk/realms/brownie-bee-dev/protocol/openid-connect/auth
scopes: {}
schemas:
experiment:
title: An experiment definition
Expand Down
11 changes: 9 additions & 2 deletions optimizerapi/optimizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,8 @@ def process_result(result, optimizer, dimensions, cfg, extras, data, space):
experiment_suggestion_count = extras["experimentSuggestionCount"]

next_exp = optimizer.ask(n_points=experiment_suggestion_count)
if len(next_exp) > 0 and not any(isinstance(x, list) for x in next_exp): next_exp = [next_exp]
if len(next_exp) > 0 and not any(isinstance(x, list) for x in next_exp):
next_exp = [next_exp]
result_details["next"] = round_to_length_scales(next_exp, optimizer.space)

if len(data) >= cfg["initialPoints"]:
Expand All @@ -209,7 +210,7 @@ def process_result(result, optimizer, dimensions, cfg, extras, data, space):

plot_objective(model, dimensions=dimensions,
usepartialdependence=False,
show_confidence=True,
show_confidence=True,
pars=objective_pars)
add_plot(plots, f"objective_{idx}")

Expand All @@ -228,6 +229,12 @@ def process_result(result, optimizer, dimensions, cfg, extras, data, space):
add_version_info(result_details["extras"])

# print(str(response))
org_models = response["result"]['models']
for model in org_models:
# Flatten expected minimum entries
model['expected_minimum'] = [[
item for sublist in [x if isinstance(
x, list) else [x] for x in model['expected_minimum']] for item in sublist]]
return response


Expand Down
6 changes: 3 additions & 3 deletions optimizerapi/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,16 @@
import re
import connexion
from waitress import serve
from .securepickle import get_crypto
from flask_cors import CORS
from .securepickle import get_crypto

if __name__ == '__main__':
# Initialize crypto
get_crypto()
app = connexion.FlaskApp(
__name__, port=9090, specification_dir='./openapi/')
app.add_api('specification.yml', arguments={
'title': 'Hello World Example'})
app.add_api('specification.yml', strict_validation=True,
validate_responses=True)

DEVELOPMENT = "development"
flask_env = os.getenv("FLASK_ENV", DEVELOPMENT)
Expand Down
11 changes: 7 additions & 4 deletions requirements-freeze.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ chardet==4.0.0
click==7.1.2
clickclick==20.10.2
colorama==0.4.4
connexion==2.7.0
connexion==2.14.2
cryptography==3.4.7
cycler==0.10.0
deap==1.3.3
Expand All @@ -27,22 +27,25 @@ kiwisolver==1.3.1
MarkupSafe==1.1.1
matplotlib==3.5.3
numpy==1.23.3
ProcessOptimizer[browniebee]==0.7.7
openapi-spec-validator==0.2.9
packaging==20.9
Pillow==9.0.1
pluggy==0.13.1
ProcessOptimizer[browniebee]==0.7.7
py==1.10.0
pycparser==2.20
pyparsing==2.4.7
pyrsistent==0.17.3
pytest==6.2.2
pytest-watch==4.2.0
python-dateutil==2.8.1
python-keycloak==2.13.2
PyYAML==6.0
redis==4.0.2
requests==2.25.1
requests==2.28.2
requests-toolbelt==0.10.1
rq==1.10.0
rsa==4.9
scikit-learn==1.1.2
scipy==1.9.1
six==1.16.0
Expand All @@ -51,7 +54,7 @@ threadpoolctl==2.1.0
toml==0.10.2
tornado==6.2
typing_extensions==4.4.0
urllib3==1.26.5
urllib3==1.26.15
waitress==2.1.2
watchdog==2.0.2
Werkzeug==1.0.1
Expand Down
3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
connexion==2.7.0
connexion==2.14.2
connexion[swagger-ui]
python-keycloak==2.13.2
Flask==1.1.2
Flask-Cors==3.0.10
ProcessOptimizer[browniebee]==0.7.7
Expand Down

0 comments on commit 0305a8f

Please sign in to comment.