From 61309956df3e720b8ae792288012164383ea6a29 Mon Sep 17 00:00:00 2001 From: Cody Finegan <44886+codyfinegan@users.noreply.github.com> Date: Thu, 16 Feb 2023 10:42:50 +1300 Subject: [PATCH] Changing the hard-coded instance to a looser implementation (#4) Now configured by SP_URLS env var, and it must be a full URL to a metadata XML file. Added docekr-compose.yml to help with maintaining this image Added a internal refresh link to allow the metadata to be refreshed. --- .env.dist | 2 + .gitignore | 1 + Dockerfile | 29 +++++- README.md | 30 +++--- config/config.php | 18 ++-- docker-compose.yml | 26 +++++ metadata/saml20-sp-remote.php | 32 ++++-- .../totara/themes/simple/default/_header.twig | 60 ------------ .../simple/default/frontpage_federation.twig | 98 +++++++++++++++++++ .../themes/simple/default/show_metadata.twig | 18 ++++ 10 files changed, 218 insertions(+), 96 deletions(-) create mode 100644 .env.dist create mode 100644 docker-compose.yml create mode 100644 modules/totara/themes/simple/default/frontpage_federation.twig create mode 100644 modules/totara/themes/simple/default/show_metadata.twig diff --git a/.env.dist b/.env.dist new file mode 100644 index 0000000..9fe0821 --- /dev/null +++ b/.env.dist @@ -0,0 +1,2 @@ +# SP_URLS should be a full URL to a metadata file +SP_URLS=http://example.com/my/metadata.php diff --git a/.gitignore b/.gitignore index e8abb80..ee6b928 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ server.pem server.crt simplesamlphp-*/ simplesamlphp-*.tar.gz +.env diff --git a/Dockerfile b/Dockerfile index 5356a3c..66d54d7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,7 +18,7 @@ RUN mkdir /app && \ rm -rf metadata -FROM php:8.0-apache-buster +FROM php:8.0-apache-buster as dev COPY --from=node_builder /app/samlphp/ /var/www/html/ @@ -35,7 +35,6 @@ RUN sed -ri -e 's!/var/www/html!/var/www/html/www/!g' /etc/apache2/sites-availab sed -ri -e 's!:80>!:${LISTEN_PORT}>!g' /etc/apache2/sites-available/*.conf && \ sed -ri -e "s!usenewui!:usenewui2!g" /var/www/html/www/index.php # Hack to prevent auto-redirect to login page - # Generate the internal certificate RUN cd /var/www/html/cert && \ openssl req -subj /C=NZ/ST=Wellington/L=Wellington/O=Totara/OU=Development/CN=server \ @@ -43,6 +42,30 @@ RUN cd /var/www/html/cert && \ chown www-data server.* && \ chown www-data /var/www/html/cache +# Expose PHP errors to the CLI +RUN cp "$PHP_INI_DIR/php.ini-development" "$PHP_INI_DIR/php.ini" && \ + echo "log_errors = On\nerror_log = /dev/stderr" > "$PHP_INI_DIR/conf.d/error.ini" + +# Override the core module so we can alter the theme +RUN sed -ri -e 's!core:show_metadata.tpl.php!show_metadata.tpl.php!g' /var/www/html/modules/core/www/show_metadata.php && \ + sed -ri -e 's!core:frontpage_federation.tpl.php!frontpage_federation.tpl.php!g' /var/www/html/modules/core/www/frontpage_federation.php + +RUN mkdir -p /var/www/html/modules/totara/locales/en/LC_MESSAGES && \ + cp /var/www/html/modules/core/locales/en/LC_MESSAGES/core.po /var/www/html/modules/totara/locales/en/LC_MESSAGES/totara.po + +FROM php:8.0-apache-buster as prod + +ENV SIMPLESAMLPHP_CONFIG_DIR /var/www/config/ +ENV LISTEN_PORT 8089 + +# Default expose port +EXPOSE 8089 + +RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" + +COPY --from=dev /var/www/html /var/www/html +COPY --from=dev /etc/apache2/ /etc/apache2/ + COPY config/ /var/www/config/ COPY metadata/ /var/www/html/metadata/ -COPY modules/totara/ /var/www/html/modules/totara/ +COPY modules/totara/themes/ /var/www/html/modules/totara/themes/ diff --git a/README.md b/README.md index 2b46368..9ada59f 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ There are two environmental variables that need to be set in the Docker image fo | Variable | Description | |---------------|---------------------------------------------------------------------------------------------------------------------------------------------------------| -| `TOTARA_URL` | The URLs of the Totara instance to mark as trusted. Eg: `http://my-totara-instance.local,http://another-site`. Accept multiple if seperated by a comma. | +| `SP_URLS` | The URLs of the Service Provider instances to register. Eg: `http://my-instance.local/path/to/metadata.php,http://another-site/path/to/metadata.php`. Accept multiple if seperated by a comma. **Must be the full URL to the metadata**. | | `LISTEN_PORT` | The port used to access the service. Defaults to `8089`. | | `SITE_TITLE` | Override the default site title, used when running multiple to tell them apart. | @@ -22,23 +22,23 @@ There are two environmental variables that need to be set in the Docker image fo docker pull totara/simple-saml-test:latest # Start the service -docker run --rm -p 8089:8089 -e LISTEN_PORT=8089 -e TOTARA_URL={YOUR_TOTARA_URL} -it totara/simple-saml-test:latest +docker run --rm -p 8089:8089 -e LISTEN_PORT=8089 -e SP_URLS=http://{YOUR_SP_INSTANCE}/path/to/metadata.php -it totara/simple-saml-test:latest ``` -Set `{YOUR_TOTARA_URL}` to the domain of your Totara instance. +Set `{YOUR_SP_INSTANCE}` to the domain of your service provider instance (typically a Totara instance but technically could work with any SAML site). -If your Totara instance domain isn't publicly resolvable (such as it's a test environment) you will need to teach the +If your domain isn't publicly resolvable (such as it's a test environment) you will need to teach the Domain/IP to this docker image. ```shell -# Totara instance is running directly on the host machine -docker run --add-host={YOUR_TOTARA_URL}:host-gateway ... -it totara/simple-saml-test:latest +# Instance is running directly on the host machine +docker run --add-host={YOUR_SP_INSTANCE}:host-gateway ... -it totara/simple-saml-test:latest -# Totara instance is somewhere else, replace the domain & IP -docker run --add-host={YOUR_TOTARA_URL}:{IP_OF_SITE} ... -it totara/simple-saml-test:latest +# Instance is somewhere else, replace the domain & IP +docker run --add-host={YOUR_SP_INSTANCE}:{IP_OF_SITE} ... -it totara/simple-saml-test:latest ``` -Once started, you can access the service via `http://localhost:{LISTEN_PORT}`. +Once started, you can access the service via `http://localhost:{LISTEN_PORT}` (defaults to 8089). ### Using Totara Docker Dev @@ -59,7 +59,7 @@ services: ports: - "8089:8089" environment: - - TOTARA_URL=http://${SAML_TOTARA_URL:-totara74} + - SP_URLS=${SAML_TOTARA_URLS} - LISTEN_PORT=8089 - SITE_TITLE="Testing" ``` @@ -67,7 +67,7 @@ services: Edit your `.env` file and add the following line. ```dotenv -SAML_TOTARA_URL=totara74 +SAML_TOTARA_URLS=http://totara74/path/to/sp/metadata.php ``` In it place the name of the Totara instance you're using to connect. @@ -80,9 +80,11 @@ Try and access http://saml2:8089 and confirm you see the test environment. *Important*: The URL that Totara and the URL that you access the site on via your browser must be the same. +The path to the metadata file depends on what SAML plugin you are using which is why it's not specified here. + ## Developing This Image * Fork this repo, create a new branch and make the change. -* Test your image by building it directly with `docker build -t test_saml2 .` -* Try running the image with the commands above, swapping out with the test image name. -* If it's all good, submit a pull request for the change. +* Test using the built-in docker image with docker-compose, you can run `docker-compose up --build dev` to run the dev version with the config/metadata/modules folders volumed in (real time changes). +* Once everything is all good, test with the prod version `docker-compose up --build prod`. +* If it is all good, submit a pull request for the change. diff --git a/config/config.php b/config/config.php index 66a8c20..e7d8df9 100644 --- a/config/config.php +++ b/config/config.php @@ -5,7 +5,7 @@ */ $env = getenv('SAML_TRUSTED'); -$trusted = $env ? [$env] : []; +$trusted = $env ? [$env] : null; $title = getenv('SITE_HEADER') ?: 'SimpleSAML Test'; $config = [ @@ -203,7 +203,7 @@ * https://idp.example.org/ssp/, then * http://idp.example.org/ssp/module.php/core/postredirect.php must be accessible. */ - 'enable.http_post' => false, + 'enable.http_post' => true, /* * Set the allowed clock skew between encrypting/decrypting assertions @@ -255,7 +255,7 @@ 'debug' => [ 'saml' => true, 'backtraces' => true, - 'validatexml' => false, + 'validatexml' => true, ], /* @@ -266,7 +266,7 @@ * the error to 'technicalcontact_email'. */ 'showerrors' => true, - 'errorreporting' => true, + 'errorreporting' => false, /* * Custom error show function called from SimpleSAML\Error\Error::show. @@ -295,8 +295,8 @@ * Options: [syslog,file,errorlog,stderr] * */ - 'logging.level' => SimpleSAML\Logger::NOTICE, - 'logging.handler' => 'syslog', + 'logging.level' => SimpleSAML\Logger::ERR, + 'logging.handler' => 'stderr', /* * Specify the format of the logs. Its use varies depending on the log handler used (for instance, you cannot @@ -796,9 +796,7 @@ * Languages available, RTL languages, and what language is the default. */ 'language.available' => [ - 'en', 'no', 'nn', 'se', 'da', 'de', 'sv', 'fi', 'es', 'ca', 'fr', 'it', 'nl', 'lb', - 'cs', 'sl', 'lt', 'hr', 'hu', 'pl', 'pt', 'pt-br', 'tr', 'ja', 'zh', 'zh-tw', 'ru', - 'et', 'he', 'id', 'sr', 'lv', 'ro', 'eu', 'el', 'af', 'zu', 'xh', 'st', + 'en' ], 'language.rtl' => ['ar', 'dv', 'fa', 'ur', 'he'], 'language.default' => 'en', @@ -901,7 +899,7 @@ * change. If you don't want to check the source templates for every request, * set it to false. */ - 'template.auto_reload' => false, + 'template.auto_reload' => true, /* * Set this option to true to indicate that your installation of SimpleSAMLphp diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..85f0cd5 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,26 @@ +version: "3.7" + +services: + dev: + build: + context: . + target: dev + ports: + - "8099:8099" + environment: + - LISTEN_PORT=8099 + - SITE_HEADER=Dev SAML Image + - SP_URLS=$SP_URLS + volumes: + - ./metadata/:/var/www/html/metadata/ + - ./config/:/var/www/config/ + - ./modules/totara/themes/:/var/www/html/modules/totara/themes/ + + prod: + build: . + ports: + - "8098:8098" + environment: + - LISTEN_PORT=8098 + - SITE_HEADER=Prod SAML Image + - SP_URLS=$SP_URLS diff --git a/metadata/saml20-sp-remote.php b/metadata/saml20-sp-remote.php index be50ee5..4c87882 100644 --- a/metadata/saml20-sp-remote.php +++ b/metadata/saml20-sp-remote.php @@ -9,18 +9,19 @@ /* * Example SimpleSAMLphp SAML 2.0 SP */ -$totara_instance = getenv('TOTARA_URL'); +$sp_instance = getenv('SP_URLS'); -if (empty($totara_instance)) { - die('No TOTARA_URL environment variable was defined.'); +if (empty($sp_instance)) { + echo 'No SP_URLS environment variable were defined.'; + return; } $instances = []; -if (str_contains($totara_instance, ',')) { - $instances = explode(',', $totara_instance); +if (str_contains($sp_instance, ',')) { + $instances = explode(',', $sp_instance); array_walk($instances, fn($instance) => trim($instance)); } else { - $instances[] = $totara_instance; + $instances[] = $sp_instance; } $config = \SimpleSAML\Configuration::getInstance(); @@ -57,11 +58,11 @@ return file_get_contents($filename); }; -$reset = isset($_GET['refresh_metadata']) && $_GET['refresh_metadata'] === 'y'; +$refresh = isset($_GET['refresh_metadata']) && (in_array($_GET['refresh_metadata'], ['y', 'yes', 't', 'true', 1, '1'], true)); $metadata = []; -foreach ($instances as $totara_instance) { - $xml = $load_metadata($totara_instance . '/auth/saml2/sp/metadata.php', $reset); +foreach ($instances as $sp_instance) { + $xml = $load_metadata($sp_instance, $refresh); // No data, silently ignore it. if (!$xml) { @@ -75,6 +76,13 @@ foreach ($entities as &$entity) { $data = $entity->getMetadata20SP(); + // Hack to get en-US behaving + foreach ($data as $key => $value) { + if (is_array($value) && isset($value['en-US'])) { + $data[$key]['en'] = $value['en-US']; + } + } + if (isset($data['entityDescriptor'])) { unset($data['entityDescriptor']); } @@ -89,3 +97,9 @@ $metadata = array_merge($metadata, $output['saml20-sp-remote']); } + +if ($refresh) { + $new_url = str_replace('refresh_metadata=y', 'refreshed=done', $_SERVER['REQUEST_URI']); + header('Location: ' . $new_url); + exit; +} diff --git a/modules/totara/themes/simple/default/_header.twig b/modules/totara/themes/simple/default/_header.twig index 30e844c..cff2ec2 100644 --- a/modules/totara/themes/simple/default/_header.twig +++ b/modules/totara/themes/simple/default/_header.twig @@ -25,66 +25,6 @@

Admin Backend

- {% if not hideLanguageBar %} -
- - - -
-
- -
- {% endif %} -
-
-
- {% if not hideLanguageBar %} -
- {% for name, value in queryParams %} - {% if value %} - - {% else %} - - {% endif %} - {% endfor %} - - -
- {% endif %} -
-
{# language bar #} -
{# show-for-large #} {# wrap #} {# header #} diff --git a/modules/totara/themes/simple/default/frontpage_federation.twig b/modules/totara/themes/simple/default/frontpage_federation.twig new file mode 100644 index 0000000..e92221c --- /dev/null +++ b/modules/totara/themes/simple/default/frontpage_federation.twig @@ -0,0 +1,98 @@ +{% set pagetitle = '{core:frontpage:page_title}'|trans %} +{% set frontpage_section = 'federation' %} +{% extends "base.twig" %} + +{% block content %} + {% include "@core/_frontpage_menu.twig" %} + + {% if metaentries.hosted is iterable and metaentries.hosted is not empty %} +

{% trans %}Hosted entities{% endtrans %}

+
+ {% for key, set in metaentries.hosted %} + {% set metadataset = attribute(set, 'metadata-set') %} +
{{ mtype[metadataset]|trans }}
+
+

Entity ID: {{ set.entityid }} + {% if set.deprecated is defined and set.deprecated %} +
Deprecated + {% endif %} + {% if set.entityid != attribute(set, 'metadata-index') %} +
Index: {{ attribute(set, 'metadata-index') }} + {% endif %} + {% if set.name_translated is defined %} +
{{ set.name_translated }} + {% endif %} + {% if set.descr_translated is defined %} +
{{ set.descr_translated }} + {% endif %} +
[ {{'Show IdP Metadata'|trans }} ] +

+
+ {% endfor %} +
+ {% endif %} + + +

{% trans %}Trusted entities (Your Remote Instances){% endtrans %}

+ + {% if isadmin %} + +

Refresh Remote Metadata

+ + {% if metaentries.remote is iterable and metaentries.remote is not empty %} + {% for key, set in metaentries.remote %} +
+ {{ mtype[key]|trans }} (Trusted) + +
+ {% endfor %} + {% else %} +

No metadata could be found, check your SP_URLS environmental variable or try refreshing.

+ {% endif %} + + {% else %} +

You must log in as admin to see the remote service providers.

+ {% endif %} + +

{{ 'Tools'|trans }}

+ + + {% if isadmin %} +
+

Look up metadata for entity: + + + +

+
+ + {% endif %} +{% endblock %} diff --git a/modules/totara/themes/simple/default/show_metadata.twig b/modules/totara/themes/simple/default/show_metadata.twig new file mode 100644 index 0000000..b747563 --- /dev/null +++ b/modules/totara/themes/simple/default/show_metadata.twig @@ -0,0 +1,18 @@ +{% set pagetile = 'SimpleSAMLphp Show Metadata'|trans %} +{% extends 'base.twig' %} +{% block content %} +
+
+

{{ 'Metadata'|trans }}

+ +
+
+
$metadata[{{ entityid }}] => {{ metadata|escape }}
+
+
+
+ {{ 'Back'|trans }} +
+{% endblock content %}