Service token server (#971)

This commit is contained in:
Xavier Guimard 2017-03-01 06:41:42 +00:00
parent e2f4de3f9d
commit 64756142e1
12 changed files with 136 additions and 7 deletions

View File

@ -23,7 +23,7 @@ use constant HANDLERSECTION => "handler";
use constant MANAGERSECTION => "manager";
use constant SESSIONSEXPLORERSECTION => "sessionsExplorer";
use constant APPLYSECTION => "apply";
our $hashParameters = qr/^(?:(?:l(?:o(?:ca(?:lSessionStorageOption|tionRule)|goutService)|dapExportedVar|wp(?:Ssl)?Opt)|(?:(?:d(?:emo|bi)|facebook|webID)ExportedVa|exported(?:Heade|Va))r|g(?:r(?:antSessionRule|oup)|lobalStorageOption)|n(?:otificationStorageOption|ginxCustomHandler)|c(?:as(?:StorageOption|Attribute)|ombModule)|re(?:moteGlobalStorageOption|loadUrl)|CAS_proxiedService|macro)s|o(?:idc(?:RPMetaData(?:(?:Option(?:sExtraClaim)?|ExportedVar)s|Node)|OPMetaData(?:(?:ExportedVar|Option)s|J(?:SON|WKS)|Node)|S(?:erviceMetaDataAuthnContext|torageOptions))|penIdExportedVars)|s(?:aml(?:S(?:PMetaData(?:(?:ExportedAttribute|Option)s|Node|XML)|torageOptions)|IDPMetaData(?:(?:ExportedAttribute|Option)s|Node|XML))|essionDataToRemember|laveExportedVars)|p(?:ersistentStorageOptions|o(?:rtalSkinRules|st))|a(?:uthChoiceModules|pplicationList)|v(?:hostOptions|irtualHost)|SSLVarIf)$/;
our $hashParameters = qr/^(?:(?:l(?:o(?:ca(?:lSessionStorageOption|tionRule)|goutService)|dapExportedVar|wp(?:Ssl)?Opt)|(?:(?:d(?:emo|bi)|facebook|webID)ExportedVa|exported(?:Heade|Va))r|g(?:r(?:antSessionRule|oup)|lobalStorageOption)|n(?:otificationStorageOption|ginxCustomHandler)|c(?:as(?:StorageOption|Attribute)|ombModule)|re(?:moteGlobalStorageOption|loadUrl)|CAS_proxiedService|macro)s|o(?:idc(?:RPMetaData(?:(?:Option(?:sExtraClaim)?|ExportedVar)s|Node)|OPMetaData(?:(?:ExportedVar|Option)s|J(?:SON|WKS)|Node)|S(?:erviceMetaDataAuthnContext|torageOptions))|penIdExportedVars)|s(?:aml(?:S(?:PMetaData(?:(?:ExportedAttribute|Option)s|Node|XML)|torageOptions)|IDPMetaData(?:(?:ExportedAttribute|Option)s|Node|XML))|(?:laveExportedVar|TokenScope)s|essionDataToRemember)|p(?:ersistentStorageOptions|o(?:rtalSkinRules|st))|a(?:uthChoiceModules|pplicationList)|v(?:hostOptions|irtualHost)|SSLVarIf)$/;
our @sessionTypes = ( 'remoteGlobal', 'cas', 'global', 'localSession', 'persistent', 'saml', 'oidc' );

View File

@ -20,7 +20,7 @@ our $specialNodeHash = {
};
our $doubleHashKeys = 'issuerDBGetParameters';
our $simpleHashKeys = '(?:(?:l(?:o(?:calSessionStorageOption|goutService)|dapExportedVar|wp(?:Ssl)?Opt)|(?:(?:d(?:emo|bi)|facebook|webID)E|e)xportedVar|g(?:r(?:antSessionRule|oup)|lobalStorageOption)|n(?:otificationStorageOption|ginxCustomHandler)|c(?:as(?:StorageOption|Attribute)|ombModule)|p(?:ersistentStorageOption|ortalSkinRule)|re(?:moteGlobalStorageOption|loadUrl)|CAS_proxiedService|macro)s|o(?:idcS(?:erviceMetaDataAuthnContext|torageOptions)|penIdExportedVars)|s(?:(?:amlStorageOption|laveExportedVar)s|essionDataToRemember)|a(?:uthChoiceModules|pplicationList)|SSLVarIf)';
our $simpleHashKeys = '(?:(?:l(?:o(?:calSessionStorageOption|goutService)|dapExportedVar|wp(?:Ssl)?Opt)|(?:(?:d(?:emo|bi)|facebook|webID)E|e)xportedVar|g(?:r(?:antSessionRule|oup)|lobalStorageOption)|n(?:otificationStorageOption|ginxCustomHandler)|c(?:as(?:StorageOption|Attribute)|ombModule)|p(?:ersistentStorageOption|ortalSkinRule)|re(?:moteGlobalStorageOption|loadUrl)|CAS_proxiedService|macro)s|s(?:(?:amlStorageOption|laveExportedVar|TokenScope)s|essionDataToRemember)|o(?:idcS(?:erviceMetaDataAuthnContext|torageOptions)|penIdExportedVars)|a(?:uthChoiceModules|pplicationList)|SSLVarIf)';
our $specialNodeKeys = '(?:(?:saml(?:ID|S)|oidc[OR])PMetaDataNode|virtualHost)s';
our $oidcOPMetaDataNodeKeys = 'oidcOPMetaData(?:Options(?:C(?:lient(?:Secret|ID)|heckJWTSignature|onfigurationURI)|TokenEndpointAuthMethod|(?:JWKSTimeou|Promp)t|I(?:DTokenMaxAge|con)|S(?:toreIDToken|cope)|U(?:iLocales|seNonce)|Display(?:Name)?|AcrValues|MaxAge)|ExportedVars|J(?:SON|WKS))';
our $oidcRPMetaDataNodeKeys = 'oidcRPMetaData(?:Options(?:I(?:DToken(?:Expiration|SignAlg)|con)|(?:RedirectUri|ExtraClaim)s|AccessTokenExpiration|Client(?:Secret|ID)|BypassConsent|DisplayName|UserIDAttr)|ExportedVars)';

View File

@ -2784,6 +2784,13 @@ qr/^(?:(?:(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.])*(?:[a-zA-Z][-a-
'staticPrefix' => {
'type' => 'text'
},
'sTokenScopes' => {
'type' => 'keyTextContainer'
},
'sTokenService' => {
'default' => 0,
'type' => 'bool'
},
'storePassword' => {
'default' => 0,
'type' => 'bool'

View File

@ -1011,6 +1011,17 @@ sub attributes {
'List of attributes to export by SOAP or REST servers',
},
# Service token service
sTokenService => {
type => 'bool',
default => 0,
documentation => 'Enable service token service',
},
sTokenScopes => {
type => 'keyTextContainer',
documentation => 'List of authorizated virtualHosts',
},
## Virtualhosts
# Fake attribute: used by manager REST API to agglomerate all other

View File

@ -498,10 +498,8 @@ sub tree {
},
'reloadUrls',
{
title => 'advancedParams',
help => 'start.html#advanced_features',
title => 'plugins',
nodes => [
'customFunctions',
'portalStatus',
{
title => 'portalServers',
@ -591,6 +589,17 @@ sub tree {
form => 'simpleInputContainer',
nodes => [ 'u2fActivation', 'u2fSelfRegistration', ]
},
{
title => 'sToken',
nodes => [ 'sTokenService', 'sTokenScopes' ]
},
]
},
{
title => 'advancedParams',
help => 'start.html#advanced_features',
nodes => [
'customFunctions',
{
title => 'security',
help => 'security.html#configure_security_settings',

View File

@ -503,6 +503,7 @@
"persistentSessions": "Persistent sessions",
"persistentStorage": "Apache::Session module",
"persistentStorageOptions": "Apache::Session module parameters",
"plugins": "Plugins",
"port": "Port",
"portal": "URL",
"portalAntiFrame": "Anti frame protection",
@ -620,6 +621,9 @@
"SSLVar": "Extracted certificate field",
"SSLVarIf": "Conditional extracted certificate field",
"startTime": "Creation date",
"sToken": "Service tokens",
"sTokenScopes": "Service token scopes",
"sTokenService": "Token server activation",
"successfullySaved": "Successfully saved",
"storePassword": "Store user password in session datas",
"successLoginNumber": "Number of registered logins",

View File

@ -503,6 +503,7 @@
"persistentSessions": "Sessions persistantes",
"persistentStorage": "Module Apache::Session",
"persistentStorageOptions": "Paramètres du module Apache::Session",
"plugins": "Extensions",
"port": "Port",
"portal": "URL",
"portalAntiFrame": "Protection anti frame",
@ -620,6 +621,9 @@
"SSLVar": "Champ extrait du certificat",
"SSLVarIf": "Champ conditionnel extrait du certificat",
"startTime": "Date de création",
"sToken": "Tickets de service",
"sTokenScopes": "Portée des tickets de service",
"sTokenService": "Activation du server de tickets",
"successfullySaved": "Sauvegarde effectuée",
"storePassword": "Stocke le mot de passe de l'utilisateur en session",
"successLoginNumber": "Nombre de connexions mémorisées",

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -86,6 +86,7 @@ lib/Lemonldap/NG/Portal/Plugins/Notifications.pm
lib/Lemonldap/NG/Portal/Plugins/PublicPages.pm
lib/Lemonldap/NG/Portal/Plugins/Register.pm
lib/Lemonldap/NG/Portal/Plugins/RESTServer.pm
lib/Lemonldap/NG/Portal/Plugins/ServiceTokenServer.pm
lib/Lemonldap/NG/Portal/Plugins/SingleSession.pm
lib/Lemonldap/NG/Portal/Plugins/SOAPServer.pm
lib/Lemonldap/NG/Portal/Plugins/Status.pm

View File

@ -22,6 +22,7 @@ our @pList = (
u2fSelfRegistration => '::Register::U2F',
notification => '::Plugins::Notifications',
portalCheckLogins => '::Plugins::History',
sTokenService => '::Plugins::ServiceTokenServer',
);
##@method list enabledPlugins

View File

@ -0,0 +1,92 @@
# Token server plugin for underlying requests
#
# This plugin handle /tokenfor path to give to applications tokens to query
# other web applications on behalf of the connected user (second apps are
# protected by specific handler).
#
# 0) Administrator set "sTokenScopes" parameter in the manager. Each entry is
# a couple of key/value where:
# - key is the name of the list
# - value is a comma separated list of virtualHosts authorizated for this
# key
# Token header are also added for App-1 (application that wants to query
# others on behalf of the connected user)
# 1) App 1 received a token in headers (header is generated using
# "token($uid,'ref')" where ref is a key of "sTokenScopes" configuration
# parameter).
# 2) It send it to this plugin (request to /tokenfor)
# 3) run() method verify that token is available and return a service token that
# can be used to request a fixed list of servers. This list is the value of
# "tokenScope"->{$ref}
# 4) App-1 queries App-2, App-3,... with this token set in "X-Llng-Token" header
# 5) App-2 handler verifies that token is valid for this vhost and accept or
# not the query
package Lemonldap::NG::Portal::Plugins::ServiceTokenServer;
use strict;
use Mouse;
our $VERSION = '2.0.0';
extends 'Lemonldap::NG::Portal::Main::Plugin';
# INITIALIZATION
has tokenScopes => (
is => 'rw',
default => sub {
my $ts = $_[0]->conf->{sTokenScopes} || {};
my %h = map {
my $v = $ts->{$_};
$v =~ s/[, ]+/:/g;
( $_ => $v );
} keys %$ts;
return \%h;
}
);
sub init {
my ($self) = @_;
$self->addUnauthRoute( tokenfor => 'run', ['POST'] );
return 1;
}
sub run {
my ( $self, $req ) = @_;
# 1. Recover request token
my $reqToken;
if ( $req->content_type =~ /json/ ) {
my $j;
eval { $j = from_json( $req->content ) };
if ($@) {
return $self->p->sendError( $req, 'Bad request', 403 );
}
$reqToken = $j->{token};
}
else {
$reqToken = $req->param('token');
}
unless ($reqToken) {
return $self->p->sendError( $req, 'Missing token', 403 );
}
# 2. Uncipher request token
my $s = $self->conf->{cipher}->decrypt($reqToken)
or return $self->p->sendError( $req, 'Bad token', 403 );
# 3. Verify time
my ( $t, $uid, $ref ) = split /:/, $s;
unless ( $t <= time and $t > time - 15 ) {
return $self->p->sendError( $req, 'Token expired', 403 );
}
unless ( $self->tokenScopes->{$ref} ) {
return $self->p->sendError( $req, 'Bad reference', 403 );
}
my $respToken = $self->conf->{cipher}
->encrypt( join ':', time, $uid, $self->tokenScopes->{$ref} );
return $self->p->sendJSONresponse( $req, { sToken => $respToken } );
}
1;