diff --git a/docker-jans-configurator/scripts/bootstrap.py b/docker-jans-configurator/scripts/bootstrap.py index b6c13490a5b..9c7dc2df7fc 100644 --- a/docker-jans-configurator/scripts/bootstrap.py +++ b/docker-jans-configurator/scripts/bootstrap.py @@ -735,14 +735,6 @@ def radius_ctx(self): partial(encode_template, fn, self.ctx, basedir), ) - def scim_ctx(self): - self.set_config("scim_client_id", lambda: f"1201.{uuid4()}") - scim_client_pw = self.set_secret("scim_client_pw", get_random_chars) - self.set_secret( - "scim_client_encoded_pw", - partial(encode_text, scim_client_pw, self.get_secret("encoded_salt")) - ) - def couchbase_ctx(self): # TODO: move this to persistence-loader? self.set_config("couchbaseTrustStoreFn", "/etc/certs/couchbase.pkcs12") @@ -758,14 +750,6 @@ def jackrabbit_ctx(self): def sql_ctx(self): self.set_secret("sql_password", self.params["sql_pw"]) - def casa_ctx(self): - self.set_config("casa_client_id", lambda: f"1902.{uuid4()}") - casa_client_pw = self.set_secret("casa_client_pw", get_random_chars) - self.set_secret( - "casa_client_encoded_pw", - partial(encode_text, casa_client_pw, self.get_secret("encoded_salt")) - ) - def generate(self): opt_scopes = self.params["optional_scopes"] @@ -786,9 +770,6 @@ def generate(self): # self.oxshibboleth_ctx() # self.radius_ctx() - if "scim" in opt_scopes: - self.scim_ctx() - if "couchbase" in opt_scopes: self.couchbase_ctx() @@ -797,9 +778,6 @@ def generate(self): if "sql" in opt_scopes: self.sql_ctx() - if "casa" in opt_scopes: - self.casa_ctx() - self.admin_ui_ctx() self.jans_cli_ctx() diff --git a/docker-jans-configurator/scripts/parameter.py b/docker-jans-configurator/scripts/parameter.py index 901b428b71d..d503ed58d92 100644 --- a/docker-jans-configurator/scripts/parameter.py +++ b/docker-jans-configurator/scripts/parameter.py @@ -25,7 +25,6 @@ OPTIONAL_SCOPES = ( "ldap", - "scim", "couchbase", "redis", "sql", @@ -34,6 +33,7 @@ "fido2", "client-api", "casa", + "scim", ) diff --git a/docker-jans-persistence-loader/scripts/utils.py b/docker-jans-persistence-loader/scripts/utils.py index cb1f739559a..062e4a98ac5 100644 --- a/docker-jans-persistence-loader/scripts/utils.py +++ b/docker-jans-persistence-loader/scripts/utils.py @@ -127,8 +127,6 @@ def get_base_ctx(manager): "auth_openidScopeBackwardCompatibility": manager.config.get("auth_openidScopeBackwardCompatibility"), "admin_inum": manager.config.get("admin_inum"), - "scim_client_id": manager.config.get("scim_client_id"), - "scim_client_encoded_pw": manager.secret.get("scim_client_encoded_pw"), "jca_client_id": manager.config.get("jca_client_id"), "jca_client_encoded_pw": manager.secret.get("jca_client_encoded_pw"), } @@ -201,20 +199,6 @@ def merge_auth_ctx(ctx): return ctx -def merge_scim_ctx(ctx): - basedir = '/app/templates/jans-scim' - file_mappings = { - 'scim_dynamic_conf_base64': 'dynamic-conf.json', - 'scim_static_conf_base64': 'static-conf.json', - } - - for key, file_ in file_mappings.items(): - file_path = os.path.join(basedir, file_) - with open(file_path) as fp: - ctx[key] = generate_base64_contents(fp.read() % ctx) - return ctx - - def merge_config_api_ctx(ctx): def transform_url(url): auth_server_url = os.environ.get("CN_AUTH_SERVER_URL", "") @@ -303,7 +287,6 @@ def prepare_template_ctx(manager): ctx = merge_extension_ctx(ctx) ctx = merge_auth_ctx(ctx) ctx = merge_config_api_ctx(ctx) - ctx = merge_scim_ctx(ctx) ctx = merge_jans_cli_ctx(manager, ctx) return ctx @@ -346,13 +329,6 @@ def default_files(): "jans-cli/client.ldif", ] - if "scim" in optional_scopes: - files += [ - "jans-scim/configuration.ldif", - "jans-scim/scopes.ldif", - "jans-scim/clients.ldif", - ] - return files def user_files(): diff --git a/docker-jans-scim/.dockerignore b/docker-jans-scim/.dockerignore index 1203624c052..7b887895ff3 100644 --- a/docker-jans-scim/.dockerignore +++ b/docker-jans-scim/.dockerignore @@ -9,3 +9,4 @@ !scripts !LICENSE !requirements.txt +!templates diff --git a/docker-jans-scim/Dockerfile b/docker-jans-scim/Dockerfile index b030eb00479..28a1873463e 100644 --- a/docker-jans-scim/Dockerfile +++ b/docker-jans-scim/Dockerfile @@ -46,7 +46,7 @@ RUN wget -q https://maven.jans.io/maven/io/jans/jython-installer/${JYTHON_VERSIO # ==== ENV CN_VERSION=1.0.2-SNAPSHOT -ENV CN_BUILD_DATE='2022-08-03 12:38' +ENV CN_BUILD_DATE='2022-08-04 15:32' ENV CN_SOURCE_URL=https://jenkins.jans.io/maven/io/jans/jans-scim-server/${CN_VERSION}/jans-scim-server-${CN_VERSION}.war # Install SCIM @@ -60,6 +60,41 @@ RUN mkdir -p ${JETTY_BASE}/jans-scim/webapps \ && java -jar ${JETTY_HOME}/start.jar jetty.home=${JETTY_HOME} jetty.base=${JETTY_BASE}/jans-scim --add-to-start=server,deploy,resources,http,http-forwarded,jsp,websocket,cdi-decorate \ && rm -rf /tmp/jans-scim.war /tmp/WEB-INF +# ===================== +# jans-linux-setup sync +# ===================== + +ENV JANS_LINUX_SETUP_VERSION=2c1414dfc4edd2c14061d375622d56ed8542e08d +ARG JANS_SETUP_DIR=jans-linux-setup/jans_setup + +# note that as we're pulling from a monorepo (with multiple project in it) +# we are using partial-clone and sparse-checkout to get the jans-linux-setup code +RUN git clone --filter blob:none --no-checkout https://github.com/janssenproject/jans /tmp/jans \ + && cd /tmp/jans \ + && git sparse-checkout init --cone \ + && git checkout ${JANS_LINUX_SETUP_VERSION} \ + && git sparse-checkout set ${JANS_SETUP_DIR} + +RUN mkdir -p /app/static/rdbm \ + /app/schema \ + /app/templates/jans-scim + +# sync static files from linux-setup +RUN cd /tmp/jans \ + && cp ${JANS_SETUP_DIR}/static/rdbm/sql_data_types.json /app/static/rdbm/ \ + && cp ${JANS_SETUP_DIR}/static/rdbm/ldap_sql_data_type_mapping.json /app/static/rdbm/ \ + && cp ${JANS_SETUP_DIR}/static/rdbm/opendj_attributes_syntax.json /app/static/rdbm/ \ + && cp ${JANS_SETUP_DIR}/static/rdbm/sub_tables.json /app/static/rdbm/ \ + && cp ${JANS_SETUP_DIR}/schema/jans_schema.json /app/schema/ \ + && cp ${JANS_SETUP_DIR}/schema/custom_schema.json /app/schema/ \ + && cp ${JANS_SETUP_DIR}/schema/opendj_types.json /app/schema/ \ + && cp ${JANS_SETUP_DIR}/templates/jans-scim/configuration.ldif /app/templates/jans-scim/ \ + && cp ${JANS_SETUP_DIR}/templates/jans-scim/dynamic-conf.json /app/templates/jans-scim/ \ + && cp ${JANS_SETUP_DIR}/templates/jans-scim/static-conf.json /app/templates/jans-scim/ + +# cleanup +RUN rm -rf /tmp/jans + # ====== # Python # ====== @@ -187,6 +222,7 @@ RUN mkdir -p /etc/certs \ COPY jetty/jans-scim.xml ${JETTY_BASE}/jans-scim/webapps/ COPY jetty/log4j2.xml ${JETTY_BASE}/jans-scim/resources/ COPY conf/*.tmpl /app/templates/ +COPY templates /app/templates COPY scripts /app/scripts RUN chmod +x /app/scripts/entrypoint.sh diff --git a/docker-jans-scim/scripts/bootstrap.py b/docker-jans-scim/scripts/bootstrap.py index a9f6bffb3a7..49129c62308 100644 --- a/docker-jans-scim/scripts/bootstrap.py +++ b/docker-jans-scim/scripts/bootstrap.py @@ -1,7 +1,12 @@ +from __future__ import annotations + import json import os import re +import typing as _t +from functools import cached_property from string import Template +from uuid import uuid4 from jans.pycloudlib import get_manager from jans.pycloudlib.persistence import render_couchbase_properties @@ -13,12 +18,25 @@ from jans.pycloudlib.persistence import sync_ldap_truststore from jans.pycloudlib.persistence import render_sql_properties from jans.pycloudlib.persistence import render_spanner_properties +from jans.pycloudlib.persistence.couchbase import CouchbaseClient +from jans.pycloudlib.persistence.ldap import LdapClient +from jans.pycloudlib.persistence.spanner import SpannerClient +from jans.pycloudlib.persistence.sql import SqlClient from jans.pycloudlib.persistence.utils import PersistenceMapper from jans.pycloudlib.utils import cert_to_truststore +from jans.pycloudlib.utils import encode_text +from jans.pycloudlib.utils import generate_base64_contents +from jans.pycloudlib.utils import get_random_chars import logging.config from settings import LOGGING_CONFIG +if _t.TYPE_CHECKING: # pragma: no cover + # imported objects for function type hint, completion, etc. + # these won't be executed in runtime + from jans.pycloudlib.manager import Manager + + logging.config.dictConfig(LOGGING_CONFIG) logger = logging.getLogger("entrypoint") @@ -115,6 +133,9 @@ def main(): modify_webdefault_xml() configure_logging() + persistence_setup = PersistenceSetup(manager) + persistence_setup.import_ldif_files() + def configure_logging(): # default config @@ -185,5 +206,75 @@ def configure_logging(): f.write(tmpl.safe_substitute(config)) +class PersistenceSetup: + def __init__(self, manager: Manager) -> None: + self.manager = manager + + client_classes = { + "ldap": LdapClient, + "couchbase": CouchbaseClient, + "spanner": SpannerClient, + "sql": SqlClient, + } + + # determine persistence type + mapper = PersistenceMapper() + self.persistence_type = mapper.mapping["default"] + + # determine persistence client + client_cls = client_classes.get(self.persistence_type) + self.client = client_cls(manager) + + @cached_property + def ctx(self) -> dict[str, _t.Any]: + ctx = { + "hostname": self.manager.config.get("hostname"), + } + + # SCIM client + ctx["scim_client_id"] = self.manager.config.get("scim_client_id") + + if not ctx["scim_client_id"]: + ctx["scim_client_id"] = f"1201.{uuid4()}" + self.manager.config.set("scim_client_id", ctx["scim_client_id"]) + + ctx["scim_client_pw"] = self.manager.secret.get("scim_client_pw") + + if not ctx["scim_client_pw"]: + ctx["scim_client_pw"] = get_random_chars() + self.manager.secret.set("scim_client_pw", ctx["scim_client_pw"]) + + ctx["scim_client_encoded_pw"] = self.manager.secret.get("scim_client_encoded_pw") + + if not ctx["scim_client_encoded_pw"]: + ctx["scim_client_encoded_pw"] = encode_text( + ctx["scim_client_pw"], self.manager.secret.get("encoded_salt"), + ).decode() + self.manager.secret.set("scim_client_encoded_pw", ctx["scim_client_encoded_pw"]) + + # pre-populate scim_dynamic_conf_base64 + with open("/app/templates/jans-scim/dynamic-conf.json") as f: + ctx["scim_dynamic_conf_base64"] = generate_base64_contents(f.read() % ctx) + + # pre-populate scim_static_conf_base64 + with open("/app/templates/jans-scim/static-conf.json") as f: + ctx["scim_static_conf_base64"] = generate_base64_contents(f.read()) + + return ctx + + @cached_property + def ldif_files(self) -> list[str]: + files = [ + f"/app/templates/jans-scim/{file_}" + for file_ in ["configuration.ldif", "scopes.ldif", "clients.ldif"] + ] + return files + + def import_ldif_files(self) -> None: + for file_ in self.ldif_files: + logger.info(f"Importing {file_}") + self.client.create_from_ldif(file_, self.ctx) + + if __name__ == "__main__": main() diff --git a/docker-jans-persistence-loader/templates/jans-scim/clients.ldif b/docker-jans-scim/templates/jans-scim/clients.ldif similarity index 99% rename from docker-jans-persistence-loader/templates/jans-scim/clients.ldif rename to docker-jans-scim/templates/jans-scim/clients.ldif index f8220434a49..58ceb45abbd 100644 --- a/docker-jans-persistence-loader/templates/jans-scim/clients.ldif +++ b/docker-jans-scim/templates/jans-scim/clients.ldif @@ -20,4 +20,3 @@ jansSubjectTyp: pairwise jansTknEndpointAuthMethod: client_secret_basic objectClass: top objectClass: jansClnt - diff --git a/docker-jans-persistence-loader/templates/jans-scim/scopes.ldif b/docker-jans-scim/templates/jans-scim/scopes.ldif similarity index 100% rename from docker-jans-persistence-loader/templates/jans-scim/scopes.ldif rename to docker-jans-scim/templates/jans-scim/scopes.ldif