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-options: Enabling custom plugins ----------------------- LemonLDAP::NG comes with many plugins already, but you might want to write your owns and load them as well In *General parameters* » *Plugins* » *Custom plugins* - **Modules list**: The list of custom modules to load. Modules will be loaded after the built-in ones - **Additional parameters**: Configuration for your custom plugins - **Disabled plugins**: (*since 2.21.0*): List of built-in modules you do not want LemonLDAP::NG to load. This is useful if you want to overload a built-in module with one of your own. 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 process - ``betweenAuthAndData``: method called after authentication and before setting "sessionInfo" provisionning - ``afterData``: method called after "sessionInfo" provisionning - ``endAuth``: method called when session is validated (after cookie build) - ``authCancel``: method called when user click on "cancel" during auth process - ``forAuthUser``: method called for already authenticated users - ``beforeLogout``: 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: .. code-block:: perl 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: .. code-block:: perl use constant aroundSub => { getUser => 'mysub', }; sub mysub { my ( $self, $sub, $req ) = @_; # Do something before my $ret = $sub->($req); # Do something after return $ret; } Hooks ~~~~~ .. versionadded:: 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 :doc:`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`` .. tip:: If you have got several plugins with several parameters, parameters can be passed by using JSON notation ``myPluginParams = {"title":"myTitle", "msg":"myMsg"}`` and converted into a PERL object with ``from_json`` function. .. code-block:: perl use Mouse; use JSON 'from_json'; has myPluginParams => ( is => 'ro', builder => sub { my $self = $_[0]; my $defaultParams = { title => 'title', msg => 'msg', path => 'path', }; my $params = eval { from_json( $self->conf->{customPluginsParams}->{myPluginParams} ); }; if ($@) { my $package = __PACKAGE__; $self->userLogger->warn( "Bad JSON file: $package plugin will use default parameters ($@)" ); return $defaultParams; } return { %$defaultParams, %$params }; } ); 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}; # Get your custom LLNG option from your parameters hash my $title = $self->myPluginParams->{title}; } 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. .. code-block:: perl 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 :doc:`loginhistory`. .. code:: perl 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 :ref:`overriding the corresponding message ` .. _pluginppolicy: Password policies ~~~~~~~~~~~~~~~~~ You can implement custom password policies in your plugins. In order to do this, implement the :ref:`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'', # Custom HTML code to inject after the element customHtmlAfter => qq'
', # Optional display order, higher goes last order => 100, } ); * Implement a JS check and trigger it on the :ref:`checkpassword event ` Creating a custom CAPTCHA ~~~~~~~~~~~~~~~~~~~~~~~~~ See the ``Lemonldap::NG::Portal::Captcha`` manual page for details on how to implement a Captcha module. Creating a custom menu tab ~~~~~~~~~~~~~~~~~~~~~~~~~~ See the ``Lemonldap::NG::Portal::MenuTab`` manual page for details on how to implement a new tab for the application menu 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``. .. code:: perl 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``. .. code:: perl package My::SFARegister; use strict; use Mouse; extends 'Lemonldap::NG::Portal::2F::Register::Base'; Custom entry points ~~~~~~~~~~~~~~~~~~~ Entrypoints allow plugins to register custom actions when they are loaded. LemonLDAP::NG comes with many pre-defined entrypoints already. But you may want to add your own, especially if you are creating a plugin that can itself have plugins. You can call the ``addEntryPoint`` method in your plugin's ``init`` to create your custom entrypoints. .. note:: Only plugins loaded *after* registering the entry point will trigger it. Be careful with the ordering of your ``customPlugins`` variable. Here are a few examples: .. code:: perl sub init { my ($self) = @_; # ... # From now on, when loading a new plugin that inherits from # My::Base::Class, run the provided code reference, with an # instance of the plugin being loaded as first argument $self->addEntryPoint( isa => "My::Base::Class", callback => sub { my ($plugin) = @_; $self->do_domething_with($plugin); } ); # From now on, when loading a new plugin that uses My::Role # Call a particular method of a particular service, passing the new # plugins instance and optional extra arguments. # This will effectively run: # $portal->getService('myservice') # ->mymethod($plugin, "role_entry_point") $self->addEntryPoint( does => "My::Role", service => "myservice", method => "mymethod", args => ["role_entry_point"], ); # ... return 1; } See also ``Lemonldap::NG::Portal::Main::Plugin`` man page for full details. 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``. .. code-block:: perl # 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 .. code-block:: perl [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.