Merge branch 'v2.0'

This commit is contained in:
Christophe Maudoux 2019-09-01 22:10:41 +02:00
commit 174193e74c
32 changed files with 1845 additions and 1381 deletions

105
lemonldap-ng-common/.prove Normal file
View File

@ -0,0 +1,105 @@
---
generation: 2
last_run_time: 1567071551.30841
tests:
t/01-Common-Conf.t:
elapsed: 0.472490072250366
gen: 2
last_pass_time: 1567071550.71014
last_result: 0
last_run_time: 1567071550.71014
last_todo: 0
seq: 5
total_passes: 1
t/02-Common-Conf-File.t:
elapsed: 0.0793302059173584
gen: 2
last_pass_time: 1567071550.68052
last_result: 0
last_run_time: 1567071550.68052
last_todo: 0
seq: 4
total_passes: 1
t/03-Common-Conf-CDBI.t:
elapsed: 0.61043119430542
gen: 2
last_pass_time: 1567071550.95767
last_result: 0
last_run_time: 1567071550.95767
last_todo: 0
seq: 6
total_passes: 1
t/03-Common-Conf-RDBI.t:
elapsed: 0.66497802734375
gen: 2
last_pass_time: 1567071551.00435
last_result: 0
last_run_time: 1567071551.00435
last_todo: 0
seq: 7
total_passes: 1
t/05-Common-Conf-LDAP.t:
elapsed: 0.64878511428833
gen: 2
last_pass_time: 1567071551.07637
last_result: 0
last_run_time: 1567071551.07637
last_todo: 0
seq: 8
total_passes: 1
t/30-Common-Safelib.t:
elapsed: 0.0283739566802979
gen: 2
last_pass_time: 1567071550.40529
last_result: 0
last_run_time: 1567071550.40529
last_todo: 0
seq: 1
total_passes: 1
t/35-Common-Crypto.t:
elapsed: 0.190783977508545
gen: 2
last_pass_time: 1567071550.63236
last_result: 0
last_run_time: 1567071550.63236
last_todo: 0
seq: 3
total_passes: 1
t/36-Common-Regexp.t:
elapsed: 0.0631709098815918
gen: 2
last_pass_time: 1567071550.50944
last_result: 0
last_run_time: 1567071550.50944
last_todo: 0
seq: 2
total_passes: 1
t/40-Common-Session.t:
elapsed: 0.184284210205078
gen: 2
last_pass_time: 1567071551.11977
last_result: 0
last_run_time: 1567071551.11977
last_todo: 0
seq: 9
total_passes: 1
t/50-Combination-Parser.t:
elapsed: 0.108580827713013
gen: 2
last_pass_time: 1567071551.1593
last_result: 0
last_run_time: 1567071551.1593
last_todo: 0
seq: 10
total_passes: 1
t/99-pod.t:
elapsed: 0.128799915313721
gen: 2
last_pass_time: 1567071551.30716
last_result: 0
last_run_time: 1567071551.30716
last_todo: 0
seq: 11
total_passes: 1
version: 1
...

View File

