Skip to content

Commit

Permalink
Add possibility to use keycloak authentication token (#381)
Browse files Browse the repository at this point in the history
* mapsets requests works

* working but not in config

* zwischenstand

* first things in readme

* things from config

* working

* user management

* flake8

* black

* fix tests

* add unittests

* unittests black

* fix for integration tests

* Apply suggestions from code review

Co-authored-by: Carmen Tawalika <mmacata@users.noreply.github.com>

* review

* linting and swagger

* linting

* keycloak token url

* linting

Co-authored-by: anikaweinmann <aweinmann@mundialis.de>
Co-authored-by: Carmen Tawalika <mmacata@users.noreply.github.com>
  • Loading branch information
3 people authored Nov 30, 2022
1 parent bb94eaa commit d1e46ce
Show file tree
Hide file tree
Showing 11 changed files with 1,055 additions and 354 deletions.
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ passlib>=1.7.1
ply>=3.11
psutil>=5.7.0
python-json-logger
python-keycloak>=2.6.0
python-magic>=0.4.15
Sphinx>=1.7.1
redis>=2.10.6
Expand Down
44 changes: 37 additions & 7 deletions src/actinia_core/core/common/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,10 +106,13 @@
"""

from flask_httpauth import HTTPBasicAuth
from flask_httpauth import HTTPTokenAuth
from flask_cors import CORS
from flask import Flask
from flask_restful_swagger_2 import Api

from actinia_core.core.common.config import global_config, DEFAULT_CONFIG_PATH

from actinia_api import API_VERSION, URL_PREFIX

__license__ = "GPLv3"
Expand All @@ -125,6 +128,7 @@
flask_app.url_map.strict_slashes = False
CORS(flask_app)


flask_api = Api(
flask_app,
prefix=URL_PREFIX,
Expand All @@ -136,10 +140,36 @@
consumes=["application/gml+xml", "application/json"],
)

# Set the security definition in an unconventional way
flask_api._swagger_object["securityDefinitions"] = {
"basicAuth": {"type": "basic"}
}
flask_api._swagger_object["security"] = [{"basicAuth": []}]

auth = HTTPBasicAuth()
# authentication method
global_config.read(DEFAULT_CONFIG_PATH)
if global_config.KEYCLOAK_CONFIG_PATH:
auth = HTTPTokenAuth(scheme="Bearer")
flask_api._swagger_object["securityDefinitions"] = {
"bearerAuth": {
"type": "http",
"scheme": "bearer",
"bearerFormat": "JWT",
}
}
# https://swagger.io/docs/specification/authentication/oauth2/
flask_api._swagger_object["security"] = [
{
"OAuth2": {
"type": "oauth2",
# "authorizationUrl": "http://swagger.io/api/oauth/dialog",
"tokenUrl": f"{global_config.KEYCLOAK_URL}/realms/"
f"{global_config.KEYCLOAK_REALM}/protocol/openid-connect/"
"token",
"flow": "implicit",
"scopes": {},
}
}
]
else:
# Set the security definition in an unconventional way
flask_api._swagger_object["securityDefinitions"] = {
"basicAuth": {"type": "basic"}
}
flask_api._swagger_object["security"] = [{"basicAuth": []}]

auth = HTTPBasicAuth()
98 changes: 97 additions & 1 deletion src/actinia_core/core/common/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,10 @@
"""

import ast
import os
import configparser
import csv
import os
from json import load as json_load

__license__ = "GPLv3"
__author__ = "Sören Gebbert, Anika Weinmann"
Expand Down Expand Up @@ -320,6 +321,24 @@ def __init__(self):
# ENDPOINTS_CONFIG: configuration csv file for endpoints
self.ENDPOINTS_CONFIG = None

"""
KEYCLOAK: has only to be set if keycloak server is configured with
actinia client and actinia attributes for the users
"""
# Json file generated by keycloak with configuration,
# e.g. /etc/default/keycloak.json
self.KEYCLOAK_CONFIG_PATH = None
# e.g. /actinia-user/
self.KEYCLOAK_GROUP_PREFIX = None
# Prefix to distinguish parameters inside keycloak from parameters
# used by other applications, e.g. actinia
self.KEYCLOAK_ATTR_PREFIX = None
# KEYCLOAK parameter read from json configured in KEYCLOAK_CONFIG_PATH
self.KEYCLOAK_URL = None
self.KEYCLOAK_CLIENT_ID = None
self.KEYCLOAK_REALM = None
self.KEYCLOAK_CLIENT_SECRET_KEY = None

"""
REDIS
"""
Expand Down Expand Up @@ -470,6 +489,25 @@ def __str__(self):

return string

def read_keycloak_config(self, key_cloak_config_path=None):
"""Read keycloak configuration json"""
if key_cloak_config_path is None:
key_cloak_config_path = self.KEYCLOAK_CONFIG_PATH
if os.path.isfile(key_cloak_config_path):
with open(key_cloak_config_path) as f:
keycloak_cfg = json_load(f)
self.KEYCLOAK_URL = keycloak_cfg["auth-server-url"]
self.KEYCLOAK_REALM = keycloak_cfg["realm"]
self.KEYCLOAK_CLIENT_ID = keycloak_cfg["resource"]
self.KEYCLOAK_CLIENT_SECRET_KEY = keycloak_cfg["credentials"][
"secret"
]
else:
raise Exception(
"KEYCLOAK_CONFIG_PATH is not a valid keycloak configuration "
"for actinia"
)

def write(self, path=DEFAULT_CONFIG_PATH):
"""Save the configuration into a file
Expand Down Expand Up @@ -518,6 +556,43 @@ def write(self, path=DEFAULT_CONFIG_PATH):
config.set("API", "PLUGINS", str(self.PLUGINS))
config.set("API", "ENDPOINTS_CONFIG", str(self.ENDPOINTS_CONFIG))

config.add_section("KEYCLOAK")
config.set(
"KEYCLOAK",
"KEYCLOAK_CONFIG_PATH",
str(self.KEYCLOAK_CONFIG_PATH),
)
config.set(
"KEYCLOAK",
"KEYCLOAK_GROUP_PREFIX",
str(self.KEYCLOAK_GROUP_PREFIX),
)
config.set(
"KEYCLOAK",
"KEYCLOAK_ATTR_PREFIX",
str(self.KEYCLOAK_ATTR_PREFIX),
)
config.set(
"KEYCLOAK",
"KEYCLOAK_URL",
str(self.KEYCLOAK_URL),
)
config.set(
"KEYCLOAK",
"KEYCLOAK_CLIENT_ID",
str(self.KEYCLOAK_CLIENT_ID),
)
config.set(
"KEYCLOAK",
"KEYCLOAK_REALM",
str(self.KEYCLOAK_REALM),
)
config.set(
"KEYCLOAK",
"KEYCLOAK_CLIENT_SECRET_KEY",
str(self.KEYCLOAK_CLIENT_SECRET_KEY),
)

config.add_section("REDIS")
config.set("REDIS", "REDIS_SERVER_URL", self.REDIS_SERVER_URL)
config.set("REDIS", "REDIS_SERVER_PORT", str(self.REDIS_SERVER_PORT))
Expand Down Expand Up @@ -726,6 +801,27 @@ def read(self, path=DEFAULT_CONFIG_PATH):
"API", "ENDPOINTS_CONFIG"
)

if config.has_section("KEYCLOAK"):
if config.has_option("KEYCLOAK", "CONFIG_PATH"):
keycloak_cfg_path = config.get("KEYCLOAK", "CONFIG_PATH")
if os.path.isfile(keycloak_cfg_path):
self.KEYCLOAK_CONFIG_PATH = keycloak_cfg_path
self.read_keycloak_config()
else:
print(
"Keycloak is configured, but configfile is not "
"an existing file! Using Redis for user "
"management."
)
if config.has_option("KEYCLOAK", "GROUP_PREFIX"):
self.KEYCLOAK_GROUP_PREFIX = config.get(
"KEYCLOAK", "GROUP_PREFIX"
)
if config.has_option("KEYCLOAK", "ATTR_PREFIX"):
self.KEYCLOAK_ATTR_PREFIX = config.get(
"KEYCLOAK", "ATTR_PREFIX"
)

if config.has_section("REDIS"):
if config.has_option("REDIS", "REDIS_SERVER_URL"):
self.REDIS_SERVER_URL = config.get(
Expand Down
Loading

0 comments on commit d1e46ce

Please sign in to comment.