Merge branch 'v2.0'
This commit is contained in:
commit
ce02973702
|
@ -49,5 +49,9 @@ with the following parameters (Options -> Basic) :
|
|||
* **Client Secret**: the same you set in Publik configuration.
|
||||
* **Allowed redirection addresses for login**: The "Callback URL" for authentic2 : https://authentic2-instance/accounts/oidc/callback/
|
||||
|
||||
And in Options -> Logout
|
||||
|
||||
* **Allowed redirection addresses for logout**: The "Logout URL" for authentic2 : https://authentic2-instance/logout/
|
||||
|
||||
.. |image0| image:: /applications/logo-publik.png
|
||||
:class: align-center
|
||||
|
|
|
@ -314,11 +314,9 @@ Options
|
|||
(RSXXX) or HMAC (HSXXX) based signature algorithms
|
||||
- **Access Token signature algorithm**: Select one of the available public
|
||||
key signature algorithms
|
||||
- **Userinfo signature algorithm** (since version ``2.0.12``): Select one
|
||||
of the available signature algorithms to release user information as a JWT
|
||||
on the ``/userinfo`` endpoint. If this option is left empty, user
|
||||
information will be released as a plain JSON object. The ``None`` value
|
||||
will release user information as an unsigned JWT.
|
||||
- **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.
|
||||
- **Require PKCE** (since version ``2.0.4``): a code challenge is
|
||||
required at token endpoint (see
|
||||
`RFC7636 <https://tools.ietf.org/html/rfc7636>`__)
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
sphinx_bootstrap_theme
|
|
@ -18,7 +18,8 @@ sub retrieveSession {
|
|||
|
||||
# Update cache
|
||||
$class->data($data);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
$req->data->{oauth2_error} = 'invalid_token';
|
||||
}
|
||||
return $data;
|
||||
|
@ -93,6 +94,10 @@ sub fetchId {
|
|||
return;
|
||||
}
|
||||
my $infos = $class->getOIDCInfos($access_token_sid);
|
||||
unless ($infos) {
|
||||
$req->data->{oauth2_error} = 'invalid_token';
|
||||
return;
|
||||
}
|
||||
|
||||
# Store scope and rpid for future session attributes
|
||||
if ( $infos->{rp} ) {
|
||||
|
@ -147,6 +152,20 @@ sub getOIDCInfos {
|
|||
unless ( $oidcSession->error ) {
|
||||
$class->logger->debug("Get OIDC session $id");
|
||||
|
||||
# Verify that session is valid
|
||||
unless ( $oidcSession->data->{_utime} ) {
|
||||
$class->logger->error("_utime missing from Access Token session");
|
||||
return;
|
||||
}
|
||||
|
||||
my $ttl = $class->tsv->{timeout} - time + $oidcSession->data->{_utime};
|
||||
$class->logger->debug( "Session TTL = " . $ttl );
|
||||
|
||||
if ( time - $oidcSession->data->{_utime} > $class->tsv->{timeout} ) {
|
||||
$class->logger->info("Access Token session $id expired");
|
||||
return;
|
||||
}
|
||||
|
||||
$infos = { %{ $oidcSession->data } };
|
||||
}
|
||||
else {
|
||||
|
|
|
@ -259,7 +259,7 @@ ok(
|
|||
$client->_get( '/', undef, 'foo.example.fr', "lemonldap=$sessionId" ),
|
||||
'Reject "foo.example.fr"'
|
||||
);
|
||||
ok( $res->[0] == 302, ' Code is 302' ) or explain( $res, 302 );
|
||||
ok( $res->[0] == 403, ' Code is 403' ) or explain( $res, 403 );
|
||||
count(2);
|
||||
|
||||
ok(
|
||||
|
@ -268,7 +268,7 @@ ok(
|
|||
),
|
||||
'Reject "foo.example.org/orgdeny"'
|
||||
);
|
||||
ok( $res->[0] == 302, ' Code is 302' ) or explain( $res, 302 );
|
||||
ok( $res->[0] == 403, ' Code is 403' ) or explain( $res, 403 );
|
||||
count(2);
|
||||
|
||||
ok(
|
||||
|
@ -286,7 +286,7 @@ ok(
|
|||
),
|
||||
'Reject "abfoo.example.org/orgdeny"'
|
||||
);
|
||||
ok( $res->[0] == 302, ' Code is 302' ) or explain( $res, 302 );
|
||||
ok( $res->[0] == 403, ' Code is 403' ) or explain( $res, 403 );
|
||||
count(2);
|
||||
|
||||
ok(
|
||||
|
@ -312,7 +312,7 @@ ok(
|
|||
$client->_get( '/', undef, 'abfoo.example.org', "lemonldap=$sessionId" ),
|
||||
'Reject "abfoo.example.org/"'
|
||||
);
|
||||
ok( $res->[0] == 302, ' Code is 302' ) or explain( $res, 302 );
|
||||
ok( $res->[0] == 403, ' Code is 403' ) or explain( $res, 403 );
|
||||
count(2);
|
||||
|
||||
ok(
|
||||
|
|
|
@ -4,7 +4,7 @@ BEGIN {
|
|||
require 't/test-psgi-lib.pm';
|
||||
}
|
||||
|
||||
my $maintests = 21;
|
||||
my $maintests = 25;
|
||||
|
||||
init(
|
||||
'Lemonldap::NG::Handler::Server',
|
||||
|
@ -57,7 +57,7 @@ Lemonldap::NG::Common::Session->new( {
|
|||
info => {
|
||||
"user_session_id" => $sessionId,
|
||||
"_type" => "access_token",
|
||||
"_utime" => time,
|
||||
"_utime" => ( time - 72000 + 300 ),
|
||||
"rp" => "rp-example2",
|
||||
"scope" => "openid email read"
|
||||
}
|
||||
|
@ -74,7 +74,7 @@ Lemonldap::NG::Common::Session->new( {
|
|||
info => {
|
||||
"offline_session_id" => '000999000',
|
||||
"_type" => "refresh_token",
|
||||
"_utime" => time,
|
||||
"_utime" => ( time - 72000 + 300 ),
|
||||
"rp" => "rp-example",
|
||||
"scope" => "openid email read"
|
||||
}
|
||||
|
@ -117,6 +117,7 @@ ok(
|
|||
|
||||
# Check headers
|
||||
%h = @{ $res->[1] };
|
||||
is( $res->[0], 401, "Got correct HTTP code" );
|
||||
is( $h{'WWW-Authenticate'}, 'Bearer', 'Got WWW-Authenticate: Bearer' );
|
||||
|
||||
# Request with invalid Access Token
|
||||
|
@ -210,6 +211,24 @@ is( $h{'Auth-ClientConfKey'},
|
|||
'rp-example', 'Client confkey correctly transmitted' );
|
||||
like( $h{'Auth-Scope'}, qr/\bemail\b/, 'Scope correctly transmitted' );
|
||||
|
||||
Time::Fake->offset("+600s");
|
||||
ok(
|
||||
$res = $client->_get(
|
||||
'/read', undef,
|
||||
'test1.example.com', '',
|
||||
VHOSTTYPE => 'OAuth2',
|
||||
HTTP_AUTHORIZATION => 'Bearer 999888777',
|
||||
),
|
||||
'Invalid access token'
|
||||
);
|
||||
%h = @{ $res->[1] };
|
||||
is( $res->[0], 401, "Access was rejected" );
|
||||
is(
|
||||
$h{'WWW-Authenticate'},
|
||||
'Bearer error="invalid_token"',
|
||||
'Got correct error code'
|
||||
);
|
||||
|
||||
count($maintests);
|
||||
done_testing( count() );
|
||||
clean();
|
||||
|
|
|
@ -2457,35 +2457,35 @@ m[^(?:ldapi://[^/]*/?|\w[\w\-\.]*(?::\d{1,5})?|ldap(?:s|\+tls)?://\w[\w\-\.]*(?:
|
|||
'default' => '',
|
||||
'select' => [ {
|
||||
'k' => '',
|
||||
'v' => ''
|
||||
'v' => 'JSON'
|
||||
},
|
||||
{
|
||||
'k' => 'none',
|
||||
'v' => 'None'
|
||||
'v' => 'JWT/None'
|
||||
},
|
||||
{
|
||||
'k' => 'HS256',
|
||||
'v' => 'HS256'
|
||||
'v' => 'JWT/HS256'
|
||||
},
|
||||
{
|
||||
'k' => 'HS384',
|
||||
'v' => 'HS384'
|
||||
'v' => 'JWT/HS384'
|
||||
},
|
||||
{
|
||||
'k' => 'HS512',
|
||||
'v' => 'HS512'
|
||||
'v' => 'JWT/HS512'
|
||||
},
|
||||
{
|
||||
'k' => 'RS256',
|
||||
'v' => 'RS256'
|
||||
'v' => 'JWT/RS256'
|
||||
},
|
||||
{
|
||||
'k' => 'RS384',
|
||||
'v' => 'RS384'
|
||||
'v' => 'JWT/RS384'
|
||||
},
|
||||
{
|
||||
'k' => 'RS512',
|
||||
'v' => 'RS512'
|
||||
'v' => 'JWT/RS512'
|
||||
}
|
||||
],
|
||||
'type' => 'select'
|
||||
|
|
|
@ -4265,14 +4265,14 @@ m{^(?:ldapi://[^/]*/?|\w[\w\-\.]*(?::\d{1,5})?|ldap(?:s|\+tls)?://\w[\w\-\.]*(?:
|
|||
oidcRPMetaDataOptionsUserInfoSignAlg => {
|
||||
type => 'select',
|
||||
select => [
|
||||
{ k => '', v => '' },
|
||||
{ k => 'none', v => 'None' },
|
||||
{ k => 'HS256', v => 'HS256' },
|
||||
{ k => 'HS384', v => 'HS384' },
|
||||
{ k => 'HS512', v => 'HS512' },
|
||||
{ k => 'RS256', v => 'RS256' },
|
||||
{ k => 'RS384', v => 'RS384' },
|
||||
{ k => 'RS512', v => 'RS512' },
|
||||
{ k => '', v => 'JSON' },
|
||||
{ k => 'none', v => 'JWT/None' },
|
||||
{ k => 'HS256', v => 'JWT/HS256' },
|
||||
{ k => 'HS384', v => 'JWT/HS384' },
|
||||
{ k => 'HS512', v => 'JWT/HS512' },
|
||||
{ k => 'RS256', v => 'JWT/RS256' },
|
||||
{ k => 'RS384', v => 'JWT/RS384' },
|
||||
{ k => 'RS512', v => 'JWT/RS512' },
|
||||
],
|
||||
default => '',
|
||||
},
|
||||
|
|
|
@ -574,35 +574,35 @@ function templates(tpl,key) {
|
|||
"select" : [
|
||||
{
|
||||
"k" : "",
|
||||
"v" : ""
|
||||
"v" : "JSON"
|
||||
},
|
||||
{
|
||||
"k" : "none",
|
||||
"v" : "None"
|
||||
"v" : "JWT/None"
|
||||
},
|
||||
{
|
||||
"k" : "HS256",
|
||||
"v" : "HS256"
|
||||
"v" : "JWT/HS256"
|
||||
},
|
||||
{
|
||||
"k" : "HS384",
|
||||
"v" : "HS384"
|
||||
"v" : "JWT/HS384"
|
||||
},
|
||||
{
|
||||
"k" : "HS512",
|
||||
"v" : "HS512"
|
||||
"v" : "JWT/HS512"
|
||||
},
|
||||
{
|
||||
"k" : "RS256",
|
||||
"v" : "RS256"
|
||||
"v" : "JWT/RS256"
|
||||
},
|
||||
{
|
||||
"k" : "RS384",
|
||||
"v" : "RS384"
|
||||
"v" : "JWT/RS384"
|
||||
},
|
||||
{
|
||||
"k" : "RS512",
|
||||
"v" : "RS512"
|
||||
"v" : "JWT/RS512"
|
||||
}
|
||||
],
|
||||
"title" : "oidcRPMetaDataOptionsUserInfoSignAlg",
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -689,7 +689,7 @@
|
|||
"oidcRPMetaDataOptionsRule":"قاعدة الدخول",
|
||||
"oidcRPMetaDataOptionsTimeouts":"Timeouts",
|
||||
"oidcRPMetaDataOptionsUserIDAttr":"خاصّيّة المستخدم",
|
||||
"oidcRPMetaDataOptionsUserInfoSignAlg":"Userinfo signature algorithm",
|
||||
"oidcRPMetaDataOptionsUserInfoSignAlg":"UserInfo response format",
|
||||
"oidcRPMetaDataScopeRules":"Scope rules",
|
||||
"oidcRPName":"اسم أوبين أيدي كونيكت RP",
|
||||
"oidcRPStateTimeout":"حالة مهلة الجلسة",
|
||||
|
|
|
@ -689,7 +689,7 @@
|
|||
"oidcRPMetaDataOptionsRule":"Access rule",
|
||||
"oidcRPMetaDataOptionsTimeouts":"Timeouts",
|
||||
"oidcRPMetaDataOptionsUserIDAttr":"User attribute",
|
||||
"oidcRPMetaDataOptionsUserInfoSignAlg":"Userinfo signature algorithm",
|
||||
"oidcRPMetaDataOptionsUserInfoSignAlg":"UserInfo response format",
|
||||
"oidcRPMetaDataScopeRules":"Scope rules",
|
||||
"oidcRPName":"OpenID Connect RP Name",
|
||||
"oidcRPStateTimeout":"State session timeout",
|
||||
|
|
|
@ -689,7 +689,7 @@
|
|||
"oidcRPMetaDataOptionsRule":"Access rule",
|
||||
"oidcRPMetaDataOptionsTimeouts":"Timeouts",
|
||||
"oidcRPMetaDataOptionsUserIDAttr":"User attribute",
|
||||
"oidcRPMetaDataOptionsUserInfoSignAlg":"Userinfo signature algorithm",
|
||||
"oidcRPMetaDataOptionsUserInfoSignAlg":"UserInfo response format",
|
||||
"oidcRPMetaDataScopeRules":"Scope rules",
|
||||
"oidcRPName":"OpenID Connect RP Name",
|
||||
"oidcRPStateTimeout":"State session timeout",
|
||||
|
|
|
@ -689,7 +689,7 @@
|
|||
"oidcRPMetaDataOptionsRule":"Regla de acceso",
|
||||
"oidcRPMetaDataOptionsTimeouts":"Timeouts",
|
||||
"oidcRPMetaDataOptionsUserIDAttr":"Atributo de usuario",
|
||||
"oidcRPMetaDataOptionsUserInfoSignAlg":"Userinfo signature algorithm",
|
||||
"oidcRPMetaDataOptionsUserInfoSignAlg":"UserInfo response format",
|
||||
"oidcRPMetaDataScopeRules":"Scope rules",
|
||||
"oidcRPName":"OpenID Connect RP Name",
|
||||
"oidcRPStateTimeout":"Caducidad de estado de sesión",
|
||||
|
|
|
@ -689,7 +689,7 @@
|
|||
"oidcRPMetaDataOptionsRule":"Règle d'accès",
|
||||
"oidcRPMetaDataOptionsTimeouts":"Expiration",
|
||||
"oidcRPMetaDataOptionsUserIDAttr":"Attribut de l'utilisateur",
|
||||
"oidcRPMetaDataOptionsUserInfoSignAlg":"Algorithme de signature des informations utilisateur",
|
||||
"oidcRPMetaDataOptionsUserInfoSignAlg":"Format de réponse Userinfo",
|
||||
"oidcRPMetaDataScopeRules":"Règles de scope",
|
||||
"oidcRPName":"Nom du client OpenID Connect",
|
||||
"oidcRPStateTimeout":"Durée d'une session state",
|
||||
|
|
|
@ -689,7 +689,7 @@
|
|||
"oidcRPMetaDataOptionsRule":"Regola di accesso",
|
||||
"oidcRPMetaDataOptionsTimeouts":"Timeouts",
|
||||
"oidcRPMetaDataOptionsUserIDAttr":"Attributo utente",
|
||||
"oidcRPMetaDataOptionsUserInfoSignAlg":"Userinfo signature algorithm",
|
||||
"oidcRPMetaDataOptionsUserInfoSignAlg":"UserInfo response format",
|
||||
"oidcRPMetaDataScopeRules":"Scope rules",
|
||||
"oidcRPName":"Nome di OpenID Connect RP",
|
||||
"oidcRPStateTimeout":"Durata della sessione stato",
|
||||
|
|
|
@ -689,7 +689,7 @@
|
|||
"oidcRPMetaDataOptionsRule":"Reguła dostępu",
|
||||
"oidcRPMetaDataOptionsTimeouts":"Limit czasu",
|
||||
"oidcRPMetaDataOptionsUserIDAttr":"Atrybut użytkownika",
|
||||
"oidcRPMetaDataOptionsUserInfoSignAlg":"Userinfo signature algorithm",
|
||||
"oidcRPMetaDataOptionsUserInfoSignAlg":"UserInfo response format",
|
||||
"oidcRPMetaDataScopeRules":"Zasady dotyczące zakresu",
|
||||
"oidcRPName":"Nazwa RP OpenID Connect",
|
||||
"oidcRPStateTimeout":"Limit czasu sesji stanowej",
|
||||
|
|
|
@ -689,7 +689,7 @@
|
|||
"oidcRPMetaDataOptionsRule":"Erişim kuralı",
|
||||
"oidcRPMetaDataOptionsTimeouts":"Zaman aşımları",
|
||||
"oidcRPMetaDataOptionsUserIDAttr":"Kullanıcı niteliği",
|
||||
"oidcRPMetaDataOptionsUserInfoSignAlg":"Userinfo signature algorithm",
|
||||
"oidcRPMetaDataOptionsUserInfoSignAlg":"UserInfo response format",
|
||||
"oidcRPMetaDataScopeRules":"Kapsam kuralları",
|
||||
"oidcRPName":"OpenID Connect RP Adı",
|
||||
"oidcRPStateTimeout":"Oturum zaman aşımını belirle",
|
||||
|
@ -699,7 +699,7 @@
|
|||
"oidcServiceAllowHybridFlow":"Hibrit Akış",
|
||||
"oidcServiceAllowImplicitFlow":"Kapalı Akış",
|
||||
"oidcServiceAllowOffline":"Çevrimdışı erişime izin ver",
|
||||
"oidcServiceAllowOnlyDeclaredScopes":"Only allow declared scopes",
|
||||
"oidcServiceAllowOnlyDeclaredScopes":"Sadece belirli kapsamlara izin ver",
|
||||
"oidcServiceAuthorizationCodeExpiration":"Yetkilendirme Kodu sona erme",
|
||||
"oidcServiceDynamicRegistrationExportedVars":"Dinamik kayıtlanma için dışa aktarılan değişkenler",
|
||||
"oidcServiceDynamicRegistrationExtraClaims":"Dinamik kayıtlanma için ekstra talepler",
|
||||
|
|
|
@ -689,7 +689,7 @@
|
|||
"oidcRPMetaDataOptionsRule":"Quy tắc truy cập",
|
||||
"oidcRPMetaDataOptionsTimeouts":"Timeouts",
|
||||
"oidcRPMetaDataOptionsUserIDAttr":"thuộc tính người dùng",
|
||||
"oidcRPMetaDataOptionsUserInfoSignAlg":"Userinfo signature algorithm",
|
||||
"oidcRPMetaDataOptionsUserInfoSignAlg":"UserInfo response format",
|
||||
"oidcRPMetaDataScopeRules":"Scope rules",
|
||||
"oidcRPName":"OpenID Connect RP Name",
|
||||
"oidcRPStateTimeout":"Thời gian chờ của trạng thái phiên làm việc",
|
||||
|
|
|
@ -689,7 +689,7 @@
|
|||
"oidcRPMetaDataOptionsRule":"Access rule",
|
||||
"oidcRPMetaDataOptionsTimeouts":"Timeouts",
|
||||
"oidcRPMetaDataOptionsUserIDAttr":"User attribute",
|
||||
"oidcRPMetaDataOptionsUserInfoSignAlg":"Userinfo signature algorithm",
|
||||
"oidcRPMetaDataOptionsUserInfoSignAlg":"UserInfo response format",
|
||||
"oidcRPMetaDataScopeRules":"Scope rules",
|
||||
"oidcRPName":"OpenID Connect RP Name",
|
||||
"oidcRPStateTimeout":"State session timeout",
|
||||
|
|
|
@ -689,7 +689,7 @@
|
|||
"oidcRPMetaDataOptionsRule":"存取規則",
|
||||
"oidcRPMetaDataOptionsTimeouts":"逾時",
|
||||
"oidcRPMetaDataOptionsUserIDAttr":"使用者屬性",
|
||||
"oidcRPMetaDataOptionsUserInfoSignAlg":"Userinfo signature algorithm",
|
||||
"oidcRPMetaDataOptionsUserInfoSignAlg":"UserInfo response format",
|
||||
"oidcRPMetaDataScopeRules":"Scope rules",
|
||||
"oidcRPName":"OpenID 連線 RP 名稱",
|
||||
"oidcRPStateTimeout":"狀態工作階段逾時",
|
||||
|
|
|
@ -741,8 +741,11 @@ sub run {
|
|||
my $access_token;
|
||||
my $at_hash;
|
||||
|
||||
my $release_claims_in_id_token = 1;
|
||||
if ( $response_type =~ /\btoken\b/ ) {
|
||||
|
||||
$release_claims_in_id_token = 0;
|
||||
|
||||
# Store data in access token
|
||||
# Generate access_token
|
||||
$access_token = $self->newAccessToken(
|
||||
|
@ -775,9 +778,14 @@ sub run {
|
|||
if $hash_level;
|
||||
}
|
||||
|
||||
my $id_token =
|
||||
$self->_generateIDToken( $req, $oidc_request,
|
||||
$rp, $scope, { at_hash => $at_hash } );
|
||||
my $id_token = $self->_generateIDToken(
|
||||
$req,
|
||||
$rp,
|
||||
$scope,
|
||||
$req->sessionInfo,
|
||||
$release_claims_in_id_token,
|
||||
{ at_hash => $at_hash, nonce => $oidc_request->{nonce} }
|
||||
);
|
||||
|
||||
unless ($id_token) {
|
||||
$self->logger->error("Could not generate ID token");
|
||||
|
@ -841,8 +849,11 @@ sub run {
|
|||
$c_hash = $self->createHash( $code, $hash_level )
|
||||
if $hash_level;
|
||||
|
||||
my $release_claims_in_id_token = 1;
|
||||
if ( $response_type =~ /\btoken\b/ ) {
|
||||
|
||||
$release_claims_in_id_token = 0;
|
||||
|
||||
# Generate access_token
|
||||
$access_token = $self->newAccessToken(
|
||||
$req, $rp, $scope,
|
||||
|
@ -873,12 +884,13 @@ sub run {
|
|||
if ( $response_type =~ /\bid_token\b/ ) {
|
||||
|
||||
$id_token = $self->_generateIDToken(
|
||||
$req,
|
||||
$oidc_request,
|
||||
$rp, $scope,
|
||||
$req, $rp, $scope,
|
||||
$req->sessionInfo,
|
||||
$release_claims_in_id_token,
|
||||
{
|
||||
at_hash => $at_hash,
|
||||
c_hash => $c_hash,
|
||||
nonce => $oidc_request->{nonce},
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -1260,6 +1272,28 @@ sub _handlePasswordGrant {
|
|||
$self->logger->debug("Generated refresh token: $refresh_token");
|
||||
}
|
||||
|
||||
# Generate ID token
|
||||
my $id_token = undef;
|
||||
if ( $self->_hasScope( "openid", $scope ) ) {
|
||||
|
||||
# Compute hash to store in at_hash
|
||||
my $alg = $self->conf->{oidcRPMetaDataOptions}->{$rp}
|
||||
->{oidcRPMetaDataOptionsIDTokenSignAlg};
|
||||
my ($hash_level) = ( $alg =~ /(?:\w{2})(\d{3})/ );
|
||||
my $at_hash = $self->createHash( $access_token, $hash_level )
|
||||
if $hash_level;
|
||||
|
||||
$id_token =
|
||||
$self->_generateIDToken( $req, $rp, $scope, $req->sessionInfo, 0,
|
||||
{ ( $at_hash ? ( at_hash => $at_hash ) : () ), } );
|
||||
|
||||
unless ($id_token) {
|
||||
$self->logger->error(
|
||||
"Failed to generate ID Token for service: $client_id");
|
||||
return $self->sendOIDCError( $req, 'server_error', 500 );
|
||||
}
|
||||
}
|
||||
|
||||
# Send token response
|
||||
my $expires_in =
|
||||
$self->conf->{oidcRPMetaDataOptions}->{$rp}
|
||||
|
@ -1272,6 +1306,7 @@ sub _handlePasswordGrant {
|
|||
expires_in => $expires_in + 0,
|
||||
( ( $scope ne $req_scope ) ? ( scope => "$scope" ) : () ),
|
||||
( $refresh_token ? ( refresh_token => "$refresh_token" ) : () ),
|
||||
( $id_token ? ( id_token => "$id_token" ) : () ),
|
||||
};
|
||||
|
||||
$self->logger->debug("Send token response");
|
||||
|
@ -1432,45 +1467,17 @@ sub _handleAuthorizationCodeGrant {
|
|||
my $at_hash = $self->createHash( $access_token, $hash_level )
|
||||
if $hash_level;
|
||||
|
||||
# ID token payload
|
||||
# TODO: refactor to use _generateIDToken
|
||||
my $id_token_exp =
|
||||
$self->conf->{oidcRPMetaDataOptions}->{$rp}
|
||||
->{oidcRPMetaDataOptionsIDTokenExpiration}
|
||||
|| $self->conf->{oidcServiceIDTokenExpiration};
|
||||
$id_token_exp += time;
|
||||
|
||||
my $id_token_acr = "loa-" . $apacheSession->data->{authenticationLevel};
|
||||
|
||||
my $id_token_payload_hash = {
|
||||
iss => $self->iss, # Issuer Identifier
|
||||
sub => $user_id, # Subject Identifier
|
||||
aud => $self->getAudiences($rp), # Audience
|
||||
exp => $id_token_exp, # expiration
|
||||
iat => time, # Issued time
|
||||
auth_time => $apacheSession->data->{_lastAuthnUTime}
|
||||
, # Authentication time
|
||||
acr => $id_token_acr, # Authentication Context Class Reference
|
||||
azp => $client_id, # Authorized party
|
||||
# TODO amr
|
||||
};
|
||||
|
||||
my $nonce = $codeSession->data->{nonce};
|
||||
$id_token_payload_hash->{nonce} = $nonce if defined $nonce;
|
||||
$id_token_payload_hash->{'at_hash'} = $at_hash if $at_hash;
|
||||
|
||||
if ( $self->force_id_claims($rp) ) {
|
||||
my $claims = $self->buildUserInfoResponseFromId( $req, $scope,
|
||||
$rp, $codeSession->data->{user_session_id} );
|
||||
|
||||
foreach ( keys %$claims ) {
|
||||
$id_token_payload_hash->{$_} = $claims->{$_}
|
||||
unless ( $_ eq "sub" );
|
||||
}
|
||||
}
|
||||
|
||||
# Create ID Token
|
||||
my $id_token = $self->createIDToken( $req, $id_token_payload_hash, $rp );
|
||||
my $nonce = $codeSession->data->{nonce};
|
||||
my $id_token = $self->_generateIDToken(
|
||||
$req, $rp, $scope,
|
||||
$apacheSession->data,
|
||||
0,
|
||||
{
|
||||
( $nonce ? ( nonce => $nonce ) : () ),
|
||||
( $at_hash ? ( at_hash => $at_hash ) : () ),
|
||||
}
|
||||
);
|
||||
|
||||
unless ($id_token) {
|
||||
$self->logger->error(
|
||||
|
@ -1533,8 +1540,6 @@ sub _handleRefreshTokenGrant {
|
|||
}
|
||||
|
||||
my $access_token;
|
||||
my $user_id;
|
||||
my $auth_time;
|
||||
my $session;
|
||||
|
||||
# If this refresh token is tied to a SSO session
|
||||
|
@ -1548,10 +1553,6 @@ sub _handleRefreshTokenGrant {
|
|||
return $self->sendOIDCError( $req, 'invalid_grant', 400 );
|
||||
}
|
||||
|
||||
$user_id = $self->getUserIDForRP( $req, $rp, $session->data );
|
||||
|
||||
$auth_time = $session->data->{_lastAuthnUTime};
|
||||
|
||||
# Generate access_token
|
||||
$access_token = $self->newAccessToken(
|
||||
$req, $rp,
|
||||
|
@ -1610,11 +1611,6 @@ sub _handleRefreshTokenGrant {
|
|||
$refreshSession->data->{$_} = $req->sessionInfo->{$_};
|
||||
}
|
||||
|
||||
$user_id = $self->getUserIDForRP( $req, $rp, $req->sessionInfo );
|
||||
$self->logger->debug("Found corresponding user: $user_id");
|
||||
|
||||
$auth_time = $refreshSession->data->{auth_time};
|
||||
|
||||
# Generate access_token
|
||||
$access_token = $self->newAccessToken(
|
||||
$req, $rp,
|
||||
|
@ -1640,57 +1636,30 @@ sub _handleRefreshTokenGrant {
|
|||
my $at_hash = $self->createHash( $access_token, $hash_level )
|
||||
if $hash_level;
|
||||
|
||||
# ID token payload
|
||||
# TODO: refactor to use _generateIDToken
|
||||
my $id_token_exp =
|
||||
$self->conf->{oidcRPMetaDataOptions}->{$rp}
|
||||
->{oidcRPMetaDataOptionsIDTokenExpiration}
|
||||
|| $self->conf->{oidcServiceIDTokenExpiration};
|
||||
$id_token_exp += time;
|
||||
|
||||
# Authentication level using refresh tokens should probably stay at 0
|
||||
my $id_token_acr = "loa-0";
|
||||
|
||||
my $id_token_payload_hash = {
|
||||
iss => $self->iss, # Issuer Identifier
|
||||
sub => $user_id, # Subject Identifier
|
||||
aud => $self->getAudiences($rp), # Audience
|
||||
exp => $id_token_exp, # expiration
|
||||
iat => time, # Issued time
|
||||
# TODO: is this the right value when using refresh tokens??
|
||||
auth_time => $auth_time, # Authentication time
|
||||
acr => $id_token_acr, # Authentication Context Class Reference
|
||||
azp => $client_id, # Authorized party
|
||||
# TODO amr
|
||||
};
|
||||
|
||||
my $nonce = $refreshSession->data->{nonce};
|
||||
$id_token_payload_hash->{nonce} = $nonce if defined $nonce;
|
||||
$id_token_payload_hash->{'at_hash'} = $at_hash if $at_hash;
|
||||
|
||||
# If we forced sending claims in ID token
|
||||
if ( $self->force_id_claims($rp) ) {
|
||||
my $claims =
|
||||
$self->buildUserInfoResponse( $req, $refreshSession->data->{scope},
|
||||
$rp, $session );
|
||||
|
||||
foreach ( keys %$claims ) {
|
||||
$id_token_payload_hash->{$_} = $claims->{$_}
|
||||
unless ( $_ eq "sub" );
|
||||
}
|
||||
}
|
||||
|
||||
# Create ID Token
|
||||
my $id_token = $self->createIDToken( $req, $id_token_payload_hash, $rp );
|
||||
my $id_token = undef;
|
||||
if ( $self->_hasScope( 'openid', $refreshSession->data->{scope} ) ) {
|
||||
my $nonce = $refreshSession->data->{nonce};
|
||||
$id_token = $self->_generateIDToken(
|
||||
$req, $rp,
|
||||
$refreshSession->data->{scope},
|
||||
$session->data,
|
||||
0,
|
||||
{
|
||||
( $nonce ? ( nonce => $nonce ) : () ),
|
||||
( $at_hash ? ( at_hash => $at_hash ) : () ),
|
||||
}
|
||||
);
|
||||
|
||||
unless ($id_token) {
|
||||
$self->logger->error(
|
||||
"Failed to generate ID Token for service: $client_id");
|
||||
return $self->sendOIDCError( $req, 'server_error', 500 );
|
||||
unless ($id_token) {
|
||||
$self->logger->error(
|
||||
"Failed to generate ID Token for service: $rp");
|
||||
return $self->sendOIDCError( $req, 'server_error', 500 );
|
||||
}
|
||||
|
||||
$self->logger->debug("Generated id token: $id_token");
|
||||
}
|
||||
|
||||
$self->logger->debug("Generated id token: $id_token");
|
||||
|
||||
# Send token response
|
||||
my $expires_in =
|
||||
$self->conf->{oidcRPMetaDataOptions}->{$rp}
|
||||
|
@ -1701,7 +1670,7 @@ sub _handleRefreshTokenGrant {
|
|||
access_token => "$access_token",
|
||||
token_type => 'Bearer',
|
||||
expires_in => $expires_in + 0,
|
||||
id_token => "$id_token",
|
||||
( $id_token ? ( id_token => "$id_token" ) : () ),
|
||||
};
|
||||
|
||||
# TODO
|
||||
|
@ -2348,10 +2317,11 @@ sub _convertOldFormatConsents {
|
|||
}
|
||||
|
||||
sub _generateIDToken {
|
||||
my ( $self, $req, $oidc_request, $rp, $scope, $extra_claims ) = @_;
|
||||
my ( $self, $req, $rp, $scope, $sessionInfo, $release_user_claims,
|
||||
$extra_claims )
|
||||
= @_;
|
||||
|
||||
my $response_type = $oidc_request->{'response_type'};
|
||||
my $client_id = $oidc_request->{'client_id'};
|
||||
my $client_id = $self->oidcRPList->{$rp}->{oidcRPMetaDataOptionsClientID};
|
||||
|
||||
my $id_token_exp =
|
||||
$self->conf->{oidcRPMetaDataOptions}->{$rp}
|
||||
|
@ -2359,7 +2329,7 @@ sub _generateIDToken {
|
|||
|| $self->conf->{oidcServiceIDTokenExpiration};
|
||||
$id_token_exp += time;
|
||||
|
||||
my $authenticationLevel = $req->{sessionInfo}->{authenticationLevel} || 0;
|
||||
my $authenticationLevel = $sessionInfo->{authenticationLevel} || 0;
|
||||
|
||||
my $id_token_acr = "loa-$authenticationLevel";
|
||||
foreach ( keys %{ $self->conf->{oidcServiceMetaDataAuthnContext} } ) {
|
||||
|
@ -2371,20 +2341,18 @@ sub _generateIDToken {
|
|||
}
|
||||
}
|
||||
|
||||
my $user_id = $self->getUserIDForRP( $req, $rp, $req->{sessionInfo} );
|
||||
my $user_id = $self->getUserIDForRP( $req, $rp, $sessionInfo );
|
||||
|
||||
my $id_token_payload_hash = {
|
||||
iss => $self->iss, # Issuer Identifier
|
||||
sub => $user_id, # Subject Identifier
|
||||
aud => $self->getAudiences($rp), # Audience
|
||||
exp => $id_token_exp, # expiration
|
||||
iat => time, # Issued time
|
||||
auth_time => $req->{sessionInfo}->{_lastAuthnUTime}
|
||||
, # Authentication time
|
||||
iss => $self->iss, # Issuer Identifier
|
||||
sub => $user_id, # Subject Identifier
|
||||
aud => $self->getAudiences($rp), # Audience
|
||||
exp => $id_token_exp, # expiration
|
||||
iat => time, # Issued time
|
||||
auth_time => $sessionInfo->{_lastAuthnUTime}, # Authentication time
|
||||
acr => $id_token_acr, # Authentication Context Class Reference
|
||||
azp => $client_id, # Authorized party
|
||||
# TODO amr
|
||||
nonce => $oidc_request->{'nonce'} # Nonce
|
||||
};
|
||||
|
||||
for ( keys %{$extra_claims} ) {
|
||||
|
@ -2392,14 +2360,12 @@ sub _generateIDToken {
|
|||
if $extra_claims->{$_};
|
||||
}
|
||||
|
||||
if ( $response_type !~ /\btoken\b/
|
||||
|| $self->force_id_claims($rp) )
|
||||
{
|
||||
# Decided by response_type or forced in RP config
|
||||
if ( $release_user_claims || $self->force_id_claims($rp) ) {
|
||||
|
||||
# No access_token
|
||||
# Claims must be set in id_token
|
||||
my $claims =
|
||||
$self->buildUserInfoResponseFromId( $req, $scope, $rp, $req->id );
|
||||
$self->buildUserInfoResponseFromData( $req, $scope, $rp,
|
||||
$sessionInfo );
|
||||
|
||||
foreach ( keys %$claims ) {
|
||||
$id_token_payload_hash->{$_} = $claims->{$_}
|
||||
|
|
|
@ -181,6 +181,10 @@ ok( $res->{cn} eq 'Frédéric Accents', 'UTF-8 values' )
|
|||
or explain( $res, 'cn => Frédéric Accents' );
|
||||
count(2);
|
||||
|
||||
my $id_token_decoded = id_token_payload( $res->{_oidc_id_token} );
|
||||
is( $id_token_decoded->{acr}, 'customacr-1', "Correct custom ACR" );
|
||||
count(1);
|
||||
|
||||
# Logout initiated by RP
|
||||
ok(
|
||||
$res = $rp->_get(
|
||||
|
@ -193,7 +197,7 @@ ok(
|
|||
);
|
||||
count(1);
|
||||
( $url, $query ) = expectRedirection( $res,
|
||||
qr#http://auth.op.com(/oauth2/logout)\?(post_logout_redirect_uri=.+)$# );
|
||||
qr#http://auth.op.com(/oauth2/logout)\?.*(post_logout_redirect_uri=.+)$# );
|
||||
|
||||
# Push logout to OP
|
||||
switch ('op');
|
||||
|
@ -337,11 +341,11 @@ sub op {
|
|||
oidcOPMetaDataJSON => {},
|
||||
oidcOPMetaDataJWKS => {},
|
||||
oidcServiceMetaDataAuthnContext => {
|
||||
'loa-4' => 4,
|
||||
'loa-1' => 1,
|
||||
'loa-5' => 5,
|
||||
'loa-2' => 2,
|
||||
'loa-3' => 3
|
||||
'loa-4' => 4,
|
||||
'customacr-1' => 1,
|
||||
'loa-5' => 5,
|
||||
'loa-2' => 2,
|
||||
'loa-3' => 3
|
||||
},
|
||||
oidcServicePrivateKeySig => oidc_key_op_private_sig,
|
||||
oidcServicePublicKeySig => oidc_key_op_public_sig,
|
||||
|
@ -378,6 +382,7 @@ sub rp {
|
|||
oidcOPMetaDataOptionsMaxAge => 30,
|
||||
oidcOPMetaDataOptionsDisplay => "",
|
||||
oidcOPMetaDataOptionsClientID => "rpid",
|
||||
oidcOPMetaDataOptionsStoreIDToken => 1,
|
||||
oidcOPMetaDataOptionsConfigurationURI =>
|
||||
"https://auth.op.com/.well-known/openid-configuration"
|
||||
}
|
||||
|
|
|
@ -159,9 +159,10 @@ count(4);
|
|||
|
||||
# Check attributes in ID Token
|
||||
my $id_token_decoded = id_token_payload( $prms{id_token} );
|
||||
ok( $id_token_decoded->{sub} eq "dwho", 'Check sub value' );
|
||||
is( $id_token_decoded->{sub}, "dwho", 'Check sub value' );
|
||||
ok( !$id_token_decoded->{name}, 'Claim name must not be in ID token' );
|
||||
count(2);
|
||||
is( $id_token_decoded->{azp}, 'rpid', ' azp found' );
|
||||
count(3);
|
||||
|
||||
$op->logout($idpId);
|
||||
|
||||
|
|
|
@ -51,6 +51,8 @@ sub runTest {
|
|||
ok( $id_token, "Got ID token" );
|
||||
|
||||
my $id_token_payload = id_token_payload($id_token);
|
||||
my $auth_time = $id_token_payload->{auth_time};
|
||||
ok( $auth_time, "Authentication date found in token");
|
||||
is(
|
||||
$id_token_payload->{name},
|
||||
'Frédéric Accents',
|
||||
|
@ -117,6 +119,7 @@ sub runTest {
|
|||
ok( !defined $refresh_token2, "Refresh token not present" );
|
||||
|
||||
$id_token_payload = id_token_payload($id_token);
|
||||
is( $id_token_payload->{auth_time}, $auth_time, 'Original auth_time retained' );
|
||||
is(
|
||||
$id_token_payload->{name},
|
||||
'Frédéric Accents',
|
||||
|
|
|
@ -52,7 +52,7 @@ my $op = LLNG::Manager::Test->new( {
|
|||
oidcRPMetaDataOptionsIDTokenSignAlg => "HS512",
|
||||
oidcRPMetaDataOptionsClientSecret => "rpsecret",
|
||||
oidcRPMetaDataOptionsUserIDAttr => "",
|
||||
oidcRPMetaDataOptionsAccessTokenExpiration => 3600,
|
||||
oidcRPMetaDataOptionsAccessTokenExpiration => 120,
|
||||
oidcRPMetaDataOptionsBypassConsent => 1,
|
||||
oidcRPMetaDataOptionsRefreshToken => 1,
|
||||
oidcRPMetaDataOptionsIDTokenForceClaims => 1,
|
||||
|
@ -97,7 +97,7 @@ my $goodquery = buildForm( {
|
|||
grant_type => 'password',
|
||||
username => 'french',
|
||||
password => 'french',
|
||||
scope => 'openid profile email',
|
||||
scope => 'profile email openid',
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -136,8 +136,12 @@ my $access_token = $payload->{access_token};
|
|||
ok( $access_token, "Access Token found" );
|
||||
count(1);
|
||||
my $token_res_scope = $payload->{scope};
|
||||
ok( $token_res_scope, "Scope found in token response" );
|
||||
count(1);
|
||||
ok( $token_res_scope, "Scope found in token response" );
|
||||
ok( $payload->{id_token}, "Found ID token in original grant" );
|
||||
|
||||
my $refresh_token = $payload->{refresh_token};
|
||||
ok( $refresh_token, "Got refresh token" );
|
||||
count(3);
|
||||
|
||||
# Get userinfo
|
||||
$res = $op->_post(
|
||||
|
@ -180,6 +184,46 @@ like( $payload->{scope}, qr/\balways\b/, "Rule-enforced scope found" );
|
|||
is( $payload->{scope}, $token_res_scope,
|
||||
"Token response scope matches token scope" );
|
||||
|
||||
# Expire token
|
||||
Time::Fake->offset("+305m");
|
||||
|
||||
ok(
|
||||
$res = $op->_post(
|
||||
"/oauth2/introspect",
|
||||
IO::String->new($query),
|
||||
accept => 'text/html',
|
||||
length => length $query,
|
||||
custom => {
|
||||
HTTP_AUTHORIZATION => "Basic " . encode_base64("rpid:rpsecret"),
|
||||
},
|
||||
),
|
||||
"Post introspection"
|
||||
);
|
||||
|
||||
$res = expectJSON($res);
|
||||
is( $res->{active}, 0, "Token is no longer active" );
|
||||
|
||||
$query = buildForm( {
|
||||
grant_type => 'refresh_token',
|
||||
refresh_token => $refresh_token,
|
||||
}
|
||||
);
|
||||
|
||||
ok(
|
||||
$res = $op->_post(
|
||||
"/oauth2/token",
|
||||
IO::String->new($query),
|
||||
accept => 'text/json',
|
||||
length => length $query,
|
||||
custom => {
|
||||
HTTP_AUTHORIZATION => "Basic " . encode_base64("rpid:rpsecret"),
|
||||
},
|
||||
),
|
||||
"Post introspection"
|
||||
);
|
||||
$res = expectJSON($res);
|
||||
ok( $res->{id_token}, "Found ID token in refresh grant" );
|
||||
|
||||
clean_sessions();
|
||||
done_testing();
|
||||
|
||||
|
|
|
@ -0,0 +1,186 @@
|
|||
use lib 'inc';
|
||||
use Test::More;
|
||||
use strict;
|
||||
use IO::String;
|
||||
use LWP::UserAgent;
|
||||
use LWP::Protocol::PSGI;
|
||||
use MIME::Base64;
|
||||
use JSON;
|
||||
|
||||
BEGIN {
|
||||
require 't/test-lib.pm';
|
||||
require 't/oidc-lib.pm';
|
||||
}
|
||||
|
||||
my $debug = 'error';
|
||||
|
||||
# Initialization
|
||||
my $op = LLNG::Manager::Test->new( {
|
||||
ini => {
|
||||
logLevel => $debug,
|
||||
domain => 'op.com',
|
||||
portal => 'http://auth.op.com',
|
||||
|
||||
macros => {
|
||||
gender => '"32"',
|
||||
_whatToTrace => '$uid',
|
||||
nickname => '"froggie; frenchie"',
|
||||
},
|
||||
issuerDBOpenIDConnectActivation => 1,
|
||||
oidcRPMetaDataExportedVars => {
|
||||
rp => {
|
||||
email => "mail;string;always",
|
||||
preferred_username => "uid",
|
||||
name => "cn",
|
||||
gender => "gender;int;auto",
|
||||
nickname => "nickname",
|
||||
}
|
||||
},
|
||||
oidcRPMetaDataOptions => {
|
||||
rp => {
|
||||
oidcRPMetaDataOptionsDisplayName => "RP",
|
||||
oidcRPMetaDataOptionsIDTokenExpiration => 3600,
|
||||
oidcRPMetaDataOptionsClientID => "rpid",
|
||||
oidcRPMetaDataOptionsAllowOffline => 1,
|
||||
oidcRPMetaDataOptionsAllowPasswordGrant => 1,
|
||||
oidcRPMetaDataOptionsIDTokenSignAlg => "HS512",
|
||||
oidcRPMetaDataOptionsClientSecret => "rpsecret",
|
||||
oidcRPMetaDataOptionsUserIDAttr => "",
|
||||
oidcRPMetaDataOptionsAccessTokenExpiration => 120,
|
||||
oidcRPMetaDataOptionsBypassConsent => 1,
|
||||
oidcRPMetaDataOptionsRefreshToken => 1,
|
||||
oidcRPMetaDataOptionsIDTokenForceClaims => 1,
|
||||
oidcRPMetaDataOptionsRule => '$uid eq "french"',
|
||||
}
|
||||
},
|
||||
oidcRPMetaDataScopeRules => {
|
||||
rp => {
|
||||
"read" => '$requested',
|
||||
"french" => '$uid eq "french"',
|
||||
"always" => '1',
|
||||
},
|
||||
},
|
||||
oidcServicePrivateKeySig => oidc_key_op_private_sig,
|
||||
oidcServicePublicKeySig => oidc_key_op_public_sig,
|
||||
}
|
||||
}
|
||||
);
|
||||
my $res;
|
||||
|
||||
# Resource Owner Password Credentials Grant
|
||||
# Access Token Request
|
||||
# https://tools.ietf.org/html/rfc6749#section-4.3
|
||||
my $query = buildForm( {
|
||||
client_id => 'rpid',
|
||||
client_secret => 'rpsecret',
|
||||
grant_type => 'password',
|
||||
username => 'french',
|
||||
password => 'french',
|
||||
scope => 'profile email',
|
||||
}
|
||||
);
|
||||
|
||||
## Login should be valid
|
||||
$res = $op->_post(
|
||||
"/oauth2/token",
|
||||
IO::String->new($query),
|
||||
accept => 'application/json',
|
||||
length => length($query),
|
||||
);
|
||||
my $payload = expectJSON($res);
|
||||
|
||||
my $access_token = $payload->{access_token};
|
||||
ok( $access_token, "Access Token found" );
|
||||
count(1);
|
||||
my $token_res_scope = $payload->{scope};
|
||||
ok( $token_res_scope, "Scope found in token response" );
|
||||
is( $payload->{id_token}, undef, "No ID token in original request" );
|
||||
|
||||
my $refresh_token = $payload->{refresh_token};
|
||||
ok( $refresh_token, "Got refresh token" );
|
||||
count(3);
|
||||
|
||||
# Get userinfo
|
||||
$res = $op->_post(
|
||||
"/oauth2/userinfo",
|
||||
IO::String->new(''),
|
||||
accept => 'application/json',
|
||||
length => 0,
|
||||
custom => {
|
||||
HTTP_AUTHORIZATION => "Bearer " . $access_token,
|
||||
},
|
||||
);
|
||||
|
||||
$payload = expectJSON($res);
|
||||
|
||||
ok( $payload->{'name'} eq "Frédéric Accents", 'Got User Info' );
|
||||
like( $res->[2]->[0], qr/"gender":32/, "Attribute released as int in JSON" );
|
||||
is( ref( $payload->{email} ),
|
||||
"ARRAY", "Single valued attribute forced as array" );
|
||||
is( ref( $payload->{nickname} ),
|
||||
"ARRAY", "Multi valued attribute exposed as array" );
|
||||
|
||||
my $query = "token=$access_token";
|
||||
ok(
|
||||
$res = $op->_post(
|
||||
"/oauth2/introspect",
|
||||
IO::String->new($query),
|
||||
accept => 'text/html',
|
||||
length => length $query,
|
||||
custom => {
|
||||
HTTP_AUTHORIZATION => "Basic " . encode_base64("rpid:rpsecret"),
|
||||
},
|
||||
),
|
||||
"Post introspection"
|
||||
);
|
||||
$payload = expectJSON($res);
|
||||
unlike( $payload->{scope}, qr/\bread\b/,
|
||||
"Scope read not asked, and thus not found" );
|
||||
like( $payload->{scope}, qr/\bfrench\b/, "Attribute-based scope found" );
|
||||
like( $payload->{scope}, qr/\balways\b/, "Rule-enforced scope found" );
|
||||
is( $payload->{scope}, $token_res_scope,
|
||||
"Token response scope matches token scope" );
|
||||
|
||||
# Expire token
|
||||
Time::Fake->offset("+5m");
|
||||
|
||||
ok(
|
||||
$res = $op->_post(
|
||||
"/oauth2/introspect",
|
||||
IO::String->new($query),
|
||||
accept => 'text/html',
|
||||
length => length $query,
|
||||
custom => {
|
||||
HTTP_AUTHORIZATION => "Basic " . encode_base64("rpid:rpsecret"),
|
||||
},
|
||||
),
|
||||
"Post introspection"
|
||||
);
|
||||
|
||||
$res = expectJSON($res);
|
||||
is( $res->{active}, 0, "Token is no longer active" );
|
||||
|
||||
$query = buildForm( {
|
||||
grant_type => 'refresh_token',
|
||||
refresh_token => $refresh_token,
|
||||
}
|
||||
);
|
||||
|
||||
ok(
|
||||
$res = $op->_post(
|
||||
"/oauth2/token",
|
||||
IO::String->new($query),
|
||||
accept => 'text/json',
|
||||
length => length $query,
|
||||
custom => {
|
||||
HTTP_AUTHORIZATION => "Basic " . encode_base64("rpid:rpsecret"),
|
||||
},
|
||||
),
|
||||
"Post introspection"
|
||||
);
|
||||
$res = expectJSON($res);
|
||||
is( $res->{id_token}, undef, "No ID token in refreshed response" );
|
||||
|
||||
clean_sessions();
|
||||
done_testing();
|
||||
|
|
@ -159,6 +159,7 @@ BuildRequires: perl(SOAP::Transport::HTTP)
|
|||
BuildRequires: perl(strict)
|
||||
BuildRequires: perl(String::Random)
|
||||
BuildRequires: perl(Sys::Syslog)
|
||||
BuildRequires: perl(Test::LeakTrace)
|
||||
BuildRequires: perl(Test::MockObject)
|
||||
BuildRequires: perl(Test::Output)
|
||||
BuildRequires: perl(Test::Pod) >= 1.00
|
||||
|
|
Loading…
Reference in New Issue