Commit d2d9988b authored by Christophe Maudoux's avatar Christophe Maudoux 🐛
Browse files

Merge branch 'v2.0'

parents 19724434 cd53d785
......@@ -96,6 +96,12 @@ Comment: downloaded from
.
Author is unknown and license may be W3C or public-domain
Files: lemonldap-ng-portal/site/htdocs/static/common/modules/GitHub.png
Copyright: GitHub
License: MIT
Comment: downloaded from
https://commons.wikimedia.org/wiki/File:Octicons-mark-github.svg
Files: lemonldap-ng-portal/site/htdocs/static/bootstrap/u2f.png
Copyright: Bautsch <https://commons.wikimedia.org/wiki/User:Bautsch>
License: CC0-1.0
......
......@@ -62,6 +62,12 @@
DocumentRoot __MANAGERAPIDIR__
<Location />
# By default, access to this VHost is denied
# If you want to enable the manager APIs, you MUST
# implement a robust authentication scheme to protect this
# VHost since LemonLDAP::NG provides no protection to the
# Manager APIs yet
Require all denied
<IfModule mod_deflate.c>
......
......@@ -62,6 +62,12 @@
DocumentRoot __MANAGERAPIDIR__
<Location />
# By default, access to this VHost is denied
# If you want to enable the manager APIs, you MUST
# implement a robust authentication scheme to protect this
# VHost since LemonLDAP::NG provides no protection to the
# Manager APIs yet
<IfVersion >= 2.3>
Require all denied
</IfVersion>
......
......@@ -62,6 +62,12 @@
DocumentRoot __MANAGERAPIDIR__
<Location />
# By default, access to this VHost is denied
# If you want to enable the manager APIs, you MUST
# implement a robust authentication scheme to protect this
# VHost since LemonLDAP::NG provides no protection to the
# Manager APIs yet
Order Deny,Allow
Deny from all
......
server {
listen __PORT__;
listen [::]:__PORT__;
server_name manager-api.__DNSDOMAIN__;
root __MANAGERAPIDIR__;
# Use "lm_app" format to get username in nginx.log (see nginx-lmlog.conf)
......@@ -36,8 +37,14 @@ server {
# Uncomment this if you use https only
#add_header Strict-Transport-Security "max-age=15768000";
# Access control
# By default, access to this VHost is denied
# If you want to enable the manager APIs, you MUST
# implement a robust authentication scheme to protect this
# VHost since LemonLDAP::NG provides no protection to the
# Manager APIs yet
#
#allow 127.0.0.0/8;
#allow ::1/128;
deny all;
}
......
......@@ -18,6 +18,7 @@ include __CONFDIR__/nginx-lmlog.conf;
server {
listen __PORT__;
listen [::]:__PORT__;
server_name reload.__DNSDOMAIN__;
root /var/www/html;
......@@ -31,7 +32,8 @@ server {
#real_ip_header X-Forwarded-For;
location = /reload {
allow 127.0.0.1;
allow 127.0.0.1/8;
allow ::1/128;
deny all;
# FastCGI configuration
......@@ -55,7 +57,8 @@ server {
# Uncomment this if status is enabled
#location = /status {
# allow 127.0.0.1;
# allow 127.0.0.1/8;
# allow ::1/128;
# deny all;
# # FastCGI configuration
# include /etc/nginx/fastcgi_params;
......
server {
listen __PORT__;
listen [::]:__PORT__;
server_name manager.__DNSDOMAIN__;
root __MANAGERSITEDIR__;
# Use "lm_app" format to get username in nginx.log (see nginx-lmlog.conf)
......@@ -43,6 +44,7 @@ server {
index manager.psgi;
try_files $uri $uri/ =404;
allow 127.0.0.0/8;
allow ::1/128;
deny all;
}
......
......@@ -12,6 +12,7 @@ upstream llng_portal_upstream {
server {
listen __PORT__;
listen [::]:__PORT__;
server_name auth.__DNSDOMAIN__;
root __PORTALSITEDIR__;
# Use "lm_app" format to get username in nginx.log (see nginx-lmlog.conf)
......
server {
listen __PORT__;
listen [::]:__PORT__;
server_name test1.__DNSDOMAIN__ test2.__DNSDOMAIN__;
root __TESTDIR__;
......@@ -113,7 +114,8 @@ server {
}
#location = /status {
# allow 127.0.0.1;
# allow 127.0.0.1/8;
# allow ::1/128;
# deny all;
# include /etc/nginx/fastcgi_params;
# fastcgi_pass unix:__FASTCGISOCKDIR__/llng-fastcgi.sock;
......
......@@ -24,7 +24,7 @@ use constant MANAGERSECTION => "manager";
use constant SESSIONSEXPLORERSECTION => "sessionsExplorer";
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(?:S(?:ervice(?:DynamicRegistrationEx(?:portedVar|traClaim)s|MetaDataAuthnContext)|torageOptions)|RPMetaData(?:(?:Option(?:sExtraClaim)?|ExportedVar|Macro)s|Node)|OPMetaData(?:(?:ExportedVar|Option)s|J(?:SON|WKS)|Node))|penIdExportedVars)|s(?:aml(?:S(?:PMetaData(?:(?:ExportedAttribute|Option|Macro)s|Node|XML)|torageOptions)|IDPMetaData(?:(?:ExportedAttribute|Option)s|Node|XML))|essionDataToRemember|laveExportedVars|fExtra)|c(?:as(?:A(?:ppMetaData(?:(?:ExportedVar|Option|Macro)s|Node)|ttributes)|S(?:rvMetaData(?:(?:ExportedVar|Option)s|Node)|torageOptions))|(?:ustom(?:Plugins|Add)Param|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|laveDisplayLogo|howLanguages|slByAjax)|o(?:idc(?:RPMetaDataOptions(?:Re(?:freshToken|quirePKCE)|LogoutSessionRequired|IDTokenForceClaims|BypassConsent|AllowOffline|Public)|ServiceAllow(?:(?:AuthorizationCode|Implicit|Hybrid)Flow|DynamicRegistration)|OPMetaDataOptions(?:(?:CheckJWTSignatur|UseNonc)e|StoreIDToken))|ldNotifFormat)|c(?:a(?:ptcha_(?:register|login|mail)_enabled|sSrvMetaDataOptions(?:Gateway|Renew))|heck(?:User(?:Display(?:Empty(?:Header|Value)s|PersistentInfo))?|State|XSS)|o(?:ntextSwitchingStopWithLogout|mpactConf|rsEnabled)|da)|p(?:ortal(?:Display(?:Re(?:setPassword|gister)|GeneratePassword|PasswordPolicy)|ErrorOn(?:ExpiredSession|MailNotFound)|(?:CheckLogin|Statu)s|OpenLinkInNewWindow|RequireOldPassword|ForceAuthn|AntiFrame)|roxyUseSoap)|l(?:dap(?:(?:Group(?:DecodeSearchedValu|Recursiv)|UsePasswordResetAttribut)e|(?:AllowResetExpired|Set)Password|ChangePasswordAsUser|PpolicyControl|ITDS)|oginHistoryEnabled)|no(?:tif(?:ication(?:Server(?:(?:POS|GE)T|DELETE)?|sExplorer)?|y(?:Deleted|Other))|AjaxHook)|i(?:ssuerDB(?:OpenID(?:Connect)?|SAML|CAS|Get)Activation|mpersonationSkipEmptyValues)|to(?:tp2f(?:UserCan(?:Chang|Remov)eKey|DisplayExistingSecret)|kenUseGlobalStorage)|u(?:se(?:RedirectOn(?:Forbidden|Error)|SafeJail)|2fUserCanRemoveKey|pgradeSession)|br(?:uteForceProtection(?:IncrementalTempo)?|owsersDontStorePassword)|re(?:st(?:(?:Session|Config)Server|ExportSecretKeys)|freshSessions)|(?:mai(?:lOnPasswordChang|ntenanc)|vhostMaintenanc)e|d(?:isablePersistentStorage|biDynamicHashEnabled)|g(?:roupsBeforeMacros|lobalLogoutTimer)|h(?:ideOldPassword|ttpOnly)|yubikey2fUserCanRemoveKey|(?:activeTim|wsdlServ)er|krb(?:RemoveDomain|ByJs))$/;
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)|oap(?:Session|Config)Server|t(?:ayConnecte|orePasswor)d|kipRenewConfirmation|fRemovedUseNotif|laveDisplayLogo|howLanguages|slByAjax)|o(?:idc(?:RPMetaDataOptions(?:Allow(?:PasswordGrant|Offline)|Re(?:freshToken|quirePKCE)|LogoutSessionRequired|IDTokenForceClaims|BypassConsent|Public)|ServiceAllow(?:(?:AuthorizationCode|Implicit|Hybrid)Flow|DynamicRegistration)|OPMetaDataOptions(?:(?:CheckJWTSignatur|UseNonc)e|StoreIDToken))|ldNotifFormat)|p(?:ortal(?:Display(?:Re(?:setPassword|gister)|GeneratePassword|PasswordPolicy)|ErrorOn(?:ExpiredSession|MailNotFound)|(?:CheckLogin|Statu)s|OpenLinkInNewWindow|RequireOldPassword|ForceAuthn|AntiFrame)|roxyUseSoap)|l(?:dap(?:(?:Group(?:DecodeSearchedValu|Recursiv)|UsePasswordResetAttribut)e|(?:AllowResetExpired|Set)Password|ChangePasswordAsUser|PpolicyControl|ITDS)|oginHistoryEnabled)|c(?:a(?:ptcha_(?:register|login|mail)_enabled|sSrvMetaDataOptions(?:Gateway|Renew))|o(?:ntextSwitchingStopWithLogout|mpactConf|rsEnabled)|heck(?:State|User|XSS)|da)|no(?:tif(?:ication(?:Server(?:(?:POS|GE)T|DELETE)?|sExplorer)?|y(?:Deleted|Other))|AjaxHook)|i(?:ssuerDB(?:OpenID(?:Connect)?|SAML|CAS|Get)Activation|mpersonationSkipEmptyValues)|to(?:tp2f(?:UserCan(?:Chang|Remov)eKey|DisplayExistingSecret)|kenUseGlobalStorage)|u(?:se(?:RedirectOn(?:Forbidden|Error)|SafeJail)|2fUserCanRemoveKey|pgradeSession)|br(?:uteForceProtection(?:IncrementalTempo)?|owsersDontStorePassword)|re(?:st(?:(?:Session|Config)Server|ExportSecretKeys)|freshSessions)|(?:mai(?:lOnPasswordChang|ntenanc)|vhostMaintenanc)e|d(?:isablePersistentStorage|biDynamicHashEnabled)|g(?:roupsBeforeMacros|lobalLogoutTimer)|h(?:ideOldPassword|ttpOnly)|yubikey2fUserCanRemoveKey|(?:activeTim|wsdlServ)er|krb(?:RemoveDomain|ByJs))$/;
our @sessionTypes = ( 'remoteGlobal', 'global', 'localSession', 'persistent', 'saml', 'oidc', 'cas' );
......
......@@ -8,7 +8,7 @@ sub defaultValues {
'activeTimer' => 1,
'ADPwdExpireWarning' => 0,
'ADPwdMaxAge' => 0,
'apacheAuthnLevel' => 4,
'apacheAuthnLevel' => 3,
'applicationList' => {
'default' => {
'catname' => 'Default category',
......@@ -36,6 +36,9 @@ sub defaultValues {
'http://auth.example.com/certificateReset',
'certificateResetByMailValidityDelay' => 0,
'checkTime' => 600,
'checkUserDisplayEmptyHeaders' => 0,
'checkUserDisplayEmptyValues' => 0,
'checkUserDisplayPersistentInfo' => 0,
'checkUserHiddenAttributes' => '_loginHistory _session_id hGroups',
'checkUserIdRule' => 1,
'checkXSS' => 1,
......@@ -80,6 +83,9 @@ sub defaultValues {
'failedLoginNumber' => 5,
'favAppsMaxNumber' => 3,
'formTimeout' => 120,
'githubAuthnLevel' => 1,
'githubScope' => 'user:email',
'githubUserField' => 'login',
'globalLogoutRule' => 0,
'globalLogoutTimer' => 1,
'globalStorage' => 'Apache::Session::File',
......@@ -326,6 +332,9 @@ sub defaultValues {
'sfRemovedNotifTitle' => 'Second factor notification',
'sfRequired' => 0,
'showLanguages' => 1,
'singleIP' => 0,
'singleSession' => 0,
'singleUserByIP' => 0,
'slaveAuthnLevel' => 2,
'slaveExportedVars' => {},
'SMTPServer' => '',
......
......@@ -733,7 +733,7 @@ sub sfExtra {
$tmp->{id} = "sfExtra/$mod";
$tmp->{type} = 'sfExtra';
$tmp->{data}->{$_} = $val->{$mod}->{$_}
foreach (qw(type rule logo label));
foreach (qw(type rule logo level label));
my $over = $val->{$mod}->{over} // {};
$tmp->{data}->{over} = [ map { [ $_, $over->{$_} ] } keys %$over ];
push @$res, $tmp;
......
......@@ -27,7 +27,7 @@ our $specialNodeKeys = '(?:(?:(?:saml(?:ID|S)|oidc[OR])P|cas(?:App|Srv))MetaData
our $casAppMetaDataNodeKeys = 'casAppMetaData(?:Options(?:UserAttribut|Servic|Rul)e|(?:ExportedVar|Macro)s)';
our $casSrvMetaDataNodeKeys = 'casSrvMetaData(?:Options(?:ProxiedServices|DisplayName|SortNumber|Gateway|Renew|Icon|Url)|ExportedVars)';
our $oidcOPMetaDataNodeKeys = 'oidcOPMetaData(?:Options(?:C(?:lient(?:Secret|ID)|heckJWTSignature|onfigurationURI)|S(?:toreIDToken|ortNumber|cope)|TokenEndpointAuthMethod|(?:JWKSTimeou|Promp)t|I(?:DTokenMaxAge|con)|U(?:iLocales|seNonce)|Display(?:Name)?|AcrValues|MaxAge)|ExportedVars|J(?:SON|WKS))';
our $oidcRPMetaDataNodeKeys = 'oidcRPMetaData(?:Options(?:A(?:(?:uthorizationCode|ccessToken)Expiration|llowOffline)|I(?:DToken(?:ForceClaims|Expiration|SignAlg)|con)|R(?:e(?:directUris|freshToken|quirePKCE)|ule)|Logout(?:SessionRequired|Type|Url)|P(?:ostLogoutRedirectUris|ublic)|OfflineSessionExpiration|Client(?:Secret|ID)|BypassConsent|DisplayName|ExtraClaims|UserIDAttr)|(?:ExportedVar|Macro)s)';
our $oidcRPMetaDataNodeKeys = 'oidcRPMetaData(?:Options(?:A(?:(?:uthorizationCode|ccessToken)Expiration|llow(?:PasswordGrant|Offline)|dditionalAudiences)|I(?:DToken(?:ForceClaims|Expiration|SignAlg)|con)|R(?:e(?:directUris|freshToken|quirePKCE)|ule)|Logout(?:SessionRequired|Type|Url)|P(?:ostLogoutRedirectUris|ublic)|OfflineSessionExpiration|Client(?:Secret|ID)|BypassConsent|DisplayName|ExtraClaims|UserIDAttr)|(?:ExportedVar|Macro)s)';
our $samlIDPMetaDataNodeKeys = 'samlIDPMetaData(?:Options(?:(?:Check(?:S[LS]OMessageSignatur|Audienc|Tim)|EncryptionMod|UserAttribut|DisplayNam)e|S(?:ignS[LS]OMessage|toreSAMLToken|[LS]OBinding|ortNumber)|A(?:llow(?:LoginFromIDP|ProxiedAuthn)|daptSessionUtime)|Re(?:questedAuthnContext|solutionRule|layStateURL)|Force(?:Authn|UTF8)|I(?:sPassive|con)|NameIDFormat)|ExportedAttributes|XML)';
our $samlSPMetaDataNodeKeys = 'samlSPMetaData(?:Options(?:N(?:ameID(?:SessionKey|Format)|otOnOrAfterTimeout)|S(?:essionNotOnOrAfterTimeout|ignS[LS]OMessage)|(?:CheckS[LS]OMessageSignatur|OneTimeUs|Rul)e|En(?:ableIDPInitiatedURL|cryptionMode)|ForceUTF8)|(?:ExportedAttribute|Macro)s|XML)';
our $virtualHostKeys = '(?:vhost(?:A(?:uthnLevel|liases)|(?:Maintenanc|Typ)e|ServiceTokenTTL|Https|Port)|(?:exportedHeader|locationRule)s|post)';
......@@ -42,6 +42,7 @@ our $authParameters = {
dbiParams => [qw(dbiAuthnLevel dbiExportedVars dbiAuthChain dbiAuthUser dbiAuthPassword dbiUserChain dbiUserUser dbiUserPassword dbiAuthTable dbiUserTable dbiAuthLoginCol dbiAuthPasswordCol dbiPasswordMailCol userPivot dbiAuthPasswordHash dbiDynamicHashEnabled dbiDynamicHashValidSchemes dbiDynamicHashValidSaltedSchemes dbiDynamicHashNewPasswordScheme)],
demoParams => [qw(demoExportedVars)],
facebookParams => [qw(facebookAuthnLevel facebookExportedVars facebookAppId facebookAppSecret facebookUserField)],
githubParams => [qw(githubAuthnLevel githubClientID githubClientSecret githubUserField githubScope)],
gpgParams => [qw(gpgAuthnLevel gpgDb)],
kerberosParams => [qw(krbAuthnLevel krbKeytab krbByJs krbRemoveDomain)],
ldapParams => [qw(ldapAuthnLevel ldapExportedVars ldapServer ldapPort ldapBase managerDn managerPassword ldapTimeout ldapVersion ldapRaw LDAPFilter AuthLDAPFilter mailLDAPFilter ldapSearchDeref ldapGroupBase ldapGroupObjectClass ldapGroupAttributeName ldapGroupAttributeNameUser ldapGroupAttributeNameSearch ldapGroupDecodeSearchedValue ldapGroupRecursive ldapGroupAttributeNameGroup ldapPpolicyControl ldapSetPassword ldapChangePasswordAsUser ldapPwdEnc ldapUsePasswordResetAttribute ldapPasswordResetAttribute ldapPasswordResetAttributeValue ldapAllowResetExpiredPassword ldapITDS)],
......
......@@ -72,6 +72,7 @@ sub encodings { $_[0]->env->{HTTP_ACCEPT_ENCODING} }
sub languages { $_[0]->env->{HTTP_ACCEPT_LANGUAGE} }
sub authorization { $_[0]->env->{HTTP_AUTHORIZATION} }
sub hostname { $_[0]->env->{HTTP_HOST} }
sub origin { $_[0]->env->{HTTP_ORIGIN} }
sub referer { $_[0]->env->{REFERER} }
sub query_string { $_[0]->env->{QUERY_STRING} }
......
......@@ -8,7 +8,7 @@ sub run {
my ( $class, $req, $rule, $protection ) = @_;
my $uri = $req->{env}->{REQUEST_URI};
my $cn = $class->tsv->{cookieName};
my ( $id, $ret, $session );
my ( $id, $session );
if ( $uri =~ s/[\?&;]${cn}cda=(\w+)$//oi ) {
if ( $id = $class->fetchId($req)
and $session = $class->retrieveSession( $req, $id ) )
......@@ -48,10 +48,8 @@ sub run {
return $class->REDIRECT;
}
}
( $ret, $session ) =
$class->Lemonldap::NG::Handler::Main::run( $req, $rule, $protection );
return $ret;
return $class->Lemonldap::NG::Handler::Main::run( $req, $rule,
$protection );
}
## @rmethod protected hash getCDAInfos(id)
......
......@@ -172,7 +172,9 @@ sub tplParams {
sub javascript {
my ( $self, $req ) = @_;
my $res = eval {
$self->hLoadedPlugins->{viewer}->diffRule->( $req, $req->{userData} );
$self->hLoadedPlugins->{viewer}
&& $self->hLoadedPlugins->{viewer}
->diffRule->( $req, $req->{userData} );
} || 0;
print STDERR $@ if $@;
my $impPrefix = $self->{impersonationPrefix} || 'real_';
......
......@@ -353,7 +353,7 @@ sub _checkType {
return {
res => "ko",
code => 405,
code => 400,
msg =>
"Invalid input: Type \"$type\" does not exist. Allowed values for type are: \"U2F\", \"TOTP\" or \"UBK\""
}
......
......@@ -20,17 +20,16 @@ sub _isSimpleKeyValueHash {
return 1;
}
sub _setDefaultValues {
my ( $self, $attrs, $rootNode ) = @_;
sub _getDefaultValues {
my ( $self, $rootNode ) = @_;
my @allAttrs = $self->_listAttributes($rootNode);
my $defaultAttrs = Lemonldap::NG::Manager::Build::Attributes::attributes();
my $attrs = {};
foreach $attr (@allAttrs) {
unless ( defined $attrs->{$attr} ) {
$attrs->{$attr} = $defaultAttrs->{$attr}->{default}
if ( defined $defaultAttrs->{$attr}
&& defined $defaultAttrs->{$attr}->{default} );
}
$attrs->{$attr} = $defaultAttrs->{$attr}->{default}
if ( defined $defaultAttrs->{$attr}
&& defined $defaultAttrs->{$attr}->{default} );
}
return $attrs;
......@@ -47,7 +46,7 @@ sub _hasAllowedAttributes {
msg => "Invalid input: Attribute $attribute is not a string."
};
}
unless ( grep { /^$attribute$/ } @allowedAttributes ) {
unless ( grep { $_ eq $attribute } @allowedAttributes ) {
return {
res => "ko",
msg => "Invalid input: Attribute $attribute does not exist."
......@@ -76,4 +75,41 @@ sub _listNodeAttributes {
return @attributes;
}
sub _translateOptionApiToConf {
my ( $self, $optionName, $prefix ) = @_;
# For consistency
$optionName =~ s/^clientId$/clientID/;
return $prefix . "MetaDataOptions" . ( ucfirst $optionName );
}
sub _translateOptionConfToApi {
my ( $self, $optionName ) = @_;
$optionName =~ s/^(\w+)MetaDataOptions//;
$optionName = lcfirst $optionName;
# iDToken looks ugly
$optionName =~ s/^iDToken/IDToken/;
# For consistency
$optionName =~ s/^clientID/clientId/;
return $optionName;
}
sub _getRegexpFromPattern {
my ( $self, $pattern ) = @_;
return unless ( $pattern =~ /[\w\.\-\*]+/ );
# . is allowed, and must be escaped
$pattern =~ s/\./\\\./g;
$pattern =~ s/\*/\.\*/g;
# anchor string, unless * was provided
$pattern = "^$pattern\$" if ( $pattern =~ /\*/ );
return qr/$pattern/;
}
1;
......@@ -7,6 +7,7 @@ package Lemonldap::NG::Manager::Api;
use 5.10.0;
use utf8;
use Mouse;
use Lemonldap::NG::Manager::Conf::Parser;
extends 'Lemonldap::NG::Manager::Api::Common';
......@@ -40,9 +41,14 @@ sub findOidcRpByConfKey {
: ( defined $req->params('pattern') ? $req->params('pattern') : undef )
);
return $self->sendError( $req, 'Invalid input: pattern is missing', 405 )
return $self->sendError( $req, 'Invalid input: pattern is missing', 400 )
unless ( defined $pattern );
unless ( $pattern = $self->_getRegexpFromPattern($pattern) ) {
return $self->sendError( $req, 'Invalid input: pattern is invalid',
400 );
}
$self->logger->debug(
"[API] Find OIDC RPs by confKey regexp $pattern requested");
......@@ -67,7 +73,7 @@ sub findOidcRpByClientId {
)
);
return $self->sendError( $req, 'Invalid input: clientId is missing', 405 )
return $self->sendError( $req, 'Invalid input: clientId is missing', 400 )
unless ( defined $clientId );
$self->logger->debug("[API] Find OIDC RPs by clientId $clientId requested");
......@@ -87,15 +93,31 @@ sub addOidcRp {
my ( $self, $req ) = @_;
my $add = $req->jsonBodyToObj;
return $self->sendError( $req, "Invalid input: " . $req->error, 405 )
return $self->sendError( $req, "Invalid input: " . $req->error, 400 )
unless ($add);
return $self->sendError( $req, 'Invalid input: confKey is missing', 405 )
return $self->sendError( $req, 'Invalid input: confKey is missing', 400 )
unless ( defined $add->{confKey} );
return $self->sendError( $req, 'Invalid input: clientId is missing', 405 )
return $self->sendError( $req, 'Invalid input: confKey is not a string',
400 )
if ( ref $add->{confKey} );
return $self->sendError( $req, 'Invalid input: clientId is missing', 400 )
unless ( defined $add->{clientId} );
return $self->sendError( $req, 'Invalid input: clientId is not a string',
400 )
if ( ref $add->{clientId} );
return $self->sendError( $req, 'Invalid input: redirectUris is missing',
400 )
unless ( defined $add->{redirectUris} );
return $self->sendError( $req,
'Invalid input: redirectUris must be an array', 400 )
unless ( ref( $add->{redirectUris} ) eq "ARRAY" );
$self->logger->debug(
"[API] Add OIDC RP with confKey $add->{confKey} and clientId $add->{clientId} requested"
);
......@@ -106,25 +128,29 @@ sub addOidcRp {
return $self->sendError(
$req,
"Invalid input: An OIDC RP with confKey $add->{confKey} already exists",
405
409
) if ( defined $self->_getOidcRpByConfKey( $conf, $add->{confKey} ) );
return $self->sendError(
$req,
"Invalid input: An OIDC RP with clientId $add->{clientId} already exists",
405
409
) if ( defined $self->_getOidcRpByClientId( $conf, $add->{clientId} ) );
$add->{options} = {} unless ( defined $add->{options} );
$add->{options}->{oidcRPMetaDataOptionsClientID} = $add->{clientId};
$add->{options}->{clientId} = $add->{clientId};
$add->{options}->{redirectUris} = $add->{redirectUris};
my $res = $self->_pushOidcRp( $conf, $add->{confKey}, $add, 1 );
return $self->sendError( $req, $res->{msg}, 405 )
return $self->sendError( $req, $res->{msg}, 400 )
unless ( $res->{res} eq 'ok' );
return $self->sendJSONresponse( $req,
{ message => "Successful operation" } );
return $self->sendJSONresponse(
$req,
{ message => "Successful operation" },
code => 201
);
}
sub updateOidcRp {
......@@ -134,7 +160,7 @@ sub updateOidcRp {
my $update = $req->jsonBodyToObj;
return $self->sendError( $req, "Invalid input: " . $req->error, 405 )
return $self->sendError( $req, "Invalid input: " . $req->error, 400 )
unless ($update);
$self->logger->debug(
......@@ -154,16 +180,15 @@ sub updateOidcRp {
# check if new clientID exists already
my $res = $self->_isNewOidcRpClientIdUnique( $conf, $confKey, $update );
return $self->sendError( $req, $res->{msg}, 405 )
return $self->sendError( $req, $res->{msg}, 409 )
unless ( $res->{res} eq 'ok' );
$res = $self->_pushOidcRp( $conf, $confKey, $update, 0 );
return $self->sendError( $req, $res->{msg}, 405 )
return $self->sendError( $req, $res->{msg}, 400 )
unless ( $res->{res} eq 'ok' );
return $self->sendJSONresponse( $req,
{ message => "Successful operation" } );
return $self->sendJSONresponse( $req, undef, code => 204 );
}
sub replaceOidcRp {
......@@ -173,11 +198,31 @@ sub replaceOidcRp {
my $replace = $req->jsonBodyToObj;
return $self->sendError( $req, "Invalid input: " . $req->error, 405 )
return $self->sendError( $req, "Invalid input: " . $req->error, 400 )
unless ($replace);
return $self->sendError( $req, 'Invalid input: clientId is missing', 405 )
return $self->sendError( $req, 'Invalid input: confKey is missing', 400 )
unless ( defined $replace->{confKey} );
return $self->sendError( $req, 'Invalid input: confKey is not a string',
400 )
if ( ref $replace->{confKey} );
return $self->sendError( $req, 'Invalid input: clientId is missing', 400 )
unless ( defined $replace->{clientId} );
return $self->sendError( $req, 'Invalid input: clientId is not a string',
400 )
if ( ref $replace->{clientId} );
return $self->sendError( $req, 'Invalid input: redirectUris is missing',
400 )
unless ( defined $replace->{redirectUris} );
return $self->sendError( $req,
'Invalid input: redirectUris must be an array', 400 )
unless ( ref( $replace->{redirectUris} ) eq "ARRAY" );
$self->logger->debug(
"[API] OIDC RP $confKey configuration replace requested");
......@@ -192,15 +237,18 @@ sub replaceOidcRp {
# check if new clientID exists already
my $res = $self->_isNewOidcRpClientIdUnique( $conf, $confKey, $replace );
return $self->sendError( $req, $res->{msg}, 405 )
return $self->sendError( $req, $res->{msg}, 409 )
unless ( $res->{res} eq 'ok' );
$replace->{options} = {} unless ( defined $replace->{options} );
$replace->{options}->{clientId} = $replace->{clientId};
$replace->{options}->{redirectUris} = $replace->{redirectUris};
$res = $self->_pushOidcRp( $conf, $confKey, $replace, 1 );
return $self->sendError( $req, $res->{msg}, 405 )
return $self->sendError( $req, $res->{msg}, 400 )
unless ( $res->{res} eq 'ok' );
return $self->sendJSONresponse( $req,
{ message => "Successful operation" } );
return $self->sendJSONresponse( $req, undef, code => 204 );
}
sub deleteOidcRp {
......@@ -222,12 +270,12 @@ sub deleteOidcRp {
delete $conf->{oidcRPMetaDataOptions}->{$confKey};
delete $conf->{oidcRPMetaDataExportedVars}->{$confKey};
delete $conf->{oidcRPMetaDataOptionsExtraClaims}->{$confKey};
delete $conf->{oidcRPMetaDataMacros}->{$confKey};
# Save configuration
$self->_confAcc->saveConf($conf);
return $self->sendJSONresponse( $req,
{ message => "Successful operation" } );
return $self->sendJSONresponse( $req, undef, code => 204 );
}
sub _getOidcRpByConfKey {
......@@ -244,16 +292,37 @@ sub _getOidcRpByConfKey {
my $exportedVars = $conf->{oidcRPMetaDataExportedVars}->{$confKey};
# Get extra claim
my $extraClaim = $conf->{oidcRPMetaDataOptionsExtraClaims}->{$confKey};
my $extraClaims = $conf->{oidcRPMetaDataOptionsExtraClaims}->{$confKey};
# Get macros
my $macros = $conf->{oidcRPMetaDataMacros}->{$confKey} || {};
# Get options
my $options = $conf->{oidcRPMetaDataOptions}->{$confKey};
my $options = {};
for
my $configOption ( keys %{ $conf->{oidcRPMetaDataOptions}->{$confKey} } )
{
# redirectUris is handled as an array
if ( $configOption eq "oidcRPMetaDataOptionsRedirectUris" ) {
$options->{ $self->_translateOptionConfToApi($configOption) } = [
split(
/\s+/,
$conf->{oidcRPMetaDataOptions}->{$confKey}->{$configOption}
)
];
}
else {
$options->{ $self->_translateOptionConfToApi($configOption) } =
$conf->{oidcRPMetaDataOptions}->{$confKey}->{$configOption};
}
}
return {