From 2e0f1b70888688cdcf56fa9b8e34e3217bdb3bea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Oudot?= Date: Mon, 30 Mar 2015 15:57:23 +0000 Subject: [PATCH] Start of registration endpoint implementation (#184) --- .../Lemonldap/NG/Common/Conf/Attributes.pm | 7 ++ .../lib/Lemonldap/NG/Manager/_Struct.pm | 37 ++++---- .../lib/Lemonldap/NG/Manager/_i18n.pm | 72 ++++++++-------- .../example/openid-configuration.pl | 5 +- .../NG/Portal/IssuerDBOpenIDConnect.pm | 84 +++++++++++++++++++ 5 files changed, 150 insertions(+), 55 deletions(-) diff --git a/lemonldap-ng-common/lib/Lemonldap/NG/Common/Conf/Attributes.pm b/lemonldap-ng-common/lib/Lemonldap/NG/Common/Conf/Attributes.pm index efe070151..88da7b4b7 100644 --- a/lemonldap-ng-common/lib/Lemonldap/NG/Common/Conf/Attributes.pm +++ b/lemonldap-ng-common/lib/Lemonldap/NG/Common/Conf/Attributes.pm @@ -774,6 +774,13 @@ has 'oidcServiceMetaDataJWKSURI' => ( documentation => 'OpenID Connect JWKS endpoint', ); +has 'oidcServiceMetaDataRegistrationURI' => ( + is => 'rw', + isa => 'Str', + default => 'register', + documentation => 'OpenID Connect registration endpoint', +); + has 'oidcServiceMetaDataTokenURI' => ( is => 'rw', isa => 'Str', diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/_Struct.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/_Struct.pm index 6542e5436..02f97cfff 100644 --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/_Struct.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/_Struct.pm @@ -316,9 +316,8 @@ sub cstruct { %$h, oidcRPMetaDataNode => { $k2 => { - _nodes => [ - qw(oidcRPMetaDataExportedVars oidcRPMetaDataOptions) - ], + _nodes => + [ qw(oidcRPMetaDataExportedVars oidcRPMetaDataOptions) ], oidcRPMetaDataExportedVars => { _nodes => ["hash:/oidcRPMetaDataExportedVars/$k2:vars:btext"], @@ -328,24 +327,24 @@ sub cstruct { _nodes => [ qw(oidcRPMetaDataOptionsAuthentication oidcRPMetaDataOptionsDisplay oidcRPMetaDataOptionsUserIDAttr oidcRPMetaDataOptionsIDTokenSignAlg oidcRPMetaDataOptionsIDTokenExpiration oidcRPMetaDataOptionsAccessTokenExpiration oidcRPMetaDataOptionsRedirectUris) ], -oidcRPMetaDataOptionsAuthentication => { - _nodes => [ - qw(oidcRPMetaDataOptionsClientID oidcRPMetaDataOptionsClientSecret) - ], - oidcRPMetaDataOptionsClientID => + oidcRPMetaDataOptionsAuthentication => { + _nodes => [ + qw(oidcRPMetaDataOptionsClientID oidcRPMetaDataOptionsClientSecret) + ], + oidcRPMetaDataOptionsClientID => "text:/oidcRPMetaDataOptions/$k2/oidcRPMetaDataOptionsClientID", - oidcRPMetaDataOptionsClientSecret => + oidcRPMetaDataOptionsClientSecret => "password:/oidcRPMetaDataOptions/$k2/oidcRPMetaDataOptionsClientSecret", - }, - oidcRPMetaDataOptionsDisplay => { - _nodes => [ - qw(oidcRPMetaDataOptionsDisplayName oidcRPMetaDataOptionsIcon) - ], - oidcRPMetaDataOptionsDisplayName => + }, + oidcRPMetaDataOptionsDisplay => { + _nodes => [ + qw(oidcRPMetaDataOptionsDisplayName oidcRPMetaDataOptionsIcon) + ], + oidcRPMetaDataOptionsDisplayName => "text:/oidcRPMetaDataOptions/$k2/oidcRPMetaDataOptionsDisplayName", - oidcRPMetaDataOptionsIcon => + oidcRPMetaDataOptionsIcon => "text:/oidcRPMetaDataOptions/$k2/oidcRPMetaDataOptionsIcon", - }, + }, oidcRPMetaDataOptionsUserIDAttr => "text:/oidcRPMetaDataOptions/$k2/oidcRPMetaDataOptionsUserIDAttr", oidcRPMetaDataOptionsIDTokenSignAlg => @@ -1601,7 +1600,7 @@ sub struct { oidcServiceMetaDataEndPoints => { _nodes => [ - qw(oidcServiceMetaDataAuthorizeURI oidcServiceMetaDataTokenURI oidcServiceMetaDataUserInfoURI oidcServiceMetaDataJWKSURI) + qw(oidcServiceMetaDataAuthorizeURI oidcServiceMetaDataTokenURI oidcServiceMetaDataUserInfoURI oidcServiceMetaDataJWKSURI oidcServiceMetaDataRegistrationURI) ], oidcServiceMetaDataAuthorizeURI => @@ -1612,6 +1611,8 @@ sub struct { 'text:/oidcServiceMetaDataUserInfoURI', oidcServiceMetaDataJWKSURI => 'text:/oidcServiceMetaDataJWKSURI', + oidcServiceMetaDataRegistrationURI => + 'text:/oidcServiceMetaDataRegistrationURI', }, oidcServiceMetaDataSecurity => { diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/_i18n.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/_i18n.pm index 4bc564444..9dfe89506 100644 --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/_i18n.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/_i18n.pm @@ -306,6 +306,7 @@ sub en { oidcServiceMetaDataEndPoints => 'End points', oidcServiceMetaDataIssuer => 'Issuer identifier', oidcServiceMetaDataJWKSURI => 'JWKS', + oidcServiceMetaDataRegistrationURI => 'Registration', oidcServiceMetaDataSecurity => 'Security', oidcServiceMetaDataTokenURI => 'Token', oidcServiceMetaDataUserInfoURI => 'User Info', @@ -864,41 +865,42 @@ sub fr { "Expiration des jetons d'identité", oidcRPMetaDataOptionsIDTokenSignAlg => "Algorithme de signature des jetons d'identité", - oidcRPMetaDataOptionsRedirectUris => 'Adresses de redirection', - oidcRPMetaDataOptionsUserIDAttr => "Attribut de l'identifiant", - oidcRPStateTimeout => 'Durée d\'une session state', - oidcServiceMetaData => "Service OpenID Connect", - oidcServiceMetaDataAuthorizeURI => "Autorisation", - oidcServiceMetaDataEndPoints => "Points d'accès", - oidcServiceMetaDataIssuer => "Identifiant du fournisseur", - oidcServiceMetaDataJWKSURI => 'JWKS', - oidcServiceMetaDataSecurity => 'Sécurité', - oidcServiceMetaDataTokenURI => "Jeton", - oidcServiceMetaDataUserInfoURI => 'Informations Utilisateur', - oidcServicePrivateKeySig => 'Clé privée de signature', - oidcServicePublicKeySig => 'Clé publique de signature', - openIdAttr => 'Identifiant OpenID', - openIdAuthnLevel => 'Niveau d\'authentification', - openIdExportedVars => 'Variables exportées', - openIdIDPList => 'Domaines autorisés', - openIdIssuerSecret => 'Jeton secret', - openIdParams => 'Paramètres OpenID', - openIdSecret => 'Jeton secret', - openIdSreg => 'Associations SREG', - openIdSreg_fullname => 'Nom complet', - openIdSreg_nickname => 'Surnom', - openIdSreg_language => 'Langage', - openIdSreg_postcode => 'Code postal', - openIdSreg_timezone => 'Zone horaire', - openIdSreg_country => 'Pays', - openIdSreg_gender => 'Genre', - openIdSreg_email => 'Email', - openIdSreg_dob => 'Date de naissance', - openIdSPList => 'Domaines autorisés', - passwordDB => 'Module de mot de passe', - passwordManagement => 'Gestion des mots de passe', - persistentSessions => 'Sessions persistantes', - persistentStorage => 'Module Apache::Session', + oidcRPMetaDataOptionsRedirectUris => 'Adresses de redirection', + oidcRPMetaDataOptionsUserIDAttr => "Attribut de l'identifiant", + oidcRPStateTimeout => 'Durée d\'une session state', + oidcServiceMetaData => "Service OpenID Connect", + oidcServiceMetaDataAuthorizeURI => "Autorisation", + oidcServiceMetaDataEndPoints => "Points d'accès", + oidcServiceMetaDataIssuer => "Identifiant du fournisseur", + oidcServiceMetaDataJWKSURI => 'JWKS', + oidcServiceMetaDataRegistrationURI => 'Enregistrement', + oidcServiceMetaDataSecurity => 'Sécurité', + oidcServiceMetaDataTokenURI => "Jeton", + oidcServiceMetaDataUserInfoURI => 'Informations Utilisateur', + oidcServicePrivateKeySig => 'Clé privée de signature', + oidcServicePublicKeySig => 'Clé publique de signature', + openIdAttr => 'Identifiant OpenID', + openIdAuthnLevel => 'Niveau d\'authentification', + openIdExportedVars => 'Variables exportées', + openIdIDPList => 'Domaines autorisés', + openIdIssuerSecret => 'Jeton secret', + openIdParams => 'Paramètres OpenID', + openIdSecret => 'Jeton secret', + openIdSreg => 'Associations SREG', + openIdSreg_fullname => 'Nom complet', + openIdSreg_nickname => 'Surnom', + openIdSreg_language => 'Langage', + openIdSreg_postcode => 'Code postal', + openIdSreg_timezone => 'Zone horaire', + openIdSreg_country => 'Pays', + openIdSreg_gender => 'Genre', + openIdSreg_email => 'Email', + openIdSreg_dob => 'Date de naissance', + openIdSPList => 'Domaines autorisés', + passwordDB => 'Module de mot de passe', + passwordManagement => 'Gestion des mots de passe', + persistentSessions => 'Sessions persistantes', + persistentStorage => 'Module Apache::Session', persistentStorageOptions => 'Paramètres du module Apache::Session', port => 'Port', portal => 'URL', diff --git a/lemonldap-ng-portal/example/openid-configuration.pl b/lemonldap-ng-portal/example/openid-configuration.pl index 55b9f9c21..14462d6e7 100755 --- a/lemonldap-ng-portal/example/openid-configuration.pl +++ b/lemonldap-ng-portal/example/openid-configuration.pl @@ -11,6 +11,7 @@ my $authorize_uri = $portal->{oidcServiceMetaDataAuthorizeURI}; my $token_uri = $portal->{oidcServiceMetaDataTokenURI}; my $userinfo_uri = $portal->{oidcServiceMetaDataUserInfoURI}; my $jwks_uri = $portal->{oidcServiceMetaDataJWKSURI}; +my $registration_uri = $portal->{oidcServiceMetaDataRegistrationURI}; my ($path) = ( $issuerDBOpenIDConnectPath =~ /(\w+)/ ); my $issuer = $portal->{oidcServiceMetaDataIssuer}; @@ -23,8 +24,8 @@ $configuration->{authorization_endpoint} = $configuration->{token_endpoint} = $issuer . $path . "/" . $token_uri; $configuration->{userinfo_endpoint} = $issuer . $path . "/" . $userinfo_uri; $configuration->{jwks_uri} = $issuer . $path . "/" . $jwks_uri; - -# RECOMMENDED # $configuration->{registration_endpoint} +$configuration->{registration_endpoint} = + $issuer . $path . "/" . $registration_uri; $configuration->{scopes_supported} = [qw/openid profile email address phone/]; $configuration->{response_types_supported} = [ "code", diff --git a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/IssuerDBOpenIDConnect.pm b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/IssuerDBOpenIDConnect.pm index 4ad3ee5fd..8ba9db1fc 100644 --- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/IssuerDBOpenIDConnect.pm +++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/IssuerDBOpenIDConnect.pm @@ -7,6 +7,7 @@ package Lemonldap::NG::Portal::IssuerDBOpenIDConnect; use strict; use Lemonldap::NG::Portal::Simple; +use String::Random qw(random_string); use base qw(Lemonldap::NG::Portal::_OpenIDConnect); our $VERSION = '2.00'; @@ -34,6 +35,7 @@ sub issuerForUnAuthUser { my $token_uri = $self->{oidcServiceMetaDataTokenURI}; my $userinfo_uri = $self->{oidcServiceMetaDataUserInfoURI}; my $jwks_uri = $self->{oidcServiceMetaDataJWKSURI}; + my $registration_uri = $self->{oidcServiceMetaDataRegistrationURI}; my $issuer = $self->{oidcServiceMetaDataIssuer}; # Called URL @@ -356,6 +358,71 @@ sub issuerForUnAuthUser { } + # REGISTRATION + if ( $url_path =~ m#${issuerDBOpenIDConnectPath}${registration_uri}# ) { + + $self->lmLog( "URL $url detected as an OpenID Connect REGISTRATION URL", + 'debug' ); + + # TODO: check Initial Access Token + + # Specific message to allow DOS detection + my $source_ip = $self->ipAddr; + $self->lmLog( "OpenID Connect Registration request from $source_ip", + 'warn' ); + + # Get client metadata + my $client_metadata_json = $self->param('POSTDATA'); + + $self->lmLog( "Client metadata received: $client_metadata_json", + 'debug' ); + + my $client_metadata = $self->decodeJSON($client_metadata_json); + my $registration_response = {}; + + # Check redirect_uris + unless ( $client_metadata->{redirect_uris} ) { + $self->lmLog( "Field redirect_uris is mandatory", 'error' ); + $self->returnJSONError( 'invalid_client_metadata', + 'Field redirect_uris is mandatory' ); + $self->quit; + } + + # RP identifier + my $registration_time = time; + my $rp = "register-$registration_time"; + + # Generate Client ID and Client Password + my $client_id = random_string("..............."); + my $client_secret = random_string("..............."); + + # Register known parameters + my $client_name = + $client_metadata->{client_name} || "Self registered client"; + my $logo_uri = $client_metadata->{logo_uri}; + my $id_token_signed_response_alg = + $client_metadata->{id_token_signed_response_alg} || "RS256"; + + # TODO: register RP in global configuration + + # Send registration response + $registration_response->{'client_id'} = $client_id; + $registration_response->{'client_secret'} = $client_secret; + $registration_response->{'client_id_issued_at'} = $registration_time; + $registration_response->{'client_id_expires_at'} = 0; + $registration_response->{'client_name'} = $client_name; + $registration_response->{'logo_uri'} = $logo_uri; + $registration_response->{'id_token_signed_response_alg'} = + $id_token_signed_response_alg; + + # TODO: return 201 HTTP code + $self->returnJSON($registration_response); + + $self->lmLog( "Registration response sent", 'debug' ); + + $self->quit; + } + PE_OK; } @@ -371,6 +438,7 @@ sub issuerForAuthUser { my $token_uri = $self->{issuerDBOpenIDConnectTokenURI}; my $userinfo_uri = $self->{issuerDBOpenIDConnectUserInfoURI}; my $jwks_uri = $self->{oidcServiceMetaDataJWKSURI}; + my $registration_uri = $self->{oidcServiceMetaDataRegistrationURI}; my $issuer = $self->{oidcServiceMetaDataIssuer}; # Session ID @@ -989,6 +1057,22 @@ sub issuerForAuthUser { $self->quit; } + # REGISTRATION + if ( $url_path =~ m#${issuerDBOpenIDConnectPath}${registration_uri}# ) { + + $self->lmLog( "URL $url detected as an OpenID Connect REGISTRATION URL", + 'debug' ); + + # This should not happen + $self->lmLog( + "Registration request found on an active SSO session, ignoring it", + 'error' + ); + $self->returnJSONError("invalid_request"); + + $self->quit; + } + PE_OK; }