@ -9,6 +9,8 @@ our $VERSION = '2.1.0';
sub compactConf { sub compactConf {
my ( $self, $conf ) = @_; my ( $self, $conf ) = @_;
return $conf if ( $conf->{'dontCompactConf'} );
# Remove unused auth parameters # Remove unused auth parameters
my %keep; my %keep;
foreach my $type (qw(authentication userDB passwordDB registerDB)) { foreach my $type (qw(authentication userDB passwordDB registerDB)) {

View File

@ -24,7 +24,7 @@ use constant MANAGERSECTION => "manager";
use constant SESSIONSEXPLORERSECTION => "sessionsExplorer"; use constant SESSIONSEXPLORERSECTION => "sessionsExplorer";
use constant APPLYSECTION => "apply"; use constant APPLYSECTION => "apply";
our $hashParameters = qr/^(?:(?:l(?:o(?:ca(?:lSessionStorageOption|tionRule)|goutService)|dapExportedVar|wp(?:Ssl)?Opt)|(?:(?:d(?:emo|bi)|facebook|webID)ExportedVa|exported(?:Heade|Va)|issuerDBGetParamete)r|re(?:moteGlobalStorageOption|st2f(?:Verify|Init)Arg|loadUrl)|g(?:r(?:antSessionRule|oup)|lobalStorageOption)|n(?:otificationStorageOption|ginxCustomHandler)|macro)s|o(?:idc(?:RPMetaData(?:(?:Option(?:sExtraClaim)?|ExportedVar)s|Node)|OPMetaData(?:(?:ExportedVar|Option)s|J(?:SON|WKS)|Node)|S(?:erviceMetaDataAuthnContext|torageOptions))|penIdExportedVars)|s(?:aml(?:S(?:PMetaData(?:(?:ExportedAttribute|Option)s|Node|XML)|torageOptions)|IDPMetaData(?:(?:ExportedAttribute|Option)s|Node|XML))|essionDataToRemember|laveExportedVars|fExtra)|c(?:as(?:S(?:rvMetaData(?:(?:ExportedVar|Option)s|Node)|torageOptions)|A(?:ppMetaData(?:(?:ExportedVar|Option)s|Node)|ttributes))|(?:ustomAddParam|ombModule)s)|p(?:ersistentStorageOptions|o(?:rtalSkinRules|st))|a(?:ut(?:hChoiceMod|oSigninR)ules|pplicationList)|v(?:hostOptions|irtualHost)|S(?:MTPTLSOpts|SLVarIf))$/; our $hashParameters = qr/^(?:(?:l(?:o(?:ca(?:lSessionStorageOption|tionRule)|goutService)|dapExportedVar|wp(?:Ssl)?Opt)|(?:(?:d(?:emo|bi)|facebook|webID)ExportedVa|exported(?:Heade|Va)|issuerDBGetParamete)r|re(?:moteGlobalStorageOption|st2f(?:Verify|Init)Arg|loadUrl)|g(?:r(?:antSessionRule|oup)|lobalStorageOption)|n(?:otificationStorageOption|ginxCustomHandler)|macro)s|o(?:idc(?:RPMetaData(?:(?:Option(?:sExtraClaim)?|ExportedVar)s|Node)|OPMetaData(?:(?:ExportedVar|Option)s|J(?:SON|WKS)|Node)|S(?:erviceMetaDataAuthnContext|torageOptions))|penIdExportedVars)|s(?:aml(?:S(?:PMetaData(?:(?:ExportedAttribute|Option)s|Node|XML)|torageOptions)|IDPMetaData(?:(?:ExportedAttribute|Option)s|Node|XML))|essionDataToRemember|laveExportedVars|fExtra)|c(?:as(?:S(?:rvMetaData(?:(?:ExportedVar|Option)s|Node)|torageOptions)|A(?:ppMetaData(?:(?:ExportedVar|Option)s|Node)|ttributes))|(?:ustomAddParam|ombModule)s)|p(?:ersistentStorageOptions|o(?:rtalSkinRules|st))|a(?:ut(?:hChoiceMod|oSigninR)ules|pplicationList)|v(?:hostOptions|irtualHost)|S(?:MTPTLSOpts|SLVarIf))$/;
our $boolKeys = qr/^(?:s(?:aml(?:IDP(?:MetaDataOptions(?:(?:Check(?:S[LS]OMessageSignatur|Audienc|Tim)|IsPassiv)e|A(?:llow(?:LoginFromIDP|ProxiedAuthn)|daptSessionUtime)|Force(?:Authn|UTF8)|StoreSAMLToken|RelayStateURL)|SSODescriptorWantAuthnRequestsSigned)|S(?:P(?:MetaDataOptions(?:(?:CheckS[LS]OMessageSignatur|OneTimeUs)e|EnableIDPInitiatedURL|ForceUTF8)|SSODescriptor(?:WantAssertion|AuthnRequest)sSigned)|erviceUseCertificateInResponse)|DiscoveryProtocol(?:Activation|IsPassive)|CommonDomainCookieActivation|UseQueryStringSpecific|MetadataForceUTF8)|ingle(?:Session(?:UserByIP)?|(?:UserBy)?IP)|oap(?:Session|Config)Server|t(?:ayConnecte|orePasswor)d|kipRenewConfirmation|fRemovedUseNotif|howLanguages|slByAjax)|o(?:idc(?:ServiceAllow(?:(?:AuthorizationCode|Implicit|Hybrid)Flow|DynamicRegistration)|RPMetaDataOptions(?:LogoutSessionRequired|BypassConsent|RequirePKCE|Public)|OPMetaDataOptions(?:(?:CheckJWTSignatur|UseNonc)e|StoreIDToken))|ldNotifFormat)|c(?:a(?:ptcha_(?:register|login|mail)_enabled|sSrvMetaDataOptions(?:Gateway|Renew))|heck(?:User(?:Display(?:PersistentInfo|EmptyValues))?|State|XSS)|o(?:ntextSwitchingStopWithLogout|rsEnabled)|da)|p(?:ortal(?:ErrorOn(?:ExpiredSession|MailNotFound)|DisplayRe(?:setPassword|gister)|(?:CheckLogin|Statu)s|OpenLinkInNewWindow|RequireOldPassword|ForceAuthn|AntiFrame)|roxyUseSoap)|l(?:dap(?:(?:Group(?:DecodeSearchedValu|Recursiv)|UsePasswordResetAttribut)e|(?:AllowResetExpired|Set)Password|ChangePasswordAsUser|PpolicyControl)|oginHistoryEnabled)|i(?:ssuerDB(?:OpenID(?:Connect)?|SAML|CAS|Get)Activation|mpersonationSkipEmptyValues)|no(?:tif(?:ication(?:Server(?:(?:POS|GE)T|DELETE)?)?|y(?:Deleted|Other))|AjaxHook)|to(?:tp2f(?:UserCan(?:Chang|Remov)eKey|DisplayExistingSecret)|kenUseGlobalStorage)|u(?:se(?:RedirectOn(?:Forbidden|Error)|SafeJail)|2fUserCanRemoveKey|pgradeSession)|(?:mai(?:lOnPasswordChang|ntenanc)|vhostMaintenanc)e|d(?:isablePersistentStorage|biDynamicHashEnabled)|rest(?:(?:Session|Config)Server|ExportSecretKeys)|h(?:ideOldPassword|ttpOnly)|yubikey2fUserCanRemoveKey|(?:activeTim|wsdlServ)er|krb(?:RemoveDomain|ByJs)|bruteForceProtection)$/; our $boolKeys = qr/^(?:s(?:aml(?:IDP(?:MetaDataOptions(?:(?:Check(?:S[LS]OMessageSignatur|Audienc|Tim)|IsPassiv)e|A(?:llow(?:LoginFromIDP|ProxiedAuthn)|daptSessionUtime)|Force(?:Authn|UTF8)|StoreSAMLToken|RelayStateURL)|SSODescriptorWantAuthnRequestsSigned)|S(?:P(?:MetaDataOptions(?:(?:CheckS[LS]OMessageSignatur|OneTimeUs)e|EnableIDPInitiatedURL|ForceUTF8)|SSODescriptor(?:WantAssertion|AuthnRequest)sSigned)|erviceUseCertificateInResponse)|DiscoveryProtocol(?:Activation|IsPassive)|CommonDomainCookieActivation|UseQueryStringSpecific|MetadataForceUTF8)|ingle(?:Session(?:UserByIP)?|(?:UserBy)?IP)|oap(?:Session|Config)Server|t(?:ayConnecte|orePasswor)d|kipRenewConfirmation|fRemovedUseNotif|howLanguages|slByAjax)|o(?:idc(?:ServiceAllow(?:(?:AuthorizationCode|Implicit|Hybrid)Flow|DynamicRegistration)|RPMetaDataOptions(?:LogoutSessionRequired|BypassConsent|RequirePKCE|Public)|OPMetaDataOptions(?:(?:CheckJWTSignatur|UseNonc)e|StoreIDToken))|ldNotifFormat)|c(?:a(?:ptcha_(?:register|login|mail)_enabled|sSrvMetaDataOptions(?:Gateway|Renew))|heck(?:User(?:Display(?:PersistentInfo|EmptyValues))?|State|XSS)|o(?:ntextSwitchingStopWithLogout|rsEnabled)|da)|p(?:ortal(?:ErrorOn(?:ExpiredSession|MailNotFound)|DisplayRe(?:setPassword|gister)|(?:CheckLogin|Statu)s|OpenLinkInNewWindow|RequireOldPassword|ForceAuthn|AntiFrame)|roxyUseSoap)|l(?:dap(?:(?:Group(?:DecodeSearchedValu|Recursiv)|UsePasswordResetAttribut)e|(?:AllowResetExpired|Set)Password|ChangePasswordAsUser|PpolicyControl)|oginHistoryEnabled)|i(?:ssuerDB(?:OpenID(?:Connect)?|SAML|CAS|Get)Activation|mpersonationSkipEmptyValues)|no(?:tif(?:ication(?:Server(?:(?:POS|GE)T|DELETE)?)?|y(?:Deleted|Other))|AjaxHook)|to(?:tp2f(?:UserCan(?:Chang|Remov)eKey|DisplayExistingSecret)|kenUseGlobalStorage)|u(?:se(?:RedirectOn(?:Forbidden|Error)|SafeJail)|2fUserCanRemoveKey|pgradeSession)|d(?:isablePersistentStorage|biDynamicHashEnabled|ontCompactConf)|(?:mai(?:lOnPasswordChang|ntenanc)|vhostMaintenanc)e|rest(?:(?:Session|Config)Server|ExportSecretKeys)|h(?:ideOldPassword|ttpOnly)|yubikey2fUserCanRemoveKey|(?:activeTim|wsdlServ)er|krb(?:RemoveDomain|ByJs)|bruteForceProtection)$/;
our @sessionTypes = ( 'remoteGlobal', 'global', 'localSession', 'persistent', 'saml', 'oidc', 'cas' ); our @sessionTypes = ( 'remoteGlobal', 'global', 'localSession', 'persistent', 'saml', 'oidc', 'cas' );

View File

@ -179,55 +179,56 @@ sub defaultValues {
'loa-4' => 4, 'loa-4' => 4,
'loa-5' => 5 'loa-5' => 5
}, },
'oidcServiceMetaDataAuthorizeURI' => 'authorize', 'oidcServiceMetaDataAuthorizeURI' => 'authorize',
'oidcServiceMetaDataBackChannelURI' => 'blogout', 'oidcServiceMetaDataBackChannelURI' => 'blogout',
'oidcServiceMetaDataCheckSessionURI' => 'checksession.html', 'oidcServiceMetaDataCheckSessionURI' => 'checksession.html',
'oidcServiceMetaDataEndSessionURI' => 'logout', 'oidcServiceMetaDataEndSessionURI' => 'logout',
'oidcServiceMetaDataFrontChannelURI' => 'flogout', 'oidcServiceMetaDataFrontChannelURI' => 'flogout',
'oidcServiceMetaDataJWKSURI' => 'jwks', 'oidcServiceMetaDataIntrospectionURI' => 'introspect',
'oidcServiceMetaDataRegistrationURI' => 'register', 'oidcServiceMetaDataJWKSURI' => 'jwks',
'oidcServiceMetaDataTokenURI' => 'token', 'oidcServiceMetaDataRegistrationURI' => 'register',
'oidcServiceMetaDataUserInfoURI' => 'userinfo', 'oidcServiceMetaDataTokenURI' => 'token',
'openIdAuthnLevel' => 1, 'oidcServiceMetaDataUserInfoURI' => 'userinfo',
'openIdExportedVars' => {}, 'openIdAuthnLevel' => 1,
'openIdIDPList' => '0;', 'openIdExportedVars' => {},
'openIdSPList' => '0;', 'openIdIDPList' => '0;',
'openIdSreg_email' => 'mail', 'openIdSPList' => '0;',
'openIdSreg_fullname' => 'cn', 'openIdSreg_email' => 'mail',
'openIdSreg_nickname' => 'uid', 'openIdSreg_fullname' => 'cn',
'openIdSreg_timezone' => '_timezone', 'openIdSreg_nickname' => 'uid',
'pamAuthnLevel' => 2, 'openIdSreg_timezone' => '_timezone',
'pamService' => 'login', 'pamAuthnLevel' => 2,
'passwordDB' => 'Demo', 'pamService' => 'login',
'passwordResetAllowedRetries' => 3, 'passwordDB' => 'Demo',
'port' => -1, 'passwordResetAllowedRetries' => 3,
'portal' => 'http://auth.example.com/', 'port' => -1,
'portalAntiFrame' => 1, 'portal' => 'http://auth.example.com/',
'portalCheckLogins' => 1, 'portalAntiFrame' => 1,
'portalDisplayAppslist' => 1, 'portalCheckLogins' => 1,
'portalDisplayChangePassword' => '$_auth =~ /^(LDAP|DBI|Demo)$/', 'portalDisplayAppslist' => 1,
'portalDisplayFavApps' => 1, 'portalDisplayChangePassword' => '$_auth =~ /^(LDAP|DBI|Demo)$/',
'portalDisplayLoginHistory' => 1, 'portalDisplayFavApps' => 1,
'portalDisplayLogout' => 1, 'portalDisplayLoginHistory' => 1,
'portalDisplayOidcConsents' => '$_oidcConnectedRP', 'portalDisplayLogout' => 1,
'portalDisplayRegister' => 1, 'portalDisplayOidcConsents' => '$_oidcConnectedRP',
'portalErrorOnExpiredSession' => 1, 'portalDisplayRegister' => 1,
'portalForceAuthnInterval' => 5, 'portalErrorOnExpiredSession' => 1,
'portalMainLogo' => 'common/logos/logo_llng_400px.png', 'portalForceAuthnInterval' => 5,
'portalPingInterval' => 60000, 'portalMainLogo' => 'common/logos/logo_llng_400px.png',
'portalRequireOldPassword' => 1, 'portalPingInterval' => 60000,
'portalSkin' => 'bootstrap', 'portalRequireOldPassword' => 1,
'portalUserAttr' => '_user', 'portalSkin' => 'bootstrap',
'proxyAuthnLevel' => 2, 'portalUserAttr' => '_user',
'radius2fActivation' => 0, 'proxyAuthnLevel' => 2,
'radius2fTimeout' => 20, 'radius2fActivation' => 0,
'radiusAuthnLevel' => 3, 'radius2fTimeout' => 20,
'randomPasswordRegexp' => '[A-Z]{3}[a-z]{5}.\\d{2}', 'radiusAuthnLevel' => 3,
'redirectFormMethod' => 'get', 'randomPasswordRegexp' => '[A-Z]{3}[a-z]{5}.\\d{2}',
'registerDB' => 'Null', 'redirectFormMethod' => 'get',
'registerTimeout' => 0, 'registerDB' => 'Null',
'registerUrl' => 'http://auth.example.com/register', 'registerTimeout' => 0,
'reloadTimeout' => 5, 'registerUrl' => 'http://auth.example.com/register',
'reloadTimeout' => 5,
'remoteGlobalStorage' => 'Lemonldap::NG::Common::Apache::Session::SOAP', 'remoteGlobalStorage' => 'Lemonldap::NG::Common::Apache::Session::SOAP',
'remoteGlobalStorageOptions' => { 'remoteGlobalStorageOptions' => {
'ns' => 'ns' =>

View File

@ -67,6 +67,6 @@ our $issuerParameters = {
issuerDBSAML => [qw(issuerDBSAMLActivation issuerDBSAMLPath issuerDBSAMLRule)], issuerDBSAML => [qw(issuerDBSAMLActivation issuerDBSAMLPath issuerDBSAMLRule)],
}; };
our $samlServiceParameters = [qw(samlEntityID samlServicePrivateKeySig samlServicePrivateKeySigPwd samlServicePublicKeySig samlServicePrivateKeyEnc samlServicePrivateKeyEncPwd samlServicePublicKeyEnc samlServiceUseCertificateInResponse samlServiceSignatureMethod samlNameIDFormatMapEmail samlNameIDFormatMapX509 samlNameIDFormatMapWindows samlNameIDFormatMapKerberos samlAuthnContextMapPassword samlAuthnContextMapPasswordProtectedTransport samlAuthnContextMapTLSClient samlAuthnContextMapKerberos samlOrganizationDisplayName samlOrganizationName samlOrganizationURL samlSPSSODescriptorAuthnRequestsSigned samlSPSSODescriptorWantAssertionsSigned samlSPSSODescriptorSingleLogoutServiceHTTPRedirect samlSPSSODescriptorSingleLogoutServiceHTTPPost samlSPSSODescriptorSingleLogoutServiceSOAP samlSPSSODescriptorAssertionConsumerServiceHTTPArtifact samlSPSSODescriptorAssertionConsumerServiceHTTPPost samlSPSSODescriptorArtifactResolutionServiceArtifact samlIDPSSODescriptorWantAuthnRequestsSigned samlIDPSSODescriptorSingleSignOnServiceHTTPRedirect samlIDPSSODescriptorSingleSignOnServiceHTTPPost samlIDPSSODescriptorSingleSignOnServiceHTTPArtifact samlIDPSSODescriptorSingleLogoutServiceHTTPRedirect samlIDPSSODescriptorSingleLogoutServiceHTTPPost samlIDPSSODescriptorSingleLogoutServiceSOAP samlIDPSSODescriptorArtifactResolutionServiceArtifact samlAttributeAuthorityDescriptorAttributeServiceSOAP samlIdPResolveCookie samlMetadataForceUTF8 samlStorage samlStorageOptions samlRelayStateTimeout samlUseQueryStringSpecific samlCommonDomainCookieActivation samlCommonDomainCookieDomain samlCommonDomainCookieReader samlCommonDomainCookieWriter samlDiscoveryProtocolActivation samlDiscoveryProtocolURL samlDiscoveryProtocolPolicy samlDiscoveryProtocolIsPassive samlOverrideIDPEntityID)]; our $samlServiceParameters = [qw(samlEntityID samlServicePrivateKeySig samlServicePrivateKeySigPwd samlServicePublicKeySig samlServicePrivateKeyEnc samlServicePrivateKeyEncPwd samlServicePublicKeyEnc samlServiceUseCertificateInResponse samlServiceSignatureMethod samlNameIDFormatMapEmail samlNameIDFormatMapX509 samlNameIDFormatMapWindows samlNameIDFormatMapKerberos samlAuthnContextMapPassword samlAuthnContextMapPasswordProtectedTransport samlAuthnContextMapTLSClient samlAuthnContextMapKerberos samlOrganizationDisplayName samlOrganizationName samlOrganizationURL samlSPSSODescriptorAuthnRequestsSigned samlSPSSODescriptorWantAssertionsSigned samlSPSSODescriptorSingleLogoutServiceHTTPRedirect samlSPSSODescriptorSingleLogoutServiceHTTPPost samlSPSSODescriptorSingleLogoutServiceSOAP samlSPSSODescriptorAssertionConsumerServiceHTTPArtifact samlSPSSODescriptorAssertionConsumerServiceHTTPPost samlSPSSODescriptorArtifactResolutionServiceArtifact samlIDPSSODescriptorWantAuthnRequestsSigned samlIDPSSODescriptorSingleSignOnServiceHTTPRedirect samlIDPSSODescriptorSingleSignOnServiceHTTPPost samlIDPSSODescriptorSingleSignOnServiceHTTPArtifact samlIDPSSODescriptorSingleLogoutServiceHTTPRedirect samlIDPSSODescriptorSingleLogoutServiceHTTPPost samlIDPSSODescriptorSingleLogoutServiceSOAP samlIDPSSODescriptorArtifactResolutionServiceArtifact samlAttributeAuthorityDescriptorAttributeServiceSOAP samlIdPResolveCookie samlMetadataForceUTF8 samlStorage samlStorageOptions samlRelayStateTimeout samlUseQueryStringSpecific samlCommonDomainCookieActivation samlCommonDomainCookieDomain samlCommonDomainCookieReader samlCommonDomainCookieWriter samlDiscoveryProtocolActivation samlDiscoveryProtocolURL samlDiscoveryProtocolPolicy samlDiscoveryProtocolIsPassive samlOverrideIDPEntityID)];
our $oidcServiceParameters = [qw(oidcServiceMetaDataIssuer oidcServiceMetaDataAuthorizeURI oidcServiceMetaDataTokenURI oidcServiceMetaDataUserInfoURI oidcServiceMetaDataJWKSURI oidcServiceMetaDataRegistrationURI oidcServiceMetaDataEndSessionURI oidcServiceMetaDataCheckSessionURI oidcServiceMetaDataFrontChannelURI oidcServiceMetaDataBackChannelURI oidcServiceMetaDataAuthnContext oidcServicePrivateKeySig oidcServicePublicKeySig oidcServiceKeyIdSig oidcServiceAllowDynamicRegistration oidcServiceAllowAuthorizationCodeFlow oidcServiceAllowImplicitFlow oidcServiceAllowHybridFlow oidcStorage oidcStorageOptions)]; our $oidcServiceParameters = [qw(oidcServiceMetaDataIssuer oidcServiceMetaDataAuthorizeURI oidcServiceMetaDataTokenURI oidcServiceMetaDataUserInfoURI oidcServiceMetaDataJWKSURI oidcServiceMetaDataRegistrationURI oidcServiceMetaDataIntrospectionURI oidcServiceMetaDataEndSessionURI oidcServiceMetaDataCheckSessionURI oidcServiceMetaDataFrontChannelURI oidcServiceMetaDataBackChannelURI oidcServiceMetaDataAuthnContext oidcServicePrivateKeySig oidcServicePublicKeySig oidcServiceKeyIdSig oidcServiceAllowDynamicRegistration oidcServiceAllowAuthorizationCodeFlow oidcServiceAllowImplicitFlow oidcServiceAllowHybridFlow oidcStorage oidcStorageOptions)];
1; 1;

View File

@ -380,7 +380,8 @@ sub headersInit {
$class->tsv->{headerList}->{$vhost} = [ keys %headers ]; $class->tsv->{headerList}->{$vhost} = [ keys %headers ];
my $sub = ''; my $sub = '';
foreach ( keys %headers ) { foreach ( keys %headers ) {
my $val = $class->substitute( $headers{$_} ) . " || ''"; $headers{$_} ||= "''";
my $val = $class->substitute( $headers{$_} ) . " // ''";
$sub .= "('$_' => $val),"; $sub .= "('$_' => $val),";
} }

View File

@ -26,6 +26,8 @@ init(
exportedHeaders => { exportedHeaders => {
'test2.example.com' => { 'test2.example.com' => {
'Auth-User' => '$uid', 'Auth-User' => '$uid',
'empty' => undef,
'zero' => "'0'",
}, },
} }
} }
@ -114,6 +116,13 @@ ok(
ok( $res->[0] == 200, 'Code is 200' ) or explain( $res->[0], 200 ); ok( $res->[0] == 200, 'Code is 200' ) or explain( $res->[0], 200 );
count(2); count(2);
my %headers = @{ $res->[1] };
ok( $headers{'zero'} eq '0', 'Found "zero" header with "0"' )
or print STDERR Data::Dumper::Dumper( $res->[1] );
ok( $headers{'empty'} eq '', 'Found "empty" header without value' )
or print STDERR Data::Dumper::Dumper( $res->[1] );
count(2);
@headers = grep { /service|^XFromVH$/ } @{ $res->[1] }; @headers = grep { /service|^XFromVH$/ } @{ $res->[1] };
@values = grep { /\.example\.com|^$sessionId$/ } @{ $res->[1] }; @values = grep { /\.example\.com|^$sessionId$/ } @{ $res->[1] };
ok( @headers == 2, 'Found 2 service headers' ) ok( @headers == 2, 'Found 2 service headers' )

View File

@ -1111,6 +1111,10 @@ qr/(?:(?:https?):\/\/(?:(?:(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.]
qr/^(?:(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.])*(?:[a-zA-Z][-a-zA-Z0-9]*[a-zA-Z0-9]|[a-zA-Z])[.]?))?$/, qr/^(?:(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.])*(?:[a-zA-Z][-a-zA-Z0-9]*[a-zA-Z0-9]|[a-zA-Z])[.]?))?$/,
'type' => 'text' 'type' => 'text'
}, },
'dontCompactConf' => {
'default' => 0,
'type' => 'bool'
},
'exportedAttr' => { 'exportedAttr' => {
'type' => 'text' 'type' => 'text'
}, },
@ -2107,6 +2111,10 @@ qr/^(?:\*\.)?(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.])*(?:[a-zA-Z][
'default' => 'flogout', 'default' => 'flogout',
'type' => 'text' 'type' => 'text'
}, },
'oidcServiceMetaDataIntrospectionURI' => {
'default' => 'introspect',
'type' => 'text'
},
'oidcServiceMetaDataIssuer' => { 'oidcServiceMetaDataIssuer' => {
'type' => 'text' 'type' => 'text'
}, },

View File

@ -385,6 +385,11 @@ sub attributes {
msgFail => '__badUrl__', msgFail => '__badUrl__',
documentation => 'URL to call on reload', documentation => 'URL to call on reload',
}, },
dontCompactConf => {
type => 'bool',
default => 0,
documentation => 'Don t compact configuration',
},
portalMainLogo => { portalMainLogo => {
type => 'text', type => 'text',
default => 'common/logos/logo_llng_400px.png', default => 'common/logos/logo_llng_400px.png',
@ -3483,6 +3488,11 @@ m{^(?:ldapi://[^/]*/?|\w[\w\-\.]*(?::\d{1,5})?|ldap(?:s|\+tls)?://\w[\w\-\.]*(?:
default => 'register', default => 'register',
documentation => 'OpenID Connect registration endpoint', documentation => 'OpenID Connect registration endpoint',
}, },
oidcServiceMetaDataIntrospectionURI => {
type => 'text',
default => 'introspect',
documentation => 'OpenID Connect introspection endpoint',
},
oidcServiceMetaDataEndSessionURI => { oidcServiceMetaDataEndSessionURI => {
type => 'text', type => 'text',
default => 'logout', default => 'logout',

View File

@ -511,7 +511,8 @@ sub tree {
title => 'logParams', title => 'logParams',
help => 'logs.html', help => 'logs.html',
form => 'simpleInputContainer', form => 'simpleInputContainer',
nodes => [ 'whatToTrace', 'customToTrace', 'hiddenAttributes' ] nodes =>
[ 'whatToTrace', 'customToTrace', 'hiddenAttributes' ]
}, },
{ {
title => 'cookieParams', title => 'cookieParams',
@ -564,7 +565,7 @@ sub tree {
{ {
title => 'reloadParams', title => 'reloadParams',
help => 'configlocation.html#configuration_reload', help => 'configlocation.html#configuration_reload',
nodes => [ 'reloadUrls', 'reloadTimeout', ] nodes => [ 'reloadUrls', 'reloadTimeout', 'dontCompactConf' ]
}, },
{ {
title => 'plugins', title => 'plugins',
@ -1125,6 +1126,7 @@ sub tree {
'oidcServiceMetaDataUserInfoURI', 'oidcServiceMetaDataUserInfoURI',
'oidcServiceMetaDataJWKSURI', 'oidcServiceMetaDataJWKSURI',
'oidcServiceMetaDataRegistrationURI', 'oidcServiceMetaDataRegistrationURI',
'oidcServiceMetaDataIntrospectionURI',
'oidcServiceMetaDataEndSessionURI', 'oidcServiceMetaDataEndSessionURI',
'oidcServiceMetaDataCheckSessionURI', 'oidcServiceMetaDataCheckSessionURI',
'oidcServiceMetaDataFrontChannelURI', 'oidcServiceMetaDataFrontChannelURI',

View File

@ -103,13 +103,30 @@ sub check {
hdebug(" testNewConf() failed"); hdebug(" testNewConf() failed");
return 0; return 0;
} }
my $separator = $self->newConf->{multiValuesSeparator} || '; ';
hdebug(" tests succeed"); hdebug(" tests succeed");
$self->compactConf( $self->newConf ); my %conf = %{ $self->newConf() };
my %compactedConf = %{ $self->compactConf( $self->newConf ) };
my @removedKeys = ();
unless ( $self->confChanged ) { unless ( $self->confChanged ) {
hdebug(" no change detected"); hdebug(" no change detected");
$self->message('__confNotChanged__'); $self->message('__confNotChanged__');
return 0; return 0;
} }
unless ( $self->newConf->{dontCompactConf} ) {
foreach ( sort keys %conf ) {
push @removedKeys, $_ unless exists $compactedConf{$_};
}
}
push @{ $self->changes },
(
$self->{newConf}->{dontCompactConf}
? { confCompacted => '0' }
: {
confCompacted => '1',
removedKeys => join( $separator, @removedKeys )
}
);
return 1; return 1;
} }

View File

@ -252,6 +252,7 @@
"dateTitle":"تاريخ", "dateTitle":"تاريخ",
"dn":"دي أن", "dn":"دي أن",
"domain":"نطاق", "domain":"نطاق",
"dontCompactConf":"Don't compact configuration file",
"download":"تحميل", "download":"تحميل",
"downloadIt":"نزله", "downloadIt":"نزله",
"duplicate":"مكررة", "duplicate":"مكررة",
@ -570,6 +571,7 @@
"oidcServiceMetaDataJWKSURI":"JWKS", "oidcServiceMetaDataJWKSURI":"JWKS",
"oidcServiceMetaDataKeys":"المفاتيح", "oidcServiceMetaDataKeys":"المفاتيح",
"oidcServiceMetaDataRegistrationURI":"التسجيل", "oidcServiceMetaDataRegistrationURI":"التسجيل",
"oidcServiceMetaDataIntrospectionURI":"Introspection",
"oidcServiceMetaDataSecurity":"الحماية", "oidcServiceMetaDataSecurity":"الحماية",
"oidcServiceMetaDataEndSessionURI":"نهاية الجلسة", "oidcServiceMetaDataEndSessionURI":"نهاية الجلسة",
"oidcServiceMetaDataAuthnContext":"سياق إثبات الهوية", "oidcServiceMetaDataAuthnContext":"سياق إثبات الهوية",
@ -1051,4 +1053,4 @@
"samlRelayStateTimeout":"تناوب حالة مهلة الجلسة ", "samlRelayStateTimeout":"تناوب حالة مهلة الجلسة ",
"samlUseQueryStringSpecific":"استخدام أسلوب query_string المعين", "samlUseQueryStringSpecific":"استخدام أسلوب query_string المعين",
"samlOverrideIDPEntityID":"Override Entity ID when acting as IDP" "samlOverrideIDPEntityID":"Override Entity ID when acting as IDP"
} }

View File

@ -251,6 +251,7 @@
"dateTitle":"Dates", "dateTitle":"Dates",
"dn":"DN", "dn":"DN",
"domain":"Domain", "domain":"Domain",
"dontCompactConf":"Don't compact configuration file",
"download":"Download", "download":"Download",
"downloadIt":"Download it", "downloadIt":"Download it",
"duplicate":"Duplicate", "duplicate":"Duplicate",
@ -569,6 +570,7 @@
"oidcServiceMetaDataJWKSURI":"JWKS", "oidcServiceMetaDataJWKSURI":"JWKS",
"oidcServiceMetaDataKeys":"Keys", "oidcServiceMetaDataKeys":"Keys",
"oidcServiceMetaDataRegistrationURI":"Registration", "oidcServiceMetaDataRegistrationURI":"Registration",
"oidcServiceMetaDataIntrospectionURI":"Introspection",
"oidcServiceMetaDataSecurity":"Security", "oidcServiceMetaDataSecurity":"Security",
"oidcServiceMetaDataEndSessionURI":"End of session", "oidcServiceMetaDataEndSessionURI":"End of session",
"oidcServiceMetaDataAuthnContext":"Authentication context", "oidcServiceMetaDataAuthnContext":"Authentication context",
@ -1050,4 +1052,4 @@
"samlRelayStateTimeout":"RelayState session timeout", "samlRelayStateTimeout":"RelayState session timeout",
"samlUseQueryStringSpecific":"Use specific query_string method", "samlUseQueryStringSpecific":"Use specific query_string method",
"samlOverrideIDPEntityID":"Override Entity ID when acting as IDP" "samlOverrideIDPEntityID":"Override Entity ID when acting as IDP"
} }

View File

@ -251,6 +251,7 @@
"dateTitle":"Dates", "dateTitle":"Dates",
"dn":"DN", "dn":"DN",
"domain":"Domain", "domain":"Domain",
"dontCompactConf":"Don't compact configuration file",
"download":"Download", "download":"Download",
"downloadIt":"Download it", "downloadIt":"Download it",
"duplicate":"Duplicate", "duplicate":"Duplicate",
@ -569,6 +570,7 @@
"oidcServiceMetaDataJWKSURI":"JWKS", "oidcServiceMetaDataJWKSURI":"JWKS",
"oidcServiceMetaDataKeys":"Keys", "oidcServiceMetaDataKeys":"Keys",
"oidcServiceMetaDataRegistrationURI":"Registration", "oidcServiceMetaDataRegistrationURI":"Registration",
"oidcServiceMetaDataIntrospectionURI":"Introspection",
"oidcServiceMetaDataSecurity":"Security", "oidcServiceMetaDataSecurity":"Security",
"oidcServiceMetaDataEndSessionURI":"End of session", "oidcServiceMetaDataEndSessionURI":"End of session",
"oidcServiceMetaDataAuthnContext":"Authentication context", "oidcServiceMetaDataAuthnContext":"Authentication context",

View File

@ -251,6 +251,7 @@
"dateTitle":"Dates", "dateTitle":"Dates",
"dn":"DN", "dn":"DN",
"domain":"Domaine", "domain":"Domaine",
"dontCompactConf":"Ne pas compacter le fichier de configuration",
"download":"Télécharger", "download":"Télécharger",
"downloadIt":"Télécharger", "downloadIt":"Télécharger",
"duplicate":"Dupliquer", "duplicate":"Dupliquer",
@ -569,6 +570,7 @@
"oidcServiceMetaDataJWKSURI":"JWKS", "oidcServiceMetaDataJWKSURI":"JWKS",
"oidcServiceMetaDataKeys":"Clefs", "oidcServiceMetaDataKeys":"Clefs",
"oidcServiceMetaDataRegistrationURI":"Enregistrement", "oidcServiceMetaDataRegistrationURI":"Enregistrement",
"oidcServiceMetaDataIntrospectionURI":"Introspection",
"oidcServiceMetaDataSecurity":"Sécurité", "oidcServiceMetaDataSecurity":"Sécurité",
"oidcServiceMetaDataEndSessionURI":"Fin de session", "oidcServiceMetaDataEndSessionURI":"Fin de session",
"oidcServiceMetaDataAuthnContext":"Contexte d'authentification", "oidcServiceMetaDataAuthnContext":"Contexte d'authentification",

View File

@ -251,6 +251,7 @@
"dateTitle":"Date", "dateTitle":"Date",
"dn":"DN", "dn":"DN",
"domain":"Dominio", "domain":"Dominio",
"dontCompactConf":"Don't compact configuration file",
"download":"Scarica", "download":"Scarica",
"downloadIt":"Scaricalo", "downloadIt":"Scaricalo",
"duplicate":"Duplicato", "duplicate":"Duplicato",
@ -569,6 +570,7 @@
"oidcServiceMetaDataJWKSURI":"JWKS", "oidcServiceMetaDataJWKSURI":"JWKS",
"oidcServiceMetaDataKeys":"Chiavi", "oidcServiceMetaDataKeys":"Chiavi",
"oidcServiceMetaDataRegistrationURI":"Registrazione", "oidcServiceMetaDataRegistrationURI":"Registrazione",
"oidcServiceMetaDataIntrospectionURI":"Introspection",
"oidcServiceMetaDataSecurity":"Sicurezza", "oidcServiceMetaDataSecurity":"Sicurezza",
"oidcServiceMetaDataEndSessionURI":"Fine sessione", "oidcServiceMetaDataEndSessionURI":"Fine sessione",
"oidcServiceMetaDataAuthnContext":"Contesto di autenticazione", "oidcServiceMetaDataAuthnContext":"Contesto di autenticazione",
@ -1050,4 +1052,4 @@
"samlRelayStateTimeout":"Timeout di sessione di RelayState", "samlRelayStateTimeout":"Timeout di sessione di RelayState",
"samlUseQueryStringSpecific":"Utilizza il metodo specifico query_string", "samlUseQueryStringSpecific":"Utilizza il metodo specifico query_string",
"samlOverrideIDPEntityID":"Sostituisci l'ID entità quando agisce come IDP" "samlOverrideIDPEntityID":"Sostituisci l'ID entità quando agisce come IDP"
} }

