Merge branch 'v2.0'

This commit is contained in:
Yadd 2021-07-09 12:08:29 +02:00
commit 6c4a5b911c
55 changed files with 2818 additions and 319 deletions

View File

@ -300,7 +300,7 @@ Options
default value is one minute.
- **ID Token expiration**: Expiration time of ID Tokens. The default
value is one hour.
- **Access token expiration** (since version ``2.0.12``): Expiration time
- **Access token expiration**: Expiration time
of Access Tokens. The default value is one hour.
- **Offline session expiration**: This sets the lifetime of the
refresh token obtained with the **offline_access** scope. The
@ -311,8 +311,8 @@ Options
- **ID Token signature algorithm**: Select one of the available public key
(RSXXX) or HMAC (HSXXX) based signature algorithms
- **Access Token signature algorithm**: Select one of the available public
key signature algorithms
- **Access Token signature algorithm** (since version ``2.0.12``): Select
one of the available public key signature algorithms
- **Userinfo response format** (since version ``2.0.12``): By default,
UserInfo is returned as a simple JSON object. You can also choose to
return it as a JWT, using one of the available signature algorithms.

View File

@ -42,7 +42,7 @@ Name Comment Example
======================== ================================= ===============================
**ldapServer** URI of the server ldap://localhost
**ldapConfBase** DN of sessions branch ou=sessions,dc=example,dc=com
**ldapBindDN** Connection login cn=admin,dc=example,dc=password
**ldapBindDN** Connection login cn=admin,dc=example,dc=dom
**ldapBindPassword** Connection password secret
======================== ================================= ===============================

View File

@ -7,11 +7,13 @@ is the faster shareable session backend
Setup
-----
Install and launch a `Redis server <http://code.google.com/p/redis/>`__.
Install and launch a `Redis server <https://redis.io/>`__.
Install
`Apache::Session::Browseable::Redis <https://metacpan.org/pod/Apache::Session::Redis>`__
`Apache::Session::Browseable::Redis <https://metacpan.org/pod/Apache::Session::Browseable::Redis>`__
Perl module.
With Sentinel, make sure you are using at least version 1.3.8 of ``Apache::Session::Browseable``, this might require installing it from Debian Backports or CPAN.
In the manager: set
`Apache::Session::Browseable::Redis <https://metacpan.org/pod/Apache::Session::Browseable::Redis>`__
in ``General parameters`` » ``Sessions`` » ``Session storage`` »
@ -28,6 +30,7 @@ Name Comment Example
**server** Redis server @ IP:PORT 127.0.0.1:6379
**sock** Redis server @ unix socket unix:/path/to/redis.sock
**sentinels** Redis sentinels list 127.0.0.1:26379,127.0.0.2:26379,127.0.0.3:26379
**service** Sentinel service name mymaster
**password** password (== requirepass) ChangeMe
**select** Redis DB 1
**Index** Fields to index refer to :ref:`fieldstoindex`

View File

@ -332,6 +332,9 @@ General
- **Send mail on password change**: send a mail if the password is
changed from the Menu, or from forced password reset (LDAP password
policy)
- **Allow to display password**: if enabled, a small icon in the password
field is added and when users click on it, the password value is
revealed. Disabled by default.
Password Policy
~~~~~~~~~~~~~~~

View File

@ -92,6 +92,53 @@ Then run the script:
/usr/share/lemonldap-ng/bin/importMetadataRenater -m https://metadata.federation.renater.fr/renater/main/main-idps-renater-metadata.xml -r -i "idp-renater-" -s "sp-renater-"
The script provide the following options
* -i (--idpconfprefix): Prefix used to set IDP configuration key
* -h (--help): print this message
* -m (--metadata): URL of metadata document
* -s (--spconfprefix): Prefix used to set SP configuration key
* --ignore-sp: ignore SP maching this entityID (can be specified multiple times)
* --ignore-idp: ignore IdP matching this entityID (can be specified multiple times)
* -a (--nagios): output statistics in Nagios format
* -n (--dry-run): print statistics but do not apply changes
* -v (--verbose): increase verbosity of output
* -r (--remove): remove provider from LemonLDAP::NG if it does not appear in metadata
Example :
::
/usr/libexec/lemonldap-ng/bin/importMetadata -m https://pub.federation.renater.fr/metadata/renater/main/main-sps-renater-metadata.xml -s "sp-fed-prd" -c https://pub.federation.renater.fr/metadata/certs/renater-metadata-signing-cert-2016.pem -bs https://test-sp.federation.renater.fr -r -v -d
This command will
* fetch all SPs metadata from renater
* set a prefix to entity stored inside LemonLdap::NG
* disable local modification of SP https://test-sp.federation.renater.fr
* remove local SPs wich didn't exist anymore in Federation metadata
* show only all modifications to apply
The output is the following :
::
...
Update SP https://www-iuem.univ-brest.fr/sp in configuration
Attribute mail (urn:oid:0.9.2342.19200300.100.1.3) requested by SP https://gesper.ad.bnu.fr/shibboleth
Attribute eduPersonPrimaryAffiliation (urn:oid:1.3.6.1.4.1.5923.1.1.1.5) requested by SP https://gesper.ad.bnu.fr/shibboleth
Attribute eduPersonPrincipalName (urn:oid:1.3.6.1.4.1.5923.1.1.1.6) requested by SP https://gesper.ad.bnu.fr/shibboleth
Attribute displayName (urn:oid:2.16.840.1.113730.3.1.241) requested by SP https://gesper.ad.bnu.fr/shibboleth
Update SP https://gesper.ad.bnu.fr/shibboleth in configuration
[INFO] Dry-run mod no EntityID inserted
[IDP] Found: 0 Updated: 0 Created: 0 Removed: 0 Rejected: 0 Ignored: 0
[SP] Found: 1248 Updated: 1240 Created: 0 Removed: 0 Rejected: 7 Ignored: 1
With "-n" options you could get a "nagios like" output with metrics :
::
/usr/libexec/lemonldap-ng/bin/importMetadataFedRenater -m https://pub.federation.renater.fr/metadata/renater/main/main-sps-renater-metadata.xml -s "sp-fed-prd" -c https://pub.federation.renater.fr/metadata/certs/renater-metadata-signing-cert-2016.pem -bs https://test-sp.federation.renater.fr -r -d -n
Metadata loaded inside Conf: [DRY-RUN]|idp_found=0, idp_updated=0, idp_created=0, idp_removed=0, idp_rejected=0, idp_ignored=0, sp_found=1248, sp_updated=1240, sp_created=0, sp_removed=0, sp_rejected=7, sp_ignored=1
.. attention::

View File

@ -4,7 +4,7 @@ SQL configuration backends
There is 2 types of SQL configuration backends for LemonLDAP::NG:
- **CDBI**: very simple storage (recommended)
- **RDBI**: triple store storage
- **RDBI**: triple store storage (not recommended)
.. tip::
@ -50,6 +50,16 @@ Use database to create table:
use lemonldap-ng
CDBI
^^^^
.. code-block:: sql
CREATE TABLE lmConfig (
cfgNum int not null primary key,
data longtext
);
RDBI
^^^^
@ -62,16 +72,6 @@ RDBI
PRIMARY KEY (cfgNum,field)
);
CDBI
^^^^
.. code-block:: sql
CREATE TABLE lmConfig (
cfgNum int not null primary key,
data longtext
);
Grant access
~~~~~~~~~~~~
@ -107,7 +107,7 @@ file (section configuration):
.. code-block:: ini
[configuration]
type = RDBI
type = CDBI
dbiChain = DBI:mysql:database=lemonldap-ng;host=1.2.3.4
dbiUser = lemonldaprw
dbiPassword = mypassword
@ -155,6 +155,18 @@ Use database to create table:
.. _rdbi-1:
CDBI
^^^^
.. code-block:: sql
CREATE TABLE lmConfig (
cfgnum integer not null primary key,
data text
);
.. _connection-settings-1:
RDBI
^^^^
@ -169,18 +181,6 @@ RDBI
.. _cdbi-1:
CDBI
^^^^
.. code-block:: sql
CREATE TABLE lmConfig (
cfgnum integer not null primary key,
data text
);
.. _connection-settings-1:
Connection settings
-------------------
@ -190,7 +190,7 @@ file (section configuration):
.. code-block:: ini
[configuration]
type = RDBI
type = CDBI
dbiChain = DBI:Pg:database=lemonldap-ng;host=1.2.3.4
dbiUser = lemonldaprw
dbiPassword = mypassword

View File

@ -341,7 +341,7 @@ Backend Shareable Comment
Selected by default during installation.
:doc:`YAML<yamlconfbackend>` |new| Same as :doc:`File<fileconfbackend>` but in YAML format
instead of JSON
:doc:`SQL (RDBI/CDBI)<sqlconfbackend>` ✔ **Recommended for large-scale systems**. Prefer CDBI.
:doc:`SQL (CDBI/RDBI)<sqlconfbackend>` ✔ **Recommended for large-scale systems**. Prefer CDBI.
:doc:`LDAP<ldapconfbackend>`
:doc:`MongoDB<mongodbconfbackend>`
:doc:`SOAP<soapconfbackend>` |deprecated| ✔ Proxy backend to be used in conjunction with another

View File

