SimpleSAMLphp modules

This document describes how the module system in SimpleSAMLphp works. It describes what types of modules there are, how they are configured, and how to write new modules.

Overview

There are currently three parts of SimpleSAMLphp which can be stored in modules - authentication sources, authentication processing filters and themes. There is also support for defining hooks - functions run at specific times. More than one thing can be stored in a single module. There is also support for storing supporting files, such as templates and dictionaries, in modules.

The different functionalities which can be created as modules will be described in more detail in the following sections; what follows is a short introduction to what you can do with them:

Module layout

Each SimpleSAMLphp module is stored in a directory under the modules -directory. The module directory contains the following directories and files:

locales
This directory contains dictionaries which belong to this module. To use a dictionary stored in a module, the extended tag names can be used: {<module name>:<dictionary name>:<tag name>} For example, {example:login:hello} will look up hello in modules/example/locales/<lang>/login.po .
It is also possible to specify <module name>:<dictionary name> as the default dictionary when instantiating the \SimpleSAML\XHTML\Template class.
hooks
This directory contains hook functions for this module. Each file in this directory represents a single function. See the hook-section in the documentation for more information.
src
This directory contains classes which belong to this module. All classes must be named in the following pattern: \SimpleSAML\Module\<module name>\<class name> When looking up the filename of a class, SimpleSAMLphp will search for <class name> in the src directory. Underscores in the class name will be translated into slashes.
Thus, if SimpleSAMLphp needs to load a class named \SimpleSAML\Module\example\Auth\Source\Example , it will load the file named modules/example/src/Auth/Source/Example.php .
templates
These are module-specific templates. To use one of these templates, specify <module name>:<template file>.twig as the template file in the constructor of \SimpleSAML\XHTML\Template . For example, example:login-form.twig is translated to the file modules/example/templates/default/login-form.twig . Note that default in the previous example is defined by the theme.use configuration option.
themes
This directory contains themes the module defines. A single module can define multiple themes, and these themes may override all templates in all modules. Each subdirectory of themes defines a theme. The theme directory contains a subdirectory for each module. The templates stored under simplesamlphp/templates can be overridden by a directory named default .
To use a theme provided by a module, the theme.use configuration option should be set to <module name>:<theme name> .
When using the theme example:blue , the template templates/default/login.twig will be overridden by modules/example/themes/blue/default/login.twig , while the template modules/core/templates/default/loginuserpass.twig will be overridden by modules/example/themes/blue/core/loginuserpass.twig .
public
All files stored in this directory will be available by accessing the URL https://.../simplesamlphp/module.php/<module name>/<file name> . For example, if a script named login.php is stored in modules/example/public/ , it can be accessed by the URL https://.../simplesamlphp/module.php/example/login.php .
To retrieve this URL, the SimpleSAML\Module::getModuleURL($resource) -function can be used. This function takes in a resource on the form <module>/<file> . This function will then return a URL to the given file in the public -directory of module .

Authentication sources

An authentication source is used to authenticate a user and receive a set of attributes belonging to this user. In a single-signon setup, the authentication source will only be called once, and the attributes belonging to the user will be cached until the user logs out.

Authentication sources are defined in config/authsources.php . This file contains an array of name => configuration pairs. The name is used to refer to the authentication source in metadata. When configuring an IdP to authenticate against an authentication source, \the auth option should be set to this name. The configuration for an authentication source is an array. The first element in the array identifies the class which implements the authentication source. The remaining elements in the array are configuration entries for the authentication source.

A typical configuration entry for an authentication source looks like this:

  'example-static' => [
      /* This maps to modules/exampleauth/src/Auth/Source/Static.php */
      'exampleauth:StaticSource',

      /* The following is configuration which is passed on to
       * the exampleauth:StaticSource authentication source. */
      'uid' => 'testuser',
      'eduPersonAffiliation' => ['member', 'employee'],
      'cn' => ['Test User'],
  ],

To use this authentication source in a SAML 2.0 IdP, set the auth -option of the IdP to 'example-static' :

  'https://example.org/saml-idp' => [
      'host' => '__DEFAULT__',
      'privatekey' => 'example.org.pem',
      'certificate' => 'example.org.crt',
      'auth' => 'example-static',
  ],

Creating authentication sources

This is described in a separate document:

Authentication processing filters

Authentication processing filters is explained in a separate document:

Themes

This feature allows you to collect all your custom templates in one place. The directory structure is like this: modules/<thememodule>/themes/<theme>/<module>/<template> thememodule is the module where you store your theme, while theme is the name of the theme. A theme is activated by setting the theme.use configuration option to <thememodule>:<theme> . module is the module the template belongs to, and template is the template in that module.

