Write a custom plugin¶
Presentation¶
Portal plugins let you customize LemonLDAP::NG’s behavior.
Common use cases for plugins are:
Looking up session information in an additional backend
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¶
Authentication entry points¶
You can now write a custom portal plugin that will hook in the authentication process:
beforeAuth
: method called before authentication processbetweenAuthAndData
: method called after authentication and before setting “sessionInfo” provisionningafterData
: method called after “sessionInfo” provisionningendAuth
: method called when session is validated (after cookie build)authCancel
: method called when user click on “cancel” during auth processforAuthUser
: method called for already authenticated usersbeforeLogout
: method called before logout
Generic entry points¶
If you need to call a method just after any standard method in
authentication process, then use afterSub
, for example:
use constant afterSub => {
getUser => 'mysub',
};
sub mysub {
my ( $self ,$req ) = @_;
# Do something
return PE_OK;
}
If you need to call a method instead any standard method in
authentication process, then use aroundSub
, for example:
use constant aroundSub => {
getUser => 'mysub',
};
sub mysub {
my ( $self, $sub, $req ) = @_;
# Do something before
my $ret = $sub->($req);
# Do something after
return $ret;
}
Hooks¶
New in version 2.0.10.
Your plugin can also register itself to be called at some points of interest within the main LemonLDAP::NG code.
Check the list of available plugin hooks for details
Routes¶
The plugin can also define new routes and call actions on them.
See also Lemonldap::NG::Portal::Main::Plugin
man page.
Configuration¶
The current LemonLDAP::NG configuration can be accessed in the $self->conf
hash. This variable is only meant to be read. Don’t try changing its content, or Bad Things may happen.
You can set your own parameters in General Parameters
» Plugins
» Custom plugins
» Additional parameters
and reach them through customPluginsParams
sub my_function {
my ($self, $req) = @_;
# Get a standard LLNG option
my $llng_logo = $self->conf->{portalMainLogo};
# Get your custom LLNG option
my $myvar = $self->conf->{customPluginsParams}->{myvar};
}
Logs¶
You can use the $self->logger
and $self->userLogger
objects to log information during your plugin execution. Use logger
for technical logs and userLogger
for accounting and tracability events.
sub my_function {
my ($self, $req) = @_;
$self->logger->debug("Debug message");
if (my_custom_test($req->user)) {
$self->userLogger->debug("User ". $req->user .
" is not allowed because XXX");
return PE_ERROR;
}
return PE_OK;
}
Remembering data¶
In order to remember data between different steps, you can use the $req->data
hash.
Data will not be remembered in between requests, only in between methods that process the same HTTP request.
History management¶
Plugins may declare additional session fields to be stored in the Login history.
sub init {
my ($self) = @_;
$self->addSessionDataToRemember({
# This field will be hidden from the user
_language => '__hidden__',
# This field will be displayed on the portal. The column name
# is treated like a message and can be internationalized
authenticationLevel => "Human friendly column name",
});
return 1;
}
Column names can be translated by overriding the corresponding message
Password policies¶
You can implement custom password policies in your plugins.
In order to do this, implement the passwordBeforeChange hook.
If you want to display user feedback, you need to do two additional things
In you plugin’s
init
method, declare a HTML control for your policy$self->p->addPasswordPolicyDisplay( 'ppolicy-custom1', { # Activation condition condition => $self->conf->{customPluginsParams}->{myPpolicyEnabled}, # Label to show the user label => "myppolicy", # What value to display in front of the label value => 1, # Optional key-value HTML data-* elements data => { }, # Custom HTML code to inject before the element customHtml => qq'<script type="text/javascript" src="$self->{p}->{staticPrefix}/common/js/myppolicy.min.js"></script>', # Custom HTML code to inject after the element customHtmlAfter => qq'<div id="advanced_feedback"></div>', # Optional display order, higher goes last order => 100, } );
Implement a JS check and trigger it on the checkpassword event
Creating a second factor module¶
Adding a new type of second factor requires one or two modules:
A module for using the second factor during authentication
This module must inherit from Lemonldap::NG::Portal::Main::SecondFactor
.
package My::SFA;
use strict;
use Mouse;
extends 'Lemonldap::NG::Portal::Main::SecondFactor';
(optional) A module for registering the second factor
This module must inherit from Lemonldap::NG::Portal::2F::Register::Base
.
package My::SFARegister;
use strict;
use Mouse;
extends 'Lemonldap::NG::Portal::2F::Register::Base';
Example¶
Plugin Perl module¶
This example creates a Lemonldap::NG::Portal::Plugins::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/Plugins/MyPlugin.pm
Tip
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/Plugins/MyPlugin.pm
.
Or you can use your own namespace such as ACME::Corp::MyPlugin
.
# The package name must match the file path
# This file must be in Lemonldap/NG/Portal/Plugins/MyPlugin.pm
package Lemonldap::NG::Portal::Plugins::MyPlugin;
use Mouse;
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' };
# 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) = @_;
my $userid = $req->user;
$self->p->logger->debug("Call welcome for $userid");
...
return $self->p->sendHtml($req, 'template', params => { WELCOME => 1 });
}
# Your file must return 1, or Perl will complain.
1;
Enabling your plugin¶
Declare the plugin in lemonldap-ng.ini:
vi /etc/lemonldap-ng/lemonldap-ng.ini
[portal]
customPlugins = Lemonldap::NG::Portal::Plugins::MyPlugin
;customPlugins = Lemonldap::NG::Portal::Plugins::MyPlugin1, Lemonldap::NG::Portal::Plugins::MyPlugin2, ...
Since 2.0.7, it can also be configured in Manager, in General Parameters > Plugins > Custom Plugins.