View File

@ -251,6 +251,7 @@
"dateTitle":"Ngày", "dateTitle":"Ngày",
"dn":"DN", "dn":"DN",
"domain":"Tên miền", "domain":"Tên miền",
"dontCompactConf":"Don't compact configuration file",
"download":"Tải xuống", "download":"Tải xuống",
"downloadIt":"Tải xuống", "downloadIt":"Tải xuống",
"duplicate":"Sao y", "duplicate":"Sao y",
@ -569,6 +570,7 @@
"oidcServiceMetaDataJWKSURI":"JWKS", "oidcServiceMetaDataJWKSURI":"JWKS",
"oidcServiceMetaDataKeys":"Khóa", "oidcServiceMetaDataKeys":"Khóa",
"oidcServiceMetaDataRegistrationURI":"Đăng ký", "oidcServiceMetaDataRegistrationURI":"Đăng ký",
"oidcServiceMetaDataIntrospectionURI":"Introspection",
"oidcServiceMetaDataSecurity":"Bảo mật", "oidcServiceMetaDataSecurity":"Bảo mật",
"oidcServiceMetaDataEndSessionURI":"Kết thúc phiên", "oidcServiceMetaDataEndSessionURI":"Kết thúc phiên",
"oidcServiceMetaDataAuthnContext":"Ngữ cảnh xác thực", "oidcServiceMetaDataAuthnContext":"Ngữ cảnh xác thực",
@ -1050,4 +1052,4 @@
"samlRelayStateTimeout":"Thời gian hết hạn phiên RelayState ", "samlRelayStateTimeout":"Thời gian hết hạn phiên RelayState ",
"samlUseQueryStringSpecific":"Sử dụng phương pháp query_string cụ thể", "samlUseQueryStringSpecific":"Sử dụng phương pháp query_string cụ thể",
"samlOverrideIDPEntityID":"Override Entity ID when acting as IDP" "samlOverrideIDPEntityID":"Override Entity ID when acting as IDP"
} }

