From 7fe25b2f68520ecdd53e9f520d6a6092c8bdcbbb Mon Sep 17 00:00:00 2001 From: Maxime Besson Date: Fri, 8 Nov 2019 18:31:50 +0100 Subject: [PATCH 01/31] API skeleton --- .../lib/Lemonldap/NG/Manager.pm | 2 +- .../lib/Lemonldap/NG/Manager/Api.pm | 37 +++++++++++++++++++ .../lib/Lemonldap/NG/Manager/Api/2F.pm | 11 ++++++ .../lib/Lemonldap/NG/Manager/Api/Providers.pm | 6 +++ lemonldap-ng-manager/t/04-hello-api.t | 19 ++++++++++ lemonldap-ng-manager/t/lemonldap-ng.ini | 2 +- 6 files changed, 75 insertions(+), 2 deletions(-) create mode 100644 lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api.pm create mode 100644 lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/2F.pm create mode 100644 lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers.pm create mode 100644 lemonldap-ng-manager/t/04-hello-api.t diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager.pm index 950f0f7ff5..173f6e4319 100644 --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager.pm @@ -52,7 +52,7 @@ sub init { return 0; } - $self->{enabledModules} ||= "conf, sessions, notifications, 2ndFA"; + $self->{enabledModules} ||= "conf, sessions, notifications, 2ndFA, api"; my @links; my @enabledModules = map { push @links, $_; "Lemonldap::NG::Manager::" . ucfirst($_) } diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api.pm new file mode 100644 index 0000000000..7355115897 --- /dev/null +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api.pm @@ -0,0 +1,37 @@ +# This module implements all the methods that responds to '/api/*' requests +package Lemonldap::NG::Manager::Api; + +use 5.10.0; +use utf8; +use Mouse; + +extends 'Lemonldap::NG::Common::Conf::RESTServer'; + +use Lemonldap::NG::Manager::Api::2F; +use Lemonldap::NG::Manager::Api::Providers; + +our $VERSION = '2.0.7'; + +############################# +# I. INITIALIZATION METHODS # +############################# + +use constant defaultRoute => 'api.html'; + +sub addRoutes { + my ( $self, $conf ) = @_; + + # HTML template + $self->addRoute( 'api.html', undef, ['GET'] ) + + ->addRoute( + api => { + v1 => { + hello => "helloworld", + }, + }, + ['GET'] + ); + } + +1; diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/2F.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/2F.pm new file mode 100644 index 0000000000..ad3cc0db69 --- /dev/null +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/2F.pm @@ -0,0 +1,11 @@ +package Lemonldap::NG::Manager::Api::2F; +our $VERSION = '2.0.7'; + +package Lemonldap::NG::Manager::Api; + +sub helloworld { + my ( $self, $req, @others ) = @_; + return [ 200, [], ["Hello world"]]; +} + +1; diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers.pm new file mode 100644 index 0000000000..1cf5544cb2 --- /dev/null +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers.pm @@ -0,0 +1,6 @@ +package Lemonldap::NG::Manager::Api::Providers; +our $VERSION = '2.0.7'; + +package Lemonldap::NG::Manager::Api; + +1; diff --git a/lemonldap-ng-manager/t/04-hello-api.t b/lemonldap-ng-manager/t/04-hello-api.t new file mode 100644 index 0000000000..884aa1b55c --- /dev/null +++ b/lemonldap-ng-manager/t/04-hello-api.t @@ -0,0 +1,19 @@ +# Test RSA key generation + +use Test::More; +use strict; +use JSON; +use IO::String; +require 't/test-lib.pm'; + +my $res; +ok( + $res = &client->_get('/api/v1/hello', '') + , + "Request succeed" +); +ok( $res->[0] == 200, "Result code is 200" ); + +diag Dumper($res); + +done_testing( ); diff --git a/lemonldap-ng-manager/t/lemonldap-ng.ini b/lemonldap-ng-manager/t/lemonldap-ng.ini index 03eb553683..84ddc1406a 100644 --- a/lemonldap-ng-manager/t/lemonldap-ng.ini +++ b/lemonldap-ng-manager/t/lemonldap-ng.ini @@ -29,7 +29,7 @@ protection = manager staticPrefix = app/ languages = fr, en, vi, ar templateDir = site/templates/ -enabledModules = conf, sessions, notifications, 2ndFA, viewer +enabledModules = conf, sessions, notifications, 2ndFA, viewer, api viewerHiddenKeys = samlIDPMetaDataNodes samlSPMetaDataNodes portalDisplayLogout captcha_login_enabled viewerAllowBrowser = $env->{REMOTE_ADDR} eq '127.0.0.1' viewerAllowDiff = 1 -- GitLab From 0c11dedc44cef949112052ac90267e85f4cd58ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20OUDOT?= Date: Wed, 4 Dec 2019 16:08:12 +0100 Subject: [PATCH 02/31] Add a specific vhost for API (#2033, #2034) --- Makefile | 1 + _example/etc/manager-apache2.4.conf | 73 +++++++++++++++++++++ _example/etc/manager-apache2.X.conf | 80 +++++++++++++++++++++++ _example/etc/manager-apache2.conf | 74 +++++++++++++++++++++ lemonldap-ng-manager/MANIFEST | 5 ++ lemonldap-ng-manager/site/htdocs/api.fcgi | 12 ++++ 6 files changed, 245 insertions(+) create mode 100755 lemonldap-ng-manager/site/htdocs/api.fcgi diff --git a/Makefile b/Makefile index 691d6f73cd..35a68e6d94 100644 --- a/Makefile +++ b/Makefile @@ -428,6 +428,7 @@ prepare_test_server: ETCDEFAULTDIR=`pwd`/e2e-tests/conf/def #@cp -f e2e-tests/index.* e2e-tests/conf/ @cp -f $(SRCMANAGERDIR)/site/htdocs/manager* e2e-tests/conf/manager + @cp -f $(SRCMANAGERDIR)/site/htdocs/api* e2e-tests/conf/manager @cp -f $(SRCPORTALDIR)/site/htdocs/index* e2e-tests/conf/portal @cp e2e-tests/persistent/5efe8af397fc3577e05b483aca964f1b e2e-tests/conf/persistents @cp e2e-tests/saml-sp.xml e2e-tests/conf/site/saml-sp.xml diff --git a/_example/etc/manager-apache2.4.conf b/_example/etc/manager-apache2.4.conf index 62841e33dc..bccef04fe6 100644 --- a/_example/etc/manager-apache2.4.conf +++ b/_example/etc/manager-apache2.4.conf @@ -99,3 +99,76 @@ # Uncomment this if site if you use SSL only #Header set Strict-Transport-Security "max-age=15768000" + +# API virtual host (manager.__DNSDOMAIN__) + + ServerName api.__DNSDOMAIN__ + LogLevel notice + # See above to set LLNG user id in Apache logs + #CustomLog __APACHELOGDIR__/manager.log llng + #ErrorLog __APACHELOGDIR__/lm_err.log + + # Uncomment this if you are running behind a reverse proxy and want + # LemonLDAP::NG to see the real IP address of the end user + # Adjust the settings to match the IP address of your reverse proxy + # and the header containing the original IP address + # + #RemoteIPHeader X-Forwarded-For + #RemoteIPInternalProxy 127.0.0.1 + + + # FASTCGI CONFIGURATION + # --------------------- + + # 1) URI management + RewriteEngine on + + # For performances, you can delete the previous RewriteRule line after + # puttings html files: simply put the HTML results of differents modules + # (configuration, sessions, notifications) as manager.html, sessions.html, + # notifications.html and uncomment the 2 following lines: + # DirectoryIndex manager.html + # RewriteCond "%{REQUEST_URI}" "!\.html(?:/.*)?$" + + # REST URLs + RewriteCond "%{REQUEST_URI}" "!^/(?:static|doc|lib|javascript|favicon).*" + RewriteRule "^/(.+)$" "/api.fcgi/$1" [PT] + + # 2) FastCGI engine + + # You can choose any FastCGI system. Here is an example using mod_fcgid + # mod_fcgid configuration + FcgidMaxRequestLen 2000000 + + SetHandler fcgid-script + Options +ExecCGI + header unset Lm-Remote-User + + + # If you want to use mod_fastcgi, replace lines below by: + #FastCgiServer __MANAGERSITEDIR__/manager.fcgi + + # GLOBAL CONFIGURATION + # -------------------- + + DocumentRoot __MANAGERSITEDIR__ + + + Require all granted + + + AddOutputFilterByType DEFLATE text/html text/plain text/xml text/javascript text/css + SetOutputFilter DEFLATE + BrowserMatch ^Mozilla/4 gzip-only-text/html + BrowserMatch ^Mozilla/4\.0[678] no-gzip + BrowserMatch \bMSIE !no-gzip !gzip-only-text/html + SetEnvIfNoCase Request_URI \.(?:gif|jpe?g|png)$ no-gzip dont-vary + + + Header append Vary User-Agent env=!dont-vary + + + + # Uncomment this if site if you use SSL only + #Header set Strict-Transport-Security "max-age=15768000" + diff --git a/_example/etc/manager-apache2.X.conf b/_example/etc/manager-apache2.X.conf index 614c311f7c..e5cd662297 100644 --- a/_example/etc/manager-apache2.X.conf +++ b/_example/etc/manager-apache2.X.conf @@ -118,3 +118,83 @@ # Uncomment this if site if you use SSL only #Header set Strict-Transport-Security "max-age=15768000" + +# API virtual host (manager.__DNSDOMAIN__) + + ServerName api.__DNSDOMAIN__ + LogLevel notice + # See above to set LLNG user id in Apache logs + #CustomLog __APACHELOGDIR__/manager.log llng + #ErrorLog __APACHELOGDIR__/lm_err.log + + # Uncomment this if you are running behind a reverse proxy and want + # LemonLDAP::NG to see the real IP address of the end user + # Adjust the settings to match the IP address of your reverse proxy + # and the header containing the original IP address + # + #RemoteIPHeader X-Forwarded-For + #RemoteIPInternalProxy 127.0.0.1 + + + # FASTCGI CONFIGURATION + # --------------------- + + # 1) URI management + RewriteEngine on + + # For performances, you can delete the previous RewriteRule line after + # puttings html files: simply put the HTML results of differents modules + # (configuration, sessions, notifications) as manager.html, sessions.html, + # notifications.html and uncomment the 2 following lines: + # DirectoryIndex manager.html + # RewriteCond "%{REQUEST_URI}" "!\.html(?:/.*)?$" + + # REST URLs + RewriteCond "%{REQUEST_URI}" "!^/(?:static|doc|lib|javascript|favicon).*" + RewriteRule "^/(.+)$" "/api.fcgi/$1" [PT] + + # 2) FastCGI engine + + # You can choose any FastCGI system. Here is an example using mod_fcgid + # mod_fcgid configuration + FcgidMaxRequestLen 2000000 + + SetHandler fcgid-script + Options +ExecCGI + header unset Lm-Remote-User + + + # If you want to use mod_fastcgi, replace lines below by: + #FastCgiServer __MANAGERSITEDIR__/manager.fcgi + + # GLOBAL CONFIGURATION + # -------------------- + + DocumentRoot __MANAGERSITEDIR__ + + + = 2.3> + Require all granted + + + Order Deny,Allow + Allow from all + + Options +FollowSymLinks + + + AddOutputFilterByType DEFLATE text/html text/plain text/xml text/javascript text/css + SetOutputFilter DEFLATE + BrowserMatch ^Mozilla/4 gzip-only-text/html + BrowserMatch ^Mozilla/4\.0[678] no-gzip + BrowserMatch \bMSIE !no-gzip !gzip-only-text/html + SetEnvIfNoCase Request_URI \.(?:gif|jpe?g|png)$ no-gzip dont-vary + + + Header append Vary User-Agent env=!dont-vary + + + + # Uncomment this if site if you use SSL only + #Header set Strict-Transport-Security "max-age=15768000" + diff --git a/_example/etc/manager-apache2.conf b/_example/etc/manager-apache2.conf index 540557cbcc..00cb4aa812 100644 --- a/_example/etc/manager-apache2.conf +++ b/_example/etc/manager-apache2.conf @@ -102,3 +102,77 @@ # Uncomment this if site if you use SSL only #Header set Strict-Transport-Security "max-age=15768000" + +# API virtual host (api.__DNSDOMAIN__) + + ServerName api.__DNSDOMAIN__ + LogLevel notice + # See above to set LLNG user id in Apache logs + #CustomLog __APACHELOGDIR__/manager.log llng + #ErrorLog __APACHELOGDIR__/lm_err.log + + # Uncomment this if you are running behind a reverse proxy and want + # LemonLDAP::NG to see the real IP address of the end user + # Adjust the settings to match the IP address of your reverse proxy + # and the header containing the original IP address + # + #RemoteIPHeader X-Forwarded-For + #RemoteIPInternalProxy 127.0.0.1 + + + # FASTCGI CONFIGURATION + # --------------------- + + # 1) URI management + RewriteEngine on + + # For performances, you can delete the previous RewriteRule line after + # puttings html files: simply put the HTML results of differents modules + # (configuration, sessions, notifications) as manager.html, sessions.html, + # notifications.html and uncomment the 2 following lines: + # DirectoryIndex manager.html + # RewriteCond "%{REQUEST_URI}" "!\.html(?:/.*)?$" + + # REST URLs + RewriteCond "%{REQUEST_URI}" "!^/(?:static|doc|lib|javascript|favicon).*" + RewriteRule "^/(.+)$" "/api.fcgi/$1" [PT] + + # 2) FastCGI engine + + # You can choose any FastCGI system. Here is an example using mod_fcgid + # mod_fcgid configuration + FcgidMaxRequestLen 2000000 + + SetHandler fcgid-script + Options +ExecCGI + header unset Lm-Remote-User + + + # If you want to use mod_fastcgi, replace lines below by: + #FastCgiServer __MANAGERSITEDIR__/manager.fcgi + + # GLOBAL CONFIGURATION + # -------------------- + + DocumentRoot __MANAGERSITEDIR__ + + + Order Deny,Allow + Allow from all + + + AddOutputFilterByType DEFLATE text/html text/plain text/xml text/javascript text/css + SetOutputFilter DEFLATE + BrowserMatch ^Mozilla/4 gzip-only-text/html + BrowserMatch ^Mozilla/4\.0[678] no-gzip + BrowserMatch \bMSIE !no-gzip !gzip-only-text/html + SetEnvIfNoCase Request_URI \.(?:gif|jpe?g|png)$ no-gzip dont-vary + + + Header append Vary User-Agent env=!dont-vary + + + + # Uncomment this if site if you use SSL only + #Header set Strict-Transport-Security "max-age=15768000" + diff --git a/lemonldap-ng-manager/MANIFEST b/lemonldap-ng-manager/MANIFEST index cb250062d7..903ca16b81 100644 --- a/lemonldap-ng-manager/MANIFEST +++ b/lemonldap-ng-manager/MANIFEST @@ -7,6 +7,9 @@ eg/manager.psgi KINEMATIC.md lib/Lemonldap/NG/Manager.pm lib/Lemonldap/NG/Manager/2ndFA.pm +lib/Lemonldap/NG/Manager/Api.pm +lib/Lemonldap/NG/Manager/Api/2F.pm +lib/Lemonldap/NG/Manager/Api/Providers.pm lib/Lemonldap/NG/Manager/Attributes.pm lib/Lemonldap/NG/Manager/Build.pm lib/Lemonldap/NG/Manager/Build/Attributes.pm @@ -40,6 +43,7 @@ site/coffee/notifications.coffee site/coffee/sessions.coffee site/coffee/viewDiff.coffee site/coffee/viewer.coffee +site/htdocs/api.fcgi site/htdocs/manager.fcgi site/htdocs/manager.psgi site/htdocs/static/bwr/angular-animate/angular-animate.js @@ -213,6 +217,7 @@ site/templates/viewDiff.tpl site/templates/viewer.tpl t/02-HTML-template.t t/03-HTML-forms.t +t/04-hello-api.t t/05-rest-api.t t/06-rest-api.t t/07-utf8.t diff --git a/lemonldap-ng-manager/site/htdocs/api.fcgi b/lemonldap-ng-manager/site/htdocs/api.fcgi new file mode 100755 index 0000000000..67d37b8d86 --- /dev/null +++ b/lemonldap-ng-manager/site/htdocs/api.fcgi @@ -0,0 +1,12 @@ +#!/usr/bin/perl + +use Plack::Handler::FCGI; +use Lemonldap::NG::Manager; + +# Roll your own +my $server = Plack::Handler::FCGI->new(); +$server->run( + Lemonldap::NG::Manager->run( + { enabledModules => "api", protection => "none" } + ) +); -- GitLab From 7a61750eb3ea41bf9d598b48689b9bc4570b121a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20OUDOT?= Date: Wed, 4 Dec 2019 17:50:41 +0100 Subject: [PATCH 03/31] Start OIDC RP API (#2034) --- .../lib/Lemonldap/NG/Manager/Api.pm | 23 +++++++---- .../lib/Lemonldap/NG/Manager/Api/Providers.pm | 39 +++++++++++++++++++ 2 files changed, 55 insertions(+), 7 deletions(-) diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api.pm index 7355115897..3e32e805ee 100644 --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api.pm @@ -25,13 +25,22 @@ sub addRoutes { $self->addRoute( 'api.html', undef, ['GET'] ) ->addRoute( - api => { - v1 => { - hello => "helloworld", - }, - }, - ['GET'] + api => { + v1 => { + hello => "helloworld", + }, + }, + ['GET'] + ) + + ->addRoute( + providers => { + oidc => { + rp => { ':confKey' => 'getOidcRpByConfKey' }, + }, + }, + ['GET'] ); - } +} 1; diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers.pm index 1cf5544cb2..70de4eb726 100644 --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers.pm @@ -3,4 +3,43 @@ our $VERSION = '2.0.7'; package Lemonldap::NG::Manager::Api; +sub getOidcRpByConfKey { + my ( $self, $req ) = @_; + + my $confKey = $req->params('confKey') + or return $self->sendError( $req, 'confKey is missing', 400 ); + + $self->logger->debug("[API] OIDC RP $confKey configuration requested"); + + # Get latest configuration + my $conf = $self->_confAcc->getConf; + + # To save configuration + #$self->_confAcc->saveConf( $conf ) ; + + # Dump object + #use Data::Dumper; print STDERR Dumper($self); + + # Check if confKey is defined + if ( !defined $conf->{oidcRPMetaDataOptions}->{$confKey} ) { + return $self->sendError( $req, 'Service Provider not found', 404 ); + } + + # Get Client ID + my $clientId = $conf->{oidcRPMetaDataOptions}->{$confKey} + ->{oidcRPMetaDataOptionsClientID}; + + # Get exported vars + my $exportedVars = $conf->{oidcRPMetaDataExportedVars}->{$confKey}; + + return $self->sendJSONresponse( + $req, + { + confKey => $confKey, + clientId => $clientId, + exportedVars => $exportedVars + } + ); +} + 1; -- GitLab From cd93caef72fb48b918aee43cea1a951627730bcc Mon Sep 17 00:00:00 2001 From: Soisik Froger Date: Thu, 5 Dec 2019 09:29:35 +0000 Subject: [PATCH 04/31] Manager API - get OIDC rp (options and extraclaims) #2034 --- lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api.pm | 8 +++++--- .../lib/Lemonldap/NG/Manager/Api/Providers.pm | 10 +++++++++- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api.pm index 3e32e805ee..aed1d2c096 100644 --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api.pm @@ -34,9 +34,11 @@ sub addRoutes { ) ->addRoute( - providers => { - oidc => { - rp => { ':confKey' => 'getOidcRpByConfKey' }, + v1 => { + providers => { + oidc => { + rp => { ':confKey' => 'getOidcRpByConfKey' }, + }, }, }, ['GET'] diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers.pm index 70de4eb726..ff48cbf0e6 100644 --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers.pm @@ -32,12 +32,20 @@ sub getOidcRpByConfKey { # Get exported vars my $exportedVars = $conf->{oidcRPMetaDataExportedVars}->{$confKey}; + # Get extra claim + my $extraClaim = $conf->{oidcRPMetaDataOptionsExtraClaims}->{$confKey}; + + # Get options + my $options = $conf->{oidcRPMetaDataOptions}->{$confKey}; + return $self->sendJSONresponse( $req, { confKey => $confKey, clientId => $clientId, - exportedVars => $exportedVars + exportedVars => $exportedVars, + extraClaim => $extraClaim, + options => $options } ); } -- GitLab From e5a45f505b6376efa360c16c543f00c101dfb36c Mon Sep 17 00:00:00 2001 From: Soisik Froger Date: Thu, 5 Dec 2019 11:40:03 +0000 Subject: [PATCH 05/31] Manager API - get SAML sp #2034 --- .../lib/Lemonldap/NG/Manager/Api.pm | 3 + .../lib/Lemonldap/NG/Manager/Api/Providers.pm | 70 ++++++++++++++++++- 2 files changed, 72 insertions(+), 1 deletion(-) diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api.pm index aed1d2c096..de58ec9408 100644 --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api.pm @@ -39,6 +39,9 @@ sub addRoutes { oidc => { rp => { ':confKey' => 'getOidcRpByConfKey' }, }, + saml => { + sp => { ':confKey' => 'getSamlSpByConfKey' }, + }, }, }, ['GET'] diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers.pm index ff48cbf0e6..97476c2671 100644 --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers.pm @@ -22,7 +22,7 @@ sub getOidcRpByConfKey { # Check if confKey is defined if ( !defined $conf->{oidcRPMetaDataOptions}->{$confKey} ) { - return $self->sendError( $req, 'Service Provider not found', 404 ); + return $self->sendError( $req, "OIDC relying party '$confKey' not found", 404 ); } # Get Client ID @@ -50,4 +50,72 @@ sub getOidcRpByConfKey { ); } +sub getSamlSpByConfKey { + my ( $self, $req ) = @_; + + my $confKey = $req->params('confKey') + or return $self->sendError( $req, 'confKey is missing', 400 ); + + $self->logger->debug("[API] SAML SP $confKey configuration requested"); + + # Get latest configuration + my $conf = $self->_confAcc->getConf; + + # Check if confKey is defined + if ( !defined $conf->{samlSPMetaDataXML}->{$confKey} ) { + return $self->sendError( $req, "SAML service Provider '$confKey' not found", 404 ); + } + + # Get metadata + my $metadata = $conf->{samlSPMetaDataXML}->{$confKey} + ->{samlSPMetaDataXML}; + + # Get exported attributes + my %exportedAttributes; + foreach ( + keys %{ + $conf->{samlSPMetaDataExportedAttributes} + ->{$confKey} + } + ) + { + # Extract fields from exportedAttr value + my ( $mandatory, $name, $format, $friendly_name ) = + split( /;/, + $conf->{samlSPMetaDataExportedAttributes} + ->{$confKey}->{$_} ); + + $mandatory = !!$mandatory ? 'true' : 'false'; + + $exportedAttributes->{$_} = { + name => $name, + mandatory => $mandatory + }; + + if (defined $friendly_name && $friendly_name ne '') { + $exportedAttributes->{$_}->{friendlyName} = $friendly_name; + } + + if (defined $format && $format ne '') { + $exportedAttributes->{$_}->{format} = $format; + } + } + + # Dump object + use Data::Dumper; print STDERR Dumper($exportedAttributes); + + # Get options + my $options = $conf->{samlSPMetaDataOptions}->{$confKey}; + + return $self->sendJSONresponse( + $req, + { + confKey => $confKey, + metadata => $metadata, + exportedAttributes => $exportedAttributes, + options => $options + } + ); +} + 1; -- GitLab From 1359c81e4e02760aa95f51a1f544ce6645543a11 Mon Sep 17 00:00:00 2001 From: Soisik Froger Date: Thu, 5 Dec 2019 14:06:33 +0000 Subject: [PATCH 06/31] Manager API - find oidcrps or samlsps by configKey #2034 --- .../lib/Lemonldap/NG/Manager/Api.pm | 14 +- .../lib/Lemonldap/NG/Manager/Api/Providers.pm | 142 +++++++++++++++--- 2 files changed, 129 insertions(+), 27 deletions(-) diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api.pm index de58ec9408..67a7a188b0 100644 --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api.pm @@ -37,10 +37,20 @@ sub addRoutes { v1 => { providers => { oidc => { - rp => { ':confKey' => 'getOidcRpByConfKey' }, + rp => { + findByConfKey => { + ':uPattern' => 'findOidcRpByConfKey' + }, + ':confKey' => 'getOidcRpByConfKey' + }, }, saml => { - sp => { ':confKey' => 'getSamlSpByConfKey' }, + sp => { + findByConfKey => { + ':uPattern' => 'findSamlSpByConfKey' + }, + ':confKey' => 'getSamlSpByConfKey' + }, }, }, }, diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers.pm index 97476c2671..1a0694e965 100644 --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers.pm @@ -14,17 +14,67 @@ sub getOidcRpByConfKey { # Get latest configuration my $conf = $self->_confAcc->getConf; + my $oidcRp = $self->_getOidcRp($conf, $confKey); # To save configuration #$self->_confAcc->saveConf( $conf ) ; # Dump object #use Data::Dumper; print STDERR Dumper($self); - # Check if confKey is defined - if ( !defined $conf->{oidcRPMetaDataOptions}->{$confKey} ) { + # Return 404 if not found + unless (defined $oidcRp) { return $self->sendError( $req, "OIDC relying party '$confKey' not found", 404 ); } + return $self->sendJSONresponse( + $req, $oidcRp + ); +} + +sub findOidcRpByConfKey { + my ( $self, $req ) = @_; + + my $pattern = ( + defined $req->params('uPattern') ? + $req->params('uPattern') : + ( defined $req->params('pattern') ? $req->params('pattern') : undef ) + ); + + unless (defined $pattern) { + return $self->sendError( $req, 'pattern is missing', 400 ); + } + + $self->logger->debug("[API] Find OIDC RPs by confKey regexp $pattern requested"); + + # Get latest configuration + my $conf = $self->_confAcc->getConf; + + my @oidcRps; + + foreach ( + keys %{ + $conf->{oidcRPMetaDataOptions} + } + ) + { + if ($_ =~ $pattern) { + push @oidcRps, $self->_getOidcRp($conf, $_); + } + } + + return $self->sendJSONresponse( + $req, [ @oidcRps ] + ); +} + +sub _getOidcRp { + my ( $self, $conf, $confKey ) = @_; + + # Check if confKey is defined + unless ( defined $conf->{oidcRPMetaDataOptions}->{$confKey} ) { + return undef; + } + # Get Client ID my $clientId = $conf->{oidcRPMetaDataOptions}->{$confKey} ->{oidcRPMetaDataOptionsClientID}; @@ -38,16 +88,13 @@ sub getOidcRpByConfKey { # Get options my $options = $conf->{oidcRPMetaDataOptions}->{$confKey}; - return $self->sendJSONresponse( - $req, - { - confKey => $confKey, - clientId => $clientId, - exportedVars => $exportedVars, - extraClaim => $extraClaim, - options => $options - } - ); + return { + confKey => $confKey, + clientId => $clientId, + exportedVars => $exportedVars, + extraClaim => $extraClaim, + options => $options + }; } sub getSamlSpByConfKey { @@ -61,11 +108,62 @@ sub getSamlSpByConfKey { # Get latest configuration my $conf = $self->_confAcc->getConf; + $samlSp = $self->_getSamlSp($conf, $confKey); + # Check if confKey is defined - if ( !defined $conf->{samlSPMetaDataXML}->{$confKey} ) { + unless (defined $samlSp) { return $self->sendError( $req, "SAML service Provider '$confKey' not found", 404 ); } + return $self->sendJSONresponse( + $req, $samlSp + ); +} + +sub findSamlSpByConfKey { + my ( $self, $req ) = @_; + + my $pattern = ( + defined $req->params('uPattern') ? + $req->params('uPattern') : + ( defined $req->params('pattern') ? $req->params('pattern') : undef ) + ); + + unless (defined $pattern) { + return $self->sendError( $req, 'pattern is missing', 400 ); + } + + $self->logger->debug("[API] Find SAML SPs by confKey regexp $pattern requested"); + + # Get latest configuration + my $conf = $self->_confAcc->getConf; + + my @samlSps; + + foreach ( + keys %{ + $conf->{samlSPMetaDataXML} + } + ) + { + if ($_ =~ $pattern) { + push @samlSps, $self->_getSamlSp($conf, $_); + } + } + + return $self->sendJSONresponse( + $req, [ @samlSps ] + ); +} + +sub _getSamlSp { + my ( $self, $conf, $confKey ) = @_; + + # Check if confKey is defined + if ( !defined $conf->{samlSPMetaDataXML}->{$confKey} ) { + return undef; + } + # Get metadata my $metadata = $conf->{samlSPMetaDataXML}->{$confKey} ->{samlSPMetaDataXML}; @@ -101,21 +199,15 @@ sub getSamlSpByConfKey { } } - # Dump object - use Data::Dumper; print STDERR Dumper($exportedAttributes); - # Get options my $options = $conf->{samlSPMetaDataOptions}->{$confKey}; - return $self->sendJSONresponse( - $req, - { - confKey => $confKey, - metadata => $metadata, - exportedAttributes => $exportedAttributes, - options => $options - } - ); + return { + confKey => $confKey, + metadata => $metadata, + exportedAttributes => $exportedAttributes, + options => $options + }; } 1; -- GitLab From 67db0b6865925cb27c00893dca026713870f15c8 Mon Sep 17 00:00:00 2001 From: Soisik Froger Date: Thu, 5 Dec 2019 14:59:53 +0000 Subject: [PATCH 07/31] Manager API - find oidcrps by clientId or samlsps by entityId #2034 --- .../lib/Lemonldap/NG/Manager/Api.pm | 6 ++ .../lib/Lemonldap/NG/Manager/Api/Providers.pm | 66 +++++++++++++++++++ 2 files changed, 72 insertions(+) diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api.pm index 67a7a188b0..2d30bbebd2 100644 --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api.pm @@ -41,6 +41,9 @@ sub addRoutes { findByConfKey => { ':uPattern' => 'findOidcRpByConfKey' }, + findByClientId => { + ':uClientId' => 'findOidcRpByClientId' + }, ':confKey' => 'getOidcRpByConfKey' }, }, @@ -49,6 +52,9 @@ sub addRoutes { findByConfKey => { ':uPattern' => 'findSamlSpByConfKey' }, + findByEntityId => { + ':uEntityId' => 'findSamlSpByEntityId' + }, ':confKey' => 'getSamlSpByConfKey' }, }, diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers.pm index 1a0694e965..7a5f906f97 100644 --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers.pm @@ -67,6 +67,39 @@ sub findOidcRpByConfKey { ); } +sub findOidcRpByClientId { + my ( $self, $req ) = @_; + + my $clientId = ( + defined $req->params('uClientId') ? + $req->params('uClientId') : + ( defined $req->params('clientId') ? $req->params('clientId') : undef ) + ); + + unless (defined $clientId) { + return $self->sendError( $req, 'clientId is missing', 400 ); + } + + $self->logger->debug("[API] Find OIDC RPs by clientId $clientId requested"); + + # Get latest configuration + my $conf = $self->_confAcc->getConf; + + foreach ( + keys %{ + $conf->{oidcRPMetaDataOptions} + } + ) + { + if ($conf->{oidcRPMetaDataOptions}->{$_}->{oidcRPMetaDataOptionsClientID} eq $clientId) { + return $self->sendJSONresponse( + $req, $self->_getOidcRp($conf, $_) + ); + } + } + return $self->sendJSONresponse($req, {}); +} + sub _getOidcRp { my ( $self, $conf, $confKey ) = @_; @@ -156,6 +189,39 @@ sub findSamlSpByConfKey { ); } +sub findSamlSpByEntityId { + my ( $self, $req ) = @_; + + my $entityId = ( + defined $req->params('uEntityId') ? + $req->params('uEntityId') : + ( defined $req->params('entityId') ? $req->params('entityId') : undef ) + ); + + unless (defined $entityId) { + return $self->sendError( $req, 'entityId is missing', 400 ); + } + + $self->logger->debug("[API] Find SAML SPs by entityId $entityId requested"); + + # Get latest configuration + my $conf = $self->_confAcc->getConf; + + foreach ( + keys %{ + $conf->{samlSPMetaDataXML} + } + ) + { + if ($conf->{samlSPMetaDataXML}->{$_}->{samlSPMetaDataXML} =~ /entityID=['"](.+?)['"]/ && $1 eq $entityId) { + return $self->sendJSONresponse( + $req, $self->_getSamlSp($conf, $_) + ); + } + } + return $self->sendJSONresponse($req, {}); +} + sub _getSamlSp { my ( $self, $conf, $confKey ) = @_; -- GitLab From f14ae6cd4bfa105212605010ad09f2ef8ba72135 Mon Sep 17 00:00:00 2001 From: Soisik Froger Date: Thu, 5 Dec 2019 15:58:21 +0000 Subject: [PATCH 08/31] Manager API - fixed bug with exportedAttributes getting mixed up between saml sps #2034 --- .../lib/Lemonldap/NG/Manager/Api/Providers.pm | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers.pm index 7a5f906f97..b195ba6b62 100644 --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers.pm @@ -234,8 +234,17 @@ sub _getSamlSp { my $metadata = $conf->{samlSPMetaDataXML}->{$confKey} ->{samlSPMetaDataXML}; + # Get options + my $options = $conf->{samlSPMetaDataOptions}->{$confKey}; + + my $samlSp = { + confKey => $confKey, + metadata => $metadata, + exportedAttributes => {}, + options => $options + }; + # Get exported attributes - my %exportedAttributes; foreach ( keys %{ $conf->{samlSPMetaDataExportedAttributes} @@ -251,29 +260,22 @@ sub _getSamlSp { $mandatory = !!$mandatory ? 'true' : 'false'; - $exportedAttributes->{$_} = { + $samlSp->{exportedAttributes}->{$_} = { name => $name, mandatory => $mandatory }; if (defined $friendly_name && $friendly_name ne '') { - $exportedAttributes->{$_}->{friendlyName} = $friendly_name; + $samlSp->{exportedAttributes}->{$_}->{friendlyName} = $friendly_name; } if (defined $format && $format ne '') { - $exportedAttributes->{$_}->{format} = $format; + $samlSp->{exportedAttributes}->{$_}->{format} = $format; } } - # Get options - my $options = $conf->{samlSPMetaDataOptions}->{$confKey}; - return { - confKey => $confKey, - metadata => $metadata, - exportedAttributes => $exportedAttributes, - options => $options - }; + return $samlSp; } 1; -- GitLab From 2da104e45b84a2ffbb4b7f9e89ca8afc16ab4a50 Mon Sep 17 00:00:00 2001 From: Soisik Froger Date: Fri, 6 Dec 2019 10:38:02 +0000 Subject: [PATCH 09/31] Manager API - add new oidc rp or new saml sp #2034 --- .../lib/Lemonldap/NG/Manager/Api.pm | 14 + .../lib/Lemonldap/NG/Manager/Api/Providers.pm | 299 ++++++++++++++++-- 2 files changed, 286 insertions(+), 27 deletions(-) diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api.pm index 2d30bbebd2..d4d38419b9 100644 --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api.pm @@ -61,6 +61,20 @@ sub addRoutes { }, }, ['GET'] + ) + + ->addRoute( + v1 => { + providers => { + oidc => { + rp => 'addOidcRp' + }, + saml => { + sp => 'addSamlSp' + }, + }, + }, + ['POST'] ); } diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers.pm index b195ba6b62..65fecc8c87 100644 --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers.pm @@ -3,6 +3,9 @@ our $VERSION = '2.0.7'; package Lemonldap::NG::Manager::Api; +use Lemonldap::NG::Manager::Build::CTrees; +use Scalar::Util 'weaken'; + sub getOidcRpByConfKey { my ( $self, $req ) = @_; @@ -14,7 +17,7 @@ sub getOidcRpByConfKey { # Get latest configuration my $conf = $self->_confAcc->getConf; - my $oidcRp = $self->_getOidcRp($conf, $confKey); + my $oidcRp = $self->_getOidcRpByConfKey($conf, $confKey); # To save configuration #$self->_confAcc->saveConf( $conf ) ; @@ -58,7 +61,7 @@ sub findOidcRpByConfKey { ) { if ($_ =~ $pattern) { - push @oidcRps, $self->_getOidcRp($conf, $_); + push @oidcRps, $self->_getOidcRpByConfKey($conf, $_); } } @@ -85,22 +88,78 @@ sub findOidcRpByClientId { # Get latest configuration my $conf = $self->_confAcc->getConf; - foreach ( - keys %{ - $conf->{oidcRPMetaDataOptions} + my $oidcRp = $self->_getOidcRpByClientId($conf, $clientId); + + if (defined $oidcRp) { + return $self->sendJSONresponse($req, $oidcRp); + } + return $self->sendJSONresponse($req, {}); +} + +sub addOidcRp { + my ( $self, $req) = @_; + + my $oidcRp = $req->jsonBodyToObj; + use Data::Dumper; print STDERR Dumper($oidcRp); + + unless (defined $oidcRp->{confKey}) { + return $self->sendError( $req, 'Invalid input: confKey is missing', 405 ); + } + + unless (defined $oidcRp->{clientId}) { + return $self->sendError( $req, 'Invalid input: clientId is missing', 405 ); + } + + $self->logger->debug("[API] Add OIDC RP with confKey $oidcRp->{confKey} and clientId $oidcRp->{clientId} requested"); + + # Get latest configuration + my $conf = $self->_confAcc->getConf; + + if (defined $self->_getOidcRpByConfKey($conf, $oidcRp->{confKey})) { + return $self->sendError( $req, "Invalid input: An OIDC RP with confKey $oidcRp->{confKey} already exists", 405 ); + } + + if (defined $self->_getOidcRpByClientId($conf, $oidcRp->{clientId})) { + return $self->sendError( $req, "Invalid input: An OIDC RP with clientId $oidcRp->{clientId} already exists", 405 ); + } + + my $options; + if (defined $oidcRp->{options}) { + $options = $oidcRp->{options}; + } + $options->{oidcRPMetaDataOptionsClientID} = $oidcRp->{clientId}; + my $res = $self->_hasAllowedAttributes($options, 'oidcRPMetaDataNode', 'oidcRPMetaDataOptions'); + unless ($res->{res} eq 'ok') { + return $self->sendError( $req, $res->{message}, 405); + } + $conf->{oidcRPMetaDataOptions}->{$oidcRp->{confKey}} = $options; + + $self->logger->debug("options"); + use Data::Dumper; print STDERR Dumper($options); + + if (defined $oidcRp->{exportedVars}) { + if ($self->_isSimpleKeyValueHash($oidcRp->{exportedVars})) { + $conf->{oidcRPMetaDataExportedVars}->{$oidcRp->{confKey}} = $oidcRp->{exportedVars}; + } else { + return $self->sendError( $req, "Invalid input: exportedVars is not a hash object with \"key\":\"value\" attributes", 405 ); } - ) - { - if ($conf->{oidcRPMetaDataOptions}->{$_}->{oidcRPMetaDataOptionsClientID} eq $clientId) { - return $self->sendJSONresponse( - $req, $self->_getOidcRp($conf, $_) - ); + } + + if (defined $oidcRp->{extraClaim}) { + if ($self->_isSimpleKeyValueHash($oidcRp->{extraClaim})) { + $conf->{oidcRPMetaDataOptionsExtraClaims}->{$oidcRp->{confKey}} = $oidcRp->{extraClaim}; + } else { + return $self->sendError( $req, "Invalid input: extraClaim is not a hash object with \"key\":\"value\" attributes", 405 ); } } - return $self->sendJSONresponse($req, {}); + + # Save configuration + $self->_confAcc->saveConf($conf); + + return $self->sendJSONresponse($req, "Successful operation"); } -sub _getOidcRp { +sub _getOidcRpByConfKey { my ( $self, $conf, $confKey ) = @_; # Check if confKey is defined @@ -130,6 +189,22 @@ sub _getOidcRp { }; } +sub _getOidcRpByClientId { + my ( $self, $conf, $clientId ) = @_; + + foreach ( + keys %{ + $conf->{oidcRPMetaDataOptions} + } + ) + { + if ($conf->{oidcRPMetaDataOptions}->{$_}->{oidcRPMetaDataOptionsClientID} eq $clientId) { + return $self->_getOidcRpByConfKey($conf, $_) + } + } + return undef; +} + sub getSamlSpByConfKey { my ( $self, $req ) = @_; @@ -141,7 +216,7 @@ sub getSamlSpByConfKey { # Get latest configuration my $conf = $self->_confAcc->getConf; - $samlSp = $self->_getSamlSp($conf, $confKey); + $samlSp = $self->_getSamlSpByConfKey($conf, $confKey); # Check if confKey is defined unless (defined $samlSp) { @@ -180,7 +255,7 @@ sub findSamlSpByConfKey { ) { if ($_ =~ $pattern) { - push @samlSps, $self->_getSamlSp($conf, $_); + push @samlSps, $self->_getSamlSpByConfKey($conf, $_); } } @@ -207,22 +282,79 @@ sub findSamlSpByEntityId { # Get latest configuration my $conf = $self->_confAcc->getConf; - foreach ( - keys %{ - $conf->{samlSPMetaDataXML} - } - ) - { - if ($conf->{samlSPMetaDataXML}->{$_}->{samlSPMetaDataXML} =~ /entityID=['"](.+?)['"]/ && $1 eq $entityId) { - return $self->sendJSONresponse( - $req, $self->_getSamlSp($conf, $_) - ); - } + my $samlSp = $self->_getSamlSpByEntityId($conf, $entityId); + + if (defined $samlSp) { + return $self->sendJSONresponse($req, $samlSp); } return $self->sendJSONresponse($req, {}); } -sub _getSamlSp { +sub addSamlSp { + my ( $self, $req) = @_; + + my $samlSp = $req->jsonBodyToObj; + use Data::Dumper; print STDERR Dumper($samlSp); + + unless (defined $samlSp->{confKey}) { + return $self->sendError( $req, 'Invalid input: confKey is missing', 405 ); + } + + unless (defined $samlSp->{metadata}) { + return $self->sendError( $req, 'Invalid input: metadata is missing', 405 ); + } + + my $entityId = $self->_readSamlSpEntityId($samlSp->{metadata}); + + unless (defined $entityId) { + return $self->sendError( $req, 'Invalid input: entityID is missing in metadata', 405 ); + } + + $self->logger->debug("[API] Add SAML SP with confKey $samlSp->{confKey} and entityID $entityId requested"); + + # Get latest configuration + my $conf = $self->_confAcc->getConf; + + if (defined $self->_getSamlSpByConfKey($conf, $samlSp->{confKey})) { + return $self->sendError( $req, "Invalid input: A SAML SP with confKey $samlSp->{confKey} already exists", 405 ); + } + + if (defined $self->_getSamlSpByEntityId($conf, $entityId)) { + return $self->sendError( $req, "Invalid input: A SAML SP with entityID $entityId already exists", 405 ); + } + + $conf->{samlSPMetaDataXML}->{$samlSp->{confKey}}->{samlSPMetaDataXML} = $samlSp->{metadata}; + + my $options; + if (defined $samlSp->{options}) { + $options = $samlSp->{options}; + } + my $res = $self->_hasAllowedAttributes($options, 'samlSPMetaDataNode', 'samlSPMetaDataOptions'); + unless ($res->{res} eq 'ok') { + return $self->sendError( $req, $res->{message}, 405); + } + $conf->{samlSPMetaDataOptions}->{$samlSp->{confKey}} = $options; + + $self->logger->debug("options"); + use Data::Dumper; print STDERR Dumper($options); + + if (defined $samlSp->{exportedAttributes}) { + my $res = $self->_readSamlSpExportedAttributes($samlSp->{exportedAttributes}); + unless ($res->{res} eq 'ok') { + return $self->sendError( $req, $res->{message}, 405); + } + $self->logger->debug("exportedAttributes"); + use Data::Dumper; print STDERR Dumper($res->{exportedAttributes}); + $conf->{samlSPMetaDataExportedAttributes}->{$samlSp->{confKey}} = $res->{exportedAttributes}; + } + + # Save configuration + $self->_confAcc->saveConf($conf); + + return $self->sendJSONresponse($req, "Successful operation"); +} + +sub _getSamlSpByConfKey { my ( $self, $conf, $confKey ) = @_; # Check if confKey is defined @@ -278,4 +410,117 @@ sub _getSamlSp { return $samlSp; } +sub _getSamlSpByEntityId { + my ( $self, $conf, $entityId ) = @_; + + foreach ( + keys %{ + $conf->{samlSPMetaDataXML} + } + ) + { + if ($self->_readSamlSpEntityId($conf->{samlSPMetaDataXML}->{$_}->{samlSPMetaDataXML}) eq $entityId) { + return $self->_getSamlSpByConfKey($conf, $_); + } + } + return undef; +} + +sub _readSamlSpEntityId { + my ( $self, $metadata ) = @_; + if ($metadata =~ /entityID=['"](.+?)['"]/) { + return $1; + } + return undef; +} +sub _readSamlSpExportedAttributes { + my ( $self, $attrs ) = @_; + my $exportedAttributes; + my @allowedFormats = [ + "urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified", + "urn:oasis:names:tc:SAML:2.0:attrname-format:uri", + "urn:oasis:names:tc:SAML:2.0:attrname-format:basic" + ]; + $self->logger->debug("attributes "); + use Data::Dumper; print STDERR Dumper($attrs); + foreach (keys %{$attrs}) { + $self->logger->debug("parse attribute: $_"); + + unless (defined $attrs->{$_}->{name}) { + return { res => "ko", message => "Exported attribute $_ has no name"}; + } + my $mandatory = 0; + if (defined $attrs->{$_}->{mandatory}) { + if ( $attrs->{$_}->{mandatory} eq '1' or $attrs->{$_}->{mandatory} eq 'true' ) { + $mandatory = 1; + } + } + my $format = ''; + if (defined $attrs->{$_}->{format}) { + unless (grep {/^$attrs->{$_}->{format}/} @allowedFormats) { + return { res => "ko", message => "Exported attribute $_ format does not exist."}; + } + $format = $attrs->{$_}->{format}; + } + my $friendlyName = ''; + if (defined $attrs->{$_}->{friendlyName}) { + $friendlyName = $attrs->{$_}->{friendlyName}; + } + $exportedAttributes->{$_} = "$mandatory;$attrs->{$_}->{name};$format;$friendlyName"; + } + $self->logger->debug("exportedAttributes "); + use Data::Dumper; print STDERR Dumper($exportedAttributes); + + return { res => "ok", exportedAttributes => $exportedAttributes }; +} + +sub _isSimpleKeyValueHash { + my ( $self, $hash) = @_; + if (ref($hash) ne "HASH") { + return 0; + } + foreach (keys %{$hash}) { + if (ref($hash->{$_}) ne '' || ref($_) ne '') { + return 0; + } + } + return 1; +} +sub _hasAllowedAttributes { + my ( $self, $attributes, $node, $title) = @_; + + my $mainTree = Lemonldap::NG::Manager::Build::CTrees::cTrees(); + + my @allowedAttributes = $self->_listAttributes( + grep { $_->{title} eq $title } (grep { ref($_) eq "HASH" } @{$mainTree->{$node}}) + ); + + foreach $attribute (keys %{$attributes}) { + unless (grep {/^$attribute/} @allowedAttributes) { + return { + res => "ko", + message => "Invalid input: Attribute $attribute does not exist." + }; + } + } + + return { res => "ok" }; +} + +sub _listAttributes { + my ( $self, $node) = @_; + + my @attributes; + + foreach $child (@{$node->{nodes}}) { + if (ref($child) eq "HASH"){ + push (@attributes, $self->_listAttributes($child)); + } else { + push (@attributes, $child); + } + } + + return @attributes; +} + 1; -- GitLab From f8eb811f12c992e87e1ae91b8c736d66125a30e2 Mon Sep 17 00:00:00 2001 From: Soisik Froger Date: Fri, 6 Dec 2019 10:41:40 +0000 Subject: [PATCH 10/31] Manager API - clean up log #2034 --- .../lib/Lemonldap/NG/Manager/Api/Providers.pm | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers.pm index 65fecc8c87..f0f8b240ec 100644 --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers.pm @@ -100,7 +100,6 @@ sub addOidcRp { my ( $self, $req) = @_; my $oidcRp = $req->jsonBodyToObj; - use Data::Dumper; print STDERR Dumper($oidcRp); unless (defined $oidcRp->{confKey}) { return $self->sendError( $req, 'Invalid input: confKey is missing', 405 ); @@ -134,9 +133,6 @@ sub addOidcRp { } $conf->{oidcRPMetaDataOptions}->{$oidcRp->{confKey}} = $options; - $self->logger->debug("options"); - use Data::Dumper; print STDERR Dumper($options); - if (defined $oidcRp->{exportedVars}) { if ($self->_isSimpleKeyValueHash($oidcRp->{exportedVars})) { $conf->{oidcRPMetaDataExportedVars}->{$oidcRp->{confKey}} = $oidcRp->{exportedVars}; @@ -294,7 +290,6 @@ sub addSamlSp { my ( $self, $req) = @_; my $samlSp = $req->jsonBodyToObj; - use Data::Dumper; print STDERR Dumper($samlSp); unless (defined $samlSp->{confKey}) { return $self->sendError( $req, 'Invalid input: confKey is missing', 405 ); @@ -335,16 +330,11 @@ sub addSamlSp { } $conf->{samlSPMetaDataOptions}->{$samlSp->{confKey}} = $options; - $self->logger->debug("options"); - use Data::Dumper; print STDERR Dumper($options); - if (defined $samlSp->{exportedAttributes}) { my $res = $self->_readSamlSpExportedAttributes($samlSp->{exportedAttributes}); unless ($res->{res} eq 'ok') { return $self->sendError( $req, $res->{message}, 405); } - $self->logger->debug("exportedAttributes"); - use Data::Dumper; print STDERR Dumper($res->{exportedAttributes}); $conf->{samlSPMetaDataExportedAttributes}->{$samlSp->{confKey}} = $res->{exportedAttributes}; } @@ -441,11 +431,7 @@ sub _readSamlSpExportedAttributes { "urn:oasis:names:tc:SAML:2.0:attrname-format:uri", "urn:oasis:names:tc:SAML:2.0:attrname-format:basic" ]; - $self->logger->debug("attributes "); - use Data::Dumper; print STDERR Dumper($attrs); foreach (keys %{$attrs}) { - $self->logger->debug("parse attribute: $_"); - unless (defined $attrs->{$_}->{name}) { return { res => "ko", message => "Exported attribute $_ has no name"}; } @@ -468,9 +454,6 @@ sub _readSamlSpExportedAttributes { } $exportedAttributes->{$_} = "$mandatory;$attrs->{$_}->{name};$format;$friendlyName"; } - $self->logger->debug("exportedAttributes "); - use Data::Dumper; print STDERR Dumper($exportedAttributes); - return { res => "ok", exportedAttributes => $exportedAttributes }; } -- GitLab From 35eea585152eb3559739b290cbb82c64e932de5d Mon Sep 17 00:00:00 2001 From: Soisik Froger Date: Fri, 6 Dec 2019 14:30:40 +0000 Subject: [PATCH 11/31] Manager API - include display options in oidc rp options #2034 --- .../lib/Lemonldap/NG/Manager/Api/Providers.pm | 85 +++++++++++++++---- 1 file changed, 67 insertions(+), 18 deletions(-) diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers.pm index f0f8b240ec..15aabd3056 100644 --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers.pm @@ -122,16 +122,31 @@ sub addOidcRp { return $self->sendError( $req, "Invalid input: An OIDC RP with clientId $oidcRp->{clientId} already exists", 405 ); } - my $options; - if (defined $oidcRp->{options}) { - $options = $oidcRp->{options}; + unless (defined $oidcRp->{options}) { + $oidcRp->{options} = {}; } - $options->{oidcRPMetaDataOptionsClientID} = $oidcRp->{clientId}; - my $res = $self->_hasAllowedAttributes($options, 'oidcRPMetaDataNode', 'oidcRPMetaDataOptions'); + $oidcRp->{options}->{oidcRPMetaDataOptionsClientID} = $oidcRp->{clientId}; + my $res = $self->_hasAllowedAttributes($oidcRp->{options}, 'oidcRPMetaDataNode', ['oidcRPMetaDataOptions', 'oidcRPMetaDataOptionsDisplay']); unless ($res->{res} eq 'ok') { return $self->sendError( $req, $res->{message}, 405); } - $conf->{oidcRPMetaDataOptions}->{$oidcRp->{confKey}} = $options; + my $displayOptions = $self->_filterAttributes($oidcRp->{options}, 'oidcRPMetaDataNode', ['oidcRPMetaDataOptionsDisplay']); + + $self->logger->debug("displayOptions"); + use Data::Dumper; print STDERR Dumper($displayOptions); + + if ($displayOptions) { + $conf->{oidcRPMetaDataOptionsDisplay}->{$oidcRp->{confKey}} = $displayOptions; + } + + my $options = $self->_filterAttributes($oidcRp->{options}, 'oidcRPMetaDataNode', ['oidcRPMetaDataOptions']); + + $self->logger->debug("options"); + use Data::Dumper; print STDERR Dumper($options); + + if ($options) { + $conf->{oidcRPMetaDataOptions}->{$oidcRp->{confKey}} = $options; + } if (defined $oidcRp->{exportedVars}) { if ($self->_isSimpleKeyValueHash($oidcRp->{exportedVars})) { @@ -150,7 +165,7 @@ sub addOidcRp { } # Save configuration - $self->_confAcc->saveConf($conf); + #$self->_confAcc->saveConf($conf); return $self->sendJSONresponse($req, "Successful operation"); } @@ -176,6 +191,10 @@ sub _getOidcRpByConfKey { # Get options my $options = $conf->{oidcRPMetaDataOptions}->{$confKey}; + # Merge display with options + my $dispOptions = $conf->{oidcRPMetaDataOptionsDisplay}->{$confKey}; + @options{keys %{$dispOptions}} = values %{$dispOptions}; + return { confKey => $confKey, clientId => $clientId, @@ -324,10 +343,11 @@ sub addSamlSp { if (defined $samlSp->{options}) { $options = $samlSp->{options}; } - my $res = $self->_hasAllowedAttributes($options, 'samlSPMetaDataNode', 'samlSPMetaDataOptions'); + my $res = $self->_hasAllowedAttributes($options, 'samlSPMetaDataNode', ['samlSPMetaDataOptions']); unless ($res->{res} eq 'ok') { return $self->sendError( $req, $res->{message}, 405); } + $conf->{samlSPMetaDataOptions}->{$samlSp->{confKey}} = $options; if (defined $samlSp->{exportedAttributes}) { @@ -426,12 +446,14 @@ sub _readSamlSpEntityId { sub _readSamlSpExportedAttributes { my ( $self, $attrs ) = @_; my $exportedAttributes; + my @allowedFormats = [ "urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified", "urn:oasis:names:tc:SAML:2.0:attrname-format:uri", "urn:oasis:names:tc:SAML:2.0:attrname-format:basic" ]; foreach (keys %{$attrs}) { + unless (defined $attrs->{$_}->{name}) { return { res => "ko", message => "Exported attribute $_ has no name"}; } @@ -443,7 +465,7 @@ sub _readSamlSpExportedAttributes { } my $format = ''; if (defined $attrs->{$_}->{format}) { - unless (grep {/^$attrs->{$_}->{format}/} @allowedFormats) { + unless (length(grep {/^$attrs->{$_}->{format}$/} @allowedFormats)) { return { res => "ko", message => "Exported attribute $_ format does not exist."}; } $format = $attrs->{$_}->{format}; @@ -470,16 +492,12 @@ sub _isSimpleKeyValueHash { return 1; } sub _hasAllowedAttributes { - my ( $self, $attributes, $node, $title) = @_; + my ( $self, $attributes, $rootNode, @titles) = @_; - my $mainTree = Lemonldap::NG::Manager::Build::CTrees::cTrees(); - - my @allowedAttributes = $self->_listAttributes( - grep { $_->{title} eq $title } (grep { ref($_) eq "HASH" } @{$mainTree->{$node}}) - ); + my @allowedAttributes = $self->_listAttributes($rootNode, @titles); foreach $attribute (keys %{$attributes}) { - unless (grep {/^$attribute/} @allowedAttributes) { + unless (grep {/^$attribute$/} @allowedAttributes) { return { res => "ko", message => "Invalid input: Attribute $attribute does not exist." @@ -490,14 +508,45 @@ sub _hasAllowedAttributes { return { res => "ok" }; } +sub _filterAttributes { + my ( $self, $attributes, $rootNode, $titles) = @_; + + my @keepAttributes = $self->_listAttributes($rootNode, $titles); + + my $filteredAttributes = {}; + + foreach $attribute (keys %{$attributes}) { + if (grep {/^$attribute$/} @keepAttributes) { + $filteredAttributes->{$attribute} = $attributes->{$attribute}; + } + } + + return $filteredAttributes; +} + sub _listAttributes { - my ( $self, $node) = @_; + my ( $self, $rootNode, $titles) = @_; + + my $mainTree = Lemonldap::NG::Manager::Build::CTrees::cTrees(); + + my $rootNodes = [ grep { ref($_) eq "HASH" } @{$mainTree->{$rootNode}} ]; my @attributes; + foreach $node (@{$rootNodes}) { + if (grep {/^$node->{title}$/} @{$titles}) { + push @attributes, $self->_listNodeAttributes($node); + } + } + return @attributes; +} + +sub _listNodeAttributes { + my ( $self, $node) = @_; + my @attributes; foreach $child (@{$node->{nodes}}) { if (ref($child) eq "HASH"){ - push (@attributes, $self->_listAttributes($child)); + push (@attributes, $self->_listNodeAttributes($child)); } else { push (@attributes, $child); } -- GitLab From adc2b2ec1eac3623c15be97f6150ad253a9cfc05 Mon Sep 17 00:00:00 2001 From: Soisik Froger Date: Fri, 6 Dec 2019 14:32:15 +0000 Subject: [PATCH 12/31] Manager API - clean up logs #2034 --- .../lib/Lemonldap/NG/Manager/Api/Providers.pm | 6 ------ 1 file changed, 6 deletions(-) diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers.pm index 15aabd3056..fea6d3e9d1 100644 --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers.pm @@ -132,18 +132,12 @@ sub addOidcRp { } my $displayOptions = $self->_filterAttributes($oidcRp->{options}, 'oidcRPMetaDataNode', ['oidcRPMetaDataOptionsDisplay']); - $self->logger->debug("displayOptions"); - use Data::Dumper; print STDERR Dumper($displayOptions); - if ($displayOptions) { $conf->{oidcRPMetaDataOptionsDisplay}->{$oidcRp->{confKey}} = $displayOptions; } my $options = $self->_filterAttributes($oidcRp->{options}, 'oidcRPMetaDataNode', ['oidcRPMetaDataOptions']); - $self->logger->debug("options"); - use Data::Dumper; print STDERR Dumper($options); - if ($options) { $conf->{oidcRPMetaDataOptions}->{$oidcRp->{confKey}} = $options; } -- GitLab From b4c017852a43bb985ce1514ba30c6297b32f1908 Mon Sep 17 00:00:00 2001 From: Soisik Froger Date: Mon, 9 Dec 2019 08:16:21 +0000 Subject: [PATCH 13/31] Manager API - update and replace for oidcrp and samlsp #2034 --- .../lib/Lemonldap/NG/Common/PSGI/Router.pm | 4 +- .../lib/Lemonldap/NG/Manager/Api.pm | 28 ++ .../lib/Lemonldap/NG/Manager/Api/Providers.pm | 378 ++++++++++++++---- 3 files changed, 323 insertions(+), 87 deletions(-) diff --git a/lemonldap-ng-common/lib/Lemonldap/NG/Common/PSGI/Router.pm b/lemonldap-ng-common/lib/Lemonldap/NG/Common/PSGI/Router.pm index 2acbd68ffc..3d59a5c3c7 100644 --- a/lemonldap-ng-common/lib/Lemonldap/NG/Common/PSGI/Router.pm +++ b/lemonldap-ng-common/lib/Lemonldap/NG/Common/PSGI/Router.pm @@ -12,7 +12,7 @@ extends 'Lemonldap::NG::Common::PSGI'; has 'routes' => ( is => 'rw', isa => 'HashRef', - default => sub { { GET => {}, POST => {}, PUT => {}, DELETE => {} } } + default => sub { { GET => {}, POST => {}, PUT => {}, PATCH => {}, DELETE => {} } } ); has 'defaultRoute' => ( is => 'rw', default => 'index.html' ); @@ -20,7 +20,7 @@ has 'defaultRoute' => ( is => 'rw', default => 'index.html' ); sub addRoute { my ( $self, $word, $dest, $methods, $transform ) = (@_); - $methods ||= [qw(GET POST PUT DELETE)]; + $methods ||= [qw(GET POST PUT PATCH DELETE)]; foreach my $method (@$methods) { $self->logger->debug("Add $method route:"); $self->genRoute( $self->routes->{$method}, $word, $dest, $transform ); diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api.pm index d4d38419b9..e0d1f82302 100644 --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api.pm @@ -75,6 +75,34 @@ sub addRoutes { }, }, ['POST'] + ) + + ->addRoute( + v1 => { + providers => { + oidc => { + rp => {':confKey' => 'replaceOidcRp'} + }, + saml => { + sp => {':confKey' => 'replaceSamlSp'} + }, + }, + }, + ['PUT'] + ) + + ->addRoute( + v1 => { + providers => { + oidc => { + rp => {':confKey' => 'updateOidcRp'} + }, + saml => { + sp => {':confKey' => 'updateSamlSp'} + }, + }, + }, + ['PATCH'] ); } diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers.pm index fea6d3e9d1..2c5d793330 100644 --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers.pm @@ -6,6 +6,8 @@ package Lemonldap::NG::Manager::Api; use Lemonldap::NG::Manager::Build::CTrees; use Scalar::Util 'weaken'; +# TODO check attributes type + first node + sub getOidcRpByConfKey { my ( $self, $req ) = @_; @@ -44,7 +46,7 @@ sub findOidcRpByConfKey { ); unless (defined $pattern) { - return $self->sendError( $req, 'pattern is missing', 400 ); + return $self->sendError( $req, 'pattern is missing', 405 ); } $self->logger->debug("[API] Find OIDC RPs by confKey regexp $pattern requested"); @@ -80,7 +82,7 @@ sub findOidcRpByClientId { ); unless (defined $clientId) { - return $self->sendError( $req, 'clientId is missing', 400 ); + return $self->sendError( $req, 'clientId is missing', 405 ); } $self->logger->debug("[API] Find OIDC RPs by clientId $clientId requested"); @@ -99,71 +101,138 @@ sub findOidcRpByClientId { sub addOidcRp { my ( $self, $req) = @_; - my $oidcRp = $req->jsonBodyToObj; + my $add = $req->jsonBodyToObj; + + unless ($add) { + return $self->sendError( $req, "Invalid input: Request content could not be parse to json", 404 ); + } - unless (defined $oidcRp->{confKey}) { + unless (defined $add->{confKey}) { return $self->sendError( $req, 'Invalid input: confKey is missing', 405 ); } - unless (defined $oidcRp->{clientId}) { + unless (defined $add->{clientId}) { return $self->sendError( $req, 'Invalid input: clientId is missing', 405 ); } - $self->logger->debug("[API] Add OIDC RP with confKey $oidcRp->{confKey} and clientId $oidcRp->{clientId} requested"); + $self->logger->debug("[API] Add OIDC RP with confKey $add->{confKey} and clientId $add->{clientId} requested"); # Get latest configuration my $conf = $self->_confAcc->getConf; - if (defined $self->_getOidcRpByConfKey($conf, $oidcRp->{confKey})) { - return $self->sendError( $req, "Invalid input: An OIDC RP with confKey $oidcRp->{confKey} already exists", 405 ); + if (defined $self->_getOidcRpByConfKey($conf, $add->{confKey})) { + return $self->sendError( $req, "Invalid input: An OIDC RP with confKey $add->{confKey} already exists", 405 ); } - if (defined $self->_getOidcRpByClientId($conf, $oidcRp->{clientId})) { - return $self->sendError( $req, "Invalid input: An OIDC RP with clientId $oidcRp->{clientId} already exists", 405 ); + if (defined $self->_getOidcRpByClientId($conf, $add->{clientId})) { + return $self->sendError( $req, "Invalid input: An OIDC RP with clientId $add->{clientId} already exists", 405 ); } - unless (defined $oidcRp->{options}) { - $oidcRp->{options} = {}; + unless (defined $add->{options}) { + $add->{options} = {}; } - $oidcRp->{options}->{oidcRPMetaDataOptionsClientID} = $oidcRp->{clientId}; - my $res = $self->_hasAllowedAttributes($oidcRp->{options}, 'oidcRPMetaDataNode', ['oidcRPMetaDataOptions', 'oidcRPMetaDataOptionsDisplay']); + $add->{options}->{oidcRPMetaDataOptionsClientID} = $add->{clientId}; + + $res = $self->_pushOidcRp($conf, $add->{confKey}, $add, 0); unless ($res->{res} eq 'ok') { - return $self->sendError( $req, $res->{message}, 405); + return $self->sendError( $req, $res->{msg}, 405 ); } - my $displayOptions = $self->_filterAttributes($oidcRp->{options}, 'oidcRPMetaDataNode', ['oidcRPMetaDataOptionsDisplay']); - if ($displayOptions) { - $conf->{oidcRPMetaDataOptionsDisplay}->{$oidcRp->{confKey}} = $displayOptions; + return $self->sendJSONresponse($req, "Successful operation"); +} + +sub updateOidcRp { + my ( $self, $req ) = @_; + + my $confKey = $req->params('confKey') + or return $self->sendError( $req, 'confKey is missing', 400 ); + + my $update = $req->jsonBodyToObj; + + unless ($update) { + return $self->sendError( $req, "Invalid input: Request content could not be parse to json", 405 ); } - my $options = $self->_filterAttributes($oidcRp->{options}, 'oidcRPMetaDataNode', ['oidcRPMetaDataOptions']); + $self->logger->debug("[API] OIDC RP $confKey configuration update requested"); + + # Get latest configuration + my $conf = $self->_confAcc->getConf; + + my $current = $self->_getOidcRpByConfKey($conf, $confKey); - if ($options) { - $conf->{oidcRPMetaDataOptions}->{$oidcRp->{confKey}} = $options; + # Return 404 if not found + unless (defined $current) { + return $self->sendError( $req, "OIDC relying party '$confKey' not found", 404 ); } - if (defined $oidcRp->{exportedVars}) { - if ($self->_isSimpleKeyValueHash($oidcRp->{exportedVars})) { - $conf->{oidcRPMetaDataExportedVars}->{$oidcRp->{confKey}} = $oidcRp->{exportedVars}; - } else { - return $self->sendError( $req, "Invalid input: exportedVars is not a hash object with \"key\":\"value\" attributes", 405 ); - } + # check if new clientID exists already + my $res = $self->_isNewOidcRpClientIdUnique($conf, $confKey, $update); + unless ($res->{res} eq 'ok') { + return $self->sendError( $req, $res->{msg}, 405 ); } - if (defined $oidcRp->{extraClaim}) { - if ($self->_isSimpleKeyValueHash($oidcRp->{extraClaim})) { - $conf->{oidcRPMetaDataOptionsExtraClaims}->{$oidcRp->{confKey}} = $oidcRp->{extraClaim}; - } else { - return $self->sendError( $req, "Invalid input: extraClaim is not a hash object with \"key\":\"value\" attributes", 405 ); - } + $res = $self->_pushOidcRp($conf, $confKey, $update, 0); + unless ($res->{res} eq 'ok') { + return $self->sendError( $req, $res->{msg}, 405 ); } - # Save configuration - #$self->_confAcc->saveConf($conf); + return $self->sendJSONresponse($req, "Successful operation"); +} + +sub replaceOidcRp { + + my ( $self, $req ) = @_; + + my $confKey = $req->params('confKey') + or return $self->sendError( $req, 'confKey is missing', 400 ); + + my $replace = $req->jsonBodyToObj; + + unless ($replace) { + return $self->sendError( $req, "Invalid input: Request content could not be parse to json", 405 ); + } + + unless (defined $replace->{clientId}) { + return $self->sendError( $req, 'Invalid input: clientId is missing', 405 ); + } + + $self->logger->debug("[API] OIDC RP $confKey configuration replace requested"); + + # Get latest configuration + my $conf = $self->_confAcc->getConf; + + # Return 404 if not found + unless (defined $self->_getOidcRpByConfKey($conf, $confKey)) { + return $self->sendError( $req, "OIDC relying party '$confKey' not found", 404 ); + } + + # check if new clientID exists already + my $res = $self->_isNewOidcRpClientIdUnique($conf, $confKey, $replace); + unless ($res->{res} eq 'ok') { + return $self->sendError( $req, $res->{msg}, 405 ); + } + + $res = $self->_pushOidcRp($conf, $confKey, $replace, 1); + unless ($res->{res} eq 'ok') { + return $self->sendError( $req, $res->{msg}, 405 ); + } return $self->sendJSONresponse($req, "Successful operation"); } +sub _isNewOidcRpClientIdUnique { + my ( $self, $conf, $confKey, $oidcRp) = @_; + + my $curClientId = $self->_getOidcRpByConfKey($conf, $confKey)->{clientId}; + my $newClientId = $oidcRp->{clientId} || $oidcRp->{options}->{oidcRPMetaDataOptionsClientID} || ""; + if ($newClientId ne '' && $newClientId ne $curClientId) { + if ( defined $self->_getOidcRpByClientId($conf, $newClientId) ) { + return { res => 'ko' , msg => "An OIDC relying party with clientId '$newClientId' already exists" }; + } + } + return { res => 'ok' }; +} + sub _getOidcRpByConfKey { my ( $self, $conf, $confKey ) = @_; @@ -185,10 +254,6 @@ sub _getOidcRpByConfKey { # Get options my $options = $conf->{oidcRPMetaDataOptions}->{$confKey}; - # Merge display with options - my $dispOptions = $conf->{oidcRPMetaDataOptionsDisplay}->{$confKey}; - @options{keys %{$dispOptions}} = values %{$dispOptions}; - return { confKey => $confKey, clientId => $clientId, @@ -214,6 +279,50 @@ sub _getOidcRpByClientId { return undef; } +sub _pushOidcRp { + my ( $self, $conf, $confKey, $push, $replace) = @_; + + if ($replace) { + $conf->{oidcRPMetaDataOptions}->{$confKey} = {}; + $conf->{oidcRPMetaDataExportedVars}->{$confKey} = {}; + $conf->{samlSPMetaDataExportedAttributes}->{$confKey} = {}; + $conf->{oidcRPMetaDataOptionsExtraClaims}->{$confKey} = {}; + } + + if (defined $push->{options}) { + $res = $self->_hasAllowedAttributes($push->{options}, 'oidcRPMetaDataNode'); + unless ($res->{res} eq 'ok') { + return $res; + } + $conf->{oidcRPMetaDataOptions}->{$confKey} = $push->{options}; + } + + if (defined $push->{clientId}) { + $conf->{oidcRPMetaDataOptions}->{$confKey}->{oidcRPMetaDataOptionsClientID} = $push->{clientId}; + } + + if (defined $push->{exportedVars}) { + if ($self->_isSimpleKeyValueHash($push->{exportedVars})) { + $conf->{oidcRPMetaDataExportedVars}->{$confKey} = $push->{exportedVars}; + } else { + return { res => 'ko', msg => "Invalid input: exportedVars is not a hash object with \"key\":\"value\" attributes" }; + } + } + + if (defined $push->{extraClaim}) { + if ($self->_isSimpleKeyValueHash($push->{extraClaim})) { + $conf->{oidcRPMetaDataOptionsExtraClaims}->{$confKey} = $push->{extraClaim}; + } else { + return { res => 'ko', msg => "Invalid input: extraClaim is not a hash object with \"key\":\"value\" attributes" }; + } + } + + # Save configuration + $self->_confAcc->saveConf($conf); + + return { res => 'ok' }; +} + sub getSamlSpByConfKey { my ( $self, $req ) = @_; @@ -247,7 +356,7 @@ sub findSamlSpByConfKey { ); unless (defined $pattern) { - return $self->sendError( $req, 'pattern is missing', 400 ); + return $self->sendError( $req, 'pattern is missing', 405 ); } $self->logger->debug("[API] Find SAML SPs by confKey regexp $pattern requested"); @@ -283,7 +392,7 @@ sub findSamlSpByEntityId { ); unless (defined $entityId) { - return $self->sendError( $req, 'entityId is missing', 400 ); + return $self->sendError( $req, 'entityId is missing', 405 ); } $self->logger->debug("[API] Find SAML SPs by entityId $entityId requested"); @@ -302,60 +411,126 @@ sub findSamlSpByEntityId { sub addSamlSp { my ( $self, $req) = @_; - my $samlSp = $req->jsonBodyToObj; + my $add = $req->jsonBodyToObj; + + unless ($add) { + return $self->sendError( $req, "Invalid input: Request content could not be parse to json", 404 ); + } - unless (defined $samlSp->{confKey}) { + unless (defined $add->{confKey}) { return $self->sendError( $req, 'Invalid input: confKey is missing', 405 ); } - unless (defined $samlSp->{metadata}) { + unless (defined $add->{metadata}) { return $self->sendError( $req, 'Invalid input: metadata is missing', 405 ); } - my $entityId = $self->_readSamlSpEntityId($samlSp->{metadata}); + my $entityId = $self->_readSamlSpEntityId($add->{metadata}); unless (defined $entityId) { return $self->sendError( $req, 'Invalid input: entityID is missing in metadata', 405 ); } - $self->logger->debug("[API] Add SAML SP with confKey $samlSp->{confKey} and entityID $entityId requested"); + $self->logger->debug("[API] Add SAML SP with confKey $add->{confKey} and entityID $entityId requested"); # Get latest configuration my $conf = $self->_confAcc->getConf; - if (defined $self->_getSamlSpByConfKey($conf, $samlSp->{confKey})) { - return $self->sendError( $req, "Invalid input: A SAML SP with confKey $samlSp->{confKey} already exists", 405 ); + if (defined $self->_getSamlSpByConfKey($conf, $add->{confKey})) { + return $self->sendError( $req, "Invalid input: A SAML SP with confKey $add->{confKey} already exists", 405 ); } if (defined $self->_getSamlSpByEntityId($conf, $entityId)) { return $self->sendError( $req, "Invalid input: A SAML SP with entityID $entityId already exists", 405 ); } - $conf->{samlSPMetaDataXML}->{$samlSp->{confKey}}->{samlSPMetaDataXML} = $samlSp->{metadata}; + $res = $self->_pushSamlSp($conf, $add->{confKey}, $add, 0); + unless ($res->{res} eq 'ok') { + return $self->sendError( $req, $res->{msg}, 405 ); + } + + return $self->sendJSONresponse($req, "Successful operation"); +} + +sub replaceSamlSp { + my ( $self, $req ) = @_; + + my $confKey = $req->params('confKey') + or return $self->sendError( $req, 'confKey is missing', 400 ); + + my $replace = $req->jsonBodyToObj; + + unless ($replace) { + return $self->sendError( $req, "Invalid input: Request content could not be parse to json", 405 ); + } + + unless (defined $replace->{metadata}) { + return $self->sendError( $req, 'Invalid input: metadata is missing', 405 ); + } + + $self->logger->debug("[API] SAML SP $confKey configuration replace requested"); + + # Get latest configuration + my $conf = $self->_confAcc->getConf; + + # Return 404 if not found + unless (defined $self->_getSamlSpByConfKey($conf, $confKey)) { + return $self->sendError( $req, "SAML service provider '$confKey' not found", 404 ); + } - my $options; - if (defined $samlSp->{options}) { - $options = $samlSp->{options}; + # check if new entityId exists already + $res = $self->_isNewSamlSpEntityIdUnique($conf, $confKey, $replace); + unless ($res->{res} eq 'ok') { + return $self->sendError( $req, $res->{msg}, 405 ); } - my $res = $self->_hasAllowedAttributes($options, 'samlSPMetaDataNode', ['samlSPMetaDataOptions']); + + $res = $self->_pushSamlSp($conf, $confKey, $replace, 1); unless ($res->{res} eq 'ok') { - return $self->sendError( $req, $res->{message}, 405); + return $self->sendError( $req, $res->{msg}, 405 ); } - $conf->{samlSPMetaDataOptions}->{$samlSp->{confKey}} = $options; + return $self->sendJSONresponse($req, "Successful operation"); +} + +sub updateSamlSp { + my ( $self, $req ) = @_; + + my $confKey = $req->params('confKey') + or return $self->sendError( $req, 'confKey is missing', 400 ); - if (defined $samlSp->{exportedAttributes}) { - my $res = $self->_readSamlSpExportedAttributes($samlSp->{exportedAttributes}); + my $update = $req->jsonBodyToObj; + + unless ($update) { + return $self->sendError( $req, "Invalid input: Request content could not be parse to json", 405 ); + } + + $self->logger->debug("[API] SAML SP $confKey configuration update requested"); + + # Get latest configuration + my $conf = $self->_confAcc->getConf; + + my $current = $self->_getSamlSpByConfKey($conf, $confKey); + + # Return 404 if not found + unless (defined $current) { + return $self->sendError( $req, "SAML service provider '$confKey' not found", 404 ); + } + + if (defined $update->{metadata}) { + # check if new entityId exists already + $res = $self->_isNewSamlSpEntityIdUnique($conf, $confKey, $update); unless ($res->{res} eq 'ok') { - return $self->sendError( $req, $res->{message}, 405); + return $self->sendError( $req, $res->{msg}, 405 ); } - $conf->{samlSPMetaDataExportedAttributes}->{$samlSp->{confKey}} = $res->{exportedAttributes}; } - # Save configuration - $self->_confAcc->saveConf($conf); + $res = $self->_pushSamlSp($conf, $confKey, $update, 0); + unless ($res->{res} eq 'ok') { + return $self->sendError( $req, $res->{msg}, 405 ); + } return $self->sendJSONresponse($req, "Successful operation"); + } sub _getSamlSpByConfKey { @@ -449,7 +624,7 @@ sub _readSamlSpExportedAttributes { foreach (keys %{$attrs}) { unless (defined $attrs->{$_}->{name}) { - return { res => "ko", message => "Exported attribute $_ has no name"}; + return { res => "ko", msg => "Exported attribute $_ has no name"}; } my $mandatory = 0; if (defined $attrs->{$_}->{mandatory}) { @@ -460,7 +635,7 @@ sub _readSamlSpExportedAttributes { my $format = ''; if (defined $attrs->{$_}->{format}) { unless (length(grep {/^$attrs->{$_}->{format}$/} @allowedFormats)) { - return { res => "ko", message => "Exported attribute $_ format does not exist."}; + return { res => "ko", msg => "Exported attribute $_ format does not exist."}; } $format = $attrs->{$_}->{format}; } @@ -473,6 +648,51 @@ sub _readSamlSpExportedAttributes { return { res => "ok", exportedAttributes => $exportedAttributes }; } +sub _pushSamlSp { + my ( $self, $conf, $confKey, $push, $replace) = @_; + + if ($replace) { + $conf->{samlSPMetaDataXML}->{$confKey} = {}; + $conf->{samlSPMetaDataOptions}->{$confKey} = {}; + $conf->{samlSPMetaDataExportedAttributes}->{$confKey} = {}; + } + + $conf->{samlSPMetaDataXML}->{$confKey}->{samlSPMetaDataXML} = $push->{metadata}; + + if (defined $push->{options}) { + my $res = $self->_hasAllowedAttributes($push->{options}, 'samlSPMetaDataNode'); + unless ($res->{res} eq 'ok') { + return $res; + } + $conf->{samlSPMetaDataOptions}->{$confKey} = $push->{options}; + } + + if (defined $push->{exportedAttributes}) { + my $res = $self->_readSamlSpExportedAttributes($push->{exportedAttributes}); + unless ($res->{res} eq 'ok') { + return $res; + } + $conf->{samlSPMetaDataExportedAttributes}->{$confKey} = $res->{exportedAttributes}; + } + + # Save configuration + $self->_confAcc->saveConf($conf); + + return { res => 'ok' }; +} + +sub _isNewSamlSpEntityIdUnique { + my ( $self, $conf, $confKey, $newSp) = @_; + my $newEntityId = $self->_readSamlSpEntityId($newSp->{metadata}); + my $curEntityId = $self->_readSamlSpEntityId($self->_getSamlSpByConfKey($conf, $confKey)->{metadata}); + if ($newEntityId ne $curEntityId) { + if (defined $self->_getSamlSpByEntityId($conf, $newEntityId) ) { + return { res => 'ko', msg => "An SAML service provide with entityId '$newEntityId' already exists" }; + } + } + return { res => 'ok' }; +} + sub _isSimpleKeyValueHash { my ( $self, $hash) = @_; if (ref($hash) ne "HASH") { @@ -486,15 +706,21 @@ sub _isSimpleKeyValueHash { return 1; } sub _hasAllowedAttributes { - my ( $self, $attributes, $rootNode, @titles) = @_; + my ( $self, $attributes, $rootNode) = @_; - my @allowedAttributes = $self->_listAttributes($rootNode, @titles); + my @allowedAttributes = $self->_listAttributes($rootNode); foreach $attribute (keys %{$attributes}) { + if (length(ref($attribute))) { + return { + res => "ko", + msg => "Invalid input: Attribute $attribute is not a string." + }; + } unless (grep {/^$attribute$/} @allowedAttributes) { return { res => "ko", - message => "Invalid input: Attribute $attribute does not exist." + msg => "Invalid input: Attribute $attribute does not exist." }; } } @@ -502,22 +728,6 @@ sub _hasAllowedAttributes { return { res => "ok" }; } -sub _filterAttributes { - my ( $self, $attributes, $rootNode, $titles) = @_; - - my @keepAttributes = $self->_listAttributes($rootNode, $titles); - - my $filteredAttributes = {}; - - foreach $attribute (keys %{$attributes}) { - if (grep {/^$attribute$/} @keepAttributes) { - $filteredAttributes->{$attribute} = $attributes->{$attribute}; - } - } - - return $filteredAttributes; -} - sub _listAttributes { my ( $self, $rootNode, $titles) = @_; @@ -527,9 +737,7 @@ sub _listAttributes { my @attributes; foreach $node (@{$rootNodes}) { - if (grep {/^$node->{title}$/} @{$titles}) { - push @attributes, $self->_listNodeAttributes($node); - } + push @attributes, $self->_listNodeAttributes($node); } return @attributes; } -- GitLab From 3061e748c363cfac31f73a86713bcce7cbdf1431 Mon Sep 17 00:00:00 2001 From: Soisik Froger Date: Mon, 9 Dec 2019 14:05:36 +0000 Subject: [PATCH 14/31] Manager API - set default values for missing options attributes - #2034 --- .../lib/Lemonldap/NG/Manager/Api/Providers.pm | 36 +++++++++++++++---- 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers.pm index 2c5d793330..d92d14c59e 100644 --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers.pm @@ -3,6 +3,7 @@ our $VERSION = '2.0.7'; package Lemonldap::NG::Manager::Api; +use Lemonldap::NG::Manager::Build::Attributes; use Lemonldap::NG::Manager::Build::CTrees; use Scalar::Util 'weaken'; @@ -46,7 +47,7 @@ sub findOidcRpByConfKey { ); unless (defined $pattern) { - return $self->sendError( $req, 'pattern is missing', 405 ); + return $self->sendError( $req, 'Invalid input: pattern is missing', 405 ); } $self->logger->debug("[API] Find OIDC RPs by confKey regexp $pattern requested"); @@ -82,7 +83,7 @@ sub findOidcRpByClientId { ); unless (defined $clientId) { - return $self->sendError( $req, 'clientId is missing', 405 ); + return $self->sendError( $req, 'Invalid input: clientId is missing', 405 ); } $self->logger->debug("[API] Find OIDC RPs by clientId $clientId requested"); @@ -133,7 +134,7 @@ sub addOidcRp { } $add->{options}->{oidcRPMetaDataOptionsClientID} = $add->{clientId}; - $res = $self->_pushOidcRp($conf, $add->{confKey}, $add, 0); + $res = $self->_pushOidcRp($conf, $add->{confKey}, $add, 1); unless ($res->{res} eq 'ok') { return $self->sendError( $req, $res->{msg}, 405 ); } @@ -289,11 +290,14 @@ sub _pushOidcRp { $conf->{oidcRPMetaDataOptionsExtraClaims}->{$confKey} = {}; } - if (defined $push->{options}) { + if (defined $push->{options} || $replace) { $res = $self->_hasAllowedAttributes($push->{options}, 'oidcRPMetaDataNode'); unless ($res->{res} eq 'ok') { return $res; } + if ($replace) { + $push->{options} = $self->_setDefaultValues($push->{options}, 'oidcRPMetaDataNode'); + } $conf->{oidcRPMetaDataOptions}->{$confKey} = $push->{options}; } @@ -356,7 +360,7 @@ sub findSamlSpByConfKey { ); unless (defined $pattern) { - return $self->sendError( $req, 'pattern is missing', 405 ); + return $self->sendError( $req, 'Invalid input: pattern is missing', 405 ); } $self->logger->debug("[API] Find SAML SPs by confKey regexp $pattern requested"); @@ -444,7 +448,7 @@ sub addSamlSp { return $self->sendError( $req, "Invalid input: A SAML SP with entityID $entityId already exists", 405 ); } - $res = $self->_pushSamlSp($conf, $add->{confKey}, $add, 0); + $res = $self->_pushSamlSp($conf, $add->{confKey}, $add, 1); unless ($res->{res} eq 'ok') { return $self->sendError( $req, $res->{msg}, 405 ); } @@ -666,6 +670,9 @@ sub _pushSamlSp { } $conf->{samlSPMetaDataOptions}->{$confKey} = $push->{options}; } + if ($replace) { + $push->{options} = $self->_setDefaultValues($push->{options}, 'samlSPMetaDataNode'); + } if (defined $push->{exportedAttributes}) { my $res = $self->_readSamlSpExportedAttributes($push->{exportedAttributes}); @@ -705,6 +712,23 @@ sub _isSimpleKeyValueHash { } return 1; } + +sub _setDefaultValues { + my ( $self, $attrs, $rootNode) = @_; + my @allAttrs = $self->_listAttributes($rootNode); + my $defaultAttrs = Lemonldap::NG::Manager::Build::Attributes::attributes(); + + foreach $attr (@allAttrs) { + unless (defined $attrs->{$attr}) { + if (defined $defaultAttrs->{$attr} && defined $defaultAttrs->{$attr}->{default}) { + $attrs->{$attr} = $defaultAttrs->{$attr}->{default}; + } + } + } + + return $attrs; +} + sub _hasAllowedAttributes { my ( $self, $attributes, $rootNode) = @_; -- GitLab From edd262caf8164c51f48136c5314e010c401676c5 Mon Sep 17 00:00:00 2001 From: Soisik Froger Date: Mon, 9 Dec 2019 19:32:54 +0000 Subject: [PATCH 15/31] Manager API - Delete method for SAML SP and OIDC RAP - #2034 --- .../lib/Lemonldap/NG/Common/PSGI/Request.pm | 8 +- .../lib/Lemonldap/NG/Manager/Api.pm | 14 +++ .../lib/Lemonldap/NG/Manager/Api/Providers.pm | 95 ++++++++++++++----- lemonldap-ng-manager/t/04-provider-api.t | 34 +++++++ 4 files changed, 125 insertions(+), 26 deletions(-) create mode 100644 lemonldap-ng-manager/t/04-provider-api.t diff --git a/lemonldap-ng-common/lib/Lemonldap/NG/Common/PSGI/Request.pm b/lemonldap-ng-common/lib/Lemonldap/NG/Common/PSGI/Request.pm index 0f3762f36a..9f85d50ddf 100644 --- a/lemonldap-ng-common/lib/Lemonldap/NG/Common/PSGI/Request.pm +++ b/lemonldap-ng-common/lib/Lemonldap/NG/Common/PSGI/Request.pm @@ -126,18 +126,18 @@ PSGIs =head1 SYNOPSIS package My::PSGI; - + use base Lemonldap::NG::Common::PSGI; - + # See Lemonldap::NG::Common::PSGI ... - + sub handler { my ( $self, $req ) = @_; # Do something and return a PSGI response # NB: $req is a Lemonldap::NG::Common::PSGI::Request object if ( $req->accept eq 'text/plain' ) { ... } - + return [ 200, [ 'Content-Type' => 'text/plain' ], [ 'Body lines' ] ]; } diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api.pm index e0d1f82302..2b46fb8d0c 100644 --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api.pm @@ -103,6 +103,20 @@ sub addRoutes { }, }, ['PATCH'] + ) + + ->addRoute( + v1 => { + providers => { + oidc => { + rp => {':confKey' => 'deleteOidcRp'} + }, + saml => { + sp => {':confKey' => 'deleteSamlSp'} + }, + }, + }, + ['DELETE'] ); } diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers.pm index d92d14c59e..46dbcf64d3 100644 --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers.pm @@ -105,7 +105,7 @@ sub addOidcRp { my $add = $req->jsonBodyToObj; unless ($add) { - return $self->sendError( $req, "Invalid input: Request content could not be parse to json", 404 ); + return $self->sendError( $req, "Invalid input: " . $req->error, 405 ); } unless (defined $add->{confKey}) { @@ -139,7 +139,7 @@ sub addOidcRp { return $self->sendError( $req, $res->{msg}, 405 ); } - return $self->sendJSONresponse($req, "Successful operation"); + return $self->sendJSONresponse($req, { message => "Successful operation" }); } sub updateOidcRp { @@ -151,7 +151,7 @@ sub updateOidcRp { my $update = $req->jsonBodyToObj; unless ($update) { - return $self->sendError( $req, "Invalid input: Request content could not be parse to json", 405 ); + return $self->sendError( $req, "Invalid input: " . $req->error, 405 ); } $self->logger->debug("[API] OIDC RP $confKey configuration update requested"); @@ -177,7 +177,7 @@ sub updateOidcRp { return $self->sendError( $req, $res->{msg}, 405 ); } - return $self->sendJSONresponse($req, "Successful operation"); + return $self->sendJSONresponse($req, { message => "Successful operation" }); } sub replaceOidcRp { @@ -190,7 +190,7 @@ sub replaceOidcRp { my $replace = $req->jsonBodyToObj; unless ($replace) { - return $self->sendError( $req, "Invalid input: Request content could not be parse to json", 405 ); + return $self->sendError( $req, "Invalid input: " . $req->error, 405 ); } unless (defined $replace->{clientId}) { @@ -218,20 +218,33 @@ sub replaceOidcRp { return $self->sendError( $req, $res->{msg}, 405 ); } - return $self->sendJSONresponse($req, "Successful operation"); + return $self->sendJSONresponse($req, { message => "Successful operation" }); } -sub _isNewOidcRpClientIdUnique { - my ( $self, $conf, $confKey, $oidcRp) = @_; +sub deleteOidcRp { + my ( $self, $req ) = @_; - my $curClientId = $self->_getOidcRpByConfKey($conf, $confKey)->{clientId}; - my $newClientId = $oidcRp->{clientId} || $oidcRp->{options}->{oidcRPMetaDataOptionsClientID} || ""; - if ($newClientId ne '' && $newClientId ne $curClientId) { - if ( defined $self->_getOidcRpByClientId($conf, $newClientId) ) { - return { res => 'ko' , msg => "An OIDC relying party with clientId '$newClientId' already exists" }; - } + my $confKey = $req->params('confKey') + or return $self->sendError( $req, 'confKey is missing', 400 ); + + # Get latest configuration + my $conf = $self->_confAcc->getConf; + + my $delete = $self->_getOidcRpByConfKey($conf, $confKey); + + # Return 404 if not found + unless (defined $delete) { + return $self->sendError( $req, "OIDC relying party '$confKey' not found", 404 ); } - return { res => 'ok' }; + + delete $conf->{oidcRPMetaDataOptions}->{$confKey}; + delete $conf->{oidcRPMetaDataExportedVars}->{$confKey}; + delete $conf->{oidcRPMetaDataOptionsExtraClaims}->{$confKey}; + + # Save configuration + $self->_confAcc->saveConf($conf); + + return $self->sendJSONresponse($req, { message => "Successful operation" }); } sub _getOidcRpByConfKey { @@ -280,13 +293,25 @@ sub _getOidcRpByClientId { return undef; } +sub _isNewOidcRpClientIdUnique { + my ( $self, $conf, $confKey, $oidcRp) = @_; + + my $curClientId = $self->_getOidcRpByConfKey($conf, $confKey)->{clientId}; + my $newClientId = $oidcRp->{clientId} || $oidcRp->{options}->{oidcRPMetaDataOptionsClientID} || ""; + if ($newClientId ne '' && $newClientId ne $curClientId) { + if ( defined $self->_getOidcRpByClientId($conf, $newClientId) ) { + return { res => 'ko' , msg => "An OIDC relying party with clientId '$newClientId' already exists" }; + } + } + return { res => 'ok' }; +} + sub _pushOidcRp { my ( $self, $conf, $confKey, $push, $replace) = @_; if ($replace) { $conf->{oidcRPMetaDataOptions}->{$confKey} = {}; $conf->{oidcRPMetaDataExportedVars}->{$confKey} = {}; - $conf->{samlSPMetaDataExportedAttributes}->{$confKey} = {}; $conf->{oidcRPMetaDataOptionsExtraClaims}->{$confKey} = {}; } @@ -418,7 +443,7 @@ sub addSamlSp { my $add = $req->jsonBodyToObj; unless ($add) { - return $self->sendError( $req, "Invalid input: Request content could not be parse to json", 404 ); + return $self->sendError( $req, "Invalid input: " . $req->error, 405 ); } unless (defined $add->{confKey}) { @@ -453,7 +478,7 @@ sub addSamlSp { return $self->sendError( $req, $res->{msg}, 405 ); } - return $self->sendJSONresponse($req, "Successful operation"); + return $self->sendJSONresponse($req, { message => "Successful operation" }); } sub replaceSamlSp { @@ -465,7 +490,7 @@ sub replaceSamlSp { my $replace = $req->jsonBodyToObj; unless ($replace) { - return $self->sendError( $req, "Invalid input: Request content could not be parse to json", 405 ); + return $self->sendError( $req, "Invalid input: " . $req->error, 405 ); } unless (defined $replace->{metadata}) { @@ -493,7 +518,7 @@ sub replaceSamlSp { return $self->sendError( $req, $res->{msg}, 405 ); } - return $self->sendJSONresponse($req, "Successful operation"); + return $self->sendJSONresponse($req, { message => "Successful operation" }); } sub updateSamlSp { @@ -505,7 +530,7 @@ sub updateSamlSp { my $update = $req->jsonBodyToObj; unless ($update) { - return $self->sendError( $req, "Invalid input: Request content could not be parse to json", 405 ); + return $self->sendError( $req, "Invalid input: " . $req->error, 405 ); } $self->logger->debug("[API] SAML SP $confKey configuration update requested"); @@ -533,8 +558,34 @@ sub updateSamlSp { return $self->sendError( $req, $res->{msg}, 405 ); } - return $self->sendJSONresponse($req, "Successful operation"); + return $self->sendJSONresponse($req, { message => "Successful operation" }); + +} + +sub deleteSamlSp { + my ( $self, $req ) = @_; + + my $confKey = $req->params('confKey') + or return $self->sendError( $req, 'confKey is missing', 400 ); + + # Get latest configuration + my $conf = $self->_confAcc->getConf; + + my $delete = $self->_getSamlSpByConfKey($conf, $confKey); + + # Return 404 if not found + unless (defined $delete) { + return $self->sendError( $req, "SAML service provider '$confKey' not found", 404 ); + } + + delete $conf->{samlSPMetaDataXML}->{$confKey}; + delete $conf->{samlSPMetaDataOptions}->{$confKey}; + delete $conf->{samlSPMetaDataExportedAttributes}->{$confKey}; + + # Save configuration + $self->_confAcc->saveConf($conf); + return $self->sendJSONresponse($req, { message => "Successful operation" }); } sub _getSamlSpByConfKey { diff --git a/lemonldap-ng-manager/t/04-provider-api.t b/lemonldap-ng-manager/t/04-provider-api.t new file mode 100644 index 0000000000..b8425cddab --- /dev/null +++ b/lemonldap-ng-manager/t/04-provider-api.t @@ -0,0 +1,34 @@ +# Test RSA key generation + +use Test::More; +use strict; +use JSON; +use IO::String; +require 't/test-lib.pm'; + +my $res; +# ok( +# $res = &client->_get('/api/v1/hello', '') +# , +# "Request succeed" +# ); +# ok( $res->[0] == 200, "Result code is 200" ); +# +# diag Dumper($res); +my $content = '{"confKey":"dsfsdfsdf","clientId":"sdfsdfsdf", "exportedVars": {"titi":"toto","tata":"tutu"}, "extraClaim": {"titi":"toto","tata":"tutu"}, "options":{"oidcRPMetaDataOptionsClientSecret":"secret","oidcRPMetaDataOptionsDisplayName":"tralala","oidcRPMetaDataOptionsIcon":"web.png"}}'; +my $res; +ok( + $res = &client->_post( + '/v1/providers/oidc/rp', '', IO::String->new($content), 'application/json', length($content) + ), + "Request succeed" +); + +use Data::Dumper; print STDERR Dumper($res); + +ok( $res->[0] == 200, "Result code is 200" ); +my $key; +ok( $key = from_json( $res->[2]->[0] ), 'Response is JSON' ); +count(3); + +done_testing( ); -- GitLab From 7c289e72705fab66fc102fda36d77d44fb4f34bb Mon Sep 17 00:00:00 2001 From: Soisik Froger Date: Tue, 17 Dec 2019 08:43:13 +0000 Subject: [PATCH 16/31] Manager API - Added support for requests using PATCH method - #2034 --- .../lib/Lemonldap/NG/Common/PSGI/Cli/Lib.pm | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/lemonldap-ng-common/lib/Lemonldap/NG/Common/PSGI/Cli/Lib.pm b/lemonldap-ng-common/lib/Lemonldap/NG/Common/PSGI/Cli/Lib.pm index 9c37ab8a16..01412974ec 100644 --- a/lemonldap-ng-common/lib/Lemonldap/NG/Common/PSGI/Cli/Lib.pm +++ b/lemonldap-ng-common/lib/Lemonldap/NG/Common/PSGI/Cli/Lib.pm @@ -92,6 +92,35 @@ sub _put { ); } +sub _patch { + my ( $self, $path, $query, $body, $type, $len ) = @_; + die "$body must be a IO::Handle" + unless ( ref($body) and $body->can('read') ); + return $self->app->( { + 'HTTP_ACCEPT' => 'application/json, text/plain, */*', + 'SCRIPT_NAME' => '', + 'HTTP_ACCEPT_ENCODING' => 'gzip, deflate', + 'SERVER_NAME' => '127.0.0.1', + 'QUERY_STRING' => $query, + 'HTTP_CACHE_CONTROL' => 'max-age=0', + 'HTTP_ACCEPT_LANGUAGE' => 'fr,fr-FR;q=0.8,en-US;q=0.5,en;q=0.3', + 'PATH_INFO' => $path, + 'REQUEST_METHOD' => 'PATCH', + 'REQUEST_URI' => $path . ( $query ? "?$query" : '' ), + 'SERVER_PORT' => '8002', + 'SERVER_PROTOCOL' => 'HTTP/1.1', + 'HTTP_USER_AGENT' => + 'Mozilla/5.0 (VAX-4000; rv:36.0) Gecko/20350101 Firefox', + 'REMOTE_ADDR' => '127.0.0.1', + 'HTTP_HOST' => '127.0.0.1:8002', + 'psgix.input.buffered' => 1, + 'psgi.input' => $body, + 'CONTENT_LENGTH' => $len // scalar( ( stat $body )[7] ), + 'CONTENT_TYPE' => $type, + } + ); +} + sub _del { my ( $self, $path, $query ) = @_; return $self->app->( { -- GitLab From bfacf29fd754585870fbf2a02b0cc3261ba9aa31 Mon Sep 17 00:00:00 2001 From: Soisik Froger Date: Tue, 17 Dec 2019 08:56:59 +0000 Subject: [PATCH 17/31] Manager API - Refactoring: common API module extended by providers sub modules oidcRP/samlSP. Added unit tests for oidcRP - #2034 --- lemonldap-ng-manager/MANIFEST | 6 +- .../lib/Lemonldap/NG/Manager/Api.pm | 3 +- .../lib/Lemonldap/NG/Manager/Api/Common.pm | 91 ++ .../lib/Lemonldap/NG/Manager/Api/Providers.pm | 835 ------------------ .../NG/Manager/Api/Providers/OidcRp.pm | 349 ++++++++ .../NG/Manager/Api/Providers/SamlSp.pm | 411 +++++++++ lemonldap-ng-manager/t/04-provider-api.t | 34 - lemonldap-ng-manager/t/04-providers-api.t | 276 ++++++ 8 files changed, 1134 insertions(+), 871 deletions(-) create mode 100644 lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Common.pm delete mode 100644 lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers.pm create mode 100644 lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers/OidcRp.pm create mode 100644 lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers/SamlSp.pm delete mode 100644 lemonldap-ng-manager/t/04-provider-api.t create mode 100644 lemonldap-ng-manager/t/04-providers-api.t diff --git a/lemonldap-ng-manager/MANIFEST b/lemonldap-ng-manager/MANIFEST index 903ca16b81..68d2edd54b 100644 --- a/lemonldap-ng-manager/MANIFEST +++ b/lemonldap-ng-manager/MANIFEST @@ -1,4 +1,5 @@ .bowerrc +_d bower.json Changes eg/manager.cgi @@ -9,7 +10,9 @@ lib/Lemonldap/NG/Manager.pm lib/Lemonldap/NG/Manager/2ndFA.pm lib/Lemonldap/NG/Manager/Api.pm lib/Lemonldap/NG/Manager/Api/2F.pm -lib/Lemonldap/NG/Manager/Api/Providers.pm +lib/Lemonldap/NG/Manager/Api/Common.pm +lib/Lemonldap/NG/Manager/Api/Providers/OidcRp.pm +lib/Lemonldap/NG/Manager/Api/Providers/SamlSp.pm lib/Lemonldap/NG/Manager/Attributes.pm lib/Lemonldap/NG/Manager/Build.pm lib/Lemonldap/NG/Manager/Build/Attributes.pm @@ -218,6 +221,7 @@ site/templates/viewer.tpl t/02-HTML-template.t t/03-HTML-forms.t t/04-hello-api.t +t/04-providers-api.t t/05-rest-api.t t/06-rest-api.t t/07-utf8.t diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api.pm index 2b46fb8d0c..eaad2298b6 100644 --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api.pm @@ -8,7 +8,8 @@ use Mouse; extends 'Lemonldap::NG::Common::Conf::RESTServer'; use Lemonldap::NG::Manager::Api::2F; -use Lemonldap::NG::Manager::Api::Providers; +use Lemonldap::NG::Manager::Api::Providers::OidcRp; +use Lemonldap::NG::Manager::Api::Providers::SamlSp; our $VERSION = '2.0.7'; diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Common.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Common.pm new file mode 100644 index 0000000000..d0b158e23d --- /dev/null +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Common.pm @@ -0,0 +1,91 @@ +package Lemonldap::NG::Manager::Api::Common; +our $VERSION = '2.0.7'; + +package Lemonldap::NG::Manager::Api; + +use Lemonldap::NG::Manager::Build::Attributes; +use Lemonldap::NG::Manager::Build::CTrees; +# use Scalar::Util 'weaken'; ? + +sub _isSimpleKeyValueHash { + my ( $self, $hash) = @_; + if (ref($hash) ne "HASH") { + return 0; + } + foreach (keys %{$hash}) { + if (ref($hash->{$_}) ne '' || ref($_) ne '') { + return 0; + } + } + return 1; +} + +sub _setDefaultValues { + my ( $self, $attrs, $rootNode) = @_; + my @allAttrs = $self->_listAttributes($rootNode); + my $defaultAttrs = Lemonldap::NG::Manager::Build::Attributes::attributes(); + + foreach $attr (@allAttrs) { + unless (defined $attrs->{$attr}) { + if (defined $defaultAttrs->{$attr} && defined $defaultAttrs->{$attr}->{default}) { + $attrs->{$attr} = $defaultAttrs->{$attr}->{default}; + } + } + } + + return $attrs; +} + +sub _hasAllowedAttributes { + my ( $self, $attributes, $rootNode) = @_; + + my @allowedAttributes = $self->_listAttributes($rootNode); + + foreach $attribute (keys %{$attributes}) { + if (length(ref($attribute))) { + return { + res => "ko", + msg => "Invalid input: Attribute $attribute is not a string." + }; + } + unless (grep {/^$attribute$/} @allowedAttributes) { + return { + res => "ko", + msg => "Invalid input: Attribute $attribute does not exist." + }; + } + } + + return { res => "ok" }; +} + +sub _listAttributes { + my ( $self, $rootNode) = @_; + + my $mainTree = Lemonldap::NG::Manager::Build::CTrees::cTrees(); + + my $rootNodes = [ grep { ref($_) eq "HASH" } @{$mainTree->{$rootNode}} ]; + + my @attributes; + foreach $node (@{$rootNodes}) { + push @attributes, $self->_listNodeAttributes($node); + } + return @attributes; +} + +sub _listNodeAttributes { + my ( $self, $node) = @_; + + my @attributes; + foreach $child (@{$node->{nodes}}) { + if (ref($child) eq "HASH"){ + push (@attributes, $self->_listNodeAttributes($child)); + } else { + push (@attributes, $child); + } + } + + return @attributes; +} + +1; diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers.pm deleted file mode 100644 index 46dbcf64d3..0000000000 --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers.pm +++ /dev/null @@ -1,835 +0,0 @@ -package Lemonldap::NG::Manager::Api::Providers; -our $VERSION = '2.0.7'; - -package Lemonldap::NG::Manager::Api; - -use Lemonldap::NG::Manager::Build::Attributes; -use Lemonldap::NG::Manager::Build::CTrees; -use Scalar::Util 'weaken'; - -# TODO check attributes type + first node - -sub getOidcRpByConfKey { - my ( $self, $req ) = @_; - - my $confKey = $req->params('confKey') - or return $self->sendError( $req, 'confKey is missing', 400 ); - - $self->logger->debug("[API] OIDC RP $confKey configuration requested"); - - # Get latest configuration - my $conf = $self->_confAcc->getConf; - - my $oidcRp = $self->_getOidcRpByConfKey($conf, $confKey); - # To save configuration - #$self->_confAcc->saveConf( $conf ) ; - - # Dump object - #use Data::Dumper; print STDERR Dumper($self); - - # Return 404 if not found - unless (defined $oidcRp) { - return $self->sendError( $req, "OIDC relying party '$confKey' not found", 404 ); - } - - return $self->sendJSONresponse( - $req, $oidcRp - ); -} - -sub findOidcRpByConfKey { - my ( $self, $req ) = @_; - - my $pattern = ( - defined $req->params('uPattern') ? - $req->params('uPattern') : - ( defined $req->params('pattern') ? $req->params('pattern') : undef ) - ); - - unless (defined $pattern) { - return $self->sendError( $req, 'Invalid input: pattern is missing', 405 ); - } - - $self->logger->debug("[API] Find OIDC RPs by confKey regexp $pattern requested"); - - # Get latest configuration - my $conf = $self->_confAcc->getConf; - - my @oidcRps; - - foreach ( - keys %{ - $conf->{oidcRPMetaDataOptions} - } - ) - { - if ($_ =~ $pattern) { - push @oidcRps, $self->_getOidcRpByConfKey($conf, $_); - } - } - - return $self->sendJSONresponse( - $req, [ @oidcRps ] - ); -} - -sub findOidcRpByClientId { - my ( $self, $req ) = @_; - - my $clientId = ( - defined $req->params('uClientId') ? - $req->params('uClientId') : - ( defined $req->params('clientId') ? $req->params('clientId') : undef ) - ); - - unless (defined $clientId) { - return $self->sendError( $req, 'Invalid input: clientId is missing', 405 ); - } - - $self->logger->debug("[API] Find OIDC RPs by clientId $clientId requested"); - - # Get latest configuration - my $conf = $self->_confAcc->getConf; - - my $oidcRp = $self->_getOidcRpByClientId($conf, $clientId); - - if (defined $oidcRp) { - return $self->sendJSONresponse($req, $oidcRp); - } - return $self->sendJSONresponse($req, {}); -} - -sub addOidcRp { - my ( $self, $req) = @_; - - my $add = $req->jsonBodyToObj; - - unless ($add) { - return $self->sendError( $req, "Invalid input: " . $req->error, 405 ); - } - - unless (defined $add->{confKey}) { - return $self->sendError( $req, 'Invalid input: confKey is missing', 405 ); - } - - unless (defined $add->{clientId}) { - return $self->sendError( $req, 'Invalid input: clientId is missing', 405 ); - } - - $self->logger->debug("[API] Add OIDC RP with confKey $add->{confKey} and clientId $add->{clientId} requested"); - - # Get latest configuration - my $conf = $self->_confAcc->getConf; - - if (defined $self->_getOidcRpByConfKey($conf, $add->{confKey})) { - return $self->sendError( $req, "Invalid input: An OIDC RP with confKey $add->{confKey} already exists", 405 ); - } - - if (defined $self->_getOidcRpByClientId($conf, $add->{clientId})) { - return $self->sendError( $req, "Invalid input: An OIDC RP with clientId $add->{clientId} already exists", 405 ); - } - - unless (defined $add->{options}) { - $add->{options} = {}; - } - $add->{options}->{oidcRPMetaDataOptionsClientID} = $add->{clientId}; - - $res = $self->_pushOidcRp($conf, $add->{confKey}, $add, 1); - unless ($res->{res} eq 'ok') { - return $self->sendError( $req, $res->{msg}, 405 ); - } - - return $self->sendJSONresponse($req, { message => "Successful operation" }); -} - -sub updateOidcRp { - my ( $self, $req ) = @_; - - my $confKey = $req->params('confKey') - or return $self->sendError( $req, 'confKey is missing', 400 ); - - my $update = $req->jsonBodyToObj; - - unless ($update) { - return $self->sendError( $req, "Invalid input: " . $req->error, 405 ); - } - - $self->logger->debug("[API] OIDC RP $confKey configuration update requested"); - - # Get latest configuration - my $conf = $self->_confAcc->getConf; - - my $current = $self->_getOidcRpByConfKey($conf, $confKey); - - # Return 404 if not found - unless (defined $current) { - return $self->sendError( $req, "OIDC relying party '$confKey' not found", 404 ); - } - - # check if new clientID exists already - my $res = $self->_isNewOidcRpClientIdUnique($conf, $confKey, $update); - unless ($res->{res} eq 'ok') { - return $self->sendError( $req, $res->{msg}, 405 ); - } - - $res = $self->_pushOidcRp($conf, $confKey, $update, 0); - unless ($res->{res} eq 'ok') { - return $self->sendError( $req, $res->{msg}, 405 ); - } - - return $self->sendJSONresponse($req, { message => "Successful operation" }); -} - -sub replaceOidcRp { - - my ( $self, $req ) = @_; - - my $confKey = $req->params('confKey') - or return $self->sendError( $req, 'confKey is missing', 400 ); - - my $replace = $req->jsonBodyToObj; - - unless ($replace) { - return $self->sendError( $req, "Invalid input: " . $req->error, 405 ); - } - - unless (defined $replace->{clientId}) { - return $self->sendError( $req, 'Invalid input: clientId is missing', 405 ); - } - - $self->logger->debug("[API] OIDC RP $confKey configuration replace requested"); - - # Get latest configuration - my $conf = $self->_confAcc->getConf; - - # Return 404 if not found - unless (defined $self->_getOidcRpByConfKey($conf, $confKey)) { - return $self->sendError( $req, "OIDC relying party '$confKey' not found", 404 ); - } - - # check if new clientID exists already - my $res = $self->_isNewOidcRpClientIdUnique($conf, $confKey, $replace); - unless ($res->{res} eq 'ok') { - return $self->sendError( $req, $res->{msg}, 405 ); - } - - $res = $self->_pushOidcRp($conf, $confKey, $replace, 1); - unless ($res->{res} eq 'ok') { - return $self->sendError( $req, $res->{msg}, 405 ); - } - - return $self->sendJSONresponse($req, { message => "Successful operation" }); -} - -sub deleteOidcRp { - my ( $self, $req ) = @_; - - my $confKey = $req->params('confKey') - or return $self->sendError( $req, 'confKey is missing', 400 ); - - # Get latest configuration - my $conf = $self->_confAcc->getConf; - - my $delete = $self->_getOidcRpByConfKey($conf, $confKey); - - # Return 404 if not found - unless (defined $delete) { - return $self->sendError( $req, "OIDC relying party '$confKey' not found", 404 ); - } - - delete $conf->{oidcRPMetaDataOptions}->{$confKey}; - delete $conf->{oidcRPMetaDataExportedVars}->{$confKey}; - delete $conf->{oidcRPMetaDataOptionsExtraClaims}->{$confKey}; - - # Save configuration - $self->_confAcc->saveConf($conf); - - return $self->sendJSONresponse($req, { message => "Successful operation" }); -} - -sub _getOidcRpByConfKey { - my ( $self, $conf, $confKey ) = @_; - - # Check if confKey is defined - unless ( defined $conf->{oidcRPMetaDataOptions}->{$confKey} ) { - return undef; - } - - # Get Client ID - my $clientId = $conf->{oidcRPMetaDataOptions}->{$confKey} - ->{oidcRPMetaDataOptionsClientID}; - - # Get exported vars - my $exportedVars = $conf->{oidcRPMetaDataExportedVars}->{$confKey}; - - # Get extra claim - my $extraClaim = $conf->{oidcRPMetaDataOptionsExtraClaims}->{$confKey}; - - # Get options - my $options = $conf->{oidcRPMetaDataOptions}->{$confKey}; - - return { - confKey => $confKey, - clientId => $clientId, - exportedVars => $exportedVars, - extraClaim => $extraClaim, - options => $options - }; -} - -sub _getOidcRpByClientId { - my ( $self, $conf, $clientId ) = @_; - - foreach ( - keys %{ - $conf->{oidcRPMetaDataOptions} - } - ) - { - if ($conf->{oidcRPMetaDataOptions}->{$_}->{oidcRPMetaDataOptionsClientID} eq $clientId) { - return $self->_getOidcRpByConfKey($conf, $_) - } - } - return undef; -} - -sub _isNewOidcRpClientIdUnique { - my ( $self, $conf, $confKey, $oidcRp) = @_; - - my $curClientId = $self->_getOidcRpByConfKey($conf, $confKey)->{clientId}; - my $newClientId = $oidcRp->{clientId} || $oidcRp->{options}->{oidcRPMetaDataOptionsClientID} || ""; - if ($newClientId ne '' && $newClientId ne $curClientId) { - if ( defined $self->_getOidcRpByClientId($conf, $newClientId) ) { - return { res => 'ko' , msg => "An OIDC relying party with clientId '$newClientId' already exists" }; - } - } - return { res => 'ok' }; -} - -sub _pushOidcRp { - my ( $self, $conf, $confKey, $push, $replace) = @_; - - if ($replace) { - $conf->{oidcRPMetaDataOptions}->{$confKey} = {}; - $conf->{oidcRPMetaDataExportedVars}->{$confKey} = {}; - $conf->{oidcRPMetaDataOptionsExtraClaims}->{$confKey} = {}; - } - - if (defined $push->{options} || $replace) { - $res = $self->_hasAllowedAttributes($push->{options}, 'oidcRPMetaDataNode'); - unless ($res->{res} eq 'ok') { - return $res; - } - if ($replace) { - $push->{options} = $self->_setDefaultValues($push->{options}, 'oidcRPMetaDataNode'); - } - $conf->{oidcRPMetaDataOptions}->{$confKey} = $push->{options}; - } - - if (defined $push->{clientId}) { - $conf->{oidcRPMetaDataOptions}->{$confKey}->{oidcRPMetaDataOptionsClientID} = $push->{clientId}; - } - - if (defined $push->{exportedVars}) { - if ($self->_isSimpleKeyValueHash($push->{exportedVars})) { - $conf->{oidcRPMetaDataExportedVars}->{$confKey} = $push->{exportedVars}; - } else { - return { res => 'ko', msg => "Invalid input: exportedVars is not a hash object with \"key\":\"value\" attributes" }; - } - } - - if (defined $push->{extraClaim}) { - if ($self->_isSimpleKeyValueHash($push->{extraClaim})) { - $conf->{oidcRPMetaDataOptionsExtraClaims}->{$confKey} = $push->{extraClaim}; - } else { - return { res => 'ko', msg => "Invalid input: extraClaim is not a hash object with \"key\":\"value\" attributes" }; - } - } - - # Save configuration - $self->_confAcc->saveConf($conf); - - return { res => 'ok' }; -} - -sub getSamlSpByConfKey { - my ( $self, $req ) = @_; - - my $confKey = $req->params('confKey') - or return $self->sendError( $req, 'confKey is missing', 400 ); - - $self->logger->debug("[API] SAML SP $confKey configuration requested"); - - # Get latest configuration - my $conf = $self->_confAcc->getConf; - - $samlSp = $self->_getSamlSpByConfKey($conf, $confKey); - - # Check if confKey is defined - unless (defined $samlSp) { - return $self->sendError( $req, "SAML service Provider '$confKey' not found", 404 ); - } - - return $self->sendJSONresponse( - $req, $samlSp - ); -} - -sub findSamlSpByConfKey { - my ( $self, $req ) = @_; - - my $pattern = ( - defined $req->params('uPattern') ? - $req->params('uPattern') : - ( defined $req->params('pattern') ? $req->params('pattern') : undef ) - ); - - unless (defined $pattern) { - return $self->sendError( $req, 'Invalid input: pattern is missing', 405 ); - } - - $self->logger->debug("[API] Find SAML SPs by confKey regexp $pattern requested"); - - # Get latest configuration - my $conf = $self->_confAcc->getConf; - - my @samlSps; - - foreach ( - keys %{ - $conf->{samlSPMetaDataXML} - } - ) - { - if ($_ =~ $pattern) { - push @samlSps, $self->_getSamlSpByConfKey($conf, $_); - } - } - - return $self->sendJSONresponse( - $req, [ @samlSps ] - ); -} - -sub findSamlSpByEntityId { - my ( $self, $req ) = @_; - - my $entityId = ( - defined $req->params('uEntityId') ? - $req->params('uEntityId') : - ( defined $req->params('entityId') ? $req->params('entityId') : undef ) - ); - - unless (defined $entityId) { - return $self->sendError( $req, 'entityId is missing', 405 ); - } - - $self->logger->debug("[API] Find SAML SPs by entityId $entityId requested"); - - # Get latest configuration - my $conf = $self->_confAcc->getConf; - - my $samlSp = $self->_getSamlSpByEntityId($conf, $entityId); - - if (defined $samlSp) { - return $self->sendJSONresponse($req, $samlSp); - } - return $self->sendJSONresponse($req, {}); -} - -sub addSamlSp { - my ( $self, $req) = @_; - - my $add = $req->jsonBodyToObj; - - unless ($add) { - return $self->sendError( $req, "Invalid input: " . $req->error, 405 ); - } - - unless (defined $add->{confKey}) { - return $self->sendError( $req, 'Invalid input: confKey is missing', 405 ); - } - - unless (defined $add->{metadata}) { - return $self->sendError( $req, 'Invalid input: metadata is missing', 405 ); - } - - my $entityId = $self->_readSamlSpEntityId($add->{metadata}); - - unless (defined $entityId) { - return $self->sendError( $req, 'Invalid input: entityID is missing in metadata', 405 ); - } - - $self->logger->debug("[API] Add SAML SP with confKey $add->{confKey} and entityID $entityId requested"); - - # Get latest configuration - my $conf = $self->_confAcc->getConf; - - if (defined $self->_getSamlSpByConfKey($conf, $add->{confKey})) { - return $self->sendError( $req, "Invalid input: A SAML SP with confKey $add->{confKey} already exists", 405 ); - } - - if (defined $self->_getSamlSpByEntityId($conf, $entityId)) { - return $self->sendError( $req, "Invalid input: A SAML SP with entityID $entityId already exists", 405 ); - } - - $res = $self->_pushSamlSp($conf, $add->{confKey}, $add, 1); - unless ($res->{res} eq 'ok') { - return $self->sendError( $req, $res->{msg}, 405 ); - } - - return $self->sendJSONresponse($req, { message => "Successful operation" }); -} - -sub replaceSamlSp { - my ( $self, $req ) = @_; - - my $confKey = $req->params('confKey') - or return $self->sendError( $req, 'confKey is missing', 400 ); - - my $replace = $req->jsonBodyToObj; - - unless ($replace) { - return $self->sendError( $req, "Invalid input: " . $req->error, 405 ); - } - - unless (defined $replace->{metadata}) { - return $self->sendError( $req, 'Invalid input: metadata is missing', 405 ); - } - - $self->logger->debug("[API] SAML SP $confKey configuration replace requested"); - - # Get latest configuration - my $conf = $self->_confAcc->getConf; - - # Return 404 if not found - unless (defined $self->_getSamlSpByConfKey($conf, $confKey)) { - return $self->sendError( $req, "SAML service provider '$confKey' not found", 404 ); - } - - # check if new entityId exists already - $res = $self->_isNewSamlSpEntityIdUnique($conf, $confKey, $replace); - unless ($res->{res} eq 'ok') { - return $self->sendError( $req, $res->{msg}, 405 ); - } - - $res = $self->_pushSamlSp($conf, $confKey, $replace, 1); - unless ($res->{res} eq 'ok') { - return $self->sendError( $req, $res->{msg}, 405 ); - } - - return $self->sendJSONresponse($req, { message => "Successful operation" }); -} - -sub updateSamlSp { - my ( $self, $req ) = @_; - - my $confKey = $req->params('confKey') - or return $self->sendError( $req, 'confKey is missing', 400 ); - - my $update = $req->jsonBodyToObj; - - unless ($update) { - return $self->sendError( $req, "Invalid input: " . $req->error, 405 ); - } - - $self->logger->debug("[API] SAML SP $confKey configuration update requested"); - - # Get latest configuration - my $conf = $self->_confAcc->getConf; - - my $current = $self->_getSamlSpByConfKey($conf, $confKey); - - # Return 404 if not found - unless (defined $current) { - return $self->sendError( $req, "SAML service provider '$confKey' not found", 404 ); - } - - if (defined $update->{metadata}) { - # check if new entityId exists already - $res = $self->_isNewSamlSpEntityIdUnique($conf, $confKey, $update); - unless ($res->{res} eq 'ok') { - return $self->sendError( $req, $res->{msg}, 405 ); - } - } - - $res = $self->_pushSamlSp($conf, $confKey, $update, 0); - unless ($res->{res} eq 'ok') { - return $self->sendError( $req, $res->{msg}, 405 ); - } - - return $self->sendJSONresponse($req, { message => "Successful operation" }); - -} - -sub deleteSamlSp { - my ( $self, $req ) = @_; - - my $confKey = $req->params('confKey') - or return $self->sendError( $req, 'confKey is missing', 400 ); - - # Get latest configuration - my $conf = $self->_confAcc->getConf; - - my $delete = $self->_getSamlSpByConfKey($conf, $confKey); - - # Return 404 if not found - unless (defined $delete) { - return $self->sendError( $req, "SAML service provider '$confKey' not found", 404 ); - } - - delete $conf->{samlSPMetaDataXML}->{$confKey}; - delete $conf->{samlSPMetaDataOptions}->{$confKey}; - delete $conf->{samlSPMetaDataExportedAttributes}->{$confKey}; - - # Save configuration - $self->_confAcc->saveConf($conf); - - return $self->sendJSONresponse($req, { message => "Successful operation" }); -} - -sub _getSamlSpByConfKey { - my ( $self, $conf, $confKey ) = @_; - - # Check if confKey is defined - if ( !defined $conf->{samlSPMetaDataXML}->{$confKey} ) { - return undef; - } - - # Get metadata - my $metadata = $conf->{samlSPMetaDataXML}->{$confKey} - ->{samlSPMetaDataXML}; - - # Get options - my $options = $conf->{samlSPMetaDataOptions}->{$confKey}; - - my $samlSp = { - confKey => $confKey, - metadata => $metadata, - exportedAttributes => {}, - options => $options - }; - - # Get exported attributes - foreach ( - keys %{ - $conf->{samlSPMetaDataExportedAttributes} - ->{$confKey} - } - ) - { - # Extract fields from exportedAttr value - my ( $mandatory, $name, $format, $friendly_name ) = - split( /;/, - $conf->{samlSPMetaDataExportedAttributes} - ->{$confKey}->{$_} ); - - $mandatory = !!$mandatory ? 'true' : 'false'; - - $samlSp->{exportedAttributes}->{$_} = { - name => $name, - mandatory => $mandatory - }; - - if (defined $friendly_name && $friendly_name ne '') { - $samlSp->{exportedAttributes}->{$_}->{friendlyName} = $friendly_name; - } - - if (defined $format && $format ne '') { - $samlSp->{exportedAttributes}->{$_}->{format} = $format; - } - } - - - return $samlSp; -} - -sub _getSamlSpByEntityId { - my ( $self, $conf, $entityId ) = @_; - - foreach ( - keys %{ - $conf->{samlSPMetaDataXML} - } - ) - { - if ($self->_readSamlSpEntityId($conf->{samlSPMetaDataXML}->{$_}->{samlSPMetaDataXML}) eq $entityId) { - return $self->_getSamlSpByConfKey($conf, $_); - } - } - return undef; -} - -sub _readSamlSpEntityId { - my ( $self, $metadata ) = @_; - if ($metadata =~ /entityID=['"](.+?)['"]/) { - return $1; - } - return undef; -} -sub _readSamlSpExportedAttributes { - my ( $self, $attrs ) = @_; - my $exportedAttributes; - - my @allowedFormats = [ - "urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified", - "urn:oasis:names:tc:SAML:2.0:attrname-format:uri", - "urn:oasis:names:tc:SAML:2.0:attrname-format:basic" - ]; - foreach (keys %{$attrs}) { - - unless (defined $attrs->{$_}->{name}) { - return { res => "ko", msg => "Exported attribute $_ has no name"}; - } - my $mandatory = 0; - if (defined $attrs->{$_}->{mandatory}) { - if ( $attrs->{$_}->{mandatory} eq '1' or $attrs->{$_}->{mandatory} eq 'true' ) { - $mandatory = 1; - } - } - my $format = ''; - if (defined $attrs->{$_}->{format}) { - unless (length(grep {/^$attrs->{$_}->{format}$/} @allowedFormats)) { - return { res => "ko", msg => "Exported attribute $_ format does not exist."}; - } - $format = $attrs->{$_}->{format}; - } - my $friendlyName = ''; - if (defined $attrs->{$_}->{friendlyName}) { - $friendlyName = $attrs->{$_}->{friendlyName}; - } - $exportedAttributes->{$_} = "$mandatory;$attrs->{$_}->{name};$format;$friendlyName"; - } - return { res => "ok", exportedAttributes => $exportedAttributes }; -} - -sub _pushSamlSp { - my ( $self, $conf, $confKey, $push, $replace) = @_; - - if ($replace) { - $conf->{samlSPMetaDataXML}->{$confKey} = {}; - $conf->{samlSPMetaDataOptions}->{$confKey} = {}; - $conf->{samlSPMetaDataExportedAttributes}->{$confKey} = {}; - } - - $conf->{samlSPMetaDataXML}->{$confKey}->{samlSPMetaDataXML} = $push->{metadata}; - - if (defined $push->{options}) { - my $res = $self->_hasAllowedAttributes($push->{options}, 'samlSPMetaDataNode'); - unless ($res->{res} eq 'ok') { - return $res; - } - $conf->{samlSPMetaDataOptions}->{$confKey} = $push->{options}; - } - if ($replace) { - $push->{options} = $self->_setDefaultValues($push->{options}, 'samlSPMetaDataNode'); - } - - if (defined $push->{exportedAttributes}) { - my $res = $self->_readSamlSpExportedAttributes($push->{exportedAttributes}); - unless ($res->{res} eq 'ok') { - return $res; - } - $conf->{samlSPMetaDataExportedAttributes}->{$confKey} = $res->{exportedAttributes}; - } - - # Save configuration - $self->_confAcc->saveConf($conf); - - return { res => 'ok' }; -} - -sub _isNewSamlSpEntityIdUnique { - my ( $self, $conf, $confKey, $newSp) = @_; - my $newEntityId = $self->_readSamlSpEntityId($newSp->{metadata}); - my $curEntityId = $self->_readSamlSpEntityId($self->_getSamlSpByConfKey($conf, $confKey)->{metadata}); - if ($newEntityId ne $curEntityId) { - if (defined $self->_getSamlSpByEntityId($conf, $newEntityId) ) { - return { res => 'ko', msg => "An SAML service provide with entityId '$newEntityId' already exists" }; - } - } - return { res => 'ok' }; -} - -sub _isSimpleKeyValueHash { - my ( $self, $hash) = @_; - if (ref($hash) ne "HASH") { - return 0; - } - foreach (keys %{$hash}) { - if (ref($hash->{$_}) ne '' || ref($_) ne '') { - return 0; - } - } - return 1; -} - -sub _setDefaultValues { - my ( $self, $attrs, $rootNode) = @_; - my @allAttrs = $self->_listAttributes($rootNode); - my $defaultAttrs = Lemonldap::NG::Manager::Build::Attributes::attributes(); - - foreach $attr (@allAttrs) { - unless (defined $attrs->{$attr}) { - if (defined $defaultAttrs->{$attr} && defined $defaultAttrs->{$attr}->{default}) { - $attrs->{$attr} = $defaultAttrs->{$attr}->{default}; - } - } - } - - return $attrs; -} - -sub _hasAllowedAttributes { - my ( $self, $attributes, $rootNode) = @_; - - my @allowedAttributes = $self->_listAttributes($rootNode); - - foreach $attribute (keys %{$attributes}) { - if (length(ref($attribute))) { - return { - res => "ko", - msg => "Invalid input: Attribute $attribute is not a string." - }; - } - unless (grep {/^$attribute$/} @allowedAttributes) { - return { - res => "ko", - msg => "Invalid input: Attribute $attribute does not exist." - }; - } - } - - return { res => "ok" }; -} - -sub _listAttributes { - my ( $self, $rootNode, $titles) = @_; - - my $mainTree = Lemonldap::NG::Manager::Build::CTrees::cTrees(); - - my $rootNodes = [ grep { ref($_) eq "HASH" } @{$mainTree->{$rootNode}} ]; - - my @attributes; - foreach $node (@{$rootNodes}) { - push @attributes, $self->_listNodeAttributes($node); - } - return @attributes; -} - -sub _listNodeAttributes { - my ( $self, $node) = @_; - - my @attributes; - foreach $child (@{$node->{nodes}}) { - if (ref($child) eq "HASH"){ - push (@attributes, $self->_listNodeAttributes($child)); - } else { - push (@attributes, $child); - } - } - - return @attributes; -} - -1; diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers/OidcRp.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers/OidcRp.pm new file mode 100644 index 0000000000..37d4a44db9 --- /dev/null +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers/OidcRp.pm @@ -0,0 +1,349 @@ +package Lemonldap::NG::Manager::Api::Providers::OidcRp; +our $VERSION = '2.0.7'; + +package Lemonldap::NG::Manager::Api; + +use 5.10.0; +use utf8; +use Mouse; + +extends 'Lemonldap::NG::Manager::Api::Common'; + +sub getOidcRpByConfKey { + my ( $self, $req ) = @_; + + my $confKey = $req->params('confKey') + or return $self->sendError( $req, 'confKey is missing', 400 ); + + $self->logger->debug("[API] OIDC RP $confKey configuration requested"); + + # Get latest configuration + my $conf = $self->_confAcc->getConf; + + my $oidcRp = $self->_getOidcRpByConfKey($conf, $confKey); + + # Return 404 if not found + unless (defined $oidcRp) { + return $self->sendError( $req, "OIDC relying party '$confKey' not found", 404 ); + } + + return $self->sendJSONresponse( + $req, $oidcRp + ); +} + +sub findOidcRpByConfKey { + my ( $self, $req ) = @_; + + my $pattern = ( + defined $req->params('uPattern') ? + $req->params('uPattern') : + ( defined $req->params('pattern') ? $req->params('pattern') : undef ) + ); + + unless (defined $pattern) { + return $self->sendError( $req, 'Invalid input: pattern is missing', 405 ); + } + + $self->logger->debug("[API] Find OIDC RPs by confKey regexp $pattern requested"); + + # Get latest configuration + my $conf = $self->_confAcc->getConf; + + my @oidcRps; + + foreach ( + keys %{ + $conf->{oidcRPMetaDataOptions} + } + ) + { + if ($_ =~ $pattern) { + push @oidcRps, $self->_getOidcRpByConfKey($conf, $_); + } + } + + return $self->sendJSONresponse( + $req, [ @oidcRps ] + ); +} + +sub findOidcRpByClientId { + my ( $self, $req ) = @_; + + my $clientId = ( + defined $req->params('uClientId') ? + $req->params('uClientId') : + ( defined $req->params('clientId') ? $req->params('clientId') : undef ) + ); + + unless (defined $clientId) { + return $self->sendError( $req, 'Invalid input: clientId is missing', 405 ); + } + + $self->logger->debug("[API] Find OIDC RPs by clientId $clientId requested"); + + # Get latest configuration + my $conf = $self->_confAcc->getConf; + + my $oidcRp = $self->_getOidcRpByClientId($conf, $clientId); + + if (defined $oidcRp) { + return $self->sendJSONresponse($req, $oidcRp); + } + return $self->sendJSONresponse($req, {}); +} + +sub addOidcRp { + my ( $self, $req) = @_; + + my $add = $req->jsonBodyToObj; + + unless ($add) { + return $self->sendError( $req, "Invalid input: " . $req->error, 405 ); + } + + unless (defined $add->{confKey}) { + return $self->sendError( $req, 'Invalid input: confKey is missing', 405 ); + } + + unless (defined $add->{clientId}) { + return $self->sendError( $req, 'Invalid input: clientId is missing', 405 ); + } + + $self->logger->debug("[API] Add OIDC RP with confKey $add->{confKey} and clientId $add->{clientId} requested"); + + # Get latest configuration + my $conf = $self->_confAcc->getConf; + + if (defined $self->_getOidcRpByConfKey($conf, $add->{confKey})) { + return $self->sendError( $req, "Invalid input: An OIDC RP with confKey $add->{confKey} already exists", 405 ); + } + + if (defined $self->_getOidcRpByClientId($conf, $add->{clientId})) { + return $self->sendError( $req, "Invalid input: An OIDC RP with clientId $add->{clientId} already exists", 405 ); + } + + unless (defined $add->{options}) { + $add->{options} = {}; + } + $add->{options}->{oidcRPMetaDataOptionsClientID} = $add->{clientId}; + + my $res = $self->_pushOidcRp($conf, $add->{confKey}, $add, 1); + unless ($res->{res} eq 'ok') { + return $self->sendError( $req, $res->{msg}, 405 ); + } + + return $self->sendJSONresponse($req, { message => "Successful operation" }); +} + +sub updateOidcRp { + my ( $self, $req ) = @_; + + my $confKey = $req->params('confKey') + or return $self->sendError( $req, 'confKey is missing', 400 ); + + my $update = $req->jsonBodyToObj; + + unless ($update) { + return $self->sendError( $req, "Invalid input: " . $req->error, 405 ); + } + + $self->logger->debug("[API] OIDC RP $confKey configuration update requested"); + + # Get latest configuration + my $conf = $self->_confAcc->getConf; + + my $current = $self->_getOidcRpByConfKey($conf, $confKey); + + # Return 404 if not found + unless (defined $current) { + return $self->sendError( $req, "OIDC relying party '$confKey' not found", 404 ); + } + + # check if new clientID exists already + my $res = $self->_isNewOidcRpClientIdUnique($conf, $confKey, $update); + unless ($res->{res} eq 'ok') { + return $self->sendError( $req, $res->{msg}, 405 ); + } + + $res = $self->_pushOidcRp($conf, $confKey, $update, 0); + unless ($res->{res} eq 'ok') { + return $self->sendError( $req, $res->{msg}, 405 ); + } + + return $self->sendJSONresponse($req, { message => "Successful operation" }); +} + +sub replaceOidcRp { + + my ( $self, $req ) = @_; + + my $confKey = $req->params('confKey') + or return $self->sendError( $req, 'confKey is missing', 400 ); + + my $replace = $req->jsonBodyToObj; + + unless ($replace) { + return $self->sendError( $req, "Invalid input: " . $req->error, 405 ); + } + + unless (defined $replace->{clientId}) { + return $self->sendError( $req, 'Invalid input: clientId is missing', 405 ); + } + + $self->logger->debug("[API] OIDC RP $confKey configuration replace requested"); + + # Get latest configuration + my $conf = $self->_confAcc->getConf; + + # Return 404 if not found + unless (defined $self->_getOidcRpByConfKey($conf, $confKey)) { + return $self->sendError( $req, "OIDC relying party '$confKey' not found", 404 ); + } + + # check if new clientID exists already + my $res = $self->_isNewOidcRpClientIdUnique($conf, $confKey, $replace); + unless ($res->{res} eq 'ok') { + return $self->sendError( $req, $res->{msg}, 405 ); + } + + $res = $self->_pushOidcRp($conf, $confKey, $replace, 1); + unless ($res->{res} eq 'ok') { + return $self->sendError( $req, $res->{msg}, 405 ); + } + + return $self->sendJSONresponse($req, { message => "Successful operation" }); +} + +sub deleteOidcRp { + my ( $self, $req ) = @_; + + my $confKey = $req->params('confKey') + or return $self->sendError( $req, 'confKey is missing', 400 ); + + # Get latest configuration + my $conf = $self->_confAcc->getConf; + + my $delete = $self->_getOidcRpByConfKey($conf, $confKey); + + # Return 404 if not found + unless (defined $delete) { + return $self->sendError( $req, "OIDC relying party '$confKey' not found", 404 ); + } + + delete $conf->{oidcRPMetaDataOptions}->{$confKey}; + delete $conf->{oidcRPMetaDataExportedVars}->{$confKey}; + delete $conf->{oidcRPMetaDataOptionsExtraClaims}->{$confKey}; + + # Save configuration + $self->_confAcc->saveConf($conf); + + return $self->sendJSONresponse($req, { message => "Successful operation" }); +} + +sub _getOidcRpByConfKey { + my ( $self, $conf, $confKey ) = @_; + + # Check if confKey is defined + unless ( defined $conf->{oidcRPMetaDataOptions}->{$confKey} ) { + return undef; + } + + # Get Client ID + my $clientId = $conf->{oidcRPMetaDataOptions}->{$confKey} + ->{oidcRPMetaDataOptionsClientID}; + + # Get exported vars + my $exportedVars = $conf->{oidcRPMetaDataExportedVars}->{$confKey}; + + # Get extra claim + my $extraClaim = $conf->{oidcRPMetaDataOptionsExtraClaims}->{$confKey}; + + # Get options + my $options = $conf->{oidcRPMetaDataOptions}->{$confKey}; + + return { + confKey => $confKey, + clientId => $clientId, + exportedVars => $exportedVars, + extraClaim => $extraClaim, + options => $options + }; +} + +sub _getOidcRpByClientId { + my ( $self, $conf, $clientId ) = @_; + + foreach ( + keys %{ + $conf->{oidcRPMetaDataOptions} + } + ) + { + if ($conf->{oidcRPMetaDataOptions}->{$_}->{oidcRPMetaDataOptionsClientID} eq $clientId) { + return $self->_getOidcRpByConfKey($conf, $_) + } + } + return undef; +} + +sub _isNewOidcRpClientIdUnique { + my ( $self, $conf, $confKey, $oidcRp) = @_; + my $curClientId = $self->_getOidcRpByConfKey($conf, $confKey)->{clientId}; + my $newClientId = $oidcRp->{clientId} || $oidcRp->{options}->{oidcRPMetaDataOptionsClientID} || ""; + if ($newClientId ne '' && $newClientId ne $curClientId) { + if ( defined $self->_getOidcRpByClientId($conf, $newClientId) ) { + return { res => 'ko' , msg => "An OIDC relying party with clientId '$newClientId' already exists" }; + } + } + return { res => 'ok' }; +} + +sub _pushOidcRp { + my ( $self, $conf, $confKey, $push, $replace) = @_; + + if ($replace) { + $conf->{oidcRPMetaDataOptions}->{$confKey} = {}; + $conf->{oidcRPMetaDataExportedVars}->{$confKey} = {}; + $conf->{oidcRPMetaDataOptionsExtraClaims}->{$confKey} = {}; + } + + if (defined $push->{options} || $replace) { + my $res = $self->_hasAllowedAttributes($push->{options}, 'oidcRPMetaDataNode'); + unless ($res->{res} eq 'ok') { + return $res; + } + if ($replace) { + $push->{options} = $self->_setDefaultValues($push->{options}, 'oidcRPMetaDataNode'); + } + $conf->{oidcRPMetaDataOptions}->{$confKey} = $push->{options}; + } + + if (defined $push->{clientId}) { + $conf->{oidcRPMetaDataOptions}->{$confKey}->{oidcRPMetaDataOptionsClientID} = $push->{clientId}; + } + + if (defined $push->{exportedVars}) { + if ($self->_isSimpleKeyValueHash($push->{exportedVars})) { + $conf->{oidcRPMetaDataExportedVars}->{$confKey} = $push->{exportedVars}; + } else { + return { res => 'ko', msg => "Invalid input: exportedVars is not a hash object with \"key\":\"value\" attributes" }; + } + } + + if (defined $push->{extraClaim}) { + if ($self->_isSimpleKeyValueHash($push->{extraClaim})) { + $conf->{oidcRPMetaDataOptionsExtraClaims}->{$confKey} = $push->{extraClaim}; + } else { + return { res => 'ko', msg => "Invalid input: extraClaim is not a hash object with \"key\":\"value\" attributes" }; + } + } + + # Save configuration + $self->_confAcc->saveConf($conf); + + return { res => 'ok' }; +} + +1; diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers/SamlSp.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers/SamlSp.pm new file mode 100644 index 0000000000..a9e58c9ca5 --- /dev/null +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers/SamlSp.pm @@ -0,0 +1,411 @@ +package Lemonldap::NG::Manager::Api::Providers::SamlSp; +our $VERSION = '2.0.7'; + +package Lemonldap::NG::Manager::Api; + +use 5.10.0; +use utf8; +use Mouse; + +extends 'Lemonldap::NG::Manager::Api::Common'; + +sub getSamlSpByConfKey { + my ( $self, $req ) = @_; + + my $confKey = $req->params('confKey') + or return $self->sendError( $req, 'confKey is missing', 400 ); + + $self->logger->debug("[API] SAML SP $confKey configuration requested"); + + # Get latest configuration + my $conf = $self->_confAcc->getConf; + + my $samlSp = $self->_getSamlSpByConfKey($conf, $confKey); + + # Check if confKey is defined + unless (defined $samlSp) { + return $self->sendError( $req, "SAML service Provider '$confKey' not found", 404 ); + } + + return $self->sendJSONresponse( + $req, $samlSp + ); +} + +sub findSamlSpByConfKey { + my ( $self, $req ) = @_; + + my $pattern = ( + defined $req->params('uPattern') ? + $req->params('uPattern') : + ( defined $req->params('pattern') ? $req->params('pattern') : undef ) + ); + + unless (defined $pattern) { + return $self->sendError( $req, 'Invalid input: pattern is missing', 405 ); + } + + $self->logger->debug("[API] Find SAML SPs by confKey regexp $pattern requested"); + + # Get latest configuration + my $conf = $self->_confAcc->getConf; + + my @samlSps; + + foreach ( + keys %{ + $conf->{samlSPMetaDataXML} + } + ) + { + if ($_ =~ $pattern) { + push @samlSps, $self->_getSamlSpByConfKey($conf, $_); + } + } + + return $self->sendJSONresponse( + $req, [ @samlSps ] + ); +} + +sub findSamlSpByEntityId { + my ( $self, $req ) = @_; + + my $entityId = ( + defined $req->params('uEntityId') ? + $req->params('uEntityId') : + ( defined $req->params('entityId') ? $req->params('entityId') : undef ) + ); + + unless (defined $entityId) { + return $self->sendError( $req, 'entityId is missing', 405 ); + } + + $self->logger->debug("[API] Find SAML SPs by entityId $entityId requested"); + + # Get latest configuration + my $conf = $self->_confAcc->getConf; + + my $samlSp = $self->_getSamlSpByEntityId($conf, $entityId); + + if (defined $samlSp) { + return $self->sendJSONresponse($req, $samlSp); + } + return $self->sendJSONresponse($req, {}); +} + +sub addSamlSp { + my ( $self, $req) = @_; + + my $add = $req->jsonBodyToObj; + + unless ($add) { + return $self->sendError( $req, "Invalid input: " . $req->error, 405 ); + } + + unless (defined $add->{confKey}) { + return $self->sendError( $req, 'Invalid input: confKey is missing', 405 ); + } + + unless (defined $add->{metadata}) { + return $self->sendError( $req, 'Invalid input: metadata is missing', 405 ); + } + + my $entityId = $self->_readSamlSpEntityId($add->{metadata}); + + unless (defined $entityId) { + return $self->sendError( $req, 'Invalid input: entityID is missing in metadata', 405 ); + } + + $self->logger->debug("[API] Add SAML SP with confKey $add->{confKey} and entityID $entityId requested"); + + # Get latest configuration + my $conf = $self->_confAcc->getConf; + + if (defined $self->_getSamlSpByConfKey($conf, $add->{confKey})) { + return $self->sendError( $req, "Invalid input: A SAML SP with confKey $add->{confKey} already exists", 405 ); + } + + if (defined $self->_getSamlSpByEntityId($conf, $entityId)) { + return $self->sendError( $req, "Invalid input: A SAML SP with entityID $entityId already exists", 405 ); + } + + my $res = $self->_pushSamlSp($conf, $add->{confKey}, $add, 1); + unless ($res->{res} eq 'ok') { + return $self->sendError( $req, $res->{msg}, 405 ); + } + + return $self->sendJSONresponse($req, { message => "Successful operation" }); +} + +sub replaceSamlSp { + my ( $self, $req ) = @_; + + my $confKey = $req->params('confKey') + or return $self->sendError( $req, 'confKey is missing', 400 ); + + my $replace = $req->jsonBodyToObj; + + unless ($replace) { + return $self->sendError( $req, "Invalid input: " . $req->error, 405 ); + } + + unless (defined $replace->{metadata}) { + return $self->sendError( $req, 'Invalid input: metadata is missing', 405 ); + } + + $self->logger->debug("[API] SAML SP $confKey configuration replace requested"); + + # Get latest configuration + my $conf = $self->_confAcc->getConf; + + # Return 404 if not found + unless (defined $self->_getSamlSpByConfKey($conf, $confKey)) { + return $self->sendError( $req, "SAML service provider '$confKey' not found", 404 ); + } + + # check if new entityId exists already + my $res = $self->_isNewSamlSpEntityIdUnique($conf, $confKey, $replace); + unless ($res->{res} eq 'ok') { + return $self->sendError( $req, $res->{msg}, 405 ); + } + + $res = $self->_pushSamlSp($conf, $confKey, $replace, 1); + unless ($res->{res} eq 'ok') { + return $self->sendError( $req, $res->{msg}, 405 ); + } + + return $self->sendJSONresponse($req, { message => "Successful operation" }); +} + +sub updateSamlSp { + my ( $self, $req ) = @_; + + my $confKey = $req->params('confKey') + or return $self->sendError( $req, 'confKey is missing', 400 ); + + my $update = $req->jsonBodyToObj; + + unless ($update) { + return $self->sendError( $req, "Invalid input: " . $req->error, 405 ); + } + + $self->logger->debug("[API] SAML SP $confKey configuration update requested"); + + # Get latest configuration + my $conf = $self->_confAcc->getConf; + + my $current = $self->_getSamlSpByConfKey($conf, $confKey); + + # Return 404 if not found + unless (defined $current) { + return $self->sendError( $req, "SAML service provider '$confKey' not found", 404 ); + } + + my $res; + if (defined $update->{metadata}) { + # check if new entityId exists already + $res = $self->_isNewSamlSpEntityIdUnique($conf, $confKey, $update); + unless ($res->{res} eq 'ok') { + return $self->sendError( $req, $res->{msg}, 405 ); + } + } + + $res = $self->_pushSamlSp($conf, $confKey, $update, 0); + unless ($res->{res} eq 'ok') { + return $self->sendError( $req, $res->{msg}, 405 ); + } + + return $self->sendJSONresponse($req, { message => "Successful operation" }); + +} + +sub deleteSamlSp { + my ( $self, $req ) = @_; + + my $confKey = $req->params('confKey') + or return $self->sendError( $req, 'confKey is missing', 400 ); + + # Get latest configuration + my $conf = $self->_confAcc->getConf; + + my $delete = $self->_getSamlSpByConfKey($conf, $confKey); + + # Return 404 if not found + unless (defined $delete) { + return $self->sendError( $req, "SAML service provider '$confKey' not found", 404 ); + } + + delete $conf->{samlSPMetaDataXML}->{$confKey}; + delete $conf->{samlSPMetaDataOptions}->{$confKey}; + delete $conf->{samlSPMetaDataExportedAttributes}->{$confKey}; + + # Save configuration + $self->_confAcc->saveConf($conf); + + return $self->sendJSONresponse($req, { message => "Successful operation" }); +} + +sub _getSamlSpByConfKey { + my ( $self, $conf, $confKey ) = @_; + + # Check if confKey is defined + if ( !defined $conf->{samlSPMetaDataXML}->{$confKey} ) { + return undef; + } + + # Get metadata + my $metadata = $conf->{samlSPMetaDataXML}->{$confKey} + ->{samlSPMetaDataXML}; + + # Get options + my $options = $conf->{samlSPMetaDataOptions}->{$confKey}; + + my $samlSp = { + confKey => $confKey, + metadata => $metadata, + exportedAttributes => {}, + options => $options + }; + + # Get exported attributes + foreach ( + keys %{ + $conf->{samlSPMetaDataExportedAttributes} + ->{$confKey} + } + ) + { + # Extract fields from exportedAttr value + my ( $mandatory, $name, $format, $friendly_name ) = + split( /;/, + $conf->{samlSPMetaDataExportedAttributes} + ->{$confKey}->{$_} ); + + $mandatory = !!$mandatory ? 'true' : 'false'; + + my $samlSp->{exportedAttributes}->{$_} = { + name => $name, + mandatory => $mandatory + }; + + if (defined $friendly_name && $friendly_name ne '') { + $samlSp->{exportedAttributes}->{$_}->{friendlyName} = $friendly_name; + } + + if (defined $format && $format ne '') { + $samlSp->{exportedAttributes}->{$_}->{format} = $format; + } + } + + + return $samlSp; +} + +sub _getSamlSpByEntityId { + my ( $self, $conf, $entityId ) = @_; + + foreach ( + keys %{ + $conf->{samlSPMetaDataXML} + } + ) + { + if ($self->_readSamlSpEntityId($conf->{samlSPMetaDataXML}->{$_}->{samlSPMetaDataXML}) eq $entityId) { + return $self->_getSamlSpByConfKey($conf, $_); + } + } + return undef; +} + +sub _readSamlSpEntityId { + my ( $self, $metadata ) = @_; + if ($metadata =~ /entityID=['"](.+?)['"]/) { + return $1; + } + return undef; +} +sub _readSamlSpExportedAttributes { + my ( $self, $attrs ) = @_; + my $allowedFormats = [ + "urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified", + "urn:oasis:names:tc:SAML:2.0:attrname-format:uri", + "urn:oasis:names:tc:SAML:2.0:attrname-format:basic" + ]; + my $exportedAttributes; + foreach (keys %{$attrs}) { + + unless (defined $attrs->{$_}->{name}) { + return { res => "ko", msg => "Exported attribute $_ has no name"}; + } + my $mandatory = 0; + if (defined $attrs->{$_}->{mandatory}) { + if ( $attrs->{$_}->{mandatory} eq '1' or $attrs->{$_}->{mandatory} eq 'true' ) { + $mandatory = 1; + } + } + my $format = ''; + if (defined $attrs->{$_}->{format}) { + $format = $attrs->{$_}->{format}; + unless (length(grep {/^$format$/} @{$allowedFormats})) { + return { res => "ko", msg => "Exported attribute $_ format does not exist."}; + } + } + my $friendlyName = ''; + if (defined $attrs->{$_}->{friendlyName}) { + $friendlyName = $attrs->{$_}->{friendlyName}; + } + $exportedAttributes->{$_} = "$mandatory;$attrs->{$_}->{name};$format;$friendlyName"; + } + return { res => "ok", exportedAttributes => $exportedAttributes }; +} + +sub _pushSamlSp { + my ( $self, $conf, $confKey, $push, $replace) = @_; + + if ($replace) { + $conf->{samlSPMetaDataXML}->{$confKey} = {}; + $conf->{samlSPMetaDataOptions}->{$confKey} = {}; + $conf->{samlSPMetaDataExportedAttributes}->{$confKey} = {}; + } + + $conf->{samlSPMetaDataXML}->{$confKey}->{samlSPMetaDataXML} = $push->{metadata}; + + if (defined $push->{options}) { + my $res = $self->_hasAllowedAttributes($push->{options}, 'samlSPMetaDataNode'); + unless ($res->{res} eq 'ok') { + return $res; + } + $conf->{samlSPMetaDataOptions}->{$confKey} = $push->{options}; + } + if ($replace) { + $push->{options} = $self->_setDefaultValues($push->{options}, 'samlSPMetaDataNode'); + } + + if (defined $push->{exportedAttributes}) { + my $res = $self->_readSamlSpExportedAttributes($push->{exportedAttributes}); + unless ($res->{res} eq 'ok') { + return $res; + } + $conf->{samlSPMetaDataExportedAttributes}->{$confKey} = $res->{exportedAttributes}; + } + + # Save configuration + $self->_confAcc->saveConf($conf); + + return { res => 'ok' }; +} + +sub _isNewSamlSpEntityIdUnique { + my ( $self, $conf, $confKey, $newSp) = @_; + my $newEntityId = $self->_readSamlSpEntityId($newSp->{metadata}); + my $curEntityId = $self->_readSamlSpEntityId($self->_getSamlSpByConfKey($conf, $confKey)->{metadata}); + if ($newEntityId ne $curEntityId) { + if (defined $self->_getSamlSpByEntityId($conf, $newEntityId) ) { + return { res => 'ko', msg => "An SAML service provide with entityId '$newEntityId' already exists" }; + } + } + return { res => 'ok' }; +} + +1; diff --git a/lemonldap-ng-manager/t/04-provider-api.t b/lemonldap-ng-manager/t/04-provider-api.t deleted file mode 100644 index b8425cddab..0000000000 --- a/lemonldap-ng-manager/t/04-provider-api.t +++ /dev/null @@ -1,34 +0,0 @@ -# Test RSA key generation - -use Test::More; -use strict; -use JSON; -use IO::String; -require 't/test-lib.pm'; - -my $res; -# ok( -# $res = &client->_get('/api/v1/hello', '') -# , -# "Request succeed" -# ); -# ok( $res->[0] == 200, "Result code is 200" ); -# -# diag Dumper($res); -my $content = '{"confKey":"dsfsdfsdf","clientId":"sdfsdfsdf", "exportedVars": {"titi":"toto","tata":"tutu"}, "extraClaim": {"titi":"toto","tata":"tutu"}, "options":{"oidcRPMetaDataOptionsClientSecret":"secret","oidcRPMetaDataOptionsDisplayName":"tralala","oidcRPMetaDataOptionsIcon":"web.png"}}'; -my $res; -ok( - $res = &client->_post( - '/v1/providers/oidc/rp', '', IO::String->new($content), 'application/json', length($content) - ), - "Request succeed" -); - -use Data::Dumper; print STDERR Dumper($res); - -ok( $res->[0] == 200, "Result code is 200" ); -my $key; -ok( $key = from_json( $res->[2]->[0] ), 'Response is JSON' ); -count(3); - -done_testing( ); diff --git a/lemonldap-ng-manager/t/04-providers-api.t b/lemonldap-ng-manager/t/04-providers-api.t new file mode 100644 index 0000000000..b94670bc6d --- /dev/null +++ b/lemonldap-ng-manager/t/04-providers-api.t @@ -0,0 +1,276 @@ +# Test RSA key generation + +use Test::More; +use strict; +use JSON; +use IO::String; +require 't/test-lib.pm'; + +our $_json = JSON->new->allow_nonref; + +sub check200 { + my ( $test, $res ) = splice @_; + #diag Dumper($res); + ok( $res->[0] == 200, "$test: Result code is 200" ); + count(1); + checkJson($test, $res); + +} +sub check404 { + my ( $test, $res ) = splice @_; + #diag Dumper($res); + ok( $res->[0] == 404, "$test: Result code is 404" ); + count(1); + checkJson($test, $res); +} +sub check405 { + my ( $test, $res ) = splice @_; + ok( $res->[0] == 405, "$test: Result code is 405" ); + count(1); + checkJson($test, $res); +} +sub checkJson { + my ( $test, $res ) = splice @_; + my $key; + #diag Dumper($res->[2]->[0]); + ok( $key = from_json( $res->[2]->[0] ), "$test: Response is JSON" ); + count(1); +} + +sub add { + my ( $test, $type, $obj) = splice @_; + my $j = $_json->encode($obj); + my $res; + #diag Dumper($j); + ok( + $res = &client->_post( + "/v1/providers/$type", '', IO::String->new($j), 'application/json', length($j) + ), + "$test: Request succeed" + ); + count(1); + return $res; +} + +sub checkAdd { + my ( $test, $type, $add) = splice @_; + check200($test, add($test, $type, $add)); +} + +sub checkAddFailsIfExists { + my ( $test, $type, $add) = splice @_; + check405($test, add($test, $type, $add)); +} + +sub checkAddWithUnknownAttributes { + my ( $test, $type, $add) = splice @_; + check405($test, add($test, $type, $add)); +} + +sub get { + my ( $test, $type, $confKey) = splice @_; + my $res; + ok( + $res = &client->_get( + "/v1/providers/$type/$confKey", '', '', 'application/json', 0 + ), + "$test: Request succeed" + ); + count(1); + return $res; +} + +sub checkGet { + my ( $test, $type, $confKey, $attribute, $expectedValue) = splice @_; + my $res = get($test, $type, $confKey); + my $key = from_json($res->[2]->[0]); + check200($test, $res); + ok ( + $key->{options}->{$attribute} eq $expectedValue, + "$test: check if $attribute eq $expectedValue" + ); + count(1); +} + +sub checkGetNotFound { + my ( $test, $type, $confKey) = splice @_; + check404($test, get($test, $type, $confKey)); +} + +sub update { + my ( $test, $type, $confKey, $obj) = splice @_; + my $j = $_json->encode($obj); + #diag Dumper($j); + my $res; + ok( + $res = &client->_patch( + "/v1/providers/$type/$confKey", '', IO::String->new($j), 'application/json', length($j) + ), + "$test: Request succeed" + ); + count(1); + return $res; +} + +sub checkUpdate { + my ( $test, $type, $confKey, $update) = splice @_; + check200($test, update($test, $type, $confKey, $update)); +} + +sub checkUpdateNotFound { + my ( $test, $type, $confKey, $update) = splice @_; + check404($test, update($test, $type, $confKey, $update)); +} + +sub checkUpdateFailsIfExists { + my ( $test, $type, $confKey, $update) = splice @_; + check405($test, update($test, $type, $confKey, $update)); +} + +sub checkUpdateWithUnknownAttributes { + my ( $test, $type, $confKey, $update) = splice @_; + check405($test, update($test, $type, $confKey, $update)); +} + +sub replace { + my ( $test, $type, $confKey, $obj) = splice @_; + my $j = $_json->encode($obj); + my $res; + ok( + $res = &client->_put( + "/v1/providers/$type/$confKey", '', IO::String->new($j), 'application/json', length($j) + ), + "$test: Request succeed" + ); + count(1); + return $res; +} + +sub checkReplace { + my ( $test, $type, $confKey, $replace) = splice @_; + check200($test, replace($test, $type, $confKey, $replace)); +} + +sub checkReplaceAlreadyThere { + my ( $test, $type, $confKey, $replace) = splice @_; + check405($test, replace($test, $type, $confKey, $replace)); +} + +sub checkReplaceNotFound { + my ( $test, $type, $confKey, $update) = splice @_; + check404($test, replace($test, $type, $confKey, $update)); +} + +sub checkReplaceWithUnknownAttribute { + my ( $test, $type, $confKey, $replace) = splice @_; + check405($test, replace($test, $type, $confKey, $replace)); +} + +# TODO +sub checkFindByConfKey {} + +sub checkFindByConfKeyNoHits {} + +sub checkFindByProviderId {} + +sub checkFindByProviderId404 {} + +sub deleteProvider { + my ( $test, $type, $confKey) = splice @_; + my $res; + ok( + $res = &client->_del( + "/v1/providers/$type/$confKey", '', '', 'application/json', 0 + ), + "Request succeed" + ); + count(1); + return $res; +} +sub checkDelete { + my ( $test, $type, $confKey) = splice @_; + check200($test, deleteProvider($test, $type, $confKey)); +} + +sub checkDeleteNotFound { + my ( $test, $type, $confKey) = splice @_; + check404($test, deleteProvider($test, $type, $confKey)); +} + +my $test; +my $oidcRp = { + confKey => 'newOidcRp', + clientId => 'newOidcRpClientId', + exportedVars => { + 'sub' => "uid", + family_name => "sn", + given_name => "givenName" + }, + extraClaim => { + phone => 'telephoneNumber', + email => 'mail' + }, + options => { + oidcRPMetaDataOptionsClientSecret => 'secret', + oidcRPMetaDataOptionsIcon => 'web.png' + } +}; + +# $test="Delete after crash"; +# checkDelete($test, '/oidc/rp', 'newOidcRp'); + +$test = "Add should succeed"; +checkAdd($test, '/oidc/rp', $oidcRp); +checkGet($test, '/oidc/rp', 'newOidcRp', 'oidcRPMetaDataOptionsIcon', 'web.png'); +checkGet($test, '/oidc/rp', 'newOidcRp', 'oidcRPMetaDataOptionsClientSecret', 'secret'); + +$test = "Check attribute default value was set after add"; +checkGet($test, '/oidc/rp', 'newOidcRp', 'oidcRPMetaDataOptionsIDTokenSignAlg', 'HS512'); + +$test = "Add Should fail on duplicate confKey"; +checkAddFailsIfExists($test, '/oidc/rp', $oidcRp); + +$test = "Update should succeed"; +$oidcRp->{options}->{oidcRPMetaDataOptionsClientSecret} = 'secret2'; +checkUpdate($test, '/oidc/rp', 'newOidcRp', $oidcRp); +checkGet($test, '/oidc/rp', 'newOidcRp', 'oidcRPMetaDataOptionsClientSecret', 'secret2'); + +$test = "Update should fail on non existing options"; +$oidcRp->{options}->{playingPossum} = 'elephant'; +checkUpdateWithUnknownAttributes($test, '/oidc/rp', 'newOidcRp', $oidcRp); +delete $oidcRp->{options}->{playingPossum}; + +$test = "Add Should fail on duplicate clientId"; +$oidcRp->{confKey} = 'newOidcRp2'; +checkAddFailsIfExists($test, '/oidc/rp', $oidcRp); + +$test = "Add Should fail on non existing options"; +$oidcRp->{confKey} = 'newOidcRp2'; +$oidcRp->{clientId} = 'newOidcRpClientId2'; +$oidcRp->{options}->{playingPossum} = 'ElephantInTheRoom'; +checkAddWithUnknownAttributes($test, '/oidc/rp', $oidcRp); +delete $oidcRp->{options}->{playingPossum}; + +$test = "2nd add should succeed"; +checkAdd($test, '/oidc/rp', $oidcRp); + +$test = "Update should fail if client id exists"; +$oidcRp->{clientId} = 'newOidcRpClientId'; +checkUpdateFailsIfExists($test, '/oidc/rp', 'newOidcRp2', $oidcRp); + +$test = "Update should fail if confKey not found"; +$oidcRp->{confKey} = 'EasyAsPie'; +checkUpdateNotFound($test, '/oidc/rp', 'EasyAsPie', $oidcRp); + + +# TODO oidcRP checkReplace checkReplaceWithUnknownAttribute checkReplaceNotFound checkReplaceWithUnknownAttribute + +$test = "Clean up"; +checkDelete($test, '/oidc/rp', 'newOidcRp'); +checkDelete($test, '/oidc/rp', 'newOidcRp2'); +$test = "Entity should not be found after clean up"; +checkDeleteNotFound($test, '/oidc/rp', 'newOidcRp'); + +# TODO samlSP + +done_testing( ); -- GitLab From 63c8e0b848b02861eb8052ac621ad7275d22e0ec Mon Sep 17 00:00:00 2001 From: Soisik Froger Date: Tue, 17 Dec 2019 09:51:19 +0000 Subject: [PATCH 18/31] Manager API - Fixed unit tests - #2034 --- lemonldap-ng-manager/t/04-providers-api.t | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lemonldap-ng-manager/t/04-providers-api.t b/lemonldap-ng-manager/t/04-providers-api.t index b94670bc6d..c3055de08f 100644 --- a/lemonldap-ng-manager/t/04-providers-api.t +++ b/lemonldap-ng-manager/t/04-providers-api.t @@ -273,4 +273,7 @@ checkDeleteNotFound($test, '/oidc/rp', 'newOidcRp'); # TODO samlSP +# Clean up generated conf files, except for "lmConf-1.json" +unlink grep { $_ ne "t/conf/lmConf-1.json" } glob "t/conf/lmConf-*.json"; + done_testing( ); -- GitLab From f107356e5e673a4df8718fecd582408e57029383 Mon Sep 17 00:00:00 2001 From: Soisik Froger Date: Tue, 17 Dec 2019 13:35:38 +0000 Subject: [PATCH 19/31] Manager API - Added unit tests for oidcRP findByConfKey/findByClientId - #2034 --- lemonldap-ng-manager/t/04-providers-api.t | 162 +++++++++++++++++----- 1 file changed, 125 insertions(+), 37 deletions(-) diff --git a/lemonldap-ng-manager/t/04-providers-api.t b/lemonldap-ng-manager/t/04-providers-api.t index c3055de08f..0c56865788 100644 --- a/lemonldap-ng-manager/t/04-providers-api.t +++ b/lemonldap-ng-manager/t/04-providers-api.t @@ -18,7 +18,7 @@ sub check200 { } sub check404 { my ( $test, $res ) = splice @_; - #diag Dumper($res); + diag Dumper($res); ok( $res->[0] == 404, "$test: Result code is 404" ); count(1); checkJson($test, $res); @@ -71,9 +71,7 @@ sub get { my ( $test, $type, $confKey) = splice @_; my $res; ok( - $res = &client->_get( - "/v1/providers/$type/$confKey", '', '', 'application/json', 0 - ), + $res = &client->_get("/v1/providers/$type/$confKey", ''), "$test: Request succeed" ); count(1); @@ -166,14 +164,74 @@ sub checkReplaceWithUnknownAttribute { check405($test, replace($test, $type, $confKey, $replace)); } -# TODO -sub checkFindByConfKey {} +sub findByConfKey { + my ( $test, $type, $confKey) = splice @_; + my $res; + ok( + $res = &client->_get("/v1/providers/$type/findByConfKey", "pattern=$confKey"), + "$test: Request succeed" + ); + count(1); + return $res; +} -sub checkFindByConfKeyNoHits {} +sub checkFindByConfKey { + my ( $test, $type, $confKey, $expectedHits, $attribute, $expectedValues) = splice @_; + my $res = findByConfKey($test, $type, $confKey); + check200($test, $res); + my $hits = from_json($res->[2]->[0]); + my $hit; + my $counter = 0; + foreach $hit (@{$hits}) { + my $expected = grep {/^$hit->{options}->{$attribute}$/} @{$expectedValues}; + $counter++; + ok ( + $expected, + "$test: check if $attribute value \"$hit->{options}->{$attribute}\" matches one of expectedValues: " . $_json->encode($expectedValues) + ); + count(1); + } + ok ( + $counter eq $expectedHits, + "$test: check if returned nb of hits ($counter) matches $expectedHits" + ); + count(1); +} + +sub findByProviderId { + my ( $test, $type, $providerIdName, $providerId) = splice @_; + my $res; + ok( + $res = &client->_get("/v1/providers/$type/findBy" . ucfirst $providerIdName, "$providerIdName=$providerId"), + "$test: Request succeed" + ); + count(1); + return $res; +} -sub checkFindByProviderId {} +sub checkFindByProviderId { + my ( $test, $type, $providerIdName, $providerId) = splice @_; + my $res = findByProviderId($test, $type, $providerIdName, $providerId); + check200($test, $res); + my $result = from_json($res->[2]->[0]); + ok( + $result->{$providerIdName} eq $providerId, + "$test: Check $providerIdName value returned \"$result->{$providerIdName}\" matched expected value \"$providerId\"" + ); + count(1); +} -sub checkFindByProviderId404 {} +sub checkFindByProviderIdNotFound { + my ( $test, $type, $providerIdName, $providerId) = splice @_; + my $res = findByProviderId($test, $type, $providerIdName, $providerId); + check200($test, $res); + my $result = from_json($res->[2]->[0]); + ok( + !defined $result->{$providerIdName}, + "$test: Check object is empty" + ); + count(1); +} sub deleteProvider { my ( $test, $type, $confKey) = splice @_; @@ -198,9 +256,10 @@ sub checkDeleteNotFound { } my $test; + my $oidcRp = { - confKey => 'newOidcRp', - clientId => 'newOidcRpClientId', + confKey => 'myOidcRp1', + clientId => 'myOidcClient1', exportedVars => { 'sub' => "uid", family_name => "sn", @@ -216,62 +275,91 @@ my $oidcRp = { } }; -# $test="Delete after crash"; -# checkDelete($test, '/oidc/rp', 'newOidcRp'); - $test = "Add should succeed"; -checkAdd($test, '/oidc/rp', $oidcRp); -checkGet($test, '/oidc/rp', 'newOidcRp', 'oidcRPMetaDataOptionsIcon', 'web.png'); -checkGet($test, '/oidc/rp', 'newOidcRp', 'oidcRPMetaDataOptionsClientSecret', 'secret'); +checkAdd($test, 'oidc/rp', $oidcRp); +checkGet($test, 'oidc/rp', 'myOidcRp1', 'oidcRPMetaDataOptionsIcon', 'web.png'); +checkGet($test, 'oidc/rp', 'myOidcRp1', 'oidcRPMetaDataOptionsClientSecret', 'secret'); $test = "Check attribute default value was set after add"; -checkGet($test, '/oidc/rp', 'newOidcRp', 'oidcRPMetaDataOptionsIDTokenSignAlg', 'HS512'); +checkGet($test, 'oidc/rp', 'myOidcRp1', 'oidcRPMetaDataOptionsIDTokenSignAlg', 'HS512'); $test = "Add Should fail on duplicate confKey"; -checkAddFailsIfExists($test, '/oidc/rp', $oidcRp); +checkAddFailsIfExists($test, 'oidc/rp', $oidcRp); $test = "Update should succeed"; $oidcRp->{options}->{oidcRPMetaDataOptionsClientSecret} = 'secret2'; -checkUpdate($test, '/oidc/rp', 'newOidcRp', $oidcRp); -checkGet($test, '/oidc/rp', 'newOidcRp', 'oidcRPMetaDataOptionsClientSecret', 'secret2'); +$oidcRp->{options}->{oidcRPMetaDataOptionsIDTokenSignAlg} = 'RS512'; +checkUpdate($test, 'oidc/rp', 'myOidcRp1', $oidcRp); +checkGet($test, 'oidc/rp', 'myOidcRp1', 'oidcRPMetaDataOptionsClientSecret', 'secret2'); +checkGet($test, 'oidc/rp', 'myOidcRp1', 'oidcRPMetaDataOptionsIDTokenSignAlg', 'RS512'); $test = "Update should fail on non existing options"; $oidcRp->{options}->{playingPossum} = 'elephant'; -checkUpdateWithUnknownAttributes($test, '/oidc/rp', 'newOidcRp', $oidcRp); +checkUpdateWithUnknownAttributes($test, 'oidc/rp', 'myOidcRp1', $oidcRp); delete $oidcRp->{options}->{playingPossum}; $test = "Add Should fail on duplicate clientId"; -$oidcRp->{confKey} = 'newOidcRp2'; -checkAddFailsIfExists($test, '/oidc/rp', $oidcRp); +$oidcRp->{confKey} = 'myOidcRp2'; +checkAddFailsIfExists($test, 'oidc/rp', $oidcRp); $test = "Add Should fail on non existing options"; -$oidcRp->{confKey} = 'newOidcRp2'; -$oidcRp->{clientId} = 'newOidcRpClientId2'; +$oidcRp->{confKey} = 'myOidcRp2'; +$oidcRp->{clientId} = 'myOidcClient2'; $oidcRp->{options}->{playingPossum} = 'ElephantInTheRoom'; -checkAddWithUnknownAttributes($test, '/oidc/rp', $oidcRp); +checkAddWithUnknownAttributes($test, 'oidc/rp', $oidcRp); delete $oidcRp->{options}->{playingPossum}; $test = "2nd add should succeed"; -checkAdd($test, '/oidc/rp', $oidcRp); +checkAdd($test, 'oidc/rp', $oidcRp); $test = "Update should fail if client id exists"; -$oidcRp->{clientId} = 'newOidcRpClientId'; -checkUpdateFailsIfExists($test, '/oidc/rp', 'newOidcRp2', $oidcRp); +$oidcRp->{clientId} = 'myOidcClient1'; +checkUpdateFailsIfExists($test, 'oidc/rp', 'myOidcRp2', $oidcRp); $test = "Update should fail if confKey not found"; -$oidcRp->{confKey} = 'EasyAsPie'; -checkUpdateNotFound($test, '/oidc/rp', 'EasyAsPie', $oidcRp); +$oidcRp->{confKey} = 'myOidcRp3'; +checkUpdateNotFound($test, 'oidc/rp', 'myOidcRp3', $oidcRp); + +$test = "Replace should succeed"; +$oidcRp->{confKey} = 'myOidcRp2'; +$oidcRp->{clientId} = 'myOidcClient2'; +delete $oidcRp->{options}->{oidcRPMetaDataOptionsIcon}; +delete $oidcRp->{options}->{oidcRPMetaDataOptionsIDTokenSignAlg}; +checkReplace($test, 'oidc/rp', 'myOidcRp2', $oidcRp); + +$test = "Check attribute default value was set after replace"; +checkGet($test, 'oidc/rp', 'myOidcRp2', 'oidcRPMetaDataOptionsIDTokenSignAlg', 'HS512'); + +$test = "Replace should fail on non existing options"; +$oidcRp->{options}->{playingPossum} = 'elephant'; +checkReplaceWithUnknownAttribute($test, 'oidc/rp', 'myOidcRp2', $oidcRp); +delete $oidcRp->{options}->{playingPossum}; + +$test = "Replace should fail if confKey not found"; +$oidcRp->{confKey} = 'myOidcRp3'; +checkReplaceNotFound($test, 'oidc/rp', 'myOidcRp3', $oidcRp); + +$test = "FindByConfKey should find 2 hits"; +checkFindByConfKey($test, 'oidc/rp', '^myOidcRp.$', 2, 'oidcRPMetaDataOptionsClientID', ['myOidcClient1','myOidcClient2']); + +$test = "FindByConfKey should find 1 hit"; +checkFindByConfKey($test, 'oidc/rp', 'myOidcRp1', 1, 'oidcRPMetaDataOptionsClientID', ['myOidcClient1']); + +$test = "FindByConfKey should find 0 hits"; +checkFindByConfKey($test, 'oidc/rp', 'myOidcRp3', 0); +$test = "FindByClientId should find one entry"; +checkFindByProviderId($test, 'oidc/rp', 'clientId', 'myOidcClient1'); -# TODO oidcRP checkReplace checkReplaceWithUnknownAttribute checkReplaceNotFound checkReplaceWithUnknownAttribute +$test = "FindByClientId should find nothing"; +checkFindByProviderIdNotFound($test, 'oidc/rp', 'clientId', 'myOidcClient3'); $test = "Clean up"; -checkDelete($test, '/oidc/rp', 'newOidcRp'); -checkDelete($test, '/oidc/rp', 'newOidcRp2'); +checkDelete($test, 'oidc/rp', 'myOidcRp1'); +checkDelete($test, 'oidc/rp', 'myOidcRp2'); $test = "Entity should not be found after clean up"; -checkDeleteNotFound($test, '/oidc/rp', 'newOidcRp'); +checkDeleteNotFound($test, 'oidc/rp', 'myOidcRp1'); -# TODO samlSP # Clean up generated conf files, except for "lmConf-1.json" unlink grep { $_ ne "t/conf/lmConf-1.json" } glob "t/conf/lmConf-*.json"; -- GitLab From 4c5948623ccd8899f78eee87dddeb6bb39488948 Mon Sep 17 00:00:00 2001 From: Soisik Froger Date: Tue, 17 Dec 2019 17:43:29 +0000 Subject: [PATCH 20/31] Manager API - samlSP unit tests and ajustments for update/patch to keep old values - #2034 --- .../NG/Manager/Api/Providers/OidcRp.pm | 19 +- .../NG/Manager/Api/Providers/SamlSp.pm | 34 ++- lemonldap-ng-manager/t/04-providers-api.t | 241 ++++++++++++++---- 3 files changed, 231 insertions(+), 63 deletions(-) diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers/OidcRp.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers/OidcRp.pm index 37d4a44db9..d6bed7ae30 100644 --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers/OidcRp.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers/OidcRp.pm @@ -22,6 +22,9 @@ sub getOidcRpByConfKey { my $oidcRp = $self->_getOidcRpByConfKey($conf, $confKey); + # $self->logger->debug("$oidcRp :: "); + # use Data::Dumper; print STDERR Dumper($oidcRp); + # Return 404 if not found unless (defined $oidcRp) { return $self->sendError( $req, "OIDC relying party '$confKey' not found", 404 ); @@ -307,17 +310,17 @@ sub _pushOidcRp { $conf->{oidcRPMetaDataOptions}->{$confKey} = {}; $conf->{oidcRPMetaDataExportedVars}->{$confKey} = {}; $conf->{oidcRPMetaDataOptionsExtraClaims}->{$confKey} = {}; + $push->{options} = $self->_setDefaultValues($push->{options}, 'oidcRPMetaDataNode'); } - if (defined $push->{options} || $replace) { + if (defined $push->{options}) { my $res = $self->_hasAllowedAttributes($push->{options}, 'oidcRPMetaDataNode'); unless ($res->{res} eq 'ok') { return $res; } - if ($replace) { - $push->{options} = $self->_setDefaultValues($push->{options}, 'oidcRPMetaDataNode'); + foreach (keys %{$push->{options}}) { + $conf->{oidcRPMetaDataOptions}->{$confKey}->{$_} = $push->{options}->{$_}; } - $conf->{oidcRPMetaDataOptions}->{$confKey} = $push->{options}; } if (defined $push->{clientId}) { @@ -326,7 +329,9 @@ sub _pushOidcRp { if (defined $push->{exportedVars}) { if ($self->_isSimpleKeyValueHash($push->{exportedVars})) { - $conf->{oidcRPMetaDataExportedVars}->{$confKey} = $push->{exportedVars}; + foreach (keys %{$push->{exportedVars}}) { + $conf->{oidcRPMetaDataExportedVars}->{$confKey}->{$_} = $push->{exportedVars}->{$_}; + } } else { return { res => 'ko', msg => "Invalid input: exportedVars is not a hash object with \"key\":\"value\" attributes" }; } @@ -334,7 +339,9 @@ sub _pushOidcRp { if (defined $push->{extraClaim}) { if ($self->_isSimpleKeyValueHash($push->{extraClaim})) { - $conf->{oidcRPMetaDataOptionsExtraClaims}->{$confKey} = $push->{extraClaim}; + foreach (keys %{$push->{extraClaim}}) { + $conf->{oidcRPMetaDataOptionsExtraClaims}->{$confKey}->{$_} = $push->{extraClaim}->{$_}; + } } else { return { res => 'ko', msg => "Invalid input: extraClaim is not a hash object with \"key\":\"value\" attributes" }; } diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers/SamlSp.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers/SamlSp.pm index a9e58c9ca5..f384e6c525 100644 --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers/SamlSp.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers/SamlSp.pm @@ -22,6 +22,9 @@ sub getSamlSpByConfKey { my $samlSp = $self->_getSamlSpByConfKey($conf, $confKey); + # $self->logger->debug("$oidcRp :: "); + # use Data::Dumper; print STDERR Dumper($oidcRp); + # Check if confKey is defined unless (defined $samlSp) { return $self->sendError( $req, "SAML service Provider '$confKey' not found", 404 ); @@ -284,7 +287,7 @@ sub _getSamlSpByConfKey { $mandatory = !!$mandatory ? 'true' : 'false'; - my $samlSp->{exportedAttributes}->{$_} = { + $samlSp->{exportedAttributes}->{$_} = { name => $name, mandatory => $mandatory }; @@ -298,7 +301,6 @@ sub _getSamlSpByConfKey { } } - return $samlSp; } @@ -326,38 +328,42 @@ sub _readSamlSpEntityId { return undef; } sub _readSamlSpExportedAttributes { - my ( $self, $attrs ) = @_; + my ( $self, $attrs, $mergeWith ) = @_; my $allowedFormats = [ "urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified", "urn:oasis:names:tc:SAML:2.0:attrname-format:uri", "urn:oasis:names:tc:SAML:2.0:attrname-format:basic" ]; - my $exportedAttributes; foreach (keys %{$attrs}) { - unless (defined $attrs->{$_}->{name}) { return { res => "ko", msg => "Exported attribute $_ has no name"}; } my $mandatory = 0; + my $name = $attrs->{$_}->{name}; + my $format = ''; + my $friendlyName = ''; + if (defined $mergeWith->{$_}) { + ( $mandatory, $name, $format, $friendlyName ) = split( /;/, $mergeWith->{$_} ); + } if (defined $attrs->{$_}->{mandatory}) { if ( $attrs->{$_}->{mandatory} eq '1' or $attrs->{$_}->{mandatory} eq 'true' ) { $mandatory = 1; + } else { + $mandatory = 0; } } - my $format = ''; if (defined $attrs->{$_}->{format}) { $format = $attrs->{$_}->{format}; unless (length(grep {/^$format$/} @{$allowedFormats})) { return { res => "ko", msg => "Exported attribute $_ format does not exist."}; } } - my $friendlyName = ''; if (defined $attrs->{$_}->{friendlyName}) { $friendlyName = $attrs->{$_}->{friendlyName}; } - $exportedAttributes->{$_} = "$mandatory;$attrs->{$_}->{name};$format;$friendlyName"; + $mergeWith->{$_} = "$mandatory;$name;$format;$friendlyName"; } - return { res => "ok", exportedAttributes => $exportedAttributes }; + return { res => "ok", exportedAttributes => $mergeWith }; } sub _pushSamlSp { @@ -367,6 +373,7 @@ sub _pushSamlSp { $conf->{samlSPMetaDataXML}->{$confKey} = {}; $conf->{samlSPMetaDataOptions}->{$confKey} = {}; $conf->{samlSPMetaDataExportedAttributes}->{$confKey} = {}; + $push->{options} = $self->_setDefaultValues($push->{options}, 'samlSPMetaDataNode'); } $conf->{samlSPMetaDataXML}->{$confKey}->{samlSPMetaDataXML} = $push->{metadata}; @@ -376,14 +383,13 @@ sub _pushSamlSp { unless ($res->{res} eq 'ok') { return $res; } - $conf->{samlSPMetaDataOptions}->{$confKey} = $push->{options}; - } - if ($replace) { - $push->{options} = $self->_setDefaultValues($push->{options}, 'samlSPMetaDataNode'); + foreach (keys %{$push->{options}}) { + $conf->{samlSPMetaDataOptions}->{$confKey}->{$_} = $push->{options}->{$_}; + } } if (defined $push->{exportedAttributes}) { - my $res = $self->_readSamlSpExportedAttributes($push->{exportedAttributes}); + my $res = $self->_readSamlSpExportedAttributes($push->{exportedAttributes}, $conf->{samlSPMetaDataExportedAttributes}->{$confKey}); unless ($res->{res} eq 'ok') { return $res; } diff --git a/lemonldap-ng-manager/t/04-providers-api.t b/lemonldap-ng-manager/t/04-providers-api.t index 0c56865788..9277b2150a 100644 --- a/lemonldap-ng-manager/t/04-providers-api.t +++ b/lemonldap-ng-manager/t/04-providers-api.t @@ -18,7 +18,7 @@ sub check200 { } sub check404 { my ( $test, $res ) = splice @_; - diag Dumper($res); + #diag Dumper($res); ok( $res->[0] == 404, "$test: Result code is 404" ); count(1); checkJson($test, $res); @@ -79,13 +79,17 @@ sub get { } sub checkGet { - my ( $test, $type, $confKey, $attribute, $expectedValue) = splice @_; + my ( $test, $type, $confKey, $attrPath, $expectedValue) = splice @_; my $res = get($test, $type, $confKey); - my $key = from_json($res->[2]->[0]); check200($test, $res); + my @path = split '/', $attrPath; + my $key = from_json($res->[2]->[0]); + for (@path) { + $key = $key->{$_}; + } ok ( - $key->{options}->{$attribute} eq $expectedValue, - "$test: check if $attribute eq $expectedValue" + $key eq $expectedValue, + "$test: check if $attrPath value \"$key\" matches expected value \"$expectedValue\"" ); count(1); } @@ -176,24 +180,23 @@ sub findByConfKey { } sub checkFindByConfKey { - my ( $test, $type, $confKey, $expectedHits, $attribute, $expectedValues) = splice @_; + my ( $test, $type, $confKey, $expectedHits) = splice @_; my $res = findByConfKey($test, $type, $confKey); check200($test, $res); my $hits = from_json($res->[2]->[0]); my $hit; my $counter = 0; foreach $hit (@{$hits}) { - my $expected = grep {/^$hit->{options}->{$attribute}$/} @{$expectedValues}; $counter++; ok ( - $expected, - "$test: check if $attribute value \"$hit->{options}->{$attribute}\" matches one of expectedValues: " . $_json->encode($expectedValues) + $hit->{confKey} =~ $confKey, + "$test: check if confKey value \"$hit->{confKey}\" matches pattern \"$confKey\"" ); count(1); } ok ( $counter eq $expectedHits, - "$test: check if returned nb of hits ($counter) matches $expectedHits" + "$test: check if nb of hits returned ($counter) matches expectation ($expectedHits)" ); count(1); } @@ -214,9 +217,15 @@ sub checkFindByProviderId { my $res = findByProviderId($test, $type, $providerIdName, $providerId); check200($test, $res); my $result = from_json($res->[2]->[0]); + my $gotProviderId; + if ($providerIdName eq 'entityId') { + ( $gotProviderId ) = $result->{metadata} =~ m/entityID=['"](.+?)['"]/i; + } else { + $gotProviderId = $result->{$providerIdName}; + } ok( - $result->{$providerIdName} eq $providerId, - "$test: Check $providerIdName value returned \"$result->{$providerIdName}\" matched expected value \"$providerId\"" + $gotProviderId eq $providerId, + "$test: Check $providerIdName value returned \"$gotProviderId\" matched expected value \"$providerId\"" ); count(1); } @@ -240,7 +249,7 @@ sub deleteProvider { $res = &client->_del( "/v1/providers/$type/$confKey", '', '', 'application/json', 0 ), - "Request succeed" + "$test: Request succeed" ); count(1); return $res; @@ -275,91 +284,237 @@ my $oidcRp = { } }; -$test = "Add should succeed"; +$test = "OidcRp - Add should succeed"; checkAdd($test, 'oidc/rp', $oidcRp); -checkGet($test, 'oidc/rp', 'myOidcRp1', 'oidcRPMetaDataOptionsIcon', 'web.png'); -checkGet($test, 'oidc/rp', 'myOidcRp1', 'oidcRPMetaDataOptionsClientSecret', 'secret'); +checkGet($test, 'oidc/rp', 'myOidcRp1', 'options/oidcRPMetaDataOptionsIcon', 'web.png'); +checkGet($test, 'oidc/rp', 'myOidcRp1', 'options/oidcRPMetaDataOptionsClientSecret', 'secret'); -$test = "Check attribute default value was set after add"; -checkGet($test, 'oidc/rp', 'myOidcRp1', 'oidcRPMetaDataOptionsIDTokenSignAlg', 'HS512'); +$test = "OidcRp - Check attribute default value was set after add"; +checkGet($test, 'oidc/rp', 'myOidcRp1', 'options/oidcRPMetaDataOptionsIDTokenSignAlg', 'HS512'); -$test = "Add Should fail on duplicate confKey"; +$test = "OidcRp - Add Should fail on duplicate confKey"; checkAddFailsIfExists($test, 'oidc/rp', $oidcRp); -$test = "Update should succeed"; +$test = "OidcRp - Update should succeed and keep existing values"; $oidcRp->{options}->{oidcRPMetaDataOptionsClientSecret} = 'secret2'; $oidcRp->{options}->{oidcRPMetaDataOptionsIDTokenSignAlg} = 'RS512'; +delete $oidcRp->{options}->{oidcRPMetaDataOptionsIcon}; +delete $oidcRp->{extraClaim}; +delete $oidcRp->{exportedVars}; +$oidcRp->{exportedVars}->{cn} = 'cn'; checkUpdate($test, 'oidc/rp', 'myOidcRp1', $oidcRp); -checkGet($test, 'oidc/rp', 'myOidcRp1', 'oidcRPMetaDataOptionsClientSecret', 'secret2'); -checkGet($test, 'oidc/rp', 'myOidcRp1', 'oidcRPMetaDataOptionsIDTokenSignAlg', 'RS512'); - -$test = "Update should fail on non existing options"; +checkGet($test, 'oidc/rp', 'myOidcRp1', 'options/oidcRPMetaDataOptionsClientSecret', 'secret2'); +checkGet($test, 'oidc/rp', 'myOidcRp1', 'options/oidcRPMetaDataOptionsIDTokenSignAlg', 'RS512'); +checkGet($test, 'oidc/rp', 'myOidcRp1', 'options/oidcRPMetaDataOptionsIcon', 'web.png'); +checkGet($test, 'oidc/rp', 'myOidcRp1', 'exportedVars/cn', 'cn'); +checkGet($test, 'oidc/rp', 'myOidcRp1', 'exportedVars/family_name', 'sn'); +checkGet($test, 'oidc/rp', 'myOidcRp1', 'extraClaim/phone', 'telephoneNumber'); + +$test = "OidcRp - Update should fail on non existing options"; $oidcRp->{options}->{playingPossum} = 'elephant'; checkUpdateWithUnknownAttributes($test, 'oidc/rp', 'myOidcRp1', $oidcRp); delete $oidcRp->{options}->{playingPossum}; -$test = "Add Should fail on duplicate clientId"; +$test = "OidcRp - Add Should fail on duplicate clientId"; $oidcRp->{confKey} = 'myOidcRp2'; checkAddFailsIfExists($test, 'oidc/rp', $oidcRp); -$test = "Add Should fail on non existing options"; +$test = "OidcRp - Add Should fail on non existing options"; $oidcRp->{confKey} = 'myOidcRp2'; $oidcRp->{clientId} = 'myOidcClient2'; $oidcRp->{options}->{playingPossum} = 'ElephantInTheRoom'; checkAddWithUnknownAttributes($test, 'oidc/rp', $oidcRp); delete $oidcRp->{options}->{playingPossum}; -$test = "2nd add should succeed"; +$test = "OidcRp - 2nd add should succeed"; checkAdd($test, 'oidc/rp', $oidcRp); -$test = "Update should fail if client id exists"; +$test = "OidcRp - Update should fail if client id exists"; $oidcRp->{clientId} = 'myOidcClient1'; checkUpdateFailsIfExists($test, 'oidc/rp', 'myOidcRp2', $oidcRp); -$test = "Update should fail if confKey not found"; +$test = "OidcRp - Update should fail if confKey not found"; $oidcRp->{confKey} = 'myOidcRp3'; checkUpdateNotFound($test, 'oidc/rp', 'myOidcRp3', $oidcRp); -$test = "Replace should succeed"; +$test = "OidcRp - Replace should succeed"; $oidcRp->{confKey} = 'myOidcRp2'; $oidcRp->{clientId} = 'myOidcClient2'; delete $oidcRp->{options}->{oidcRPMetaDataOptionsIcon}; delete $oidcRp->{options}->{oidcRPMetaDataOptionsIDTokenSignAlg}; checkReplace($test, 'oidc/rp', 'myOidcRp2', $oidcRp); -$test = "Check attribute default value was set after replace"; -checkGet($test, 'oidc/rp', 'myOidcRp2', 'oidcRPMetaDataOptionsIDTokenSignAlg', 'HS512'); +$test = "OidcRp - Check attribute default value was set after replace"; +checkGet($test, 'oidc/rp', 'myOidcRp2', 'options/oidcRPMetaDataOptionsIDTokenSignAlg', 'HS512'); -$test = "Replace should fail on non existing options"; +$test = "OidcRp - Replace should fail on non existing options"; $oidcRp->{options}->{playingPossum} = 'elephant'; checkReplaceWithUnknownAttribute($test, 'oidc/rp', 'myOidcRp2', $oidcRp); delete $oidcRp->{options}->{playingPossum}; -$test = "Replace should fail if confKey not found"; +$test = "OidcRp - Replace should fail if confKey not found"; $oidcRp->{confKey} = 'myOidcRp3'; checkReplaceNotFound($test, 'oidc/rp', 'myOidcRp3', $oidcRp); -$test = "FindByConfKey should find 2 hits"; -checkFindByConfKey($test, 'oidc/rp', '^myOidcRp.$', 2, 'oidcRPMetaDataOptionsClientID', ['myOidcClient1','myOidcClient2']); +$test = "OidcRp - FindByConfKey should find 2 hits"; +checkFindByConfKey($test, 'oidc/rp', '^myOidcRp.$', 2); -$test = "FindByConfKey should find 1 hit"; -checkFindByConfKey($test, 'oidc/rp', 'myOidcRp1', 1, 'oidcRPMetaDataOptionsClientID', ['myOidcClient1']); +$test = "OidcRp - FindByConfKey should find 1 hit"; +checkFindByConfKey($test, 'oidc/rp', 'myOidcRp1', 1); -$test = "FindByConfKey should find 0 hits"; +$test = "OidcRp - FindByConfKey should find 0 hits"; checkFindByConfKey($test, 'oidc/rp', 'myOidcRp3', 0); -$test = "FindByClientId should find one entry"; +$test = "OidcRp - FindByClientId should find one entry"; checkFindByProviderId($test, 'oidc/rp', 'clientId', 'myOidcClient1'); -$test = "FindByClientId should find nothing"; +$test = "OidcRp - FindByClientId should find nothing"; checkFindByProviderIdNotFound($test, 'oidc/rp', 'clientId', 'myOidcClient3'); -$test = "Clean up"; +$test = "OidcRp - Clean up"; checkDelete($test, 'oidc/rp', 'myOidcRp1'); checkDelete($test, 'oidc/rp', 'myOidcRp2'); -$test = "Entity should not be found after clean up"; +$test = "OidcRp - Entity should not be found after clean up"; checkDeleteNotFound($test, 'oidc/rp', 'myOidcRp1'); +my $metadata1 = "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"; + +my $metadata2 = "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"; + +my $samlSp = { + confKey => 'mySamlSp1', + metadata => $metadata1, + exportedAttributes => { + family_name => { + format => "urn:oasis:names:tc:SAML:2.0:attrname-format:basic", + friendlyName => "surname", + mandatory => "false", + name => "sn" + }, + cn => { + friendlyName => "commonname", + mandatory => "true", + name => "uid" + }, + uid => { + mandatory => "true", + name => "uid" + }, + phone => { + mandatory => "false", + format => "urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified", + name => "telephoneNumber" + }, + function => { + name => "title", + mandatory => "false", + format => "urn:oasis:names:tc:SAML:2.0:attrname-format:uri" + }, + given_name => { + mandatory => "false", + name => "givenName" + } + }, + options => { + samlSPMetaDataOptionsCheckSLOMessageSignature => 0, + samlSPMetaDataOptionsEncryptionMode => "assertion", + samlSPMetaDataOptionsSessionNotOnOrAfterTimeout => 36000 + } +}; + +$test = "SamlSp - Add should succeed"; +checkAdd($test, 'saml/sp', $samlSp); +checkGet($test, 'saml/sp', 'mySamlSp1', 'options/samlSPMetaDataOptionsEncryptionMode', 'assertion'); +checkGet($test, 'saml/sp', 'mySamlSp1', 'options/samlSPMetaDataOptionsSessionNotOnOrAfterTimeout', 36000); + +$test = "SamlSp - Check attribute default value was set after add"; +checkGet($test, 'saml/sp', 'mySamlSp1', 'options/samlSPMetaDataOptionsNotOnOrAfterTimeout', 72000); + +$test = "SamlSp - Add Should fail on duplicate confKey"; +checkAddFailsIfExists($test, 'saml/sp', $samlSp); + +$test = "SamlSp - Update should succeed and keep existing values"; +$samlSp->{options}->{samlSPMetaDataOptionsCheckSLOMessageSignature} = 1; +$samlSp->{options}->{samlSPMetaDataOptionsEncryptionMode} = 'nameid'; +delete $samlSp->{options}->{samlSPMetaDataOptionsSessionNotOnOrAfterTimeout}; +delete $samlSp->{exportedAttributes}; +$samlSp->{exportedAttributes}->{cn}->{name} = "cn", +$samlSp->{exportedAttributes}->{cn}->{friendlyName} = "common_name", +$samlSp->{exportedAttributes}->{cn}->{mandatory} = "false", +checkUpdate($test, 'saml/sp', 'mySamlSp1', $samlSp); +checkGet($test, 'saml/sp', 'mySamlSp1', 'options/samlSPMetaDataOptionsCheckSLOMessageSignature', 1); +checkGet($test, 'saml/sp', 'mySamlSp1', 'options/samlSPMetaDataOptionsSessionNotOnOrAfterTimeout', 36000); +checkGet($test, 'saml/sp', 'mySamlSp1', 'exportedAttributes/cn/friendlyName', 'common_name'); +checkGet($test, 'saml/sp', 'mySamlSp1', 'exportedAttributes/cn/mandatory', 'false'); +checkGet($test, 'saml/sp', 'mySamlSp1', 'exportedAttributes/cn/mandatory', 'false'); +checkGet($test, 'saml/sp', 'mySamlSp1', 'exportedAttributes/cn/name', 'uid'); +checkGet($test, 'saml/sp', 'mySamlSp1', 'exportedAttributes/given_name/name', 'givenName'); + +$test = "SamlSp - Update should fail on non existing options"; +$samlSp->{options}->{playingPossum} = 'elephant'; +checkUpdateWithUnknownAttributes($test, 'saml/sp', 'mySamlSp1', $samlSp); +delete $samlSp->{options}->{playingPossum}; + +$test = "SamlSp - Add Should fail on duplicate entityId"; +$samlSp->{confKey} = 'mySamlSp2'; +checkAddFailsIfExists($test, 'saml/sp', $samlSp); + +$test = "SamlSp - Add Should fail on non existing options"; +$samlSp->{confKey} = 'mySamlSp2'; +$samlSp->{metadata} = $metadata2; +$samlSp->{options}->{playingPossum} = 'ElephantInTheRoom'; +checkAddWithUnknownAttributes($test, 'saml/sp', $samlSp); +delete $samlSp->{options}->{playingPossum}; + +$test = "SamlSp - 2nd add should succeed"; +checkAdd($test, 'saml/sp', $samlSp); + +$test = "SamlSp - Update should fail if client id exists"; +$samlSp->{metadata} = $metadata1; +checkUpdateFailsIfExists($test, 'saml/sp', 'mySamlSp2', $samlSp); + +$test = "SamlSp - Update should fail if confKey not found"; +$samlSp->{confKey} = 'mySamlSp3'; +checkUpdateNotFound($test, 'saml/sp', 'mySamlSp3', $samlSp); + +$test = "SamlSp - Replace should succeed"; +$samlSp->{confKey} = 'mySamlSp2'; +$samlSp->{metadata} = $metadata2; +delete $samlSp->{options}->{samlSPMetaDataOptionsEncryptionMode}; +checkReplace($test, 'saml/sp', 'mySamlSp2', $samlSp); + +$test = "SamlSp - Check attribute default value was set after replace"; +checkGet($test, 'saml/sp', 'mySamlSp2', 'options/samlSPMetaDataOptionsEncryptionMode', 'none'); + +$test = "SamlSp - Replace should fail on non existing options"; +$samlSp->{options}->{playingPossum} = 'elephant'; +checkReplaceWithUnknownAttribute($test, 'saml/sp', 'mySamlSp2', $samlSp); +delete $samlSp->{options}->{playingPossum}; + +$test = "SamlSp - Replace should fail if confKey not found"; +$samlSp->{confKey} = 'mySamlSp3'; +checkReplaceNotFound($test, 'saml/sp', 'mySamlSp3', $samlSp); + +$test = "SamlSp - FindByConfKey should find 2 hits"; +checkFindByConfKey($test, 'saml/sp', '^mySamlSp.$', 2); + +$test = "SamlSp - FindByConfKey should find 1 hit"; +checkFindByConfKey($test, 'saml/sp', 'mySamlSp1', 1); + +$test = "SamlSp - FindByConfKey should find 0 hits"; +checkFindByConfKey($test, 'saml/sp', 'mySamlSp3', 0); + +$test = "SamlSp - FindByEntityId should find one entry"; +checkFindByProviderId($test, 'saml/sp', 'entityId', 'https://myapp.domain.com/saml/metadata'); + +$test = "SamlSp - FindByEntityId should find nothing"; +checkFindByProviderIdNotFound($test, 'saml/sp', 'entityId', 'https://myapp3.domain.com/saml/metadata'); + +$test = "SamlSp - Clean up"; +checkDelete($test, 'saml/sp', 'mySamlSp1'); +checkDelete($test, 'saml/sp', 'mySamlSp2'); +$test = "SamlSp - Entity should not be found after clean up"; +checkDeleteNotFound($test, 'saml/sp', 'mySamlSp1'); # Clean up generated conf files, except for "lmConf-1.json" unlink grep { $_ ne "t/conf/lmConf-1.json" } glob "t/conf/lmConf-*.json"; -- GitLab From 37d01266a9cefe0eebcfea98f49dc83a0d584881 Mon Sep 17 00:00:00 2001 From: Soisik Froger Date: Tue, 17 Dec 2019 19:58:55 +0000 Subject: [PATCH 21/31] Manager API - perltidy - #2034 --- .../lib/Lemonldap/NG/Manager/Api.pm | 12 +- .../lib/Lemonldap/NG/Manager/Api/Common.pm | 46 +- .../NG/Manager/Api/Providers/OidcRp.pm | 256 ++++++----- .../NG/Manager/Api/Providers/SamlSp.pm | 306 +++++++------ lemonldap-ng-manager/t/04-providers-api.t | 406 ++++++++++-------- 5 files changed, 570 insertions(+), 456 deletions(-) diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api.pm index eaad2298b6..8d64941125 100644 --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api.pm @@ -82,10 +82,10 @@ sub addRoutes { v1 => { providers => { oidc => { - rp => {':confKey' => 'replaceOidcRp'} + rp => { ':confKey' => 'replaceOidcRp' } }, saml => { - sp => {':confKey' => 'replaceSamlSp'} + sp => { ':confKey' => 'replaceSamlSp' } }, }, }, @@ -96,10 +96,10 @@ sub addRoutes { v1 => { providers => { oidc => { - rp => {':confKey' => 'updateOidcRp'} + rp => { ':confKey' => 'updateOidcRp' } }, saml => { - sp => {':confKey' => 'updateSamlSp'} + sp => { ':confKey' => 'updateSamlSp' } }, }, }, @@ -110,10 +110,10 @@ sub addRoutes { v1 => { providers => { oidc => { - rp => {':confKey' => 'deleteOidcRp'} + rp => { ':confKey' => 'deleteOidcRp' } }, saml => { - sp => {':confKey' => 'deleteSamlSp'} + sp => { ':confKey' => 'deleteSamlSp' } }, }, }, diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Common.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Common.pm index d0b158e23d..d4123d6d37 100644 --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Common.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Common.pm @@ -5,15 +5,16 @@ package Lemonldap::NG::Manager::Api; use Lemonldap::NG::Manager::Build::Attributes; use Lemonldap::NG::Manager::Build::CTrees; + # use Scalar::Util 'weaken'; ? sub _isSimpleKeyValueHash { - my ( $self, $hash) = @_; - if (ref($hash) ne "HASH") { + my ( $self, $hash ) = @_; + if ( ref($hash) ne "HASH" ) { return 0; } - foreach (keys %{$hash}) { - if (ref($hash->{$_}) ne '' || ref($_) ne '') { + foreach ( keys %{$hash} ) { + if ( ref( $hash->{$_} ) ne '' || ref($_) ne '' ) { return 0; } } @@ -21,13 +22,15 @@ sub _isSimpleKeyValueHash { } sub _setDefaultValues { - my ( $self, $attrs, $rootNode) = @_; - my @allAttrs = $self->_listAttributes($rootNode); + my ( $self, $attrs, $rootNode ) = @_; + my @allAttrs = $self->_listAttributes($rootNode); my $defaultAttrs = Lemonldap::NG::Manager::Build::Attributes::attributes(); foreach $attr (@allAttrs) { - unless (defined $attrs->{$attr}) { - if (defined $defaultAttrs->{$attr} && defined $defaultAttrs->{$attr}->{default}) { + unless ( defined $attrs->{$attr} ) { + if ( defined $defaultAttrs->{$attr} + && defined $defaultAttrs->{$attr}->{default} ) + { $attrs->{$attr} = $defaultAttrs->{$attr}->{default}; } } @@ -37,18 +40,18 @@ sub _setDefaultValues { } sub _hasAllowedAttributes { - my ( $self, $attributes, $rootNode) = @_; + my ( $self, $attributes, $rootNode ) = @_; my @allowedAttributes = $self->_listAttributes($rootNode); - foreach $attribute (keys %{$attributes}) { - if (length(ref($attribute))) { + foreach $attribute ( keys %{$attributes} ) { + if ( length( ref($attribute) ) ) { return { res => "ko", msg => "Invalid input: Attribute $attribute is not a string." }; } - unless (grep {/^$attribute$/} @allowedAttributes) { + unless ( grep { /^$attribute$/ } @allowedAttributes ) { return { res => "ko", msg => "Invalid input: Attribute $attribute does not exist." @@ -60,28 +63,29 @@ sub _hasAllowedAttributes { } sub _listAttributes { - my ( $self, $rootNode) = @_; + my ( $self, $rootNode ) = @_; my $mainTree = Lemonldap::NG::Manager::Build::CTrees::cTrees(); - my $rootNodes = [ grep { ref($_) eq "HASH" } @{$mainTree->{$rootNode}} ]; + my $rootNodes = [ grep { ref($_) eq "HASH" } @{ $mainTree->{$rootNode} } ]; my @attributes; - foreach $node (@{$rootNodes}) { + foreach $node ( @{$rootNodes} ) { push @attributes, $self->_listNodeAttributes($node); } return @attributes; } sub _listNodeAttributes { - my ( $self, $node) = @_; + my ( $self, $node ) = @_; my @attributes; - foreach $child (@{$node->{nodes}}) { - if (ref($child) eq "HASH"){ - push (@attributes, $self->_listNodeAttributes($child)); - } else { - push (@attributes, $child); + foreach $child ( @{ $node->{nodes} } ) { + if ( ref($child) eq "HASH" ) { + push( @attributes, $self->_listNodeAttributes($child) ); + } + else { + push( @attributes, $child ); } } diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers/OidcRp.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers/OidcRp.pm index d6bed7ae30..103b4a5ccd 100644 --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers/OidcRp.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers/OidcRp.pm @@ -20,68 +20,64 @@ sub getOidcRpByConfKey { # Get latest configuration my $conf = $self->_confAcc->getConf; - my $oidcRp = $self->_getOidcRpByConfKey($conf, $confKey); + my $oidcRp = $self->_getOidcRpByConfKey( $conf, $confKey ); # $self->logger->debug("$oidcRp :: "); # use Data::Dumper; print STDERR Dumper($oidcRp); # Return 404 if not found - unless (defined $oidcRp) { - return $self->sendError( $req, "OIDC relying party '$confKey' not found", 404 ); + unless ( defined $oidcRp ) { + return $self->sendError( $req, + "OIDC relying party '$confKey' not found", 404 ); } - return $self->sendJSONresponse( - $req, $oidcRp - ); + return $self->sendJSONresponse( $req, $oidcRp ); } sub findOidcRpByConfKey { my ( $self, $req ) = @_; my $pattern = ( - defined $req->params('uPattern') ? - $req->params('uPattern') : - ( defined $req->params('pattern') ? $req->params('pattern') : undef ) + defined $req->params('uPattern') + ? $req->params('uPattern') + : ( defined $req->params('pattern') ? $req->params('pattern') : undef ) ); - unless (defined $pattern) { - return $self->sendError( $req, 'Invalid input: pattern is missing', 405 ); + unless ( defined $pattern ) { + return $self->sendError( $req, 'Invalid input: pattern is missing', + 405 ); } - $self->logger->debug("[API] Find OIDC RPs by confKey regexp $pattern requested"); + $self->logger->debug( + "[API] Find OIDC RPs by confKey regexp $pattern requested"); # Get latest configuration my $conf = $self->_confAcc->getConf; my @oidcRps; - foreach ( - keys %{ - $conf->{oidcRPMetaDataOptions} - } - ) - { - if ($_ =~ $pattern) { - push @oidcRps, $self->_getOidcRpByConfKey($conf, $_); + foreach ( keys %{ $conf->{oidcRPMetaDataOptions} } ) { + if ( $_ =~ $pattern ) { + push @oidcRps, $self->_getOidcRpByConfKey( $conf, $_ ); } } - return $self->sendJSONresponse( - $req, [ @oidcRps ] - ); + return $self->sendJSONresponse( $req, [@oidcRps] ); } sub findOidcRpByClientId { my ( $self, $req ) = @_; my $clientId = ( - defined $req->params('uClientId') ? - $req->params('uClientId') : - ( defined $req->params('clientId') ? $req->params('clientId') : undef ) + defined $req->params('uClientId') ? $req->params('uClientId') + : ( + defined $req->params('clientId') ? $req->params('clientId') + : undef ) ); - unless (defined $clientId) { - return $self->sendError( $req, 'Invalid input: clientId is missing', 405 ); + unless ( defined $clientId ) { + return $self->sendError( $req, 'Invalid input: clientId is missing', + 405 ); } $self->logger->debug("[API] Find OIDC RPs by clientId $clientId requested"); @@ -89,16 +85,16 @@ sub findOidcRpByClientId { # Get latest configuration my $conf = $self->_confAcc->getConf; - my $oidcRp = $self->_getOidcRpByClientId($conf, $clientId); + my $oidcRp = $self->_getOidcRpByClientId( $conf, $clientId ); - if (defined $oidcRp) { - return $self->sendJSONresponse($req, $oidcRp); + if ( defined $oidcRp ) { + return $self->sendJSONresponse( $req, $oidcRp ); } - return $self->sendJSONresponse($req, {}); + return $self->sendJSONresponse( $req, {} ); } sub addOidcRp { - my ( $self, $req) = @_; + my ( $self, $req ) = @_; my $add = $req->jsonBodyToObj; @@ -106,38 +102,51 @@ sub addOidcRp { return $self->sendError( $req, "Invalid input: " . $req->error, 405 ); } - unless (defined $add->{confKey}) { - return $self->sendError( $req, 'Invalid input: confKey is missing', 405 ); + unless ( defined $add->{confKey} ) { + return $self->sendError( $req, 'Invalid input: confKey is missing', + 405 ); } - unless (defined $add->{clientId}) { - return $self->sendError( $req, 'Invalid input: clientId is missing', 405 ); + unless ( defined $add->{clientId} ) { + return $self->sendError( $req, 'Invalid input: clientId is missing', + 405 ); } - $self->logger->debug("[API] Add OIDC RP with confKey $add->{confKey} and clientId $add->{clientId} requested"); + $self->logger->debug( +"[API] Add OIDC RP with confKey $add->{confKey} and clientId $add->{clientId} requested" + ); # Get latest configuration my $conf = $self->_confAcc->getConf; - if (defined $self->_getOidcRpByConfKey($conf, $add->{confKey})) { - return $self->sendError( $req, "Invalid input: An OIDC RP with confKey $add->{confKey} already exists", 405 ); + if ( defined $self->_getOidcRpByConfKey( $conf, $add->{confKey} ) ) { + return $self->sendError( + $req, +"Invalid input: An OIDC RP with confKey $add->{confKey} already exists", + 405 + ); } - if (defined $self->_getOidcRpByClientId($conf, $add->{clientId})) { - return $self->sendError( $req, "Invalid input: An OIDC RP with clientId $add->{clientId} already exists", 405 ); + if ( defined $self->_getOidcRpByClientId( $conf, $add->{clientId} ) ) { + return $self->sendError( + $req, +"Invalid input: An OIDC RP with clientId $add->{clientId} already exists", + 405 + ); } - unless (defined $add->{options}) { + unless ( defined $add->{options} ) { $add->{options} = {}; } $add->{options}->{oidcRPMetaDataOptionsClientID} = $add->{clientId}; - my $res = $self->_pushOidcRp($conf, $add->{confKey}, $add, 1); - unless ($res->{res} eq 'ok') { + my $res = $self->_pushOidcRp( $conf, $add->{confKey}, $add, 1 ); + unless ( $res->{res} eq 'ok' ) { return $self->sendError( $req, $res->{msg}, 405 ); } - return $self->sendJSONresponse($req, { message => "Successful operation" }); + return $self->sendJSONresponse( $req, + { message => "Successful operation" } ); } sub updateOidcRp { @@ -149,33 +158,36 @@ sub updateOidcRp { my $update = $req->jsonBodyToObj; unless ($update) { - return $self->sendError( $req, "Invalid input: " . $req->error, 405 ); + return $self->sendError( $req, "Invalid input: " . $req->error, 405 ); } - $self->logger->debug("[API] OIDC RP $confKey configuration update requested"); + $self->logger->debug( + "[API] OIDC RP $confKey configuration update requested"); # Get latest configuration my $conf = $self->_confAcc->getConf; - my $current = $self->_getOidcRpByConfKey($conf, $confKey); + my $current = $self->_getOidcRpByConfKey( $conf, $confKey ); # Return 404 if not found - unless (defined $current) { - return $self->sendError( $req, "OIDC relying party '$confKey' not found", 404 ); + unless ( defined $current ) { + return $self->sendError( $req, + "OIDC relying party '$confKey' not found", 404 ); } # check if new clientID exists already - my $res = $self->_isNewOidcRpClientIdUnique($conf, $confKey, $update); - unless ($res->{res} eq 'ok') { + my $res = $self->_isNewOidcRpClientIdUnique( $conf, $confKey, $update ); + unless ( $res->{res} eq 'ok' ) { return $self->sendError( $req, $res->{msg}, 405 ); } - $res = $self->_pushOidcRp($conf, $confKey, $update, 0); - unless ($res->{res} eq 'ok') { + $res = $self->_pushOidcRp( $conf, $confKey, $update, 0 ); + unless ( $res->{res} eq 'ok' ) { return $self->sendError( $req, $res->{msg}, 405 ); } - return $self->sendJSONresponse($req, { message => "Successful operation" }); + return $self->sendJSONresponse( $req, + { message => "Successful operation" } ); } sub replaceOidcRp { @@ -191,32 +203,36 @@ sub replaceOidcRp { return $self->sendError( $req, "Invalid input: " . $req->error, 405 ); } - unless (defined $replace->{clientId}) { - return $self->sendError( $req, 'Invalid input: clientId is missing', 405 ); + unless ( defined $replace->{clientId} ) { + return $self->sendError( $req, 'Invalid input: clientId is missing', + 405 ); } - $self->logger->debug("[API] OIDC RP $confKey configuration replace requested"); + $self->logger->debug( + "[API] OIDC RP $confKey configuration replace requested"); # Get latest configuration my $conf = $self->_confAcc->getConf; # Return 404 if not found - unless (defined $self->_getOidcRpByConfKey($conf, $confKey)) { - return $self->sendError( $req, "OIDC relying party '$confKey' not found", 404 ); + unless ( defined $self->_getOidcRpByConfKey( $conf, $confKey ) ) { + return $self->sendError( $req, + "OIDC relying party '$confKey' not found", 404 ); } # check if new clientID exists already - my $res = $self->_isNewOidcRpClientIdUnique($conf, $confKey, $replace); - unless ($res->{res} eq 'ok') { + my $res = $self->_isNewOidcRpClientIdUnique( $conf, $confKey, $replace ); + unless ( $res->{res} eq 'ok' ) { return $self->sendError( $req, $res->{msg}, 405 ); } - $res = $self->_pushOidcRp($conf, $confKey, $replace, 1); - unless ($res->{res} eq 'ok') { + $res = $self->_pushOidcRp( $conf, $confKey, $replace, 1 ); + unless ( $res->{res} eq 'ok' ) { return $self->sendError( $req, $res->{msg}, 405 ); } - return $self->sendJSONresponse($req, { message => "Successful operation" }); + return $self->sendJSONresponse( $req, + { message => "Successful operation" } ); } sub deleteOidcRp { @@ -228,11 +244,12 @@ sub deleteOidcRp { # Get latest configuration my $conf = $self->_confAcc->getConf; - my $delete = $self->_getOidcRpByConfKey($conf, $confKey); + my $delete = $self->_getOidcRpByConfKey( $conf, $confKey ); # Return 404 if not found - unless (defined $delete) { - return $self->sendError( $req, "OIDC relying party '$confKey' not found", 404 ); + unless ( defined $delete ) { + return $self->sendError( $req, + "OIDC relying party '$confKey' not found", 404 ); } delete $conf->{oidcRPMetaDataOptions}->{$confKey}; @@ -242,7 +259,8 @@ sub deleteOidcRp { # Save configuration $self->_confAcc->saveConf($conf); - return $self->sendJSONresponse($req, { message => "Successful operation" }); + return $self->sendJSONresponse( $req, + { message => "Successful operation" } ); } sub _getOidcRpByConfKey { @@ -270,80 +288,100 @@ sub _getOidcRpByConfKey { confKey => $confKey, clientId => $clientId, exportedVars => $exportedVars, - extraClaim => $extraClaim, - options => $options + extraClaim => $extraClaim, + options => $options }; } sub _getOidcRpByClientId { my ( $self, $conf, $clientId ) = @_; - foreach ( - keys %{ - $conf->{oidcRPMetaDataOptions} - } - ) - { - if ($conf->{oidcRPMetaDataOptions}->{$_}->{oidcRPMetaDataOptionsClientID} eq $clientId) { - return $self->_getOidcRpByConfKey($conf, $_) + foreach ( keys %{ $conf->{oidcRPMetaDataOptions} } ) { + if ( $conf->{oidcRPMetaDataOptions}->{$_} + ->{oidcRPMetaDataOptionsClientID} eq $clientId ) + { + return $self->_getOidcRpByConfKey( $conf, $_ ); } } return undef; } sub _isNewOidcRpClientIdUnique { - my ( $self, $conf, $confKey, $oidcRp) = @_; - my $curClientId = $self->_getOidcRpByConfKey($conf, $confKey)->{clientId}; - my $newClientId = $oidcRp->{clientId} || $oidcRp->{options}->{oidcRPMetaDataOptionsClientID} || ""; - if ($newClientId ne '' && $newClientId ne $curClientId) { - if ( defined $self->_getOidcRpByClientId($conf, $newClientId) ) { - return { res => 'ko' , msg => "An OIDC relying party with clientId '$newClientId' already exists" }; + my ( $self, $conf, $confKey, $oidcRp ) = @_; + my $curClientId = $self->_getOidcRpByConfKey( $conf, $confKey )->{clientId}; + my $newClientId = + $oidcRp->{clientId} + || $oidcRp->{options}->{oidcRPMetaDataOptionsClientID} + || ""; + if ( $newClientId ne '' && $newClientId ne $curClientId ) { + if ( defined $self->_getOidcRpByClientId( $conf, $newClientId ) ) { + return { + res => 'ko', + msg => +"An OIDC relying party with clientId '$newClientId' already exists" + }; } } return { res => 'ok' }; } sub _pushOidcRp { - my ( $self, $conf, $confKey, $push, $replace) = @_; + my ( $self, $conf, $confKey, $push, $replace ) = @_; if ($replace) { - $conf->{oidcRPMetaDataOptions}->{$confKey} = {}; - $conf->{oidcRPMetaDataExportedVars}->{$confKey} = {}; + $conf->{oidcRPMetaDataOptions}->{$confKey} = {}; + $conf->{oidcRPMetaDataExportedVars}->{$confKey} = {}; $conf->{oidcRPMetaDataOptionsExtraClaims}->{$confKey} = {}; - $push->{options} = $self->_setDefaultValues($push->{options}, 'oidcRPMetaDataNode'); + $push->{options} = + $self->_setDefaultValues( $push->{options}, 'oidcRPMetaDataNode' ); } - if (defined $push->{options}) { - my $res = $self->_hasAllowedAttributes($push->{options}, 'oidcRPMetaDataNode'); - unless ($res->{res} eq 'ok') { + if ( defined $push->{options} ) { + my $res = $self->_hasAllowedAttributes( $push->{options}, + 'oidcRPMetaDataNode' ); + unless ( $res->{res} eq 'ok' ) { return $res; } - foreach (keys %{$push->{options}}) { - $conf->{oidcRPMetaDataOptions}->{$confKey}->{$_} = $push->{options}->{$_}; + foreach ( keys %{ $push->{options} } ) { + $conf->{oidcRPMetaDataOptions}->{$confKey}->{$_} = + $push->{options}->{$_}; } } - if (defined $push->{clientId}) { - $conf->{oidcRPMetaDataOptions}->{$confKey}->{oidcRPMetaDataOptionsClientID} = $push->{clientId}; + if ( defined $push->{clientId} ) { + $conf->{oidcRPMetaDataOptions}->{$confKey} + ->{oidcRPMetaDataOptionsClientID} = $push->{clientId}; } - if (defined $push->{exportedVars}) { - if ($self->_isSimpleKeyValueHash($push->{exportedVars})) { - foreach (keys %{$push->{exportedVars}}) { - $conf->{oidcRPMetaDataExportedVars}->{$confKey}->{$_} = $push->{exportedVars}->{$_}; + if ( defined $push->{exportedVars} ) { + if ( $self->_isSimpleKeyValueHash( $push->{exportedVars} ) ) { + foreach ( keys %{ $push->{exportedVars} } ) { + $conf->{oidcRPMetaDataExportedVars}->{$confKey}->{$_} = + $push->{exportedVars}->{$_}; } - } else { - return { res => 'ko', msg => "Invalid input: exportedVars is not a hash object with \"key\":\"value\" attributes" }; + } + else { + return { + res => 'ko', + msg => +"Invalid input: exportedVars is not a hash object with \"key\":\"value\" attributes" + }; } } - if (defined $push->{extraClaim}) { - if ($self->_isSimpleKeyValueHash($push->{extraClaim})) { - foreach (keys %{$push->{extraClaim}}) { - $conf->{oidcRPMetaDataOptionsExtraClaims}->{$confKey}->{$_} = $push->{extraClaim}->{$_}; + if ( defined $push->{extraClaim} ) { + if ( $self->_isSimpleKeyValueHash( $push->{extraClaim} ) ) { + foreach ( keys %{ $push->{extraClaim} } ) { + $conf->{oidcRPMetaDataOptionsExtraClaims}->{$confKey}->{$_} = + $push->{extraClaim}->{$_}; } - } else { - return { res => 'ko', msg => "Invalid input: extraClaim is not a hash object with \"key\":\"value\" attributes" }; + } + else { + return { + res => 'ko', + msg => +"Invalid input: extraClaim is not a hash object with \"key\":\"value\" attributes" + }; } } diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers/SamlSp.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers/SamlSp.pm index f384e6c525..b2799348ae 100644 --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers/SamlSp.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers/SamlSp.pm @@ -20,68 +20,63 @@ sub getSamlSpByConfKey { # Get latest configuration my $conf = $self->_confAcc->getConf; - my $samlSp = $self->_getSamlSpByConfKey($conf, $confKey); + my $samlSp = $self->_getSamlSpByConfKey( $conf, $confKey ); # $self->logger->debug("$oidcRp :: "); # use Data::Dumper; print STDERR Dumper($oidcRp); # Check if confKey is defined - unless (defined $samlSp) { - return $self->sendError( $req, "SAML service Provider '$confKey' not found", 404 ); + unless ( defined $samlSp ) { + return $self->sendError( $req, + "SAML service Provider '$confKey' not found", 404 ); } - return $self->sendJSONresponse( - $req, $samlSp - ); + return $self->sendJSONresponse( $req, $samlSp ); } sub findSamlSpByConfKey { my ( $self, $req ) = @_; my $pattern = ( - defined $req->params('uPattern') ? - $req->params('uPattern') : - ( defined $req->params('pattern') ? $req->params('pattern') : undef ) + defined $req->params('uPattern') + ? $req->params('uPattern') + : ( defined $req->params('pattern') ? $req->params('pattern') : undef ) ); - unless (defined $pattern) { - return $self->sendError( $req, 'Invalid input: pattern is missing', 405 ); + unless ( defined $pattern ) { + return $self->sendError( $req, 'Invalid input: pattern is missing', + 405 ); } - $self->logger->debug("[API] Find SAML SPs by confKey regexp $pattern requested"); + $self->logger->debug( + "[API] Find SAML SPs by confKey regexp $pattern requested"); # Get latest configuration my $conf = $self->_confAcc->getConf; my @samlSps; - foreach ( - keys %{ - $conf->{samlSPMetaDataXML} - } - ) - { - if ($_ =~ $pattern) { - push @samlSps, $self->_getSamlSpByConfKey($conf, $_); + foreach ( keys %{ $conf->{samlSPMetaDataXML} } ) { + if ( $_ =~ $pattern ) { + push @samlSps, $self->_getSamlSpByConfKey( $conf, $_ ); } } - return $self->sendJSONresponse( - $req, [ @samlSps ] - ); + return $self->sendJSONresponse( $req, [@samlSps] ); } sub findSamlSpByEntityId { my ( $self, $req ) = @_; my $entityId = ( - defined $req->params('uEntityId') ? - $req->params('uEntityId') : - ( defined $req->params('entityId') ? $req->params('entityId') : undef ) + defined $req->params('uEntityId') ? $req->params('uEntityId') + : ( + defined $req->params('entityId') ? $req->params('entityId') + : undef ) ); - unless (defined $entityId) { - return $self->sendError( $req, 'entityId is missing', 405 ); + unless ( defined $entityId ) { + return $self->sendError( $req, 'entityId is missing', 405 ); } $self->logger->debug("[API] Find SAML SPs by entityId $entityId requested"); @@ -89,16 +84,16 @@ sub findSamlSpByEntityId { # Get latest configuration my $conf = $self->_confAcc->getConf; - my $samlSp = $self->_getSamlSpByEntityId($conf, $entityId); + my $samlSp = $self->_getSamlSpByEntityId( $conf, $entityId ); - if (defined $samlSp) { - return $self->sendJSONresponse($req, $samlSp); + if ( defined $samlSp ) { + return $self->sendJSONresponse( $req, $samlSp ); } - return $self->sendJSONresponse($req, {}); + return $self->sendJSONresponse( $req, {} ); } sub addSamlSp { - my ( $self, $req) = @_; + my ( $self, $req ) = @_; my $add = $req->jsonBodyToObj; @@ -106,39 +101,51 @@ sub addSamlSp { return $self->sendError( $req, "Invalid input: " . $req->error, 405 ); } - unless (defined $add->{confKey}) { - return $self->sendError( $req, 'Invalid input: confKey is missing', 405 ); + unless ( defined $add->{confKey} ) { + return $self->sendError( $req, 'Invalid input: confKey is missing', + 405 ); } - unless (defined $add->{metadata}) { - return $self->sendError( $req, 'Invalid input: metadata is missing', 405 ); + unless ( defined $add->{metadata} ) { + return $self->sendError( $req, 'Invalid input: metadata is missing', + 405 ); } - my $entityId = $self->_readSamlSpEntityId($add->{metadata}); + my $entityId = $self->_readSamlSpEntityId( $add->{metadata} ); - unless (defined $entityId) { - return $self->sendError( $req, 'Invalid input: entityID is missing in metadata', 405 ); + unless ( defined $entityId ) { + return $self->sendError( $req, + 'Invalid input: entityID is missing in metadata', 405 ); } - $self->logger->debug("[API] Add SAML SP with confKey $add->{confKey} and entityID $entityId requested"); + $self->logger->debug( +"[API] Add SAML SP with confKey $add->{confKey} and entityID $entityId requested" + ); # Get latest configuration my $conf = $self->_confAcc->getConf; - if (defined $self->_getSamlSpByConfKey($conf, $add->{confKey})) { - return $self->sendError( $req, "Invalid input: A SAML SP with confKey $add->{confKey} already exists", 405 ); + if ( defined $self->_getSamlSpByConfKey( $conf, $add->{confKey} ) ) { + return $self->sendError( + $req, +"Invalid input: A SAML SP with confKey $add->{confKey} already exists", + 405 + ); } - if (defined $self->_getSamlSpByEntityId($conf, $entityId)) { - return $self->sendError( $req, "Invalid input: A SAML SP with entityID $entityId already exists", 405 ); + if ( defined $self->_getSamlSpByEntityId( $conf, $entityId ) ) { + return $self->sendError( $req, + "Invalid input: A SAML SP with entityID $entityId already exists", + 405 ); } - my $res = $self->_pushSamlSp($conf, $add->{confKey}, $add, 1); - unless ($res->{res} eq 'ok') { + my $res = $self->_pushSamlSp( $conf, $add->{confKey}, $add, 1 ); + unless ( $res->{res} eq 'ok' ) { return $self->sendError( $req, $res->{msg}, 405 ); } - return $self->sendJSONresponse($req, { message => "Successful operation" }); + return $self->sendJSONresponse( $req, + { message => "Successful operation" } ); } sub replaceSamlSp { @@ -153,32 +160,36 @@ sub replaceSamlSp { return $self->sendError( $req, "Invalid input: " . $req->error, 405 ); } - unless (defined $replace->{metadata}) { - return $self->sendError( $req, 'Invalid input: metadata is missing', 405 ); + unless ( defined $replace->{metadata} ) { + return $self->sendError( $req, 'Invalid input: metadata is missing', + 405 ); } - $self->logger->debug("[API] SAML SP $confKey configuration replace requested"); + $self->logger->debug( + "[API] SAML SP $confKey configuration replace requested"); # Get latest configuration my $conf = $self->_confAcc->getConf; # Return 404 if not found - unless (defined $self->_getSamlSpByConfKey($conf, $confKey)) { - return $self->sendError( $req, "SAML service provider '$confKey' not found", 404 ); + unless ( defined $self->_getSamlSpByConfKey( $conf, $confKey ) ) { + return $self->sendError( $req, + "SAML service provider '$confKey' not found", 404 ); } # check if new entityId exists already - my $res = $self->_isNewSamlSpEntityIdUnique($conf, $confKey, $replace); - unless ($res->{res} eq 'ok') { + my $res = $self->_isNewSamlSpEntityIdUnique( $conf, $confKey, $replace ); + unless ( $res->{res} eq 'ok' ) { return $self->sendError( $req, $res->{msg}, 405 ); } - $res = $self->_pushSamlSp($conf, $confKey, $replace, 1); - unless ($res->{res} eq 'ok') { + $res = $self->_pushSamlSp( $conf, $confKey, $replace, 1 ); + unless ( $res->{res} eq 'ok' ) { return $self->sendError( $req, $res->{msg}, 405 ); } - return $self->sendJSONresponse($req, { message => "Successful operation" }); + return $self->sendJSONresponse( $req, + { message => "Successful operation" } ); } sub updateSamlSp { @@ -190,36 +201,40 @@ sub updateSamlSp { my $update = $req->jsonBodyToObj; unless ($update) { - return $self->sendError( $req, "Invalid input: " . $req->error, 405 ); + return $self->sendError( $req, "Invalid input: " . $req->error, 405 ); } - $self->logger->debug("[API] SAML SP $confKey configuration update requested"); + $self->logger->debug( + "[API] SAML SP $confKey configuration update requested"); # Get latest configuration my $conf = $self->_confAcc->getConf; - my $current = $self->_getSamlSpByConfKey($conf, $confKey); + my $current = $self->_getSamlSpByConfKey( $conf, $confKey ); # Return 404 if not found - unless (defined $current) { - return $self->sendError( $req, "SAML service provider '$confKey' not found", 404 ); + unless ( defined $current ) { + return $self->sendError( $req, + "SAML service provider '$confKey' not found", 404 ); } my $res; - if (defined $update->{metadata}) { + if ( defined $update->{metadata} ) { + # check if new entityId exists already - $res = $self->_isNewSamlSpEntityIdUnique($conf, $confKey, $update); - unless ($res->{res} eq 'ok') { + $res = $self->_isNewSamlSpEntityIdUnique( $conf, $confKey, $update ); + unless ( $res->{res} eq 'ok' ) { return $self->sendError( $req, $res->{msg}, 405 ); } } - $res = $self->_pushSamlSp($conf, $confKey, $update, 0); - unless ($res->{res} eq 'ok') { + $res = $self->_pushSamlSp( $conf, $confKey, $update, 0 ); + unless ( $res->{res} eq 'ok' ) { return $self->sendError( $req, $res->{msg}, 405 ); } - return $self->sendJSONresponse($req, { message => "Successful operation" }); + return $self->sendJSONresponse( $req, + { message => "Successful operation" } ); } @@ -232,11 +247,12 @@ sub deleteSamlSp { # Get latest configuration my $conf = $self->_confAcc->getConf; - my $delete = $self->_getSamlSpByConfKey($conf, $confKey); + my $delete = $self->_getSamlSpByConfKey( $conf, $confKey ); # Return 404 if not found - unless (defined $delete) { - return $self->sendError( $req, "SAML service provider '$confKey' not found", 404 ); + unless ( defined $delete ) { + return $self->sendError( $req, + "SAML service provider '$confKey' not found", 404 ); } delete $conf->{samlSPMetaDataXML}->{$confKey}; @@ -246,7 +262,8 @@ sub deleteSamlSp { # Save configuration $self->_confAcc->saveConf($conf); - return $self->sendJSONresponse($req, { message => "Successful operation" }); + return $self->sendJSONresponse( $req, + { message => "Successful operation" } ); } sub _getSamlSpByConfKey { @@ -258,45 +275,39 @@ sub _getSamlSpByConfKey { } # Get metadata - my $metadata = $conf->{samlSPMetaDataXML}->{$confKey} - ->{samlSPMetaDataXML}; + my $metadata = $conf->{samlSPMetaDataXML}->{$confKey}->{samlSPMetaDataXML}; # Get options my $options = $conf->{samlSPMetaDataOptions}->{$confKey}; my $samlSp = { - confKey => $confKey, - metadata => $metadata, + confKey => $confKey, + metadata => $metadata, exportedAttributes => {}, - options => $options + options => $options }; # Get exported attributes - foreach ( - keys %{ - $conf->{samlSPMetaDataExportedAttributes} - ->{$confKey} - } - ) + foreach ( keys %{ $conf->{samlSPMetaDataExportedAttributes}->{$confKey} } ) { # Extract fields from exportedAttr value my ( $mandatory, $name, $format, $friendly_name ) = split( /;/, - $conf->{samlSPMetaDataExportedAttributes} - ->{$confKey}->{$_} ); + $conf->{samlSPMetaDataExportedAttributes}->{$confKey}->{$_} ); $mandatory = !!$mandatory ? 'true' : 'false'; $samlSp->{exportedAttributes}->{$_} = { - name => $name, + name => $name, mandatory => $mandatory }; - if (defined $friendly_name && $friendly_name ne '') { - $samlSp->{exportedAttributes}->{$_}->{friendlyName} = $friendly_name; + if ( defined $friendly_name && $friendly_name ne '' ) { + $samlSp->{exportedAttributes}->{$_}->{friendlyName} = + $friendly_name; } - if (defined $format && $format ne '') { + if ( defined $format && $format ne '' ) { $samlSp->{exportedAttributes}->{$_}->{format} = $format; } } @@ -307,14 +318,14 @@ sub _getSamlSpByConfKey { sub _getSamlSpByEntityId { my ( $self, $conf, $entityId ) = @_; - foreach ( - keys %{ - $conf->{samlSPMetaDataXML} - } - ) - { - if ($self->_readSamlSpEntityId($conf->{samlSPMetaDataXML}->{$_}->{samlSPMetaDataXML}) eq $entityId) { - return $self->_getSamlSpByConfKey($conf, $_); + foreach ( keys %{ $conf->{samlSPMetaDataXML} } ) { + if ( + $self->_readSamlSpEntityId( + $conf->{samlSPMetaDataXML}->{$_}->{samlSPMetaDataXML} + ) eq $entityId + ) + { + return $self->_getSamlSpByConfKey( $conf, $_ ); } } return undef; @@ -322,43 +333,51 @@ sub _getSamlSpByEntityId { sub _readSamlSpEntityId { my ( $self, $metadata ) = @_; - if ($metadata =~ /entityID=['"](.+?)['"]/) { + if ( $metadata =~ /entityID=['"](.+?)['"]/ ) { return $1; } return undef; } + sub _readSamlSpExportedAttributes { my ( $self, $attrs, $mergeWith ) = @_; my $allowedFormats = [ - "urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified", - "urn:oasis:names:tc:SAML:2.0:attrname-format:uri", - "urn:oasis:names:tc:SAML:2.0:attrname-format:basic" + "urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified", + "urn:oasis:names:tc:SAML:2.0:attrname-format:uri", + "urn:oasis:names:tc:SAML:2.0:attrname-format:basic" ]; - foreach (keys %{$attrs}) { - unless (defined $attrs->{$_}->{name}) { - return { res => "ko", msg => "Exported attribute $_ has no name"}; + foreach ( keys %{$attrs} ) { + unless ( defined $attrs->{$_}->{name} ) { + return { res => "ko", msg => "Exported attribute $_ has no name" }; } - my $mandatory = 0; - my $name = $attrs->{$_}->{name}; - my $format = ''; + my $mandatory = 0; + my $name = $attrs->{$_}->{name}; + my $format = ''; my $friendlyName = ''; - if (defined $mergeWith->{$_}) { - ( $mandatory, $name, $format, $friendlyName ) = split( /;/, $mergeWith->{$_} ); + if ( defined $mergeWith->{$_} ) { + ( $mandatory, $name, $format, $friendlyName ) = + split( /;/, $mergeWith->{$_} ); } - if (defined $attrs->{$_}->{mandatory}) { - if ( $attrs->{$_}->{mandatory} eq '1' or $attrs->{$_}->{mandatory} eq 'true' ) { + if ( defined $attrs->{$_}->{mandatory} ) { + if ( $attrs->{$_}->{mandatory} eq '1' + or $attrs->{$_}->{mandatory} eq 'true' ) + { $mandatory = 1; - } else { + } + else { $mandatory = 0; } } - if (defined $attrs->{$_}->{format}) { + if ( defined $attrs->{$_}->{format} ) { $format = $attrs->{$_}->{format}; - unless (length(grep {/^$format$/} @{$allowedFormats})) { - return { res => "ko", msg => "Exported attribute $_ format does not exist."}; + unless ( length( grep { /^$format$/ } @{$allowedFormats} ) ) { + return { + res => "ko", + msg => "Exported attribute $_ format does not exist." + }; } } - if (defined $attrs->{$_}->{friendlyName}) { + if ( defined $attrs->{$_}->{friendlyName} ) { $friendlyName = $attrs->{$_}->{friendlyName}; } $mergeWith->{$_} = "$mandatory;$name;$format;$friendlyName"; @@ -367,33 +386,40 @@ sub _readSamlSpExportedAttributes { } sub _pushSamlSp { - my ( $self, $conf, $confKey, $push, $replace) = @_; + my ( $self, $conf, $confKey, $push, $replace ) = @_; if ($replace) { - $conf->{samlSPMetaDataXML}->{$confKey} = {}; - $conf->{samlSPMetaDataOptions}->{$confKey} = {}; + $conf->{samlSPMetaDataXML}->{$confKey} = {}; + $conf->{samlSPMetaDataOptions}->{$confKey} = {}; $conf->{samlSPMetaDataExportedAttributes}->{$confKey} = {}; - $push->{options} = $self->_setDefaultValues($push->{options}, 'samlSPMetaDataNode'); + $push->{options} = + $self->_setDefaultValues( $push->{options}, 'samlSPMetaDataNode' ); } - $conf->{samlSPMetaDataXML}->{$confKey}->{samlSPMetaDataXML} = $push->{metadata}; + $conf->{samlSPMetaDataXML}->{$confKey}->{samlSPMetaDataXML} = + $push->{metadata}; - if (defined $push->{options}) { - my $res = $self->_hasAllowedAttributes($push->{options}, 'samlSPMetaDataNode'); - unless ($res->{res} eq 'ok') { + if ( defined $push->{options} ) { + my $res = $self->_hasAllowedAttributes( $push->{options}, + 'samlSPMetaDataNode' ); + unless ( $res->{res} eq 'ok' ) { return $res; } - foreach (keys %{$push->{options}}) { - $conf->{samlSPMetaDataOptions}->{$confKey}->{$_} = $push->{options}->{$_}; + foreach ( keys %{ $push->{options} } ) { + $conf->{samlSPMetaDataOptions}->{$confKey}->{$_} = + $push->{options}->{$_}; } } - if (defined $push->{exportedAttributes}) { - my $res = $self->_readSamlSpExportedAttributes($push->{exportedAttributes}, $conf->{samlSPMetaDataExportedAttributes}->{$confKey}); - unless ($res->{res} eq 'ok') { + if ( defined $push->{exportedAttributes} ) { + my $res = + $self->_readSamlSpExportedAttributes( $push->{exportedAttributes}, + $conf->{samlSPMetaDataExportedAttributes}->{$confKey} ); + unless ( $res->{res} eq 'ok' ) { return $res; } - $conf->{samlSPMetaDataExportedAttributes}->{$confKey} = $res->{exportedAttributes}; + $conf->{samlSPMetaDataExportedAttributes}->{$confKey} = + $res->{exportedAttributes}; } # Save configuration @@ -403,12 +429,18 @@ sub _pushSamlSp { } sub _isNewSamlSpEntityIdUnique { - my ( $self, $conf, $confKey, $newSp) = @_; - my $newEntityId = $self->_readSamlSpEntityId($newSp->{metadata}); - my $curEntityId = $self->_readSamlSpEntityId($self->_getSamlSpByConfKey($conf, $confKey)->{metadata}); - if ($newEntityId ne $curEntityId) { - if (defined $self->_getSamlSpByEntityId($conf, $newEntityId) ) { - return { res => 'ko', msg => "An SAML service provide with entityId '$newEntityId' already exists" }; + my ( $self, $conf, $confKey, $newSp ) = @_; + my $newEntityId = $self->_readSamlSpEntityId( $newSp->{metadata} ); + my $curEntityId = + $self->_readSamlSpEntityId( + $self->_getSamlSpByConfKey( $conf, $confKey )->{metadata} ); + if ( $newEntityId ne $curEntityId ) { + if ( defined $self->_getSamlSpByEntityId( $conf, $newEntityId ) ) { + return { + res => 'ko', + msg => +"An SAML service provide with entityId '$newEntityId' already exists" + }; } } return { res => 'ok' }; diff --git a/lemonldap-ng-manager/t/04-providers-api.t b/lemonldap-ng-manager/t/04-providers-api.t index 9277b2150a..8f3efe7a28 100644 --- a/lemonldap-ng-manager/t/04-providers-api.t +++ b/lemonldap-ng-manager/t/04-providers-api.t @@ -10,41 +10,50 @@ our $_json = JSON->new->allow_nonref; sub check200 { my ( $test, $res ) = splice @_; + #diag Dumper($res); ok( $res->[0] == 200, "$test: Result code is 200" ); count(1); - checkJson($test, $res); + checkJson( $test, $res ); } + sub check404 { my ( $test, $res ) = splice @_; + #diag Dumper($res); ok( $res->[0] == 404, "$test: Result code is 404" ); count(1); - checkJson($test, $res); + checkJson( $test, $res ); } + sub check405 { my ( $test, $res ) = splice @_; ok( $res->[0] == 405, "$test: Result code is 405" ); count(1); - checkJson($test, $res); + checkJson( $test, $res ); } + sub checkJson { my ( $test, $res ) = splice @_; my $key; + #diag Dumper($res->[2]->[0]); ok( $key = from_json( $res->[2]->[0] ), "$test: Response is JSON" ); count(1); } sub add { - my ( $test, $type, $obj) = splice @_; + my ( $test, $type, $obj ) = splice @_; my $j = $_json->encode($obj); my $res; + #diag Dumper($j); ok( $res = &client->_post( - "/v1/providers/$type", '', IO::String->new($j), 'application/json', length($j) + "/v1/providers/$type", '', + IO::String->new($j), 'application/json', + length($j) ), "$test: Request succeed" ); @@ -53,60 +62,61 @@ sub add { } sub checkAdd { - my ( $test, $type, $add) = splice @_; - check200($test, add($test, $type, $add)); + my ( $test, $type, $add ) = splice @_; + check200( $test, add( $test, $type, $add ) ); } sub checkAddFailsIfExists { - my ( $test, $type, $add) = splice @_; - check405($test, add($test, $type, $add)); + my ( $test, $type, $add ) = splice @_; + check405( $test, add( $test, $type, $add ) ); } sub checkAddWithUnknownAttributes { - my ( $test, $type, $add) = splice @_; - check405($test, add($test, $type, $add)); + my ( $test, $type, $add ) = splice @_; + check405( $test, add( $test, $type, $add ) ); } sub get { - my ( $test, $type, $confKey) = splice @_; + my ( $test, $type, $confKey ) = splice @_; my $res; - ok( - $res = &client->_get("/v1/providers/$type/$confKey", ''), - "$test: Request succeed" - ); + ok( $res = &client->_get( "/v1/providers/$type/$confKey", '' ), + "$test: Request succeed" ); count(1); return $res; } sub checkGet { - my ( $test, $type, $confKey, $attrPath, $expectedValue) = splice @_; - my $res = get($test, $type, $confKey); - check200($test, $res); + my ( $test, $type, $confKey, $attrPath, $expectedValue ) = splice @_; + my $res = get( $test, $type, $confKey ); + check200( $test, $res ); my @path = split '/', $attrPath; - my $key = from_json($res->[2]->[0]); + my $key = from_json( $res->[2]->[0] ); for (@path) { $key = $key->{$_}; } - ok ( + ok( $key eq $expectedValue, - "$test: check if $attrPath value \"$key\" matches expected value \"$expectedValue\"" +"$test: check if $attrPath value \"$key\" matches expected value \"$expectedValue\"" ); count(1); } sub checkGetNotFound { - my ( $test, $type, $confKey) = splice @_; - check404($test, get($test, $type, $confKey)); + my ( $test, $type, $confKey ) = splice @_; + check404( $test, get( $test, $type, $confKey ) ); } sub update { - my ( $test, $type, $confKey, $obj) = splice @_; + my ( $test, $type, $confKey, $obj ) = splice @_; my $j = $_json->encode($obj); + #diag Dumper($j); my $res; ok( $res = &client->_patch( - "/v1/providers/$type/$confKey", '', IO::String->new($j), 'application/json', length($j) + "/v1/providers/$type/$confKey", '', + IO::String->new($j), 'application/json', + length($j) ), "$test: Request succeed" ); @@ -115,32 +125,34 @@ sub update { } sub checkUpdate { - my ( $test, $type, $confKey, $update) = splice @_; - check200($test, update($test, $type, $confKey, $update)); + my ( $test, $type, $confKey, $update ) = splice @_; + check200( $test, update( $test, $type, $confKey, $update ) ); } sub checkUpdateNotFound { - my ( $test, $type, $confKey, $update) = splice @_; - check404($test, update($test, $type, $confKey, $update)); + my ( $test, $type, $confKey, $update ) = splice @_; + check404( $test, update( $test, $type, $confKey, $update ) ); } sub checkUpdateFailsIfExists { - my ( $test, $type, $confKey, $update) = splice @_; - check405($test, update($test, $type, $confKey, $update)); + my ( $test, $type, $confKey, $update ) = splice @_; + check405( $test, update( $test, $type, $confKey, $update ) ); } sub checkUpdateWithUnknownAttributes { - my ( $test, $type, $confKey, $update) = splice @_; - check405($test, update($test, $type, $confKey, $update)); + my ( $test, $type, $confKey, $update ) = splice @_; + check405( $test, update( $test, $type, $confKey, $update ) ); } sub replace { - my ( $test, $type, $confKey, $obj) = splice @_; + my ( $test, $type, $confKey, $obj ) = splice @_; my $j = $_json->encode($obj); my $res; ok( $res = &client->_put( - "/v1/providers/$type/$confKey", '', IO::String->new($j), 'application/json', length($j) + "/v1/providers/$type/$confKey", '', + IO::String->new($j), 'application/json', + length($j) ), "$test: Request succeed" ); @@ -149,30 +161,33 @@ sub replace { } sub checkReplace { - my ( $test, $type, $confKey, $replace) = splice @_; - check200($test, replace($test, $type, $confKey, $replace)); + my ( $test, $type, $confKey, $replace ) = splice @_; + check200( $test, replace( $test, $type, $confKey, $replace ) ); } sub checkReplaceAlreadyThere { - my ( $test, $type, $confKey, $replace) = splice @_; - check405($test, replace($test, $type, $confKey, $replace)); + my ( $test, $type, $confKey, $replace ) = splice @_; + check405( $test, replace( $test, $type, $confKey, $replace ) ); } sub checkReplaceNotFound { - my ( $test, $type, $confKey, $update) = splice @_; - check404($test, replace($test, $type, $confKey, $update)); + my ( $test, $type, $confKey, $update ) = splice @_; + check404( $test, replace( $test, $type, $confKey, $update ) ); } sub checkReplaceWithUnknownAttribute { - my ( $test, $type, $confKey, $replace) = splice @_; - check405($test, replace($test, $type, $confKey, $replace)); + my ( $test, $type, $confKey, $replace ) = splice @_; + check405( $test, replace( $test, $type, $confKey, $replace ) ); } sub findByConfKey { - my ( $test, $type, $confKey) = splice @_; + my ( $test, $type, $confKey ) = splice @_; my $res; ok( - $res = &client->_get("/v1/providers/$type/findByConfKey", "pattern=$confKey"), + $res = &client->_get( + "/v1/providers/$type/findByConfKey", + "pattern=$confKey" + ), "$test: Request succeed" ); count(1); @@ -180,32 +195,35 @@ sub findByConfKey { } sub checkFindByConfKey { - my ( $test, $type, $confKey, $expectedHits) = splice @_; - my $res = findByConfKey($test, $type, $confKey); - check200($test, $res); - my $hits = from_json($res->[2]->[0]); + my ( $test, $type, $confKey, $expectedHits ) = splice @_; + my $res = findByConfKey( $test, $type, $confKey ); + check200( $test, $res ); + my $hits = from_json( $res->[2]->[0] ); my $hit; my $counter = 0; - foreach $hit (@{$hits}) { + foreach $hit ( @{$hits} ) { $counter++; - ok ( + ok( $hit->{confKey} =~ $confKey, - "$test: check if confKey value \"$hit->{confKey}\" matches pattern \"$confKey\"" +"$test: check if confKey value \"$hit->{confKey}\" matches pattern \"$confKey\"" ); count(1); } - ok ( + ok( $counter eq $expectedHits, - "$test: check if nb of hits returned ($counter) matches expectation ($expectedHits)" +"$test: check if nb of hits returned ($counter) matches expectation ($expectedHits)" ); count(1); } sub findByProviderId { - my ( $test, $type, $providerIdName, $providerId) = splice @_; + my ( $test, $type, $providerIdName, $providerId ) = splice @_; my $res; ok( - $res = &client->_get("/v1/providers/$type/findBy" . ucfirst $providerIdName, "$providerIdName=$providerId"), + $res = &client->_get( + "/v1/providers/$type/findBy" . ucfirst $providerIdName, + "$providerIdName=$providerId" + ), "$test: Request succeed" ); count(1); @@ -213,66 +231,66 @@ sub findByProviderId { } sub checkFindByProviderId { - my ( $test, $type, $providerIdName, $providerId) = splice @_; - my $res = findByProviderId($test, $type, $providerIdName, $providerId); - check200($test, $res); - my $result = from_json($res->[2]->[0]); + my ( $test, $type, $providerIdName, $providerId ) = splice @_; + my $res = findByProviderId( $test, $type, $providerIdName, $providerId ); + check200( $test, $res ); + my $result = from_json( $res->[2]->[0] ); my $gotProviderId; - if ($providerIdName eq 'entityId') { - ( $gotProviderId ) = $result->{metadata} =~ m/entityID=['"](.+?)['"]/i; - } else { + if ( $providerIdName eq 'entityId' ) { + ($gotProviderId) = $result->{metadata} =~ m/entityID=['"](.+?)['"]/i; + } + else { $gotProviderId = $result->{$providerIdName}; } ok( $gotProviderId eq $providerId, - "$test: Check $providerIdName value returned \"$gotProviderId\" matched expected value \"$providerId\"" +"$test: Check $providerIdName value returned \"$gotProviderId\" matched expected value \"$providerId\"" ); count(1); } sub checkFindByProviderIdNotFound { - my ( $test, $type, $providerIdName, $providerId) = splice @_; - my $res = findByProviderId($test, $type, $providerIdName, $providerId); - check200($test, $res); - my $result = from_json($res->[2]->[0]); - ok( - !defined $result->{$providerIdName}, - "$test: Check object is empty" - ); + my ( $test, $type, $providerIdName, $providerId ) = splice @_; + my $res = findByProviderId( $test, $type, $providerIdName, $providerId ); + check200( $test, $res ); + my $result = from_json( $res->[2]->[0] ); + ok( !defined $result->{$providerIdName}, "$test: Check object is empty" ); count(1); } sub deleteProvider { - my ( $test, $type, $confKey) = splice @_; + my ( $test, $type, $confKey ) = splice @_; my $res; ok( $res = &client->_del( - "/v1/providers/$type/$confKey", '', '', 'application/json', 0 + "/v1/providers/$type/$confKey", + '', '', 'application/json', 0 ), "$test: Request succeed" ); count(1); return $res; } + sub checkDelete { - my ( $test, $type, $confKey) = splice @_; - check200($test, deleteProvider($test, $type, $confKey)); + my ( $test, $type, $confKey ) = splice @_; + check200( $test, deleteProvider( $test, $type, $confKey ) ); } sub checkDeleteNotFound { - my ( $test, $type, $confKey) = splice @_; - check404($test, deleteProvider($test, $type, $confKey)); + my ( $test, $type, $confKey ) = splice @_; + check404( $test, deleteProvider( $test, $type, $confKey ) ); } my $test; my $oidcRp = { - confKey => 'myOidcRp1', - clientId => 'myOidcClient1', + confKey => 'myOidcRp1', + clientId => 'myOidcClient1', exportedVars => { - 'sub' => "uid", + 'sub' => "uid", family_name => "sn", - given_name => "givenName" + given_name => "givenName" }, extraClaim => { phone => 'telephoneNumber', @@ -280,243 +298,265 @@ my $oidcRp = { }, options => { oidcRPMetaDataOptionsClientSecret => 'secret', - oidcRPMetaDataOptionsIcon => 'web.png' + oidcRPMetaDataOptionsIcon => 'web.png' } }; $test = "OidcRp - Add should succeed"; -checkAdd($test, 'oidc/rp', $oidcRp); -checkGet($test, 'oidc/rp', 'myOidcRp1', 'options/oidcRPMetaDataOptionsIcon', 'web.png'); -checkGet($test, 'oidc/rp', 'myOidcRp1', 'options/oidcRPMetaDataOptionsClientSecret', 'secret'); +checkAdd( $test, 'oidc/rp', $oidcRp ); +checkGet( $test, 'oidc/rp', 'myOidcRp1', 'options/oidcRPMetaDataOptionsIcon', + 'web.png' ); +checkGet( $test, 'oidc/rp', 'myOidcRp1', + 'options/oidcRPMetaDataOptionsClientSecret', 'secret' ); $test = "OidcRp - Check attribute default value was set after add"; -checkGet($test, 'oidc/rp', 'myOidcRp1', 'options/oidcRPMetaDataOptionsIDTokenSignAlg', 'HS512'); +checkGet( $test, 'oidc/rp', 'myOidcRp1', + 'options/oidcRPMetaDataOptionsIDTokenSignAlg', 'HS512' ); $test = "OidcRp - Add Should fail on duplicate confKey"; -checkAddFailsIfExists($test, 'oidc/rp', $oidcRp); +checkAddFailsIfExists( $test, 'oidc/rp', $oidcRp ); $test = "OidcRp - Update should succeed and keep existing values"; -$oidcRp->{options}->{oidcRPMetaDataOptionsClientSecret} = 'secret2'; +$oidcRp->{options}->{oidcRPMetaDataOptionsClientSecret} = 'secret2'; $oidcRp->{options}->{oidcRPMetaDataOptionsIDTokenSignAlg} = 'RS512'; delete $oidcRp->{options}->{oidcRPMetaDataOptionsIcon}; delete $oidcRp->{extraClaim}; delete $oidcRp->{exportedVars}; $oidcRp->{exportedVars}->{cn} = 'cn'; -checkUpdate($test, 'oidc/rp', 'myOidcRp1', $oidcRp); -checkGet($test, 'oidc/rp', 'myOidcRp1', 'options/oidcRPMetaDataOptionsClientSecret', 'secret2'); -checkGet($test, 'oidc/rp', 'myOidcRp1', 'options/oidcRPMetaDataOptionsIDTokenSignAlg', 'RS512'); -checkGet($test, 'oidc/rp', 'myOidcRp1', 'options/oidcRPMetaDataOptionsIcon', 'web.png'); -checkGet($test, 'oidc/rp', 'myOidcRp1', 'exportedVars/cn', 'cn'); -checkGet($test, 'oidc/rp', 'myOidcRp1', 'exportedVars/family_name', 'sn'); -checkGet($test, 'oidc/rp', 'myOidcRp1', 'extraClaim/phone', 'telephoneNumber'); +checkUpdate( $test, 'oidc/rp', 'myOidcRp1', $oidcRp ); +checkGet( $test, 'oidc/rp', 'myOidcRp1', + 'options/oidcRPMetaDataOptionsClientSecret', 'secret2' ); +checkGet( $test, 'oidc/rp', 'myOidcRp1', + 'options/oidcRPMetaDataOptionsIDTokenSignAlg', 'RS512' ); +checkGet( $test, 'oidc/rp', 'myOidcRp1', 'options/oidcRPMetaDataOptionsIcon', + 'web.png' ); +checkGet( $test, 'oidc/rp', 'myOidcRp1', 'exportedVars/cn', 'cn' ); +checkGet( $test, 'oidc/rp', 'myOidcRp1', 'exportedVars/family_name', 'sn' ); +checkGet( $test, 'oidc/rp', 'myOidcRp1', 'extraClaim/phone', + 'telephoneNumber' ); $test = "OidcRp - Update should fail on non existing options"; $oidcRp->{options}->{playingPossum} = 'elephant'; -checkUpdateWithUnknownAttributes($test, 'oidc/rp', 'myOidcRp1', $oidcRp); +checkUpdateWithUnknownAttributes( $test, 'oidc/rp', 'myOidcRp1', $oidcRp ); delete $oidcRp->{options}->{playingPossum}; $test = "OidcRp - Add Should fail on duplicate clientId"; $oidcRp->{confKey} = 'myOidcRp2'; -checkAddFailsIfExists($test, 'oidc/rp', $oidcRp); +checkAddFailsIfExists( $test, 'oidc/rp', $oidcRp ); -$test = "OidcRp - Add Should fail on non existing options"; -$oidcRp->{confKey} = 'myOidcRp2'; +$test = "OidcRp - Add Should fail on non existing options"; +$oidcRp->{confKey} = 'myOidcRp2'; $oidcRp->{clientId} = 'myOidcClient2'; $oidcRp->{options}->{playingPossum} = 'ElephantInTheRoom'; -checkAddWithUnknownAttributes($test, 'oidc/rp', $oidcRp); +checkAddWithUnknownAttributes( $test, 'oidc/rp', $oidcRp ); delete $oidcRp->{options}->{playingPossum}; $test = "OidcRp - 2nd add should succeed"; -checkAdd($test, 'oidc/rp', $oidcRp); +checkAdd( $test, 'oidc/rp', $oidcRp ); $test = "OidcRp - Update should fail if client id exists"; $oidcRp->{clientId} = 'myOidcClient1'; -checkUpdateFailsIfExists($test, 'oidc/rp', 'myOidcRp2', $oidcRp); +checkUpdateFailsIfExists( $test, 'oidc/rp', 'myOidcRp2', $oidcRp ); $test = "OidcRp - Update should fail if confKey not found"; $oidcRp->{confKey} = 'myOidcRp3'; -checkUpdateNotFound($test, 'oidc/rp', 'myOidcRp3', $oidcRp); +checkUpdateNotFound( $test, 'oidc/rp', 'myOidcRp3', $oidcRp ); -$test = "OidcRp - Replace should succeed"; -$oidcRp->{confKey} = 'myOidcRp2'; +$test = "OidcRp - Replace should succeed"; +$oidcRp->{confKey} = 'myOidcRp2'; $oidcRp->{clientId} = 'myOidcClient2'; delete $oidcRp->{options}->{oidcRPMetaDataOptionsIcon}; delete $oidcRp->{options}->{oidcRPMetaDataOptionsIDTokenSignAlg}; -checkReplace($test, 'oidc/rp', 'myOidcRp2', $oidcRp); +checkReplace( $test, 'oidc/rp', 'myOidcRp2', $oidcRp ); $test = "OidcRp - Check attribute default value was set after replace"; -checkGet($test, 'oidc/rp', 'myOidcRp2', 'options/oidcRPMetaDataOptionsIDTokenSignAlg', 'HS512'); +checkGet( $test, 'oidc/rp', 'myOidcRp2', + 'options/oidcRPMetaDataOptionsIDTokenSignAlg', 'HS512' ); $test = "OidcRp - Replace should fail on non existing options"; $oidcRp->{options}->{playingPossum} = 'elephant'; -checkReplaceWithUnknownAttribute($test, 'oidc/rp', 'myOidcRp2', $oidcRp); +checkReplaceWithUnknownAttribute( $test, 'oidc/rp', 'myOidcRp2', $oidcRp ); delete $oidcRp->{options}->{playingPossum}; $test = "OidcRp - Replace should fail if confKey not found"; $oidcRp->{confKey} = 'myOidcRp3'; -checkReplaceNotFound($test, 'oidc/rp', 'myOidcRp3', $oidcRp); +checkReplaceNotFound( $test, 'oidc/rp', 'myOidcRp3', $oidcRp ); $test = "OidcRp - FindByConfKey should find 2 hits"; -checkFindByConfKey($test, 'oidc/rp', '^myOidcRp.$', 2); +checkFindByConfKey( $test, 'oidc/rp', '^myOidcRp.$', 2 ); $test = "OidcRp - FindByConfKey should find 1 hit"; -checkFindByConfKey($test, 'oidc/rp', 'myOidcRp1', 1); +checkFindByConfKey( $test, 'oidc/rp', 'myOidcRp1', 1 ); $test = "OidcRp - FindByConfKey should find 0 hits"; -checkFindByConfKey($test, 'oidc/rp', 'myOidcRp3', 0); +checkFindByConfKey( $test, 'oidc/rp', 'myOidcRp3', 0 ); $test = "OidcRp - FindByClientId should find one entry"; -checkFindByProviderId($test, 'oidc/rp', 'clientId', 'myOidcClient1'); +checkFindByProviderId( $test, 'oidc/rp', 'clientId', 'myOidcClient1' ); $test = "OidcRp - FindByClientId should find nothing"; -checkFindByProviderIdNotFound($test, 'oidc/rp', 'clientId', 'myOidcClient3'); +checkFindByProviderIdNotFound( $test, 'oidc/rp', 'clientId', 'myOidcClient3' ); $test = "OidcRp - Clean up"; -checkDelete($test, 'oidc/rp', 'myOidcRp1'); -checkDelete($test, 'oidc/rp', 'myOidcRp2'); +checkDelete( $test, 'oidc/rp', 'myOidcRp1' ); +checkDelete( $test, 'oidc/rp', 'myOidcRp2' ); $test = "OidcRp - Entity should not be found after clean up"; -checkDeleteNotFound($test, 'oidc/rp', 'myOidcRp1'); +checkDeleteNotFound( $test, 'oidc/rp', 'myOidcRp1' ); -my $metadata1 = "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"; +my $metadata1 = +"urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"; -my $metadata2 = "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"; +my $metadata2 = +"urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"; my $samlSp = { - confKey => 'mySamlSp1', - metadata => $metadata1, + confKey => 'mySamlSp1', + metadata => $metadata1, exportedAttributes => { family_name => { - format => "urn:oasis:names:tc:SAML:2.0:attrname-format:basic", - friendlyName => "surname", - mandatory => "false", - name => "sn" + format => "urn:oasis:names:tc:SAML:2.0:attrname-format:basic", + friendlyName => "surname", + mandatory => "false", + name => "sn" }, cn => { - friendlyName => "commonname", - mandatory => "true", - name => "uid" + friendlyName => "commonname", + mandatory => "true", + name => "uid" }, uid => { - mandatory => "true", - name => "uid" + mandatory => "true", + name => "uid" }, phone => { - mandatory => "false", - format => "urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified", - name => "telephoneNumber" + mandatory => "false", + format => "urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified", + name => "telephoneNumber" }, function => { - name => "title", - mandatory => "false", - format => "urn:oasis:names:tc:SAML:2.0:attrname-format:uri" + name => "title", + mandatory => "false", + format => "urn:oasis:names:tc:SAML:2.0:attrname-format:uri" }, given_name => { - mandatory => "false", - name => "givenName" + mandatory => "false", + name => "givenName" } }, options => { - samlSPMetaDataOptionsCheckSLOMessageSignature => 0, - samlSPMetaDataOptionsEncryptionMode => "assertion", - samlSPMetaDataOptionsSessionNotOnOrAfterTimeout => 36000 + samlSPMetaDataOptionsCheckSLOMessageSignature => 0, + samlSPMetaDataOptionsEncryptionMode => "assertion", + samlSPMetaDataOptionsSessionNotOnOrAfterTimeout => 36000 } }; $test = "SamlSp - Add should succeed"; -checkAdd($test, 'saml/sp', $samlSp); -checkGet($test, 'saml/sp', 'mySamlSp1', 'options/samlSPMetaDataOptionsEncryptionMode', 'assertion'); -checkGet($test, 'saml/sp', 'mySamlSp1', 'options/samlSPMetaDataOptionsSessionNotOnOrAfterTimeout', 36000); +checkAdd( $test, 'saml/sp', $samlSp ); +checkGet( $test, 'saml/sp', 'mySamlSp1', + 'options/samlSPMetaDataOptionsEncryptionMode', 'assertion' ); +checkGet( $test, 'saml/sp', 'mySamlSp1', + 'options/samlSPMetaDataOptionsSessionNotOnOrAfterTimeout', 36000 ); $test = "SamlSp - Check attribute default value was set after add"; -checkGet($test, 'saml/sp', 'mySamlSp1', 'options/samlSPMetaDataOptionsNotOnOrAfterTimeout', 72000); +checkGet( $test, 'saml/sp', 'mySamlSp1', + 'options/samlSPMetaDataOptionsNotOnOrAfterTimeout', 72000 ); $test = "SamlSp - Add Should fail on duplicate confKey"; -checkAddFailsIfExists($test, 'saml/sp', $samlSp); +checkAddFailsIfExists( $test, 'saml/sp', $samlSp ); $test = "SamlSp - Update should succeed and keep existing values"; $samlSp->{options}->{samlSPMetaDataOptionsCheckSLOMessageSignature} = 1; -$samlSp->{options}->{samlSPMetaDataOptionsEncryptionMode} = 'nameid'; +$samlSp->{options}->{samlSPMetaDataOptionsEncryptionMode} = 'nameid'; delete $samlSp->{options}->{samlSPMetaDataOptionsSessionNotOnOrAfterTimeout}; delete $samlSp->{exportedAttributes}; -$samlSp->{exportedAttributes}->{cn}->{name} = "cn", -$samlSp->{exportedAttributes}->{cn}->{friendlyName} = "common_name", -$samlSp->{exportedAttributes}->{cn}->{mandatory} = "false", -checkUpdate($test, 'saml/sp', 'mySamlSp1', $samlSp); -checkGet($test, 'saml/sp', 'mySamlSp1', 'options/samlSPMetaDataOptionsCheckSLOMessageSignature', 1); -checkGet($test, 'saml/sp', 'mySamlSp1', 'options/samlSPMetaDataOptionsSessionNotOnOrAfterTimeout', 36000); -checkGet($test, 'saml/sp', 'mySamlSp1', 'exportedAttributes/cn/friendlyName', 'common_name'); -checkGet($test, 'saml/sp', 'mySamlSp1', 'exportedAttributes/cn/mandatory', 'false'); -checkGet($test, 'saml/sp', 'mySamlSp1', 'exportedAttributes/cn/mandatory', 'false'); -checkGet($test, 'saml/sp', 'mySamlSp1', 'exportedAttributes/cn/name', 'uid'); -checkGet($test, 'saml/sp', 'mySamlSp1', 'exportedAttributes/given_name/name', 'givenName'); +$samlSp->{exportedAttributes}->{cn}->{name} = "cn", + $samlSp->{exportedAttributes}->{cn}->{friendlyName} = "common_name", + $samlSp->{exportedAttributes}->{cn}->{mandatory} = "false", + checkUpdate( $test, 'saml/sp', 'mySamlSp1', $samlSp ); +checkGet( $test, 'saml/sp', 'mySamlSp1', + 'options/samlSPMetaDataOptionsCheckSLOMessageSignature', 1 ); +checkGet( $test, 'saml/sp', 'mySamlSp1', + 'options/samlSPMetaDataOptionsSessionNotOnOrAfterTimeout', 36000 ); +checkGet( $test, 'saml/sp', 'mySamlSp1', 'exportedAttributes/cn/friendlyName', + 'common_name' ); +checkGet( $test, 'saml/sp', 'mySamlSp1', 'exportedAttributes/cn/mandatory', + 'false' ); +checkGet( $test, 'saml/sp', 'mySamlSp1', 'exportedAttributes/cn/mandatory', + 'false' ); +checkGet( $test, 'saml/sp', 'mySamlSp1', 'exportedAttributes/cn/name', 'uid' ); +checkGet( $test, 'saml/sp', 'mySamlSp1', 'exportedAttributes/given_name/name', + 'givenName' ); $test = "SamlSp - Update should fail on non existing options"; $samlSp->{options}->{playingPossum} = 'elephant'; -checkUpdateWithUnknownAttributes($test, 'saml/sp', 'mySamlSp1', $samlSp); +checkUpdateWithUnknownAttributes( $test, 'saml/sp', 'mySamlSp1', $samlSp ); delete $samlSp->{options}->{playingPossum}; $test = "SamlSp - Add Should fail on duplicate entityId"; $samlSp->{confKey} = 'mySamlSp2'; -checkAddFailsIfExists($test, 'saml/sp', $samlSp); +checkAddFailsIfExists( $test, 'saml/sp', $samlSp ); -$test = "SamlSp - Add Should fail on non existing options"; -$samlSp->{confKey} = 'mySamlSp2'; +$test = "SamlSp - Add Should fail on non existing options"; +$samlSp->{confKey} = 'mySamlSp2'; $samlSp->{metadata} = $metadata2; $samlSp->{options}->{playingPossum} = 'ElephantInTheRoom'; -checkAddWithUnknownAttributes($test, 'saml/sp', $samlSp); +checkAddWithUnknownAttributes( $test, 'saml/sp', $samlSp ); delete $samlSp->{options}->{playingPossum}; $test = "SamlSp - 2nd add should succeed"; -checkAdd($test, 'saml/sp', $samlSp); +checkAdd( $test, 'saml/sp', $samlSp ); $test = "SamlSp - Update should fail if client id exists"; $samlSp->{metadata} = $metadata1; -checkUpdateFailsIfExists($test, 'saml/sp', 'mySamlSp2', $samlSp); +checkUpdateFailsIfExists( $test, 'saml/sp', 'mySamlSp2', $samlSp ); $test = "SamlSp - Update should fail if confKey not found"; $samlSp->{confKey} = 'mySamlSp3'; -checkUpdateNotFound($test, 'saml/sp', 'mySamlSp3', $samlSp); +checkUpdateNotFound( $test, 'saml/sp', 'mySamlSp3', $samlSp ); -$test = "SamlSp - Replace should succeed"; -$samlSp->{confKey} = 'mySamlSp2'; +$test = "SamlSp - Replace should succeed"; +$samlSp->{confKey} = 'mySamlSp2'; $samlSp->{metadata} = $metadata2; delete $samlSp->{options}->{samlSPMetaDataOptionsEncryptionMode}; -checkReplace($test, 'saml/sp', 'mySamlSp2', $samlSp); +checkReplace( $test, 'saml/sp', 'mySamlSp2', $samlSp ); $test = "SamlSp - Check attribute default value was set after replace"; -checkGet($test, 'saml/sp', 'mySamlSp2', 'options/samlSPMetaDataOptionsEncryptionMode', 'none'); +checkGet( $test, 'saml/sp', 'mySamlSp2', + 'options/samlSPMetaDataOptionsEncryptionMode', 'none' ); $test = "SamlSp - Replace should fail on non existing options"; $samlSp->{options}->{playingPossum} = 'elephant'; -checkReplaceWithUnknownAttribute($test, 'saml/sp', 'mySamlSp2', $samlSp); +checkReplaceWithUnknownAttribute( $test, 'saml/sp', 'mySamlSp2', $samlSp ); delete $samlSp->{options}->{playingPossum}; $test = "SamlSp - Replace should fail if confKey not found"; $samlSp->{confKey} = 'mySamlSp3'; -checkReplaceNotFound($test, 'saml/sp', 'mySamlSp3', $samlSp); +checkReplaceNotFound( $test, 'saml/sp', 'mySamlSp3', $samlSp ); $test = "SamlSp - FindByConfKey should find 2 hits"; -checkFindByConfKey($test, 'saml/sp', '^mySamlSp.$', 2); +checkFindByConfKey( $test, 'saml/sp', '^mySamlSp.$', 2 ); $test = "SamlSp - FindByConfKey should find 1 hit"; -checkFindByConfKey($test, 'saml/sp', 'mySamlSp1', 1); +checkFindByConfKey( $test, 'saml/sp', 'mySamlSp1', 1 ); $test = "SamlSp - FindByConfKey should find 0 hits"; -checkFindByConfKey($test, 'saml/sp', 'mySamlSp3', 0); +checkFindByConfKey( $test, 'saml/sp', 'mySamlSp3', 0 ); $test = "SamlSp - FindByEntityId should find one entry"; -checkFindByProviderId($test, 'saml/sp', 'entityId', 'https://myapp.domain.com/saml/metadata'); +checkFindByProviderId( $test, 'saml/sp', 'entityId', + 'https://myapp.domain.com/saml/metadata' ); $test = "SamlSp - FindByEntityId should find nothing"; -checkFindByProviderIdNotFound($test, 'saml/sp', 'entityId', 'https://myapp3.domain.com/saml/metadata'); +checkFindByProviderIdNotFound( $test, 'saml/sp', 'entityId', + 'https://myapp3.domain.com/saml/metadata' ); $test = "SamlSp - Clean up"; -checkDelete($test, 'saml/sp', 'mySamlSp1'); -checkDelete($test, 'saml/sp', 'mySamlSp2'); +checkDelete( $test, 'saml/sp', 'mySamlSp1' ); +checkDelete( $test, 'saml/sp', 'mySamlSp2' ); $test = "SamlSp - Entity should not be found after clean up"; -checkDeleteNotFound($test, 'saml/sp', 'mySamlSp1'); +checkDeleteNotFound( $test, 'saml/sp', 'mySamlSp1' ); # Clean up generated conf files, except for "lmConf-1.json" unlink grep { $_ ne "t/conf/lmConf-1.json" } glob "t/conf/lmConf-*.json"; -done_testing( ); +done_testing(); -- GitLab From bf6e7c2afdefd869d87efcbba32841a05be09ddf Mon Sep 17 00:00:00 2001 From: Soisik Froger Date: Wed, 18 Dec 2019 15:45:42 +0000 Subject: [PATCH 22/31] Manager API - Change all providers API routes from /v1/providers to /api/v1/providers - #2034 --- .../lib/Lemonldap/NG/Manager/Api.pm | 104 +++-- lemonldap-ng-manager/t/04-providers-api.t | 406 ++++++++---------- 2 files changed, 240 insertions(+), 270 deletions(-) diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api.pm index 8d64941125..45d4c214b7 100644 --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api.pm @@ -35,28 +35,30 @@ sub addRoutes { ) ->addRoute( - v1 => { - providers => { - oidc => { - rp => { - findByConfKey => { - ':uPattern' => 'findOidcRpByConfKey' - }, - findByClientId => { - ':uClientId' => 'findOidcRpByClientId' + api => { + v1 => { + providers => { + oidc => { + rp => { + findByConfKey => { + ':uPattern' => 'findOidcRpByConfKey' + }, + findByClientId => { + ':uClientId' => 'findOidcRpByClientId' + }, + ':confKey' => 'getOidcRpByConfKey' }, - ':confKey' => 'getOidcRpByConfKey' }, - }, - saml => { - sp => { - findByConfKey => { - ':uPattern' => 'findSamlSpByConfKey' + saml => { + sp => { + findByConfKey => { + ':uPattern' => 'findSamlSpByConfKey' + }, + findByEntityId => { + ':uEntityId' => 'findSamlSpByEntityId' + }, + ':confKey' => 'getSamlSpByConfKey' }, - findByEntityId => { - ':uEntityId' => 'findSamlSpByEntityId' - }, - ':confKey' => 'getSamlSpByConfKey' }, }, }, @@ -65,13 +67,15 @@ sub addRoutes { ) ->addRoute( - v1 => { - providers => { - oidc => { - rp => 'addOidcRp' - }, - saml => { - sp => 'addSamlSp' + api => { + v1 => { + providers => { + oidc => { + rp => 'addOidcRp' + }, + saml => { + sp => 'addSamlSp' + }, }, }, }, @@ -79,13 +83,15 @@ sub addRoutes { ) ->addRoute( - v1 => { - providers => { - oidc => { - rp => { ':confKey' => 'replaceOidcRp' } - }, - saml => { - sp => { ':confKey' => 'replaceSamlSp' } + api => { + v1 => { + providers => { + oidc => { + rp => {':confKey' => 'replaceOidcRp'} + }, + saml => { + sp => {':confKey' => 'replaceSamlSp'} + }, }, }, }, @@ -93,13 +99,15 @@ sub addRoutes { ) ->addRoute( - v1 => { - providers => { - oidc => { - rp => { ':confKey' => 'updateOidcRp' } - }, - saml => { - sp => { ':confKey' => 'updateSamlSp' } + api => { + v1 => { + providers => { + oidc => { + rp => {':confKey' => 'updateOidcRp'} + }, + saml => { + sp => {':confKey' => 'updateSamlSp'} + }, }, }, }, @@ -107,13 +115,15 @@ sub addRoutes { ) ->addRoute( - v1 => { - providers => { - oidc => { - rp => { ':confKey' => 'deleteOidcRp' } - }, - saml => { - sp => { ':confKey' => 'deleteSamlSp' } + api => { + v1 => { + providers => { + oidc => { + rp => {':confKey' => 'deleteOidcRp'} + }, + saml => { + sp => {':confKey' => 'deleteSamlSp'} + }, }, }, }, diff --git a/lemonldap-ng-manager/t/04-providers-api.t b/lemonldap-ng-manager/t/04-providers-api.t index 8f3efe7a28..c43c8d8d3e 100644 --- a/lemonldap-ng-manager/t/04-providers-api.t +++ b/lemonldap-ng-manager/t/04-providers-api.t @@ -10,50 +10,41 @@ our $_json = JSON->new->allow_nonref; sub check200 { my ( $test, $res ) = splice @_; - #diag Dumper($res); ok( $res->[0] == 200, "$test: Result code is 200" ); count(1); - checkJson( $test, $res ); + checkJson($test, $res); } - sub check404 { my ( $test, $res ) = splice @_; - #diag Dumper($res); ok( $res->[0] == 404, "$test: Result code is 404" ); count(1); - checkJson( $test, $res ); + checkJson($test, $res); } - sub check405 { my ( $test, $res ) = splice @_; ok( $res->[0] == 405, "$test: Result code is 405" ); count(1); - checkJson( $test, $res ); + checkJson($test, $res); } - sub checkJson { my ( $test, $res ) = splice @_; my $key; - #diag Dumper($res->[2]->[0]); ok( $key = from_json( $res->[2]->[0] ), "$test: Response is JSON" ); count(1); } sub add { - my ( $test, $type, $obj ) = splice @_; + my ( $test, $type, $obj) = splice @_; my $j = $_json->encode($obj); my $res; - #diag Dumper($j); ok( $res = &client->_post( - "/v1/providers/$type", '', - IO::String->new($j), 'application/json', - length($j) + "/api/v1/providers/$type", '', IO::String->new($j), 'application/json', length($j) ), "$test: Request succeed" ); @@ -62,61 +53,60 @@ sub add { } sub checkAdd { - my ( $test, $type, $add ) = splice @_; - check200( $test, add( $test, $type, $add ) ); + my ( $test, $type, $add) = splice @_; + check200($test, add($test, $type, $add)); } sub checkAddFailsIfExists { - my ( $test, $type, $add ) = splice @_; - check405( $test, add( $test, $type, $add ) ); + my ( $test, $type, $add) = splice @_; + check405($test, add($test, $type, $add)); } sub checkAddWithUnknownAttributes { - my ( $test, $type, $add ) = splice @_; - check405( $test, add( $test, $type, $add ) ); + my ( $test, $type, $add) = splice @_; + check405($test, add($test, $type, $add)); } sub get { - my ( $test, $type, $confKey ) = splice @_; + my ( $test, $type, $confKey) = splice @_; my $res; - ok( $res = &client->_get( "/v1/providers/$type/$confKey", '' ), - "$test: Request succeed" ); + ok( + $res = &client->_get("/api/v1/providers/$type/$confKey", ''), + "$test: Request succeed" + ); count(1); return $res; } sub checkGet { - my ( $test, $type, $confKey, $attrPath, $expectedValue ) = splice @_; - my $res = get( $test, $type, $confKey ); - check200( $test, $res ); + my ( $test, $type, $confKey, $attrPath, $expectedValue) = splice @_; + my $res = get($test, $type, $confKey); + check200($test, $res); my @path = split '/', $attrPath; - my $key = from_json( $res->[2]->[0] ); + my $key = from_json($res->[2]->[0]); for (@path) { $key = $key->{$_}; } - ok( + ok ( $key eq $expectedValue, -"$test: check if $attrPath value \"$key\" matches expected value \"$expectedValue\"" + "$test: check if $attrPath value \"$key\" matches expected value \"$expectedValue\"" ); count(1); } sub checkGetNotFound { - my ( $test, $type, $confKey ) = splice @_; - check404( $test, get( $test, $type, $confKey ) ); + my ( $test, $type, $confKey) = splice @_; + check404($test, get($test, $type, $confKey)); } sub update { - my ( $test, $type, $confKey, $obj ) = splice @_; + my ( $test, $type, $confKey, $obj) = splice @_; my $j = $_json->encode($obj); - #diag Dumper($j); my $res; ok( $res = &client->_patch( - "/v1/providers/$type/$confKey", '', - IO::String->new($j), 'application/json', - length($j) + "/api/v1/providers/$type/$confKey", '', IO::String->new($j), 'application/json', length($j) ), "$test: Request succeed" ); @@ -125,34 +115,32 @@ sub update { } sub checkUpdate { - my ( $test, $type, $confKey, $update ) = splice @_; - check200( $test, update( $test, $type, $confKey, $update ) ); + my ( $test, $type, $confKey, $update) = splice @_; + check200($test, update($test, $type, $confKey, $update)); } sub checkUpdateNotFound { - my ( $test, $type, $confKey, $update ) = splice @_; - check404( $test, update( $test, $type, $confKey, $update ) ); + my ( $test, $type, $confKey, $update) = splice @_; + check404($test, update($test, $type, $confKey, $update)); } sub checkUpdateFailsIfExists { - my ( $test, $type, $confKey, $update ) = splice @_; - check405( $test, update( $test, $type, $confKey, $update ) ); + my ( $test, $type, $confKey, $update) = splice @_; + check405($test, update($test, $type, $confKey, $update)); } sub checkUpdateWithUnknownAttributes { - my ( $test, $type, $confKey, $update ) = splice @_; - check405( $test, update( $test, $type, $confKey, $update ) ); + my ( $test, $type, $confKey, $update) = splice @_; + check405($test, update($test, $type, $confKey, $update)); } sub replace { - my ( $test, $type, $confKey, $obj ) = splice @_; + my ( $test, $type, $confKey, $obj) = splice @_; my $j = $_json->encode($obj); my $res; ok( $res = &client->_put( - "/v1/providers/$type/$confKey", '', - IO::String->new($j), 'application/json', - length($j) + "/api/v1/providers/$type/$confKey", '', IO::String->new($j), 'application/json', length($j) ), "$test: Request succeed" ); @@ -161,33 +149,30 @@ sub replace { } sub checkReplace { - my ( $test, $type, $confKey, $replace ) = splice @_; - check200( $test, replace( $test, $type, $confKey, $replace ) ); + my ( $test, $type, $confKey, $replace) = splice @_; + check200($test, replace($test, $type, $confKey, $replace)); } sub checkReplaceAlreadyThere { - my ( $test, $type, $confKey, $replace ) = splice @_; - check405( $test, replace( $test, $type, $confKey, $replace ) ); + my ( $test, $type, $confKey, $replace) = splice @_; + check405($test, replace($test, $type, $confKey, $replace)); } sub checkReplaceNotFound { - my ( $test, $type, $confKey, $update ) = splice @_; - check404( $test, replace( $test, $type, $confKey, $update ) ); + my ( $test, $type, $confKey, $update) = splice @_; + check404($test, replace($test, $type, $confKey, $update)); } sub checkReplaceWithUnknownAttribute { - my ( $test, $type, $confKey, $replace ) = splice @_; - check405( $test, replace( $test, $type, $confKey, $replace ) ); + my ( $test, $type, $confKey, $replace) = splice @_; + check405($test, replace($test, $type, $confKey, $replace)); } sub findByConfKey { - my ( $test, $type, $confKey ) = splice @_; + my ( $test, $type, $confKey) = splice @_; my $res; ok( - $res = &client->_get( - "/v1/providers/$type/findByConfKey", - "pattern=$confKey" - ), + $res = &client->_get("/api/v1/providers/$type/findByConfKey", "pattern=$confKey"), "$test: Request succeed" ); count(1); @@ -195,35 +180,32 @@ sub findByConfKey { } sub checkFindByConfKey { - my ( $test, $type, $confKey, $expectedHits ) = splice @_; - my $res = findByConfKey( $test, $type, $confKey ); - check200( $test, $res ); - my $hits = from_json( $res->[2]->[0] ); + my ( $test, $type, $confKey, $expectedHits) = splice @_; + my $res = findByConfKey($test, $type, $confKey); + check200($test, $res); + my $hits = from_json($res->[2]->[0]); my $hit; my $counter = 0; - foreach $hit ( @{$hits} ) { + foreach $hit (@{$hits}) { $counter++; - ok( + ok ( $hit->{confKey} =~ $confKey, -"$test: check if confKey value \"$hit->{confKey}\" matches pattern \"$confKey\"" + "$test: check if confKey value \"$hit->{confKey}\" matches pattern \"$confKey\"" ); count(1); } - ok( + ok ( $counter eq $expectedHits, -"$test: check if nb of hits returned ($counter) matches expectation ($expectedHits)" + "$test: check if nb of hits returned ($counter) matches expectation ($expectedHits)" ); count(1); } sub findByProviderId { - my ( $test, $type, $providerIdName, $providerId ) = splice @_; + my ( $test, $type, $providerIdName, $providerId) = splice @_; my $res; ok( - $res = &client->_get( - "/v1/providers/$type/findBy" . ucfirst $providerIdName, - "$providerIdName=$providerId" - ), + $res = &client->_get("/api/v1/providers/$type/findBy" . ucfirst $providerIdName, "$providerIdName=$providerId"), "$test: Request succeed" ); count(1); @@ -231,66 +213,66 @@ sub findByProviderId { } sub checkFindByProviderId { - my ( $test, $type, $providerIdName, $providerId ) = splice @_; - my $res = findByProviderId( $test, $type, $providerIdName, $providerId ); - check200( $test, $res ); - my $result = from_json( $res->[2]->[0] ); + my ( $test, $type, $providerIdName, $providerId) = splice @_; + my $res = findByProviderId($test, $type, $providerIdName, $providerId); + check200($test, $res); + my $result = from_json($res->[2]->[0]); my $gotProviderId; - if ( $providerIdName eq 'entityId' ) { - ($gotProviderId) = $result->{metadata} =~ m/entityID=['"](.+?)['"]/i; - } - else { + if ($providerIdName eq 'entityId') { + ( $gotProviderId ) = $result->{metadata} =~ m/entityID=['"](.+?)['"]/i; + } else { $gotProviderId = $result->{$providerIdName}; } ok( $gotProviderId eq $providerId, -"$test: Check $providerIdName value returned \"$gotProviderId\" matched expected value \"$providerId\"" + "$test: Check $providerIdName value returned \"$gotProviderId\" matched expected value \"$providerId\"" ); count(1); } sub checkFindByProviderIdNotFound { - my ( $test, $type, $providerIdName, $providerId ) = splice @_; - my $res = findByProviderId( $test, $type, $providerIdName, $providerId ); - check200( $test, $res ); - my $result = from_json( $res->[2]->[0] ); - ok( !defined $result->{$providerIdName}, "$test: Check object is empty" ); + my ( $test, $type, $providerIdName, $providerId) = splice @_; + my $res = findByProviderId($test, $type, $providerIdName, $providerId); + check200($test, $res); + my $result = from_json($res->[2]->[0]); + ok( + !defined $result->{$providerIdName}, + "$test: Check object is empty" + ); count(1); } sub deleteProvider { - my ( $test, $type, $confKey ) = splice @_; + my ( $test, $type, $confKey) = splice @_; my $res; ok( $res = &client->_del( - "/v1/providers/$type/$confKey", - '', '', 'application/json', 0 + "/api/v1/providers/$type/$confKey", '', '', 'application/json', 0 ), "$test: Request succeed" ); count(1); return $res; } - sub checkDelete { - my ( $test, $type, $confKey ) = splice @_; - check200( $test, deleteProvider( $test, $type, $confKey ) ); + my ( $test, $type, $confKey) = splice @_; + check200($test, deleteProvider($test, $type, $confKey)); } sub checkDeleteNotFound { - my ( $test, $type, $confKey ) = splice @_; - check404( $test, deleteProvider( $test, $type, $confKey ) ); + my ( $test, $type, $confKey) = splice @_; + check404($test, deleteProvider($test, $type, $confKey)); } my $test; my $oidcRp = { - confKey => 'myOidcRp1', - clientId => 'myOidcClient1', + confKey => 'myOidcRp1', + clientId => 'myOidcClient1', exportedVars => { - 'sub' => "uid", + 'sub' => "uid", family_name => "sn", - given_name => "givenName" + given_name => "givenName" }, extraClaim => { phone => 'telephoneNumber', @@ -298,265 +280,243 @@ my $oidcRp = { }, options => { oidcRPMetaDataOptionsClientSecret => 'secret', - oidcRPMetaDataOptionsIcon => 'web.png' + oidcRPMetaDataOptionsIcon => 'web.png' } }; $test = "OidcRp - Add should succeed"; -checkAdd( $test, 'oidc/rp', $oidcRp ); -checkGet( $test, 'oidc/rp', 'myOidcRp1', 'options/oidcRPMetaDataOptionsIcon', - 'web.png' ); -checkGet( $test, 'oidc/rp', 'myOidcRp1', - 'options/oidcRPMetaDataOptionsClientSecret', 'secret' ); +checkAdd($test, 'oidc/rp', $oidcRp); +checkGet($test, 'oidc/rp', 'myOidcRp1', 'options/oidcRPMetaDataOptionsIcon', 'web.png'); +checkGet($test, 'oidc/rp', 'myOidcRp1', 'options/oidcRPMetaDataOptionsClientSecret', 'secret'); $test = "OidcRp - Check attribute default value was set after add"; -checkGet( $test, 'oidc/rp', 'myOidcRp1', - 'options/oidcRPMetaDataOptionsIDTokenSignAlg', 'HS512' ); +checkGet($test, 'oidc/rp', 'myOidcRp1', 'options/oidcRPMetaDataOptionsIDTokenSignAlg', 'HS512'); $test = "OidcRp - Add Should fail on duplicate confKey"; -checkAddFailsIfExists( $test, 'oidc/rp', $oidcRp ); +checkAddFailsIfExists($test, 'oidc/rp', $oidcRp); $test = "OidcRp - Update should succeed and keep existing values"; -$oidcRp->{options}->{oidcRPMetaDataOptionsClientSecret} = 'secret2'; +$oidcRp->{options}->{oidcRPMetaDataOptionsClientSecret} = 'secret2'; $oidcRp->{options}->{oidcRPMetaDataOptionsIDTokenSignAlg} = 'RS512'; delete $oidcRp->{options}->{oidcRPMetaDataOptionsIcon}; delete $oidcRp->{extraClaim}; delete $oidcRp->{exportedVars}; $oidcRp->{exportedVars}->{cn} = 'cn'; -checkUpdate( $test, 'oidc/rp', 'myOidcRp1', $oidcRp ); -checkGet( $test, 'oidc/rp', 'myOidcRp1', - 'options/oidcRPMetaDataOptionsClientSecret', 'secret2' ); -checkGet( $test, 'oidc/rp', 'myOidcRp1', - 'options/oidcRPMetaDataOptionsIDTokenSignAlg', 'RS512' ); -checkGet( $test, 'oidc/rp', 'myOidcRp1', 'options/oidcRPMetaDataOptionsIcon', - 'web.png' ); -checkGet( $test, 'oidc/rp', 'myOidcRp1', 'exportedVars/cn', 'cn' ); -checkGet( $test, 'oidc/rp', 'myOidcRp1', 'exportedVars/family_name', 'sn' ); -checkGet( $test, 'oidc/rp', 'myOidcRp1', 'extraClaim/phone', - 'telephoneNumber' ); +checkUpdate($test, 'oidc/rp', 'myOidcRp1', $oidcRp); +checkGet($test, 'oidc/rp', 'myOidcRp1', 'options/oidcRPMetaDataOptionsClientSecret', 'secret2'); +checkGet($test, 'oidc/rp', 'myOidcRp1', 'options/oidcRPMetaDataOptionsIDTokenSignAlg', 'RS512'); +checkGet($test, 'oidc/rp', 'myOidcRp1', 'options/oidcRPMetaDataOptionsIcon', 'web.png'); +checkGet($test, 'oidc/rp', 'myOidcRp1', 'exportedVars/cn', 'cn'); +checkGet($test, 'oidc/rp', 'myOidcRp1', 'exportedVars/family_name', 'sn'); +checkGet($test, 'oidc/rp', 'myOidcRp1', 'extraClaim/phone', 'telephoneNumber'); $test = "OidcRp - Update should fail on non existing options"; $oidcRp->{options}->{playingPossum} = 'elephant'; -checkUpdateWithUnknownAttributes( $test, 'oidc/rp', 'myOidcRp1', $oidcRp ); +checkUpdateWithUnknownAttributes($test, 'oidc/rp', 'myOidcRp1', $oidcRp); delete $oidcRp->{options}->{playingPossum}; $test = "OidcRp - Add Should fail on duplicate clientId"; $oidcRp->{confKey} = 'myOidcRp2'; -checkAddFailsIfExists( $test, 'oidc/rp', $oidcRp ); +checkAddFailsIfExists($test, 'oidc/rp', $oidcRp); -$test = "OidcRp - Add Should fail on non existing options"; -$oidcRp->{confKey} = 'myOidcRp2'; +$test = "OidcRp - Add Should fail on non existing options"; +$oidcRp->{confKey} = 'myOidcRp2'; $oidcRp->{clientId} = 'myOidcClient2'; $oidcRp->{options}->{playingPossum} = 'ElephantInTheRoom'; -checkAddWithUnknownAttributes( $test, 'oidc/rp', $oidcRp ); +checkAddWithUnknownAttributes($test, 'oidc/rp', $oidcRp); delete $oidcRp->{options}->{playingPossum}; $test = "OidcRp - 2nd add should succeed"; -checkAdd( $test, 'oidc/rp', $oidcRp ); +checkAdd($test, 'oidc/rp', $oidcRp); $test = "OidcRp - Update should fail if client id exists"; $oidcRp->{clientId} = 'myOidcClient1'; -checkUpdateFailsIfExists( $test, 'oidc/rp', 'myOidcRp2', $oidcRp ); +checkUpdateFailsIfExists($test, 'oidc/rp', 'myOidcRp2', $oidcRp); $test = "OidcRp - Update should fail if confKey not found"; $oidcRp->{confKey} = 'myOidcRp3'; -checkUpdateNotFound( $test, 'oidc/rp', 'myOidcRp3', $oidcRp ); +checkUpdateNotFound($test, 'oidc/rp', 'myOidcRp3', $oidcRp); -$test = "OidcRp - Replace should succeed"; -$oidcRp->{confKey} = 'myOidcRp2'; +$test = "OidcRp - Replace should succeed"; +$oidcRp->{confKey} = 'myOidcRp2'; $oidcRp->{clientId} = 'myOidcClient2'; delete $oidcRp->{options}->{oidcRPMetaDataOptionsIcon}; delete $oidcRp->{options}->{oidcRPMetaDataOptionsIDTokenSignAlg}; -checkReplace( $test, 'oidc/rp', 'myOidcRp2', $oidcRp ); +checkReplace($test, 'oidc/rp', 'myOidcRp2', $oidcRp); $test = "OidcRp - Check attribute default value was set after replace"; -checkGet( $test, 'oidc/rp', 'myOidcRp2', - 'options/oidcRPMetaDataOptionsIDTokenSignAlg', 'HS512' ); +checkGet($test, 'oidc/rp', 'myOidcRp2', 'options/oidcRPMetaDataOptionsIDTokenSignAlg', 'HS512'); $test = "OidcRp - Replace should fail on non existing options"; $oidcRp->{options}->{playingPossum} = 'elephant'; -checkReplaceWithUnknownAttribute( $test, 'oidc/rp', 'myOidcRp2', $oidcRp ); +checkReplaceWithUnknownAttribute($test, 'oidc/rp', 'myOidcRp2', $oidcRp); delete $oidcRp->{options}->{playingPossum}; $test = "OidcRp - Replace should fail if confKey not found"; $oidcRp->{confKey} = 'myOidcRp3'; -checkReplaceNotFound( $test, 'oidc/rp', 'myOidcRp3', $oidcRp ); +checkReplaceNotFound($test, 'oidc/rp', 'myOidcRp3', $oidcRp); $test = "OidcRp - FindByConfKey should find 2 hits"; -checkFindByConfKey( $test, 'oidc/rp', '^myOidcRp.$', 2 ); +checkFindByConfKey($test, 'oidc/rp', '^myOidcRp.$', 2); $test = "OidcRp - FindByConfKey should find 1 hit"; -checkFindByConfKey( $test, 'oidc/rp', 'myOidcRp1', 1 ); +checkFindByConfKey($test, 'oidc/rp', 'myOidcRp1', 1); $test = "OidcRp - FindByConfKey should find 0 hits"; -checkFindByConfKey( $test, 'oidc/rp', 'myOidcRp3', 0 ); +checkFindByConfKey($test, 'oidc/rp', 'myOidcRp3', 0); $test = "OidcRp - FindByClientId should find one entry"; -checkFindByProviderId( $test, 'oidc/rp', 'clientId', 'myOidcClient1' ); +checkFindByProviderId($test, 'oidc/rp', 'clientId', 'myOidcClient1'); $test = "OidcRp - FindByClientId should find nothing"; -checkFindByProviderIdNotFound( $test, 'oidc/rp', 'clientId', 'myOidcClient3' ); +checkFindByProviderIdNotFound($test, 'oidc/rp', 'clientId', 'myOidcClient3'); $test = "OidcRp - Clean up"; -checkDelete( $test, 'oidc/rp', 'myOidcRp1' ); -checkDelete( $test, 'oidc/rp', 'myOidcRp2' ); +checkDelete($test, 'oidc/rp', 'myOidcRp1'); +checkDelete($test, 'oidc/rp', 'myOidcRp2'); $test = "OidcRp - Entity should not be found after clean up"; -checkDeleteNotFound( $test, 'oidc/rp', 'myOidcRp1' ); +checkDeleteNotFound($test, 'oidc/rp', 'myOidcRp1'); -my $metadata1 = -"urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"; +my $metadata1 = "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"; -my $metadata2 = -"urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"; +my $metadata2 = "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"; my $samlSp = { - confKey => 'mySamlSp1', - metadata => $metadata1, + confKey => 'mySamlSp1', + metadata => $metadata1, exportedAttributes => { family_name => { - format => "urn:oasis:names:tc:SAML:2.0:attrname-format:basic", - friendlyName => "surname", - mandatory => "false", - name => "sn" + format => "urn:oasis:names:tc:SAML:2.0:attrname-format:basic", + friendlyName => "surname", + mandatory => "false", + name => "sn" }, cn => { - friendlyName => "commonname", - mandatory => "true", - name => "uid" + friendlyName => "commonname", + mandatory => "true", + name => "uid" }, uid => { - mandatory => "true", - name => "uid" + mandatory => "true", + name => "uid" }, phone => { - mandatory => "false", - format => "urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified", - name => "telephoneNumber" + mandatory => "false", + format => "urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified", + name => "telephoneNumber" }, function => { - name => "title", - mandatory => "false", - format => "urn:oasis:names:tc:SAML:2.0:attrname-format:uri" + name => "title", + mandatory => "false", + format => "urn:oasis:names:tc:SAML:2.0:attrname-format:uri" }, given_name => { - mandatory => "false", - name => "givenName" + mandatory => "false", + name => "givenName" } }, options => { - samlSPMetaDataOptionsCheckSLOMessageSignature => 0, - samlSPMetaDataOptionsEncryptionMode => "assertion", - samlSPMetaDataOptionsSessionNotOnOrAfterTimeout => 36000 + samlSPMetaDataOptionsCheckSLOMessageSignature => 0, + samlSPMetaDataOptionsEncryptionMode => "assertion", + samlSPMetaDataOptionsSessionNotOnOrAfterTimeout => 36000 } }; $test = "SamlSp - Add should succeed"; -checkAdd( $test, 'saml/sp', $samlSp ); -checkGet( $test, 'saml/sp', 'mySamlSp1', - 'options/samlSPMetaDataOptionsEncryptionMode', 'assertion' ); -checkGet( $test, 'saml/sp', 'mySamlSp1', - 'options/samlSPMetaDataOptionsSessionNotOnOrAfterTimeout', 36000 ); +checkAdd($test, 'saml/sp', $samlSp); +checkGet($test, 'saml/sp', 'mySamlSp1', 'options/samlSPMetaDataOptionsEncryptionMode', 'assertion'); +checkGet($test, 'saml/sp', 'mySamlSp1', 'options/samlSPMetaDataOptionsSessionNotOnOrAfterTimeout', 36000); $test = "SamlSp - Check attribute default value was set after add"; -checkGet( $test, 'saml/sp', 'mySamlSp1', - 'options/samlSPMetaDataOptionsNotOnOrAfterTimeout', 72000 ); +checkGet($test, 'saml/sp', 'mySamlSp1', 'options/samlSPMetaDataOptionsNotOnOrAfterTimeout', 72000); $test = "SamlSp - Add Should fail on duplicate confKey"; -checkAddFailsIfExists( $test, 'saml/sp', $samlSp ); +checkAddFailsIfExists($test, 'saml/sp', $samlSp); $test = "SamlSp - Update should succeed and keep existing values"; $samlSp->{options}->{samlSPMetaDataOptionsCheckSLOMessageSignature} = 1; -$samlSp->{options}->{samlSPMetaDataOptionsEncryptionMode} = 'nameid'; +$samlSp->{options}->{samlSPMetaDataOptionsEncryptionMode} = 'nameid'; delete $samlSp->{options}->{samlSPMetaDataOptionsSessionNotOnOrAfterTimeout}; delete $samlSp->{exportedAttributes}; -$samlSp->{exportedAttributes}->{cn}->{name} = "cn", - $samlSp->{exportedAttributes}->{cn}->{friendlyName} = "common_name", - $samlSp->{exportedAttributes}->{cn}->{mandatory} = "false", - checkUpdate( $test, 'saml/sp', 'mySamlSp1', $samlSp ); -checkGet( $test, 'saml/sp', 'mySamlSp1', - 'options/samlSPMetaDataOptionsCheckSLOMessageSignature', 1 ); -checkGet( $test, 'saml/sp', 'mySamlSp1', - 'options/samlSPMetaDataOptionsSessionNotOnOrAfterTimeout', 36000 ); -checkGet( $test, 'saml/sp', 'mySamlSp1', 'exportedAttributes/cn/friendlyName', - 'common_name' ); -checkGet( $test, 'saml/sp', 'mySamlSp1', 'exportedAttributes/cn/mandatory', - 'false' ); -checkGet( $test, 'saml/sp', 'mySamlSp1', 'exportedAttributes/cn/mandatory', - 'false' ); -checkGet( $test, 'saml/sp', 'mySamlSp1', 'exportedAttributes/cn/name', 'uid' ); -checkGet( $test, 'saml/sp', 'mySamlSp1', 'exportedAttributes/given_name/name', - 'givenName' ); +$samlSp->{exportedAttributes}->{cn}->{name} = "cn", +$samlSp->{exportedAttributes}->{cn}->{friendlyName} = "common_name", +$samlSp->{exportedAttributes}->{cn}->{mandatory} = "false", +checkUpdate($test, 'saml/sp', 'mySamlSp1', $samlSp); +checkGet($test, 'saml/sp', 'mySamlSp1', 'options/samlSPMetaDataOptionsCheckSLOMessageSignature', 1); +checkGet($test, 'saml/sp', 'mySamlSp1', 'options/samlSPMetaDataOptionsSessionNotOnOrAfterTimeout', 36000); +checkGet($test, 'saml/sp', 'mySamlSp1', 'exportedAttributes/cn/friendlyName', 'common_name'); +checkGet($test, 'saml/sp', 'mySamlSp1', 'exportedAttributes/cn/mandatory', 'false'); +checkGet($test, 'saml/sp', 'mySamlSp1', 'exportedAttributes/cn/mandatory', 'false'); +checkGet($test, 'saml/sp', 'mySamlSp1', 'exportedAttributes/cn/name', 'uid'); +checkGet($test, 'saml/sp', 'mySamlSp1', 'exportedAttributes/given_name/name', 'givenName'); $test = "SamlSp - Update should fail on non existing options"; $samlSp->{options}->{playingPossum} = 'elephant'; -checkUpdateWithUnknownAttributes( $test, 'saml/sp', 'mySamlSp1', $samlSp ); +checkUpdateWithUnknownAttributes($test, 'saml/sp', 'mySamlSp1', $samlSp); delete $samlSp->{options}->{playingPossum}; $test = "SamlSp - Add Should fail on duplicate entityId"; $samlSp->{confKey} = 'mySamlSp2'; -checkAddFailsIfExists( $test, 'saml/sp', $samlSp ); +checkAddFailsIfExists($test, 'saml/sp', $samlSp); -$test = "SamlSp - Add Should fail on non existing options"; -$samlSp->{confKey} = 'mySamlSp2'; +$test = "SamlSp - Add Should fail on non existing options"; +$samlSp->{confKey} = 'mySamlSp2'; $samlSp->{metadata} = $metadata2; $samlSp->{options}->{playingPossum} = 'ElephantInTheRoom'; -checkAddWithUnknownAttributes( $test, 'saml/sp', $samlSp ); +checkAddWithUnknownAttributes($test, 'saml/sp', $samlSp); delete $samlSp->{options}->{playingPossum}; $test = "SamlSp - 2nd add should succeed"; -checkAdd( $test, 'saml/sp', $samlSp ); +checkAdd($test, 'saml/sp', $samlSp); $test = "SamlSp - Update should fail if client id exists"; $samlSp->{metadata} = $metadata1; -checkUpdateFailsIfExists( $test, 'saml/sp', 'mySamlSp2', $samlSp ); +checkUpdateFailsIfExists($test, 'saml/sp', 'mySamlSp2', $samlSp); $test = "SamlSp - Update should fail if confKey not found"; $samlSp->{confKey} = 'mySamlSp3'; -checkUpdateNotFound( $test, 'saml/sp', 'mySamlSp3', $samlSp ); +checkUpdateNotFound($test, 'saml/sp', 'mySamlSp3', $samlSp); -$test = "SamlSp - Replace should succeed"; -$samlSp->{confKey} = 'mySamlSp2'; +$test = "SamlSp - Replace should succeed"; +$samlSp->{confKey} = 'mySamlSp2'; $samlSp->{metadata} = $metadata2; delete $samlSp->{options}->{samlSPMetaDataOptionsEncryptionMode}; -checkReplace( $test, 'saml/sp', 'mySamlSp2', $samlSp ); +checkReplace($test, 'saml/sp', 'mySamlSp2', $samlSp); $test = "SamlSp - Check attribute default value was set after replace"; -checkGet( $test, 'saml/sp', 'mySamlSp2', - 'options/samlSPMetaDataOptionsEncryptionMode', 'none' ); +checkGet($test, 'saml/sp', 'mySamlSp2', 'options/samlSPMetaDataOptionsEncryptionMode', 'none'); $test = "SamlSp - Replace should fail on non existing options"; $samlSp->{options}->{playingPossum} = 'elephant'; -checkReplaceWithUnknownAttribute( $test, 'saml/sp', 'mySamlSp2', $samlSp ); +checkReplaceWithUnknownAttribute($test, 'saml/sp', 'mySamlSp2', $samlSp); delete $samlSp->{options}->{playingPossum}; $test = "SamlSp - Replace should fail if confKey not found"; $samlSp->{confKey} = 'mySamlSp3'; -checkReplaceNotFound( $test, 'saml/sp', 'mySamlSp3', $samlSp ); +checkReplaceNotFound($test, 'saml/sp', 'mySamlSp3', $samlSp); $test = "SamlSp - FindByConfKey should find 2 hits"; -checkFindByConfKey( $test, 'saml/sp', '^mySamlSp.$', 2 ); +checkFindByConfKey($test, 'saml/sp', '^mySamlSp.$', 2); $test = "SamlSp - FindByConfKey should find 1 hit"; -checkFindByConfKey( $test, 'saml/sp', 'mySamlSp1', 1 ); +checkFindByConfKey($test, 'saml/sp', 'mySamlSp1', 1); $test = "SamlSp - FindByConfKey should find 0 hits"; -checkFindByConfKey( $test, 'saml/sp', 'mySamlSp3', 0 ); +checkFindByConfKey($test, 'saml/sp', 'mySamlSp3', 0); $test = "SamlSp - FindByEntityId should find one entry"; -checkFindByProviderId( $test, 'saml/sp', 'entityId', - 'https://myapp.domain.com/saml/metadata' ); +checkFindByProviderId($test, 'saml/sp', 'entityId', 'https://myapp.domain.com/saml/metadata'); $test = "SamlSp - FindByEntityId should find nothing"; -checkFindByProviderIdNotFound( $test, 'saml/sp', 'entityId', - 'https://myapp3.domain.com/saml/metadata' ); +checkFindByProviderIdNotFound($test, 'saml/sp', 'entityId', 'https://myapp3.domain.com/saml/metadata'); $test = "SamlSp - Clean up"; -checkDelete( $test, 'saml/sp', 'mySamlSp1' ); -checkDelete( $test, 'saml/sp', 'mySamlSp2' ); +checkDelete($test, 'saml/sp', 'mySamlSp1'); +checkDelete($test, 'saml/sp', 'mySamlSp2'); $test = "SamlSp - Entity should not be found after clean up"; -checkDeleteNotFound( $test, 'saml/sp', 'mySamlSp1' ); +checkDeleteNotFound($test, 'saml/sp', 'mySamlSp1'); # Clean up generated conf files, except for "lmConf-1.json" unlink grep { $_ ne "t/conf/lmConf-1.json" } glob "t/conf/lmConf-*.json"; -done_testing(); +done_testing( ); -- GitLab From c8e36a8899be65faebb8654940215d3738ab19bd Mon Sep 17 00:00:00 2001 From: Soisik Froger Date: Wed, 18 Dec 2019 16:18:17 +0000 Subject: [PATCH 23/31] Manager API - FindBy(clientId/entityId) should send back 404 when not found - #2034 --- .../lib/Lemonldap/NG/Manager/Api/Providers/OidcRp.pm | 7 ++++--- .../lib/Lemonldap/NG/Manager/Api/Providers/SamlSp.pm | 7 ++++--- lemonldap-ng-manager/t/04-providers-api.t | 9 +-------- 3 files changed, 9 insertions(+), 14 deletions(-) diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers/OidcRp.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers/OidcRp.pm index 103b4a5ccd..b35ef544a9 100644 --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers/OidcRp.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers/OidcRp.pm @@ -87,10 +87,11 @@ sub findOidcRpByClientId { my $oidcRp = $self->_getOidcRpByClientId( $conf, $clientId ); - if ( defined $oidcRp ) { - return $self->sendJSONresponse( $req, $oidcRp ); + unless ( defined $oidcRp ) { + return $self->sendError( $req, "OIDC relying party with clientId '$clientId' not found", 404 ); } - return $self->sendJSONresponse( $req, {} ); + + return $self->sendJSONresponse( $req, $oidcRp ); } sub addOidcRp { diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers/SamlSp.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers/SamlSp.pm index b2799348ae..7fed6074be 100644 --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers/SamlSp.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers/SamlSp.pm @@ -86,10 +86,11 @@ sub findSamlSpByEntityId { my $samlSp = $self->_getSamlSpByEntityId( $conf, $entityId ); - if ( defined $samlSp ) { - return $self->sendJSONresponse( $req, $samlSp ); + unless ( defined $samlSp ) { + return $self->sendError( $req, "SAML service Provider with entityID '$entityId' not found", 404 ); } - return $self->sendJSONresponse( $req, {} ); + + return $self->sendJSONresponse( $req, $samlSp ); } sub addSamlSp { diff --git a/lemonldap-ng-manager/t/04-providers-api.t b/lemonldap-ng-manager/t/04-providers-api.t index c43c8d8d3e..537331e7d6 100644 --- a/lemonldap-ng-manager/t/04-providers-api.t +++ b/lemonldap-ng-manager/t/04-providers-api.t @@ -232,14 +232,7 @@ sub checkFindByProviderId { sub checkFindByProviderIdNotFound { my ( $test, $type, $providerIdName, $providerId) = splice @_; - my $res = findByProviderId($test, $type, $providerIdName, $providerId); - check200($test, $res); - my $result = from_json($res->[2]->[0]); - ok( - !defined $result->{$providerIdName}, - "$test: Check object is empty" - ); - count(1); + check404($test, findByProviderId($test, $type, $providerIdName, $providerId)); } sub deleteProvider { -- GitLab From 4e4f42460af30bccb659c2aaac959c2ea7d6b8fb Mon Sep 17 00:00:00 2001 From: Soisik Froger Date: Thu, 19 Dec 2019 15:52:51 +0000 Subject: [PATCH 24/31] Manager API - 2F get & delete routes/methods - #2033 --- lemonldap-ng-manager/MANIFEST | 4 +- .../lib/Lemonldap/NG/Manager/Api.pm | 39 ++- .../lib/Lemonldap/NG/Manager/Api/2F.pm | 241 +++++++++++++++++- .../t/{04-hello-api.t => 04-2F-api.t} | 9 +- 4 files changed, 277 insertions(+), 16 deletions(-) rename lemonldap-ng-manager/t/{04-hello-api.t => 04-2F-api.t} (55%) diff --git a/lemonldap-ng-manager/MANIFEST b/lemonldap-ng-manager/MANIFEST index 677f903bed..431f22b995 100644 --- a/lemonldap-ng-manager/MANIFEST +++ b/lemonldap-ng-manager/MANIFEST @@ -1,5 +1,4 @@ .bowerrc -_d bower.json Changes eg/manager.cgi @@ -203,6 +202,7 @@ site/htdocs/static/logos/fr.png site/htdocs/static/logos/it.png site/htdocs/static/logos/llng-icon-32.png site/htdocs/static/logos/llng-logo-32.png +site/htdocs/static/logos/tr.png site/htdocs/static/logos/vi.png site/htdocs/static/logos/zh.png site/htdocs/static/reverseTree.json @@ -221,7 +221,7 @@ site/templates/viewDiff.tpl site/templates/viewer.tpl t/02-HTML-template.t t/03-HTML-forms.t -t/04-hello-api.t +t/04-2F-api.t t/04-providers-api.t t/05-rest-api.t t/06-rest-api.t diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api.pm index 45d4c214b7..3ef7465560 100644 --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api.pm @@ -5,7 +5,8 @@ use 5.10.0; use utf8; use Mouse; -extends 'Lemonldap::NG::Common::Conf::RESTServer'; +extends 'Lemonldap::NG::Common::Conf::RESTServer', + 'Lemonldap::NG::Common::Session::REST'; use Lemonldap::NG::Manager::Api::2F; use Lemonldap::NG::Manager::Api::Providers::OidcRp; @@ -25,15 +26,6 @@ sub addRoutes { # HTML template $self->addRoute( 'api.html', undef, ['GET'] ) - ->addRoute( - api => { - v1 => { - hello => "helloworld", - }, - }, - ['GET'] - ) - ->addRoute( api => { v1 => { @@ -61,6 +53,17 @@ sub addRoutes { }, }, }, + secondFactor => { + ':uid' => { + id => { + ':id' => 'getSecondFactorsById' + }, + type => { + ':type' => 'getSecondFactorsByType' + }, + '*' => 'getSecondFactors' + }, + }, }, }, ['GET'] @@ -125,10 +128,26 @@ sub addRoutes { sp => {':confKey' => 'deleteSamlSp'} }, }, + secondFactor => { + ':uid' => { + id => { + ':id' => 'deleteSecondFactorsById' + }, + type => { + ':type' => 'deleteSecondFactorsByType' + }, + '*' => 'deleteSecondFactors' + }, + }, }, }, ['DELETE'] ); + + $self->setTypes($conf); + $self->{multiValuesSeparator} ||= '; '; + $self->{hiddenAttributes} //= "_password"; + $self->{TOTPCheck} = $self->{U2FCheck} = $self->{UBKCheck} = '1'; } 1; diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/2F.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/2F.pm index ad3cc0db69..6817121603 100644 --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/2F.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/2F.pm @@ -3,9 +3,244 @@ our $VERSION = '2.0.7'; package Lemonldap::NG::Manager::Api; -sub helloworld { - my ( $self, $req, @others ) = @_; - return [ 200, [], ["Hello world"]]; +use 5.10.0; +use utf8; +use Mouse; +use JSON; + +use Lemonldap::NG::Common::Session; + +sub getSecondFactors { + my ( $self, $req ) = @_; + my ( $uid, $res); + + $uid = $req->params('uid') + or return $self->sendError( $req, 'uid is missing', 400 ); + + $self->logger->debug("[API] 2F for $uid requested"); + + $res = $self->_get2F($uid); + unless ( $res->{res} eq 'ok' ) { + return $self->sendError( $req, $res->{msg}, $res->{code} ); + } + + return $self->sendJSONresponse( $req, $res->{secondFactors} ); +} + +sub getSecondFactorsByType { + my ( $self, $req ) = @_; + my ( $uid, $type, $res); + + $uid = $req->params('uid') + or return $self->sendError( $req, 'Uid is missing', 400 ); + + $type = $req->params('type') + or return $self->sendError( $req, 'Type is missing', 400 ); + + $self->logger->debug("[API] 2F for $uid with type $type requested"); + + $res = $self->_get2F($uid, uc $type); + unless ( $res->{res} eq 'ok' ) { + return $self->sendError( $req, $res->{msg}, $res->{code} ); + } + + return $self->sendJSONresponse( $req, $res->{secondFactors} ); +} + +sub getSecondFactorsById { + my ( $self, $req ) = @_; + my ( $uid, $id, $res); + + $uid = $req->params('uid') + or return $self->sendError( $req, 'uid is missing', 400 ); + + $id = $req->params('id') + or return $self->sendError( $req, 'id is missing', 400 ); + + $self->logger->debug("[API] 2F for $uid with id $id requested"); + + $res = $self->_get2F($uid, undef, $id); + unless ( $res->{res} eq 'ok' ) { + return $self->sendError( $req, $res->{msg}, $res->{code} ); + } + + return $self->sendJSONresponse( $req, $res->{secondFactors} ); +} + +sub deleteSecondFactors { + my ( $self, $req ) = @_; + my ( $uid, $res); + + $uid = $req->params('uid') + or return $self->sendError( $req, 'uid is missing', 400 ); + + $self->logger->debug("[API] Delete all 2F for $uid requested"); + + $res = $self->_delete2F($uid); + unless ( $res->{res} eq 'ok' ) { + return $self->sendError( $req, $res->{msg}, $res->{code} ); + } + + return $self->sendJSONresponse( $req, { message => $res->{msg} } ); +} + +sub deleteSecondFactorsById { + my ( $self, $req ) = @_; + my ( $uid, $id, $res); + + $uid = $req->params('uid') + or return $self->sendError( $req, 'uid is missing', 400 ); + + $id = $req->params('id') + or return $self->sendError( $req, 'id is missing', 400 ); + + $self->logger->debug("[API] Delete 2F for $uid with id $id requested"); + + $res = $self->_delete2F($uid, undef, $id); + unless ( $res->{res} eq 'ok' ) { + return $self->sendError( $req, $res->{msg}, $res->{code} ); + } + + return $self->sendJSONresponse( $req, { message => $res->{msg} } ); +} + +sub deleteSecondFactorsByType { + my ( $self, $req ) = @_; + my ( $uid, $type, $res); + + $uid = $req->params('uid') + or return $self->sendError( $req, 'uid is missing', 400 ); + + $type = $req->params('type') + or return $self->sendError( $req, 'type is missing', 400 ); + + $self->logger->debug("[API] Delete all 2F for $uid with type $type requested"); + + $res = $self->_delete2F($uid, uc $type); + unless ( $res->{res} eq 'ok' ) { + return $self->sendError( $req, $res->{msg}, $res->{code} ); + } + + return $self->sendJSONresponse( $req, { message => $res->{msg} } ); +} + +sub _get2F { + my ( $self, $uid, $type, $id ) = @_; + my ($res, $psessions, @secondFactors); + + if (defined $type) { + $res = $self->_checkType($type); + return $res if ($res->{res} ne 'ok'); + } + + $psessions = $self->_getPSessions2F($uid, ('_session_uid', '_2fDevices')); + + foreach ( keys %{ $psessions } ) { + my $devices = from_json( $psessions->{$_}->{_2fDevices}, { allow_nonref => 1 } ); + foreach my $device ( @{$devices} ) { + push @secondFactors, { id => $device->{epoch}, type => $device->{type}, name => $device->{name} } + unless ( + ( defined $type and $type ne $device->{type} ) + or ( defined $id and $id ne $device->{epoch} ) + ); + } + } + return { res => 'ok', secondFactors => scalar @secondFactors ? @secondFactors : [] }; +} + +sub _getMod2F { + my ( $self ) = @_; + my $mod = $self->sessionTypes->{persistent}; + $mod->{options}->{backend} = $mod->{module}; + return $mod; +} +sub _getPSessions2F { + my ( $self, $uid, @fields ) = @_; + $self->logger->debug("Looking for psessions for uid $uid ..."); + my $psessions = Lemonldap::NG::Common::Apache::Session->searchOn($self->_getMod2F->{options}, + '_session_uid', $uid, @fields ); + $self->logger->debug("Found " . scalar (keys %{ $psessions }) . " psessions for uid $uid."); + return $psessions; +} + +sub getSession2F { + my ( $self, $sessionId ) = @_; + $self->logger->debug("Looking for session with sessionId $sessionId ..."); + my $session = $self->getApacheSession( $self->_getMod2F, $sessionId ); + $self->logger->debug(defined $session ? "Session $sessionId found." : " No session found for sessionId $sessionId"); + return $session; +} + +sub _delete2F { + my ( $self, $uid, $type, $id ) = @_; + my ($res, $psessions, $sessionId, $session, $devices, @keep, $total, $removed, $lremoved, $localStorage); + + $localStorage = Lemonldap::NG::Handler::PSGI::Main->tsv->{refLocalStorage}; + + if (defined $type) { + $res = $self->_checkType($type); + return $res if ($res->{res} ne 'ok'); + } + + $psessions = $self->_getPSessions2F($uid, ('_session_uid', '_session_id', '_2fDevices')); + + foreach ( keys %{ $psessions } ) { + + $sessionId = $psessions->{$_}->{_session_id}; + $session = $self->getSession2F( $sessionId ) + or return { res => 'ko', code => 500, msg => $@ }; + + $self->logger->debug("Looking for 2F Device(s) attached to session $sessionId..."); + + if ( $session->data->{_2fDevices} ) { + + $devices = from_json( $session->data->{_2fDevices}, { allow_nonref => 1 } ); + $total = scalar @$devices; + $self->logger->debug("Found $total 2F devices attached to session $sessionId."); + @keep = (); + while (@$devices) { + my $element = shift @$devices; + push @keep, $element + if (( defined $type or defined $id ) and + (( defined $type and $type ne $element->{type} ) or + ( defined $id and $id ne $element->{epoch} ))); + } + $lremoved = $total - scalar @keep; + if ($lremoved > 0) { + # Update session + $self->logger->debug("Removing $lremoved 2F device(s) attached to session $sessionId ..."); + $session->data->{_2fDevices} = to_json( \@keep ); + $session->update( \%{ $session->data } ); + + # Delete local cache + if ( $localStorage and $localStorage->get($sessionId) ) { + $self->logger->debug("Delete local cache for $sessionId ..."); + $localStorage->remove($sessionId); + } else { + $self->logger->debug("Local cache will not be cleared for $sessionId"); + } + } else { + $self->logger->debug("No matching 2F devices attached to session $sessionId were selected for removal."); + } + } else { + $self->logger->debug("No 2F devices attached to session $sessionId were found."); + } + + $removed += $lremoved; + } + return { + res => 'ok', + msg => $removed > 0 ? "Successful operation: " . $removed . " 2F were removed" : "No operation performed" + }; +} + +sub _checkType { + my ( $self, $type ) = @_; + + return {res => "ko", code=> 405, msg => "Invalid input: Type \"$type\" does not exist. Allowed values for type are: \"U2F\", \"TOTP\" or \"UBK\"" } + unless ( $type =~ /\b(?:U2F|TOTP|UBK)\b/ ); + + return { res => "ok" }; } 1; diff --git a/lemonldap-ng-manager/t/04-hello-api.t b/lemonldap-ng-manager/t/04-2F-api.t similarity index 55% rename from lemonldap-ng-manager/t/04-hello-api.t rename to lemonldap-ng-manager/t/04-2F-api.t index 884aa1b55c..3e7dfd024a 100644 --- a/lemonldap-ng-manager/t/04-hello-api.t +++ b/lemonldap-ng-manager/t/04-2F-api.t @@ -4,11 +4,18 @@ use Test::More; use strict; use JSON; use IO::String; +use Lemonldap::NG::Common::Session; + +eval { mkdir 't/sessions' }; +`rm -rf t/sessions/*`; + require 't/test-lib.pm'; +our $_json = JSON->new->allow_nonref; + my $res; ok( - $res = &client->_get('/api/v1/hello', '') + $res = &client->_get('/api/v1/secondFactor/dwho', '') , "Request succeed" ); -- GitLab From e939fa42fa00868ba6947d7cae81ed284c048ae4 Mon Sep 17 00:00:00 2001 From: Soisik Froger Date: Fri, 20 Dec 2019 10:05:16 +0000 Subject: [PATCH 25/31] Manager API - 2F unit tests - #2033 --- .../lib/Lemonldap/NG/Manager/Api/2F.pm | 72 ++-- lemonldap-ng-manager/t/04-2F-api.t | 324 +++++++++++++++++- 2 files changed, 360 insertions(+), 36 deletions(-) diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/2F.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/2F.pm index 6817121603..a58799e78a 100644 --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/2F.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/2F.pm @@ -7,6 +7,7 @@ use 5.10.0; use utf8; use Mouse; use JSON; +use MIME::Base64; use Lemonldap::NG::Common::Session; @@ -20,9 +21,9 @@ sub getSecondFactors { $self->logger->debug("[API] 2F for $uid requested"); $res = $self->_get2F($uid); - unless ( $res->{res} eq 'ok' ) { - return $self->sendError( $req, $res->{msg}, $res->{code} ); - } + + return $self->sendError( $req, $res->{msg}, $res->{code} ) + unless ( $res->{res} eq 'ok' ); return $self->sendJSONresponse( $req, $res->{secondFactors} ); } @@ -40,9 +41,9 @@ sub getSecondFactorsByType { $self->logger->debug("[API] 2F for $uid with type $type requested"); $res = $self->_get2F($uid, uc $type); - unless ( $res->{res} eq 'ok' ) { - return $self->sendError( $req, $res->{msg}, $res->{code} ); - } + + return $self->sendError( $req, $res->{msg}, $res->{code} ) + unless ( $res->{res} eq 'ok' ); return $self->sendJSONresponse( $req, $res->{secondFactors} ); } @@ -60,11 +61,14 @@ sub getSecondFactorsById { $self->logger->debug("[API] 2F for $uid with id $id requested"); $res = $self->_get2F($uid, undef, $id); - unless ( $res->{res} eq 'ok' ) { - return $self->sendError( $req, $res->{msg}, $res->{code} ); - } - return $self->sendJSONresponse( $req, $res->{secondFactors} ); + return $self->sendError( $req, $res->{msg}, $res->{code} ) + unless ( $res->{res} eq 'ok' ); + + return $self->sendError( $req, "2F id '$id' not found for user '$uid'", 404 ) + unless ( scalar @{$res->{secondFactors}} > 0 ); + + return $self->sendJSONresponse( $req, @{$res->{secondFactors}}[0]); } sub deleteSecondFactors { @@ -77,9 +81,9 @@ sub deleteSecondFactors { $self->logger->debug("[API] Delete all 2F for $uid requested"); $res = $self->_delete2F($uid); - unless ( $res->{res} eq 'ok' ) { - return $self->sendError( $req, $res->{msg}, $res->{code} ); - } + + return $self->sendError( $req, $res->{msg}, $res->{code} ) + unless ( $res->{res} eq 'ok' ); return $self->sendJSONresponse( $req, { message => $res->{msg} } ); } @@ -97,9 +101,12 @@ sub deleteSecondFactorsById { $self->logger->debug("[API] Delete 2F for $uid with id $id requested"); $res = $self->_delete2F($uid, undef, $id); - unless ( $res->{res} eq 'ok' ) { - return $self->sendError( $req, $res->{msg}, $res->{code} ); - } + + return $self->sendError( $req, $res->{msg}, $res->{code} ) + unless ( $res->{res} eq 'ok' ); + + return $self->sendError( $req, "2F id '$id' not found for user '$uid'", 404 ) + unless ( $res->{removed} > 0 ); return $self->sendJSONresponse( $req, { message => $res->{msg} } ); } @@ -117,9 +124,9 @@ sub deleteSecondFactorsByType { $self->logger->debug("[API] Delete all 2F for $uid with type $type requested"); $res = $self->_delete2F($uid, uc $type); - unless ( $res->{res} eq 'ok' ) { - return $self->sendError( $req, $res->{msg}, $res->{code} ); - } + + return $self->sendError( $req, $res->{msg}, $res->{code} ) + unless ( $res->{res} eq 'ok' ); return $self->sendJSONresponse( $req, { message => $res->{msg} } ); } @@ -133,19 +140,26 @@ sub _get2F { return $res if ($res->{res} ne 'ok'); } - $psessions = $self->_getPSessions2F($uid, ('_session_uid', '_2fDevices')); + $psessions = $self->_getPSessions2F($uid); foreach ( keys %{ $psessions } ) { my $devices = from_json( $psessions->{$_}->{_2fDevices}, { allow_nonref => 1 } ); foreach my $device ( @{$devices} ) { - push @secondFactors, { id => $device->{epoch}, type => $device->{type}, name => $device->{name} } + $self->logger->debug("Check device [epoch=$device->{epoch}, type=$device->{type}, name=$device->{name}]"); + push @secondFactors, { id => $self->_genId2F($device), type => $device->{type}, name => $device->{name} } unless ( ( defined $type and $type ne $device->{type} ) - or ( defined $id and $id ne $device->{epoch} ) + or ( defined $id and $id ne $self->_genId2F($device) ) ); } } - return { res => 'ok', secondFactors => scalar @secondFactors ? @secondFactors : [] }; + $self->logger->debug("Found " . scalar @secondFactors . " 2F devices for uid $uid."); + return { res => 'ok', secondFactors => [ @secondFactors ]}; +} + +sub _genId2F { + my ( $self, $device) = @_; + return encode_base64("$device->{epoch}::$device->{type}::$device->{name}", ""); } sub _getMod2F { @@ -155,10 +169,13 @@ sub _getMod2F { return $mod; } sub _getPSessions2F { - my ( $self, $uid, @fields ) = @_; + my ( $self, $uid ) = @_; $self->logger->debug("Looking for psessions for uid $uid ..."); my $psessions = Lemonldap::NG::Common::Apache::Session->searchOn($self->_getMod2F->{options}, - '_session_uid', $uid, @fields ); + '_session_uid', $uid, ('_session_kind' , '_session_uid', '_session_id', '_2fDevices')); + foreach ( keys %{ $psessions } ) { + delete $psessions->{$_} unless ($psessions->{$_}->{_session_kind} eq 'Persistent'); + } $self->logger->debug("Found " . scalar (keys %{ $psessions }) . " psessions for uid $uid."); return $psessions; } @@ -182,7 +199,7 @@ sub _delete2F { return $res if ($res->{res} ne 'ok'); } - $psessions = $self->_getPSessions2F($uid, ('_session_uid', '_session_id', '_2fDevices')); + $psessions = $self->_getPSessions2F($uid); foreach ( keys %{ $psessions } ) { @@ -203,7 +220,7 @@ sub _delete2F { push @keep, $element if (( defined $type or defined $id ) and (( defined $type and $type ne $element->{type} ) or - ( defined $id and $id ne $element->{epoch} ))); + ( defined $id and $id ne $self->_genId2F($element) ))); } $lremoved = $total - scalar @keep; if ($lremoved > 0) { @@ -230,6 +247,7 @@ sub _delete2F { } return { res => 'ok', + removed => $removed, msg => $removed > 0 ? "Successful operation: " . $removed . " 2F were removed" : "No operation performed" }; } diff --git a/lemonldap-ng-manager/t/04-2F-api.t b/lemonldap-ng-manager/t/04-2F-api.t index 3e7dfd024a..1839c3ef48 100644 --- a/lemonldap-ng-manager/t/04-2F-api.t +++ b/lemonldap-ng-manager/t/04-2F-api.t @@ -13,14 +13,320 @@ require 't/test-lib.pm'; our $_json = JSON->new->allow_nonref; -my $res; -ok( - $res = &client->_get('/api/v1/secondFactor/dwho', '') - , - "Request succeed" -); -ok( $res->[0] == 200, "Result code is 200" ); - -diag Dumper($res); +sub newSession { + my ( $uid, $ip, $kind, $sfaDevices ) = splice @_; + my $tmp; + ok( + $tmp = Lemonldap::NG::Common::Session->new( { + storageModule => 'Apache::Session::File', + storageModuleOptions => { + Directory => 't/sessions', + LockDirectory => 't/sessions', + backend => 'Apache::Session::File', + generateModule => +'Lemonldap::NG::Common::Apache::Session::Generate::SHA256', + }, + } + ), + 'Sessions module' + ); + count(1); + $tmp->update( { + ipAddr => $ip, + _whatToTrace => $uid, + uid => $uid, + _session_uid => $uid, + _utime => time, + _session_kind => $kind, + _2fDevices => to_json($sfaDevices), + } + ); + return $tmp->{id}; +} + +sub check200 { + my ( $test, $res ) = splice @_; + ok( $res->[0] == 200, "$test: Result code is 200" ); + count(1); + checkJson($test, $res); +} + +sub check405 { + my ( $test, $res ) = splice @_; + ok( $res->[0] == 405, "$test: Result code is 405" ); + count(1); + checkJson($test, $res); +} + +sub check404 { + my ( $test, $res ) = splice @_; + ok( $res->[0] == 404, "$test: Result code is 404" ); + count(1); + checkJson($test, $res); +} + +sub checkJson { + my ( $test, $res ) = splice @_; + my $key; + #diag Dumper($res->[2]->[0]); + ok( $key = from_json( $res->[2]->[0] ), "$test: Response is JSON" ); + count(1); +} + +sub get { + my ( $test, $uid, $type, $id ) = splice @_; + my ( $res ); + ok( + $res = &client->_get("/api/v1/secondFactor/$uid" . + ( defined $type ? "/type/$type" : ( defined $id ? "/id/$id": "" )) + ), "$test: Request succeed" + ); + count(1); + return $res; +} + +sub checkGet { + my ( $uid, $id ) = splice @_; + my ( $test, $res, $ret ); + $test = "$uid should have one 2F with id \"$id\""; + $res = get($test, $uid, undef, $id); + check200($test, $res); + #diag Dumper($res); + $ret = from_json($res->[2]->[0]); + ok ( + ref $ret eq 'HASH' && $ret->{id} eq $id, + "$test: check returned type is HASH and that ids match" + ); + count(1); +} + +sub checkGet404 { + my ( $uid, $id ) = splice @_; + my ( $test, $res, $ret ); + $test = "$uid should not have any 2F with id \"$id\""; + $res = get($test, $uid, undef, $id); + check404($test, $res); +} + +sub checkGetList { + my ( $expect, $uid, $type) = splice @_; + my ( $test, $res, $ret ); + $test = "$uid should have $expect 2F" . ( defined $type ? " of type \"$type\"" : "" ); + $res = get($test, $uid, $type); + check200($test, $res); + #diag Dumper($res); + $ret = from_json($res->[2]->[0]); + ok ( + scalar @$ret eq $expect, + "$test: check if nb of 2F found (" . scalar @$ret . ") equals expectation ($expect)" + ); + count(1); + return $ret; +} + +sub checkGetBadType { + my ( $uid, $type ) = splice @_; + my ( $test, $res ); + $test = "Get for uid $uid and type \"$type\" should get rejected."; + $res = get($test, $uid, $type); + check405($test, $res); +} + +sub checkGetOnIds { + my ( $uid, $ret ) = splice @_; + foreach ( @$ret ) { + checkGet($uid, $_->{id}); + } +} + +sub checkGetOnIdsNotFound { + my ( $uid, $ret ) = splice @_; + foreach ( @$ret ) { + checkGet404($uid, $_->{id}); + } +} + +sub del { + my ( $test, $uid, $type, $id ) = splice @_; + my ( $res ); + ok( + $res = &client->_del("/api/v1/secondFactor/$uid" . + ( defined $type ? "/type/$type" : ( defined $id ? "/id/$id": "" )) + ), "$test: Request succeed" + ); + count(1); + return $res; +} + +sub checkDelete { + my ( $uid, $id ) = splice @_; + my ( $test, $res ); + $test = "$uid should have a 2F with id \"$id\" to be deleted."; + $res = del($test, $uid, undef, $id); + check200($test, $res); +} + +sub checkDelete404 { + my ( $uid, $id ) = splice @_; + my ( $test, $res ); + $test = "$uid should not have a 2F with id \"$id\" to be deleted."; + $res = del($test, $uid, undef, $id); + check404($test, $res); +} + +sub checkDeleteList { + my ( $expect, $uid, $type) = splice @_; + my ( $test, $res, $ret, $countDel ); + $test = "Delete all 2F from $uid" . ( defined $type ? " of type \"$type\"" : "" ); + $res = del($test, $uid, $type); + check200($test, $res); + $ret = from_json($res->[2]->[0]); + ( $countDel ) = $ret->{message} =~ m/^Successful operation: ([\d]+) /i; + $countDel = 0 unless (defined $countDel); + ok( + $countDel eq $expect, + "$test: check nb of 2FA deleted ($countDel) matches expectation ($expect)" + ); + count(1); +} + +sub checkDeleteBadType { + my ( $uid, $type ) = splice @_; + my ( $test, $res ); + $test = "Delete for uid $uid and type \"$type\" should get rejected."; + $res = del($test, $uid, $type); + check405($test, $res); +} + +my @ids; +my $sfaDevices = []; +my $ret; + +## Sessions creation +# SSO session +$ids[0] = newSession( 'dwho', '127.10.0.1', 'SSO', $sfaDevices ); + +# Peristent sesssions +$ids[1] = newSession( 'msmith', '127.10.0.1', 'Persistent', $sfaDevices ); +$sfaDevices = [ { + "name" => "MyU2FKey", + "type" => "U2F", + "_userKey" => "123456", + "_keyHandle" => "654321", + "epoch" => time + }, + { + "name" => "MyYubikey", + "type" => "UBK", + "_secret" => "123456", + "epoch" => time + }, + { + "name" => "MyYubikey2", + "type" => "UBK", + "_secret" => "654321", + "epoch" => time + } +]; +$ids[2] = newSession( 'rtyler', '127.10.0.1', 'Persistent', $sfaDevices ); +$sfaDevices = [ { + "name" => "MyU2FKey", + "type" => "U2F", + "_userKey" => "123456", + "_keyHandle" => "654321", + "epoch" => time + }, + { + "name" => "MyTOTP", + "type" => "TOTP", + "_secret" => "123456", + "epoch" => time + }, + { + "name" => "MyYubikey", + "type" => "UBK", + "_secret" => "123456", + "epoch" => time + } +]; +$ids[3] = newSession( 'dwho', '127.10.0.1', 'Persistent', $sfaDevices ); +$sfaDevices = [ { + "name" => "MyU2FKey", + "type" => "U2F", + "_userKey" => "123456", + "_keyHandle" => "654321", + "epoch" => time + }, + { + "name" => "MyTOTP", + "type" => "TOTP", + "_secret" => "123456", + "epoch" => time + } +]; +$ids[4] = newSession( 'davros', '127.10.0.1', 'Persistent', $sfaDevices ); +$sfaDevices = [ { + "name" => "MyU2FKey", + "type" => "U2F", + "_userKey" => "123456", + "_keyHandle" => "654321", + "epoch" => time + } +]; +$ids[5] = newSession( 'tof', '127.10.0.1', 'Persistent', $sfaDevices ); + +# dwho +checkGetList(1, 'dwho', 'U2F'); +checkGetList(1, 'dwho', 'TOTP'); +checkGetList(1, 'dwho', 'UBK'); +checkGetBadType('dwho', 'UBKIKI'); +$ret = checkGetList(3, 'dwho'); +checkGetOnIds('dwho', $ret); +checkDelete('dwho', @$ret[0]->{id}); +checkDelete404('dwho', @$ret[0]->{id}); +checkGetList(2, 'dwho'); +checkDeleteList(2, 'dwho'); +checkGetList(0, 'dwho'); +checkDeleteList(0, 'dwho'); + +# msmith +checkGetList(0, 'msmith'); + +# rtyler +checkGetList(1, 'rtyler', 'U2F'); +checkGetList(0, 'rtyler', 'TOTP'); +checkGetList(2, 'rtyler', 'UBK'); +$ret = checkGetList(3, 'rtyler'); +checkGetOnIds('rtyler', $ret); +checkDeleteList(2,'rtyler', 'UBK'); +$ret = checkGetList(1, 'rtyler'); +checkDelete('rtyler', @$ret[0]->{id}); +checkDelete404('rtyler', @$ret[0]->{id}); +checkDeleteList(0, 'rtyler'); + + +# davros +checkGetList(1, 'davros', 'U2F'); +checkGetList(1, 'davros', 'TOTP'); +checkGetList(0, 'davros', 'UBK'); +$ret = checkGetList(2, 'davros'); +checkGetOnIds('davros', $ret); +checkDelete('davros', @$ret[0]->{id}); +checkDelete404('davros', @$ret[0]->{id}); +checkGetList(1, 'davros'); +checkDeleteList(1, 'davros', @$ret[1]->{type}); +checkGetList(0, 'davros'); +checkDeleteList(0, 'davros'); + +# tof +checkGetList(1, 'tof', 'U2F'); +checkGetList(0, 'tof', 'TOTP'); +checkGetList(0, 'tof', 'UBK'); +$ret = checkGetList(1, 'tof'); +checkGetOnIds('tof', $ret); +checkDelete('tof', @$ret[0]->{id}); +checkDelete404('tof', @$ret[0]->{id}); +checkGetList(0, 'tof'); +checkDeleteList(0, 'tof'); done_testing( ); -- GitLab From badef81aa8c77795aa4be54ce9ef75b1affa901f Mon Sep 17 00:00:00 2001 From: Soisik Froger Date: Fri, 20 Dec 2019 10:13:15 +0000 Subject: [PATCH 26/31] Manager API - perltidy - #2034 & #2033 --- .../lib/Lemonldap/NG/Manager/Api.pm | 20 +- .../lib/Lemonldap/NG/Manager/Api/2F.pm | 187 +++++--- .../NG/Manager/Api/Providers/OidcRp.pm | 6 +- .../NG/Manager/Api/Providers/SamlSp.pm | 6 +- lemonldap-ng-manager/t/04-2F-api.t | 200 ++++----- lemonldap-ng-manager/t/04-providers-api.t | 398 ++++++++++-------- 6 files changed, 462 insertions(+), 355 deletions(-) diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api.pm index 3ef7465560..7f8dd43122 100644 --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api.pm @@ -90,10 +90,10 @@ sub addRoutes { v1 => { providers => { oidc => { - rp => {':confKey' => 'replaceOidcRp'} + rp => { ':confKey' => 'replaceOidcRp' } }, saml => { - sp => {':confKey' => 'replaceSamlSp'} + sp => { ':confKey' => 'replaceSamlSp' } }, }, }, @@ -106,10 +106,10 @@ sub addRoutes { v1 => { providers => { oidc => { - rp => {':confKey' => 'updateOidcRp'} + rp => { ':confKey' => 'updateOidcRp' } }, saml => { - sp => {':confKey' => 'updateSamlSp'} + sp => { ':confKey' => 'updateSamlSp' } }, }, }, @@ -122,10 +122,10 @@ sub addRoutes { v1 => { providers => { oidc => { - rp => {':confKey' => 'deleteOidcRp'} + rp => { ':confKey' => 'deleteOidcRp' } }, saml => { - sp => {':confKey' => 'deleteSamlSp'} + sp => { ':confKey' => 'deleteSamlSp' } }, }, secondFactor => { @@ -144,10 +144,10 @@ sub addRoutes { ['DELETE'] ); - $self->setTypes($conf); - $self->{multiValuesSeparator} ||= '; '; - $self->{hiddenAttributes} //= "_password"; - $self->{TOTPCheck} = $self->{U2FCheck} = $self->{UBKCheck} = '1'; + $self->setTypes($conf); + $self->{multiValuesSeparator} ||= '; '; + $self->{hiddenAttributes} //= "_password"; + $self->{TOTPCheck} = $self->{U2FCheck} = $self->{UBKCheck} = '1'; } 1; diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/2F.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/2F.pm index a58799e78a..64393052a2 100644 --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/2F.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/2F.pm @@ -13,7 +13,7 @@ use Lemonldap::NG::Common::Session; sub getSecondFactors { my ( $self, $req ) = @_; - my ( $uid, $res); + my ( $uid, $res ); $uid = $req->params('uid') or return $self->sendError( $req, 'uid is missing', 400 ); @@ -23,14 +23,14 @@ sub getSecondFactors { $res = $self->_get2F($uid); return $self->sendError( $req, $res->{msg}, $res->{code} ) - unless ( $res->{res} eq 'ok' ); + unless ( $res->{res} eq 'ok' ); return $self->sendJSONresponse( $req, $res->{secondFactors} ); } sub getSecondFactorsByType { my ( $self, $req ) = @_; - my ( $uid, $type, $res); + my ( $uid, $type, $res ); $uid = $req->params('uid') or return $self->sendError( $req, 'Uid is missing', 400 ); @@ -40,17 +40,17 @@ sub getSecondFactorsByType { $self->logger->debug("[API] 2F for $uid with type $type requested"); - $res = $self->_get2F($uid, uc $type); + $res = $self->_get2F( $uid, uc $type ); return $self->sendError( $req, $res->{msg}, $res->{code} ) - unless ( $res->{res} eq 'ok' ); + unless ( $res->{res} eq 'ok' ); return $self->sendJSONresponse( $req, $res->{secondFactors} ); } sub getSecondFactorsById { my ( $self, $req ) = @_; - my ( $uid, $id, $res); + my ( $uid, $id, $res ); $uid = $req->params('uid') or return $self->sendError( $req, 'uid is missing', 400 ); @@ -60,20 +60,21 @@ sub getSecondFactorsById { $self->logger->debug("[API] 2F for $uid with id $id requested"); - $res = $self->_get2F($uid, undef, $id); + $res = $self->_get2F( $uid, undef, $id ); return $self->sendError( $req, $res->{msg}, $res->{code} ) - unless ( $res->{res} eq 'ok' ); + unless ( $res->{res} eq 'ok' ); - return $self->sendError( $req, "2F id '$id' not found for user '$uid'", 404 ) - unless ( scalar @{$res->{secondFactors}} > 0 ); + return $self->sendError( $req, "2F id '$id' not found for user '$uid'", + 404 ) + unless ( scalar @{ $res->{secondFactors} } > 0 ); - return $self->sendJSONresponse( $req, @{$res->{secondFactors}}[0]); + return $self->sendJSONresponse( $req, @{ $res->{secondFactors} }[0] ); } sub deleteSecondFactors { my ( $self, $req ) = @_; - my ( $uid, $res); + my ( $uid, $res ); $uid = $req->params('uid') or return $self->sendError( $req, 'uid is missing', 400 ); @@ -83,14 +84,14 @@ sub deleteSecondFactors { $res = $self->_delete2F($uid); return $self->sendError( $req, $res->{msg}, $res->{code} ) - unless ( $res->{res} eq 'ok' ); + unless ( $res->{res} eq 'ok' ); return $self->sendJSONresponse( $req, { message => $res->{msg} } ); } sub deleteSecondFactorsById { my ( $self, $req ) = @_; - my ( $uid, $id, $res); + my ( $uid, $id, $res ); $uid = $req->params('uid') or return $self->sendError( $req, 'uid is missing', 400 ); @@ -100,20 +101,21 @@ sub deleteSecondFactorsById { $self->logger->debug("[API] Delete 2F for $uid with id $id requested"); - $res = $self->_delete2F($uid, undef, $id); + $res = $self->_delete2F( $uid, undef, $id ); return $self->sendError( $req, $res->{msg}, $res->{code} ) - unless ( $res->{res} eq 'ok' ); + unless ( $res->{res} eq 'ok' ); - return $self->sendError( $req, "2F id '$id' not found for user '$uid'", 404 ) - unless ( $res->{removed} > 0 ); + return $self->sendError( $req, "2F id '$id' not found for user '$uid'", + 404 ) + unless ( $res->{removed} > 0 ); return $self->sendJSONresponse( $req, { message => $res->{msg} } ); } sub deleteSecondFactorsByType { my ( $self, $req ) = @_; - my ( $uid, $type, $res); + my ( $uid, $type, $res ); $uid = $req->params('uid') or return $self->sendError( $req, 'uid is missing', 400 ); @@ -121,62 +123,76 @@ sub deleteSecondFactorsByType { $type = $req->params('type') or return $self->sendError( $req, 'type is missing', 400 ); - $self->logger->debug("[API] Delete all 2F for $uid with type $type requested"); + $self->logger->debug( + "[API] Delete all 2F for $uid with type $type requested"); - $res = $self->_delete2F($uid, uc $type); + $res = $self->_delete2F( $uid, uc $type ); return $self->sendError( $req, $res->{msg}, $res->{code} ) - unless ( $res->{res} eq 'ok' ); + unless ( $res->{res} eq 'ok' ); return $self->sendJSONresponse( $req, { message => $res->{msg} } ); } sub _get2F { my ( $self, $uid, $type, $id ) = @_; - my ($res, $psessions, @secondFactors); + my ( $res, $psessions, @secondFactors ); - if (defined $type) { + if ( defined $type ) { $res = $self->_checkType($type); - return $res if ($res->{res} ne 'ok'); + return $res if ( $res->{res} ne 'ok' ); } $psessions = $self->_getPSessions2F($uid); - foreach ( keys %{ $psessions } ) { - my $devices = from_json( $psessions->{$_}->{_2fDevices}, { allow_nonref => 1 } ); + foreach ( keys %{$psessions} ) { + my $devices = + from_json( $psessions->{$_}->{_2fDevices}, { allow_nonref => 1 } ); foreach my $device ( @{$devices} ) { - $self->logger->debug("Check device [epoch=$device->{epoch}, type=$device->{type}, name=$device->{name}]"); - push @secondFactors, { id => $self->_genId2F($device), type => $device->{type}, name => $device->{name} } - unless ( - ( defined $type and $type ne $device->{type} ) - or ( defined $id and $id ne $self->_genId2F($device) ) - ); + $self->logger->debug( +"Check device [epoch=$device->{epoch}, type=$device->{type}, name=$device->{name}]" + ); + push @secondFactors, + { + id => $self->_genId2F($device), + type => $device->{type}, + name => $device->{name} + } + unless ( ( defined $type and $type ne $device->{type} ) + or ( defined $id and $id ne $self->_genId2F($device) ) ); } } - $self->logger->debug("Found " . scalar @secondFactors . " 2F devices for uid $uid."); - return { res => 'ok', secondFactors => [ @secondFactors ]}; + $self->logger->debug( + "Found " . scalar @secondFactors . " 2F devices for uid $uid." ); + return { res => 'ok', secondFactors => [@secondFactors] }; } sub _genId2F { - my ( $self, $device) = @_; - return encode_base64("$device->{epoch}::$device->{type}::$device->{name}", ""); + my ( $self, $device ) = @_; + return encode_base64( "$device->{epoch}::$device->{type}::$device->{name}", + "" ); } sub _getMod2F { - my ( $self ) = @_; + my ($self) = @_; my $mod = $self->sessionTypes->{persistent}; $mod->{options}->{backend} = $mod->{module}; return $mod; } + sub _getPSessions2F { my ( $self, $uid ) = @_; $self->logger->debug("Looking for psessions for uid $uid ..."); - my $psessions = Lemonldap::NG::Common::Apache::Session->searchOn($self->_getMod2F->{options}, - '_session_uid', $uid, ('_session_kind' , '_session_uid', '_session_id', '_2fDevices')); - foreach ( keys %{ $psessions } ) { - delete $psessions->{$_} unless ($psessions->{$_}->{_session_kind} eq 'Persistent'); + my $psessions = Lemonldap::NG::Common::Apache::Session->searchOn( + $self->_getMod2F->{options}, + '_session_uid', $uid, + ( '_session_kind', '_session_uid', '_session_id', '_2fDevices' ) ); + foreach ( keys %{$psessions} ) { + delete $psessions->{$_} + unless ( $psessions->{$_}->{_session_kind} eq 'Persistent' ); } - $self->logger->debug("Found " . scalar (keys %{ $psessions }) . " psessions for uid $uid."); + $self->logger->debug( + "Found " . scalar( keys %{$psessions} ) . " psessions for uid $uid." ); return $psessions; } @@ -184,79 +200,110 @@ sub getSession2F { my ( $self, $sessionId ) = @_; $self->logger->debug("Looking for session with sessionId $sessionId ..."); my $session = $self->getApacheSession( $self->_getMod2F, $sessionId ); - $self->logger->debug(defined $session ? "Session $sessionId found." : " No session found for sessionId $sessionId"); + $self->logger->debug( + defined $session + ? "Session $sessionId found." + : " No session found for sessionId $sessionId" ); return $session; } sub _delete2F { my ( $self, $uid, $type, $id ) = @_; - my ($res, $psessions, $sessionId, $session, $devices, @keep, $total, $removed, $lremoved, $localStorage); + my ( + $res, $psessions, $sessionId, $session, $devices, + @keep, $total, $removed, $lremoved, $localStorage + ); $localStorage = Lemonldap::NG::Handler::PSGI::Main->tsv->{refLocalStorage}; - if (defined $type) { + if ( defined $type ) { $res = $self->_checkType($type); - return $res if ($res->{res} ne 'ok'); + return $res if ( $res->{res} ne 'ok' ); } $psessions = $self->_getPSessions2F($uid); - foreach ( keys %{ $psessions } ) { + foreach ( keys %{$psessions} ) { $sessionId = $psessions->{$_}->{_session_id}; - $session = $self->getSession2F( $sessionId ) - or return { res => 'ko', code => 500, msg => $@ }; + $session = $self->getSession2F($sessionId) + or return { res => 'ko', code => 500, msg => $@ }; - $self->logger->debug("Looking for 2F Device(s) attached to session $sessionId..."); + $self->logger->debug( + "Looking for 2F Device(s) attached to session $sessionId..."); if ( $session->data->{_2fDevices} ) { - $devices = from_json( $session->data->{_2fDevices}, { allow_nonref => 1 } ); + $devices = + from_json( $session->data->{_2fDevices}, { allow_nonref => 1 } ); $total = scalar @$devices; - $self->logger->debug("Found $total 2F devices attached to session $sessionId."); + $self->logger->debug( + "Found $total 2F devices attached to session $sessionId."); @keep = (); while (@$devices) { my $element = shift @$devices; - push @keep, $element - if (( defined $type or defined $id ) and - (( defined $type and $type ne $element->{type} ) or - ( defined $id and $id ne $self->_genId2F($element) ))); + push @keep, + $element + if ( + ( defined $type or defined $id ) + and ( ( defined $type and $type ne $element->{type} ) + or + ( defined $id and $id ne $self->_genId2F($element) ) ) + ); } $lremoved = $total - scalar @keep; - if ($lremoved > 0) { + if ( $lremoved > 0 ) { + # Update session - $self->logger->debug("Removing $lremoved 2F device(s) attached to session $sessionId ..."); + $self->logger->debug( +"Removing $lremoved 2F device(s) attached to session $sessionId ..." + ); $session->data->{_2fDevices} = to_json( \@keep ); $session->update( \%{ $session->data } ); # Delete local cache if ( $localStorage and $localStorage->get($sessionId) ) { - $self->logger->debug("Delete local cache for $sessionId ..."); + $self->logger->debug( + "Delete local cache for $sessionId ..."); $localStorage->remove($sessionId); - } else { - $self->logger->debug("Local cache will not be cleared for $sessionId"); } - } else { - $self->logger->debug("No matching 2F devices attached to session $sessionId were selected for removal."); + else { + $self->logger->debug( + "Local cache will not be cleared for $sessionId"); + } + } + else { + $self->logger->debug( +"No matching 2F devices attached to session $sessionId were selected for removal." + ); } - } else { - $self->logger->debug("No 2F devices attached to session $sessionId were found."); + } + else { + $self->logger->debug( + "No 2F devices attached to session $sessionId were found."); } $removed += $lremoved; } return { - res => 'ok', + res => 'ok', removed => $removed, - msg => $removed > 0 ? "Successful operation: " . $removed . " 2F were removed" : "No operation performed" + msg => $removed > 0 + ? "Successful operation: " . $removed . " 2F were removed" + : "No operation performed" }; } sub _checkType { my ( $self, $type ) = @_; - return {res => "ko", code=> 405, msg => "Invalid input: Type \"$type\" does not exist. Allowed values for type are: \"U2F\", \"TOTP\" or \"UBK\"" } - unless ( $type =~ /\b(?:U2F|TOTP|UBK)\b/ ); + return { + res => "ko", + code => 405, + msg => +"Invalid input: Type \"$type\" does not exist. Allowed values for type are: \"U2F\", \"TOTP\" or \"UBK\"" + } + unless ( $type =~ /\b(?:U2F|TOTP|UBK)\b/ ); return { res => "ok" }; } diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers/OidcRp.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers/OidcRp.pm index b35ef544a9..e900b8cf1e 100644 --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers/OidcRp.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers/OidcRp.pm @@ -72,7 +72,8 @@ sub findOidcRpByClientId { defined $req->params('uClientId') ? $req->params('uClientId') : ( defined $req->params('clientId') ? $req->params('clientId') - : undef ) + : undef + ) ); unless ( defined $clientId ) { @@ -88,7 +89,8 @@ sub findOidcRpByClientId { my $oidcRp = $self->_getOidcRpByClientId( $conf, $clientId ); unless ( defined $oidcRp ) { - return $self->sendError( $req, "OIDC relying party with clientId '$clientId' not found", 404 ); + return $self->sendError( $req, + "OIDC relying party with clientId '$clientId' not found", 404 ); } return $self->sendJSONresponse( $req, $oidcRp ); diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers/SamlSp.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers/SamlSp.pm index 7fed6074be..d7dd41a5e8 100644 --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers/SamlSp.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers/SamlSp.pm @@ -72,7 +72,8 @@ sub findSamlSpByEntityId { defined $req->params('uEntityId') ? $req->params('uEntityId') : ( defined $req->params('entityId') ? $req->params('entityId') - : undef ) + : undef + ) ); unless ( defined $entityId ) { @@ -87,7 +88,8 @@ sub findSamlSpByEntityId { my $samlSp = $self->_getSamlSpByEntityId( $conf, $entityId ); unless ( defined $samlSp ) { - return $self->sendError( $req, "SAML service Provider with entityID '$entityId' not found", 404 ); + return $self->sendError( $req, + "SAML service Provider with entityID '$entityId' not found", 404 ); } return $self->sendJSONresponse( $req, $samlSp ); diff --git a/lemonldap-ng-manager/t/04-2F-api.t b/lemonldap-ng-manager/t/04-2F-api.t index 1839c3ef48..ebd5ab6965 100644 --- a/lemonldap-ng-manager/t/04-2F-api.t +++ b/lemonldap-ng-manager/t/04-2F-api.t @@ -35,7 +35,7 @@ sub newSession { ipAddr => $ip, _whatToTrace => $uid, uid => $uid, - _session_uid => $uid, + _session_uid => $uid, _utime => time, _session_kind => $kind, _2fDevices => to_json($sfaDevices), @@ -48,26 +48,27 @@ sub check200 { my ( $test, $res ) = splice @_; ok( $res->[0] == 200, "$test: Result code is 200" ); count(1); - checkJson($test, $res); + checkJson( $test, $res ); } sub check405 { my ( $test, $res ) = splice @_; ok( $res->[0] == 405, "$test: Result code is 405" ); count(1); - checkJson($test, $res); + checkJson( $test, $res ); } sub check404 { my ( $test, $res ) = splice @_; ok( $res->[0] == 404, "$test: Result code is 404" ); count(1); - checkJson($test, $res); + checkJson( $test, $res ); } sub checkJson { my ( $test, $res ) = splice @_; my $key; + #diag Dumper($res->[2]->[0]); ok( $key = from_json( $res->[2]->[0] ), "$test: Response is JSON" ); count(1); @@ -75,11 +76,15 @@ sub checkJson { sub get { my ( $test, $uid, $type, $id ) = splice @_; - my ( $res ); + my ($res); ok( - $res = &client->_get("/api/v1/secondFactor/$uid" . - ( defined $type ? "/type/$type" : ( defined $id ? "/id/$id": "" )) - ), "$test: Request succeed" + $res = &client->_get( + "/api/v1/secondFactor/$uid" + . ( + defined $type ? "/type/$type" : ( defined $id ? "/id/$id" : "" ) + ) + ), + "$test: Request succeed" ); count(1); return $res; @@ -89,14 +94,13 @@ sub checkGet { my ( $uid, $id ) = splice @_; my ( $test, $res, $ret ); $test = "$uid should have one 2F with id \"$id\""; - $res = get($test, $uid, undef, $id); - check200($test, $res); + $res = get( $test, $uid, undef, $id ); + check200( $test, $res ); + #diag Dumper($res); - $ret = from_json($res->[2]->[0]); - ok ( - ref $ret eq 'HASH' && $ret->{id} eq $id, - "$test: check returned type is HASH and that ids match" - ); + $ret = from_json( $res->[2]->[0] ); + ok( ref $ret eq 'HASH' && $ret->{id} eq $id, + "$test: check returned type is HASH and that ids match" ); count(1); } @@ -104,21 +108,25 @@ sub checkGet404 { my ( $uid, $id ) = splice @_; my ( $test, $res, $ret ); $test = "$uid should not have any 2F with id \"$id\""; - $res = get($test, $uid, undef, $id); - check404($test, $res); + $res = get( $test, $uid, undef, $id ); + check404( $test, $res ); } sub checkGetList { - my ( $expect, $uid, $type) = splice @_; + my ( $expect, $uid, $type ) = splice @_; my ( $test, $res, $ret ); - $test = "$uid should have $expect 2F" . ( defined $type ? " of type \"$type\"" : "" ); - $res = get($test, $uid, $type); - check200($test, $res); + $test = "$uid should have $expect 2F" + . ( defined $type ? " of type \"$type\"" : "" ); + $res = get( $test, $uid, $type ); + check200( $test, $res ); + #diag Dumper($res); - $ret = from_json($res->[2]->[0]); - ok ( + $ret = from_json( $res->[2]->[0] ); + ok( scalar @$ret eq $expect, - "$test: check if nb of 2F found (" . scalar @$ret . ") equals expectation ($expect)" + "$test: check if nb of 2F found (" + . scalar @$ret + . ") equals expectation ($expect)" ); count(1); return $ret; @@ -128,31 +136,35 @@ sub checkGetBadType { my ( $uid, $type ) = splice @_; my ( $test, $res ); $test = "Get for uid $uid and type \"$type\" should get rejected."; - $res = get($test, $uid, $type); - check405($test, $res); + $res = get( $test, $uid, $type ); + check405( $test, $res ); } sub checkGetOnIds { my ( $uid, $ret ) = splice @_; - foreach ( @$ret ) { - checkGet($uid, $_->{id}); + foreach (@$ret) { + checkGet( $uid, $_->{id} ); } } sub checkGetOnIdsNotFound { my ( $uid, $ret ) = splice @_; - foreach ( @$ret ) { - checkGet404($uid, $_->{id}); + foreach (@$ret) { + checkGet404( $uid, $_->{id} ); } } sub del { my ( $test, $uid, $type, $id ) = splice @_; - my ( $res ); + my ($res); ok( - $res = &client->_del("/api/v1/secondFactor/$uid" . - ( defined $type ? "/type/$type" : ( defined $id ? "/id/$id": "" )) - ), "$test: Request succeed" + $res = &client->_del( + "/api/v1/secondFactor/$uid" + . ( + defined $type ? "/type/$type" : ( defined $id ? "/id/$id" : "" ) + ) + ), + "$test: Request succeed" ); count(1); return $res; @@ -162,30 +174,31 @@ sub checkDelete { my ( $uid, $id ) = splice @_; my ( $test, $res ); $test = "$uid should have a 2F with id \"$id\" to be deleted."; - $res = del($test, $uid, undef, $id); - check200($test, $res); + $res = del( $test, $uid, undef, $id ); + check200( $test, $res ); } sub checkDelete404 { my ( $uid, $id ) = splice @_; my ( $test, $res ); $test = "$uid should not have a 2F with id \"$id\" to be deleted."; - $res = del($test, $uid, undef, $id); - check404($test, $res); + $res = del( $test, $uid, undef, $id ); + check404( $test, $res ); } sub checkDeleteList { - my ( $expect, $uid, $type) = splice @_; + my ( $expect, $uid, $type ) = splice @_; my ( $test, $res, $ret, $countDel ); - $test = "Delete all 2F from $uid" . ( defined $type ? " of type \"$type\"" : "" ); - $res = del($test, $uid, $type); - check200($test, $res); - $ret = from_json($res->[2]->[0]); - ( $countDel ) = $ret->{message} =~ m/^Successful operation: ([\d]+) /i; - $countDel = 0 unless (defined $countDel); + $test = + "Delete all 2F from $uid" . ( defined $type ? " of type \"$type\"" : "" ); + $res = del( $test, $uid, $type ); + check200( $test, $res ); + $ret = from_json( $res->[2]->[0] ); + ($countDel) = $ret->{message} =~ m/^Successful operation: ([\d]+) /i; + $countDel = 0 unless ( defined $countDel ); ok( $countDel eq $expect, - "$test: check nb of 2FA deleted ($countDel) matches expectation ($expect)" +"$test: check nb of 2FA deleted ($countDel) matches expectation ($expect)" ); count(1); } @@ -194,8 +207,8 @@ sub checkDeleteBadType { my ( $uid, $type ) = splice @_; my ( $test, $res ); $test = "Delete for uid $uid and type \"$type\" should get rejected."; - $res = del($test, $uid, $type); - check405($test, $res); + $res = del( $test, $uid, $type ); + check405( $test, $res ); } my @ids; @@ -276,57 +289,56 @@ $sfaDevices = [ { $ids[5] = newSession( 'tof', '127.10.0.1', 'Persistent', $sfaDevices ); # dwho -checkGetList(1, 'dwho', 'U2F'); -checkGetList(1, 'dwho', 'TOTP'); -checkGetList(1, 'dwho', 'UBK'); -checkGetBadType('dwho', 'UBKIKI'); -$ret = checkGetList(3, 'dwho'); -checkGetOnIds('dwho', $ret); -checkDelete('dwho', @$ret[0]->{id}); -checkDelete404('dwho', @$ret[0]->{id}); -checkGetList(2, 'dwho'); -checkDeleteList(2, 'dwho'); -checkGetList(0, 'dwho'); -checkDeleteList(0, 'dwho'); +checkGetList( 1, 'dwho', 'U2F' ); +checkGetList( 1, 'dwho', 'TOTP' ); +checkGetList( 1, 'dwho', 'UBK' ); +checkGetBadType( 'dwho', 'UBKIKI' ); +$ret = checkGetList( 3, 'dwho' ); +checkGetOnIds( 'dwho', $ret ); +checkDelete( 'dwho', @$ret[0]->{id} ); +checkDelete404( 'dwho', @$ret[0]->{id} ); +checkGetList( 2, 'dwho' ); +checkDeleteList( 2, 'dwho' ); +checkGetList( 0, 'dwho' ); +checkDeleteList( 0, 'dwho' ); # msmith -checkGetList(0, 'msmith'); +checkGetList( 0, 'msmith' ); # rtyler -checkGetList(1, 'rtyler', 'U2F'); -checkGetList(0, 'rtyler', 'TOTP'); -checkGetList(2, 'rtyler', 'UBK'); -$ret = checkGetList(3, 'rtyler'); -checkGetOnIds('rtyler', $ret); -checkDeleteList(2,'rtyler', 'UBK'); -$ret = checkGetList(1, 'rtyler'); -checkDelete('rtyler', @$ret[0]->{id}); -checkDelete404('rtyler', @$ret[0]->{id}); -checkDeleteList(0, 'rtyler'); - +checkGetList( 1, 'rtyler', 'U2F' ); +checkGetList( 0, 'rtyler', 'TOTP' ); +checkGetList( 2, 'rtyler', 'UBK' ); +$ret = checkGetList( 3, 'rtyler' ); +checkGetOnIds( 'rtyler', $ret ); +checkDeleteList( 2, 'rtyler', 'UBK' ); +$ret = checkGetList( 1, 'rtyler' ); +checkDelete( 'rtyler', @$ret[0]->{id} ); +checkDelete404( 'rtyler', @$ret[0]->{id} ); +checkDeleteList( 0, 'rtyler' ); # davros -checkGetList(1, 'davros', 'U2F'); -checkGetList(1, 'davros', 'TOTP'); -checkGetList(0, 'davros', 'UBK'); -$ret = checkGetList(2, 'davros'); -checkGetOnIds('davros', $ret); -checkDelete('davros', @$ret[0]->{id}); -checkDelete404('davros', @$ret[0]->{id}); -checkGetList(1, 'davros'); -checkDeleteList(1, 'davros', @$ret[1]->{type}); -checkGetList(0, 'davros'); -checkDeleteList(0, 'davros'); +checkGetList( 1, 'davros', 'U2F' ); +checkGetList( 1, 'davros', 'TOTP' ); +checkGetList( 0, 'davros', 'UBK' ); +$ret = checkGetList( 2, 'davros' ); +checkGetOnIds( 'davros', $ret ); +checkDelete( 'davros', @$ret[0]->{id} ); +checkDelete404( 'davros', @$ret[0]->{id} ); +checkGetList( 1, 'davros' ); +checkDeleteList( 1, 'davros', @$ret[1]->{type} ); +checkGetList( 0, 'davros' ); +checkDeleteList( 0, 'davros' ); # tof -checkGetList(1, 'tof', 'U2F'); -checkGetList(0, 'tof', 'TOTP'); -checkGetList(0, 'tof', 'UBK'); -$ret = checkGetList(1, 'tof'); -checkGetOnIds('tof', $ret); -checkDelete('tof', @$ret[0]->{id}); -checkDelete404('tof', @$ret[0]->{id}); -checkGetList(0, 'tof'); -checkDeleteList(0, 'tof'); - -done_testing( ); +checkGetList( 1, 'tof', 'U2F' ); +checkGetList( 0, 'tof', 'TOTP' ); +checkGetList( 0, 'tof', 'UBK' ); +$ret = checkGetList( 1, 'tof' ); +checkGetOnIds( 'tof', $ret ); +checkDelete( 'tof', @$ret[0]->{id} ); +checkDelete404( 'tof', @$ret[0]->{id} ); +checkGetList( 0, 'tof' ); +checkDeleteList( 0, 'tof' ); + +done_testing(); diff --git a/lemonldap-ng-manager/t/04-providers-api.t b/lemonldap-ng-manager/t/04-providers-api.t index 537331e7d6..34b9c0cee6 100644 --- a/lemonldap-ng-manager/t/04-providers-api.t +++ b/lemonldap-ng-manager/t/04-providers-api.t @@ -10,41 +10,50 @@ our $_json = JSON->new->allow_nonref; sub check200 { my ( $test, $res ) = splice @_; + #diag Dumper($res); ok( $res->[0] == 200, "$test: Result code is 200" ); count(1); - checkJson($test, $res); + checkJson( $test, $res ); } + sub check404 { my ( $test, $res ) = splice @_; + #diag Dumper($res); ok( $res->[0] == 404, "$test: Result code is 404" ); count(1); - checkJson($test, $res); + checkJson( $test, $res ); } + sub check405 { my ( $test, $res ) = splice @_; ok( $res->[0] == 405, "$test: Result code is 405" ); count(1); - checkJson($test, $res); + checkJson( $test, $res ); } + sub checkJson { my ( $test, $res ) = splice @_; my $key; + #diag Dumper($res->[2]->[0]); ok( $key = from_json( $res->[2]->[0] ), "$test: Response is JSON" ); count(1); } sub add { - my ( $test, $type, $obj) = splice @_; + my ( $test, $type, $obj ) = splice @_; my $j = $_json->encode($obj); my $res; + #diag Dumper($j); ok( $res = &client->_post( - "/api/v1/providers/$type", '', IO::String->new($j), 'application/json', length($j) + "/api/v1/providers/$type", '', + IO::String->new($j), 'application/json', + length($j) ), "$test: Request succeed" ); @@ -53,60 +62,61 @@ sub add { } sub checkAdd { - my ( $test, $type, $add) = splice @_; - check200($test, add($test, $type, $add)); + my ( $test, $type, $add ) = splice @_; + check200( $test, add( $test, $type, $add ) ); } sub checkAddFailsIfExists { - my ( $test, $type, $add) = splice @_; - check405($test, add($test, $type, $add)); + my ( $test, $type, $add ) = splice @_; + check405( $test, add( $test, $type, $add ) ); } sub checkAddWithUnknownAttributes { - my ( $test, $type, $add) = splice @_; - check405($test, add($test, $type, $add)); + my ( $test, $type, $add ) = splice @_; + check405( $test, add( $test, $type, $add ) ); } sub get { - my ( $test, $type, $confKey) = splice @_; + my ( $test, $type, $confKey ) = splice @_; my $res; - ok( - $res = &client->_get("/api/v1/providers/$type/$confKey", ''), - "$test: Request succeed" - ); + ok( $res = &client->_get( "/api/v1/providers/$type/$confKey", '' ), + "$test: Request succeed" ); count(1); return $res; } sub checkGet { - my ( $test, $type, $confKey, $attrPath, $expectedValue) = splice @_; - my $res = get($test, $type, $confKey); - check200($test, $res); + my ( $test, $type, $confKey, $attrPath, $expectedValue ) = splice @_; + my $res = get( $test, $type, $confKey ); + check200( $test, $res ); my @path = split '/', $attrPath; - my $key = from_json($res->[2]->[0]); + my $key = from_json( $res->[2]->[0] ); for (@path) { $key = $key->{$_}; } - ok ( + ok( $key eq $expectedValue, - "$test: check if $attrPath value \"$key\" matches expected value \"$expectedValue\"" +"$test: check if $attrPath value \"$key\" matches expected value \"$expectedValue\"" ); count(1); } sub checkGetNotFound { - my ( $test, $type, $confKey) = splice @_; - check404($test, get($test, $type, $confKey)); + my ( $test, $type, $confKey ) = splice @_; + check404( $test, get( $test, $type, $confKey ) ); } sub update { - my ( $test, $type, $confKey, $obj) = splice @_; + my ( $test, $type, $confKey, $obj ) = splice @_; my $j = $_json->encode($obj); + #diag Dumper($j); my $res; ok( $res = &client->_patch( - "/api/v1/providers/$type/$confKey", '', IO::String->new($j), 'application/json', length($j) + "/api/v1/providers/$type/$confKey", '', + IO::String->new($j), 'application/json', + length($j) ), "$test: Request succeed" ); @@ -115,32 +125,34 @@ sub update { } sub checkUpdate { - my ( $test, $type, $confKey, $update) = splice @_; - check200($test, update($test, $type, $confKey, $update)); + my ( $test, $type, $confKey, $update ) = splice @_; + check200( $test, update( $test, $type, $confKey, $update ) ); } sub checkUpdateNotFound { - my ( $test, $type, $confKey, $update) = splice @_; - check404($test, update($test, $type, $confKey, $update)); + my ( $test, $type, $confKey, $update ) = splice @_; + check404( $test, update( $test, $type, $confKey, $update ) ); } sub checkUpdateFailsIfExists { - my ( $test, $type, $confKey, $update) = splice @_; - check405($test, update($test, $type, $confKey, $update)); + my ( $test, $type, $confKey, $update ) = splice @_; + check405( $test, update( $test, $type, $confKey, $update ) ); } sub checkUpdateWithUnknownAttributes { - my ( $test, $type, $confKey, $update) = splice @_; - check405($test, update($test, $type, $confKey, $update)); + my ( $test, $type, $confKey, $update ) = splice @_; + check405( $test, update( $test, $type, $confKey, $update ) ); } sub replace { - my ( $test, $type, $confKey, $obj) = splice @_; + my ( $test, $type, $confKey, $obj ) = splice @_; my $j = $_json->encode($obj); my $res; ok( $res = &client->_put( - "/api/v1/providers/$type/$confKey", '', IO::String->new($j), 'application/json', length($j) + "/api/v1/providers/$type/$confKey", '', + IO::String->new($j), 'application/json', + length($j) ), "$test: Request succeed" ); @@ -149,30 +161,33 @@ sub replace { } sub checkReplace { - my ( $test, $type, $confKey, $replace) = splice @_; - check200($test, replace($test, $type, $confKey, $replace)); + my ( $test, $type, $confKey, $replace ) = splice @_; + check200( $test, replace( $test, $type, $confKey, $replace ) ); } sub checkReplaceAlreadyThere { - my ( $test, $type, $confKey, $replace) = splice @_; - check405($test, replace($test, $type, $confKey, $replace)); + my ( $test, $type, $confKey, $replace ) = splice @_; + check405( $test, replace( $test, $type, $confKey, $replace ) ); } sub checkReplaceNotFound { - my ( $test, $type, $confKey, $update) = splice @_; - check404($test, replace($test, $type, $confKey, $update)); + my ( $test, $type, $confKey, $update ) = splice @_; + check404( $test, replace( $test, $type, $confKey, $update ) ); } sub checkReplaceWithUnknownAttribute { - my ( $test, $type, $confKey, $replace) = splice @_; - check405($test, replace($test, $type, $confKey, $replace)); + my ( $test, $type, $confKey, $replace ) = splice @_; + check405( $test, replace( $test, $type, $confKey, $replace ) ); } sub findByConfKey { - my ( $test, $type, $confKey) = splice @_; + my ( $test, $type, $confKey ) = splice @_; my $res; ok( - $res = &client->_get("/api/v1/providers/$type/findByConfKey", "pattern=$confKey"), + $res = &client->_get( + "/api/v1/providers/$type/findByConfKey", + "pattern=$confKey" + ), "$test: Request succeed" ); count(1); @@ -180,32 +195,35 @@ sub findByConfKey { } sub checkFindByConfKey { - my ( $test, $type, $confKey, $expectedHits) = splice @_; - my $res = findByConfKey($test, $type, $confKey); - check200($test, $res); - my $hits = from_json($res->[2]->[0]); + my ( $test, $type, $confKey, $expectedHits ) = splice @_; + my $res = findByConfKey( $test, $type, $confKey ); + check200( $test, $res ); + my $hits = from_json( $res->[2]->[0] ); my $hit; my $counter = 0; - foreach $hit (@{$hits}) { + foreach $hit ( @{$hits} ) { $counter++; - ok ( + ok( $hit->{confKey} =~ $confKey, - "$test: check if confKey value \"$hit->{confKey}\" matches pattern \"$confKey\"" +"$test: check if confKey value \"$hit->{confKey}\" matches pattern \"$confKey\"" ); count(1); } - ok ( + ok( $counter eq $expectedHits, - "$test: check if nb of hits returned ($counter) matches expectation ($expectedHits)" +"$test: check if nb of hits returned ($counter) matches expectation ($expectedHits)" ); count(1); } sub findByProviderId { - my ( $test, $type, $providerIdName, $providerId) = splice @_; + my ( $test, $type, $providerIdName, $providerId ) = splice @_; my $res; ok( - $res = &client->_get("/api/v1/providers/$type/findBy" . ucfirst $providerIdName, "$providerIdName=$providerId"), + $res = &client->_get( + "/api/v1/providers/$type/findBy" . ucfirst $providerIdName, + "$providerIdName=$providerId" + ), "$test: Request succeed" ); count(1); @@ -213,59 +231,63 @@ sub findByProviderId { } sub checkFindByProviderId { - my ( $test, $type, $providerIdName, $providerId) = splice @_; - my $res = findByProviderId($test, $type, $providerIdName, $providerId); - check200($test, $res); - my $result = from_json($res->[2]->[0]); + my ( $test, $type, $providerIdName, $providerId ) = splice @_; + my $res = findByProviderId( $test, $type, $providerIdName, $providerId ); + check200( $test, $res ); + my $result = from_json( $res->[2]->[0] ); my $gotProviderId; - if ($providerIdName eq 'entityId') { - ( $gotProviderId ) = $result->{metadata} =~ m/entityID=['"](.+?)['"]/i; - } else { + if ( $providerIdName eq 'entityId' ) { + ($gotProviderId) = $result->{metadata} =~ m/entityID=['"](.+?)['"]/i; + } + else { $gotProviderId = $result->{$providerIdName}; } ok( $gotProviderId eq $providerId, - "$test: Check $providerIdName value returned \"$gotProviderId\" matched expected value \"$providerId\"" +"$test: Check $providerIdName value returned \"$gotProviderId\" matched expected value \"$providerId\"" ); count(1); } sub checkFindByProviderIdNotFound { - my ( $test, $type, $providerIdName, $providerId) = splice @_; - check404($test, findByProviderId($test, $type, $providerIdName, $providerId)); + my ( $test, $type, $providerIdName, $providerId ) = splice @_; + check404( $test, + findByProviderId( $test, $type, $providerIdName, $providerId ) ); } sub deleteProvider { - my ( $test, $type, $confKey) = splice @_; + my ( $test, $type, $confKey ) = splice @_; my $res; ok( $res = &client->_del( - "/api/v1/providers/$type/$confKey", '', '', 'application/json', 0 + "/api/v1/providers/$type/$confKey", + '', '', 'application/json', 0 ), "$test: Request succeed" ); count(1); return $res; } + sub checkDelete { - my ( $test, $type, $confKey) = splice @_; - check200($test, deleteProvider($test, $type, $confKey)); + my ( $test, $type, $confKey ) = splice @_; + check200( $test, deleteProvider( $test, $type, $confKey ) ); } sub checkDeleteNotFound { - my ( $test, $type, $confKey) = splice @_; - check404($test, deleteProvider($test, $type, $confKey)); + my ( $test, $type, $confKey ) = splice @_; + check404( $test, deleteProvider( $test, $type, $confKey ) ); } my $test; my $oidcRp = { - confKey => 'myOidcRp1', - clientId => 'myOidcClient1', + confKey => 'myOidcRp1', + clientId => 'myOidcClient1', exportedVars => { - 'sub' => "uid", + 'sub' => "uid", family_name => "sn", - given_name => "givenName" + given_name => "givenName" }, extraClaim => { phone => 'telephoneNumber', @@ -273,243 +295,265 @@ my $oidcRp = { }, options => { oidcRPMetaDataOptionsClientSecret => 'secret', - oidcRPMetaDataOptionsIcon => 'web.png' + oidcRPMetaDataOptionsIcon => 'web.png' } }; $test = "OidcRp - Add should succeed"; -checkAdd($test, 'oidc/rp', $oidcRp); -checkGet($test, 'oidc/rp', 'myOidcRp1', 'options/oidcRPMetaDataOptionsIcon', 'web.png'); -checkGet($test, 'oidc/rp', 'myOidcRp1', 'options/oidcRPMetaDataOptionsClientSecret', 'secret'); +checkAdd( $test, 'oidc/rp', $oidcRp ); +checkGet( $test, 'oidc/rp', 'myOidcRp1', 'options/oidcRPMetaDataOptionsIcon', + 'web.png' ); +checkGet( $test, 'oidc/rp', 'myOidcRp1', + 'options/oidcRPMetaDataOptionsClientSecret', 'secret' ); $test = "OidcRp - Check attribute default value was set after add"; -checkGet($test, 'oidc/rp', 'myOidcRp1', 'options/oidcRPMetaDataOptionsIDTokenSignAlg', 'HS512'); +checkGet( $test, 'oidc/rp', 'myOidcRp1', + 'options/oidcRPMetaDataOptionsIDTokenSignAlg', 'HS512' ); $test = "OidcRp - Add Should fail on duplicate confKey"; -checkAddFailsIfExists($test, 'oidc/rp', $oidcRp); +checkAddFailsIfExists( $test, 'oidc/rp', $oidcRp ); $test = "OidcRp - Update should succeed and keep existing values"; -$oidcRp->{options}->{oidcRPMetaDataOptionsClientSecret} = 'secret2'; +$oidcRp->{options}->{oidcRPMetaDataOptionsClientSecret} = 'secret2'; $oidcRp->{options}->{oidcRPMetaDataOptionsIDTokenSignAlg} = 'RS512'; delete $oidcRp->{options}->{oidcRPMetaDataOptionsIcon}; delete $oidcRp->{extraClaim}; delete $oidcRp->{exportedVars}; $oidcRp->{exportedVars}->{cn} = 'cn'; -checkUpdate($test, 'oidc/rp', 'myOidcRp1', $oidcRp); -checkGet($test, 'oidc/rp', 'myOidcRp1', 'options/oidcRPMetaDataOptionsClientSecret', 'secret2'); -checkGet($test, 'oidc/rp', 'myOidcRp1', 'options/oidcRPMetaDataOptionsIDTokenSignAlg', 'RS512'); -checkGet($test, 'oidc/rp', 'myOidcRp1', 'options/oidcRPMetaDataOptionsIcon', 'web.png'); -checkGet($test, 'oidc/rp', 'myOidcRp1', 'exportedVars/cn', 'cn'); -checkGet($test, 'oidc/rp', 'myOidcRp1', 'exportedVars/family_name', 'sn'); -checkGet($test, 'oidc/rp', 'myOidcRp1', 'extraClaim/phone', 'telephoneNumber'); +checkUpdate( $test, 'oidc/rp', 'myOidcRp1', $oidcRp ); +checkGet( $test, 'oidc/rp', 'myOidcRp1', + 'options/oidcRPMetaDataOptionsClientSecret', 'secret2' ); +checkGet( $test, 'oidc/rp', 'myOidcRp1', + 'options/oidcRPMetaDataOptionsIDTokenSignAlg', 'RS512' ); +checkGet( $test, 'oidc/rp', 'myOidcRp1', 'options/oidcRPMetaDataOptionsIcon', + 'web.png' ); +checkGet( $test, 'oidc/rp', 'myOidcRp1', 'exportedVars/cn', 'cn' ); +checkGet( $test, 'oidc/rp', 'myOidcRp1', 'exportedVars/family_name', 'sn' ); +checkGet( $test, 'oidc/rp', 'myOidcRp1', 'extraClaim/phone', + 'telephoneNumber' ); $test = "OidcRp - Update should fail on non existing options"; $oidcRp->{options}->{playingPossum} = 'elephant'; -checkUpdateWithUnknownAttributes($test, 'oidc/rp', 'myOidcRp1', $oidcRp); +checkUpdateWithUnknownAttributes( $test, 'oidc/rp', 'myOidcRp1', $oidcRp ); delete $oidcRp->{options}->{playingPossum}; $test = "OidcRp - Add Should fail on duplicate clientId"; $oidcRp->{confKey} = 'myOidcRp2'; -checkAddFailsIfExists($test, 'oidc/rp', $oidcRp); +checkAddFailsIfExists( $test, 'oidc/rp', $oidcRp ); -$test = "OidcRp - Add Should fail on non existing options"; -$oidcRp->{confKey} = 'myOidcRp2'; +$test = "OidcRp - Add Should fail on non existing options"; +$oidcRp->{confKey} = 'myOidcRp2'; $oidcRp->{clientId} = 'myOidcClient2'; $oidcRp->{options}->{playingPossum} = 'ElephantInTheRoom'; -checkAddWithUnknownAttributes($test, 'oidc/rp', $oidcRp); +checkAddWithUnknownAttributes( $test, 'oidc/rp', $oidcRp ); delete $oidcRp->{options}->{playingPossum}; $test = "OidcRp - 2nd add should succeed"; -checkAdd($test, 'oidc/rp', $oidcRp); +checkAdd( $test, 'oidc/rp', $oidcRp ); $test = "OidcRp - Update should fail if client id exists"; $oidcRp->{clientId} = 'myOidcClient1'; -checkUpdateFailsIfExists($test, 'oidc/rp', 'myOidcRp2', $oidcRp); +checkUpdateFailsIfExists( $test, 'oidc/rp', 'myOidcRp2', $oidcRp ); $test = "OidcRp - Update should fail if confKey not found"; $oidcRp->{confKey} = 'myOidcRp3'; -checkUpdateNotFound($test, 'oidc/rp', 'myOidcRp3', $oidcRp); +checkUpdateNotFound( $test, 'oidc/rp', 'myOidcRp3', $oidcRp ); -$test = "OidcRp - Replace should succeed"; -$oidcRp->{confKey} = 'myOidcRp2'; +$test = "OidcRp - Replace should succeed"; +$oidcRp->{confKey} = 'myOidcRp2'; $oidcRp->{clientId} = 'myOidcClient2'; delete $oidcRp->{options}->{oidcRPMetaDataOptionsIcon}; delete $oidcRp->{options}->{oidcRPMetaDataOptionsIDTokenSignAlg}; -checkReplace($test, 'oidc/rp', 'myOidcRp2', $oidcRp); +checkReplace( $test, 'oidc/rp', 'myOidcRp2', $oidcRp ); $test = "OidcRp - Check attribute default value was set after replace"; -checkGet($test, 'oidc/rp', 'myOidcRp2', 'options/oidcRPMetaDataOptionsIDTokenSignAlg', 'HS512'); +checkGet( $test, 'oidc/rp', 'myOidcRp2', + 'options/oidcRPMetaDataOptionsIDTokenSignAlg', 'HS512' ); $test = "OidcRp - Replace should fail on non existing options"; $oidcRp->{options}->{playingPossum} = 'elephant'; -checkReplaceWithUnknownAttribute($test, 'oidc/rp', 'myOidcRp2', $oidcRp); +checkReplaceWithUnknownAttribute( $test, 'oidc/rp', 'myOidcRp2', $oidcRp ); delete $oidcRp->{options}->{playingPossum}; $test = "OidcRp - Replace should fail if confKey not found"; $oidcRp->{confKey} = 'myOidcRp3'; -checkReplaceNotFound($test, 'oidc/rp', 'myOidcRp3', $oidcRp); +checkReplaceNotFound( $test, 'oidc/rp', 'myOidcRp3', $oidcRp ); $test = "OidcRp - FindByConfKey should find 2 hits"; -checkFindByConfKey($test, 'oidc/rp', '^myOidcRp.$', 2); +checkFindByConfKey( $test, 'oidc/rp', '^myOidcRp.$', 2 ); $test = "OidcRp - FindByConfKey should find 1 hit"; -checkFindByConfKey($test, 'oidc/rp', 'myOidcRp1', 1); +checkFindByConfKey( $test, 'oidc/rp', 'myOidcRp1', 1 ); $test = "OidcRp - FindByConfKey should find 0 hits"; -checkFindByConfKey($test, 'oidc/rp', 'myOidcRp3', 0); +checkFindByConfKey( $test, 'oidc/rp', 'myOidcRp3', 0 ); $test = "OidcRp - FindByClientId should find one entry"; -checkFindByProviderId($test, 'oidc/rp', 'clientId', 'myOidcClient1'); +checkFindByProviderId( $test, 'oidc/rp', 'clientId', 'myOidcClient1' ); $test = "OidcRp - FindByClientId should find nothing"; -checkFindByProviderIdNotFound($test, 'oidc/rp', 'clientId', 'myOidcClient3'); +checkFindByProviderIdNotFound( $test, 'oidc/rp', 'clientId', 'myOidcClient3' ); $test = "OidcRp - Clean up"; -checkDelete($test, 'oidc/rp', 'myOidcRp1'); -checkDelete($test, 'oidc/rp', 'myOidcRp2'); +checkDelete( $test, 'oidc/rp', 'myOidcRp1' ); +checkDelete( $test, 'oidc/rp', 'myOidcRp2' ); $test = "OidcRp - Entity should not be found after clean up"; -checkDeleteNotFound($test, 'oidc/rp', 'myOidcRp1'); +checkDeleteNotFound( $test, 'oidc/rp', 'myOidcRp1' ); -my $metadata1 = "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"; +my $metadata1 = +"urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"; -my $metadata2 = "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"; +my $metadata2 = +"urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"; my $samlSp = { - confKey => 'mySamlSp1', - metadata => $metadata1, + confKey => 'mySamlSp1', + metadata => $metadata1, exportedAttributes => { family_name => { - format => "urn:oasis:names:tc:SAML:2.0:attrname-format:basic", - friendlyName => "surname", - mandatory => "false", - name => "sn" + format => "urn:oasis:names:tc:SAML:2.0:attrname-format:basic", + friendlyName => "surname", + mandatory => "false", + name => "sn" }, cn => { - friendlyName => "commonname", - mandatory => "true", - name => "uid" + friendlyName => "commonname", + mandatory => "true", + name => "uid" }, uid => { - mandatory => "true", - name => "uid" + mandatory => "true", + name => "uid" }, phone => { - mandatory => "false", - format => "urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified", - name => "telephoneNumber" + mandatory => "false", + format => "urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified", + name => "telephoneNumber" }, function => { - name => "title", - mandatory => "false", - format => "urn:oasis:names:tc:SAML:2.0:attrname-format:uri" + name => "title", + mandatory => "false", + format => "urn:oasis:names:tc:SAML:2.0:attrname-format:uri" }, given_name => { - mandatory => "false", - name => "givenName" + mandatory => "false", + name => "givenName" } }, options => { - samlSPMetaDataOptionsCheckSLOMessageSignature => 0, - samlSPMetaDataOptionsEncryptionMode => "assertion", - samlSPMetaDataOptionsSessionNotOnOrAfterTimeout => 36000 + samlSPMetaDataOptionsCheckSLOMessageSignature => 0, + samlSPMetaDataOptionsEncryptionMode => "assertion", + samlSPMetaDataOptionsSessionNotOnOrAfterTimeout => 36000 } }; $test = "SamlSp - Add should succeed"; -checkAdd($test, 'saml/sp', $samlSp); -checkGet($test, 'saml/sp', 'mySamlSp1', 'options/samlSPMetaDataOptionsEncryptionMode', 'assertion'); -checkGet($test, 'saml/sp', 'mySamlSp1', 'options/samlSPMetaDataOptionsSessionNotOnOrAfterTimeout', 36000); +checkAdd( $test, 'saml/sp', $samlSp ); +checkGet( $test, 'saml/sp', 'mySamlSp1', + 'options/samlSPMetaDataOptionsEncryptionMode', 'assertion' ); +checkGet( $test, 'saml/sp', 'mySamlSp1', + 'options/samlSPMetaDataOptionsSessionNotOnOrAfterTimeout', 36000 ); $test = "SamlSp - Check attribute default value was set after add"; -checkGet($test, 'saml/sp', 'mySamlSp1', 'options/samlSPMetaDataOptionsNotOnOrAfterTimeout', 72000); +checkGet( $test, 'saml/sp', 'mySamlSp1', + 'options/samlSPMetaDataOptionsNotOnOrAfterTimeout', 72000 ); $test = "SamlSp - Add Should fail on duplicate confKey"; -checkAddFailsIfExists($test, 'saml/sp', $samlSp); +checkAddFailsIfExists( $test, 'saml/sp', $samlSp ); $test = "SamlSp - Update should succeed and keep existing values"; $samlSp->{options}->{samlSPMetaDataOptionsCheckSLOMessageSignature} = 1; -$samlSp->{options}->{samlSPMetaDataOptionsEncryptionMode} = 'nameid'; +$samlSp->{options}->{samlSPMetaDataOptionsEncryptionMode} = 'nameid'; delete $samlSp->{options}->{samlSPMetaDataOptionsSessionNotOnOrAfterTimeout}; delete $samlSp->{exportedAttributes}; -$samlSp->{exportedAttributes}->{cn}->{name} = "cn", -$samlSp->{exportedAttributes}->{cn}->{friendlyName} = "common_name", -$samlSp->{exportedAttributes}->{cn}->{mandatory} = "false", -checkUpdate($test, 'saml/sp', 'mySamlSp1', $samlSp); -checkGet($test, 'saml/sp', 'mySamlSp1', 'options/samlSPMetaDataOptionsCheckSLOMessageSignature', 1); -checkGet($test, 'saml/sp', 'mySamlSp1', 'options/samlSPMetaDataOptionsSessionNotOnOrAfterTimeout', 36000); -checkGet($test, 'saml/sp', 'mySamlSp1', 'exportedAttributes/cn/friendlyName', 'common_name'); -checkGet($test, 'saml/sp', 'mySamlSp1', 'exportedAttributes/cn/mandatory', 'false'); -checkGet($test, 'saml/sp', 'mySamlSp1', 'exportedAttributes/cn/mandatory', 'false'); -checkGet($test, 'saml/sp', 'mySamlSp1', 'exportedAttributes/cn/name', 'uid'); -checkGet($test, 'saml/sp', 'mySamlSp1', 'exportedAttributes/given_name/name', 'givenName'); +$samlSp->{exportedAttributes}->{cn}->{name} = "cn", + $samlSp->{exportedAttributes}->{cn}->{friendlyName} = "common_name", + $samlSp->{exportedAttributes}->{cn}->{mandatory} = "false", + checkUpdate( $test, 'saml/sp', 'mySamlSp1', $samlSp ); +checkGet( $test, 'saml/sp', 'mySamlSp1', + 'options/samlSPMetaDataOptionsCheckSLOMessageSignature', 1 ); +checkGet( $test, 'saml/sp', 'mySamlSp1', + 'options/samlSPMetaDataOptionsSessionNotOnOrAfterTimeout', 36000 ); +checkGet( $test, 'saml/sp', 'mySamlSp1', 'exportedAttributes/cn/friendlyName', + 'common_name' ); +checkGet( $test, 'saml/sp', 'mySamlSp1', 'exportedAttributes/cn/mandatory', + 'false' ); +checkGet( $test, 'saml/sp', 'mySamlSp1', 'exportedAttributes/cn/mandatory', + 'false' ); +checkGet( $test, 'saml/sp', 'mySamlSp1', 'exportedAttributes/cn/name', 'uid' ); +checkGet( $test, 'saml/sp', 'mySamlSp1', 'exportedAttributes/given_name/name', + 'givenName' ); $test = "SamlSp - Update should fail on non existing options"; $samlSp->{options}->{playingPossum} = 'elephant'; -checkUpdateWithUnknownAttributes($test, 'saml/sp', 'mySamlSp1', $samlSp); +checkUpdateWithUnknownAttributes( $test, 'saml/sp', 'mySamlSp1', $samlSp ); delete $samlSp->{options}->{playingPossum}; $test = "SamlSp - Add Should fail on duplicate entityId"; $samlSp->{confKey} = 'mySamlSp2'; -checkAddFailsIfExists($test, 'saml/sp', $samlSp); +checkAddFailsIfExists( $test, 'saml/sp', $samlSp ); -$test = "SamlSp - Add Should fail on non existing options"; -$samlSp->{confKey} = 'mySamlSp2'; +$test = "SamlSp - Add Should fail on non existing options"; +$samlSp->{confKey} = 'mySamlSp2'; $samlSp->{metadata} = $metadata2; $samlSp->{options}->{playingPossum} = 'ElephantInTheRoom'; -checkAddWithUnknownAttributes($test, 'saml/sp', $samlSp); +checkAddWithUnknownAttributes( $test, 'saml/sp', $samlSp ); delete $samlSp->{options}->{playingPossum}; $test = "SamlSp - 2nd add should succeed"; -checkAdd($test, 'saml/sp', $samlSp); +checkAdd( $test, 'saml/sp', $samlSp ); $test = "SamlSp - Update should fail if client id exists"; $samlSp->{metadata} = $metadata1; -checkUpdateFailsIfExists($test, 'saml/sp', 'mySamlSp2', $samlSp); +checkUpdateFailsIfExists( $test, 'saml/sp', 'mySamlSp2', $samlSp ); $test = "SamlSp - Update should fail if confKey not found"; $samlSp->{confKey} = 'mySamlSp3'; -checkUpdateNotFound($test, 'saml/sp', 'mySamlSp3', $samlSp); +checkUpdateNotFound( $test, 'saml/sp', 'mySamlSp3', $samlSp ); -$test = "SamlSp - Replace should succeed"; -$samlSp->{confKey} = 'mySamlSp2'; +$test = "SamlSp - Replace should succeed"; +$samlSp->{confKey} = 'mySamlSp2'; $samlSp->{metadata} = $metadata2; delete $samlSp->{options}->{samlSPMetaDataOptionsEncryptionMode}; -checkReplace($test, 'saml/sp', 'mySamlSp2', $samlSp); +checkReplace( $test, 'saml/sp', 'mySamlSp2', $samlSp ); $test = "SamlSp - Check attribute default value was set after replace"; -checkGet($test, 'saml/sp', 'mySamlSp2', 'options/samlSPMetaDataOptionsEncryptionMode', 'none'); +checkGet( $test, 'saml/sp', 'mySamlSp2', + 'options/samlSPMetaDataOptionsEncryptionMode', 'none' ); $test = "SamlSp - Replace should fail on non existing options"; $samlSp->{options}->{playingPossum} = 'elephant'; -checkReplaceWithUnknownAttribute($test, 'saml/sp', 'mySamlSp2', $samlSp); +checkReplaceWithUnknownAttribute( $test, 'saml/sp', 'mySamlSp2', $samlSp ); delete $samlSp->{options}->{playingPossum}; $test = "SamlSp - Replace should fail if confKey not found"; $samlSp->{confKey} = 'mySamlSp3'; -checkReplaceNotFound($test, 'saml/sp', 'mySamlSp3', $samlSp); +checkReplaceNotFound( $test, 'saml/sp', 'mySamlSp3', $samlSp ); $test = "SamlSp - FindByConfKey should find 2 hits"; -checkFindByConfKey($test, 'saml/sp', '^mySamlSp.$', 2); +checkFindByConfKey( $test, 'saml/sp', '^mySamlSp.$', 2 ); $test = "SamlSp - FindByConfKey should find 1 hit"; -checkFindByConfKey($test, 'saml/sp', 'mySamlSp1', 1); +checkFindByConfKey( $test, 'saml/sp', 'mySamlSp1', 1 ); $test = "SamlSp - FindByConfKey should find 0 hits"; -checkFindByConfKey($test, 'saml/sp', 'mySamlSp3', 0); +checkFindByConfKey( $test, 'saml/sp', 'mySamlSp3', 0 ); $test = "SamlSp - FindByEntityId should find one entry"; -checkFindByProviderId($test, 'saml/sp', 'entityId', 'https://myapp.domain.com/saml/metadata'); +checkFindByProviderId( $test, 'saml/sp', 'entityId', + 'https://myapp.domain.com/saml/metadata' ); $test = "SamlSp - FindByEntityId should find nothing"; -checkFindByProviderIdNotFound($test, 'saml/sp', 'entityId', 'https://myapp3.domain.com/saml/metadata'); +checkFindByProviderIdNotFound( $test, 'saml/sp', 'entityId', + 'https://myapp3.domain.com/saml/metadata' ); $test = "SamlSp - Clean up"; -checkDelete($test, 'saml/sp', 'mySamlSp1'); -checkDelete($test, 'saml/sp', 'mySamlSp2'); +checkDelete( $test, 'saml/sp', 'mySamlSp1' ); +checkDelete( $test, 'saml/sp', 'mySamlSp2' ); $test = "SamlSp - Entity should not be found after clean up"; -checkDeleteNotFound($test, 'saml/sp', 'mySamlSp1'); +checkDeleteNotFound( $test, 'saml/sp', 'mySamlSp1' ); # Clean up generated conf files, except for "lmConf-1.json" unlink grep { $_ ne "t/conf/lmConf-1.json" } glob "t/conf/lmConf-*.json"; -done_testing( ); +done_testing(); -- GitLab From 9dc29c02d9e1062a7ac09c735083647004f88636 Mon Sep 17 00:00:00 2001 From: Soisik Froger Date: Thu, 2 Jan 2020 09:56:50 +0100 Subject: [PATCH 27/31] fixed unit test comments --- lemonldap-ng-manager/t/04-2F-api.t | 2 +- lemonldap-ng-manager/t/04-providers-api.t | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lemonldap-ng-manager/t/04-2F-api.t b/lemonldap-ng-manager/t/04-2F-api.t index ebd5ab6965..7197a1f832 100644 --- a/lemonldap-ng-manager/t/04-2F-api.t +++ b/lemonldap-ng-manager/t/04-2F-api.t @@ -1,4 +1,4 @@ -# Test RSA key generation +# Test 2F API use Test::More; use strict; diff --git a/lemonldap-ng-manager/t/04-providers-api.t b/lemonldap-ng-manager/t/04-providers-api.t index 34b9c0cee6..896272c9df 100644 --- a/lemonldap-ng-manager/t/04-providers-api.t +++ b/lemonldap-ng-manager/t/04-providers-api.t @@ -1,4 +1,4 @@ -# Test RSA key generation +# Test Providers API use Test::More; use strict; -- GitLab From 62b9e8574c64631ef99aa4405414961614bd3031 Mon Sep 17 00:00:00 2001 From: Soisik Froger Date: Thu, 2 Jan 2020 16:43:53 +0100 Subject: [PATCH 28/31] Manager API - 2F remove from SSO session and local cache - #2033 --- .../lib/Lemonldap/NG/Manager/Api/2F.pm | 141 ++++++++++-------- lemonldap-ng-manager/t/04-2F-api.t | 66 ++++---- 2 files changed, 120 insertions(+), 87 deletions(-) diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/2F.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/2F.pm index 64393052a2..5e10d55130 100644 --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/2F.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/2F.pm @@ -143,7 +143,7 @@ sub _get2F { return $res if ( $res->{res} ne 'ok' ); } - $psessions = $self->_getPSessions2F($uid); + $psessions = $self->_getSessions2F($self->_getPersistentMod, 'Persistent', '_session_uid', $uid); foreach ( keys %{$psessions} ) { my $devices = @@ -173,64 +173,62 @@ sub _genId2F { "" ); } -sub _getMod2F { +sub _getPersistentMod { my ($self) = @_; my $mod = $self->sessionTypes->{persistent}; $mod->{options}->{backend} = $mod->{module}; return $mod; } -sub _getPSessions2F { - my ( $self, $uid ) = @_; - $self->logger->debug("Looking for psessions for uid $uid ..."); - my $psessions = Lemonldap::NG::Common::Apache::Session->searchOn( - $self->_getMod2F->{options}, - '_session_uid', $uid, +sub _getSSOMod { + my ($self) = @_; + my $mod = $self->sessionTypes->{global}; + $mod->{options}->{backend} = $mod->{module}; + return $mod; +} + +sub _getSessions2F { + my ( $self, $mod, $kind, $key, $uid ) = @_; + $self->logger->debug("Looking for sessions for uid $uid ..."); + my $sessions = Lemonldap::NG::Common::Apache::Session->searchOn( + $mod->{options}, $key, $uid, ( '_session_kind', '_session_uid', '_session_id', '_2fDevices' ) ); - foreach ( keys %{$psessions} ) { - delete $psessions->{$_} - unless ( $psessions->{$_}->{_session_kind} eq 'Persistent' ); + foreach ( keys %{$sessions} ) { + delete $sessions->{$_} + unless ( $sessions->{$_}->{_session_kind} eq $kind ); } $self->logger->debug( - "Found " . scalar( keys %{$psessions} ) . " psessions for uid $uid." ); - return $psessions; + "Found " . scalar( keys %{$sessions} ) . " $kind sessions for uid $uid." ); + + return $sessions; } -sub getSession2F { - my ( $self, $sessionId ) = @_; +sub _getSession2F { + my ( $self, $sessionId, $mod ) = @_; $self->logger->debug("Looking for session with sessionId $sessionId ..."); - my $session = $self->getApacheSession( $self->_getMod2F, $sessionId ); + my $session = $self->getApacheSession( $mod, $sessionId ); $self->logger->debug( defined $session ? "Session $sessionId found." : " No session found for sessionId $sessionId" ); + return $session; } -sub _delete2F { - my ( $self, $uid, $type, $id ) = @_; +sub _delete2FFromSessions { + my ( $self, $uid, $type, $id, $mod, $kind, $key ) = @_; my ( - $res, $psessions, $sessionId, $session, $devices, - @keep, $total, $removed, $lremoved, $localStorage + $sessions, $session, $devices, @keep, $removed, + $total, $module, $localStorage ); + $sessions = $self->_getSessions2F($mod, $kind, $key, $uid); + foreach ( keys %{$sessions} ) { - $localStorage = Lemonldap::NG::Handler::PSGI::Main->tsv->{refLocalStorage}; - - if ( defined $type ) { - $res = $self->_checkType($type); - return $res if ( $res->{res} ne 'ok' ); - } - - $psessions = $self->_getPSessions2F($uid); - - foreach ( keys %{$psessions} ) { - - $sessionId = $psessions->{$_}->{_session_id}; - $session = $self->getSession2F($sessionId) + $session = $self->_getSession2F($_, $mod) or return { res => 'ko', code => 500, msg => $@ }; $self->logger->debug( - "Looking for 2F Device(s) attached to session $sessionId..."); + "Looking for 2F Device(s) attached to sessionId $_"); if ( $session->data->{_2fDevices} ) { @@ -238,58 +236,81 @@ sub _delete2F { from_json( $session->data->{_2fDevices}, { allow_nonref => 1 } ); $total = scalar @$devices; $self->logger->debug( - "Found $total 2F devices attached to session $sessionId."); + "Found $total 2F devices attached to sessionId $_"); @keep = (); while (@$devices) { my $element = shift @$devices; - push @keep, - $element - if ( - ( defined $type or defined $id ) - and ( ( defined $type and $type ne $element->{type} ) + if (( defined $type or defined $id ) and ( + ( defined $type and $type ne $element->{type}) or - ( defined $id and $id ne $self->_genId2F($element) ) ) - ); + ( defined $id and $id ne $self->_genId2F($element) ) + ) + ) { + push @keep, $element; + } else { + $removed->{$self->_genId2F($element)} = "removed"; + } } - $lremoved = $total - scalar @keep; - if ( $lremoved > 0 ) { + if ( ($total - scalar @keep) > 0 ) { # Update session $self->logger->debug( -"Removing $lremoved 2F device(s) attached to session $sessionId ..." +"Removing " . ($total - scalar @keep) . " 2F device(s) attached to sessionId $_ ..." ); $session->data->{_2fDevices} = to_json( \@keep ); $session->update( \%{ $session->data } ); - # Delete local cache - if ( $localStorage and $localStorage->get($sessionId) ) { - $self->logger->debug( - "Delete local cache for $sessionId ..."); - $localStorage->remove($sessionId); - } - else { - $self->logger->debug( - "Local cache will not be cleared for $sessionId"); + # Delete from local cache + if ($session->{options}->{localStorage}) { + $module = $session->{options}->{localStorage}; + eval "use $module;"; + $localStorage = $module->new( $session->{options}->{localStorageOptions} ); + if ($localStorage->get($_) ) { + $self->logger->debug("Delete local cache for session $_"); + $localStorage->remove($_); + } } } else { $self->logger->debug( -"No matching 2F devices attached to session $sessionId were selected for removal." +"No matching 2F devices attached to sessionId $_ were selected for removal." ); } } else { $self->logger->debug( - "No 2F devices attached to session $sessionId were found."); + "No 2F devices attached to sessionId $_ were found."); } - $removed += $lremoved; } + + return { res => 'ok', removed => $removed }; +} + +sub _delete2F { + my ( $self, $uid, $type, $id ) = @_; + my ( $res, $removed, $count); + if ( defined $type ) { + $res = $self->_checkType($type); + return $res if ( $res->{res} ne 'ok' ); + } + + $res = $self->_delete2FFromSessions($uid, $type, $id, $self->_getPersistentMod, 'Persistent', '_session_uid'); + return $res if ( $res->{res} ne 'ok' ); + + $removed = $res->{removed}; + + $res = $self->_delete2FFromSessions($uid, $type, $id, $self->_getSSOMod, 'SSO', 'uid'); + return $res if ( $res->{res} ne 'ok' ); + + $removed = ($removed, $res->{removed}); + $count = scalar (keys %{$removed}); + return { res => 'ok', - removed => $removed, - msg => $removed > 0 - ? "Successful operation: " . $removed . " 2F were removed" + removed => $count, + msg => $count > 0 + ? "Successful operation: " . $count . " 2F were removed" : "No operation performed" }; } diff --git a/lemonldap-ng-manager/t/04-2F-api.t b/lemonldap-ng-manager/t/04-2F-api.t index 7197a1f832..b899a276d4 100644 --- a/lemonldap-ng-manager/t/04-2F-api.t +++ b/lemonldap-ng-manager/t/04-2F-api.t @@ -31,17 +31,19 @@ sub newSession { 'Sessions module' ); count(1); - $tmp->update( { - ipAddr => $ip, - _whatToTrace => $uid, - uid => $uid, - _session_uid => $uid, - _utime => time, - _session_kind => $kind, - _2fDevices => to_json($sfaDevices), - } + ok( + $tmp->update( { + ipAddr => $ip, + _whatToTrace => $uid, + uid => $uid, + _session_uid => $uid, + _utime => time, + _session_kind => $kind, + _2fDevices => to_json($sfaDevices), + } + ), "New $kind session for $uid" ); - return $tmp->{id}; + #return $tmp->{id}; } sub check200 { @@ -211,16 +213,16 @@ sub checkDeleteBadType { check405( $test, $res ); } -my @ids; +#my @ids; my $sfaDevices = []; my $ret; ## Sessions creation -# SSO session -$ids[0] = newSession( 'dwho', '127.10.0.1', 'SSO', $sfaDevices ); +# msmith +newSession( 'msmith', '127.10.0.1', 'SSO', $sfaDevices ); +newSession( 'msmith', '127.10.0.1', 'Persistent', $sfaDevices ); -# Peristent sesssions -$ids[1] = newSession( 'msmith', '127.10.0.1', 'Persistent', $sfaDevices ); +# dwho $sfaDevices = [ { "name" => "MyU2FKey", "type" => "U2F", @@ -229,19 +231,22 @@ $sfaDevices = [ { "epoch" => time }, { - "name" => "MyYubikey", - "type" => "UBK", + "name" => "MyTOTP", + "type" => "TOTP", "_secret" => "123456", "epoch" => time }, { - "name" => "MyYubikey2", + "name" => "MyYubikey", "type" => "UBK", - "_secret" => "654321", + "_secret" => "123456", "epoch" => time } ]; -$ids[2] = newSession( 'rtyler', '127.10.0.1', 'Persistent', $sfaDevices ); +newSession( 'dwho', '127.10.0.1', 'SSO', $sfaDevices ); +newSession( 'dwho', '127.10.0.1', 'Persistent', $sfaDevices ); + +# rtyler $sfaDevices = [ { "name" => "MyU2FKey", "type" => "U2F", @@ -250,19 +255,22 @@ $sfaDevices = [ { "epoch" => time }, { - "name" => "MyTOTP", - "type" => "TOTP", + "name" => "MyYubikey", + "type" => "UBK", "_secret" => "123456", "epoch" => time }, { - "name" => "MyYubikey", + "name" => "MyYubikey2", "type" => "UBK", - "_secret" => "123456", + "_secret" => "654321", "epoch" => time } ]; -$ids[3] = newSession( 'dwho', '127.10.0.1', 'Persistent', $sfaDevices ); +newSession( 'rtyler', '127.10.0.1', 'SSO', $sfaDevices ); +newSession( 'rtyler', '127.10.0.1', 'Persistent', $sfaDevices ); + +# davros $sfaDevices = [ { "name" => "MyU2FKey", "type" => "U2F", @@ -277,7 +285,10 @@ $sfaDevices = [ { "epoch" => time } ]; -$ids[4] = newSession( 'davros', '127.10.0.1', 'Persistent', $sfaDevices ); +newSession( 'davros', '127.10.0.1', 'SSO', $sfaDevices ); +newSession( 'davros', '127.10.0.1', 'Persistent', $sfaDevices ); + +# tof $sfaDevices = [ { "name" => "MyU2FKey", "type" => "U2F", @@ -286,7 +297,8 @@ $sfaDevices = [ { "epoch" => time } ]; -$ids[5] = newSession( 'tof', '127.10.0.1', 'Persistent', $sfaDevices ); +newSession( 'tof', '127.10.0.1', 'SSO', $sfaDevices ); +newSession( 'tof', '127.10.0.1', 'Persistent', $sfaDevices ); # dwho checkGetList( 1, 'dwho', 'U2F' ); -- GitLab From be414ee4fa96262f534c1ae8f2094a65f35b8d32 Mon Sep 17 00:00:00 2001 From: Soisik Froger Date: Thu, 2 Jan 2020 15:55:44 +0000 Subject: [PATCH 29/31] Manager API - perltidy - #2033 --- .../lib/Lemonldap/NG/Manager/Api/2F.pm | 81 +++++++++++-------- lemonldap-ng-manager/t/04-2F-api.t | 3 +- 2 files changed, 48 insertions(+), 36 deletions(-) diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/2F.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/2F.pm index 5e10d55130..335c4e3fac 100644 --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/2F.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/2F.pm @@ -143,7 +143,8 @@ sub _get2F { return $res if ( $res->{res} ne 'ok' ); } - $psessions = $self->_getSessions2F($self->_getPersistentMod, 'Persistent', '_session_uid', $uid); + $psessions = $self->_getSessions2F( $self->_getPersistentMod, 'Persistent', + '_session_uid', $uid ); foreach ( keys %{$psessions} ) { my $devices = @@ -190,15 +191,17 @@ sub _getSSOMod { sub _getSessions2F { my ( $self, $mod, $kind, $key, $uid ) = @_; $self->logger->debug("Looking for sessions for uid $uid ..."); - my $sessions = Lemonldap::NG::Common::Apache::Session->searchOn( - $mod->{options}, $key, $uid, + my $sessions = + Lemonldap::NG::Common::Apache::Session->searchOn( $mod->{options}, $key, + $uid, ( '_session_kind', '_session_uid', '_session_id', '_2fDevices' ) ); foreach ( keys %{$sessions} ) { delete $sessions->{$_} unless ( $sessions->{$_}->{_session_kind} eq $kind ); } - $self->logger->debug( - "Found " . scalar( keys %{$sessions} ) . " $kind sessions for uid $uid." ); + $self->logger->debug( "Found " + . scalar( keys %{$sessions} ) + . " $kind sessions for uid $uid." ); return $sessions; } @@ -210,63 +213,68 @@ sub _getSession2F { $self->logger->debug( defined $session ? "Session $sessionId found." - : " No session found for sessionId $sessionId" ); + : " No session found for sessionId $sessionId" + ); return $session; } sub _delete2FFromSessions { my ( $self, $uid, $type, $id, $mod, $kind, $key ) = @_; - my ( - $sessions, $session, $devices, @keep, $removed, - $total, $module, $localStorage - ); - $sessions = $self->_getSessions2F($mod, $kind, $key, $uid); + my ( $sessions, $session, $devices, @keep, $removed, + $total, $module, $localStorage ); + $sessions = $self->_getSessions2F( $mod, $kind, $key, $uid ); foreach ( keys %{$sessions} ) { - $session = $self->_getSession2F($_, $mod) + $session = $self->_getSession2F( $_, $mod ) or return { res => 'ko', code => 500, msg => $@ }; $self->logger->debug( "Looking for 2F Device(s) attached to sessionId $_"); if ( $session->data->{_2fDevices} ) { - $devices = from_json( $session->data->{_2fDevices}, { allow_nonref => 1 } ); $total = scalar @$devices; + $self->logger->debug( "Found $total 2F devices attached to sessionId $_"); + @keep = (); while (@$devices) { my $element = shift @$devices; - if (( defined $type or defined $id ) and ( - ( defined $type and $type ne $element->{type}) + if ( + ( defined $type or defined $id ) + and ( ( defined $type and $type ne $element->{type} ) or - ( defined $id and $id ne $self->_genId2F($element) ) - ) - ) { + ( defined $id and $id ne $self->_genId2F($element) ) ) + ) + { push @keep, $element; - } else { - $removed->{$self->_genId2F($element)} = "removed"; + } + else { + $removed->{ $self->_genId2F($element) } = "removed"; } } - if ( ($total - scalar @keep) > 0 ) { + if ( ( $total - scalar @keep ) > 0 ) { # Update session - $self->logger->debug( -"Removing " . ($total - scalar @keep) . " 2F device(s) attached to sessionId $_ ..." - ); + $self->logger->debug( "Removing " + . ( $total - scalar @keep ) + . " 2F device(s) attached to sessionId $_ ..." ); $session->data->{_2fDevices} = to_json( \@keep ); $session->update( \%{ $session->data } ); # Delete from local cache - if ($session->{options}->{localStorage}) { + if ( $session->{options}->{localStorage} ) { $module = $session->{options}->{localStorage}; eval "use $module;"; - $localStorage = $module->new( $session->{options}->{localStorageOptions} ); - if ($localStorage->get($_) ) { - $self->logger->debug("Delete local cache for session $_"); + $localStorage = + $module->new( + $session->{options}->{localStorageOptions} ); + if ( $localStorage->get($_) ) { + $self->logger->debug( + "Delete local cache for session $_"); $localStorage->remove($_); } } @@ -284,27 +292,32 @@ sub _delete2FFromSessions { } - return { res => 'ok', removed => $removed }; + return { res => 'ok', removed => $removed }; } sub _delete2F { my ( $self, $uid, $type, $id ) = @_; - my ( $res, $removed, $count); + my ( $res, $removed, $count ); if ( defined $type ) { $res = $self->_checkType($type); return $res if ( $res->{res} ne 'ok' ); } - $res = $self->_delete2FFromSessions($uid, $type, $id, $self->_getPersistentMod, 'Persistent', '_session_uid'); + $res = + $self->_delete2FFromSessions( $uid, $type, $id, $self->_getPersistentMod, + 'Persistent', '_session_uid' ); return $res if ( $res->{res} ne 'ok' ); $removed = $res->{removed}; - $res = $self->_delete2FFromSessions($uid, $type, $id, $self->_getSSOMod, 'SSO', 'uid'); + $res = + $self->_delete2FFromSessions( $uid, $type, $id, $self->_getSSOMod, 'SSO', + 'uid' ); return $res if ( $res->{res} ne 'ok' ); - $removed = ($removed, $res->{removed}); - $count = scalar (keys %{$removed}); + # merge results + $removed = ( $removed, $res->{removed} ); + $count = scalar( keys %{$removed} ); return { res => 'ok', diff --git a/lemonldap-ng-manager/t/04-2F-api.t b/lemonldap-ng-manager/t/04-2F-api.t index b899a276d4..a21c2bd851 100644 --- a/lemonldap-ng-manager/t/04-2F-api.t +++ b/lemonldap-ng-manager/t/04-2F-api.t @@ -43,7 +43,7 @@ sub newSession { } ), "New $kind session for $uid" ); - #return $tmp->{id}; + count(1); } sub check200 { @@ -213,7 +213,6 @@ sub checkDeleteBadType { check405( $test, $res ); } -#my @ids; my $sfaDevices = []; my $ret; -- GitLab From 715987c292c50d6ee44110f59f3e6b337b76750d Mon Sep 17 00:00:00 2001 From: Soisik Froger Date: Wed, 8 Jan 2020 09:21:18 +0000 Subject: [PATCH 30/31] Manager API - Set noCache=1 to getConf before calling saveConf (related to #2058) --- .../lib/Lemonldap/NG/Manager/Api/Providers/OidcRp.pm | 11 ++++------- .../lib/Lemonldap/NG/Manager/Api/Providers/SamlSp.pm | 11 ++++------- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers/OidcRp.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers/OidcRp.pm index e900b8cf1e..11b7b85e4e 100644 --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers/OidcRp.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers/OidcRp.pm @@ -22,9 +22,6 @@ sub getOidcRpByConfKey { my $oidcRp = $self->_getOidcRpByConfKey( $conf, $confKey ); - # $self->logger->debug("$oidcRp :: "); - # use Data::Dumper; print STDERR Dumper($oidcRp); - # Return 404 if not found unless ( defined $oidcRp ) { return $self->sendError( $req, @@ -120,7 +117,7 @@ sub addOidcRp { ); # Get latest configuration - my $conf = $self->_confAcc->getConf; + my $conf = $self->_confAcc->getConf({ noCache => 1 }); if ( defined $self->_getOidcRpByConfKey( $conf, $add->{confKey} ) ) { return $self->sendError( @@ -168,7 +165,7 @@ sub updateOidcRp { "[API] OIDC RP $confKey configuration update requested"); # Get latest configuration - my $conf = $self->_confAcc->getConf; + my $conf = $self->_confAcc->getConf({ noCache => 1 }); my $current = $self->_getOidcRpByConfKey( $conf, $confKey ); @@ -215,7 +212,7 @@ sub replaceOidcRp { "[API] OIDC RP $confKey configuration replace requested"); # Get latest configuration - my $conf = $self->_confAcc->getConf; + my $conf = $self->_confAcc->getConf({ noCache => 1 }); # Return 404 if not found unless ( defined $self->_getOidcRpByConfKey( $conf, $confKey ) ) { @@ -245,7 +242,7 @@ sub deleteOidcRp { or return $self->sendError( $req, 'confKey is missing', 400 ); # Get latest configuration - my $conf = $self->_confAcc->getConf; + my $conf = $self->_confAcc->getConf({ noCache => 1 }); my $delete = $self->_getOidcRpByConfKey( $conf, $confKey ); diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers/SamlSp.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers/SamlSp.pm index d7dd41a5e8..fdacfec77f 100644 --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers/SamlSp.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Api/Providers/SamlSp.pm @@ -22,9 +22,6 @@ sub getSamlSpByConfKey { my $samlSp = $self->_getSamlSpByConfKey( $conf, $confKey ); - # $self->logger->debug("$oidcRp :: "); - # use Data::Dumper; print STDERR Dumper($oidcRp); - # Check if confKey is defined unless ( defined $samlSp ) { return $self->sendError( $req, @@ -126,7 +123,7 @@ sub addSamlSp { ); # Get latest configuration - my $conf = $self->_confAcc->getConf; + my $conf = $self->_confAcc->getConf({ noCache => 1 }); if ( defined $self->_getSamlSpByConfKey( $conf, $add->{confKey} ) ) { return $self->sendError( @@ -172,7 +169,7 @@ sub replaceSamlSp { "[API] SAML SP $confKey configuration replace requested"); # Get latest configuration - my $conf = $self->_confAcc->getConf; + my $conf = $self->_confAcc->getConf({ noCache => 1 }); # Return 404 if not found unless ( defined $self->_getSamlSpByConfKey( $conf, $confKey ) ) { @@ -211,7 +208,7 @@ sub updateSamlSp { "[API] SAML SP $confKey configuration update requested"); # Get latest configuration - my $conf = $self->_confAcc->getConf; + my $conf = $self->_confAcc->getConf({ noCache => 1 }); my $current = $self->_getSamlSpByConfKey( $conf, $confKey ); @@ -248,7 +245,7 @@ sub deleteSamlSp { or return $self->sendError( $req, 'confKey is missing', 400 ); # Get latest configuration - my $conf = $self->_confAcc->getConf; + my $conf = $self->_confAcc->getConf({ noCache => 1 }); my $delete = $self->_getSamlSpByConfKey( $conf, $confKey ); -- GitLab From f26c79da1ae693fd78b8fb8b64c0f4188fff41f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20OUDOT?= Date: Fri, 10 Jan 2020 15:29:33 +0100 Subject: [PATCH 31/31] Protect by default access to API manager vhost (#2033, #2034) --- _example/etc/manager-apache2.4.conf | 2 +- _example/etc/manager-apache2.X.conf | 4 ++-- _example/etc/manager-apache2.conf | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/_example/etc/manager-apache2.4.conf b/_example/etc/manager-apache2.4.conf index bccef04fe6..20608ea08c 100644 --- a/_example/etc/manager-apache2.4.conf +++ b/_example/etc/manager-apache2.4.conf @@ -154,7 +154,7 @@ DocumentRoot __MANAGERSITEDIR__ - Require all granted + Require all denied AddOutputFilterByType DEFLATE text/html text/plain text/xml text/javascript text/css diff --git a/_example/etc/manager-apache2.X.conf b/_example/etc/manager-apache2.X.conf index e5cd662297..a87605fc50 100644 --- a/_example/etc/manager-apache2.X.conf +++ b/_example/etc/manager-apache2.X.conf @@ -174,11 +174,11 @@ = 2.3> - Require all granted + Require all denied Order Deny,Allow - Allow from all + Deny from all Options +FollowSymLinks diff --git a/_example/etc/manager-apache2.conf b/_example/etc/manager-apache2.conf index 00cb4aa812..fe8003430e 100644 --- a/_example/etc/manager-apache2.conf +++ b/_example/etc/manager-apache2.conf @@ -157,8 +157,8 @@ DocumentRoot __MANAGERSITEDIR__ - Order Deny,Allow - Allow from all + Order Deny,Allow + Deny from all AddOutputFilterByType DEFLATE text/html text/plain text/xml text/javascript text/css -- GitLab