From 3e7179651cfee753606ad906c07c1e5214c66fd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Till=20Gl=C3=B6ggler?= Date: Wed, 25 Jun 2025 23:40:10 +0200 Subject: working on SSO SAML --- app/controllers/admin/saml.php | 33 +++++ composer.json | 3 +- composer.lock | 108 +++++++++++++- lib/classes/JsonApi/RouteMap.php | 7 + .../JsonApi/Routes/SAML/ConfigurationShow.php | 30 ++++ .../JsonApi/Routes/SAML/ConfigurationUpdate.php | 35 +++++ .../JsonApi/Routes/SAML/SetupInformation.php | 40 ++++++ lib/navigation/AdminNavigation.php | 2 + resources/vue/apps/SSOSAML.vue | 160 +++++++++++++++++++++ 9 files changed, 416 insertions(+), 2 deletions(-) create mode 100644 app/controllers/admin/saml.php create mode 100644 lib/classes/JsonApi/Routes/SAML/ConfigurationShow.php create mode 100644 lib/classes/JsonApi/Routes/SAML/ConfigurationUpdate.php create mode 100644 lib/classes/JsonApi/Routes/SAML/SetupInformation.php create mode 100644 resources/vue/apps/SSOSAML.vue diff --git a/app/controllers/admin/saml.php b/app/controllers/admin/saml.php new file mode 100644 index 0000000..aa65027 --- /dev/null +++ b/app/controllers/admin/saml.php @@ -0,0 +1,33 @@ +check('root'); + + Navigation::activateItem('/admin/config/saml'); + PageLayout::setTitle(_('SAML Verwaltung')); + } + + public function index_action(): void + { + $this->render_vue_app( + Studip\VueApp::create('SSOSAML') + ->withProps([ + ]) + ); + } +} diff --git a/composer.json b/composer.json index 278b2f6..e2bb7d6 100644 --- a/composer.json +++ b/composer.json @@ -137,7 +137,8 @@ "phpseclib/phpseclib2_compat": "1.0.6", "oat-sa/lib-lti1p3-deep-linking": "4.1.0", "lcobucci/jwt": "^4.3", - "guzzlehttp/guzzle": "^7.9.2" + "guzzlehttp/guzzle": "^7.9.2", + "onelogin/php-saml": "^4.3" }, "replace": { "symfony/polyfill-php54": "*", diff --git a/composer.lock b/composer.lock index 6377a6a..5d16bba 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "dc44dd49880b457f30e1faec87e74daf", + "content-hash": "19a2a8677273053cbce3c6e37db911ec", "packages": [ { "name": "algo26-matthias/idna-convert", @@ -3039,6 +3039,70 @@ "time": "2023-09-26T11:13:49+00:00" }, { + "name": "onelogin/php-saml", + "version": "4.3.0", + "source": { + "type": "git", + "url": "https://github.com/SAML-Toolkits/php-saml.git", + "reference": "bf5efce9f2df5d489d05e78c27003a0fc8bc50f0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/SAML-Toolkits/php-saml/zipball/bf5efce9f2df5d489d05e78c27003a0fc8bc50f0", + "reference": "bf5efce9f2df5d489d05e78c27003a0fc8bc50f0", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "robrichards/xmlseclibs": "^3.1" + }, + "require-dev": { + "pdepend/pdepend": "^2.8.0", + "php-coveralls/php-coveralls": "^2.0", + "phploc/phploc": "^4.0 || ^5.0 || ^6.0 || ^7.0", + "phpunit/phpunit": "^9.5", + "sebastian/phpcpd": "^4.0 || ^5.0 || ^6.0 ", + "squizlabs/php_codesniffer": "^3.5.8" + }, + "suggest": { + "ext-curl": "Install curl lib to be able to use the IdPMetadataParser for parsing remote XMLs", + "ext-dom": "Install xml lib", + "ext-openssl": "Install openssl lib in order to handle with x509 certs (require to support sign and encryption)", + "ext-zlib": "Install zlib" + }, + "type": "library", + "autoload": { + "psr-4": { + "OneLogin\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHP SAML Toolkit", + "homepage": "https://github.com/SAML-Toolkits/php-saml", + "keywords": [ + "Federation", + "SAML2", + "SSO", + "identity", + "saml" + ], + "support": { + "email": "sixto.martin.garcia@gmail.com", + "issues": "https://github.com/onelogin/SAML-Toolkits/issues", + "source": "https://github.com/onelogin/SAML-Toolkits/" + }, + "funding": [ + { + "url": "https://github.com/SAML-Toolkits", + "type": "github" + } + ], + "time": "2025-05-25T14:28:00+00:00" + }, + { "name": "opis/json-schema", "version": "2.4.1", "source": { @@ -4628,6 +4692,48 @@ "time": "2024-04-27T21:32:50+00:00" }, { + "name": "robrichards/xmlseclibs", + "version": "3.1.3", + "source": { + "type": "git", + "url": "https://github.com/robrichards/xmlseclibs.git", + "reference": "2bdfd742624d739dfadbd415f00181b4a77aaf07" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/robrichards/xmlseclibs/zipball/2bdfd742624d739dfadbd415f00181b4a77aaf07", + "reference": "2bdfd742624d739dfadbd415f00181b4a77aaf07", + "shasum": "" + }, + "require": { + "ext-openssl": "*", + "php": ">= 5.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "RobRichards\\XMLSecLibs\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "A PHP library for XML Security", + "homepage": "https://github.com/robrichards/xmlseclibs", + "keywords": [ + "security", + "signature", + "xml", + "xmldsig" + ], + "support": { + "issues": "https://github.com/robrichards/xmlseclibs/issues", + "source": "https://github.com/robrichards/xmlseclibs/tree/3.1.3" + }, + "time": "2024-11-20T21:13:56+00:00" + }, + { "name": "scssphp/scssphp", "version": "v2.0.1", "source": { diff --git a/lib/classes/JsonApi/RouteMap.php b/lib/classes/JsonApi/RouteMap.php index 86c6d92..530ae96 100644 --- a/lib/classes/JsonApi/RouteMap.php +++ b/lib/classes/JsonApi/RouteMap.php @@ -141,6 +141,7 @@ class RouteMap $this->addAuthenticatedStudyAreasRoutes($group); $this->addAuthenticatedUserFilterRoutes($group); $this->addAuthenticatedWikiRoutes($group); + $this->addAuthenticatedSAMLRoutes($group); } /** @@ -743,6 +744,12 @@ class RouteMap } + private function addAuthenticatedSAMLRoutes(RouteCollectorProxy $group): void + { + $group->get('/saml/configuration', Routes\SAML\ConfigurationShow::class); + $group->patch('/saml/configuration', Routes\SAML\ConfigurationUpdate::class); + } + private function addRelationship(RouteCollectorProxy $group, string $url, string $handler): void { $group->map(['GET', 'PATCH', 'POST', 'DELETE'], $url, $handler); diff --git a/lib/classes/JsonApi/Routes/SAML/ConfigurationShow.php b/lib/classes/JsonApi/Routes/SAML/ConfigurationShow.php new file mode 100644 index 0000000..e3b9ce3 --- /dev/null +++ b/lib/classes/JsonApi/Routes/SAML/ConfigurationShow.php @@ -0,0 +1,30 @@ +have_perm('root')) { + throw new AuthorizationFailedException(); + } + + $setupInformation = $this->container->get(SetupInformation::class); + $config = $setupInformation->getConfiguration(); + + return $this->jsonResponse($response, [ + 'data' => [ + 'type' => 'saml-configuration', + 'id' => '1', + 'attributes' => $config, + ], + ]); + } +} \ No newline at end of file diff --git a/lib/classes/JsonApi/Routes/SAML/ConfigurationUpdate.php b/lib/classes/JsonApi/Routes/SAML/ConfigurationUpdate.php new file mode 100644 index 0000000..7845d2d --- /dev/null +++ b/lib/classes/JsonApi/Routes/SAML/ConfigurationUpdate.php @@ -0,0 +1,35 @@ +have_perm('root')) { + throw new AuthorizationFailedException(); + } + + $data = $this->getJsonApiData($request); + $attributes = $data['attributes'] ?? []; + + $setupInformation = $this->container->get(SetupInformation::class); + $setupInformation->updateConfiguration($attributes); + + $updatedConfig = $setupInformation->getConfiguration(); + + return $this->jsonResponse($response, [ + 'data' => [ + 'type' => 'saml-configuration', + 'id' => '1', + 'attributes' => $updatedConfig, + ], + ]); + } +} \ No newline at end of file diff --git a/lib/classes/JsonApi/Routes/SAML/SetupInformation.php b/lib/classes/JsonApi/Routes/SAML/SetupInformation.php new file mode 100644 index 0000000..6dc8f44 --- /dev/null +++ b/lib/classes/JsonApi/Routes/SAML/SetupInformation.php @@ -0,0 +1,40 @@ +{self::CONFIG_KEY} ?? '{}', true); + + return [ + 'entityId' => $samlConfig['entityId'] ?? '', + 'assertionConsumerService' => $samlConfig['assertionConsumerService'] ?? '', + 'singleLogoutService' => $samlConfig['singleLogoutService'] ?? '', + 'nameIdFormat' => $samlConfig['nameIdFormat'] ?? '', + 'x509cert' => $samlConfig['x509cert'] ?? '', + 'privateKey' => $samlConfig['privateKey'] ?? '', + 'security' => [ + 'authnRequestsSigned' => $samlConfig['security']['authnRequestsSigned'] ?? false, + 'wantMessagesSigned' => $samlConfig['security']['wantMessagesSigned'] ?? false, + 'wantAssertionsSigned' => $samlConfig['security']['wantAssertionsSigned'] ?? false, + ], + ]; + } + + public function updateConfiguration(array $config): void + { + $existingConfig = $this->getConfiguration(); + $updatedConfig = array_merge($existingConfig, $config); + + $configInstance = Config::get(); + $configInstance->{self::CONFIG_KEY} = json_encode($updatedConfig); + $configInstance->store(self::CONFIG_KEY, $configInstance->{self::CONFIG_KEY}); + } +} \ No newline at end of file diff --git a/lib/navigation/AdminNavigation.php b/lib/navigation/AdminNavigation.php index fe31b72..1557670 100644 --- a/lib/navigation/AdminNavigation.php +++ b/lib/navigation/AdminNavigation.php @@ -206,6 +206,8 @@ class AdminNavigation extends Navigation $navigation->addSubNavigation('admissionrules', new Navigation(_('Anmelderegeln'), 'dispatch.php/admission/ruleadministration')); $navigation->addSubNavigation('oauth2', new Navigation(_('OAuth2'), 'dispatch.php/admin/oauth2/index')); + $navigation->addSubNavigation('saml', new Navigation(_('SAML'), 'dispatch.php/admin/saml/index')); + $navigation->addSubNavigation('globalsearch', new Navigation(_('Globale Suche'), 'dispatch.php/globalsearch/settings')); $navigation->addSubNavigation('cache', new Navigation(_('Cache'), 'dispatch.php/admin/cache/settings')); diff --git a/resources/vue/apps/SSOSAML.vue b/resources/vue/apps/SSOSAML.vue new file mode 100644 index 0000000..00fb46a --- /dev/null +++ b/resources/vue/apps/SSOSAML.vue @@ -0,0 +1,160 @@ + + + \ No newline at end of file -- cgit v1.0