Merge branch 'v2.0' into CheckUser_history

This commit is contained in:
Christophe Maudoux 2021-10-29 16:28:25 +02:00
commit 01ef4c98d8
9 changed files with 108 additions and 35 deletions

View File

@ -12,6 +12,9 @@ Common use cases for plugins are:
* Implementing additional controls or steps during login
* Adjusting the behavior of SAML, OIDC or CAS protocols to work around application bugs
Creating a plugin can be as simple as writing a short Perl module file and
declaring it in your configuration. See below for an example.
Plugin API
----------
@ -140,9 +143,10 @@ Example
Plugin Perl module
~~~~~~~~~~~~~~~~~~
Create for example the MyPlugin module:
This example creates a ``Lemonldap::NG::Portal::MyPlugin`` plugin that
showcases some features of the plugin system.
::
First, create a file to contain the plugin code ::
vi /usr/share/perl5/Lemonldap/NG/Portal/MyPlugin.pm
@ -150,7 +154,8 @@ Create for example the MyPlugin module:
If you do not want to mix files from the distribution with
your own work, put your own code in
``/usr/local/lib/site_perl/Lemonldap/NG/Portal/MyPlugin.pm``\
``/usr/local/lib/site_perl/Lemonldap/NG/Portal/MyPlugin.pm``.
Or you can use your own namespace such as ``ACME::Corp::MyPlugin``.
.. code-block:: perl
@ -162,28 +167,46 @@ Create for example the MyPlugin module:
use Lemonldap::NG::Portal::Main::Constants;
extends 'Lemonldap::NG::Portal::Main::Plugin';
# Declare when LemonLDAP::NG must call your functions
use constant beforeAuth => 'verifyIP';
use constant hook => { passwordAfterChange => 'logPasswordChange' };
sub init {
# Every plugin must have an init function that returns 1
# You can define your custom initialization here
my ($self) = @_;
$self->addUnauthRoute( mypath => 'hello', [ 'GET', 'PUT' ] );
$self->addAuthRoute( mypath => 'welcome', [ 'GET', 'PUT' ] );
return 1;
}
# This function will be called at the "beforeAuth" login step
sub verifyIP {
my ($self, $req) = @_;
return PE_ERROR if($req->address !~ /^10/);
return PE_OK;
}
# This function will be called when changing passwords
sub logPasswordChange {
my ( $self, $req, $user, $password, $old ) = @_;
$self->userLogger->info("Password changed for $user");
return PE_OK;
}
# You can define your custom initialization in the
# init method.
# Before LemonLDAP::NG 2.0.14, this function was mandatory
sub init {
my ($self) = @_;
# This is how you declare HTTP routes
$self->addUnauthRoute( mypath => 'hello', [ 'GET', 'PUT' ] );
$self->addAuthRoute( mypath => 'welcome', [ 'GET', 'PUT' ] );
# The function can return 0 to indicate failure
return 1;
}
# This method will be called to handle unauthenticated requests to /mypath
sub hello {
my ($self, $req) = @_;
...
return $self->p->sendJSONresponse($req, { hello => 1 });
}
# This method will be called to handle authenticated requests to /mypath
sub welcome {
my ($self, $req) = @_;
@ -193,6 +216,7 @@ Create for example the MyPlugin module:
...
return $self->p->sendHtml($req, 'template', params => { WELCOME => 1 });
}
# Your file must return 1, or Perl will complain.
1;

View File

