Skip to content

Commit

Permalink
feat: pre-populate role scope mapping dynamically (#1201)
Browse files Browse the repository at this point in the history
  • Loading branch information
iromli committed Apr 13, 2022
1 parent f241cdf commit 3ab6a11
Show file tree
Hide file tree
Showing 5 changed files with 150 additions and 4 deletions.
8 changes: 5 additions & 3 deletions docker-jans-persistence-loader/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@ RUN apk update \
# ======

COPY requirements.txt /app/requirements.txt
RUN pip3 install -U pip wheel \
RUN pip3 install --no-cache-dir -U pip wheel \
&& pip3 install --no-cache-dir --default-timeout=300 -r /app/requirements.txt

# =====================
# jans-linux-setup sync
# =====================

ENV JANS_LINUX_SETUP_VERSION=cfdc70e6a2ddcc9d2ba4e3cf07df19344d613944
ENV JANS_LINUX_SETUP_VERSION=f241cdfd569daf054c844978ee868f1879442dbf
ARG JANS_SETUP_DIR=jans-linux-setup/jans_setup

# note that as we're pulling from a monorepo (with multiple project in it)
Expand All @@ -46,7 +46,6 @@ RUN cd /tmp/jans \
&& cp ${JANS_SETUP_DIR}/schema/custom_schema.json /app/schema/custom_schema.json \
&& cp ${JANS_SETUP_DIR}/static/opendj/index.json /app/static/opendj/index.json


RUN mkdir -p /app/templates/jans-config-api

# partially sync templates from linux-setup
Expand All @@ -62,6 +61,9 @@ RUN cd /tmp/jans \
&& cp ${JANS_SETUP_DIR}/templates/jans-config-api/config.ldif /app/templates/jans-config-api/config.ldif \
&& cp -R ${JANS_SETUP_DIR}/templates/jans-cli /app/templates/jans-cli

# Download jans-config-api-swagger for role_scope_mapping
RUN wget -q https://github.com/JanssenProject/jans/raw/${JANS_LINUX_SETUP_VERSION}/jans-config-api/docs/jans-config-api-swagger.yaml -P /app/static

# TODO: casa should be moved from this image
ENV GLUU_CASA_VERSION=a8251496ff2ade9dd8101873b45f4c490ae9c64e
RUN wget -q https://github.com/GluuFederation/flex/raw/${GLUU_CASA_VERSION}/casa/extras/Casa.py -O /app/static/extension/person_authentication/Casa.py
Expand Down
1 change: 1 addition & 0 deletions docker-jans-persistence-loader/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@
grpcio==1.41.0
ldif==4.1.1
libcst<0.4
ruamel.yaml==0.16.10
git+https://github.com/JanssenProject/jans@abc89dc6fadae5627a68a97ab4f4f5ceb56af809#egg=jans-pycloudlib&subdirectory=jans-pycloudlib
93 changes: 92 additions & 1 deletion docker-jans-persistence-loader/scripts/upgrade.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from settings import LOGGING_CONFIG
from utils import doc_id_from_dn
from utils import id_from_dn
from utils import get_role_scope_mappings

logging.config.dictConfig(LOGGING_CONFIG)
logger = logging.getLogger("entrypoint")
Expand Down Expand Up @@ -347,7 +348,6 @@ def __init__(self, manager):
self.backend = backend_cls(manager)

def invoke(self):
# TODO: refactor all self.backend.update_ to this class method
logger.info("Running upgrade process (if required)")

self.update_people_entries()
Expand All @@ -362,6 +362,8 @@ def invoke(self):
self.update_auth_dynamic_config()
self.update_attributes_entries()
self.update_scripts_entries()
self.update_admin_ui_config()
self.update_api_dynamic_config()

def update_scripts_entries(self):
# default to ldap persistence
Expand Down Expand Up @@ -647,3 +649,92 @@ def _update_token_server_client():

_update_jca_client()
_update_token_server_client()

def update_admin_ui_config(self):
kwargs = {}
id_ = "ou=admin-ui,ou=configuration,o=jans"

if self.backend.type in ("sql", "spanner"):
kwargs = {"table_name": "jansAdminConfDyn"}
id_ = doc_id_from_dn(id_)
elif self.backend.type == "couchbase":
kwargs = {"bucket": os.environ.get("CN_COUCHBASE_BUCKET_PREFIX", "jans")}
id_ = id_from_dn(id_)

entry = self.backend.get_entry(id_, **kwargs)

if not entry:
return

# calculate new permissions for api-admin
role_mapping = get_role_scope_mappings()
api_admin_perms = []

for api_role in role_mapping["rolePermissionMapping"]:
if api_role["role"] == "api-admin":
api_admin_perms = api_role["permissions"]
break

# current permissions
current_role_mapping = json.loads(entry.attrs["jansConfDyn"])
should_update = False

for i, api_role in enumerate(current_role_mapping["rolePermissionMapping"]):
if api_role["role"] == "api-admin":
# compare permissions between the ones from persistence (current) and newer permissions
if sorted(api_role["permissions"]) != sorted(api_admin_perms):
current_role_mapping["rolePermissionMapping"][i]["permissions"] = api_admin_perms
should_update = True
break

if should_update:
entry.attrs["jansConfDyn"] = json.dumps(current_role_mapping)
entry.attrs["jansRevision"] += 1
self.backend.modify_entry(entry.id, entry.attrs, **kwargs)

def update_api_dynamic_config(self):
kwargs = {}
id_ = "ou=jans-config-api,ou=configuration,o=jans"

if self.backend.type in ("sql", "spanner"):
kwargs = {"table_name": "jansAppConf"}
id_ = doc_id_from_dn(id_)
elif self.backend.type == "couchbase":
kwargs = {"bucket": os.environ.get("CN_COUCHBASE_BUCKET_PREFIX", "jans")}
id_ = id_from_dn(id_)

entry = self.backend.get_entry(id_, **kwargs)

if not entry:
return

if self.backend.type != "couchbase":
entry.attrs["jansConfDyn"] = json.loads(entry.attrs["jansConfDyn"])

conf, should_update = _transform_api_dynamic_config(entry.attrs["jansConfDyn"])

if should_update:
if self.backend.type != "couchbase":
entry.attrs["jansConfDyn"] = json.dumps(conf)

entry.attrs["jansRevision"] += 1
self.backend.modify_entry(entry.id, entry.attrs, **kwargs)


def _transform_api_dynamic_config(conf):
should_update = False

if "userExclusionAttributes" not in conf:
conf["userExclusionAttributes"] = ["userPassword"]
should_update = True

if "userMandatoryAttributes" not in conf:
conf["userMandatoryAttributes"] = [
"mail",
"displayName",
"jansStatus",
"userPassword",
"givenName",
]
should_update = True
return conf, should_update
42 changes: 42 additions & 0 deletions docker-jans-persistence-loader/scripts/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@
import base64
import json
import os
from itertools import chain
from pathlib import Path
from urllib.parse import urlparse
from uuid import uuid4

import ruamel.yaml
from ldap3.utils import dn as dnutils

from jans.pycloudlib.utils import as_boolean
Expand Down Expand Up @@ -231,6 +233,9 @@ def merge_auth_ctx(ctx):
file_path = os.path.join(basedir, file_)
with open(file_path) as fp:
ctx[key] = generate_base64_contents(fp.read() % ctx)

# determine role scope mappings
ctx["role_scope_mappings"] = json.dumps(get_role_scope_mappings())
return ctx


Expand Down Expand Up @@ -486,3 +491,40 @@ def id_from_dn(dn):

# the actual key
return '_'.join(dns) or "_"


def get_config_api_swagger(path="/app/static/jans-config-api-swagger.yaml"):
with open(path) as f:
txt = f.read()
txt = txt.replace("\t", " ")
return ruamel.yaml.load(txt, Loader=ruamel.yaml.RoundTripLoader)


def get_config_api_scopes():
swagger = get_config_api_swagger()
scope_list = []

for _, methods in swagger["paths"].items():
for _, attrs in methods.items():
if "security" not in attrs:
continue
scope_list += [attr["oauth2"] for attr in attrs["security"]]

# make sure there's no duplication
return list(set(chain(*scope_list)))


def get_role_scope_mappings(path="/app/templates/jans-auth/role-scope-mappings.json"):
with open(path) as f:
role_mapping = json.loads(f.read())

scope_list = get_config_api_scopes()

for i, api_role in enumerate(role_mapping["rolePermissionMapping"]):
if api_role["role"] == "api-admin":
# merge scopes without duplication
role_mapping["rolePermissionMapping"][i]["permissions"] = list(set(
role_mapping["rolePermissionMapping"][i]["permissions"] + scope_list
))
break
return role_mapping
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,15 @@
"corsRequestDecorate": true,
"corsEnabled": true
}
],
"userExclusionAttributes": [
"userPassword"
],
"userMandatoryAttributes": [
"mail",
"displayName",
"jansStatus",
"userPassword",
"givenName"
]
}

0 comments on commit 3ab6a11

Please sign in to comment.