For example, modules/example/themes/test/core/loginuserpass.php replaces modules/core/templates/default/loginuserpass.php . modules/example/themes/test/default/frontpage.php replaces templates/default/frontpage.php . This theme can be activated by setting theme.use to example:test .

Event listeners (new Hooks)

In SimpleSAMLphp 2.5 the hook interface was updated to use PSR-14 events PSR-14 events as well as the legacy custom "hook" interface. When hooks are called in 2.5 both the new PSR-14 event listeners are called followed by the older custom hook. You are likely better to create new event listeners in new configurations and might like to consider moving your hooks to event listeners over time.

If you are using an older version of SimpleSAMLphp please see the below section for information on how to use the older interface.

This section discusses how to make new event listeners with an eye toward moving an existing hook to become an event listener.

The PSR events that can be used are defined in your module's src/Event subdirectory. For example, for the admin module you can see modules/admin/src/Event/ConfigPageEvent.php which defines a class ConfigPageEvent in the SimpleSAML\Module\admin\Event namespace.

That event takes some state in the constructor and likely has one or more access methods to later retrieve that state.

The event might be listened to by the same or another module by defining a Listener class. In the running concrete example the class in modules/cron/src/Event/Listener/ConfigPageListener.php defines a ConfigPageListener class in the SimpleSAML\Module\cron\Event\Listener namespace. That listener class has an __invoke method that will be passed the ConfigPageEvent object and has no return value.

The ConfigPageEvent is a reasonable starting point for testing your own listener. If you create a new listener for ConfigPageEvent you can use Logger::error to check that you get called and use the admin/config page through the admin module to trigger this event fairly easily. You might like to base your new listener on the ConfigPageListener for the cron module.

To update a hook from a 2.4 release or below you will need to move your hook class from the hooks directory to the src/Event/Listener subdirectory. Considering the config page example again, the hooks/hook_configpage.php file should move to src/Event/Listener . That hook defined a single top level function with a specific function name cron_hook_configpage . In the event listener the new src/Event/Listener/ConfigPageListener.php file will need to be in the SimpleSAML\Module\cron\Event\Listener namespace and define a class ConfigPageListener . The single top level function is moved into a member function __invoke taking a single parameter ConfigPageEvent .

The old hook function took a reference Template &$template directly as the single argument. The new PSR class takes a ConfigPageEvent object. The ConfigPageEvent object provides access to the template using the getTemplate method on the event.

The old hook function in hooks might look like the following.

  // old hook interface
  function cron_hook_configpage(Template &$template): void
  {
    $template->data['links'][] = [
        'href' => Module::getModuleURL('cron/info'),
        'text' => Translate::noop('Cron module information page'),
    ];
    ...
  }

The new Listener version in src/Event/Listener is shown below.

  class ConfigPageListener
  {
      public function __invoke(ConfigPageEvent $event): void
      {
          $template = $event->getTemplate();

          $template->data['links'][] = [
              'href' => \SimpleSAML\Module::getModuleURL('cron/info'),
              'text' => \SimpleSAML\Locale\Translate::noop('Cron module information page'),
          ];
          ...
      }
  }

That ConfigPageEvent class is defined in modules/admin/src/Event/ConfigPageEvent.php .

  class ConfigPageEvent
  {
      public function __construct(
          private readonly XHTML\Template $template,
      )
      {}

      public function getTemplate(): XHTML\Template
      {
          return $this->template;
      }
  }

In summary to move a hook to the new PSR-14 events you have to move the hook file to your module's src/Event/Listener directory and have it take a new Event object that passes the function argument to it. The event will be a simple class defined in your src/Event directory.

When you wish to call event listeners in your code you can use code like the following. The event dispatcher is passed an event which contains all of the state you wish to make available to the event listeners. You can then get the state which may have been updated by one or more listeners using a getter method and do something with the result.

  $eventDispatcher = ModuleEventDispatcherFactory::getInstance();
  $event = $eventDispatcher->dispatch(new ConfigPageEvent($t));
  $t = $event->getTemplate();

Hook interface (SimpleSAMLphp 2.4 and below)

Releases 2.4 and below use a custom hook interface to allow code to run when specific things of interest are happening. If you are using SimpleSAMLphp 2.5 or above please see the above section on Event listeners instead.

The hook interface allows you to call a hook function in all enabled modules which define that hook. Hook functions are stored in a directory called 'hooks' in each module directory. Each hook is stored in a file named hook_<hook name>.php , and each file defines a function named <module name>_hook_<hook name> .

Each hook function accepts a single argument. This argument will be passed by reference, which allows each hook to update that argument.

For an example of hook usage, see the cron module, which adds a link to its information page in the Configuration section of the admin module, through the file modules/cron/hooks/hook_configpage.php .