@ -1104,7 +1104,7 @@ sub _handleClientCredentialsGrant {
$self->userLogger->warn( 'Client '
. $client_id
. " was not granted any requested scopes ($req_scope) for $rp" );
return $self->sendOIDCError( $req, 'invalid_scope', 403 );
return $self->sendOIDCError( $req, 'invalid_scope', 400 );
}
my $infos = {
@ -1239,7 +1239,7 @@ sub _handlePasswordGrant {
$self->userLogger->warn( 'User '
. $req->sessionInfo->{ $self->conf->{whatToTrace} }
. " was not granted any requested scopes ($req_scope) for $rp" );
return $self->sendOIDCError( $req, 'invalid_scope', 403 );
return $self->sendOIDCError( $req, 'invalid_scope', 400 );
}
my $user_id = $self->getUserIDForRP( $req, $rp, $req->sessionInfo );

View File

@ -577,7 +577,11 @@ sub loadModule {
$self->error("Unable to build $module object: $@");
return 0;
}
unless ( $obj and $obj->init ) {
unless ($obj) {
$self->error("$module instanciation failed");
return 0;
}
if ( $obj->can("init") and ( !$obj->init ) ) {
$self->error("$module init failed");
return 0;
}

View File

@ -51,6 +51,21 @@ my $op = LLNG::Manager::Test->new( {
oidcRPMetaDataOptionsIDTokenForceClaims => 1,
oidcRPMetaDataOptionsRule => '$_scope =~ /\bread\b/',
},
scopelessrp => {
oidcRPMetaDataOptionsDisplayName => "RP",
oidcRPMetaDataOptionsIDTokenExpiration => 3600,
oidcRPMetaDataOptionsClientID => "scopelessrp",
oidcRPMetaDataOptionsAllowOffline => 1,
oidcRPMetaDataOptionsAllowClientCredentialsGrant => 1,
oidcRPMetaDataOptionsIDTokenSignAlg => "HS512",
oidcRPMetaDataOptionsClientSecret => "rpsecret",
oidcRPMetaDataOptionsUserIDAttr => "",
oidcRPMetaDataOptionsAccessTokenExpiration => 3600,
oidcRPMetaDataOptionsBypassConsent => 1,
oidcRPMetaDataOptionsRefreshToken => 1,
oidcRPMetaDataOptionsIDTokenForceClaims => 1,
oidcRPMetaDataOptionsRule => '',
},
pubrp => {
oidcRPMetaDataOptionsAccessTokenExpiration => 3600,
oidcRPMetaDataOptionsAllowClientCredentialsGrant => 1,
@ -103,6 +118,13 @@ my $badquery2 = buildForm( {
}
);
my $badquery3 = buildForm( {
client_id => 'scopelessrp',
client_secret => 'rpsecret',
grant_type => 'client_credentials',
}
);
my $goodquery = buildForm( {
client_id => 'rpid',
client_secret => 'rpsecret',
@ -129,6 +151,15 @@ $res = $op->_post(
);
expectBadRequest($res);
## Test empty scope
$res = $op->_post(
"/oauth2/token",
IO::String->new($badquery3),
accept => 'application/json',
length => length($badquery3),
);
expectReject($res, 400, "invalid_scope");
## Test a confidential RP
$res = $op->_post(
"/oauth2/token",

View File

@ -37,6 +37,21 @@ my $op = LLNG::Manager::Test->new( {
}
},
oidcRPMetaDataOptions => {
scopelessrp => {
oidcRPMetaDataOptionsDisplayName => "RP",
oidcRPMetaDataOptionsIDTokenExpiration => 3600,
oidcRPMetaDataOptionsClientID => "scopelessrp",
oidcRPMetaDataOptionsAllowOffline => 1,
oidcRPMetaDataOptionsAllowPasswordGrant => 1,
oidcRPMetaDataOptionsIDTokenSignAlg => "HS512",
oidcRPMetaDataOptionsClientSecret => "rpsecret",
oidcRPMetaDataOptionsUserIDAttr => "",
oidcRPMetaDataOptionsAccessTokenExpiration => 120,
oidcRPMetaDataOptionsBypassConsent => 1,
oidcRPMetaDataOptionsRefreshToken => 1,
oidcRPMetaDataOptionsIDTokenForceClaims => 1,
oidcRPMetaDataOptionsRule => '$uid eq "french"',
},
rp => {
oidcRPMetaDataOptionsDisplayName => "RP",
oidcRPMetaDataOptionsIDTokenExpiration => 3600,
@ -92,6 +107,24 @@ $res = $op->_post(
expectReject( $res, 400, "invalid_grant" );
# Empty scope should fail
my $query = buildForm( {
client_id => 'scopelessrp',
client_secret => 'rpsecret',
grant_type => 'password',
username => 'french',
password => 'french',
}
);
$res = $op->_post(
"/oauth2/token",
IO::String->new($query),
accept => 'application/json',
length => length($query),
);
expectReject( $res, 400, "invalid_scope" );
$query = buildForm( {
client_id => 'rpid',
client_secret => 'rpsecret',

View File

@ -9,11 +9,6 @@ use constant hook => {
'casGenerateValidateResponse' => 'genResponse',
};
sub init {
my ($self) = @_;
return 1;
}
sub filterService {
my ( $self, $req, $cas_request ) = @_;
if ( $cas_request->{service} eq "http://auth.sp.com/" ) {

View File

@ -17,11 +17,6 @@ use constant hook => {
oidcGotClientCredentialsGrant => 'oidcGotClientCredentialsGrant',
};
sub init {
my ($self) = @_;
return 1;
}
sub addClaimToIDToken {
my ( $self, $req, $payload, $rp ) = @_;
$payload->{"id_token_hook"} = 1;
@ -43,7 +38,7 @@ sub addScopeToRequest {
sub addHardcodedScope {
my ( $self, $req, $scopeList, $rp ) = @_;
push @{$scopeList}, "myscope";
push @{$scopeList}, "myscope" if $rp ne "scopelessrp";
return PE_OK;
}

View File

@ -10,10 +10,6 @@ use constant hook => {
passwordAfterChange => 'afterChange',
};
sub init {
1;
}
sub beforeChange {
my ( $self, $req, $user, $password, $old ) = @_;
if ( $password eq "12345" ) {

View File

@ -5,11 +5,6 @@ extends 'Lemonldap::NG::Portal::Main::Plugin';
use constant hook => { samlGotAuthnRequest => 'gotRequest', };
sub init {
my ($self) = @_;
return 1;
}
sub gotRequest {
my ( $self, $res, $login ) = @_;