Merge branch 'v2.0'
This commit is contained in:
commit
6c4a5b911c
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
======================== ================================= ===============================
|
||||
|
||||
|
|
|
@ -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`
|
||||
|
|
|
@ -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
|
||||
~~~~~~~~~~~~~~~
|
||||
|
|
|
@ -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::
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")'
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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} ) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
|
||||
|
|
|
@ -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__
|
||||
|
|
|
@ -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' );
|
||||
|
||||
|
|
|
@ -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) = @_;
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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'
|
||||
);
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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__',
|
||||
|
|
|
@ -88,6 +88,7 @@ sub tree {
|
|||
'portalRequireOldPassword',
|
||||
'hideOldPassword',
|
||||
'mailOnPasswordChange',
|
||||
'portalEnablePasswordDisplay',
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -992,6 +993,7 @@ sub tree {
|
|||
'sfRemovedNotifMsg',
|
||||
],
|
||||
},
|
||||
'sfRegisterTimeout',
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
|
@ -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":"لا يحتوي الخادم على إعدادات. استخدام قالب لحفظ الأول"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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."
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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."
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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."
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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."
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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."
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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. "
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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."
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
@ -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 };
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
[all]
|
||||
|
||||
logLevel = debug
|
||||
logLevel = error
|
||||
localSessionStorage =
|
||||
localSessionStorageOptions =
|
||||
|
||||
[configuration]
|
||||
|
||||
type=CDBI
|
||||
type=RDBI
|
||||
dbiChain=dbi:SQLite:dbname=t/conf.db
|
||||
|
||||
[portal]
|
||||
|
|
|
@ -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 )
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
);
|
||||
|
|
|
@ -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
|
||||
}
|
||||
);
|
||||
|
|
|
@ -25,4 +25,4 @@ sub init {
|
|||
return 1;
|
||||
}
|
||||
|
||||
1;
|
||||
1;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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() );
|
|
@ -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,
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
|
@ -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,
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
|
@ -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,
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
|
@ -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() );
|
||||
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue