Merge branch 'v2.0' into 2683
This commit is contained in:
commit
344eae6f3e
|
@ -71,6 +71,10 @@ Then go in ``CAS Service`` to define:
|
|||
matching. By default, LemonLDAP::NG will try to find a declared CAS
|
||||
Application matching the hostname of the requested application if it cannot
|
||||
find a match using the full path. See :ref:`idpcas-url-matching` for details
|
||||
- **Temporary ticket lifetime**: (since *2.0.14*): restricts how long Service
|
||||
and Proxy tickets are valid after being generated. For compatibility, the
|
||||
default value of ``0`` means they are valid for the entire session duration.
|
||||
But the CAS spefications recommends ``300`` (5 minutes).
|
||||
|
||||
|
||||
.. tip::
|
||||
|
|
|
@ -62,6 +62,7 @@ casSrvMetaDataOptions Root of CAS server optio
|
|||
casStorage Apache::Session module to store CAS user data ✔
|
||||
casStorageOptions Apache::Session module parameters ✔
|
||||
casStrictMatching Disable host-based matching of CAS services ✔
|
||||
casTicketExpiration CAS Service and Proxy tickets TTL ✔
|
||||
cda Enable Cross Domain Authentication ✔ ✔
|
||||
certificateResetByMailCeaAttribute ✔
|
||||
certificateResetByMailCertificateAttribute ✔
|
||||
|
|
|
@ -4,12 +4,11 @@ REST session backend
|
|||
Session <type> can be 'global' for SSO sessions or 'persistent' for
|
||||
persistent sessions.
|
||||
|
||||
LL::NG portal provides REST end points for sessions management:
|
||||
LL::NG Portal provides REST end points for sessions management:
|
||||
|
||||
- GET /sessions/<type>/<session-id> : get session datas
|
||||
- GET /sessions/<type>/<session-id>/<key> : get a session key value
|
||||
- GET /sessions/<type>/<session-id>/[k1,k2] : get some session key
|
||||
value
|
||||
- GET /sessions/<type>/<session-id>/[k1,k2] : get some keys value
|
||||
- POST /sessions/<type> : create a session
|
||||
- PUT /sessions/<type>/<session-id> : update some keys
|
||||
- DELETE /sessions/<type>/<session-id> : delete a session
|
||||
|
@ -20,17 +19,21 @@ Sessions for connected users (used by :doc:`LLNG Proxy<authproxy>`):
|
|||
- GET /session/my/<type>/key : get session key
|
||||
- DELETE /session/my : ask for logout
|
||||
|
||||
Authorizations for connected users (always enabled):
|
||||
Services for connected users (always enabled):
|
||||
|
||||
- GET /mysession/?authorizationfor=<base64-encoded-url>: ask if url is
|
||||
authorizated
|
||||
- GET /mysession/?authorizationfor=<base64-encoded-url> : ask if an url
|
||||
is authorized
|
||||
- GET /mysession/?whoami : get "my" uid
|
||||
- PUT /mysession/<type> : update some persistent data (restricted)
|
||||
- DELETE /mysession/<type>/key : delete key in data (restricted)
|
||||
- GET /myapplications : get "my" appplications list
|
||||
|
||||
This session backend can be used to share sessions stored in a
|
||||
non-network backend (like
|
||||
:doc:`file session backend<filesessionbackend>`) or in a network backend
|
||||
protected with a firewall that only accepts HTTP flows.
|
||||
|
||||
Most of the time, REST session backend is used by Handlers installed on
|
||||
Most of the time, REST session backend is used by Handlers deployed on
|
||||
external servers.
|
||||
|
||||
To configure it, REST session backend will be set through Manager in
|
||||
|
@ -69,16 +72,16 @@ Name Comment Example
|
|||
=================== ======================================== ==================================================
|
||||
|
||||
`user` and `password` parameters are only used if the entry point `index.fcgi/sessions/global`
|
||||
is protected by a basic authentication. Thus, handlers will make requests to the portal
|
||||
is protected by a basic authentication. Thus, handlers will make requests to the Portal
|
||||
using these parameters.
|
||||
|
||||
|
||||
.. attention::
|
||||
|
||||
By default, user password and other secret keys are
|
||||
hidden by LLNG REST server. You can force REST server to export their
|
||||
hidden by LL::NG REST server. You can force REST server to export their
|
||||
real values by selecting "Export secret attributes in REST" in the
|
||||
manager. This less secure option is disabled by default.
|
||||
Manager. This less secure option is disabled by default.
|
||||
|
||||
Apache
|
||||
~~~~~~
|
||||
|
|
|
@ -29,6 +29,57 @@ None
|
|||
2.0.14
|
||||
------
|
||||
|
||||
Security
|
||||
~~~~~~~~
|
||||
|
||||
* **CVE-2021-40874**: RESTServer pwdConfirm always returns true with Combination + Kerberos (see `issue 2612 <https://gitlab.ow2.org/lemonldap-ng/lemonldap-ng/-/issues/2612>`__)
|
||||
|
||||
Weak encryption used for password-protected SAML keys
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Previous versions of LemonLDAP::NG used a weak encryption algorithm to protect
|
||||
SAML keys when a password was set during certificate generation.
|
||||
|
||||
Run the following command to check if this is your case::
|
||||
|
||||
lemonldap-ng-cli get samlServicePrivateKeySig
|
||||
lemonldap-ng-cli get samlServicePrivateKeyEnc
|
||||
|
||||
If the output of either command starts with ``BEGIN ENCRYPTED PRIVATE KEY``,
|
||||
then it probably means you generated your keys using the vulnerable manager
|
||||
code.
|
||||
|
||||
In this case, you can convert your existing keys to a stronger encryption using
|
||||
the following command ::
|
||||
|
||||
# Extract your existing keys. If samlServicePrivateKeyEnc is empty, you can
|
||||
# skip it entirely
|
||||
lemonldap-ng-cli get samlServicePrivateKeySig | \
|
||||
sed 's/samlServicePrivateKeySig = //' > saml-sig.pem
|
||||
lemonldap-ng-cli get samlServicePrivateKeyEnc | \
|
||||
sed 's/samlServicePrivateKeyEnc = //' > saml-enc.pem
|
||||
|
||||
# Re-encrypt the private key, using the same passphrase
|
||||
openssl pkey -in saml-sig.pem -aes256 -out saml-sig-aes.pem
|
||||
openssl pkey -in saml-enc.pem -aes256 -out saml-enc-aes.pem
|
||||
|
||||
#Or, if you are using OpenSSL 3+
|
||||
openssl pkey -provider legacy -provider default -in saml-sig.pem \
|
||||
-aes256 -out saml-sig-aes.pem
|
||||
openssl pkey -provider legacy -provider default -in saml-enc.pem \
|
||||
-aes256 -out saml-enc-aes.pem
|
||||
|
||||
Then, simply reimport your keys ::
|
||||
|
||||
lemonldap-ng-cli set samlServicePrivateKeySig "$(cat saml-sig-aes.pem)"
|
||||
lemonldap-ng-cli set samlServicePrivateKeyEnc "$(cat saml-enc-aes.pem)"
|
||||
|
||||
If is recommended to keep the same password as before, if not, adjust the
|
||||
``samlServicePrivateKeySigPwd`` and ``samlServicePrivateKeyEncPwd`` variables as well.
|
||||
|
||||
This operation is transparent and does not require any change to your existing
|
||||
SAML configuration or SAML applications
|
||||
|
||||
LemonLDAP::NG version is returned by the CheckState plugin
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@ sub defaultValues {
|
|||
'captcha_size' => 6,
|
||||
'casAccessControlPolicy' => 'none',
|
||||
'casAuthnLevel' => 1,
|
||||
'casTicketExpiration' => 0,
|
||||
'certificateResetByMailCeaAttribute' => 'description',
|
||||
'certificateResetByMailCertificateAttribute' =>
|
||||
'userCertificate;binary',
|
||||
|
|
|
@ -815,6 +815,10 @@ qr/(?:(?:https?):\/\/(?:(?:(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.]
|
|||
'default' => 0,
|
||||
'type' => 'bool'
|
||||
},
|
||||
'casTicketExpiration' => {
|
||||
'default' => 0,
|
||||
'type' => 'int'
|
||||
},
|
||||
'cda' => {
|
||||
'default' => 0,
|
||||
'type' => 'bool'
|
||||
|
|
|
@ -2489,6 +2489,11 @@ sub attributes {
|
|||
type => 'bool',
|
||||
documentation => 'Disable host-based matching of CAS services',
|
||||
},
|
||||
casTicketExpiration => {
|
||||
default => 0,
|
||||
type => 'int',
|
||||
documentation => 'Expiration time of Service and Proxy tickets',
|
||||
},
|
||||
issuerDBCASActivation => {
|
||||
default => 0,
|
||||
type => 'bool',
|
||||
|
|
|
@ -1393,6 +1393,7 @@ sub tree {
|
|||
'casStorageOptions',
|
||||
'casAttributes',
|
||||
'casStrictMatching',
|
||||
'casTicketExpiration',
|
||||
|
||||
]
|
||||
},
|
||||
|
|
|
@ -212,7 +212,10 @@ sub _generateX509 {
|
|||
my $strCert = Net::SSLeay::PEM_get_string_X509($cert);
|
||||
my $strPrivate;
|
||||
if ($password) {
|
||||
$strPrivate = Net::SSLeay::PEM_get_string_PrivateKey( $key, $password );
|
||||
my $alg = Net::SSLeay::EVP_get_cipherbyname("AES-256-CBC")
|
||||
|| Net::SSLeay::EVP_get_cipherbyname("DES-EDE3-CBC");
|
||||
$strPrivate =
|
||||
Net::SSLeay::PEM_get_string_PrivateKey( $key, $password, $alg );
|
||||
}
|
||||
else {
|
||||
$strPrivate = Net::SSLeay::PEM_get_string_PrivateKey($key);
|
||||
|
|
|
@ -44,10 +44,14 @@ sub tests {
|
|||
|
||||
# Check if portal URL is well formated
|
||||
portalURL => sub {
|
||||
my $url = $conf->{portal};
|
||||
|
||||
# Append or remove trailing slashes
|
||||
$conf->{portal} =~ s%/*$%/%;
|
||||
return (
|
||||
1,
|
||||
(
|
||||
( $conf->{portal} =~ m%/$% )
|
||||
( $url =~ m%/$% )
|
||||
? ''
|
||||
: "Portal URL should end with a /"
|
||||
)
|
||||
|
|
|
@ -167,6 +167,7 @@
|
|||
"casStorage":"اسم وحدة جلسات كاس",
|
||||
"casStorageOptions":" خيارات وحدة جلسات كاس",
|
||||
"casStrictMatching":"Use strict URL matching",
|
||||
"casTicketExpiration":"Temporary ticket lifetime",
|
||||
"categoryName":"اسم الفئة",
|
||||
"cda":"نطاقات متعددة",
|
||||
"certificateMailContent":"محتوى البريد",
|
||||
|
@ -1230,4 +1231,4 @@
|
|||
"yubikey2fUrl":"خدمة أل يو أر ل",
|
||||
"yubikey2fUserCanRemoveKey":"Allow user to remove Yubikey",
|
||||
"zeroConfExplanations":"لا يحتوي الخادم على إعدادات. استخدام قالب لحفظ الأول"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -167,6 +167,7 @@
|
|||
"casStorage":"CAS sessions module name",
|
||||
"casStorageOptions":"CAS sessions module options",
|
||||
"casStrictMatching":"Use strict URL matching",
|
||||
"casTicketExpiration":"Temporary ticket lifetime",
|
||||
"categoryName":"Category name",
|
||||
"cda":"Mehrere Domains",
|
||||
"certificateMailContent":"Mail content",
|
||||
|
@ -1230,4 +1231,4 @@
|
|||
"yubikey2fUrl":"Service URL",
|
||||
"yubikey2fUserCanRemoveKey":"Allow user to remove Yubikey",
|
||||
"zeroConfExplanations":"Server has no configuration. Use template to save the first."
|
||||
}
|
||||
}
|
||||
|
|
|
@ -167,6 +167,7 @@
|
|||
"casStorage":"CAS sessions module name",
|
||||
"casStorageOptions":"CAS sessions module options",
|
||||
"casStrictMatching":"Use strict URL matching",
|
||||
"casTicketExpiration":"Temporary ticket lifetime",
|
||||
"categoryName":"Category name",
|
||||
"cda":"Multiple domains",
|
||||
"certificateMailContent":"Mail content",
|
||||
|
|
|
@ -167,6 +167,7 @@
|
|||
"casStorage":"CAS sessions module name",
|
||||
"casStorageOptions":"CAS sessions module options",
|
||||
"casStrictMatching":"Use strict URL matching",
|
||||
"casTicketExpiration":"Temporary ticket lifetime",
|
||||
"categoryName":"Nombre de categoría",
|
||||
"cda":"Dominios múltiples",
|
||||
"certificateMailContent":"Contenido de correo",
|
||||
|
@ -1230,4 +1231,4 @@
|
|||
"yubikey2fUrl":"Service URL",
|
||||
"yubikey2fUserCanRemoveKey":"Allow user to remove Yubikey",
|
||||
"zeroConfExplanations":"Server has no configuration. Use template to save the first."
|
||||
}
|
||||
}
|
||||
|
|
|
@ -167,6 +167,7 @@
|
|||
"casStorage":"Nom du module des sessions CAS",
|
||||
"casStorageOptions":"Options du module des sessions CAS",
|
||||
"casStrictMatching":"Filtrage strict des URL",
|
||||
"casTicketExpiration":"Expiration des tickets temporaires",
|
||||
"categoryName":"Nom de la catégorie",
|
||||
"cda":"Domaines multiples",
|
||||
"certificateMailContent":"Contenu du mail",
|
||||
|
|
|
@ -167,6 +167,7 @@
|
|||
"casStorage":"Nome del modulo sessioni CAS",
|
||||
"casStorageOptions":"Opzioni del modulo sessioni CAS",
|
||||
"casStrictMatching":"Use strict URL matching",
|
||||
"casTicketExpiration":"Temporary ticket lifetime",
|
||||
"categoryName":"Nome della categoria",
|
||||
"cda":"Domini multipli",
|
||||
"certificateMailContent":"Contenuto della mail",
|
||||
|
@ -1230,4 +1231,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."
|
||||
}
|
||||
}
|
||||
|
|
|
@ -167,6 +167,7 @@
|
|||
"casStorage":"Nazwa modułu sesji CAS",
|
||||
"casStorageOptions":"Opcje modułu sesji CAS",
|
||||
"casStrictMatching":"Use strict URL matching",
|
||||
"casTicketExpiration":"Temporary ticket lifetime",
|
||||
"categoryName":"Nazwa Kategorii",
|
||||
"cda":"Wiele domen",
|
||||
"certificateMailContent":"Treść wiadomości",
|
||||
|
@ -1230,4 +1231,4 @@
|
|||
"yubikey2fUrl":"URL usługi",
|
||||
"yubikey2fUserCanRemoveKey":"Pozwól użytkownikowi usunąć Yubikey",
|
||||
"zeroConfExplanations":"Serwer nie ma konfiguracji. Użyj szablonu, aby zapisać pierwszy."
|
||||
}
|
||||
}
|
||||
|
|
|
@ -167,6 +167,7 @@
|
|||
"casStorage":"CAS oturumları modül adı",
|
||||
"casStorageOptions":"CAS oturumları modül seçenekleri",
|
||||
"casStrictMatching":"Katı URL eşleşmesi kullan",
|
||||
"casTicketExpiration":"Temporary ticket lifetime",
|
||||
"categoryName":"Kategori ismi",
|
||||
"cda":"Çoklu alan adları",
|
||||
"certificateMailContent":"E-posta içeriği",
|
||||
|
@ -1230,4 +1231,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."
|
||||
}
|
||||
}
|
||||
|
|
|
@ -167,6 +167,7 @@
|
|||
"casStorage":"Tên mô-đun phiên CAS",
|
||||
"casStorageOptions":"Các tùy chọn mô-đun phiên CAS",
|
||||
"casStrictMatching":"Use strict URL matching",
|
||||
"casTicketExpiration":"Temporary ticket lifetime",
|
||||
"categoryName":"Tên thể loại",
|
||||
"cda":"Nhiều tên miền",
|
||||
"certificateMailContent":"Nội dung thư",
|
||||
|
@ -1230,4 +1231,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. "
|
||||
}
|
||||
}
|
||||
|
|
|
@ -167,6 +167,7 @@
|
|||
"casStorage":"CAS 会话模块名称",
|
||||
"casStorageOptions":"CAS 会话模块选项",
|
||||
"casStrictMatching":"Use strict URL matching",
|
||||
"casTicketExpiration":"Temporary ticket lifetime",
|
||||
"categoryName":"分类名称",
|
||||
"cda":"Multiple domains",
|
||||
"certificateMailContent":"Mail content",
|
||||
|
@ -1230,4 +1231,4 @@
|
|||
"yubikey2fUrl":"Service URL",
|
||||
"yubikey2fUserCanRemoveKey":"Allow user to remove Yubikey",
|
||||
"zeroConfExplanations":"Server has no configuration. Use template to save the first."
|
||||
}
|
||||
}
|
||||
|
|
|
@ -167,6 +167,7 @@
|
|||
"casStorage":"CAS 工作階段模組名稱",
|
||||
"casStorageOptions":"CAS 工作階段模組選項",
|
||||
"casStrictMatching":"Use strict URL matching",
|
||||
"casTicketExpiration":"Temporary ticket lifetime",
|
||||
"categoryName":"分類名稱",
|
||||
"cda":"多域名",
|
||||
"certificateMailContent":"郵件內容",
|
||||
|
@ -1230,4 +1231,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
|
@ -582,6 +582,8 @@ t/32-CAS-Gateway.t
|
|||
t/32-CAS-Hooks.t
|
||||
t/32-CAS-Macros.t
|
||||
t/32-CAS-Prefix.t
|
||||
t/32-CAS-Proxy.t
|
||||
t/32-CAS-Security.t
|
||||
t/32-OIDC-ClaimTypes.t
|
||||
t/32-OIDC-ClientCredentials-Grant.t
|
||||
t/32-OIDC-Code-Flow-with-2F-UpgradeOnly.t
|
||||
|
|
|
@ -163,9 +163,6 @@ sub run {
|
|||
# Session ID
|
||||
my $session_id = $req->{sessionInfo}->{_session_id} || $req->id;
|
||||
|
||||
# Session creation timestamp
|
||||
my $time = $req->{sessionInfo}->{_utime} || time();
|
||||
|
||||
# 1. LOGIN
|
||||
if ( $target eq $cas_login ) {
|
||||
|
||||
|
@ -306,12 +303,20 @@ sub run {
|
|||
$self->logger->debug(
|
||||
"Create a CAS service ticket for service $service");
|
||||
|
||||
my $_utime =
|
||||
$self->conf->{casTicketExpiration}
|
||||
? (
|
||||
time +
|
||||
$self->conf->{casTicketExpiration} -
|
||||
$self->conf->{timeout} )
|
||||
: ( $req->{sessionInfo}->{_utime} || time() );
|
||||
|
||||
my $Sinfos;
|
||||
$Sinfos->{type} = 'casService';
|
||||
$Sinfos->{service} = $service;
|
||||
$Sinfos->{renew} = $casRenewFlag;
|
||||
$Sinfos->{_cas_id} = $session_id;
|
||||
$Sinfos->{_utime} = $time;
|
||||
$Sinfos->{_utime} = $_utime;
|
||||
$Sinfos->{_casApp} = $app;
|
||||
|
||||
my $h = $self->p->processHook( $req, 'casGenerateServiceTicket',
|
||||
|
@ -516,6 +521,18 @@ sub validate {
|
|||
return $self->returnCasValidateError();
|
||||
}
|
||||
|
||||
# Make sure the token is still valid, we already compensated for
|
||||
# different TTLs when storing _utime
|
||||
if ( $casServiceSession->{data}->{_utime} ) {
|
||||
if (
|
||||
time >
|
||||
( $casServiceSession->{data}->{_utime} + $self->conf->{timeout} ) )
|
||||
{
|
||||
$self->logger->error("Session $ticket has expired");
|
||||
return $self->returnCasValidateError();
|
||||
}
|
||||
}
|
||||
|
||||
$self->logger->debug("Service ticket session $ticket found");
|
||||
|
||||
my $service1_uri = URI->new($service);
|
||||
|
@ -637,11 +654,16 @@ sub proxy {
|
|||
'Error in proxy session management' );
|
||||
}
|
||||
|
||||
my $_utime =
|
||||
$self->conf->{casTicketExpiration}
|
||||
? ( time + $self->conf->{casTicketExpiration} - $self->conf->{timeout} )
|
||||
: $casProxyGrantingSession->data->{_utime};
|
||||
|
||||
my $Pinfos;
|
||||
$Pinfos->{type} = 'casProxy';
|
||||
$Pinfos->{service} = $targetService;
|
||||
$Pinfos->{_cas_id} = $casProxyGrantingSession->data->{_cas_id};
|
||||
$Pinfos->{_utime} = $casProxyGrantingSession->data->{_utime};
|
||||
$Pinfos->{_utime} = $_utime;
|
||||
$Pinfos->{proxies} = $casProxyGrantingSession->data->{proxies};
|
||||
|
||||
$casProxySession->update($Pinfos);
|
||||
|
@ -711,6 +733,20 @@ sub _validate2 {
|
|||
return $self->returnCasServiceValidateError( $req, 'INVALID_TICKET',
|
||||
'Ticket not found' );
|
||||
}
|
||||
|
||||
# Make sure the token is still valid, we already compensated for
|
||||
# different TTLs when storing _utime
|
||||
if ( $casServiceSession->{data}->{_utime} ) {
|
||||
if (
|
||||
time >
|
||||
( $casServiceSession->{data}->{_utime} + $self->conf->{timeout} ) )
|
||||
{
|
||||
$self->logger->error("$urlType ticket session $ticket has expired");
|
||||
return $self->returnCasServiceValidateError( $req, 'INVALID_TICKET',
|
||||
'Ticket expired' );
|
||||
}
|
||||
}
|
||||
|
||||
my $app = $casServiceSession->data->{_casApp};
|
||||
|
||||
$self->logger->debug("$urlType ticket session $ticket found");
|
||||
|
@ -777,7 +813,7 @@ sub _validate2 {
|
|||
$PGinfos->{type} = 'casProxyGranting';
|
||||
$PGinfos->{service} = $service;
|
||||
$PGinfos->{_cas_id} = $casServiceSession->data->{_cas_id};
|
||||
$PGinfos->{_utime} = $casServiceSession->data->{_utime};
|
||||
$PGinfos->{_utime} = time;
|
||||
$PGinfos->{_casApp} = $app;
|
||||
|
||||
# Trace proxies
|
||||
|
|
|
@ -226,8 +226,10 @@ sub returnCasServiceValidateSuccess {
|
|||
}
|
||||
if ($proxies) {
|
||||
$self->logger->debug("Add proxies $proxies in response");
|
||||
$s .= "\t\t<cas:proxies>\n\t\t\t<cas:proxy>$_</cas:proxy>\n"
|
||||
foreach ( split( /$self->conf->{multiValuesSeparator}/, $proxies ) );
|
||||
$s .= "\t\t<cas:proxies>\n";
|
||||
$s .= "\t\t\t<cas:proxy>$_</cas:proxy>\n"
|
||||
foreach (
|
||||
reverse( split( $self->conf->{multiValuesSeparator}, $proxies ) ) );
|
||||
$s .= "\t\t</cas:proxies>\n";
|
||||
}
|
||||
$s .= "\t</cas:authenticationSuccess>\n</cas:serviceResponse>\n";
|
||||
|
|
|
@ -231,6 +231,10 @@ sub findUser {
|
|||
# Validate LDAP connection before use
|
||||
sub validateLdap {
|
||||
my ($self) = @_;
|
||||
local $SIG{'PIPE'} = sub {
|
||||
$self->logger->info("Reconnecting to LDAP server due to broken socket");
|
||||
};
|
||||
|
||||
unless ($self->ldap
|
||||
and $self->ldap->root_dse( attrs => ['supportedLDAPVersion'] ) )
|
||||
{
|
||||
|
|
|
@ -1887,6 +1887,9 @@ sub resolveArtifact {
|
|||
$message = $soap_answer->content();
|
||||
$self->logger->debug("Get message $message");
|
||||
}
|
||||
else {
|
||||
$self->logger->error("Error while sending message: ".$soap_answer->status_line);
|
||||
}
|
||||
}
|
||||
|
||||
return $message;
|
||||
|
|
|
@ -375,13 +375,7 @@ sub display {
|
|||
# 3 Authentication has been refused OR first access
|
||||
else {
|
||||
$skinfile = 'login';
|
||||
my $login = $self->userId($req);
|
||||
if ( $login eq 'anonymous' ) {
|
||||
$login = '';
|
||||
}
|
||||
elsif ( $req->user ) {
|
||||
$login = $req->{user};
|
||||
}
|
||||
my $login = $req->user;
|
||||
%templateParams = (
|
||||
MAIN_LOGO => $self->conf->{portalMainLogo},
|
||||
LANGS => $self->conf->{showLanguages},
|
||||
|
|
|
@ -169,7 +169,7 @@ sub displayModules {
|
|||
foreach my $module ( @{ $self->menuModules } ) {
|
||||
$self->logger->debug("Check if $module->[0] has to be displayed");
|
||||
|
||||
if ( $module->[1]->( $req, $req->sessionInfo ) ) {
|
||||
if ( $module->[1]->( $req, $req->userData ) ) {
|
||||
my $moduleHash = { $module->[0] => 1 };
|
||||
if ( $module->[0] eq 'Appslist' ) {
|
||||
$moduleHash->{'APPSLIST_LOOP'} = $self->appslist($req);
|
||||
|
@ -177,16 +177,16 @@ sub displayModules {
|
|||
elsif ( $module->[0] eq 'LoginHistory' ) {
|
||||
$moduleHash->{'SUCCESS_LOGIN'} =
|
||||
$self->p->mkSessionArray( $req,
|
||||
$req->{sessionInfo}->{_loginHistory}->{successLogin},
|
||||
$req->{userData}->{_loginHistory}->{successLogin},
|
||||
"", 0, 0 );
|
||||
$moduleHash->{'FAILED_LOGIN'} =
|
||||
$self->p->mkSessionArray( $req,
|
||||
$req->{sessionInfo}->{_loginHistory}->{failedLogin},
|
||||
$req->{userData}->{_loginHistory}->{failedLogin},
|
||||
"", 0, 1 );
|
||||
}
|
||||
elsif ( $module->[0] eq 'OidcConsents' ) {
|
||||
$moduleHash->{'OIDC_CONSENTS'} =
|
||||
$self->p->mkOidcConsent( $req, $req->sessionInfo );
|
||||
$self->p->mkOidcConsent( $req, $req->userData );
|
||||
}
|
||||
push @$displayModules, $moduleHash;
|
||||
}
|
||||
|
@ -413,7 +413,7 @@ sub _filterHash {
|
|||
if ( my $sub = $self->p->spRules->{$p} ) {
|
||||
eval {
|
||||
delete $apphash->{$key}
|
||||
unless ( $sub->( $req, $req->sessionInfo ) );
|
||||
unless ( $sub->( $req, $req->userData ) );
|
||||
};
|
||||
if ($@) {
|
||||
$self->logger->error("Partner rule $p returns: $@");
|
||||
|
@ -438,7 +438,7 @@ sub _filterHash {
|
|||
delete $apphash->{$key}
|
||||
unless (
|
||||
$self->p->HANDLER->grant(
|
||||
$req, $req->sessionInfo, $appuri, $cond, $vhost
|
||||
$req, $req->userData, $appuri, $cond, $vhost
|
||||
)
|
||||
);
|
||||
next;
|
||||
|
|
|
@ -49,6 +49,8 @@
|
|||
# (restricted)
|
||||
# * DELETE /mysession/<type>/key : delete key in data
|
||||
# (restricted)
|
||||
# * GET /myapplications : get my appplications
|
||||
# list
|
||||
#
|
||||
# There is no conflict with SOAP server, they can be used together
|
||||
|
||||
|
@ -65,7 +67,7 @@ use Lemonldap::NG::Portal::Main::Constants qw(
|
|||
URIRE
|
||||
);
|
||||
|
||||
our $VERSION = '2.0.12';
|
||||
our $VERSION = '2.0.14';
|
||||
|
||||
extends qw(
|
||||
Lemonldap::NG::Portal::Main::Plugin
|
||||
|
@ -247,8 +249,11 @@ sub init {
|
|||
->addAuthRoute(
|
||||
mysession => { ':sessionType' => 'updateMySession' },
|
||||
['PUT']
|
||||
);
|
||||
extends @parents if ($add);
|
||||
)
|
||||
|
||||
->addAuthRoute( myapplications => 'myApplications', ['GET'] );
|
||||
|
||||
extends @parents if ($add);
|
||||
$self->setTypes( $self->conf ) if ( $self->conf->{restSessionServer} );
|
||||
|
||||
return 1;
|
||||
|
@ -764,6 +769,23 @@ sub getUser {
|
|||
}
|
||||
}
|
||||
|
||||
sub myApplications {
|
||||
my ( $self, $req ) = @_;
|
||||
my @appslist = map {
|
||||
my @apps = map {
|
||||
{
|
||||
$_->{appname} => {
|
||||
AppUri => $_->{appuri},
|
||||
AppDesc => $_->{appdesc}
|
||||
}
|
||||
}
|
||||
} @{ $_->{applications} };
|
||||
{ Category => $_->{catname}, Applications => \@apps },
|
||||
} @{ $self->p->menu->appslist($req) };
|
||||
|
||||
return $self->p->sendJSONresponse( $req, { result => 1, myapplications => \@appslist } );
|
||||
}
|
||||
|
||||
sub _checkSecret {
|
||||
my ( $self, $secret ) = @_;
|
||||
my $isValid = 0;
|
||||
|
|
|
@ -2,6 +2,8 @@ use Test::More;
|
|||
use strict;
|
||||
use IO::String;
|
||||
use MIME::Base64;
|
||||
use URI;
|
||||
use URI::QueryParam;
|
||||
|
||||
require 't/test-lib.pm';
|
||||
|
||||
|
@ -80,6 +82,14 @@ ok( $res->[2]->[0] =~ m%<span trspan="connect">Connect</span>%,
|
|||
or print STDERR Dumper( $res->[2]->[0] );
|
||||
count(3);
|
||||
|
||||
my ( $host, $uri, $query ) =
|
||||
expectForm( $res, undef, undef, 'user', 'password' );
|
||||
my $uri = URI->new;
|
||||
$uri->query($query);
|
||||
is( $uri->query_param("user"), 'jdoe',
|
||||
"Login is pre-filled on second attemps" );
|
||||
count(1);
|
||||
|
||||
# Try to authenticate with bad password
|
||||
# -------------------------------------
|
||||
ok(
|
||||
|
|
|
@ -29,7 +29,7 @@ my ( $host, $url, $query ) =
|
|||
$mock->fake_module( 'Authen::Radius', check_pwd => sub { 0 } );
|
||||
|
||||
# Try to authenticate with bad password
|
||||
$query =~ s/user=/user=dwho/;
|
||||
$query =~ s/user=[^&]*/user=dwho/;
|
||||
$query =~ s/password=/password=jdoe/;
|
||||
ok(
|
||||
$res = $client->_post(
|
||||
|
@ -48,7 +48,7 @@ count(1);
|
|||
$mock->fake_module( 'Authen::Radius', check_pwd => sub { 1 } );
|
||||
|
||||
# Try to authenticate
|
||||
$query =~ s/user=/user=dwho/;
|
||||
$query =~ s/user=[^&]*/user=dwho/;
|
||||
$query =~ s/password=/password=dwho/;
|
||||
ok(
|
||||
$res = $client->_post(
|
||||
|
|
|
@ -63,7 +63,7 @@ foreach (@form) {
|
|||
expectForm( [ $res->[0], $res->[1], [$_] ], undef, undef, 'test' );
|
||||
}
|
||||
|
||||
$query =~ s/user=/user=dwho/;
|
||||
$query =~ s/user=[^&]*/user=dwho/;
|
||||
$query =~ s/password=/password=dwho/;
|
||||
$query =~ s/test=\w*\b/test=1_demo/;
|
||||
|
||||
|
|
254
lemonldap-ng-portal/t/32-CAS-Proxy.t
Normal file
254
lemonldap-ng-portal/t/32-CAS-Proxy.t
Normal file
|
@ -0,0 +1,254 @@
|
|||
use lib 'inc';
|
||||
use Test::More; # skip_all => 'CAS is in rebuild';
|
||||
use strict;
|
||||
use IO::String;
|
||||
use LWP::UserAgent;
|
||||
use LWP::Protocol::PSGI;
|
||||
use MIME::Base64;
|
||||
use XML::LibXML;
|
||||
|
||||
use LWP::UserAgent;
|
||||
use LWP::Protocol::PSGI;
|
||||
|
||||
our $PGTs = {};
|
||||
|
||||
LWP::Protocol::PSGI->register(
|
||||
sub {
|
||||
my $req = Plack::Request->new(@_);
|
||||
my $iou = $req->param("pgtIou");
|
||||
my $pgt = $req->param("pgtId");
|
||||
$PGTs->{$iou} = $pgt;
|
||||
return [ 200, [], [] ];
|
||||
}
|
||||
);
|
||||
|
||||
BEGIN {
|
||||
require 't/test-lib.pm';
|
||||
}
|
||||
|
||||
my $debug = 'error';
|
||||
my ( $issuer, $res );
|
||||
|
||||
eval { require XML::Simple };
|
||||
plan skip_all => "Missing dependencies: $@" if ($@);
|
||||
|
||||
ok( $issuer = issuer(), 'Issuer portal' );
|
||||
count(1);
|
||||
|
||||
my $s = "user=french&password=french";
|
||||
|
||||
# Login
|
||||
ok(
|
||||
$res = $issuer->_post(
|
||||
'/',
|
||||
IO::String->new($s),
|
||||
accept => 'text/html',
|
||||
length => length($s),
|
||||
),
|
||||
'Post authentication'
|
||||
);
|
||||
count(1);
|
||||
my $idpId = expectCookie($res);
|
||||
|
||||
# Request to an unknown service is rejected
|
||||
ok(
|
||||
$res = $issuer->_get(
|
||||
'/cas/login',
|
||||
cookie => "lemonldap=$idpId",
|
||||
query => 'service=http://auth.sp3.com/',
|
||||
accept => 'text/html'
|
||||
),
|
||||
'Query CAS server'
|
||||
);
|
||||
count(1);
|
||||
|
||||
expectPortalError( $res, 68, "Unauthorized CAS service" );
|
||||
|
||||
my $ticket;
|
||||
my $iou;
|
||||
|
||||
# Get a ticket for CAS application
|
||||
$ticket = casGetTicket( $issuer, $idpId, "http://casapp.com/" );
|
||||
|
||||
# CAS application gets PGT
|
||||
my $pgt = casGetPgt( $issuer, $ticket, "http://casapp.com/",
|
||||
"http://casapp.com/proxy" );
|
||||
|
||||
# CAS application gets PT for web service
|
||||
my $pt = casGetProxyTicket( $issuer, $pgt, "http://service.com/srv" );
|
||||
|
||||
# Service validate PT and gets PGT
|
||||
my $pgt2 = casGetPgt( $issuer, $pt, "http://service.com/srv",
|
||||
"http://service.com/proxy" );
|
||||
|
||||
# Service gets PT for sub-service
|
||||
my $pt2 = casGetProxyTicket( $issuer, $pgt2, "http://subservice.com/srv" );
|
||||
|
||||
# Sub-service validates PT
|
||||
my $res = casGetProxyResponse( $issuer, $pt2, "http://subservice.com/srv" );
|
||||
expectCasSuccess($res);
|
||||
|
||||
is_deeply( [
|
||||
casXPathAll(
|
||||
$res->[2]->[0],
|
||||
'/cas:serviceResponse/cas:authenticationSuccess'
|
||||
. '/cas:proxies/cas:proxy/text()'
|
||||
)
|
||||
],
|
||||
[ "http://service.com/proxy", "http://casapp.com/proxy" ],
|
||||
"Found proxies in correct order"
|
||||
);
|
||||
count(1);
|
||||
|
||||
# Make sure PGT is still valid a long time later
|
||||
Time::Fake->offset("+10h");
|
||||
my $pt = casGetProxyTicket( $issuer, $pgt, "http://service.com/srv" );
|
||||
expectCasSuccess(
|
||||
casGetProxyResponse( $issuer, $pt, "http://service.com/srv" ) );
|
||||
|
||||
clean_sessions();
|
||||
done_testing( count() );
|
||||
|
||||
sub issuer {
|
||||
return LLNG::Manager::Test->new( {
|
||||
ini => {
|
||||
logLevel => $debug,
|
||||
domain => 'idp.com',
|
||||
portal => 'http://auth.idp.com',
|
||||
authentication => 'Demo',
|
||||
userDB => 'Same',
|
||||
issuerDBCASActivation => 1,
|
||||
casAttr => 'uid',
|
||||
casTicketExpiration => '300',
|
||||
casAppMetaDataOptions => {
|
||||
sp => {
|
||||
casAppMetaDataOptionsService => 'http://casapp.com/',
|
||||
},
|
||||
},
|
||||
casAppMetaDataExportedVars => {
|
||||
sp => {
|
||||
cn => 'cn',
|
||||
mail => 'mail',
|
||||
uid => 'uid',
|
||||
},
|
||||
sp2 => {
|
||||
cn => 'cn',
|
||||
mail => 'mail',
|
||||
uid => 'uid',
|
||||
},
|
||||
},
|
||||
casAccessControlPolicy => 'error',
|
||||
multiValuesSeparator => ';',
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
sub casGetTicket {
|
||||
my ( $issuer, $id, $service ) = @_;
|
||||
ok(
|
||||
my $res = $issuer->_get(
|
||||
'/cas/login',
|
||||
cookie => "lemonldap=$id",
|
||||
query => 'service=' . $service,
|
||||
accept => 'text/html'
|
||||
),
|
||||
'Query CAS server'
|
||||
);
|
||||
count(1);
|
||||
my ($ticket) =
|
||||
expectRedirection( $res, qr#^http://casapp.com/\?.*ticket=([^&]+)# );
|
||||
|
||||
return $ticket;
|
||||
}
|
||||
|
||||
sub casGetPgt {
|
||||
my ( $issuer, $ticket, $service, $pgtUrl ) = @_;
|
||||
ok(
|
||||
my $res = $issuer->_get(
|
||||
'/cas/p3/proxyValidate',
|
||||
query => buildForm( {
|
||||
service => $service,
|
||||
ticket => $ticket,
|
||||
pgtUrl => $pgtUrl,
|
||||
}
|
||||
),
|
||||
accept => 'text/html'
|
||||
),
|
||||
'Query CAS server'
|
||||
);
|
||||
count(1);
|
||||
|
||||
expectOK($res);
|
||||
|
||||
my $pgt = casXPath( $res->[2]->[0], '//cas:proxyGrantingTicket/text()' );
|
||||
return $PGTs->{$pgt};
|
||||
}
|
||||
|
||||
sub casGetProxyResponse {
|
||||
my ( $issuer, $ticket, $service ) = @_;
|
||||
ok(
|
||||
my $res = $issuer->_get(
|
||||
'/cas/p3/proxyValidate',
|
||||
query => buildForm( { service => $service, ticket => $ticket } ),
|
||||
accept => 'text/html'
|
||||
),
|
||||
'Query CAS server'
|
||||
);
|
||||
|
||||
expectOK($res);
|
||||
count(1);
|
||||
return $res;
|
||||
}
|
||||
|
||||
sub casGetProxyTicket {
|
||||
my ( $issuer, $pgt, $service ) = @_;
|
||||
ok(
|
||||
my $res = $issuer->_get(
|
||||
'/cas/proxy',
|
||||
query => buildForm( {
|
||||
pgt => $pgt,
|
||||
targetService => $service,
|
||||
}
|
||||
),
|
||||
accept => 'text/html'
|
||||
),
|
||||
'Query CAS server'
|
||||
);
|
||||
count(1);
|
||||
|
||||
my $pt = casXPath( $res->[2]->[0],
|
||||
'/cas:serviceResponse/cas:proxySuccess/cas:proxyTicket/text()' );
|
||||
return $pt;
|
||||
}
|
||||
|
||||
sub expectCasSuccess {
|
||||
my ($res) = @_;
|
||||
my $content = $res->[2]->[0];
|
||||
ok(
|
||||
casXPath( $content, '/cas:serviceResponse/cas:authenticationSuccess' ),
|
||||
"Cas response contains authenticationSuccess"
|
||||
);
|
||||
count(1);
|
||||
}
|
||||
|
||||
sub casXPath {
|
||||
my ( $xmlString, $expr ) = @_;
|
||||
|
||||
my $dom = XML::LibXML->load_xml( string => $xmlString );
|
||||
my $xpc = XML::LibXML::XPathContext->new($dom);
|
||||
$xpc->registerNs( 'cas', 'http://www.yale.edu/tp/cas' );
|
||||
my ($match) = $xpc->findnodes($expr);
|
||||
ok($match);
|
||||
count(1);
|
||||
return $match;
|
||||
}
|
||||
|
||||
sub casXPathAll {
|
||||
my ( $xmlString, $expr ) = @_;
|
||||
|
||||
my $dom = XML::LibXML->load_xml( string => $xmlString );
|
||||
my $xpc = XML::LibXML::XPathContext->new($dom);
|
||||
$xpc->registerNs( 'cas', 'http://www.yale.edu/tp/cas' );
|
||||
return $xpc->findnodes($expr);
|
||||
}
|
169
lemonldap-ng-portal/t/32-CAS-Security.t
Normal file
169
lemonldap-ng-portal/t/32-CAS-Security.t
Normal file
|
@ -0,0 +1,169 @@
|
|||
use lib 'inc';
|
||||
use Test::More; # skip_all => 'CAS is in rebuild';
|
||||
use strict;
|
||||
use IO::String;
|
||||
use LWP::UserAgent;
|
||||
use LWP::Protocol::PSGI;
|
||||
use MIME::Base64;
|
||||
|
||||
BEGIN {
|
||||
require 't/test-lib.pm';
|
||||
}
|
||||
|
||||
my $debug = 'error';
|
||||
my ( $issuer, $res );
|
||||
|
||||
eval { require XML::Simple };
|
||||
plan skip_all => "Missing dependencies: $@" if ($@);
|
||||
|
||||
ok( $issuer = issuer(), 'Issuer portal' );
|
||||
count(1);
|
||||
|
||||
my $s = "user=french&password=french";
|
||||
|
||||
# Login
|
||||
ok(
|
||||
$res = $issuer->_post(
|
||||
'/',
|
||||
IO::String->new($s),
|
||||
accept => 'text/html',
|
||||
length => length($s),
|
||||
),
|
||||
'Post authentication'
|
||||
);
|
||||
count(1);
|
||||
my $idpId = expectCookie($res);
|
||||
|
||||
# Request to an unknown service is rejected
|
||||
ok(
|
||||
$res = $issuer->_get(
|
||||
'/cas/login',
|
||||
cookie => "lemonldap=$idpId",
|
||||
query => 'service=http://auth.sp3.com/',
|
||||
accept => 'text/html'
|
||||
),
|
||||
'Query CAS server'
|
||||
);
|
||||
count(1);
|
||||
|
||||
expectPortalError( $res, 68, "Unauthorized CAS service" );
|
||||
|
||||
my $ticket;
|
||||
|
||||
# Ticket cannot be validated against wrong service
|
||||
$ticket = casGetTicket( $issuer, $idpId, "http://auth.sp.com/" );
|
||||
expectCasFail( casGetResponse( $issuer, $ticket, "http://auth.sp2.com/" ),
|
||||
"INVALID_SERVICE" );
|
||||
|
||||
# Tickets are invalidated after success response
|
||||
$ticket = casGetTicket( $issuer, $idpId, "http://auth.sp.com/" );
|
||||
expectCasSuccess( casGetResponse( $issuer, $ticket, "http://auth.sp.com/" ) );
|
||||
expectCasFail( casGetResponse( $issuer, $ticket, "http://auth.sp.com/" ) );
|
||||
|
||||
# Tickets are invalidated after failure response
|
||||
$ticket = casGetTicket( $issuer, $idpId, "http://auth.sp.com/" );
|
||||
expectCasFail( casGetResponse( $issuer, $ticket, "http://auth.sp2.com/" ),
|
||||
"INVALID_SERVICE" );
|
||||
expectCasFail( casGetResponse( $issuer, $ticket, "http://auth.sp.com/" ) );
|
||||
|
||||
# Ticket are no longer valid after TTL
|
||||
$ticket = casGetTicket( $issuer, $idpId, "http://auth.sp.com/" );
|
||||
Time::Fake->offset("+10m");
|
||||
expectCasFail( casGetResponse( $issuer, $ticket, "http://auth.sp.com/" ) );
|
||||
|
||||
clean_sessions();
|
||||
done_testing( count() );
|
||||
|
||||
sub issuer {
|
||||
return LLNG::Manager::Test->new( {
|
||||
ini => {
|
||||
logLevel => $debug,
|
||||
domain => 'idp.com',
|
||||
portal => 'http://auth.idp.com',
|
||||
authentication => 'Demo',
|
||||
userDB => 'Same',
|
||||
issuerDBCASActivation => 1,
|
||||
casAttr => 'uid',
|
||||
casTicketExpiration => '300',
|
||||
casAppMetaDataOptions => {
|
||||
sp => {
|
||||
casAppMetaDataOptionsService => 'http://auth.sp.com/',
|
||||
},
|
||||
sp2 => {
|
||||
casAppMetaDataOptionsService => 'http://auth.sp2.com/',
|
||||
},
|
||||
},
|
||||
casAppMetaDataExportedVars => {
|
||||
sp => {
|
||||
cn => 'cn',
|
||||
mail => 'mail',
|
||||
uid => 'uid',
|
||||
},
|
||||
sp2 => {
|
||||
cn => 'cn',
|
||||
mail => 'mail',
|
||||
uid => 'uid',
|
||||
},
|
||||
},
|
||||
casAccessControlPolicy => 'error',
|
||||
multiValuesSeparator => ';',
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
sub casGetTicket {
|
||||
my ( $issuer, $id, $service ) = @_;
|
||||
ok(
|
||||
my $res = $issuer->_get(
|
||||
'/cas/login',
|
||||
cookie => "lemonldap=$id",
|
||||
query => 'service=' . $service,
|
||||
accept => 'text/html'
|
||||
),
|
||||
'Query CAS server'
|
||||
);
|
||||
count(1);
|
||||
my ($ticket) =
|
||||
expectRedirection( $res, qr#^http://auth.sp.com/\?.*(ticket=[^&]+)# );
|
||||
|
||||
return $ticket;
|
||||
}
|
||||
|
||||
sub casGetResponse {
|
||||
my ( $issuer, $ticket, $service ) = @_;
|
||||
ok(
|
||||
my $res = $issuer->_get(
|
||||
'/cas/p3/serviceValidate',
|
||||
query => 'service=' . $service . '&' . $ticket,
|
||||
accept => 'text/html'
|
||||
),
|
||||
'Query CAS server'
|
||||
);
|
||||
|
||||
expectOK($res);
|
||||
count(1);
|
||||
return $res;
|
||||
}
|
||||
|
||||
sub expectCasFail {
|
||||
my ( $res, $code ) = @_;
|
||||
$code ||= "INVALID_TICKET";
|
||||
my $content = $res->[2]->[0];
|
||||
like(
|
||||
$content,
|
||||
qr,authenticationFailure code="([^"]+)",,
|
||||
"CAS response indicates success"
|
||||
);
|
||||
my ($response_code) = $content =~ qr,authenticationFailure code="([^"]+)",;
|
||||
is( $response_code, $code, "Incorrect CAS error code" );
|
||||
count(2);
|
||||
}
|
||||
|
||||
sub expectCasSuccess {
|
||||
my ($res) = @_;
|
||||
my $content = $res->[2]->[0];
|
||||
like( $content, qr,cas:authenticationSuccess,,
|
||||
"CAS response indicates success" );
|
||||
count(1);
|
||||
}
|
|
@ -9,7 +9,7 @@ BEGIN {
|
|||
my ( $client, $res, $id );
|
||||
|
||||
$client = LLNG::Manager::Test->new(
|
||||
{ ini => { logLevel => 'error', restSessionServer => 0, }, } );
|
||||
{ ini => { logLevel => 'error', restSessionServer => 0 } } );
|
||||
|
||||
# Try to authenticate
|
||||
# -------------------
|
||||
|
@ -55,6 +55,38 @@ ok(
|
|||
count(1);
|
||||
expectOK($res);
|
||||
|
||||
# Test myapplications endpoint
|
||||
ok(
|
||||
$res = $client->_get(
|
||||
'/myapplications', cookie => "lemonldap=$id"
|
||||
),
|
||||
'Request for my applications'
|
||||
);
|
||||
count(1);
|
||||
expectOK($res);
|
||||
$res = eval { JSON::from_json( $res->[2]->[0] ) };
|
||||
if ($@) {
|
||||
fail("Bad JSON response: $@");
|
||||
count(1);
|
||||
}
|
||||
ok( $res->{result} == 1, ' Result == 1' );
|
||||
count(1);
|
||||
ok( $res->{myapplications}->[0]->{Category} eq 'Sample applications',
|
||||
' "Sample applications" category found' );
|
||||
ok( scalar @{ $res->{myapplications}->[0]->{Applications} } == 2,
|
||||
' Two applications found' );
|
||||
ok(
|
||||
$res->{myapplications}->[0]->{Applications}->[0]->{'Application Test 1'}
|
||||
->{AppDesc} eq 'A simple application displaying authenticated user',
|
||||
' Description app1 found'
|
||||
);
|
||||
ok(
|
||||
$res->{myapplications}->[0]->{Applications}->[1]->{'Application Test 2'}
|
||||
->{AppUri} =~ m#http://test2\.example\.com/#,
|
||||
' URI app2 found'
|
||||
);
|
||||
count(4);
|
||||
|
||||
# Test logout
|
||||
$client->logout($id);
|
||||
|
||||
|
|
|
@ -7,9 +7,7 @@ BEGIN {
|
|||
require 't/test-lib.pm';
|
||||
}
|
||||
|
||||
my $res;
|
||||
my $id;
|
||||
my $json;
|
||||
my ($res, $id, $json);
|
||||
|
||||
my $client = LLNG::Manager::Test->new( {
|
||||
ini => {
|
||||
|
|
|
@ -30,7 +30,7 @@ count(1);
|
|||
my ( $host, $url, $query ) =
|
||||
expectForm( $res, '#', undef, 'user', 'password', 'spoofId', 'token' );
|
||||
|
||||
$query =~ s/user=/user=rtyler/;
|
||||
$query =~ s/user=[^&]*/user=rtyler/;
|
||||
$query =~ s/password=/password=rtyler/;
|
||||
$query =~ s/spoofId=/spoofId=dwho/;
|
||||
|
||||
|
@ -62,7 +62,7 @@ count(1);
|
|||
( $host, $url, $query ) =
|
||||
expectForm( $res, '#', undef, 'user', 'password', 'spoofId', 'token' );
|
||||
|
||||
$query =~ s/user=/user=rtyler/;
|
||||
$query =~ s/user=[^&]*/user=rtyler/;
|
||||
$query =~ s/password=/password=rtyler/;
|
||||
$query =~ s/spoofId=/spoofId=msmith/;
|
||||
|
||||
|
@ -86,7 +86,7 @@ count(2);
|
|||
( $host, $url, $query ) =
|
||||
expectForm( $res, '#', undef, 'user', 'password', 'spoofId', 'token' );
|
||||
|
||||
$query =~ s/user=/user=dwho/;
|
||||
$query =~ s/user=[^&]*/user=dwho/;
|
||||
$query =~ s/password=/password=dwho/;
|
||||
$query =~ s/spoofId=/spoofId=msmith/;
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user