View File

@ -251,6 +251,7 @@
"dateTitle":"日期", "dateTitle":"日期",
"dn":"LDAP 唯一名称", "dn":"LDAP 唯一名称",
"domain":"域", "domain":"域",
"dontCompactConf":"Don't compact configuration file",
"download":"下载", "download":"下载",
"downloadIt":"下载它", "downloadIt":"下载它",
"duplicate":"Duplicate", "duplicate":"Duplicate",
@ -569,6 +570,7 @@
"oidcServiceMetaDataJWKSURI":"JWKS", "oidcServiceMetaDataJWKSURI":"JWKS",
"oidcServiceMetaDataKeys":"键值", "oidcServiceMetaDataKeys":"键值",
"oidcServiceMetaDataRegistrationURI":"Registration", "oidcServiceMetaDataRegistrationURI":"Registration",
"oidcServiceMetaDataIntrospectionURI":"Introspection",
"oidcServiceMetaDataSecurity":"Security", "oidcServiceMetaDataSecurity":"Security",
"oidcServiceMetaDataEndSessionURI":"End of session", "oidcServiceMetaDataEndSessionURI":"End of session",
"oidcServiceMetaDataAuthnContext":"Authentication context", "oidcServiceMetaDataAuthnContext":"Authentication context",
@ -1050,4 +1052,4 @@
"samlRelayStateTimeout":"RelayState session timeout", "samlRelayStateTimeout":"RelayState session timeout",
"samlUseQueryStringSpecific":"Use specific query_string method", "samlUseQueryStringSpecific":"Use specific query_string method",
"samlOverrideIDPEntityID":"Override Entity ID when acting as IDP" "samlOverrideIDPEntityID":"Override Entity ID when acting as IDP"
} }

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -38,7 +38,7 @@ while ( my $body = &body() ) {
#print STDERR Dumper($resBody); #print STDERR Dumper($resBody);
ok( $resBody->{result} == 1, "$desc: JSON response contains \"result:1\"" ); ok( $resBody->{result} == 1, "$desc: JSON response contains \"result:1\"" );
ok( @{ $resBody->{details}->{__changes__} } eq 1, ok( @{ $resBody->{details}->{__changes__} } eq 2,
"$desc: conf has changed" ) "$desc: conf has changed" )
or print STDERR Dumper($resBody); or print STDERR Dumper($resBody);
ok( ok(

View File

@ -54,8 +54,8 @@ ok(
) or print STDERR Dumper($resBody); ) or print STDERR Dumper($resBody);
ok( ok(
@{ $resBody->{details}->{__changes__} } == 22, @{ $resBody->{details}->{__changes__} } == 23,
'JSON response contains 24 changes' 'JSON response contains 23 changes'
) or print STDERR Dumper($resBody); ) or print STDERR Dumper($resBody);
#print STDERR Dumper($resBody); #print STDERR Dumper($resBody);
@ -113,11 +113,10 @@ ok( $res = &client->jsonResponse('/diff/1/2'), 'Diff called' );
my ( @c1, @c2 ); my ( @c1, @c2 );
ok( ( @c1 = sort keys %{ $res->[0] } ), 'diff() detects changes in conf 1' ); ok( ( @c1 = sort keys %{ $res->[0] } ), 'diff() detects changes in conf 1' );
ok( ( @c2 = sort keys %{ $res->[1] } ), 'diff() detects changes in conf 2' ); ok( ( @c2 = sort keys %{ $res->[1] } ), 'diff() detects changes in conf 2' );
ok( @c1 == 12, '11 keys changed in conf 1' ) ok( @c1 == 12, '12 keys changed in conf 1' )
or print STDERR "Expect: 12 keys, get: " . join( ', ', @c1 ) . "\n"; or print STDERR "Expect: 12 keys, get: " . join( ', ', @c1 ) . "\n";
ok( @c2 == 16, '14 keys changed or created in conf 2' ) ok( @c2 == 16, '16 keys changed or created in conf 2' )
or print STDERR "Expect: 16 keys, get: " . join( ',', @c2 ) . "\n"; or print STDERR "Expect: 16 keys, get: " . join( ',', @c2 ) . "\n";
count(5); count(5);
unlink $confFiles->[1]; unlink $confFiles->[1];
@ -246,6 +245,10 @@ sub changes {
'new' => 0, 'new' => 0,
'key' => 'captcha_mail_enabled', 'key' => 'captcha_mail_enabled',
'old' => '1' 'old' => '1'
},
{
'confCompacted' => '1',
'removedKeys' => 'some; keys'
} }
]; ];
} }