@ -41,6 +41,7 @@ Because of this bug, the created sessions may never be purged by the `purgeCentr
In order to detect these sessions, you can run the following command:
::
lemonldap-ng-sessions search --where _session_kind=SSO --select _session_id --select _utime | \
jq -r '. | map(select(._utime == null)) | map(._session_id) | join ("\n")'

View File

@ -1,4 +1,4 @@
.\" Automatically generated by Pod::Man 4.11 (Pod::Simple 3.35)
.\" Automatically generated by Pod::Man 4.14 (Pod::Simple 3.40)
.\"
.\" Standard preamble:
.\" ========================================================================
@ -133,7 +133,7 @@
.\" ========================================================================
.\"
.IX Title "llng-fastcgi-server 8"
.TH llng-fastcgi-server 8 "2021-04-30" "perl v5.30.0" "User Contributed Perl Documentation"
.TH llng-fastcgi-server 8 "2021-07-09" "perl v5.32.1" "User Contributed Perl Documentation"
.\" For nroff, turn off justification. Always turn off hyphenation; it makes
.\" way too many mistakes in technical documents.
.if n .ad l

View File

@ -104,6 +104,9 @@ require Lemonldap::NG::Handler::Server::Nginx;
$_apps{handler} = Lemonldap::NG::Handler::Server::Nginx->run( {} );
my $app = sub {
$SIG{'PIPE'} = sub {
print STDERR "Got a PIPE signal";
};
my $type = $_[0]->{LLTYPE} || 'handler';
return $_apps{$type}->(@_) if ( defined $_apps{$type} );
if ( defined $builder{$type} ) {

View File

@ -103,7 +103,7 @@ checkTime = 1
;confTimeout = 5
; GLOBAL CONFIGURATION ACCESS TYPE
; (File, REST, SOAP, RDBI/CDBI, LDAP, YAMLFile)
; (File, REST, SOAP, CDBI/RDBI, LDAP, YAMLFile)
; Set here the parameters needed to access to LemonLDAP::NG configuration.
; You have to set "type" to one of the followings :
;
@ -114,11 +114,11 @@ checkTime = 1
; ; Optimize JSON for readability instead of performance
; prettyPrint = 1
;
; * RDBI/CDBI : you have to set 'dbiChain' (required) and 'dbiUser' and 'dbiPassword'
; * CDBI/RDBI : you have to set 'dbiChain' (required) and 'dbiUser' and 'dbiPassword'
; if needed. Example:
;
; type = RDBI
; ;type = CDBI
; type = CDBI
; ;type = RDBI
; dbiChain = DBI:MariaDB:database=lemonldap-ng;host=1.2.3.4
; dbiUser = lemonldap
; dbiPassword = password
@ -218,7 +218,7 @@ languages = en, fr, vi, it, ar, de, fi, tr, pl, zh_TW, es
; Override error codes
;error_0 = You are well authenticated!
; Custom template parameters
; For example to use <TMPL_VAR NAME="myparam">
; For example to use <TMPL_VAR NAME="myparam">
;tpl_myparam = test
; COMBINATION FORMS

View File

@ -119,6 +119,7 @@ sub saveConf {
my ( $self, $conf, %args ) = @_;
my $last = $self->lastCfg;
return UNKNOWN_ERROR if $last < 1;
# If configuration was modified, return an error
if ( not $args{force} ) {
@ -395,6 +396,7 @@ sub getDBConf {
: $a[0];
}
my $conf = $self->load( $args->{cfgNum} );
return undef if $conf == "-1";
$msg .= "Get configuration $conf->{cfgNum}.\n"
if ( defined $conf->{cfgNum} );
return $conf;
@ -413,7 +415,11 @@ sub _launch {
alarm 0;
die $@ if $@;
};
$msg .= $@ if $@;
if($@) {
$msg .= $@;
print STDERR "MSG $msg\n";
return undef;
}
return wantarray ? (@res) : $res[0];
}

View File

@ -16,32 +16,22 @@ sub store {
my $req;
my $lastCfg = $self->lastCfg;
$req = $self->_dbh->prepare(
"INSERT INTO $self->{dbiTable} (cfgNum,field,value) VALUES (?,?,?)");
if ( $lastCfg == $cfgNum ) {
$req = $self->_dbh->prepare(
"UPDATE $self->{dbiTable} SET field=?, value=? WHERE cfgNum=? AND field=?"
);
}
else {
$req = $self->_dbh->prepare(
"INSERT INTO $self->{dbiTable} (cfgNum,field,value) VALUES (?,?,?)"
);
}
_delete($self,$cfgNum) if $lastCfg == $cfgNum;
unless ($req) {
$self->logError;
return UNKNOWN_ERROR;
}
while ( my ( $k, $v ) = each %$fields ) {
my @execValues;
if ( $lastCfg == $cfgNum ) {
@execValues = ( $k, $v, $cfgNum, $k );
}
else { @execValues = ( $cfgNum, $k, $v ); }
my @execValues = ( $cfgNum, $k, $v );
my $execute;
eval { $execute = $req->execute(@execValues); };
print STDERR $@ if $@;
unless ($execute) {
$self->logError;
_delete( $self, $cfgNum ) if $lastCfg != $cfgNum;
$self->_dbh->do("ROLLBACK");
return UNKNOWN_ERROR;
}
@ -55,7 +45,7 @@ sub load {
my $sth =
$self->_dbh->prepare(
"SELECT field,value from " . $self->{dbiTable} . " WHERE cfgNum=?" )
or $self->logError;
or $self->logError;
$sth->execute($cfgNum) or $self->logError;
my ( $res, @row );
while ( @row = $sth->fetchrow_array ) {
@ -70,5 +60,11 @@ sub load {
return $self->unserialize($res);
}
sub _delete {
my ( $self, $cfgNum ) = @_;
my $r =
$self->_dbh->prepare("DELETE FROM $self->{dbiTable} where cfgNum=?");
$r->execute($cfgNum);
}
1;
__END__

View File

@ -31,7 +31,7 @@ use constant DEFAULTCONFBACKENDOPTIONS => (
);
our $hashParameters = qr/^(?:(?:l(?:o(?:ca(?:lSessionStorageOption|tionRule)|goutService)|dapExportedVar|wp(?:Ssl)?Opt)|(?:(?:d(?:emo|bi)|webID)ExportedVa|exported(?:Heade|Va)|issuerDBGetParamete)r|f(?:indUser(?:Exclud|Search)ingAttribute|acebookExportedVar)|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|ScopeRule|Macro)s|Node)|OPMetaData(?:(?:ExportedVar|Option)s|J(?:SON|WKS)|Node))|penIdExportedVars)|c(?:as(?:A(?:ppMetaData(?:(?:ExportedVar|Option|Macro)s|Node)|ttributes)|S(?:rvMetaData(?:(?:ExportedVar|Option)s|Node)|torageOptions))|(?:ustom(?:Plugins|Add)Param|heckUserHiddenHeader|ombModule)s)|s(?:aml(?:S(?:PMetaData(?:(?:ExportedAttribute|Option|Macro)s|Node|XML)|torageOptions)|IDPMetaData(?:(?:ExportedAttribute|Option)s|Node|XML))|essionDataToRemember|laveExportedVars|fExtra)|a(?:(?:daptativeAuthenticationLevelR|ut(?:hChoiceMod|oSigninR))ules|pplicationList)|p(?:ersistentStorageOptions|o(?:rtalSkinRules|st))|v(?:hostOptions|irtualHost)|S(?:MTPTLSOpts|SLVarIf))$/;
our $arrayParameters = qr/^mySessionAuthorizedRWKeys$/;
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)|f(?:RemovedUseNotif|OnlyUpgrade)|kip(?:Upgrade|Renew)Confirmation|oap(?:Session|Config)Server|t(?:ayConnecte|orePasswor)d|laveDisplayLogo|howLanguages|slByAjax)|o(?:idc(?:RPMetaDataOptions(?:A(?:llow(?:(?:ClientCredentials|Password)Grant|Offline)|ccessToken(?:Claims|JWT))|Re(?:freshToken|quirePKCE)|LogoutSessionRequired|IDTokenForceClaims|BypassConsent|Public)|ServiceAllow(?:(?:AuthorizationCode|Implicit|Hybrid)Flow|DynamicRegistration|OnlyDeclaredScopes)|OPMetaDataOptions(?:(?:CheckJWTSignatur|UseNonc)e|StoreIDToken))|ldNotifFormat)|c(?:a(?:sS(?:rvMetaDataOptions(?:Gateway|Renew)|trictMatching)|ptcha_(?:register|login|mail)_enabled)|o(?:ntextSwitching(?:Allowed2fModifications|StopWithLogout)|mpactConf|rsEnabled)|heck(?:DevOps(?:Download)?|State|User|XSS)|rowdsec|da)|p(?:ortal(?:Display(?:Re(?:freshMyRights|setPassword|gister)|CertificateResetByMail|GeneratePassword|PasswordPolicy)|ErrorOn(?:ExpiredSession|MailNotFound)|(?:CheckLogin|Statu)s|OpenLinkInNewWindow|ForceAuthn|AntiFrame)|roxyUseSoap)|l(?:dap(?:(?:G(?:roup(?:DecodeSearchedValu|Recursiv)|etUserBeforePasswordChang)|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)|u(?:se(?:RedirectOn(?:Forbidden|Error)|SafeJail)|2fUserCanRemoveKey|pgradeSession)|re(?:st(?:(?:Password|Session|Config|Auth)Server|ExportSecretKeys)|freshSessions)|br(?:uteForceProtection(?:IncrementalTempo)?|owsersDontStorePassword)|d(?:is(?:ablePersistentStorage|playSessionId)|biDynamicHashEnabled)|(?:mai(?:lOnPasswordChang|ntenanc)|vhostMaintenanc)e|to(?:tp2fUserCanRemoveKey|kenUseGlobalStorage)|g(?:roupsBeforeMacros|lobalLogoutTimer)|a(?:voidAssignment|ctiveTimer)|h(?:ideOldPassword|ttpOnly)|yubikey2fUserCanRemoveKey|krb(?:RemoveDomain|ByJs)|(?:wsdlServ|findUs)er)$/;
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)|f(?:RemovedUseNotif|OnlyUpgrade)|kip(?:Upgrade|Renew)Confirmation|oap(?:Session|Config)Server|t(?:ayConnecte|orePasswor)d|laveDisplayLogo|howLanguages|slByAjax)|o(?:idc(?:RPMetaDataOptions(?:A(?:llow(?:(?:ClientCredentials|Password)Grant|Offline)|ccessToken(?:Claims|JWT))|Re(?:freshToken|quirePKCE)|LogoutSessionRequired|IDTokenForceClaims|BypassConsent|Public)|ServiceAllow(?:(?:AuthorizationCode|Implicit|Hybrid)Flow|DynamicRegistration|OnlyDeclaredScopes)|OPMetaDataOptions(?:(?:CheckJWTSignatur|UseNonc)e|StoreIDToken))|ldNotifFormat)|p(?:ortal(?:Display(?:Re(?:freshMyRights|setPassword|gister)|CertificateResetByMail|GeneratePassword|PasswordPolicy)|E(?:rrorOn(?:ExpiredSession|MailNotFound)|nablePasswordDisplay)|(?:CheckLogin|Statu)s|OpenLinkInNewWindow|ForceAuthn|AntiFrame)|roxyUseSoap)|c(?:a(?:sS(?:rvMetaDataOptions(?:Gateway|Renew)|trictMatching)|ptcha_(?:register|login|mail)_enabled)|o(?:ntextSwitching(?:Allowed2fModifications|StopWithLogout)|mpactConf|rsEnabled)|heck(?:DevOps(?:Download)?|State|User|XSS)|rowdsec|da)|l(?:dap(?:(?:G(?:roup(?:DecodeSearchedValu|Recursiv)|etUserBeforePasswordChang)|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)|u(?:se(?:RedirectOn(?:Forbidden|Error)|SafeJail)|2fUserCanRemoveKey|pgradeSession)|re(?:st(?:(?:Password|Session|Config|Auth)Server|ExportSecretKeys)|freshSessions)|br(?:uteForceProtection(?:IncrementalTempo)?|owsersDontStorePassword)|d(?:is(?:ablePersistentStorage|playSessionId)|biDynamicHashEnabled)|(?:mai(?:lOnPasswordChang|ntenanc)|vhostMaintenanc)e|to(?:tp2fUserCanRemoveKey|kenUseGlobalStorage)|g(?:roupsBeforeMacros|lobalLogoutTimer)|a(?:voidAssignment|ctiveTimer)|h(?:ideOldPassword|ttpOnly)|yubikey2fUserCanRemoveKey|krb(?:RemoveDomain|ByJs)|(?:wsdlServ|findUs)er)$/;
our @sessionTypes = ( 'remoteGlobal', 'global', 'localSession', 'persistent', 'saml', 'oidc', 'cas' );

View File

@ -8,6 +8,8 @@ our @EXPORT_OK =
use JSON;
use MIME::Base64 qw/encode_base64 decode_base64/;
our $VERSION = '2.0.12';
# Gets the Access Token session ID embedded in a LLNG-emitted JWT
sub getAccessTokenSessionId {
my ($access_token) = @_;

400
lemonldap-ng-common/scripts/importMetadata Executable file → Normal file
View File

@ -7,16 +7,27 @@ use LWP::UserAgent;
use MIME::Base64;
use XML::LibXML;
sub toEntityIDkey {
my ( $prefix, $entityID ) = @_;
my $entityIDKey = $entityID;
$entityIDKey =~ s/^https?:\/\///;
$entityIDKey =~ s/[^a-zA-Z0-9]/-/g;
$entityIDKey =~ s/-+$//g;
return ( $prefix . $entityIDKey );
}
#==============================================================================
# Get command line options
#==============================================================================
my %opts;
my $result = GetOptions(
\%opts, 'metadata|m=s',
'certificate|c=s', 'verbose|v',
'help|h', 'spconfprefix|s=s',
'idpconfprefix|i=s', 'warning|w',
'remove|r'
\%opts, 'metadata|m=s',
'verbose|v', 'help|h',
'spconfprefix|s=s', 'idpconfprefix|i=s',
'remove|r', 'nagios|a',
'ignore-sp=s@', 'ignore-idp=s@',
'dry-run|n'
);
#==============================================================================
@ -28,14 +39,20 @@ if ( $opts{help} or !$opts{metadata} ) {
print STDERR "Usage: $0 -m <metadata file URL>\n\n";
print STDERR "Options:\n";
print STDERR
"\t-c (--certificate): URL of certificate, to check metadata document signature\n";
print STDERR
"\t-i (--idpconfprefix): Prefix used to set IDP configuration key\n";
print STDERR "\t-h (--help): print this message\n";
print STDERR "\t-m (--metadata): URL of metadata document\n";
print STDERR
"\t-s (--spconfprefix): Prefix used to set SP configuration key\n";
print STDERR "\t-w (--warning): print debug messages\n";
print STDERR
"\t--ignore-sp: ignore SP maching this entityID (can be specified multiple times)\n";
print STDERR
"\t--ignore-idp: ignore IdP matching this entityID (can be specified multiple times)\n";
print STDERR "\t-a (--nagios) : output statistics in Nagios format\n";
print STDERR "\t-n (--dry-run): print statistics but do not apply changes\n";
print STDERR "\t-v (--verbose): increase verbosity of output\n";
print STDERR
"\t-r (--remove): remove provider from LemonLDAP::NG if it does not appear in metadata\n";
exit 1;
}
@ -48,6 +65,7 @@ my $idpConfKeyPrefix = $opts{idpconfprefix} || "idp-";
# Set here attributes that are declared for your SP in the federation
# They will be set as exported attributes for all IDP
#
my $exportedAttributes = {
'cn' => '0;cn',
'eduPersonPrincipalName' => '0;eduPersonAffiliation',
@ -101,16 +119,22 @@ my $idpCounter = {
'updated' => 0,
'created' => 0,
'rejected' => 0,
'removed' => 0
'removed' => 0,
'ignored' => 0
};
my $spCounter = {
'found' => 0,
'updated' => 0,
'created' => 0,
'rejected' => 0,
'removed' => 0
'removed' => 0,
'ignored' => 0,
};
# BlockList initialisation
my @spIgnorelist = @{ $opts{'ignore-sp'} || [] };
my @idpIgnorelist = @{ $opts{'ignore-idp'} || [] };
#==============================================================================
# Main
#==============================================================================
@ -173,33 +197,6 @@ else {
my $dom = XML::LibXML->load_xml( string => $response->decoded_content );
# Check file signature
if ( $opts{certificate} ) {
my $certificate_file = $opts{certificate};
if ( $opts{verbose} ) {
print "Try to download certificate file at $certificate_file\n";
}
my $cert_response = $ua->get($certificate_file);
if ( $cert_response->is_success ) {
if ( $opts{verbose} ) {
print "Certificate file found:\n"
. $cert_response->decoded_content . "\n";
}
}
else {
die $cert_response->status_line;
}
if ( $opts{verbose} ) {
print "Check metadata signature with certificate";
}
# TODO
print STDERR "[WARN] Signature verification not yet implemented\n"
if $opts{warning};
}
# Remove extensions
foreach ( $dom->findnodes('//md:Extensions') ) { $_->unbindNode; }
@ -232,57 +229,64 @@ foreach
my $partner_metadata = $partner->toString;
$partner_metadata =~ s/\n//g;
# Check if entityID already in configuration
if ( defined $idpList->{$entityID} ) {
# Update metadata
$lastConf->{samlIDPMetaDataXML}->{ $idpList->{$entityID} }
->{samlIDPMetaDataXML} = $partner_metadata;
# Update attributes
$lastConf->{samlIDPMetaDataExportedAttributes}
->{ $idpList->{$entityID} } = $exportedAttributes;
# Update options
$lastConf->{samlIDPMetaDataOptions}->{ $idpList->{$entityID} }
= $idpOptions;
# test if IDP entityID is inside the block list
if ( grep { $entityID eq $_ } @idpIgnorelist ) {
if ( $opts{verbose} ) {
print "Update IDP $entityID in configuration\n";
print "IDP $entityID won't be update/added \n";
}
$idpCounter->{updated}++;
$idpCounter->{ignored}++;
}
else {
# Create a new partner
my $entityIDKey = $entityID;
$entityIDKey =~ s/^https?:\/\///;
$entityIDKey =~ s/[^a-zA-Z0-9]/-/g;
$entityIDKey =~ s/-+$//g;
my $confKey = $idpConfKeyPrefix . $entityIDKey;
# Check if entityID already in configuration
if ( defined $idpList->{$entityID} ) {
# Metadata
$lastConf->{samlIDPMetaDataXML}->{$confKey}
->{samlIDPMetaDataXML} = $partner_metadata;
# Update metadata
$lastConf->{samlIDPMetaDataXML}->{ $idpList->{$entityID} }
->{samlIDPMetaDataXML} = $partner_metadata;
# Attributes
$lastConf->{samlIDPMetaDataExportedAttributes}->{$confKey} =
$exportedAttributes;
# Update attributes
$lastConf->{samlIDPMetaDataExportedAttributes}
->{ $idpList->{$entityID} } = $exportedAttributes;
# Options
$lastConf->{samlIDPMetaDataOptions}->{$confKey} = $idpOptions;
# Update options
$lastConf->{samlIDPMetaDataOptions}
->{ $idpList->{$entityID} } = $idpOptions;
if ( $opts{verbose} ) {
print
"Declare new IDP $entityID (configuration key $confKey)\n";
if ( $opts{verbose} ) {
print "Update IDP $entityID in configuration\n";
}
$idpCounter->{updated}++;
}
else {
# Create a new partner
my $confKey = toEntityIDkey( $idpConfKeyPrefix, $entityID );
# Metadata
$lastConf->{samlIDPMetaDataXML}->{$confKey}
->{samlIDPMetaDataXML} = $partner_metadata;
# Attributes
$lastConf->{samlIDPMetaDataExportedAttributes}->{$confKey}
= $exportedAttributes;
# Options
$lastConf->{samlIDPMetaDataOptions}->{$confKey} =
$idpOptions;
if ( $opts{verbose} ) {
print
"Declare new IDP $entityID (configuration key $confKey)\n";
}
$idpCounter->{created}++;
}
$idpCounter->{created}++;
}
}
else {
print STDERR
"[WARN] IDP $entityID is not compatible with SAML 2.0, it will not be imported.\n"
if $opts{warning};
if $opts{verbose};
$idpCounter->{rejected}++;
}
}
@ -346,57 +350,78 @@ foreach
my $partner_metadata = $partner->toString;
$partner_metadata =~ s/\n//g;
# Check if entityID already in configuration
if ( defined $spList->{$entityID} ) {
# Update metadata
$lastConf->{samlSPMetaDataXML}->{ $spList->{$entityID} }
->{samlSPMetaDataXML} = $partner_metadata;
# Update attributes
$lastConf->{samlSPMetaDataExportedAttributes}
->{ $spList->{$entityID} } = $requestedAttributes;
# Update options
$lastConf->{samlSPMetaDataOptions}->{ $spList->{$entityID} } =
$spOptions;
# test if IDP entityID is inside the block list
if ( grep { $entityID eq $_ } @spIgnorelist ) {
if ( $opts{verbose} ) {
print "Update SP $entityID in configuration\n";
print "SP $entityID won't be update/added \n";
}
$spCounter->{updated}++;
$spCounter->{ignored}++;
}
else {
# Create a new partner
my $entityIDKey = $entityID;
$entityIDKey =~ s/^https?:\/\///;
$entityIDKey =~ s/[^a-zA-Z0-9]/-/g;
$entityIDKey =~ s/-+$//g;
my $confKey = $spConfKeyPrefix . $entityIDKey;
# Check if entityID already in configuration
if ( defined $spList->{$entityID} ) {
# Metadata
$lastConf->{samlSPMetaDataXML}->{$confKey}->{samlSPMetaDataXML}
= $partner_metadata;
# Update metadata
$lastConf->{samlSPMetaDataXML}->{ $spList->{$entityID} }
->{samlSPMetaDataXML} = $partner_metadata;
# Attributes
$lastConf->{samlSPMetaDataExportedAttributes}->{$confKey} =
$requestedAttributes;
# Update attributes
$lastConf->{samlSPMetaDataExportedAttributes}
->{ $spList->{$entityID} } = $requestedAttributes;
# Options
$lastConf->{samlSPMetaDataOptions}->{$confKey} = $spOptions;
# Update options
# $lastConf->{samlSPMetaDataOptions}->{ $spList->{$entityID} } =
# $spOptions;
# FIX AGA
$lastConf->{samlSPMetaDataOptions}->{ $spList->{$entityID} }
= { %{$spOptions} };
if ( $opts{verbose} ) {
print
"Declare new SP $entityID (configuration key $confKey)\n";
if ( $opts{verbose} ) {
print "Update SP $entityID in configuration\n";
}
$spCounter->{updated}++;
}
$spCounter->{created}++;
else {
# Create a new partner
my $confKey = toEntityIDkey( $spConfKeyPrefix, $entityID );
# Metadata
$lastConf->{samlSPMetaDataXML}->{$confKey}
->{samlSPMetaDataXML} = $partner_metadata;
# Attributes
$lastConf->{samlSPMetaDataExportedAttributes}->{$confKey} =
$requestedAttributes;
# Options
# $lastConf->{samlSPMetaDataOptions}->{$confKey} = $spOptions;
# FIX AGA
$lastConf->{samlSPMetaDataOptions}->{$confKey} =
{ %{$spOptions} };
if ( $opts{verbose} ) {
print
"Declare new SP $entityID (configuration key $confKey)\n";
}
$spCounter->{created}++;
}
# handle eduPersonTargetedID
if ( $requestedAttributes->{eduPersonTargetedID} ) {
delete $requestedAttributes->{eduPersonTargetedID};
$lastConf->{samlSPMetaDataOptions}->{ $spList->{$entityID} }
->{samlSPMetaDataOptionsNameIDFormat} = 'persistent';
}
}
}
else {
print STDERR
"[WARN] SP $entityID is not compatible with SAML 2.0, it will not be imported.\n"
if $opts{warning};
if $opts{verbose};
$spCounter->{rejected}++;
}
@ -406,61 +431,130 @@ foreach
# Remove partners
if ( $opts{remove} ) {
foreach ( keys %$idpList ) {
my $idpConfKey = $idpList->{$_};
unless ( defined $mdIdpList->{$_} ) {
delete $lastConf->{samlIDPMetaDataXML}->{$idpConfKey};
delete $lastConf->{samlIDPMetaDataExportedAttributes}
->{$idpConfKey};
delete $lastConf->{samlIDPMetaDataOptions}->{$idpConfKey};
$idpCounter->{removed}++;
if ( $opts{verbose} ) {
print "Remove IDP $idpConfKey\n";
foreach my $entityID ( keys %$idpList ) {
my $idpConfKey = $idpList->{$entityID};
unless ( defined $mdIdpList->{$entityID} ) {
if ( grep { $entityID eq $_ } @idpIgnorelist ) {
$idpCounter->{ignored}++;
if ( $opts{verbose} ) {
print "IDP $idpConfKey won't be deleted \n";
}
}
else {
delete $lastConf->{samlIDPMetaDataXML}->{$idpConfKey};
delete $lastConf->{samlIDPMetaDataExportedAttributes}
->{$idpConfKey};
delete $lastConf->{samlIDPMetaDataOptions}->{$idpConfKey};
$idpCounter->{removed}++;
if ( $opts{verbose} ) {
print "Remove IDP $idpConfKey\n";
}
}
}
}
foreach ( keys %$spList ) {
my $spConfKey = $spList->{$_};
unless ( defined $mdSpList->{$_} ) {
delete $lastConf->{samlSPMetaDataXML}->{$spConfKey};
delete $lastConf->{samlSPMetaDataExportedAttributes}->{$spConfKey};
delete $lastConf->{samlSPMetaDataOptions}->{$spConfKey};
$spCounter->{removed}++;
if ( $opts{verbose} ) {
print "Remove SP $spConfKey\n";
foreach my $entityID ( keys %$spList ) {
my $spConfKey = $spList->{$entityID};
unless ( defined $mdSpList->{$entityID} ) {
if ( grep { $entityID eq $_ } @spIgnorelist ) {
$spCounter->{ignored}++;
if ( $opts{verbose} ) {
print "SP $spConfKey won't be deleted \n";
}
}
else {
delete $lastConf->{samlSPMetaDataXML}->{$spConfKey};
delete $lastConf->{samlSPMetaDataExportedAttributes}
->{$spConfKey};
delete $lastConf->{samlSPMetaDataOptions}->{$spConfKey};
$spCounter->{removed}++;
if ( $opts{verbose} ) {
print "Remove SP $spConfKey\n";
}
}
}
}
}
# Register configuration
my $numConf = $conf->saveConf( $lastConf, ( cfgNumFixed => 1 ) );
my $numConf = "DRY-RUN";
my $exitCode = 0;
unless ($numConf) {
print "[ERROR] Unable to save configuration\n";
exit 1;
if ( !$opts{'dry-run'} ) {
# Register configuration
if ( $opts{verbose} ) {
print "[INFO] run mod EntityID will be inserted\n";
}
$numConf = $conf->saveConf( $lastConf, ( cfgNumFixed => 1 ) );
if ( $opts{verbose} ) {
print "[OK] Configuration $numConf saved\n";
$exitCode = 0;
}
unless ($numConf) {
print "[ERROR] Unable to save configuration\n";
$exitCode = 1;
}
}
else {
if ( $opts{verbose} ) {
print "[INFO] Dry-run mod no EntityID inserted\n";
}
}
print "[IDP]\tFound: "
. $idpCounter->{found}
. "\tUpdated: "
. $idpCounter->{updated}
. "\tCreated: "
. $idpCounter->{created}
. "\tRemoved: "
. $idpCounter->{removed}
. "\tRejected: "
. $idpCounter->{rejected} . "\n";
print "[SP]\tFound: "
. $spCounter->{found}
. "\tUpdated: "
. $spCounter->{updated}
. "\tCreated: "
. $spCounter->{created}
. "\tRemoved: "
. $spCounter->{removed}
. "\tRejected: "
. $spCounter->{rejected} . "\n";
print "[OK] Configuration $numConf saved\n";
exit 0;
if ( $opts{nagios} ) {
print "Metadata loaded inside Conf: ["
. $numConf
. "]|idp_found="
. $idpCounter->{found}
. ", idp_updated="
. $idpCounter->{updated}
. ", idp_created="
. $idpCounter->{created}
. ", idp_removed="
. $idpCounter->{removed}
. ", idp_rejected="
. $idpCounter->{rejected}
. ", idp_ignored="
. $idpCounter->{ignored}
. ", sp_found="
. $spCounter->{found}
. ", sp_updated="
. $spCounter->{updated}
. ", sp_created="
. $spCounter->{created}
. ", sp_removed="
. $spCounter->{removed}
. ", sp_rejected="
. $spCounter->{rejected}
. ", sp_ignored="
. $spCounter->{ignored} . "\n";
}
else {
print "[IDP]\tFound: "
. $idpCounter->{found}
. "\tUpdated: "
. $idpCounter->{updated}
. "\tCreated: "
. $idpCounter->{created}
. "\tRemoved: "
. $idpCounter->{removed}
. "\tRejected: "
. $idpCounter->{rejected}
. "\tIgnored: "
. $idpCounter->{ignored} . "\n";
print "[SP]\tFound: "
. $spCounter->{found}
. "\tUpdated: "
. $spCounter->{updated}
. "\tCreated: "
. $spCounter->{created}
. "\tRemoved: "
. $spCounter->{removed}
. "\tRejected: "
. $spCounter->{rejected}
. "\tIgnored: "
. $spCounter->{ignored} . "\n";
}
exit $exitCode;

View File

@ -44,7 +44,6 @@ SKIP: {
ok(
$h->_dbh->do(
"CREATE TABLE lmConfig ( cfgNum int not null, field varchar(255) NOT NULL DEFAULT '', value longblob, PRIMARY KEY (cfgNum,field))"
),
'Test database created'
);

View File

@ -2847,6 +2847,10 @@ qr/(?:(?:https?):\/\/(?:(?:(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.]
'default' => 0,
'type' => 'bool'
},
'portalEnablePasswordDisplay' => {
'default' => 0,
'type' => 'bool'
},
'portalErrorOnExpiredSession' => {
'default' => 1,
'type' => 'bool'
@ -3948,6 +3952,9 @@ qr/(?:(?:https?):\/\/(?:(?:(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.]
'sfOnlyUpgrade' => {
'type' => 'bool'
},
'sfRegisterTimeout' => {
'type' => 'int'
},
'sfRemovedMsgRule' => {
'default' => 0,
'type' => 'boolOrExpr'

View File

@ -468,6 +468,7 @@ sub buildPortalConstants() {
printf STDERR $format, $self->portalConstantsFile;
open( F, '>', $self->portalConstantsFile ) or die($!);
my $urire = $RE{URI}{HTTP}{ -scheme=>qr/https?/ }{-keep};
$urire =~ s/([\$\@])/\\$1/g;
my $content = <<EOF;
# This file is generated by $module. Don't modify it by hand
package Lemonldap::NG::Portal::Main::Constants;

View File

@ -1241,6 +1241,11 @@ sub attributes {
type => 'bool',
documentation => 'Display link to refresh the user session',
},
portalEnablePasswordDisplay => {
default => 0,
type => 'bool',
documentation => 'Allow to display password in login form',
},
# Cookies
cookieExpiration => {
@ -3238,10 +3243,14 @@ sub attributes {
sfRemovedNotifMsg => {
type => 'text',
default =>
'_removedSF_ expired second factor(s) has/have been removed (_nameSF_)!',
'_removedSF_ expired second factor(s) has/have been removed (_nameSF_)!',
help => 'secondfactor.html',
documentation => 'Notification message',
},
sfRegisterTimeout => {
type => 'int',
documentation => 'Timeout for 2F registration process',
},
available2F => {
type => 'text',
default => 'UTOTP,TOTP,U2F,REST,Mail2F,Ext2F,Yubikey,Radius',
@ -4161,8 +4170,14 @@ m{^(?:ldapi://[^/]*/?|\w[\w\-\.]*(?::\d{1,5})?|ldap(?:s|\+tls)?://\w[\w\-\.]*(?:
oidcRPMetaDataOptions => { type => 'subContainer', },
# OpenID Connect providers
oidcOPMetaDataJSON => { type => 'file', keyTest => sub { 1 } },
oidcOPMetaDataJWKS => { type => 'file', keyTest => sub { 1 } },
oidcOPMetaDataJSON => {
type => 'file',
keyTest => sub { 1 }
},
oidcOPMetaDataJWKS => {
type => 'file',
keyTest => sub { 1 }
},
oidcOPMetaDataExportedVars => {
type => 'keyTextContainer',
default => {
@ -4254,7 +4269,7 @@ m{^(?:ldapi://[^/]*/?|\w[\w\-\.]*(?::\d{1,5})?|ldap(?:s|\+tls)?://\w[\w\-\.]*(?:
oidcRPMetaDataOptionsUserInfoSignAlg => {
type => 'select',
select => [
{ k => '', v => 'JSON' },
{ k => '', v => 'JSON' },
{ k => 'none', v => 'JWT/None' },
{ k => 'HS256', v => 'JWT/HS256' },
{ k => 'HS384', v => 'JWT/HS384' },
@ -4361,6 +4376,7 @@ m{^(?:ldapi://[^/]*/?|\w[\w\-\.]*(?::\d{1,5})?|ldap(?:s|\+tls)?://\w[\w\-\.]*(?:
type => 'keyTextContainer',
help => 'idpopenidconnect.html#scope-rules',
test => {
# RFC6749
keyTest => qr/^[\x21\x23-\x5B\x5D-\x7E]+$/,
keyMsgFail => '__badMacroName__',

View File

@ -88,6 +88,7 @@ sub tree {
'portalRequireOldPassword',
'hideOldPassword',
'mailOnPasswordChange',
'portalEnablePasswordDisplay',
]
},
{
@ -992,6 +993,7 @@ sub tree {
'sfRemovedNotifMsg',
],
},
'sfRegisterTimeout',
]
},
{

View File

@ -790,6 +790,7 @@
"portalDisplayRefreshMyRights":"Display rights refresh link",
"portalDisplayRegister":"تسجيل حساب جديد",
"portalDisplayResetPassword":"إعادة تعيين كلمة المرور",
"portalEnablePasswordDisplay":"Allow to display password",
"portalErrorOnExpiredSession":"عرض الخطأ في الجلسة المنتهية صلحيتها",
"portalErrorOnMailNotFound":"إظهار الخطأ في البريد الغيرالموجود",
"portalForceAuthn":"فرض إثبات الهوية",
@ -1053,6 +1054,7 @@
"sfExtra":"Additional second factors",
"sfManagerRule":"Display Manager link",
"sfOnlyUpgrade":"Use 2FA for session upgrade",
"sfRegisterTimeout":"Registration timeout",
"sfRemovedMsg":"Display a message if an expired 2FA is removed",
"sfRemovedMsgRule":"تفعيل",
"sfRemovedNotifMsg":"Notification message",
@ -1209,4 +1211,4 @@
"yubikey2fUrl":"خدمة أل يو أر ل",
"yubikey2fUserCanRemoveKey":"Allow user to remove Yubikey",
"zeroConfExplanations":"لا يحتوي الخادم على إعدادات. استخدام قالب لحفظ الأول"
}
}

View File

@ -790,6 +790,7 @@
"portalDisplayRefreshMyRights":"Display rights refresh link",
"portalDisplayRegister":"Register new account",
"portalDisplayResetPassword":"Reset password",
"portalEnablePasswordDisplay":"Allow to display password",
"portalErrorOnExpiredSession":"Show error on expired session",
"portalErrorOnMailNotFound":"Show error on mail not found",
"portalForceAuthn":"Force authentication",
@ -1053,6 +1054,7 @@
"sfExtra":"Additional second factors",
"sfManagerRule":"Display Manager link",
"sfOnlyUpgrade":"Use 2FA for session upgrade",
"sfRegisterTimeout":"Registration timeout",
"sfRemovedMsg":"Display a message if an expired 2FA is removed",
"sfRemovedMsgRule":"Activation",
"sfRemovedNotifMsg":"Notification message",
@ -1209,4 +1211,4 @@
"yubikey2fUrl":"Service URL",
"yubikey2fUserCanRemoveKey":"Allow user to remove Yubikey",
"zeroConfExplanations":"Server has no configuration. Use template to save the first."
}
}

View File

@ -790,6 +790,7 @@
"portalDisplayRefreshMyRights":"Display rights refresh link",
"portalDisplayRegister":"Register new account",
"portalDisplayResetPassword":"Reset password",
"portalEnablePasswordDisplay":"Allow to display password",
"portalErrorOnExpiredSession":"Show error on expired session",
"portalErrorOnMailNotFound":"Show error on mail not found",
"portalForceAuthn":"Force authentication",
@ -1053,6 +1054,7 @@
"sfExtra":"Additional second factors",
"sfManagerRule":"Display Manager link",
"sfOnlyUpgrade":"Use 2FA for session upgrade",
"sfRegisterTimeout":"Registration timeout",
"sfRemovedMsg":"Display a message if an expired 2FA is removed",
"sfRemovedMsgRule":"Activation",
"sfRemovedNotifMsg":"Notification message",

View File

@ -790,6 +790,7 @@
"portalDisplayRefreshMyRights":"Display rights refresh link",
"portalDisplayRegister":"Registrar nueva cuenta",
"portalDisplayResetPassword":"Reiniciar contraseña",
"portalEnablePasswordDisplay":"Allow to display password",
"portalErrorOnExpiredSession":"Mostrar error en sesión caducada",
"portalErrorOnMailNotFound":"Mostrar error cuando no se encuentra el email",
"portalForceAuthn":"Forzar autentificación",
@ -1053,6 +1054,7 @@
"sfExtra":"Segundos factores adicionales",
"sfManagerRule":"Display Manager link",
"sfOnlyUpgrade":"Use 2FA for session upgrade",
"sfRegisterTimeout":"Registration timeout",
"sfRemovedMsg":"Display a message if an expired 2FA is removed",
"sfRemovedMsgRule":"Activación",
"sfRemovedNotifMsg":"Mensaje de notificación",
@ -1209,4 +1211,4 @@
"yubikey2fUrl":"Service URL",
"yubikey2fUserCanRemoveKey":"Allow user to remove Yubikey",
"zeroConfExplanations":"Server has no configuration. Use template to save the first."
}
}

View File

@ -790,6 +790,7 @@
"portalDisplayRefreshMyRights":"Afficher le lien de rafraichissement des droits",
"portalDisplayRegister":"Création d'un nouveau compte",
"portalDisplayResetPassword":"Réinitialisation de mot de passe",
"portalEnablePasswordDisplay":"Permettre d'afficher le mot de passe",
"portalErrorOnExpiredSession":"Affiche une erreur si la session est expirée",
"portalErrorOnMailNotFound":"Affiche une erreur si le mail n'est pas trouvé",
"portalForceAuthn":"Authentification forcée",
@ -1053,6 +1054,7 @@
"sfExtra":"Seconds facteurs additionnels",
"sfManagerRule":"Afficher le lien du Gestionnaire",
"sfOnlyUpgrade":"Utiliser le SF pour augmenter le niveau d'authentification",
"sfRegisterTimeout":"Délai d'expiration de l'enregistrement",
"sfRemovedMsg":"Afficher un message si un SF expiré est supprimé",
"sfRemovedMsgRule":"Activation",
"sfRemovedNotifMsg":"Message de la notification",

View File

@ -790,6 +790,7 @@
"portalDisplayRefreshMyRights":"Display rights refresh link",
"portalDisplayRegister":"Registra nuovo account",
"portalDisplayResetPassword":"Reimposta password",
"portalEnablePasswordDisplay":"Allow to display password",
"portalErrorOnExpiredSession":"Mostra errore nella sessione scaduta",
"portalErrorOnMailNotFound":"Mostra errore sulla posta non trovata",
"portalForceAuthn":"Forza l'autenticazione",
@ -1053,6 +1054,7 @@
"sfExtra":"Additional second factors",
"sfManagerRule":"Display Manager link",
"sfOnlyUpgrade":"Use 2FA for session upgrade",
"sfRegisterTimeout":"Registration timeout",
"sfRemovedMsg":"Display a message if an expired 2FA is removed",
"sfRemovedMsgRule":"Attivazione",
"sfRemovedNotifMsg":"Notification message",
@ -1209,4 +1211,4 @@
"yubikey2fUrl":"URL del servizio",
"yubikey2fUserCanRemoveKey":"Autorizza l'utente a rimuovere la Yubikey",
"zeroConfExplanations":"Il server non ha alcuna configurazione. Utilizza il modello per salvare il primo."
}
}

View File

@ -790,6 +790,7 @@
"portalDisplayRefreshMyRights":"Wyświetl link do odświeżania praw",
"portalDisplayRegister":"Zarejestruj Nowe Konto",
"portalDisplayResetPassword":"Zresetuj hasło",
"portalEnablePasswordDisplay":"Allow to display password",
"portalErrorOnExpiredSession":"Pokaż błąd w wygasłej sesji",
"portalErrorOnMailNotFound":"Pokaż błąd w poczcie nie znaleziono",
"portalForceAuthn":"Wymuś uwierzytelnienie",
@ -1053,6 +1054,7 @@
"sfExtra":"Dodatkowe drugie czynniki",
"sfManagerRule":"Link do Menedżera wyświetlania",
"sfOnlyUpgrade":"Użyj 2FA do aktualizacji sesji",
"sfRegisterTimeout":"Registration timeout",
"sfRemovedMsg":"Wyświetl komunikat, gdy przeterminowany 2FA został usunięty",
"sfRemovedMsgRule":"Aktywacja",
"sfRemovedNotifMsg":"Powiadomienie",
@ -1209,4 +1211,4 @@
"yubikey2fUrl":"URL usługi",
"yubikey2fUserCanRemoveKey":"Pozwól użytkownikowi usunąć Yubikey",
"zeroConfExplanations":"Serwer nie ma konfiguracji. Użyj szablonu, aby zapisać pierwszy."
}
}

View File

@ -790,6 +790,7 @@
"portalDisplayRefreshMyRights":"Görüntüleme hakları yenileme bağlantısı",
"portalDisplayRegister":"Yeni hesap kaydet",
"portalDisplayResetPassword":"Parolayı sıfırla",
"portalEnablePasswordDisplay":"Allow to display password",
"portalErrorOnExpiredSession":"Süresi dolmuş oturumda hatayı göster",
"portalErrorOnMailNotFound":"E-posta bulunamadığında hatayı göster",
"portalForceAuthn":"Kimlik doğrulamaya zorla",
@ -1053,6 +1054,7 @@
"sfExtra":"Ek ikinci faktörler",
"sfManagerRule":"Yönetici bağlantısını görüntüle",
"sfOnlyUpgrade":"Oturum yükseltme için 2FA kullan",
"sfRegisterTimeout":"Registration timeout",
"sfRemovedMsg":"Süresi dolan bir 2FA kaldırıldığında bir mesaj göster",
"sfRemovedMsgRule":"Aktivasyon",
"sfRemovedNotifMsg":"Bildirim mesajı",
@ -1209,4 +1211,4 @@
"yubikey2fUrl":"Servis URL'si",
"yubikey2fUserCanRemoveKey":"Yubikey'i kaldırmak için kullanıcıya izin ver",
"zeroConfExplanations":"Sunucunun yapılandırması yok. Şimdi bir tane kaydetmek için şablonu kullanın."
}
}

View File

@ -790,6 +790,7 @@
"portalDisplayRefreshMyRights":"Display rights refresh link",
"portalDisplayRegister":"Đăng ký tài khoản mới",
"portalDisplayResetPassword":"Đặt lại mật khẩu",
"portalEnablePasswordDisplay":"Allow to display password",
"portalErrorOnExpiredSession":"Show error on expired session",
"portalErrorOnMailNotFound":"Show error on mail not found",
"portalForceAuthn":"Bắt buộc xác thực",
@ -1053,6 +1054,7 @@
"sfExtra":"Additional second factors",
"sfManagerRule":"Display Manager link",
"sfOnlyUpgrade":"Use 2FA for session upgrade",
"sfRegisterTimeout":"Registration timeout",
"sfRemovedMsg":"Display a message if an expired 2FA is removed",
"sfRemovedMsgRule":"Kích hoạt",
"sfRemovedNotifMsg":"Notification message",
@ -1209,4 +1211,4 @@
"yubikey2fUrl":"Dịch vụ URL",
"yubikey2fUserCanRemoveKey":"Allow user to remove Yubikey",
"zeroConfExplanations":"Máy chủ không có cấu hình. Sử dụng mẫu để lưu đầu tiên. "
}
}

View File

@ -790,6 +790,7 @@
"portalDisplayRefreshMyRights":"Display rights refresh link",
"portalDisplayRegister":"Register new account",
"portalDisplayResetPassword":"Reset password",
"portalEnablePasswordDisplay":"Allow to display password",
"portalErrorOnExpiredSession":"Show error on expired session",
"portalErrorOnMailNotFound":"Show error on mail not found",
"portalForceAuthn":"Force authentication",
@ -1053,6 +1054,7 @@
"sfExtra":"Additional second factors",
"sfManagerRule":"Display Manager link",
"sfOnlyUpgrade":"Use 2FA for session upgrade",
"sfRegisterTimeout":"Registration timeout",
"sfRemovedMsg":"Display a message if an expired 2FA is removed",
"sfRemovedMsgRule":"激活",
"sfRemovedNotifMsg":"Notification message",
@ -1209,4 +1211,4 @@
"yubikey2fUrl":"Service URL",
"yubikey2fUserCanRemoveKey":"Allow user to remove Yubikey",
"zeroConfExplanations":"Server has no configuration. Use template to save the first."
}
}

View File

@ -790,6 +790,7 @@
"portalDisplayRefreshMyRights":"顯示權限重新整理連結",
"portalDisplayRegister":"註冊新帳號",
"portalDisplayResetPassword":"重設密碼",
"portalEnablePasswordDisplay":"Allow to display password",
"portalErrorOnExpiredSession":"在過期的工作階段上顯示錯誤",
"portalErrorOnMailNotFound":"找不到郵件時顯示錯誤",
"portalForceAuthn":"強制驗證",
@ -1053,6 +1054,7 @@
"sfExtra":"額外的第二因素",
"sfManagerRule":"顯示管理程式連結",
"sfOnlyUpgrade":"使用 2FA 進行工作階段升級",
"sfRegisterTimeout":"Registration timeout",
"sfRemovedMsg":"如果過期的雙因素已被移除則顯示訊息",
"sfRemovedMsgRule":"啟用",
"sfRemovedNotifMsg":"通知訊息",
@ -1209,4 +1211,4 @@
"yubikey2fUrl":"服務 URL",
"yubikey2fUserCanRemoveKey":"允許使用者移除 Yubikey",
"zeroConfExplanations":"伺服器未設定。使用飯本來儲存第一個。"
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,4 +1,4 @@
# Test notifications explorer API
# Test for https://gitlab.ow2.org/lemonldap-ng/lemonldap-ng/-/issues/2493
use strict;
use Data::Dumper;
@ -8,7 +8,7 @@ use Test::More;
my $count = 0;
my $file = 't/conf.db';
my $maintests = 1;
my $maintests = 8;
my ( $res, $client );
eval { unlink $file };
@ -25,16 +25,27 @@ SKIP: {
}
my $dbh = DBI->connect("dbi:SQLite:dbname=$file");
$dbh->do('CREATE TABLE lmConfig (cfgNum int, data text)')
or die $DBI::errstr;
$dbh->do(
"CREATE TABLE lmConfig ( cfgNum int not null, field varchar(255) NOT NULL DEFAULT '', value longblob, PRIMARY KEY (cfgNum,field))"
) or die $DBI::errstr;
use_ok('Lemonldap::NG::Common::Conf');
my $h;
ok(
$h = new Lemonldap::NG::Common::Conf( {
type => 'RDBI',
dbiChain => "DBI:SQLite:dbname=$file",
dbiUser => '',
dbiPassword => '',
}
),
'RDBI object'
);
{
local $/ = undef;
open my $f, '<', 't/conf/lmConf-1.json';
my $content = <$f>;
close $f;
my $sth = $dbh->prepare('INSERT INTO lmConfig VALUES(1,?)')
or die $DBI::errstr;
$sth->execute($content) or die $DBI::errstr;
ok( $h->store( from_json($content) ), 'Conf 1 saved' );
}
use_ok('Lemonldap::NG::Manager::Cli::Lib');
@ -44,23 +55,16 @@ SKIP: {
),
'Client object'
);
count(1);
use_ok('Lemonldap::NG::Manager::Cli');
count(1);
my @args = (qw(-yes 1 -force 1 set ldapSetPassword 0));
$ENV{LLNG_DEFAULTCONFFILE} = 't/lemonldap-ng-DBI-conf.ini';
Lemonldap::NG::Manager::Cli->run(@args);
my $res = $dbh->selectall_arrayref('SELECT * FROM lmConfig');
my $conf = from_json( $res->[0]->[1] );
ok( (
defined( $conf->{ldapSetPassword} )
and $conf->{ldapSetPassword} == 0
),
'Key inserted'
);
count(1);
my $res = $dbh->selectrow_hashref(
"SELECT * FROM lmConfig WHERE field='ldapSetPassword'");
ok( $res, 'Key inserted' );
ok( $res and $res->{value} == '0', 'Value is 0' );
}
eval { unlink $file };

View File

@ -1,12 +1,12 @@
[all]
logLevel = debug
logLevel = error
localSessionStorage =
localSessionStorageOptions =
[configuration]
type=CDBI
type=RDBI
dbiChain=dbi:SQLite:dbname=t/conf.db
[portal]

View File

@ -46,6 +46,19 @@ has ott => (
}
);
has regOtt => (
is => 'rw',
lazy => 1,
default => sub {
my $ott =
$_[0]->{p}->loadModule('Lemonldap::NG::Portal::Lib::OneTimeToken');
my $timeout = $_[0]->{conf}->{sfRegisterTimeout}
// $_[0]->{conf}->{formTimeout};
$ott->timeout($timeout);
return $ott;
}
);
sub init {
my ($self) = @_;
@ -240,7 +253,7 @@ sub run {
$self->logger->debug("Looking for expired 2F device(s)...");
my $removed = 0;
my $name = '';
my $name = '';
my $now = time();
foreach my $device (@$_2fDevices) {
my $type = lc( $device->{type} );
@ -312,7 +325,7 @@ sub run {
$self->logger->debug("2F is required...");
$self->logger->debug(" -> Register 2F");
$req->pdata->{sfRegToken} =
$self->ott->createToken( $req->sessionInfo );
$self->regOtt->createToken( $req->sessionInfo );
$self->logger->debug("Just one 2F is enabled");
$self->logger->debug(" -> Redirect to 2fregisters/");
$req->response( [
@ -602,7 +615,7 @@ sub restoreSession {
my ( $self, $req, @path ) = @_;
my $token = $req->pdata->{sfRegToken}
or return [ 302, [ Location => $self->conf->{portal} ], [] ];
$req->userData( $self->ott->getToken( $token, 1 ) );
$req->userData( $self->regOtt->getToken( $token, 1 ) );
$req->data->{sfRegRequired} = 1;
return $req->method eq 'POST'
? $self->register( $req, @path )

View File

@ -24,7 +24,9 @@ has ott => (
default => sub {
my $ott =
$_[0]->{p}->loadModule('Lemonldap::NG::Portal::Lib::OneTimeToken');
$ott->timeout( $_[0]->conf->{formTimeout} );
my $timeout = $_[0]->{conf}->{sfRegisterTimeout}
// $_[0]->{conf}->{formTimeout};
$ott->timeout($timeout);
return $ott;
}
);

View File

@ -22,8 +22,8 @@ extends 'Lemonldap::NG::Portal::Main::SecondFactor';
# INITIALIZATION
has prefix => ( is => 'ro', default => 'yubikey' );
has logo => ( is => 'rw', default => 'yubikey.png' );
has yubi => ( is => 'rw' );
has logo => ( is => 'rw', default => 'yubikey.png' );
has yubi => ( is => 'rw' );
sub init {
my ($self) = @_;
@ -77,7 +77,7 @@ sub init {
$self->conf->{yubikey2fUrl}
? ( url => $self->conf->{yubikey2fUrl} )
: ()
),
)
}
)
);
@ -96,7 +96,7 @@ sub _findYubikey {
# If we didn't find a key, lookup psession
if ( !$yubikey and $sessionInfo->{_2fDevices} ) {
$self->logger->debug("Loading 2F Devices ...");
$self->logger->debug("Loading 2F Devices...");
# Read existing 2FDevices
$_2fDevices = eval {
@ -107,10 +107,19 @@ sub _findYubikey {
return PE_ERROR;
}
$self->logger->debug("2F Device(s) found");
$self->logger->debug("Reading Yubikey ...");
$self->logger->debug("Reading Yubikey...");
$yubikey = $_->{_yubikey}
foreach grep { $_->{type} eq 'UBK' } @$_2fDevices;
if ( my $code = $req->param('code') ) {
$yubikey = $_->{_yubikey} foreach grep {
( $_->{type} eq 'UBK' )
and ( $_->{_yubikey} eq
substr( $code, 0, $self->conf->{yubikey2fPublicIDSize} ) )
} @$_2fDevices;
}
else {
$yubikey = $_->{_yubikey}
foreach grep { $_->{type} eq 'UBK' } @$_2fDevices;
}
}
return $yubikey;
@ -121,7 +130,7 @@ sub run {
my ( $self, $req, $token, $_2fDevices ) = @_;
my $checkLogins = $req->param('checkLogins');
$self->logger->debug("Yubikey; checkLogins set") if ($checkLogins);
$self->logger->debug("Yubikey; checkLogins set") if $checkLogins;
my $stayconnected = $req->param('stayconnected');
$self->logger->debug("Yubikey: stayconnected set") if $stayconnected;
@ -141,13 +150,13 @@ sub run {
$req,
'ext2fcheck',
params => {
MAIN_LOGO => $self->conf->{portalMainLogo},
SKIN => $self->p->getSkin($req),
TOKEN => $token,
TARGET => '/yubikey2fcheck?skin=' . $self->p->getSkin($req),
INPUTLOGO => 'yubikey.png',
LEGEND => 'clickOnYubikey',
CHECKLOGINS => $checkLogins,
MAIN_LOGO => $self->conf->{portalMainLogo},
SKIN => $self->p->getSkin($req),
TOKEN => $token,
TARGET => '/yubikey2fcheck?skin=' . $self->p->getSkin($req),
INPUTLOGO => 'yubikey.png',
LEGEND => 'clickOnYubikey',
CHECKLOGINS => $checkLogins,
STAYCONNECTED => $stayconnected
}
);

View File

@ -25,4 +25,4 @@ sub init {
return 1;
}
1;
1;

View File

@ -8,7 +8,7 @@ our $VERSION = '2.1.0';
use constant HANDLER => 'Lemonldap::NG::Handler::PSGI::Main';
use constant URIRE =>
qr{(((?^:https?))://((?:(?:(?:(?:(?:[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])[.]?)|(?:[0-9]+[.][0-9]+[.][0-9]+[.][0-9]+)))(?::((?:[0-9]*)))?(/(((?:(?:(?:(?:[a-zA-Z0-9\-_.!~*'():@&=+$,]+|(?:%[a-fA-F0-9][a-fA-F0-9]))*)(?:;(?:(?:[a-zA-Z0-9\-_.!~*'():@&=+$,]+|(?:%[a-fA-F0-9][a-fA-F0-9]))*))*)(?:/(?:(?:(?:[a-zA-Z0-9\-_.!~*'():@&=+$,]+|(?:%[a-fA-F0-9][a-fA-F0-9]))*)(?:;(?:(?:[a-zA-Z0-9\-_.!~*'():@&=+$,]+|(?:%[a-fA-F0-9][a-fA-F0-9]))*))*))*))(?:[?]((?:(?:[;/?:@&=+$,a-zA-Z0-9\-_.!~*'()]+|(?:%[a-fA-F0-9][a-fA-F0-9]))*)))?))?)};
qr{(((?^:https?))://((?:(?:(?:(?:(?:[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])[.]?)|(?:[0-9]+[.][0-9]+[.][0-9]+[.][0-9]+)))(?::((?:[0-9]*)))?(/(((?:(?:(?:(?:[a-zA-Z0-9\-_.!~*'():\@&=+\$,]+|(?:%[a-fA-F0-9][a-fA-F0-9]))*)(?:;(?:(?:[a-zA-Z0-9\-_.!~*'():\@&=+\$,]+|(?:%[a-fA-F0-9][a-fA-F0-9]))*))*)(?:/(?:(?:(?:[a-zA-Z0-9\-_.!~*'():\@&=+\$,]+|(?:%[a-fA-F0-9][a-fA-F0-9]))*)(?:;(?:(?:[a-zA-Z0-9\-_.!~*'():\@&=+\$,]+|(?:%[a-fA-F0-9][a-fA-F0-9]))*))*))*))(?:[?]((?:(?:[;/?:\@&=+\$,a-zA-Z0-9\-_.!~*'()]+|(?:%[a-fA-F0-9][a-fA-F0-9]))*)))?))?)};
use constant {
PE_LOGOUT_OK => -7,
PE_PASSWORD_OK => -6,

View File

@ -156,7 +156,7 @@ sub controlUrl {
}
# Unprotected hosts
unless ( $tmp =~ URIRE ) {
if ( $tmp and ( $tmp !~ URIRE ) ) {
$self->userLogger->error("Bad URL $tmp");
delete $req->{urldc};
return PE_BADURL;

View File

@ -515,13 +515,22 @@ $(window).on 'load', () ->
if window.datas.ppolicy? and $('#newpassword').length
$('#reset').change togglecheckpassword
# Functions to show/hide display password button
if datas['enablePasswordDisplay']
$(".toggle-password").mousedown (e) ->
$(this).toggleClass("fa-eye fa-eye-slash");
$("input[name=password]").attr("type", "text");
$(".toggle-password").mouseup (e) ->
$(this).toggleClass("fa-eye fa-eye-slash");
$("input[name=password]").attr("type", "password");
if datas['dontStorePassword']
$(".toggle-password").mousedown () ->
$(this).toggleClass("fa-eye fa-eye-slash")
$("input[name=password]").attr('class', 'form-control')
$(".toggle-password").mouseup () ->
$(this).toggleClass("fa-eye fa-eye-slash")
$("input[name=password]").attr('class', 'form-control key') if $("input[name=password]").get(0).value
else
$(".toggle-password").mousedown () ->
$(this).toggleClass("fa-eye fa-eye-slash")
$("input[name=password]").attr("type", "text")
$(".toggle-password").mouseup () ->
$(this).toggleClass("fa-eye fa-eye-slash")
$("input[name=password]").attr("type", "password")
# Ping if asked
if datas['pingInterval'] and datas['pingInterval'] > 0
@ -537,18 +546,50 @@ $(window).on 'load', () ->
# Functions to show/hide change password inputs
$('#show-hide-button').on 'click', () ->
if $("#newpassword").attr('type') == 'password'
console.log 'Show passwords'
$("#newpassword").attr('type', 'input')
$("#confirmpassword").attr('type', 'input')
$("#show-hide-icon-button").removeClass 'fa-eye'
$("#show-hide-icon-button").addClass 'fa-eye-slash'
if datas['dontStorePassword']
if $("#newpassword").attr('class') == 'form-control key' || $("#confirmpassword").attr('class') == 'form-control key'
console.log 'Show passwords'
$("#newpassword").attr('class', 'form-control')
$("#confirmpassword").attr('class', 'form-control')
$("#show-hide-icon-button").attr('class', 'fa fa-eye-slash')
else
console.log 'Hide passwords'
$("#newpassword").attr('class', 'form-control key') if $("#newpassword").get(0).value
$("#confirmpassword").attr('class', 'form-control key') if $("#confirmpassword").get(0).value
$("#show-hide-icon-button").attr('class', 'fa fa-eye') if ($("#newpassword").get(0).value || $("#confirmpassword").get(0).value)
else
console.log 'Hide passwords'
$("#newpassword").attr('type', 'password')
$("#confirmpassword").attr('type', 'password')
$("#show-hide-icon-button").removeClass 'fa-eye-slash'
$("#show-hide-icon-button").addClass 'fa-eye'
if $("#newpassword").attr('type') == 'password'
console.log 'Show passwords'
$("#newpassword").attr('type', 'text')
$("#confirmpassword").attr('type', 'text')
$("#show-hide-icon-button").attr('class', 'fa fa-eye-slash')
else
console.log 'Hide passwords'
$("#newpassword").attr('type', 'password')
$("#confirmpassword").attr('type', 'password')
$("#show-hide-icon-button").attr('class', 'fa fa-eye')
# Functions to show/hide placeholder password inputs
$('#passwordfield').on 'input', () ->
if $('#passwordfield').get(0).value && datas['dontStorePassword']
$("#passwordfield").attr('class', 'form-control key')
else
$("#passwordfield").attr('class', 'form-control')
$('#oldpassword').on 'input', () ->
if $('#oldpassword').get(0).value && datas['dontStorePassword']
$("#oldpassword").attr('class', 'form-control key')
else
$("#oldpassword").attr('class', 'form-control')
$('#newpassword').on 'input', () ->
if $('#newpassword').get(0).value && datas['dontStorePassword']
$("#newpassword").attr('class', 'form-control key') if $("#show-hide-icon-button").attr('class') == 'fa fa-eye'
else
$("#newpassword").attr('class', 'form-control')
$('#confirmpassword').on 'input', () ->
if $('#confirmpassword').get(0).value && datas['dontStorePassword']
$("#confirmpassword").attr('class', 'form-control key') if $("#show-hide-icon-button").attr('class') == 'fa fa-eye'
else
$("#confirmpassword").attr('class', 'form-control')
#$('#formpass').on 'submit', changePwd

View File

@ -562,14 +562,27 @@ LemonLDAP::NG Portal jQuery scripts
$('#reset').change(togglecheckpassword);
}
if (datas['enablePasswordDisplay']) {
$(".toggle-password").mousedown(function(e) {
$(this).toggleClass("fa-eye fa-eye-slash");
return $("input[name=password]").attr("type", "text");
});
$(".toggle-password").mouseup(function(e) {
$(this).toggleClass("fa-eye fa-eye-slash");
return $("input[name=password]").attr("type", "password");
});
if (datas['dontStorePassword']) {
$(".toggle-password").mousedown(function() {
$(this).toggleClass("fa-eye fa-eye-slash");
return $("input[name=password]").attr('class', 'form-control');
});
$(".toggle-password").mouseup(function() {
$(this).toggleClass("fa-eye fa-eye-slash");
if ($("input[name=password]").get(0).value) {
return $("input[name=password]").attr('class', 'form-control key');
}
});
} else {
$(".toggle-password").mousedown(function() {
$(this).toggleClass("fa-eye fa-eye-slash");
return $("input[name=password]").attr("type", "text");
});
$(".toggle-password").mouseup(function() {
$(this).toggleClass("fa-eye fa-eye-slash");
return $("input[name=password]").attr("type", "password");
});
}
}
if (datas['pingInterval'] && datas['pingInterval'] > 0) {
window.setTimeout(ping, datas['pingInterval']);
@ -583,18 +596,68 @@ LemonLDAP::NG Portal jQuery scripts
return removeOidcConsent($(this).attr('partner'));
});
$('#show-hide-button').on('click', function() {
if ($("#newpassword").attr('type') === 'password') {
console.log('Show passwords');
$("#newpassword").attr('type', 'input');
$("#confirmpassword").attr('type', 'input');
$("#show-hide-icon-button").removeClass('fa-eye');
return $("#show-hide-icon-button").addClass('fa-eye-slash');
if (datas['dontStorePassword']) {
if ($("#newpassword").attr('class') === 'form-control key' || $("#confirmpassword").attr('class') === 'form-control key') {
console.log('Show passwords');
$("#newpassword").attr('class', 'form-control');
$("#confirmpassword").attr('class', 'form-control');
return $("#show-hide-icon-button").attr('class', 'fa fa-eye-slash');
} else {
console.log('Hide passwords');
if ($("#newpassword").get(0).value) {
$("#newpassword").attr('class', 'form-control key');
}
if ($("#confirmpassword").get(0).value) {
$("#confirmpassword").attr('class', 'form-control key');
}
if ($("#newpassword").get(0).value || $("#confirmpassword").get(0).value) {
return $("#show-hide-icon-button").attr('class', 'fa fa-eye');
}
}
} else {
console.log('Hide passwords');
$("#newpassword").attr('type', 'password');
$("#confirmpassword").attr('type', 'password');
$("#show-hide-icon-button").removeClass('fa-eye-slash');
return $("#show-hide-icon-button").addClass('fa-eye');
if ($("#newpassword").attr('type') === 'password') {
console.log('Show passwords');
$("#newpassword").attr('type', 'text');
$("#confirmpassword").attr('type', 'text');
return $("#show-hide-icon-button").attr('class', 'fa fa-eye-slash');
} else {
console.log('Hide passwords');
$("#newpassword").attr('type', 'password');
$("#confirmpassword").attr('type', 'password');
return $("#show-hide-icon-button").attr('class', 'fa fa-eye');
}
}
});
$('#passwordfield').on('input', function() {
if ($('#passwordfield').get(0).value && datas['dontStorePassword']) {
return $("#passwordfield").attr('class', 'form-control key');
} else {
return $("#passwordfield").attr('class', 'form-control');
}
});
$('#oldpassword').on('input', function() {
if ($('#oldpassword').get(0).value && datas['dontStorePassword']) {
return $("#oldpassword").attr('class', 'form-control key');
} else {
return $("#oldpassword").attr('class', 'form-control');
}
});
$('#newpassword').on('input', function() {
if ($('#newpassword').get(0).value && datas['dontStorePassword']) {
if ($("#show-hide-icon-button").attr('class') === 'fa fa-eye') {
return $("#newpassword").attr('class', 'form-control key');
}
} else {
return $("#newpassword").attr('class', 'form-control');
}
});
$('#confirmpassword').on('input', function() {
if ($('#confirmpassword').get(0).value && datas['dontStorePassword']) {
if ($("#show-hide-icon-button").attr('class') === 'fa fa-eye') {
return $("#confirmpassword").attr('class', 'form-control key');
}
} else {
return $("#confirmpassword").attr('class', 'form-control');
}
});
$('#resetfinduserform').on('click', function() {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -26,7 +26,6 @@
</TMPL_IF>
<TMPL_IF NAME="REQUIRE_OLDPASSWORD">
<TMPL_IF NAME="HIDE_OLDPASSWORD">
<input id="oldpassword" name="oldpassword" type="hidden" value="<TMPL_VAR NAME=OLDPASSWORD>" aria-required="true">
<TMPL_ELSE>
@ -34,10 +33,13 @@
<div class="input-group-prepend">
<span class="input-group-text"><label for="oldpassword" class="mb-0"><i class="fa fa-lock"></i></label></span>
</div>
<input id="oldpassword" name="oldpassword" type="password" value="<TMPL_VAR NAME=OLDPASSWORD>" class="form-control" trplaceholder="currentPwd" required aria-required="true">
<TMPL_IF NAME="DONT_STORE_PASSWORD">
<input id="oldpassword" name="oldpassword" type="text" value="<TMPL_VAR NAME=OLDPASSWORD>" class="form-control" trplaceholder="currentPwd" autocomplete="off" required aria-required="true">
<TMPL_ELSE>
<input id="oldpassword" name="oldpassword" type="password" value="<TMPL_VAR NAME=OLDPASSWORD>" class="form-control" trplaceholder="currentPwd" required aria-required="true">
</TMPL_IF>
</div>
</TMPL_IF>
</TMPL_IF>
<TMPL_IF NAME="DISPLAY_PPOLICY"><TMPL_INCLUDE NAME="passwordpolicy.tpl"></TMPL_IF>
@ -46,15 +48,22 @@
<div class="input-group-prepend">
<span class="input-group-text"><label for="newpassword" class="mb-0"><i class="fa fa-lock"></i></label></span>
</div>
<input id="newpassword" name="newpassword" type="password" class="form-control" trplaceholder="newPassword" required aria-required="true"/>
<TMPL_IF NAME="DONT_STORE_PASSWORD">
<input id="newpassword" name="newpassword" type="text" class="form-control" trplaceholder="newPassword" autocomplete="off" required aria-required="true"/>
<TMPL_ELSE>
<input id="newpassword" name="newpassword" type="password" class="form-control" trplaceholder="newPassword" required aria-required="true"/>
</TMPL_IF>
</div>
<div class="form-group input-group">
<div class="input-group-prepend">
<span class="input-group-text"><label for="confirmpassword" class="mb-0"><i class="fa fa-lock"></i></label></span>
</div>
<input id="confirmpassword" name="confirmpassword" type="password" class="form-control" trplaceholder="confirmPwd" required aria-required="true"/>
<TMPL_IF NAME="DONT_STORE_PASSWORD">
<input id="confirmpassword" name="confirmpassword" type="text" class="form-control" trplaceholder="confirmPwd" autocomplete="off" required aria-required="true"/>
<TMPL_ELSE>
<input id="confirmpassword" name="confirmpassword" type="password" class="form-control" trplaceholder="confirmPwd" required aria-required="true"/>
</TMPL_IF>
</div>
<div class="buttons">
<button type="submit" class="btn btn-success">
<span class="fa fa-check-circle"></span>

View File

@ -17,13 +17,18 @@
<span class="input-group-text"><label for="passwordfield" class="mb-0"><i class="fa fa-lock"></i></label></span>
</div>
<TMPL_IF NAME="DONT_STORE_PASSWORD">
<input id="passwordfield" name="password" type="text" class="form-control key" autocomplete="off" required aria-required="true" aria-hidden="true"/>
<input id="passwordfield" name="password" type="text" class="form-control" trplaceholder="password" autocomplete="off" required aria-required="true" aria-hidden="true"/>
<TMPL_IF NAME="ENABLE_PASSWORD_DISPLAY">
<div class="input-group-append">
<span class="input-group-text"><i class="fa fa-eye-slash toggle-password"></i></span>
</div>
</TMPL_IF>
<TMPL_ELSE>
<input id="passwordfield" name="password" type="password" class="form-control" trplaceholder="password" required aria-required="true"/>
<TMPL_IF NAME="ENABLE_PASSWORD_DISPLAY">
<div class="input-group-append">
<span class="input-group-text"><i class="fa fa-eye-slash toggle-password"></i></span>
</div>
<div class="input-group-append">
<span class="input-group-text"><i class="fa fa-eye-slash toggle-password"></i></span>
</div>
</TMPL_IF>
</TMPL_IF>
</div>

View File

@ -37,7 +37,8 @@
"allowedspechar": "<TMPL_VAR NAME="PPOLICY_ALLOWEDSPECHAR" ESCAPE="js" DEFAULT="">",
"minspechar": "<TMPL_VAR NAME="PPOLICY_MINSPECHAR" DEFAULT="0">"
},</TMPL_IF>
"enablePasswordDisplay":<TMPL_VAR NAME="ENABLE_PASSWORD_DISPLAY" DEFAULT="0">
"enablePasswordDisplay":<TMPL_VAR NAME="ENABLE_PASSWORD_DISPLAY" DEFAULT="0">,
"dontStorePassword":<TMPL_VAR NAME="DONT_STORE_PASSWORD" DEFAULT="0">
}
</script>

View File

@ -0,0 +1,25 @@
use Test::More;
use strict;
require 't/test-lib.pm';
my $res;
my $client = LLNG::Manager::Test->new( {
ini => {
logLevel => 'error',
'portalEnablePasswordDisplay' => 1,
'browsersDontStorePassword' => 1
}
}
);
ok( $res = $client->_get( '/', accept => 'text/html' ), 'Display portal' );
ok( $res->[2]->[0] =~ m%<i class="fa fa-eye-slash toggle-password">%,
' toggle password icon found' )
or print STDERR Dumper( $res->[2]->[0] );
count(2);
clean_sessions();
done_testing( count() );

View File

@ -0,0 +1,458 @@
use lib 'inc';
use Test::More;
use strict;
use IO::String;
use LWP::UserAgent;
use LWP::Protocol::PSGI;
use MIME::Base64;
# ------------ ----------------------------- ----------------
# | SAML SP | <-> |SAML IDP + SAML SP (proxy) | <-> | SAML IdP |
# ------------ ----------------------------- ----------------
#
# Use case:
# - login from SP up to SAML IdP
# - logout asked from SP, and propagated up to SAML IdP
# logout between all SAML SP and IdP is done with SOAP binding
BEGIN {
require 't/test-lib.pm';
require 't/oidc-lib.pm';
require 't/saml-lib.pm';
}
my $maintests = 13;
my $debug = 'error';
my ( $sp, $proxy, $idp, $res );
# Overloads method register, for enabling direct POST requests between SP, PROXY and IDP
LWP::Protocol::PSGI->register(
sub {
my $req = Plack::Request->new(@_);
ok( $req->uri =~ m#http://auth.((?:sp|proxy|idp)).com(.*)#, ' REST request' );
my $host = $1;
my $url = $2;
my ( $res, $client );
count(1);
if ( $host eq 'sp' ) {
pass(" Request to SP, endpoint $url");
$client = $sp;
}
elsif ( $host eq 'proxy' ) {
pass(' Request from PROXY to PROXY');
$client = $proxy;
}
elsif ( $host eq 'idp' ) {
pass(' Request to IDP');
$client = $idp;
}
else {
fail(' Aborting REST request (external)');
return HTTP::Response->new(500);
}
if ( $req->method =~ /^post$/i ) {
my $s = $req->content;
ok(
$res = $client->_post(
$url, IO::String->new($s),
length => length($s),
type => $req->header('Content-Type'),
),
' Execute post request'
);
}
else {
ok(
$res = $client->_get(
$url,
custom => {
HTTP_AUTHORIZATION => $req->header('Authorization'),
}
),
' Execute get request'
);
}
ok( $res->[0] == 200, ' Response is 200' );
ok( getHeader( $res, 'Content-Type' ) =~ m#^(application/json|text/xml)#,
' Content is JSON|XML' )
or explain( $res->[1], 'Content-Type => (application/json|text/xml)' );
count(4);
return $res;
}
);
SKIP: {
eval "use Lasso";
if ($@) {
skip 'Lasso not found', $maintests;
}
# Initialization
$idp = register( 'idp', \&idp );
$sp = register( 'sp', \&sp );
$proxy = register( 'proxy', \&proxy );
# LOGIN PROCESS ############################################################
# Query SP for auth
ok( $res = $sp->_get( '/', accept => 'text/html' ), 'Unauth SP request' );
my ( $url, $query ) =
expectRedirection( $res,
qr#http://auth.proxy.com(/saml/singleSignOn)\?(.*)$# );
# Push request to PROXY
switch ('proxy');
ok( $res = $proxy->_get( $url, query => $query, accept => 'text/html' ),
"Push request to PROXY, endpoint $url" );
my $pdataproxy = expectCookie( $res, 'lemonldappdata' );
my ( $urlidp, $queryidp ) =
expectRedirection( $res,
qr#http://auth.idp.com(/saml/singleSignOn)\?(.*)$# );
# Push request to IDP
switch ('idp');
# Try to authenticate to IdP
ok(
$res = $idp->_get( $urlidp, query => $queryidp, accept => 'text/html'),
"SAML Authentication on idp, endpoint $urlidp" );
my $pdataidp = expectCookie( $res, 'lemonldappdata' );
my ( $host, $tmp );
# expectForm (result, host, uri, @requiredfield)
( $host, $tmp, $query ) = expectForm( $res, '#', undef,
( 'url', 'timezone', 'skin', 'user', 'password' ) );
$query =~ s/user=/user=dwho/;
$query =~ s/password=/password=dwho/;
ok(
$res = $idp->_post(
$urlidp,
IO::String->new($query),
accept => 'text/html',
cookie => "lemonldappdata=$pdataidp",
length => length($query),
),
"Post authentication, endpoint $urlidp"
);
$pdataidp = expectCookie( $res, 'lemonldappdata' );
my $cookieidp = expectCookie( $res, 'lemonldap' );
( $host, $url, $query ) =
expectForm( $res, 'auth.proxy.com', '/saml/proxySingleSignOnPost',
'SAMLResponse', 'RelayState' );
my ($resp) = $query =~ qr/SAMLResponse=([^&]*)/;
# Post SAML response to PROXY
switch ('proxy');
ok(
$res = $proxy->_post(
$url, IO::String->new($query),
accept => 'text/html',
length => length($query),
cookie => "lemonldappdata=$pdataproxy",
),
'Post SAML response to PROXY'
);
$pdataproxy = expectCookie( $res, 'lemonldappdata' );
my $cookieproxy = expectCookie( $res, 'lemonldap' );
( $url, $query ) = expectRedirection( $res, qr#^http://auth.proxy.com(/saml)\?*(.*)$# );
ok(
$res = $proxy->_get( $url,
query => $query,
accept => 'text/html',
cookie => "lemonldappdata=$pdataproxy; lemonldap=$cookieproxy" ),
"internal redirection to PROXY, endpoint $url" );
( $host, $url, $query ) =
expectForm( $res, 'auth.sp.com', '/saml/proxySingleSignOnPost', 'SAMLResponse' );
my ($resp) = $query =~ qr/SAMLResponse=([^&]*)/;
# Post SAML response to PROXY
switch ('sp');
ok(
$res = $sp->_post(
$url, IO::String->new($query),
accept => 'text/html',
length => length($query),
),
'Post SAML response to SP'
);
my $cookiesp = expectCookie( $res, 'lemonldap' );
# Authentication done on SP + PROXY + IDP
# LOGOUT PROCESS ###########################################################
$url = '/';
$query = 'logout=1';
ok( $res = $sp->_get( $url, query => $query,
accept => 'text/html',
cookie => "lemonldap=$cookiesp",
),
'Call logout from SP' );
# lemonldap cookie set to "0"
$cookiesp = expectCookie( $res, 'lemonldap' );
ok( $cookiesp eq "0", 'Test empty cookie on SP' );
ok ( $res->[2]->[0] =~ /trmsg="-7"/, 'Test disconnexion message on SP' );
# test connexion on PROXY
switch('proxy');
ok( $res = $proxy->_get( '/', query => '',
accept => 'text/html',
cookie => "lemonldap=$cookieproxy",
),
'Test if still logged on PROXY' );
my ( $urlidp, $queryidp ) =
expectRedirection( $res,
qr#http://auth.idp.com(/saml/singleSignOn)\?(.*)$# );
# test connexion on IDP
switch('idp');
ok( $res = $idp->_get( '/', query => '',
accept => 'text/html',
cookie => "lemonldap=$cookieidp",
),
'Test if still logged on IDP' );
like( $res->[2]->[0], qr/userfield/,
'test presence of user field in form (prove successful logout)' );
}
count($maintests);
clean_sessions();
done_testing( count() );
sub proxy {
return LLNG::Manager::Test->new( {
ini => {
logLevel => $debug,
domain => 'proxy.com',
portal => 'http://auth.proxy.com',
authentication => 'SAML',
userDB => 'Same',
issuerDBOpenIDConnectActivation => "1",
samlOrganizationDisplayName => "proxy",
samlOrganizationName => "proxy",
samlOrganizationURL => "http://www.proxy.com/",
samlServicePrivateKeyEnc => saml_key_proxy_private_enc,
samlServicePrivateKeySig => saml_key_proxy_private_sig,
samlServicePublicKeyEnc => saml_key_proxy_public_enc,
samlServicePublicKeySig => saml_key_proxy_public_sig,
samlIDPSSODescriptorWantAuthnRequestsSigned => 1,
samlSPSSODescriptorWantAssertionsSigned => 1,
samlIDPMetaDataXML => {
'idp' => {
samlIDPMetaDataXML =>
samlIDPComplexMetaDataXML( 'idp', 'HTTP-Redirect', 'SOAP' )
},
},
samlIDPMetaDataOptions => {
'idp' => {
'samlIDPMetaDataOptionsAdaptSessionUtime' => 0,
'samlIDPMetaDataOptionsAllowLoginFromIDP' => 0,
'samlIDPMetaDataOptionsAllowProxiedAuthn' => 0,
'samlIDPMetaDataOptionsCheckAudience' => 1,
'samlIDPMetaDataOptionsCheckSLOMessageSignature' => 1,
'samlIDPMetaDataOptionsCheckSSOMessageSignature' => 1,
'samlIDPMetaDataOptionsCheckTime' => 1,
'samlIDPMetaDataOptionsDisplayName' => 'idp',
'samlIDPMetaDataOptionsEncryptionMode' => 'none',
'samlIDPMetaDataOptionsForceAuthn' => 0,
'samlIDPMetaDataOptionsForceUTF8' => 0,
'samlIDPMetaDataOptionsIcon' => '',
'samlIDPMetaDataOptionsIsPassive' => 0,
'samlIDPMetaDataOptionsNameIDFormat' => '',
'samlIDPMetaDataOptionsRelayStateURL' => 0,
'samlIDPMetaDataOptionsRequestedAuthnContext' => '',
'samlIDPMetaDataOptionsResolutionRule' => '',
'samlIDPMetaDataOptionsSLOBinding' => 'http-soap',
'samlIDPMetaDataOptionsSSOBinding' => 'http-redirect',
'samlIDPMetaDataOptionsSignSLOMessage' => 1,
'samlIDPMetaDataOptionsSignSSOMessage' => 1,
'samlIDPMetaDataOptionsSignatureMethod' => '',
'samlIDPMetaDataOptionsStoreSAMLToken' => 0
}
},
samlIDPMetaDataExportedAttributes => {
'idp' => {
'cn' => '1;cn',
'uid' => '1;uid',
'mail' => '1;mail',
}
},
issuerDBSAMLActivation => 1,
restSessionServer => 1,
samlSPMetaDataOptions => {
sp => {
'samlSPMetaDataOptionsCheckSLOMessageSignature' => 1,
'samlSPMetaDataOptionsCheckSSOMessageSignature' => 1,
'samlSPMetaDataOptionsEnableIDPInitiatedURL' => 0,
'samlSPMetaDataOptionsEncryptionMode' => 'none',
'samlSPMetaDataOptionsForceUTF8' => 1,
'samlSPMetaDataOptionsNameIDFormat' => '',
'samlSPMetaDataOptionsNotOnOrAfterTimeout' => 72000,
'samlSPMetaDataOptionsOneTimeUse' => 0,
'samlSPMetaDataOptionsSessionNotOnOrAfterTimeout' => 72000,
'samlSPMetaDataOptionsSignSLOMessage' => -1,
'samlSPMetaDataOptionsSignSSOMessage' => 1,
'samlSPMetaDataOptionsSignatureMethod' => ''
}
},
samlSPMetaDataXML => {
sp => {
samlSPMetaDataXML =>
samlSPComplexMetaDataXML( 'sp', 'HTTP-Redirect', 'SOAP' ),
'samlSPSSODescriptorAuthnRequestsSigned' => 1,
'samlSPSSODescriptorWantAssertionsSigned' => 1,
}
},
samlSPMetaDataExportedAttributes => {
'sp' => {
'cn' => '1;cn',
'uid' => '1;uid',
'mail' => '1;mail',
}
},
samlSPSSODescriptorAuthnRequestsSigned => 1,
},
}
);
}
sub sp {
return LLNG::Manager::Test->new( {
ini => {
logLevel => $debug,
domain => 'sp.com',
portal => 'http://auth.sp.com',
authentication => 'SAML',
userDB => 'Same',
issuerDBOpenIDConnectActivation => "1",
samlOrganizationDisplayName => "SP",
samlOrganizationName => "SP",
samlOrganizationURL => "http://www.sp.com/",
samlServicePrivateKeyEnc => saml_key_sp_private_enc,
samlServicePrivateKeySig => saml_key_sp_private_sig,
samlServicePublicKeyEnc => saml_key_sp_public_enc,
samlServicePublicKeySig => saml_key_sp_public_sig,
samlIDPSSODescriptorWantAuthnRequestsSigned => 1,
samlSPSSODescriptorWantAssertionsSigned => 1,
samlIDPMetaDataXML => {
'proxy' => {
samlIDPMetaDataXML =>
samlProxyComplexMetaDataXML( 'proxy', 'HTTP-Redirect', 'SOAP' )
},
},
samlIDPMetaDataOptions => {
'proxy' => {
'samlIDPMetaDataOptionsAdaptSessionUtime' => 0,
'samlIDPMetaDataOptionsAllowLoginFromIDP' => 0,
'samlIDPMetaDataOptionsAllowProxiedAuthn' => 0,
'samlIDPMetaDataOptionsCheckAudience' => 1,
'samlIDPMetaDataOptionsCheckSLOMessageSignature' => 1,
'samlIDPMetaDataOptionsCheckSSOMessageSignature' => 1,
'samlIDPMetaDataOptionsCheckTime' => 1,
'samlIDPMetaDataOptionsDisplayName' => 'proxy',
'samlIDPMetaDataOptionsEncryptionMode' => 'none',
'samlIDPMetaDataOptionsForceAuthn' => 0,
'samlIDPMetaDataOptionsForceUTF8' => 0,
'samlIDPMetaDataOptionsIcon' => '',
'samlIDPMetaDataOptionsIsPassive' => 0,
'samlIDPMetaDataOptionsNameIDFormat' => '',
'samlIDPMetaDataOptionsRelayStateURL' => 0,
'samlIDPMetaDataOptionsRequestedAuthnContext' => '',
'samlIDPMetaDataOptionsResolutionRule' => '',
'samlIDPMetaDataOptionsSLOBinding' => 'http-soap',
'samlIDPMetaDataOptionsSSOBinding' => 'http-redirect',
'samlIDPMetaDataOptionsSignSLOMessage' => 1,
'samlIDPMetaDataOptionsSignSSOMessage' => 1,
'samlIDPMetaDataOptionsSignatureMethod' => '',
'samlIDPMetaDataOptionsStoreSAMLToken' => 0
}
},
samlIDPMetaDataExportedAttributes => {
'proxy' => {
'cn' => '1;cn',
'uid' => '1;uid',
'mail' => '1;mail',
}
},
}
}
);
}
sub idp {
return LLNG::Manager::Test->new( {
ini => {
logLevel => $debug,
domain => 'idp.com',
portal => 'http://auth.idp.com',
authentication => 'Demo',
userDB => 'Same',
issuerDBSAMLActivation => 1,
restSessionServer => 1,
samlSPMetaDataOptions => {
proxy => {
'samlSPMetaDataOptionsCheckSLOMessageSignature' => 1,
'samlSPMetaDataOptionsCheckSSOMessageSignature' => 1,
'samlSPMetaDataOptionsEnableIDPInitiatedURL' => 0,
'samlSPMetaDataOptionsEncryptionMode' => 'none',
'samlSPMetaDataOptionsForceUTF8' => 1,
'samlSPMetaDataOptionsNameIDFormat' => '',
'samlSPMetaDataOptionsNotOnOrAfterTimeout' => 72000,
'samlSPMetaDataOptionsOneTimeUse' => 0,
'samlSPMetaDataOptionsSessionNotOnOrAfterTimeout' => 72000,
'samlSPMetaDataOptionsSignSLOMessage' => -1,
'samlSPMetaDataOptionsSignSSOMessage' => 1,
'samlSPMetaDataOptionsSignatureMethod' => ''
}
},
samlSPMetaDataXML => {
proxy => {
samlSPMetaDataXML =>
samlProxyComplexMetaDataXML( 'proxy', 'HTTP-Redirect', 'SOAP' ),
'samlSPSSODescriptorAuthnRequestsSigned' => 1,
'samlSPSSODescriptorWantAssertionsSigned' => 1,
}
},
samlSPMetaDataExportedAttributes => {
'proxy' => {
'cn' => '1;cn',
'uid' => '1;uid',
'mail' => '1;mail',
}
},
samlOrganizationDisplayName => "IDP",
samlOrganizationName => "IDP",
samlOrganizationURL => "http://www.idp.com",
samlServicePublicKeySig => saml_key_idp_public_sig,
samlServicePrivateKeyEnc => saml_key_idp_private_enc,
samlServicePrivateKeySig => saml_key_idp_private_sig,
samlServicePublicKeyEnc => saml_key_idp_public_enc,
samlSPSSODescriptorAuthnRequestsSigned => 1,
},
}
);
}

View File

@ -0,0 +1,477 @@
use lib 'inc';
use Test::More;
use strict;
use IO::String;
use LWP::UserAgent;
use LWP::Protocol::PSGI;
use MIME::Base64;
# ------------ --------------------------- ----------------
# | OIDC RP | <-> | OIDC provider + SAML SP | <-> | SAML IdP |
# ------------ --------------------------- ----------------
#
# Use case:
# - login from RP up to SAML IdP
# - logout asked from RP, and propagated up to SAML IdP
# logout between SAML SP and IdP is done with Redirect binding
BEGIN {
require 't/test-lib.pm';
require 't/oidc-lib.pm';
require 't/saml-lib.pm';
}
my $maintests = 17;
my $debug = 'error';
#my $debug = 'error';
my ( $op, $rp, $idp, $res );
# Overloads method register, for enabling direct POST requests between RP and OP
LWP::Protocol::PSGI->register(
sub {
my $req = Plack::Request->new(@_);
ok( $req->uri =~ m#http://auth.((?:op|rp|idp)).com(.*)#, ' REST request' );
my $host = $1;
my $url = $2;
my ( $res, $client );
count(1);
if ( $host eq 'op' ) {
pass(" Request from RP to OP, endpoint $url");
$client = $op;
}
elsif ( $host eq 'rp' ) {
pass(' Request from OP to RP');
$client = $rp;
}
elsif ( $host eq 'idp' ) {
pass(' Request to IDP');
$client = $idp;
}
else {
fail(' Aborting REST request (external)');
return HTTP::Response->new(500);
}
if ( $req->method =~ /^post$/i ) {
my $s = $req->content;
ok(
$res = $client->_post(
$url, IO::String->new($s),
length => length($s),
type => $req->header('Content-Type'),
),
' Execute post request'
);
}
else {
ok(
$res = $client->_get(
$url,
custom => {
HTTP_AUTHORIZATION => $req->header('Authorization'),
}
),
' Execute get request'
);
}
ok( $res->[0] == 200, ' Response is 200' );
ok( getHeader( $res, 'Content-Type' ) =~ m#^application/json#,
' Content is JSON' )
or explain( $res->[1], 'Content-Type => application/json' );
count(4);
return $res;
}
);
SKIP: {
eval "use Lasso";
if ($@) {
skip 'Lasso not found', $maintests;
}
# Initialization
$op = register( 'op', \&op );
ok(
$res = $op->_get('/oauth2/jwks'),
'Get JWKS, endpoint /oauth2/jwks'
);
expectOK($res);
my $jwks = $res->[2]->[0];
ok(
$res = $op->_get('/.well-known/openid-configuration'),
'Get metadata, endpoint /.well-known/openid-configuration'
);
expectOK($res);
my $metadata = $res->[2]->[0];
$idp = register( 'idp', \&idp );
$rp = register( 'rp', sub { rp( $jwks, $metadata ) } );
# LOGIN PROCESS ############################################################
# Query RP for auth
ok( $res = $rp->_get( '/', accept => 'text/html' ), 'Unauth SP request' );
my ( $url, $query ) =
expectRedirection( $res,
qr#http://auth.op.com(/oauth2/authorize)\?(.*)$# );
# Push request to OP
switch ('op');
ok( $res = $op->_get( $url, query => $query, accept => 'text/html' ),
"Push request to OP, endpoint $url" );
my $pdataop = expectCookie( $res, 'lemonldappdata' );
my ( $urlidp, $queryidp ) =
expectRedirection( $res,
qr#http://auth.idp.com(/saml/singleSignOn)\?(.*)$# );
# Push request to IDP
switch ('idp');
# Try to authenticate to IdP
ok(
$res = $idp->_get( $urlidp, query => $queryidp, accept => 'text/html'),
"SAML Authentication on idp, endpoint $urlidp" );
my $pdataidp = expectCookie( $res, 'lemonldappdata' );
my ( $host, $tmp );
# expectForm (result, host, uri, @requiredfield)
( $host, $tmp, $query ) = expectForm( $res, '#', undef,
( 'url', 'timezone', 'skin', 'user', 'password' ) );
$query =~ s/user=/user=dwho/;
$query =~ s/password=/password=dwho/;
ok(
$res = $idp->_post(
$urlidp,
IO::String->new($query),
accept => 'text/html',
cookie => "lemonldappdata=$pdataidp",
length => length($query),
),
"Post authentication, endpoint $urlidp"
);
$pdataidp = expectCookie( $res, 'lemonldappdata' );
my $cookieidp = expectCookie( $res, 'lemonldap' );
( $host, $url, $query ) =
expectForm( $res, 'auth.op.com', '/saml/proxySingleSignOnPost',
'SAMLResponse', 'RelayState' );
my ($resp) = $query =~ qr/SAMLResponse=([^&]*)/;
# Post SAML response to SP
switch ('op');
ok(
$res = $op->_post(
$url, IO::String->new($query),
accept => 'text/html',
length => length($query),
cookie => "lemonldappdata=$pdataop",
),
'Post SAML response to SP'
);
$pdataop = expectCookie( $res, 'lemonldappdata' );
my $cookieop = expectCookie( $res, 'lemonldap' );
( $url, $query ) = expectRedirection( $res, qr#^http://auth.op.com(/oauth2)\?*(.*)$# );
ok( $res = $op->_get( $url, query => $query,
accept => 'text/html',
cookie => "lemonldappdata=$pdataop; lemonldap=$cookieop",
),
'Call OP from SAML SP' );
$pdataop = expectCookie( $res, 'lemonldappdata' );
# No consent here because we have disabled it (oidcRPMetaDataOptionsBypassConsent)
($query) = expectRedirection( $res, qr#^http://auth.rp.com/?\?(.*)$# );
# Push OP response to RP
switch ('rp');
ok( $res = $rp->_get( '/', query => $query, accept => 'text/html' ),
'Call openidconnectcallback on RP' );
my $cookierp = expectCookie($res, 'lemonldap');
# Authentication done on RP + OP + IDP
# LOGOUT PROCESS ###########################################################
$url = '/';
$query = 'logout=1';
ok( $res = $rp->_get( $url, query => $query,
accept => 'text/html',
cookie => "lemonldap=$cookierp",
),
'Call logout from RP' );
# lemonldap cookie set to "0"
$cookierp = expectCookie( $res, 'lemonldap' );
ok( $cookierp eq "0", 'Test empty cookie on RP' );
# forward logout to OP
( $url, $query ) = expectRedirection( $res, qr#^http://auth.op.com(/.*)\?(.*)$# );
switch ('op');
ok( $res = $op->_get( $url, query => $query,
accept => 'text/html',
cookie => "lemonldappdata=$pdataop; lemonldap=$cookieop",
),
'Forward logout to OP' );
# expectForm (result, host, uri, @requiredfield)
( $host, $tmp, $query ) = expectForm( $res, '#', undef,
( 'post_logout_redirect_uri', 'confirm', 'skin' ) );
ok(
$res = $op->_post(
$url,
IO::String->new($query),
accept => 'text/html',
cookie => "lemonldappdata=$pdataop; lemonldap=$cookieop",
length => length($query),
),
"Post logout confirmation to OP, endpoint $url"
);
# lemonldap cookie set to "0"
$cookieop = expectCookie( $res, 'lemonldap' );
ok( $cookieop eq "0", 'Test empty cookie on OP' );
( $url, $query ) = expectRedirection( $res, qr#^http://auth.idp.com(/.*)\?(.*)$# );
switch ('idp');
ok( $res = $idp->_get( $url, query => $query,
accept => 'text/html',
cookie => "lemonldappdata=$pdataidp; lemonldap=$cookieidp",
),
'redirect to IdP' );
# lemonldap cookie set to "0"
$cookieidp = expectCookie( $res, 'lemonldap' );
ok( $cookieidp eq "0", 'Test empty cookie on IDP' );
( $url, $query ) = expectRedirection( $res, qr#^http://auth.op.com(/.*)\?(.*)$# );
switch ('op');
ok( $res = $op->_get( $url, query => $query,
accept => 'text/html',
cookie => "lemonldappdata=$pdataop; lemonldap=$cookieop",
),
'redirect to OP' );
expectOK($res);
}
count($maintests);
clean_sessions();
done_testing( count() );
sub op {
return LLNG::Manager::Test->new( {
ini => {
logLevel => $debug,
domain => 'op.com',
portal => 'http://auth.op.com',
authentication => 'SAML',
userDB => 'Same',
issuerDBOpenIDConnectActivation => "1",
oidcRPMetaDataExportedVars => {
rp => {
email => "mail",
family_name => "cn",
name => "cn"
}
},
oidcServiceAllowHybridFlow => 1,
oidcServiceAllowImplicitFlow => 1,
oidcServiceAllowAuthorizationCodeFlow => 1,
oidcRPMetaDataOptions => {
rp => {
oidcRPMetaDataOptionsDisplayName => "RP",
oidcRPMetaDataOptionsIDTokenExpiration => 3600,
oidcRPMetaDataOptionsClientID => "rpid",
oidcRPMetaDataOptionsIDTokenSignAlg => "HS512",
oidcRPMetaDataOptionsBypassConsent => 1,
oidcRPMetaDataOptionsClientSecret => "rpsecret",
oidcRPMetaDataOptionsUserIDAttr => "",
oidcRPMetaDataOptionsAccessTokenExpiration => 3600
}
},
oidcOPMetaDataOptions => {},
oidcOPMetaDataJSON => {},
oidcOPMetaDataJWKS => {},
oidcServiceMetaDataAuthnContext => {
'loa-4' => 4,
'loa-1' => 1,
'loa-5' => 5,
'loa-2' => 2,
'loa-3' => 3
},
oidcServicePrivateKeySig => oidc_key_op_private_sig,
oidcServicePublicKeySig => oidc_key_op_public_sig,
samlOrganizationDisplayName => "SP",
samlOrganizationName => "SP",
samlOrganizationURL => "http://www.op.com/",
samlServicePrivateKeyEnc => saml_key_sp_private_enc,
samlServicePrivateKeySig => saml_key_sp_private_sig,
samlServicePublicKeyEnc => saml_key_sp_public_enc,
samlServicePublicKeySig => saml_key_sp_public_sig,
samlIDPSSODescriptorWantAuthnRequestsSigned => 1,
samlSPSSODescriptorWantAssertionsSigned => 1,
samlIDPMetaDataXML => {
'idp' => {
samlIDPMetaDataXML =>
samlIDPMetaDataXML( 'idp', 'HTTP-Redirect' )
},
},
samlIDPMetaDataOptions => {
'idp' => {
'samlIDPMetaDataOptionsAdaptSessionUtime' => 0,
'samlIDPMetaDataOptionsAllowLoginFromIDP' => 0,
'samlIDPMetaDataOptionsAllowProxiedAuthn' => 0,
'samlIDPMetaDataOptionsCheckAudience' => 1,
'samlIDPMetaDataOptionsCheckSLOMessageSignature' => 1,
'samlIDPMetaDataOptionsCheckSSOMessageSignature' => 1,
'samlIDPMetaDataOptionsCheckTime' => 1,
'samlIDPMetaDataOptionsDisplayName' => 'idp',
'samlIDPMetaDataOptionsEncryptionMode' => 'none',
'samlIDPMetaDataOptionsForceAuthn' => 0,
'samlIDPMetaDataOptionsForceUTF8' => 0,
'samlIDPMetaDataOptionsIcon' => '',
'samlIDPMetaDataOptionsIsPassive' => 0,
'samlIDPMetaDataOptionsNameIDFormat' => '',
'samlIDPMetaDataOptionsRelayStateURL' => 0,
'samlIDPMetaDataOptionsRequestedAuthnContext' => '',
'samlIDPMetaDataOptionsResolutionRule' => '',
'samlIDPMetaDataOptionsSLOBinding' => 'http-redirect',
'samlIDPMetaDataOptionsSSOBinding' => 'http-redirect',
'samlIDPMetaDataOptionsSignSLOMessage' => 1,
'samlIDPMetaDataOptionsSignSSOMessage' => 1,
'samlIDPMetaDataOptionsSignatureMethod' => '',
'samlIDPMetaDataOptionsStoreSAMLToken' => 0
}
},
samlIDPMetaDataExportedAttributes => {
'idp' => {
'cn' => '1;cn',
'uid' => '1;uid'
}
},
}
}
);
}
sub rp {
my ( $jwks, $metadata ) = @_;
return LLNG::Manager::Test->new( {
ini => {
logLevel => $debug,
domain => 'rp.com',
portal => 'http://auth.rp.com',
authentication => 'OpenIDConnect',
userDB => 'Same',
restSessionServer => 1,
oidcOPMetaDataExportedVars => {
op => {
cn => "name",
uid => "sub",
sn => "family_name",
mail => "email"
}
},
oidcOPMetaDataOptions => {
op => {
oidcOPMetaDataOptionsJWKSTimeout => 0,
oidcOPMetaDataOptionsClientSecret => "rpsecret",
oidcOPMetaDataOptionsScope => "openid profile",
oidcOPMetaDataOptionsStoreIDToken => 0,
oidcOPMetaDataOptionsDisplay => "",
oidcOPMetaDataOptionsClientID => "rpid",
oidcOPMetaDataOptionsConfigurationURI =>
"https://auth.op.com/.well-known/openid-configuration"
}
},
oidcOPMetaDataJWKS => {
op => $jwks,
},
oidcOPMetaDataJSON => {
op => $metadata,
}
}
}
);
}
sub idp {
return LLNG::Manager::Test->new( {
ini => {
logLevel => $debug,
domain => 'idp.com',
portal => 'http://auth.idp.com',
authentication => 'Demo',
userDB => 'Same',
issuerDBSAMLActivation => 1,
restSessionServer => 1,
samlSPMetaDataOptions => {
sp => {
'samlSPMetaDataOptionsCheckSLOMessageSignature' => 1,
'samlSPMetaDataOptionsCheckSSOMessageSignature' => 1,
'samlSPMetaDataOptionsEnableIDPInitiatedURL' => 0,
'samlSPMetaDataOptionsEncryptionMode' => 'none',
'samlSPMetaDataOptionsForceUTF8' => 1,
'samlSPMetaDataOptionsNameIDFormat' => '',
'samlSPMetaDataOptionsNotOnOrAfterTimeout' => 72000,
'samlSPMetaDataOptionsOneTimeUse' => 0,
'samlSPMetaDataOptionsSessionNotOnOrAfterTimeout' => 72000,
'samlSPMetaDataOptionsSignSLOMessage' => -1,
'samlSPMetaDataOptionsSignSSOMessage' => 1,
'samlSPMetaDataOptionsSignatureMethod' => ''
}
},
samlSPMetaDataXML => {
sp => {
samlSPMetaDataXML =>
samlSPMetaDataXML( 'op', 'HTTP-Redirect' ),
'samlSPSSODescriptorAuthnRequestsSigned' => 1,
'samlSPSSODescriptorWantAssertionsSigned' => 1,
}
},
samlSPMetaDataExportedAttributes => {
'sp' => {
'cn' => '1;cn',
'uid' => '1;uid'
}
},
samlOrganizationDisplayName => "IDP",
samlOrganizationName => "IDP",
samlOrganizationURL => "http://www.idp.com",
samlServicePublicKeySig => saml_key_idp_public_sig,
samlServicePrivateKeyEnc => saml_key_idp_private_enc,
samlServicePrivateKeySig => saml_key_idp_private_sig,
samlServicePublicKeyEnc => saml_key_idp_public_enc,
samlSPSSODescriptorAuthnRequestsSigned => 1,
},
}
);
}

View File

@ -0,0 +1,475 @@
use lib 'inc';
use Test::More;
use strict;
use IO::String;
use LWP::UserAgent;
use LWP::Protocol::PSGI;
use MIME::Base64;
# ------------ --------------------------- ----------------
# | OIDC RP | <-> | OIDC provider + SAML SP | <-> | SAML IdP |
# ------------ --------------------------- ----------------
#
# Use case:
# - login from RP up to SAML IdP
# - logout asked from RP, and propagated up to SAML IdP
# logout between SAML SP and IdP is done with SOAP binding
BEGIN {
require 't/test-lib.pm';
require 't/oidc-lib.pm';
require 't/saml-lib.pm';
}
my $maintests = 17;
my $debug = 'error';
#my $debug = 'error';
my ( $op, $rp, $idp, $res );
# Overloads method register, for enabling direct POST requests between RP and OP
LWP::Protocol::PSGI->register(
sub {
my $req = Plack::Request->new(@_);
ok( $req->uri =~ m#http://auth.((?:op|rp|idp)).com(.*)#, ' REST request' );
my $host = $1;
my $url = $2;
my ( $res, $client );
count(1);
if ( $host eq 'op' ) {
pass(" Request from RP to OP, endpoint $url");
$client = $op;
}
elsif ( $host eq 'rp' ) {
pass(' Request from OP to RP');
$client = $rp;
}
elsif ( $host eq 'idp' ) {
pass(' Request to IDP');
$client = $idp;
}
else {
fail(' Aborting REST request (external)');
return HTTP::Response->new(500);
}
if ( $req->method =~ /^post$/i ) {
my $s = $req->content;
ok(
$res = $client->_post(
$url, IO::String->new($s),
length => length($s),
type => $req->header('Content-Type'),
),
' Execute post request'
);
}
else {
ok(
$res = $client->_get(
$url,
custom => {
HTTP_AUTHORIZATION => $req->header('Authorization'),
}
),
' Execute get request'
);
}
ok( $res->[0] == 200, ' Response is 200' );
ok( getHeader( $res, 'Content-Type' ) =~ m#^(application/json|text/xml)#,
' Content is JSON|XML' )
or explain( $res->[1], 'Content-Type => (application/json|text/xml)' );
count(4);
return $res;
}
);
SKIP: {
eval "use Lasso";
if ($@) {
skip 'Lasso not found', $maintests;
}
# Initialization
$op = register( 'op', \&op );
ok(
$res = $op->_get('/oauth2/jwks'),
'Get JWKS, endpoint /oauth2/jwks'
);
expectOK($res);
my $jwks = $res->[2]->[0];
ok(
$res = $op->_get('/.well-known/openid-configuration'),
'Get metadata, endpoint /.well-known/openid-configuration'
);
expectOK($res);
my $metadata = $res->[2]->[0];
$idp = register( 'idp', \&idp );
$rp = register( 'rp', sub { rp( $jwks, $metadata ) } );
# LOGIN PROCESS ############################################################
# Query RP for auth
ok( $res = $rp->_get( '/', accept => 'text/html' ), 'Unauth SP request' );
my ( $url, $query ) =
expectRedirection( $res,
qr#http://auth.op.com(/oauth2/authorize)\?(.*)$# );
# Push request to OP
switch ('op');
ok( $res = $op->_get( $url, query => $query, accept => 'text/html' ),
"Push request to OP, endpoint $url" );
my $pdataop = expectCookie( $res, 'lemonldappdata' );
my ( $urlidp, $queryidp ) =
expectRedirection( $res,
qr#http://auth.idp.com(/saml/singleSignOn)\?(.*)$# );
# Push request to IDP
switch ('idp');
# Try to authenticate to IdP
ok(
$res = $idp->_get( $urlidp, query => $queryidp, accept => 'text/html'),
"SAML Authentication on idp, endpoint $urlidp" );
my $pdataidp = expectCookie( $res, 'lemonldappdata' );
my ( $host, $tmp );
# expectForm (result, host, uri, @requiredfield)
( $host, $tmp, $query ) = expectForm( $res, '#', undef,
( 'url', 'timezone', 'skin', 'user', 'password' ) );
$query =~ s/user=/user=dwho/;
$query =~ s/password=/password=dwho/;
ok(
$res = $idp->_post(
$urlidp,
IO::String->new($query),
accept => 'text/html',
cookie => "lemonldappdata=$pdataidp",
length => length($query),
),
"Post authentication, endpoint $urlidp"
);
$pdataidp = expectCookie( $res, 'lemonldappdata' );
my $cookieidp = expectCookie( $res, 'lemonldap' );
( $host, $url, $query ) =
expectForm( $res, 'auth.op.com', '/saml/proxySingleSignOnPost',
'SAMLResponse', 'RelayState' );
my ($resp) = $query =~ qr/SAMLResponse=([^&]*)/;
# Post SAML response to SP
switch ('op');
ok(
$res = $op->_post(
$url, IO::String->new($query),
accept => 'text/html',
length => length($query),
cookie => "lemonldappdata=$pdataop",
),
'Post SAML response to SP'
);
$pdataop = expectCookie( $res, 'lemonldappdata' );
my $cookieop = expectCookie( $res, 'lemonldap' );
( $url, $query ) = expectRedirection( $res, qr#^http://auth.op.com(/oauth2)\?*(.*)$# );
ok( $res = $op->_get( $url, query => $query,
accept => 'text/html',
cookie => "lemonldappdata=$pdataop; lemonldap=$cookieop",
),
'Call OP from SAML SP' );
$pdataop = expectCookie( $res, 'lemonldappdata' );
# No consent here because we have disabled it (oidcRPMetaDataOptionsBypassConsent)
($query) = expectRedirection( $res, qr#^http://auth.rp.com/?\?(.*)$# );
# Push OP response to RP
switch ('rp');
ok( $res = $rp->_get( '/', query => $query, accept => 'text/html' ),
'Call openidconnectcallback on RP' );
my $cookierp = expectCookie($res, 'lemonldap');
# Authentication done on RP + OP + IDP
# LOGOUT PROCESS ###########################################################
$url = '/';
$query = 'logout=1';
ok( $res = $rp->_get( $url, query => $query,
accept => 'text/html',
cookie => "lemonldap=$cookierp",
),
'Call logout from RP' );
# lemonldap cookie set to "0"
$cookierp = expectCookie( $res, 'lemonldap' );
ok( $cookierp eq "0", 'Test empty cookie on RP' );
# forward logout to OP
( $url, $query ) = expectRedirection( $res, qr#^http://auth.op.com(/.*)\?(.*)$# );
switch ('op');
ok( $res = $op->_get( $url, query => $query,
accept => 'text/html',
cookie => "lemonldappdata=$pdataop; lemonldap=$cookieop",
),
'Forward logout to OP' );
# expectForm (result, host, uri, @requiredfield)
( $host, $tmp, $query ) = expectForm( $res, '#', undef,
( 'post_logout_redirect_uri', 'confirm', 'skin' ) );
ok(
$res = $op->_post(
$url,
IO::String->new($query),
accept => 'text/html',
cookie => "lemonldappdata=$pdataop; lemonldap=$cookieop",
length => length($query),
),
"Post logout confirmation to OP, endpoint $url"
);
# lemonldap cookie set to "0"
$cookieop = expectCookie( $res, 'lemonldap' );
ok( $cookieop eq "0", 'Test empty cookie on OP' );
( $url, $query ) = expectRedirection( $res, qr#^http://auth.rp.com(/?.*)\?(.*)$# );
switch ('rp');
ok( $res = $rp->_get( $url, query => $query,
accept => 'text/html',
cookie => "lemonldap=$cookierp",
),
'redirect to RP' );
expectOK($res);
# test connexion on IDP
switch('idp');
ok( $res = $idp->_get( '/', query => '',
accept => 'text/html',
cookie => "lemonldap=$cookieidp",
),
'Test if still logged on IDP' );
like( $res->[2]->[0], qr/userfield/,
'test presence of user field in form (prove successful logout)' );
}
count($maintests);
clean_sessions();
done_testing( count() );
sub op {
return LLNG::Manager::Test->new( {
ini => {
logLevel => $debug,
domain => 'op.com',
portal => 'http://auth.op.com',
authentication => 'SAML',
userDB => 'Same',
issuerDBOpenIDConnectActivation => "1",
oidcRPMetaDataExportedVars => {
rp => {
email => "mail",
family_name => "cn",
name => "cn"
}
},
oidcServiceAllowHybridFlow => 1,
oidcServiceAllowImplicitFlow => 1,
oidcServiceAllowAuthorizationCodeFlow => 1,
oidcRPMetaDataOptions => {
rp => {
oidcRPMetaDataOptionsDisplayName => "RP",
oidcRPMetaDataOptionsIDTokenExpiration => 3600,
oidcRPMetaDataOptionsClientID => "rpid",
oidcRPMetaDataOptionsIDTokenSignAlg => "HS512",
oidcRPMetaDataOptionsBypassConsent => 1,
oidcRPMetaDataOptionsClientSecret => "rpsecret",
oidcRPMetaDataOptionsUserIDAttr => "",
oidcRPMetaDataOptionsAccessTokenExpiration => 3600,
oidcRPMetaDataOptionsPostLogoutRedirectUris => 'http://auth.rp.com?logout=1',
}
},
oidcOPMetaDataOptions => {},
oidcOPMetaDataJSON => {},
oidcOPMetaDataJWKS => {},
oidcServiceMetaDataAuthnContext => {
'loa-4' => 4,
'loa-1' => 1,
'loa-5' => 5,
'loa-2' => 2,
'loa-3' => 3
},
oidcServicePrivateKeySig => oidc_key_op_private_sig,
oidcServicePublicKeySig => oidc_key_op_public_sig,
samlOrganizationDisplayName => "SP",
samlOrganizationName => "SP",
samlOrganizationURL => "http://www.op.com/",
samlServicePrivateKeyEnc => saml_key_sp_private_enc,
samlServicePrivateKeySig => saml_key_sp_private_sig,
samlServicePublicKeyEnc => saml_key_sp_public_enc,
samlServicePublicKeySig => saml_key_sp_public_sig,
samlIDPSSODescriptorWantAuthnRequestsSigned => 1,
samlSPSSODescriptorWantAssertionsSigned => 1,
samlIDPMetaDataXML => {
'idp' => {
samlIDPMetaDataXML =>
samlIDPComplexMetaDataXML( 'idp', 'HTTP-Redirect', 'SOAP' )
},
},
samlIDPMetaDataOptions => {
'idp' => {
'samlIDPMetaDataOptionsAdaptSessionUtime' => 0,
'samlIDPMetaDataOptionsAllowLoginFromIDP' => 0,
'samlIDPMetaDataOptionsAllowProxiedAuthn' => 0,
'samlIDPMetaDataOptionsCheckAudience' => 1,
'samlIDPMetaDataOptionsCheckSLOMessageSignature' => 1,
'samlIDPMetaDataOptionsCheckSSOMessageSignature' => 1,
'samlIDPMetaDataOptionsCheckTime' => 1,
'samlIDPMetaDataOptionsDisplayName' => 'idp',
'samlIDPMetaDataOptionsEncryptionMode' => 'none',
'samlIDPMetaDataOptionsForceAuthn' => 0,
'samlIDPMetaDataOptionsForceUTF8' => 0,
'samlIDPMetaDataOptionsIcon' => '',
'samlIDPMetaDataOptionsIsPassive' => 0,
'samlIDPMetaDataOptionsNameIDFormat' => '',
'samlIDPMetaDataOptionsRelayStateURL' => 0,
'samlIDPMetaDataOptionsRequestedAuthnContext' => '',
'samlIDPMetaDataOptionsResolutionRule' => '',
'samlIDPMetaDataOptionsSLOBinding' => 'http-soap',
'samlIDPMetaDataOptionsSSOBinding' => 'http-redirect',
'samlIDPMetaDataOptionsSignSLOMessage' => 1,
'samlIDPMetaDataOptionsSignSSOMessage' => 1,
'samlIDPMetaDataOptionsSignatureMethod' => '',
'samlIDPMetaDataOptionsStoreSAMLToken' => 0
}
},
samlIDPMetaDataExportedAttributes => {
'idp' => {
'cn' => '1;cn',
'uid' => '1;uid'
}
},
}
}
);
}
sub rp {
my ( $jwks, $metadata ) = @_;
return LLNG::Manager::Test->new( {
ini => {
logLevel => $debug,
domain => 'rp.com',
portal => 'http://auth.rp.com',
authentication => 'OpenIDConnect',
userDB => 'Same',
restSessionServer => 1,
oidcOPMetaDataExportedVars => {
op => {
cn => "name",
uid => "sub",
sn => "family_name",
mail => "email"
}
},
oidcOPMetaDataOptions => {
op => {
oidcOPMetaDataOptionsJWKSTimeout => 0,
oidcOPMetaDataOptionsClientSecret => "rpsecret",
oidcOPMetaDataOptionsScope => "openid profile",
oidcOPMetaDataOptionsStoreIDToken => 0,
oidcOPMetaDataOptionsDisplay => "",
oidcOPMetaDataOptionsClientID => "rpid",
oidcOPMetaDataOptionsConfigurationURI =>
"https://auth.op.com/.well-known/openid-configuration"
}
},
oidcOPMetaDataJWKS => {
op => $jwks,
},
oidcOPMetaDataJSON => {
op => $metadata,
}
}
}
);
}
sub idp {
return LLNG::Manager::Test->new( {
ini => {
logLevel => $debug,
domain => 'idp.com',
portal => 'http://auth.idp.com',
authentication => 'Demo',
userDB => 'Same',
issuerDBSAMLActivation => 1,
restSessionServer => 1,
samlSPMetaDataOptions => {
sp => {
'samlSPMetaDataOptionsCheckSLOMessageSignature' => 1,
'samlSPMetaDataOptionsCheckSSOMessageSignature' => 1,
'samlSPMetaDataOptionsEnableIDPInitiatedURL' => 0,
'samlSPMetaDataOptionsEncryptionMode' => 'none',
'samlSPMetaDataOptionsForceUTF8' => 1,
'samlSPMetaDataOptionsNameIDFormat' => '',
'samlSPMetaDataOptionsNotOnOrAfterTimeout' => 72000,
'samlSPMetaDataOptionsOneTimeUse' => 0,
'samlSPMetaDataOptionsSessionNotOnOrAfterTimeout' => 72000,
'samlSPMetaDataOptionsSignSLOMessage' => -1,
'samlSPMetaDataOptionsSignSSOMessage' => 1,
'samlSPMetaDataOptionsSignatureMethod' => ''
}
},
samlSPMetaDataXML => {
sp => {
samlSPMetaDataXML =>
samlSPComplexMetaDataXML( 'op', 'HTTP-Redirect', 'SOAP' ),
'samlSPSSODescriptorAuthnRequestsSigned' => 1,
'samlSPSSODescriptorWantAssertionsSigned' => 1,
}
},
samlSPMetaDataExportedAttributes => {
'sp' => {
'cn' => '1;cn',
'uid' => '1;uid'
}
},
samlOrganizationDisplayName => "IDP",
samlOrganizationName => "IDP",
samlOrganizationURL => "http://www.idp.com",
samlServicePublicKeySig => saml_key_idp_public_sig,
samlServicePrivateKeyEnc => saml_key_idp_private_enc,
samlServicePrivateKeySig => saml_key_idp_private_sig,
samlServicePublicKeyEnc => saml_key_idp_public_enc,
samlSPSSODescriptorAuthnRequestsSigned => 1,
},
}
);
}

View File

@ -0,0 +1,163 @@
use Test::More;
use strict;
use IO::String;
require 't/test-lib.pm';
my $maintests = 19;
SKIP: {
eval { require Convert::Base32 };
if ($@) {
skip 'Convert::Base32 is missing', $maintests;
}
require Lemonldap::NG::Common::TOTP;
my $client = LLNG::Manager::Test->new( {
ini => {
logLevel => 'error',
totp2fSelfRegistration => 1,
totp2fActivation => 1,
sfRequired => 1,
sfRegisterTimeout => 600,
tokenUseGlobalStorage => 1,
issuerDBCASActivation => 1,
issuersTimeout => 600,
}
}
);
my $res;
# Try to authenticate
# -------------------
ok(
$res = $client->_get(
'/cas/login',
query => buildForm( {
service => "http://cas.example.com/",
}
),
accept => 'text/html',
length => 23
),
'Auth query'
);
my $pdata = 'lemonldappdata=' . expectCookie( $res, 'lemonldappdata' );
# Post login form
# ---------------
ok(
$res = $client->_post(
'/',
IO::String->new('user=dwho&password=dwho'),
cookie => $pdata,
length => 23
),
'Auth query'
);
expectRedirection( $res, qr'http://auth.example.com/+2fregisters/?' );
$pdata = 'lemonldappdata=' . expectCookie( $res, 'lemonldappdata' );
# Follow redirection to TOTP form
ok( $res = $client->_get( '/2fregisters', cookie => $pdata ),
'Follow redirection to /2fregisters' );
ok( $res->[2]->[0] =~ m#/2fregisters/totp#, 'Found TOTP link' );
# TOTP form
ok(
$res = $client->_get(
'/2fregisters/totp',
cookie => $pdata,
accept => 'text/html',
),
'Form registration'
);
ok( $res->[2]->[0] =~ /totpregistration\.(?:min\.)?js/, 'Found TOTP js' );
# JS query
ok(
$res = $client->_post(
'/2fregisters/totp/getkey', IO::String->new(''),
cookie => $pdata,
length => 0,
),
'Get new key'
);
eval { $res = JSON::from_json( $res->[2]->[0] ) };
ok( not($@), 'Content is JSON' )
or explain( $res->[2]->[0], 'JSON content' );
my ( $key, $token );
ok( $key = $res->{secret}, 'Found secret' );
ok( $token = $res->{token}, 'Found token' );
$key = Convert::Base32::decode_base32($key);
# Wait for regular form timeout to expire
Time::Fake->offset("+5m");
# Post code
my $code;
ok( $code = Lemonldap::NG::Common::TOTP::_code( undef, $key, 0, 30, 6 ),
'Code' );
ok( $code =~ /^\d{6}$/, 'Code contains 6 digits' );
my $s = "code=$code&token=$token";
ok(
$res = $client->_post(
'/2fregisters/totp/verify',
IO::String->new($s),
length => length($s),
cookie => $pdata,
),
'Post code'
);
$pdata = 'lemonldappdata=' . expectCookie( $res, 'lemonldappdata' );
eval { $res = JSON::from_json( $res->[2]->[0] ) };
ok( not($@), 'Content is JSON' )
or explain( $res->[2]->[0], 'JSON content' );
ok( $res->{result} == 1, 'Key is registered' );
# Try to sign-in
ok(
$res = $client->_post(
'/',
IO::String->new('user=dwho&password=dwho'),
length => 23,
cookie => $pdata,
accept => 'text/html',
),
'Auth query'
);
$pdata = 'lemonldappdata=' . expectCookie( $res, 'lemonldappdata' );
my ( $host, $url, $query ) =
expectForm( $res, undef, '/totp2fcheck', 'token' );
ok( $code = Lemonldap::NG::Common::TOTP::_code( undef, $key, 0, 30, 6 ),
'Code' );
$query =~ s/code=/code=$code/;
ok(
$res = $client->_post(
'/totp2fcheck', IO::String->new($query),
length => length($query),
cookie => $pdata,
accept => 'text/html',
),
'Post code'
);
my $id = expectCookie($res);
$pdata = expectCookie( $res, 'lemonldappdata' );
expectRedirection( $res, qr'http://auth.example.com//cas' );
# Follow redirection to TOTP form
ok(
$res = $client->_get(
'//cas',
cookie => "lemonldap=$id; lemonldappdata=$pdata",
accept => 'text/html',
),
'Follow redirection to issuer'
);
expectRedirection( $res, qr#^http://cas.example.com/\?(ticket.*)# );
}
count($maintests);
clean_sessions();
done_testing( count() );

View File

@ -437,6 +437,190 @@ EOF
;
}
sub samlSPComplexMetaDataXML {
my ( $name, $typeSSO, $typeSLO ) = @_;
my $org = uc($name);
return <<"EOF"
<?xml version="1.0"?>
<EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
entityID="http://auth.$name.com/saml/metadata">
<IDPSSODescriptor WantAuthnRequestsSigned="true"
protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<KeyDescriptor use="signing">
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:KeyValue>
<RSAKeyValue xmlns="http://www.w3.org/2000/09/xmldsig#">
<Modulus>
u4iToYAEmWQxgZDihGVzMMql1elPn37domWcvXeU2E4yt2hh5jkQHiFjgodfOlNeRIw5QJVlUBwr
+CQvbaKRFXd7BrOhQIDC0TZPRVB0XHarUtsCuDekN4/2GKSzHsoToKUVPWq9thsuek3xkpsJGZNX
7bglfEc9+QQpYTqN1rkdN1PVU0epNMokFFGho5pLRqLUV5+I/QXAL49jfTjaSxsp4UndTI8/+mGS
RSq+nrT2zyQRM/vkj5vR9ZVz67HO/+Wk3Mx6RAwkVcMdgMAqCq8odmbI0yCRZiTL9ybKWRKqWJoK
J0p5+Q2fPEBPupQZR09Jt/JPuLVSsGfCxi9Nqw==</Modulus>
<Exponent>AQAB</Exponent>
</RSAKeyValue>
</ds:KeyValue>
</ds:KeyInfo>
</KeyDescriptor>
<KeyDescriptor use="encryption">
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:KeyValue>
<RSAKeyValue xmlns="http://www.w3.org/2000/09/xmldsig#">
<Modulus>
sRaod2RZ8hMFBl+VhsnhyPM8l/Fj1obnBxfQIaWuHFIFfXiGe/CYHuZ5QJQLnZxHMJX6LL3Sh+Us
og3p0jpijpcg0QgfBSEkfopKTgReYN8DiDIll0rV1XdTni7E85Nd1YyNy3ui/ZD+UShWwqu6jLVL
R+QUm+/1LIKYb3OCBTvOlY7xHoP6NSU1+Mr+YzGBUacdO2vnNxe/PQhxIeP1zO0njuqGHkwEpy8r
UWRZbbDn31TmKjqlhgtsz5HPhbRaYEExhyepKgBiNz+RyxtYXVhuG8OrWQDoS5gYHSjdw1CTJyix
eJwyoqA9RGYguG5nh9zndi3LWAh7Z0lx+tIz+w==</Modulus>
<Exponent>AQAB</Exponent>
</RSAKeyValue>
</ds:KeyValue>
</ds:KeyInfo>
</KeyDescriptor>
<ArtifactResolutionService isDefault="true" index="0"
Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP"
Location="http://auth.$name.com/saml/artifact" />
<SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:$typeSLO"
Location="http://auth.$name.com/saml/singleLogout"
ResponseLocation="http://auth.$name.com/saml/singleLogoutReturn" />
<NameIDFormat>
urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</NameIDFormat>
<NameIDFormat>
urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName</NameIDFormat>
<NameIDFormat>
urn:oasis:names:tc:SAML:1.1:nameid-format:WindowsDomainQualifiedName</NameIDFormat>
<NameIDFormat>
urn:oasis:names:tc:SAML:2.0:nameid-format:kerberos</NameIDFormat>
<NameIDFormat>
urn:oasis:names:tc:SAML:2.0:nameid-format:entity</NameIDFormat>
<NameIDFormat>
urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>
<SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:$typeSSO"
Location="http://auth.$name.com/saml/singleSignOn" />
</IDPSSODescriptor>
<SPSSODescriptor AuthnRequestsSigned="true"
WantAssertionsSigned="true"
protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<KeyDescriptor use="signing">
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:KeyValue>
<RSAKeyValue xmlns="http://www.w3.org/2000/09/xmldsig#">
<Modulus>
u4iToYAEmWQxgZDihGVzMMql1elPn37domWcvXeU2E4yt2hh5jkQHiFjgodfOlNeRIw5QJVlUBwr
+CQvbaKRFXd7BrOhQIDC0TZPRVB0XHarUtsCuDekN4/2GKSzHsoToKUVPWq9thsuek3xkpsJGZNX
7bglfEc9+QQpYTqN1rkdN1PVU0epNMokFFGho5pLRqLUV5+I/QXAL49jfTjaSxsp4UndTI8/+mGS
RSq+nrT2zyQRM/vkj5vR9ZVz67HO/+Wk3Mx6RAwkVcMdgMAqCq8odmbI0yCRZiTL9ybKWRKqWJoK
J0p5+Q2fPEBPupQZR09Jt/JPuLVSsGfCxi9Nqw==</Modulus>
<Exponent>AQAB</Exponent>
</RSAKeyValue>
</ds:KeyValue>
</ds:KeyInfo>
</KeyDescriptor>
<KeyDescriptor use="encryption">
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:KeyValue>
<RSAKeyValue xmlns="http://www.w3.org/2000/09/xmldsig#">
<Modulus>
sRaod2RZ8hMFBl+VhsnhyPM8l/Fj1obnBxfQIaWuHFIFfXiGe/CYHuZ5QJQLnZxHMJX6LL3Sh+Us
og3p0jpijpcg0QgfBSEkfopKTgReYN8DiDIll0rV1XdTni7E85Nd1YyNy3ui/ZD+UShWwqu6jLVL
R+QUm+/1LIKYb3OCBTvOlY7xHoP6NSU1+Mr+YzGBUacdO2vnNxe/PQhxIeP1zO0njuqGHkwEpy8r
UWRZbbDn31TmKjqlhgtsz5HPhbRaYEExhyepKgBiNz+RyxtYXVhuG8OrWQDoS5gYHSjdw1CTJyix
eJwyoqA9RGYguG5nh9zndi3LWAh7Z0lx+tIz+w==</Modulus>
<Exponent>AQAB</Exponent>
</RSAKeyValue>
</ds:KeyValue>
</ds:KeyInfo>
</KeyDescriptor>
<ArtifactResolutionService isDefault="true" index="0"
Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP"
Location="http://auth.$name.com/saml/artifact" />
<SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:$typeSLO"
Location="http://auth.$name.com/saml/proxySingleLogout"
ResponseLocation="http://auth.$name.com/saml/proxySingleLogoutReturn" />
<NameIDFormat>
urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</NameIDFormat>
<NameIDFormat>
urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName</NameIDFormat>
<NameIDFormat>
urn:oasis:names:tc:SAML:1.1:nameid-format:WindowsDomainQualifiedName</NameIDFormat>
<NameIDFormat>
urn:oasis:names:tc:SAML:2.0:nameid-format:kerberos</NameIDFormat>
<NameIDFormat>
urn:oasis:names:tc:SAML:2.0:nameid-format:entity</NameIDFormat>
<NameIDFormat>
urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>
<AssertionConsumerService isDefault="true" index="0"
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
Location="http://auth.$name.com/saml/proxySingleSignOnPost" />
<AssertionConsumerService isDefault="false" index="1"
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact"
Location="http://auth.$name.com/saml/proxySingleSignOnArtifact" />
<AssertionConsumerService isDefault="true" index="2"
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
Location="http://auth.alternate.com/saml/proxySingleSignOnPost" />
</SPSSODescriptor>
<AttributeAuthorityDescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<KeyDescriptor use="signing">
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:KeyValue>
<RSAKeyValue xmlns="http://www.w3.org/2000/09/xmldsig#">
<Modulus>
u4iToYAEmWQxgZDihGVzMMql1elPn37domWcvXeU2E4yt2hh5jkQHiFjgodfOlNeRIw5QJVlUBwr
+CQvbaKRFXd7BrOhQIDC0TZPRVB0XHarUtsCuDekN4/2GKSzHsoToKUVPWq9thsuek3xkpsJGZNX
7bglfEc9+QQpYTqN1rkdN1PVU0epNMokFFGho5pLRqLUV5+I/QXAL49jfTjaSxsp4UndTI8/+mGS
RSq+nrT2zyQRM/vkj5vR9ZVz67HO/+Wk3Mx6RAwkVcMdgMAqCq8odmbI0yCRZiTL9ybKWRKqWJoK
J0p5+Q2fPEBPupQZR09Jt/JPuLVSsGfCxi9Nqw==</Modulus>
<Exponent>AQAB</Exponent>
</RSAKeyValue>
</ds:KeyValue>
</ds:KeyInfo>
</KeyDescriptor>
<KeyDescriptor use="encryption">
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:KeyValue>
<RSAKeyValue xmlns="http://www.w3.org/2000/09/xmldsig#">
<Modulus>
sRaod2RZ8hMFBl+VhsnhyPM8l/Fj1obnBxfQIaWuHFIFfXiGe/CYHuZ5QJQLnZxHMJX6LL3Sh+Us
og3p0jpijpcg0QgfBSEkfopKTgReYN8DiDIll0rV1XdTni7E85Nd1YyNy3ui/ZD+UShWwqu6jLVL
R+QUm+/1LIKYb3OCBTvOlY7xHoP6NSU1+Mr+YzGBUacdO2vnNxe/PQhxIeP1zO0njuqGHkwEpy8r
UWRZbbDn31TmKjqlhgtsz5HPhbRaYEExhyepKgBiNz+RyxtYXVhuG8OrWQDoS5gYHSjdw1CTJyix
eJwyoqA9RGYguG5nh9zndi3LWAh7Z0lx+tIz+w==</Modulus>
<Exponent>AQAB</Exponent>
</RSAKeyValue>
</ds:KeyValue>
</ds:KeyInfo>
</KeyDescriptor>
<AttributeService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP"
Location="http://auth.$name.com/saml/AA/SOAP" />
<NameIDFormat>
urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</NameIDFormat>
<NameIDFormat>
urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName</NameIDFormat>
<NameIDFormat>
urn:oasis:names:tc:SAML:1.1:nameid-format:WindowsDomainQualifiedName</NameIDFormat>
<NameIDFormat>
urn:oasis:names:tc:SAML:2.0:nameid-format:kerberos</NameIDFormat>
<NameIDFormat>
urn:oasis:names:tc:SAML:2.0:nameid-format:entity</NameIDFormat>
<NameIDFormat>
urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>
</AttributeAuthorityDescriptor>
<Organization>
<OrganizationName xml:lang="en">$org</OrganizationName>
<OrganizationDisplayName xml:lang="en">
$org</OrganizationDisplayName>
<OrganizationURL xml:lang="en">
http://www.$name.com</OrganizationURL>
</Organization>
</EntityDescriptor>
EOF
;
}
sub samlProxyMetaDataXML {
my ( $name, $type ) = @_;
my $org = uc($name);
@ -623,6 +807,192 @@ EOF
;
}
sub samlProxyComplexMetaDataXML {
my ( $name, $typeSSO, $typeSLO ) = @_;
my $org = uc($name);
return <<"EOF"
<?xml version="1.0"?>
<EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
entityID="http://auth.$name.com/saml/metadata">
<IDPSSODescriptor WantAuthnRequestsSigned="true"
protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<KeyDescriptor use="signing">
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:KeyValue>
<RSAKeyValue xmlns="http://www.w3.org/2000/09/xmldsig#">
<Modulus>ztmb1JZk/agkYYm23D4dqaLS4EKHKrjO4eBvwtWZLexAGR1KDpcrHqLyqJoal+q4A8drI7lxElSt
6xRKJ4DIxQM1jqRcmE6EzdL6BfTaRace3zIuhjDSQUZJdtFtlJynQT1cJbx5ZYhqZbYANm9NZRcY
Z5gWeyF9nl41xA79AMuYlpt7eWDR8cnQJXwV790991FQ9yA2BBgTdSKkFqZ72P4lWu4shz3JCGf5
hyq03hCHQ7bsfpgAdCrbQPTuJNFtS599ClMu+AcRcwJcS233pHd306PRHCXn3Eapq6gEoHxgLVNp
+luAIhRA9EaOnZ0nVkFwFKn3vLXzV01iTliMeQ==
</Modulus>
<Exponent>AQAB
</Exponent>
</RSAKeyValue>
</ds:KeyValue>
</ds:KeyInfo>
</KeyDescriptor>
<KeyDescriptor use="encryption">
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:KeyValue>
<RSAKeyValue xmlns="http://www.w3.org/2000/09/xmldsig#">
<Modulus>2vzoUiQ4GsM5qLjoxslEDKj+RrPh/A743JCWe1Hbadjd5yD4gPwmJUxMF+MJcQlo/TkmKbTonPdI
oAqDknbUxfFTntp0VkdKrB64xr0Stpy7123hPszat3SbU3RYypdobEcuSAS77w9X1KnkRL1+CIe5
9qSsghO3l3b2IJ6qPFXdx/cro7+K3O7w8wAEJ9KmxA0KdiZpSFgTAqfNDSKx8NLwZOeDpsHouAxy
1E2kine+9ESBTRAM2PgiGZvU5JA1SZscdEg3wTftJxxPFnAJMwtqM3IVC6B+TqsIP5Wlk1PQQqH7
5gjtBYDVduynBwU+l/UUmp1aDRZupuH8PF51pw==
</Modulus>
<Exponent>AQAB
</Exponent>
</RSAKeyValue>
</ds:KeyValue>
</ds:KeyInfo>
</KeyDescriptor>
<ArtifactResolutionService isDefault="true" index="0"
Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP"
Location="http://auth.$name.com/saml/artifact" />
<SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:$typeSLO"
Location="http://auth.$name.com/saml/singleLogout"
ResponseLocation="http://auth.$name.com/saml/singleLogoutReturn" />
<NameIDFormat>
urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</NameIDFormat>
<NameIDFormat>
urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName</NameIDFormat>
<NameIDFormat>
urn:oasis:names:tc:SAML:1.1:nameid-format:WindowsDomainQualifiedName</NameIDFormat>
<NameIDFormat>
urn:oasis:names:tc:SAML:2.0:nameid-format:kerberos</NameIDFormat>
<NameIDFormat>
urn:oasis:names:tc:SAML:2.0:nameid-format:entity</NameIDFormat>
<NameIDFormat>
urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>
<SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:$typeSSO"
Location="http://auth.$name.com/saml/singleSignOn" />
</IDPSSODescriptor>
<SPSSODescriptor AuthnRequestsSigned="true"
WantAssertionsSigned="true"
protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<KeyDescriptor use="signing">
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:KeyValue>
<RSAKeyValue xmlns="http://www.w3.org/2000/09/xmldsig#">
<Modulus>ztmb1JZk/agkYYm23D4dqaLS4EKHKrjO4eBvwtWZLexAGR1KDpcrHqLyqJoal+q4A8drI7lxElSt
6xRKJ4DIxQM1jqRcmE6EzdL6BfTaRace3zIuhjDSQUZJdtFtlJynQT1cJbx5ZYhqZbYANm9NZRcY
Z5gWeyF9nl41xA79AMuYlpt7eWDR8cnQJXwV790991FQ9yA2BBgTdSKkFqZ72P4lWu4shz3JCGf5
hyq03hCHQ7bsfpgAdCrbQPTuJNFtS599ClMu+AcRcwJcS233pHd306PRHCXn3Eapq6gEoHxgLVNp
+luAIhRA9EaOnZ0nVkFwFKn3vLXzV01iTliMeQ==
</Modulus>
<Exponent>AQAB
</Exponent>
</RSAKeyValue>
</ds:KeyValue>
</ds:KeyInfo>
</KeyDescriptor>
<KeyDescriptor use="encryption">
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:KeyValue>
<RSAKeyValue xmlns="http://www.w3.org/2000/09/xmldsig#">
<Modulus>2vzoUiQ4GsM5qLjoxslEDKj+RrPh/A743JCWe1Hbadjd5yD4gPwmJUxMF+MJcQlo/TkmKbTonPdI
oAqDknbUxfFTntp0VkdKrB64xr0Stpy7123hPszat3SbU3RYypdobEcuSAS77w9X1KnkRL1+CIe5
9qSsghO3l3b2IJ6qPFXdx/cro7+K3O7w8wAEJ9KmxA0KdiZpSFgTAqfNDSKx8NLwZOeDpsHouAxy
1E2kine+9ESBTRAM2PgiGZvU5JA1SZscdEg3wTftJxxPFnAJMwtqM3IVC6B+TqsIP5Wlk1PQQqH7
5gjtBYDVduynBwU+l/UUmp1aDRZupuH8PF51pw==
</Modulus>
<Exponent>AQAB
</Exponent>
</RSAKeyValue>
</ds:KeyValue>
</ds:KeyInfo>
</KeyDescriptor>
<ArtifactResolutionService isDefault="true" index="0"
Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP"
Location="http://auth.$name.com/saml/artifact" />
<SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:$typeSLO"
Location="http://auth.$name.com/saml/proxySingleLogout"
ResponseLocation="http://auth.$name.com/saml/proxySingleLogoutReturn" />
<NameIDFormat>
urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</NameIDFormat>
<NameIDFormat>
urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName</NameIDFormat>
<NameIDFormat>
urn:oasis:names:tc:SAML:1.1:nameid-format:WindowsDomainQualifiedName</NameIDFormat>
<NameIDFormat>
urn:oasis:names:tc:SAML:2.0:nameid-format:kerberos</NameIDFormat>
<NameIDFormat>
urn:oasis:names:tc:SAML:2.0:nameid-format:entity</NameIDFormat>
<NameIDFormat>
urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>
<AssertionConsumerService isDefault="true" index="0"
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
Location="http://auth.$name.com/saml/proxySingleSignOnPost" />
<AssertionConsumerService isDefault="false" index="1"
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact"
Location="http://auth.$name.com/saml/proxySingleSignOnArtifact" />
</SPSSODescriptor>
<AttributeAuthorityDescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<KeyDescriptor use="signing">
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:KeyValue>
<RSAKeyValue xmlns="http://www.w3.org/2000/09/xmldsig#">
<Modulus>ztmb1JZk/agkYYm23D4dqaLS4EKHKrjO4eBvwtWZLexAGR1KDpcrHqLyqJoal+q4A8drI7lxElSt
6xRKJ4DIxQM1jqRcmE6EzdL6BfTaRace3zIuhjDSQUZJdtFtlJynQT1cJbx5ZYhqZbYANm9NZRcY
Z5gWeyF9nl41xA79AMuYlpt7eWDR8cnQJXwV790991FQ9yA2BBgTdSKkFqZ72P4lWu4shz3JCGf5
hyq03hCHQ7bsfpgAdCrbQPTuJNFtS599ClMu+AcRcwJcS233pHd306PRHCXn3Eapq6gEoHxgLVNp
+luAIhRA9EaOnZ0nVkFwFKn3vLXzV01iTliMeQ==
</Modulus>
<Exponent>AQAB
</Exponent>
</RSAKeyValue>
</ds:KeyValue>
</ds:KeyInfo>
</KeyDescriptor>
<KeyDescriptor use="encryption">
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:KeyValue>
<RSAKeyValue xmlns="http://www.w3.org/2000/09/xmldsig#">
<Modulus>2vzoUiQ4GsM5qLjoxslEDKj+RrPh/A743JCWe1Hbadjd5yD4gPwmJUxMF+MJcQlo/TkmKbTonPdI
oAqDknbUxfFTntp0VkdKrB64xr0Stpy7123hPszat3SbU3RYypdobEcuSAS77w9X1KnkRL1+CIe5
9qSsghO3l3b2IJ6qPFXdx/cro7+K3O7w8wAEJ9KmxA0KdiZpSFgTAqfNDSKx8NLwZOeDpsHouAxy
1E2kine+9ESBTRAM2PgiGZvU5JA1SZscdEg3wTftJxxPFnAJMwtqM3IVC6B+TqsIP5Wlk1PQQqH7
5gjtBYDVduynBwU+l/UUmp1aDRZupuH8PF51pw==
</Modulus>
<Exponent>AQAB
</Exponent>
</RSAKeyValue>
</ds:KeyValue>
</ds:KeyInfo>
</KeyDescriptor>
<AttributeService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP"
Location="http://auth.$name.com/saml/AA/SOAP" />
<NameIDFormat>
urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</NameIDFormat>
<NameIDFormat>
urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName</NameIDFormat>
<NameIDFormat>
urn:oasis:names:tc:SAML:1.1:nameid-format:WindowsDomainQualifiedName</NameIDFormat>
<NameIDFormat>
urn:oasis:names:tc:SAML:2.0:nameid-format:kerberos</NameIDFormat>
<NameIDFormat>
urn:oasis:names:tc:SAML:2.0:nameid-format:entity</NameIDFormat>
<NameIDFormat>
urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>
</AttributeAuthorityDescriptor>
<Organization>
<OrganizationName xml:lang="en">$org</OrganizationName>
<OrganizationDisplayName xml:lang="en">
$org</OrganizationDisplayName>
<OrganizationURL xml:lang="en">
http://www.$name.com</OrganizationURL>
</Organization>
</EntityDescriptor>
EOF
;
}
sub samlIDPMetaDataXML {
my ( $name, $type ) = @_;
my $org = uc($name);
@ -804,6 +1174,188 @@ EOF
;
}
sub samlIDPComplexMetaDataXML {
my ( $name, $typeSSO, $typeSLO ) = @_;
my $org = uc($name);
return <<"EOF"
<?xml version="1.0"?>
<EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
entityID="http://auth.$name.com/saml/metadata">
<IDPSSODescriptor WantAuthnRequestsSigned="true"
protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<KeyDescriptor use="signing">
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:KeyValue>
<RSAKeyValue xmlns="http://www.w3.org/2000/09/xmldsig#">
<Modulus>
tR/wgDqWB4Maho5V6TjcL/NbNfjgIh7GcgkrB5RZcVT1GTejJlMjUQdgBKBuZXQN+7/29P6UcGq1
kYalURq6S8SpeJ1ofp5rBEoD/TIkvU0JOcid65wp+fdzXGXsfiZvHraU74jSCgjP/wqfVGRyBIQz
B0SIxSpnrsigqNsE1E94toDMx4wovjHu/9ABAImREV7Sz83OeFF00/sghrjTEJOD/gHf04JCn9Mg
NOqvSTysr9LXWg/oUKQDEYeTq9ux6pq/oqv1MxwONbSZPtN5yD41mi+hT8Rh+W8Je8rsiML4VMxz
sb1l9303asw6suo5bLTISKNSbu1nt1NkpNxzyw==</Modulus>
<Exponent>AQAB</Exponent>
</RSAKeyValue>
</ds:KeyValue>
</ds:KeyInfo>
</KeyDescriptor>
<KeyDescriptor use="encryption">
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:KeyValue>
<RSAKeyValue xmlns="http://www.w3.org/2000/09/xmldsig#">
<Modulus>
nfKBDG/K0TnGT7Xu8q1N45sNWvIK91SqNg8nvN2uVeKoHADTcsus5Xn3id5+8Q9TuMFsW9kIEeXi
aPKXQa9ryfSNDhWDWloNkpGEeWif2BnHUu46Abu1UBWb0mH6VwcG1PR4qHruLis1odjQ1qnVDNfS
EASVIppEBYjDX203ypmURIzU6h53GRRRlf1BLWkbVn9ysmDeR57Xw5Rsx/+tBlcnMrkv/40DSUke
hQIl2JmlFrl2Caik+gU4pd20apA/pNLjBZF0OmGoS08AIR5NMd0KFa6CwZUUSHJqH5GFy5Y2yl4l
g8K0klAS9q7L7aXI+eFQZhkwidjpxXnHPyxIGQ==</Modulus>
<Exponent>AQAB</Exponent>
</RSAKeyValue>
</ds:KeyValue>
</ds:KeyInfo>
</KeyDescriptor>
<ArtifactResolutionService isDefault="true" index="0"
Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP"
Location="http://auth.$name.com/saml/artifact" />
<SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:$typeSLO"
Location="http://auth.$name.com/saml/singleLogout"
ResponseLocation="http://auth.$name.com/saml/singleLogoutReturn" />
<NameIDFormat>
urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</NameIDFormat>
<NameIDFormat>
urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName</NameIDFormat>
<NameIDFormat>
urn:oasis:names:tc:SAML:1.1:nameid-format:WindowsDomainQualifiedName</NameIDFormat>
<NameIDFormat>
urn:oasis:names:tc:SAML:2.0:nameid-format:kerberos</NameIDFormat>
<NameIDFormat>
urn:oasis:names:tc:SAML:2.0:nameid-format:entity</NameIDFormat>
<NameIDFormat>
urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>
<SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:$typeSSO"
Location="http://auth.$name.com/saml/singleSignOn" />
</IDPSSODescriptor>
<SPSSODescriptor AuthnRequestsSigned="true"
WantAssertionsSigned="true"
protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<KeyDescriptor use="signing">
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:KeyValue>
<RSAKeyValue xmlns="http://www.w3.org/2000/09/xmldsig#">
<Modulus>
tR/wgDqWB4Maho5V6TjcL/NbNfjgIh7GcgkrB5RZcVT1GTejJlMjUQdgBKBuZXQN+7/29P6UcGq1
kYalURq6S8SpeJ1ofp5rBEoD/TIkvU0JOcid65wp+fdzXGXsfiZvHraU74jSCgjP/wqfVGRyBIQz
B0SIxSpnrsigqNsE1E94toDMx4wovjHu/9ABAImREV7Sz83OeFF00/sghrjTEJOD/gHf04JCn9Mg
NOqvSTysr9LXWg/oUKQDEYeTq9ux6pq/oqv1MxwONbSZPtN5yD41mi+hT8Rh+W8Je8rsiML4VMxz
sb1l9303asw6suo5bLTISKNSbu1nt1NkpNxzyw==</Modulus>
<Exponent>AQAB</Exponent>
</RSAKeyValue>
</ds:KeyValue>
</ds:KeyInfo>
</KeyDescriptor>
<KeyDescriptor use="encryption">
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:KeyValue>
<RSAKeyValue xmlns="http://www.w3.org/2000/09/xmldsig#">
<Modulus>
nfKBDG/K0TnGT7Xu8q1N45sNWvIK91SqNg8nvN2uVeKoHADTcsus5Xn3id5+8Q9TuMFsW9kIEeXi
aPKXQa9ryfSNDhWDWloNkpGEeWif2BnHUu46Abu1UBWb0mH6VwcG1PR4qHruLis1odjQ1qnVDNfS
EASVIppEBYjDX203ypmURIzU6h53GRRRlf1BLWkbVn9ysmDeR57Xw5Rsx/+tBlcnMrkv/40DSUke
hQIl2JmlFrl2Caik+gU4pd20apA/pNLjBZF0OmGoS08AIR5NMd0KFa6CwZUUSHJqH5GFy5Y2yl4l
g8K0klAS9q7L7aXI+eFQZhkwidjpxXnHPyxIGQ==</Modulus>
<Exponent>AQAB</Exponent>
</RSAKeyValue>
</ds:KeyValue>
</ds:KeyInfo>
</KeyDescriptor>
<ArtifactResolutionService isDefault="true" index="0"
Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP"
Location="http://auth.$name.com/saml/artifact" />
<SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:$typeSLO"
Location="http://auth.$name.com/saml/proxySingleLogout"
ResponseLocation="http://auth.$name.com/saml/proxySingleLogoutReturn" />
<NameIDFormat>
urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</NameIDFormat>
<NameIDFormat>
urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName</NameIDFormat>
<NameIDFormat>
urn:oasis:names:tc:SAML:1.1:nameid-format:WindowsDomainQualifiedName</NameIDFormat>
<NameIDFormat>
urn:oasis:names:tc:SAML:2.0:nameid-format:kerberos</NameIDFormat>
<NameIDFormat>
urn:oasis:names:tc:SAML:2.0:nameid-format:entity</NameIDFormat>
<NameIDFormat>
urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>
<AssertionConsumerService isDefault="true" index="0"
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
Location="http://auth.$name.com/saml/proxySingleSignOnPost" />
<AssertionConsumerService isDefault="false" index="1"
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact"
Location="http://auth.$name.com/saml/proxySingleSignOnArtifact" />
</SPSSODescriptor>
<AttributeAuthorityDescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<KeyDescriptor use="signing">
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:KeyValue>
<RSAKeyValue xmlns="http://www.w3.org/2000/09/xmldsig#">
<Modulus>
tR/wgDqWB4Maho5V6TjcL/NbNfjgIh7GcgkrB5RZcVT1GTejJlMjUQdgBKBuZXQN+7/29P6UcGq1
kYalURq6S8SpeJ1ofp5rBEoD/TIkvU0JOcid65wp+fdzXGXsfiZvHraU74jSCgjP/wqfVGRyBIQz
B0SIxSpnrsigqNsE1E94toDMx4wovjHu/9ABAImREV7Sz83OeFF00/sghrjTEJOD/gHf04JCn9Mg
NOqvSTysr9LXWg/oUKQDEYeTq9ux6pq/oqv1MxwONbSZPtN5yD41mi+hT8Rh+W8Je8rsiML4VMxz
sb1l9303asw6suo5bLTISKNSbu1nt1NkpNxzyw==</Modulus>
<Exponent>AQAB</Exponent>
</RSAKeyValue>
</ds:KeyValue>
</ds:KeyInfo>
</KeyDescriptor>
<KeyDescriptor use="encryption">
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:KeyValue>
<RSAKeyValue xmlns="http://www.w3.org/2000/09/xmldsig#">
<Modulus>
nfKBDG/K0TnGT7Xu8q1N45sNWvIK91SqNg8nvN2uVeKoHADTcsus5Xn3id5+8Q9TuMFsW9kIEeXi
aPKXQa9ryfSNDhWDWloNkpGEeWif2BnHUu46Abu1UBWb0mH6VwcG1PR4qHruLis1odjQ1qnVDNfS
EASVIppEBYjDX203ypmURIzU6h53GRRRlf1BLWkbVn9ysmDeR57Xw5Rsx/+tBlcnMrkv/40DSUke
hQIl2JmlFrl2Caik+gU4pd20apA/pNLjBZF0OmGoS08AIR5NMd0KFa6CwZUUSHJqH5GFy5Y2yl4l
g8K0klAS9q7L7aXI+eFQZhkwidjpxXnHPyxIGQ==</Modulus>
<Exponent>AQAB</Exponent>
</RSAKeyValue>
</ds:KeyValue>
</ds:KeyInfo>
</KeyDescriptor>
<AttributeService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP"
Location="http://auth.$name.com/saml/AA/SOAP" />
<NameIDFormat>
urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</NameIDFormat>
<NameIDFormat>
urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName</NameIDFormat>
<NameIDFormat>
urn:oasis:names:tc:SAML:1.1:nameid-format:WindowsDomainQualifiedName</NameIDFormat>
<NameIDFormat>
urn:oasis:names:tc:SAML:2.0:nameid-format:kerberos</NameIDFormat>
<NameIDFormat>
urn:oasis:names:tc:SAML:2.0:nameid-format:entity</NameIDFormat>
<NameIDFormat>
urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>
</AttributeAuthorityDescriptor>
<Organization>
<OrganizationName xml:lang="en">$org</OrganizationName>
<OrganizationDisplayName xml:lang="en">
$org</OrganizationDisplayName>
<OrganizationURL xml:lang="en">
http://www.$name.fr/</OrganizationURL>
</Organization>
</EntityDescriptor>
EOF
;
}
=head4 expectXPath($xml_string, $xpath, $namespaces, $value, $message)
Match a XPath expression against the provided string, and verify that the correct value is