View File

@ -38,7 +38,7 @@ foreach my $i ( 0 .. 1 ) {
} }
ok( ok(
@{ $resBody->{details}->{__changes__} } == 22, @{ $resBody->{details}->{__changes__} } == 23,
'JSON response contains 22 changes' 'JSON response contains 22 changes'
) or print STDERR Dumper($resBody); ) or print STDERR Dumper($resBody);
@ -224,6 +224,10 @@ sub changes {
{ {
'key' => 'virtualHosts', 'key' => 'virtualHosts',
'old' => 'test2.example.com' 'old' => 'test2.example.com'
},
{
'confCompacted' => '1',
'removedKeys' => 'some; keys'
} }
]; ];
} }

File diff suppressed because it is too large Load Diff

View File

@ -34,7 +34,7 @@ sub authenticate {
{ user => $req->user, password => $req->data->{password} } ); { user => $req->user, password => $req->data->{password} } );
}; };
if ($@) { if ($@) {
$self->logger("Auth error: $@"); $self->logger->error("Auth error: $@");
$self->setSecurity($req); $self->setSecurity($req);
return PE_ERROR; return PE_ERROR;
} }

View File

@ -57,6 +57,7 @@ has iss => (
# - userinfo : => userInfo() for unauth users (RP) # - userinfo : => userInfo() for unauth users (RP)
# - jwks : => jwks() for unauth users (RP) # - jwks : => jwks() for unauth users (RP)
# - register : => registration() for unauth users (RP) # - register : => registration() for unauth users (RP)
# - introspect : => introspection() for unauth users (RP)
# #
# Other paths will be handle by run() and return PE_ERROR # Other paths will be handle by run() and return PE_ERROR
# #
@ -86,22 +87,24 @@ sub init {
# Manage RP requests # Manage RP requests
$self->addRouteFromConf( $self->addRouteFromConf(
'Unauth', 'Unauth',
oidcServiceMetaDataEndSessionURI => 'endSessionDone', oidcServiceMetaDataEndSessionURI => 'endSessionDone',
oidcServiceMetaDataCheckSessionURI => 'checkSession', oidcServiceMetaDataCheckSessionURI => 'checkSession',
oidcServiceMetaDataTokenURI => 'token', oidcServiceMetaDataTokenURI => 'token',
oidcServiceMetaDataUserInfoURI => 'userInfo', oidcServiceMetaDataUserInfoURI => 'userInfo',
oidcServiceMetaDataJWKSURI => 'jwks', oidcServiceMetaDataJWKSURI => 'jwks',
oidcServiceMetaDataRegistrationURI => 'registration', oidcServiceMetaDataRegistrationURI => 'registration',
oidcServiceMetaDataIntrospectionURI => 'introspection',
); );
# Manage user requests # Manage user requests
$self->addRouteFromConf( $self->addRouteFromConf(
'Auth', 'Auth',
oidcServiceMetaDataCheckSessionURI => 'checkSession', oidcServiceMetaDataCheckSessionURI => 'checkSession',
oidcServiceMetaDataTokenURI => 'badAuthRequest', oidcServiceMetaDataTokenURI => 'badAuthRequest',
oidcServiceMetaDataUserInfoURI => 'badAuthRequest', oidcServiceMetaDataUserInfoURI => 'badAuthRequest',
oidcServiceMetaDataJWKSURI => 'badAuthRequest', oidcServiceMetaDataJWKSURI => 'badAuthRequest',
oidcServiceMetaDataRegistrationURI => 'badAuthRequest', oidcServiceMetaDataRegistrationURI => 'badAuthRequest',
oidcServiceMetaDataIntrospectionURI => 'badAuthRequest',
); );
# Metadata (.well-known/openid-configuration) # Metadata (.well-known/openid-configuration)
@ -997,50 +1000,13 @@ sub token {
my ( $self, $req ) = @_; my ( $self, $req ) = @_;
$self->logger->debug("URL detected as an OpenID Connect TOKEN URL"); $self->logger->debug("URL detected as an OpenID Connect TOKEN URL");
# Check authentication my $rp = $self->checkEndPointAuthenticationCredentials($req);
my ( $client_id, $client_secret ) =
$self->getEndPointAuthenticationCredentials($req);
unless ($client_id) {
$self->logger->error(
"No authentication provided to get token, or authentication type not supported"
);
return $self->p->sendError( $req, 'invalid_request', 400 );
}
# Verify that client_id is registered in configuration
my $rp = $self->getRP($client_id);
unless ($rp) { unless ($rp) {
$self->userLogger->error(
"No registered Relying Party found with client_id $client_id");
return $self->p->sendError( $req, 'invalid_request', 400 ); return $self->p->sendError( $req, 'invalid_request', 400 );
} }
else {
$self->logger->debug("Client id $client_id match Relying Party $rp");
}
# Check client_secret my $client_id = $self->oidcRPList->{$rp}->{oidcRPMetaDataOptionsClientID};
if ( $self->conf->{oidcRPMetaDataOptions}->{$rp}
->{oidcRPMetaDataOptionsPublic} )
{
$self->logger->debug(
"Relying Party $rp is public, do not check client secret");
}
else {
unless ($client_secret) {
$self->logger->error(
"Relying Party $rp is confidential but no client secret was provided to authenticate on token endpoint"
);
return $self->p->sendError( $req, 'invalid_request', 400 );
}
unless ( $client_secret eq $self->conf->{oidcRPMetaDataOptions}->{$rp}
->{oidcRPMetaDataOptionsClientSecret} )
{
$self->logger->error("Wrong credentials for $rp");
return $self->p->sendError( $req, 'invalid_request', 400 );
}
}
# Get code session # Get code session
my $code = $req->param('code'); my $code = $req->param('code');
@ -1190,7 +1156,7 @@ sub token {
return $self->p->sendJSONresponse( $req, $token_response ); return $self->p->sendJSONresponse( $req, $token_response );
} }
# Handle uerinfo endpoint # Handle userinfo endpoint
sub userInfo { sub userInfo {
my ( $self, $req ) = @_; my ( $self, $req ) = @_;
$self->logger->debug("URL detected as an OpenID Connect USERINFO URL"); $self->logger->debug("URL detected as an OpenID Connect USERINFO URL");
@ -1247,6 +1213,66 @@ sub userInfo {
} }
} }
sub introspection {
my ( $self, $req ) = @_;
$self->logger->debug("URL detected as an OpenID Connect INTROSPECTION URL");
my $rp = $self->checkEndPointAuthenticationCredentials($req);
unless ($rp) {
return $self->p->sendError( $req, 'invalid_request', 400 );
}
if ( $self->conf->{oidcRPMetaDataOptions}->{$rp}
->{oidcRPMetaDataOptionsPublic} )
{
$self->logger->error(
"Public clients are not allowed to acces the introspection endpoint"
);
return $self->p->sendError( $req, 'unauthorized_client', 401 );
}
my $token = $req->param('token');
unless ($token) {
return $self->p->sendError( $req, 'invalid_request', 400 );
}
my $response = { active => JSON::false };
my $oidcSession = $self->getOpenIDConnectSession($token);
if ($oidcSession) {
if ( my $user_session_id = $oidcSession->{data}->{user_session_id} ) {
# Get user identifier
my $apacheSession = $self->p->getApacheSession($user_session_id);
if ($apacheSession) {
$response->{active} = JSON::true;
# The ID attribute we choose is the one of the calling webservice,
# which might be different from the OIDC client the token was issued to.
my $user_id_attribute =
$self->conf->{oidcRPMetaDataOptions}->{$rp}
->{oidcRPMetaDataOptionsUserIDAttr}
|| $self->conf->{whatToTrace};
$response->{sub} = $apacheSession->data->{$user_id_attribute};
$response->{scope} = $oidcSession->{data}->{scope}
if $oidcSession->{data}->{scope};
$response->{client_id} =
$self->oidcRPList->{ $oidcSession->{data}->{rp} }
->{oidcRPMetaDataOptionsClientID}
if $oidcSession->{data}->{rp};
$response->{exp} =
$oidcSession->{data}->{_utime} + $self->conf->{timeout};
}
}
else {
$self->logger->error(
"Could not find user session ID in access token object");
}
}
return $self->p->sendJSONresponse( $req, $response );
}
# Handle jwks endpoint # Handle jwks endpoint
sub jwks { sub jwks {
my ( $self, $req ) = @_; my ( $self, $req ) = @_;
@ -1485,13 +1511,14 @@ sub logout {
sub metadata { sub metadata {
my ( $self, $req ) = @_; my ( $self, $req ) = @_;
my $issuerDBOpenIDConnectPath = $self->conf->{issuerDBOpenIDConnectPath}; my $issuerDBOpenIDConnectPath = $self->conf->{issuerDBOpenIDConnectPath};
my $authorize_uri = $self->conf->{oidcServiceMetaDataAuthorizeURI}; my $authorize_uri = $self->conf->{oidcServiceMetaDataAuthorizeURI};
my $token_uri = $self->conf->{oidcServiceMetaDataTokenURI}; my $token_uri = $self->conf->{oidcServiceMetaDataTokenURI};
my $userinfo_uri = $self->conf->{oidcServiceMetaDataUserInfoURI}; my $userinfo_uri = $self->conf->{oidcServiceMetaDataUserInfoURI};
my $jwks_uri = $self->conf->{oidcServiceMetaDataJWKSURI}; my $jwks_uri = $self->conf->{oidcServiceMetaDataJWKSURI};
my $registration_uri = $self->conf->{oidcServiceMetaDataRegistrationURI}; my $registration_uri = $self->conf->{oidcServiceMetaDataRegistrationURI};
my $endsession_uri = $self->conf->{oidcServiceMetaDataEndSessionURI}; my $endsession_uri = $self->conf->{oidcServiceMetaDataEndSessionURI};
my $checksession_uri = $self->conf->{oidcServiceMetaDataCheckSessionURI}; my $checksession_uri = $self->conf->{oidcServiceMetaDataCheckSessionURI};
my $introspection_uri = $self->conf->{oidcServiceMetaDataIntrospectionURI};
my $path = $self->path . '/'; my $path = $self->path . '/';
my $issuer = $self->iss; my $issuer = $self->iss;
@ -1531,6 +1558,7 @@ sub metadata {
authorization_endpoint => $baseUrl . $authorize_uri, authorization_endpoint => $baseUrl . $authorize_uri,
end_session_endpoint => $baseUrl . $endsession_uri, end_session_endpoint => $baseUrl . $endsession_uri,
check_session_iframe => $baseUrl . $checksession_uri, check_session_iframe => $baseUrl . $checksession_uri,
introspection_endpoint => $baseUrl . $introspection_uri,
# Logout capabilities # Logout capabilities
backchannel_logout_supported => JSON::true, backchannel_logout_supported => JSON::true,
@ -1551,6 +1579,8 @@ sub metadata {
subject_types_supported => ["public"], subject_types_supported => ["public"],
token_endpoint_auth_methods_supported => token_endpoint_auth_methods_supported =>
[qw/client_secret_post client_secret_basic/], [qw/client_secret_post client_secret_basic/],
introspection_endpoint_auth_methods_supported =>
[qw/client_secret_post client_secret_basic/],
claims_supported => [qw/sub iss auth_time acr/], claims_supported => [qw/sub iss auth_time acr/],
request_parameter_supported => JSON::true, request_parameter_supported => JSON::true,
request_uri_parameter_supported => JSON::true, request_uri_parameter_supported => JSON::true,

View File

@ -728,7 +728,7 @@ sub getOpenIDConnectSession {
return undef; return undef;
} }
if ($id) { if ( $id and $type ) {
my $storedType = $oidcSession->{data}->{_type}; my $storedType = $oidcSession->{data}->{_type};
# Only check if a type is set in DB, for backward compatibility # Only check if a type is set in DB, for backward compatibility
@ -1111,6 +1111,56 @@ sub returnBearerError {
]; ];
} }
sub checkEndPointAuthenticationCredentials {
my ( $self, $req ) = @_;
# Check authentication
my ( $client_id, $client_secret ) =
$self->getEndPointAuthenticationCredentials($req);
unless ($client_id) {
$self->logger->error(
"No authentication provided to get token, or authentication type not supported"
);
return undef;
}
# Verify that client_id is registered in configuration
my $rp = $self->getRP($client_id);
unless ($rp) {
$self->userLogger->error(
"No registered Relying Party found with client_id $client_id");
return undef;
}
else {
$self->logger->debug("Client id $client_id match Relying Party $rp");
}
# Check client_secret
if ( $self->conf->{oidcRPMetaDataOptions}->{$rp}
->{oidcRPMetaDataOptionsPublic} )
{
$self->logger->debug(
"Relying Party $rp is public, do not check client secret");
}
else {
unless ($client_secret) {
$self->logger->error(
"Relying Party $rp is confidential but no client secret was provided to authenticate on token endpoint"
);
return undef;
}
unless ( $client_secret eq $self->conf->{oidcRPMetaDataOptions}->{$rp}
->{oidcRPMetaDataOptionsClientSecret} )
{
$self->logger->error("Wrong credentials for $rp");
return undef;
}
}
return $rp;
}
# Get Client ID and Client Secret # Get Client ID and Client Secret
# @return array (client_id, client_secret) # @return array (client_id, client_secret)
sub getEndPointAuthenticationCredentials { sub getEndPointAuthenticationCredentials {

View File

@ -189,6 +189,7 @@ sub display {
elsif ( $req->{error} == PE_REDIRECT ) { elsif ( $req->{error} == PE_REDIRECT ) {
$skinfile = "redirect"; $skinfile = "redirect";
%templateParams = ( %templateParams = (
MAIN_LOGO => $self->conf->{portalMainLogo},
URL => $req->{urldc}, URL => $req->{urldc},
HIDDEN_INPUTS => $self->buildHiddenForm($req), HIDDEN_INPUTS => $self->buildHiddenForm($req),
FORM_METHOD => $req->data->{redirectFormMethod} || 'get', FORM_METHOD => $req->data->{redirectFormMethod} || 'get',

View File

@ -1 +0,0 @@
html,body{height:100%;background:radial-gradient(circle at 50% 0,#fff 0,#ddd 100%) no-repeat scroll 0 0 #ddd}#wrap{min-height:100%;height:auto;margin:0 auto -80px;padding:20px 0 80px}#footer{height:80px;background-color:#fff;background-color:rgba(255,255,255,0.9);text-align:center;padding-top:10px;overflow:hidden}#header img{background-color:#fff;background-color:rgba(255,255,255,0.8);margin-bottom:20px}.card,.navbar-light{background-color:#fff;background-color:rgba(255,255,255,0.9);background-image:none}.login,.password{text-align:center;padding:20px}div.form{margin:0 auto;max-width:330px}div.actions{margin:10px 0 0 0}div.actions a{margin-top:10px}.buttons{text-align:center;margin:10px 0 0 0;cursor:pointer}.btn{white-space:normal}.btn span.fa{padding-right:8px}li.ui-state-active{background-color:#fafafa;background-color:rgba(250,250,250,0.9)}#appslist,#password,#loginHistory,#logout,#oidcConsents{margin-top:20px}div.category{margin:10px 0;cursor:grab}div.application{margin:5px 0;overflow:hidden}div.application a,div.application a:hover{text-decoration:none}p.notifCheck label{margin-left:5px;margin-top:3px;display:inline-block}img.langicon{cursor:pointer}button.idploop{max-width:300px}button.idploop img{max-height:30px}div.oidc_consent_message>ul{text-align:left;list-style:circle}@media(min-width:768px){div.application{height:80px}div.application h4.appname{margin:0}#wrap{margin:0 auto -60px}#footer{height:60px}}.hiddenFrame{border:0;display:hidden;margin:0}.noborder{border:0}.max{width:100%}.link{cursor:pointer}.nodecor:hover,.nodecor:active.nodecor:focus{text-decoration:none}.fa.icon-blue{color:blue}.progress-bar-animated{width:100%}

View File

@ -0,0 +1,212 @@
use lib 'inc';
use Test::More;
use strict;
use IO::String;
use LWP::UserAgent;
use LWP::Protocol::PSGI;
use MIME::Base64;
use JSON;
BEGIN {
require 't/test-lib.pm';
}
my $debug = 'error';
# Initialization
my $op = LLNG::Manager::Test->new( {
ini => {
logLevel => $debug,
domain => 'idp.com',
portal => 'http://auth.op.com',
authentication => 'Demo',
userDB => 'Same',
issuerDBOpenIDConnectActivation => 1,
issuerDBOpenIDConnectRule => '$uid eq "french"',
oidcRPMetaDataExportedVars => {
rp => {
email => "mail",
family_name => "cn",
name => "cn"
},
rp2 => {
email => "mail",
family_name => "cn",
name => "cn"
}
},
oidcServiceMetaDataIssuer => "http://auth.op.com",
oidcServiceMetaDataAuthorizeURI => "authorize",
oidcServiceMetaDataCheckSessionURI => "checksession.html",
oidcServiceMetaDataJWKSURI => "jwks",
oidcServiceMetaDataEndSessionURI => "logout",
oidcServiceMetaDataRegistrationURI => "register",
oidcServiceMetaDataTokenURI => "token",
oidcServiceMetaDataUserInfoURI => "userinfo",
oidcServiceAllowHybridFlow => 1,
oidcServiceAllowImplicitFlow => 1,
oidcServiceAllowDynamicRegistration => 1,
oidcServiceAllowAuthorizationCodeFlow => 1,
oidcRPMetaDataOptions => {
rp => {
oidcRPMetaDataOptionsDisplayName => "RP",
oidcRPMetaDataOptionsIDTokenExpiration => 3600,
oidcRPMetaDataOptionsClientID => "rpid",
oidcRPMetaDataOptionsIDTokenSignAlg => "HS512",
oidcRPMetaDataOptionsClientSecret => "rpsecret",
oidcRPMetaDataOptionsUserIDAttr => "",
oidcRPMetaDataOptionsAccessTokenExpiration => 1,
oidcRPMetaDataOptionsBypassConsent => 1,
},
oauth => {
oidcRPMetaDataOptionsDisplayName => "oauth",
oidcRPMetaDataOptionsClientID => "oauth",
oidcRPMetaDataOptionsClientSecret => "service",
oidcRPMetaDataOptionsUserIDAttr => "",
}
},
oidcOPMetaDataOptions => {},
oidcOPMetaDataJSON => {},
oidcOPMetaDataJWKS => {},
oidcServiceMetaDataAuthnContext => {
'loa-4' => 4,
'loa-1' => 1,
'loa-5' => 5,
'loa-2' => 2,
'loa-3' => 3
},
oidcServicePrivateKeySig => "-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAs2jsmIoFuWzMkilJaA8//5/T30cnuzX9GImXUrFR2k9EKTMt
GMHCdKlWOl3BV+BTAU9TLz7Jzd/iJ5GJ6B8TrH1PHFmHpy8/qE/S5OhinIpIi7eb
ABqnoVcwDdCa8ugzq8k8SWxhRNXfVIlwz4NH1caJ8lmiERFj7IvNKqEhzAk0pyDr
8hubveTC39xREujKlsqutpPAFPJ3f2ybVsdykX5rx0h5SslG3jVWYhZ/SOb2aIzO
r0RMjhQmsYRwbpt3anjlBZ98aOzg7GAkbO8093X5VVk9vaPRg0zxJQ0Do0YLyzkR
isSAIFb0tdKuDnjRGK6y/N2j6At2HjkxntbtGQIDAQABAoIBADYq6LxJd977LWy3
0HT9nboFPIf+SM2qSEc/S5Po+6ipJBA4ZlZCMf7dHa6znet1TDpqA9iQ4YcqIHMH
6xZNQ7hhgSAzG9TrXBHqP+djDlrrGWotvjuy0IfS9ixFnnLWjrtAH9afRWLuG+a/
NHNC1M6DiiTE0TzL/lpt/zzut3CNmWzH+t19X6UsxUg95AzooEeewEYkv25eumWD
mfQZfCtSlIw1sp/QwxeJa/6LJw7KcPZ1wXUm1BN0b9eiKt9Cmni1MS7elgpZlgGt
xtfGTZtNLQ7bgDiM8MHzUfPBhbceNSIx2BeCuOCs/7eaqgpyYHBbAbuBQex2H61l
Lcc3Tz0CgYEA4Kx/avpCPxnvsJ+nHVQm5d/WERuDxk4vH1DNuCYBvXTdVCGADf6a
F5No1JcTH3nPTyPWazOyGdT9LcsEJicLyD8vCM6hBFstG4XjqcAuqG/9DRsElpHQ
yi1zc5DNP7Vxmiz9wII0Mjy0abYKtxnXh9YK4a9g6wrcTpvShhIcIb8CgYEAzGzG
lorVCfX9jXULIznnR/uuP5aSnTEsn0xJeqTlbW0RFWLdj8aIL1peirh1X89HroB9
GeTNqEJXD+3CVL2cx+BRggMDUmEz4hR59meZCDGUyT5fex4LIsceb/ESUl2jo6Sw
HXwWbN67rQ55N4oiOcOppsGxzOHkl5HdExKidycCgYEAr5Qev2tz+fw65LzfzHvH
Kj4S/KuT/5V6He731cFd+sEpdmX3vPgLVAFPG1Q1DZQT/rTzDDQKK0XX1cGiLG63
NnaqOye/jbfzOF8Z277kt51NFMDYhRLPKDD82IOA4xjY/rPKWndmcxwdob8yAIWh
efY76sMz6ntCT+xWSZA9i+ECgYBWMZM2TIlxLsBfEbfFfZewOUWKWEGvd9l5vV/K
D5cRIYivfMUw5yPq2267jPUolayCvniBH4E7beVpuPVUZ7KgcEvNxtlytbt7muil
5Z6X3tf+VodJ0Swe2NhTmNEB26uwxzLe68BE3VFCsbSYn2y48HAq+MawPZr18bHG
ZfgMxwKBgHHRg6HYqF5Pegzk1746uH2G+OoCovk5ylGGYzcH2ghWTK4agCHfBcDt
EYqYAev/l82wi+OZ5O8U+qjFUpT1CVeUJdDs0o5u19v0UJjunU1cwh9jsxBZAWLy
PAGd6SWf4S3uQCTw6dLeMna25YIlPh5qPA6I/pAahe8e3nSu2ckl
-----END RSA PRIVATE KEY-----
",
oidcServicePublicKeySig => "-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAs2jsmIoFuWzMkilJaA8/
/5/T30cnuzX9GImXUrFR2k9EKTMtGMHCdKlWOl3BV+BTAU9TLz7Jzd/iJ5GJ6B8T
rH1PHFmHpy8/qE/S5OhinIpIi7ebABqnoVcwDdCa8ugzq8k8SWxhRNXfVIlwz4NH
1caJ8lmiERFj7IvNKqEhzAk0pyDr8hubveTC39xREujKlsqutpPAFPJ3f2ybVsdy
kX5rx0h5SslG3jVWYhZ/SOb2aIzOr0RMjhQmsYRwbpt3anjlBZ98aOzg7GAkbO80
93X5VVk9vaPRg0zxJQ0Do0YLyzkRisSAIFb0tdKuDnjRGK6y/N2j6At2Hjkxntbt
GQIDAQAB
-----END PUBLIC KEY-----
",
}
}
);
my $res;
# Authenticate to LLNG
my $url = "/";
my $query = "user=french&password=french";
ok(
$res = $op->_post(
"/",
IO::String->new($query),
accept => 'text/html',
length => length($query),
),
"Post authentication"
);
my $idpId = expectCookie($res);
# Get code for RP1
my $query =
"response_type=code&scope=openid%20profile%20email&client_id=rpid&state=af0ifjsldkj&redirect_uri=http%3A%2F%2Frp2.com%2F";
ok(
$res = $op->_get(
"/oauth2/authorize",
query => "$query",
accept => 'text/html',
cookie => "lemonldap=$idpId",
),
"Get authorization code"
);
my ($code) = expectRedirection( $res, qr#http://rp2\.com/.*code=([^\&]*)# );
# Exchange code for AT
$query =
"grant_type=authorization_code&code=$code&redirect_uri=http%3A%2F%2Frp2.com%2F";
ok(
$res = $op->_post(
"/oauth2/token",
IO::String->new($query),
accept => 'text/html',
length => length($query),
custom => {
HTTP_AUTHORIZATION => "Basic " . encode_base64("rpid:rpsecret"),
},
),
"Post token"
);
my $json = from_json( $res->[2]->[0] );
my $token = $json->{access_token};
ok( $token, 'Access token present' );
my $query = "token=$token";
ok(
$res = $op->_post(
"/oauth2/introspect",
IO::String->new($query),
accept => 'text/html',
length => length $query,
custom => {
HTTP_AUTHORIZATION => "Basic " . encode_base64("oauth:service"),
},
),
"Post introspection"
);
expectOK($res);
my $json = from_json( $res->[2]->[0] );
ok( $json->{active}, "Token is valid" );
is( $json->{sub}, "french", "Response contains the correct sub" );
# Check status after expiration
sleep(2);
$query = "token=$token";
ok(
$res = $op->_post(
"/oauth2/introspect",
IO::String->new($query),
accept => 'text/html',
length => length $query,
custom => {
HTTP_AUTHORIZATION => "Basic " . encode_base64("oauth:service"),
},
),
"Post introspection"
);
expectOK($res);
$json = from_json( $res->[2]->[0] );
ok( !$json->{active}, "Token is no longer valid" );
clean_sessions();
done_testing();

View File

@ -26,7 +26,7 @@ SKIP: {
$client = iniCmb('[Dm] and [DB]'); $client = iniCmb('[Dm] and [DB]');
expectCookie( try('rtyler') ); expectCookie( try('rtyler') );
expectReject( try('dwho'), 5 ); expectReject( try('dwho'), 401, 5 );
$client = iniCmb('if($env->{HTTP_X} eq "dwho") then [Dm] else [DB]'); $client = iniCmb('if($env->{HTTP_X} eq "dwho") then [Dm] else [DB]');
expectCookie( try('dwho') ); expectCookie( try('dwho') );
@ -37,7 +37,7 @@ SKIP: {
); );
expectCookie( try('rtyler') ); expectCookie( try('rtyler') );
expectCookie( try('dvador') ); expectCookie( try('dvador') );
expectReject( try('dwho'), 5 ); expectReject( try('dwho'), 401, 5 );
} }
count($maintests); count($maintests);
clean_sessions(); clean_sessions();

View File

@ -344,14 +344,14 @@ Note that it works only for Ajax request (see below).
=cut =cut
sub expectReject { sub expectReject {
my ( $res, $code ) = @_; my ( $res, $status, $code ) = @_;
ok( $res->[0] == 401, ' Response is 401' ) or explain( $res->[0], 401 ); $status ||= 401;
cmp_ok( $res->[0], '==', $status, " Response status is $status" );
eval { $res = JSON::from_json( $res->[2]->[0] ) }; eval { $res = JSON::from_json( $res->[2]->[0] ) };
ok( not($@), 'Content is JSON' ) ok( not($@), ' Content is JSON' )
or explain( $res->[2]->[0], 'JSON content' ); or explain( $res->[2]->[0], 'JSON content' );
if ( defined $code ) { if ( defined $code ) {
ok( $res->{error} == $code, "Error code is $code" ) is( $res->{error}, $code, " Error code is $code" );
or explain( $res->{error}, $code );
} }
else { else {
pass("Error code is $res->{error}"); pass("Error code is $res->{error}");
@ -696,9 +696,8 @@ sub _get {
$args{remote_user} ? ( 'REMOTE_USER' => $args{remote_user} ) $args{remote_user} ? ( 'REMOTE_USER' => $args{remote_user} )
: () : ()
), ),
'REQUEST_METHOD' => $args{method} 'REQUEST_METHOD' => $args{method} || 'GET',
|| 'GET', 'REQUEST_URI' => $path . ( $args{query} ? "?$args{query}" : '' ),
'REQUEST_URI' => $path . ( $args{query} ? "?$args{query}" : '' ),
( $args{query} ? ( QUERY_STRING => $args{query} ) : () ), ( $args{query} ? ( QUERY_STRING => $args{query} ) : () ),
'SCRIPT_NAME' => '', 'SCRIPT_NAME' => '',
'SERVER_NAME' => 'auth.example.com', 'SERVER_NAME' => 'auth.example.com',