416 lines
14 KiB
Plaintext
416 lines
14 KiB
Plaintext
=========================================
|
|
Writing a SAML2 service Provider in PHP
|
|
=========================================
|
|
|
|
:author Nicolas Clapiès
|
|
:contact: nclapies@entrouvert.com
|
|
:date: $Date$
|
|
:revision: $Revision$
|
|
:copyright: Copyright © 2004-2007 Entr'ouvert
|
|
|
|
.. contents:: Table of Contents
|
|
.. section-numbering::
|
|
|
|
|
|
Lasso PHP Binding Basics
|
|
========================
|
|
|
|
Lasso functions are available from the Lasso PHP extension.
|
|
|
|
There are two ways to load this extension.
|
|
|
|
This first one is to add the line::
|
|
|
|
extension = lasso.so
|
|
|
|
in your ``php.ini`` configuration file, which can be found in something like
|
|
``/etc/php4/apache2/php.ini`` (if you're using apache2 and php4 on Debian,
|
|
otherwise you may need to adapt the path to your local configuration).
|
|
|
|
|
|
The other way is to load it dynamically, like::
|
|
|
|
if (!extension_loaded('lasso')) {
|
|
$prefix = (PHP_SHLIB_SUFFIX == 'dll') ? 'php_' : '';
|
|
dl($prefix . 'lasso.' . PHP_SHLIB_SUFFIX);
|
|
}
|
|
|
|
You can easily include this code every time you need lasso.
|
|
|
|
|
|
The first thing to do is to call ``lasso_init()``. Similarly, the last thing
|
|
should be to call ``lasso_shutdown()``.
|
|
|
|
Once ``lasso_init()`` is called. The smallest and useless Lasso project will
|
|
therefore be::
|
|
|
|
lasso_init();
|
|
print("Hello world.\n");
|
|
lasso_shutdown();
|
|
|
|
If your PHP code is used in HTML script environment, it could be difficult to
|
|
call ``lasso_shutdown()``, this is not mandatory.
|
|
|
|
|
|
SAML2 and Lasso profiles
|
|
========================
|
|
|
|
Lasso provides the necessary functions to implement SAML2 profiles,
|
|
as defined in the `SAML2 Profiles Specification`_. They
|
|
are:
|
|
|
|
- Single Sign-On
|
|
- Single Logout
|
|
- Name Identifier Management
|
|
|
|
Each profile maps to a Lasso object : Single Sign-on profile is implemented by
|
|
``LassoLogin`` object, Single Logout profile is implemented by ``LassoLogout``
|
|
object.
|
|
|
|
Those are initialised with data known about identity and service providers,
|
|
available in a ``LassoServer`` object.
|
|
|
|
The ``LassoServer`` object may be created as follows::
|
|
|
|
lasso_init();
|
|
$server = new LassoServer("sp-saml2-metadata.xml", "sp-private-key.pem",
|
|
NULL, "sp-crt.pem");
|
|
$server->addProvider(LASSO_PROVIDER_ROLE_IDP, "idp-saml2-metadata.xml",
|
|
"idp-public-key.pem", "ca-crt.pem");
|
|
lasso_shutdown();
|
|
|
|
- ``sp-saml2-metadata.xml`` is the Liberty metadata file for the service provider
|
|
- ``idp-saml2-metadata.xml`` is the Liberty metadata file for the identity provider
|
|
- ``sp-private-key.pem`` is the service provider private key; used to sign
|
|
documents
|
|
- ``sp-crt.pem`` is the service provider certificate; sent inside signed
|
|
documents
|
|
- ``idp-public-key.pem`` is the identity provider public key; used to verify
|
|
signature in documents sent by the identity provider
|
|
- ``ca-crt.pem`` is the certificate of the certification authority used by the
|
|
identity provider.
|
|
|
|
It is of course possible to have several calls to the ``addProvider`` method of
|
|
an instantiated ``LassoServer`` object if there are more than one identity provider.
|
|
|
|
.. note:: Figures in the previously referred Binding and Profiles specification
|
|
document are quite helpful in figuring out the message passing.
|
|
|
|
Serialisation
|
|
-------------
|
|
|
|
``LassoServer`` objects can be serialised into a XML formatted string::
|
|
|
|
$dump = $server->dump();
|
|
|
|
It is then really easy to get back properly constructed objects::
|
|
|
|
$server = LassoServer::newFromDump($dump);
|
|
|
|
.. warning:: The server dump only contains the file names, not the actual file
|
|
contents. Files should not be moved afterwards.
|
|
|
|
|
|
Liberty Metadata Files
|
|
======================
|
|
|
|
Providers are described by metadata containing an ``entityID`` and various
|
|
normative URLs for supported profiles ::
|
|
|
|
<?xml version="1.0"?>
|
|
<EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
|
|
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
|
|
xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
|
|
entityID="http://sp.example.com/liberty/metadata">
|
|
|
|
<SPSSODescriptor
|
|
AuthnRequestsSigned="true"
|
|
protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
|
|
|
|
<KeyDescriptor use="signing">
|
|
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
|
|
<ds:KeyValue></ds:KeyValue>
|
|
</ds:KeyInfo>
|
|
</KeyDescriptor>
|
|
|
|
<SingleLogoutService
|
|
Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP"
|
|
Location="https://sp.example.com/liberty/singleLogoutSoap" />
|
|
|
|
<ManageNameIDService
|
|
Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP"
|
|
Location="https://sp.example.com/liberty/nameIdManagmentSoap" />
|
|
|
|
<AssertionConsumerService index="0" isDefault="true"
|
|
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact"
|
|
Location="https://sp.example.com/liberty/assertionConsumerServiceArtifact" />
|
|
|
|
</SPSSODescriptor>
|
|
|
|
<Organization>
|
|
<OrganizationName xml:lang="en"></OrganizationName>
|
|
</Organization>
|
|
|
|
</EntityDescriptor>
|
|
|
|
``SingleLogoutService`` entry allows to define method and url supported where
|
|
service provider wants to receive single logout requests. In previous metadata
|
|
example, service provider receives SOAP single logout requests on the
|
|
url ``https://sp.example.com/liberty/singleLogoutSoap``.
|
|
|
|
``ManageNameIDService`` entry allows to define method and url supported where
|
|
service provider wants to receive name id management requests. In previous metadata
|
|
example, service provider receives SOAP name id management requests on the
|
|
url ``http://sp.example.com/liberty/nameIdManagmentSoap``.
|
|
|
|
``AssertionConsumerService`` entry allows to define method and url supported where
|
|
service provider wants to receive assertion consumer responses. In previous metadata
|
|
example, service provider consume assertions responses on the
|
|
url ``https://sp.example.com/liberty/assertionConsumerServicePost``.
|
|
|
|
|
|
Single Sign-On and Federation Profile
|
|
=====================================
|
|
|
|
.. warning:: The source code presented in this section has for sole purpose
|
|
to explain the different steps necessary to implement this
|
|
profile; they notably lack proper error checking. See `Proper
|
|
Error Checking`_ for details on error checking.
|
|
|
|
|
|
As a first step the user points its browser to the service provider to the
|
|
login URL; the service provider must then respond with an HTTP 302 Redirect
|
|
response, pointing the user browser to the identity provider single sign on
|
|
service.
|
|
|
|
.. note:: the login URL is not normative; any name will do.
|
|
|
|
|
|
``$server`` is a instantiated ``LassoServer`` as seen earlier and
|
|
``$idpEntityId`` is a string with the idp entity Id (the string must
|
|
match a entityID defined in the metadata file).
|
|
|
|
::
|
|
|
|
|
|
|
|
$entityIdList = $server->providerIds;
|
|
$idpEntityId = $entityIdList->getItem(0);
|
|
|
|
$lassoLogin = new LassoLogin($server);
|
|
$lassoLogin->initAuthnRequest($idpEntityId, LASSO_HTTP_METHOD_REDIRECT);
|
|
|
|
$lassoRequest = $lassoLogin->request;
|
|
$lassoRequest->ProtocolBinding = LASSO_SAML2_METADATA_BINDING_ARTIFACT;
|
|
|
|
$lassoNameIdPolicy = $lassoRequest->NameIDPolicy;
|
|
$lassoNameIdPolicy->Format = LASSO_SAML2_NAME_IDENTIFIER_FORMAT_PERSISTENT;
|
|
$lassoNameIdPolicy->AllowCreate = TRUE;
|
|
|
|
$lassoLogin->buildAuthnRequestMsg();
|
|
|
|
|
|
You can now redirect the user to the URL defined in ``$lassoLogin->msgUrl``; for
|
|
example::
|
|
|
|
header("Location: ".$lassoLogin->msgUrl);
|
|
|
|
|
|
The user then logs in on the identity provider which ultimately redirects back
|
|
to the service provider; to the assertion consumer URL. A SAML artifact is
|
|
passed in the query parameter.
|
|
|
|
.. note:: the assertion consumer URL is defined by Liberty; it must be declared
|
|
in the ``AssertionConsumerServiceURL`` element of the metadata file.
|
|
|
|
::
|
|
|
|
$lassoLogin = new LassoLogin($server);
|
|
$lassoLogin->initRequest($query_string, LASSO_HTTP_METHOD_ARTIFACT_GET);
|
|
$lassoLogin->buildRequestMsg();
|
|
|
|
The service provider must check this artifact using a SOAP request to the
|
|
identity provider. The URL is ``$lassoLogin->msgUrl`` while the
|
|
request is ``$lassoLogin->msgBody``. The request must succeed with
|
|
an HTTP 200 status code; let's consider its content is put in the ``$answer``,
|
|
the next statement would be::
|
|
|
|
$lassoLogin->processResponseMsg($answer);
|
|
|
|
The users are defined by a ``nameIdentifier`` (accessible through
|
|
``$lassoLogin->nameIdentifier``). Those typically map to users
|
|
and sessions in some database on the service provider. If existing; the
|
|
session should probably contains a ``session_dump`` element and the user a
|
|
``identity_dump`` element. See `Database Considerations`_ below for more
|
|
information.
|
|
|
|
It is now time to get them out of the database and apply them to the ``login``
|
|
object.
|
|
|
|
::
|
|
|
|
if ($session_dump != NULL) {
|
|
$lassoLogin->setSessionFromDump($session_dump);
|
|
}
|
|
if ($identity_dump != NULL) {
|
|
$lassoLogin->setIdentityFromDump($identity_dump);
|
|
}
|
|
$lassoLogin->acceptSso();
|
|
|
|
After ``lassoLogin->acceptSso()`` the session and the identity are updated (or
|
|
created) and should then be saved. If the identity has not recognised by the
|
|
service provider an account will probably have to be created on the service
|
|
provider; this is a good opportunity to ask the user for more information.
|
|
|
|
You can get respective dumps like this::
|
|
|
|
if ($lassoLogin->isIdentityDirty) {
|
|
$lassoIdentity = $lassoLogin->identity;
|
|
$lassoIdentityDump = $lassoIdentity->dump();
|
|
}
|
|
|
|
if ($lassoLogin->isSessionDirty) {
|
|
$lassoSession = $lassoLogin->session;
|
|
$lassoSessionDump = $lassoSession->dump();
|
|
}
|
|
|
|
/* code to store $identity_dump and $session_dump */
|
|
|
|
|
|
A success web page can be displayed.
|
|
|
|
|
|
Single Logout Profile
|
|
=====================
|
|
|
|
There are different single logout profiles; some initiated on the identity
|
|
provider, others initiated on the service provider, using either HTTP redirects
|
|
or SOAP requests.
|
|
|
|
This part is about a logout using SOAP and initiated on the service provider.
|
|
|
|
::
|
|
|
|
$lassoLogout = new LassoLogout($server);
|
|
|
|
|
|
Identity and session dumps should be restored to prepare the logout request.
|
|
|
|
::
|
|
|
|
if ($session_dump != NULL) {
|
|
$lassoLogout->setSessionFromDump($session_dump);
|
|
}
|
|
if ($identity_dump != NULL) {
|
|
$lassoLogout->setIdentityFromDump($identity_dump);
|
|
}
|
|
|
|
$lassoLogout->initRequest($idpProviderId, LASSO_HTTP_METHOD_SOAP);
|
|
$lassoLogout->buildRequestMsg();
|
|
|
|
|
|
The service provider must then make a SOAP request to the identity provider;
|
|
``$msgUrl`` and ``$msgBody``. You should then pass the answer to Lasso::
|
|
|
|
$lassoLogout->processResponseMsg($answer));
|
|
|
|
And save back session and user dump; the process is similar as the one at the
|
|
end of the single sign on profile.
|
|
|
|
|
|
Proper Error Checking
|
|
=====================
|
|
|
|
Most Lasso functions raise PHP error (fatal) or warning (non-fatal).
|
|
|
|
It is strongly advised to code an user error handler::
|
|
|
|
function userErrorHandler($errno, $errmsg, $filename, $linenum, $vars) {
|
|
print("No: ".$errno." - ".$errmsg." at ".$filename.", line: ".$linenum."\n");
|
|
}
|
|
|
|
and to set up the script to use it::
|
|
|
|
set_error_handler("userErrorHandler");
|
|
|
|
Most Lasso functions returns 0 on success and a negative number on failure. It
|
|
is strongly advised to check this return code on each call. If the error raise a
|
|
PHP warning, the code resume after the call to the error handler function.
|
|
|
|
::
|
|
|
|
$lrv = $lassoLogin->processResponseMsg($responseMsg);
|
|
if ($lrv > 0) {
|
|
print("Lasso Error: ".$lrv."\n");
|
|
/* handling error; most probably bailing out */
|
|
}
|
|
|
|
|
|
Database Considerations
|
|
=======================
|
|
|
|
Lasso has been designed to let the service provider keep on using existing
|
|
databases. Typically there is already a table describing users; just add an
|
|
identity dump column to the existing table:
|
|
|
|
======= ======================================== ==============
|
|
User Id existing data (name, address...) Identity dump
|
|
======= ======================================== ==============
|
|
1 ... <Identity> ...
|
|
2 ... <Identity> ...
|
|
======= ======================================== ==============
|
|
|
|
Mapping between existing users and name identifiers sent by the identity
|
|
provider can be done with a simple table.
|
|
|
|
=============== =======
|
|
Name Identifier User Id
|
|
=============== =======
|
|
AQWWRRS... 1
|
|
CGFASDE... 2
|
|
YYSSSDS... 1
|
|
=============== =======
|
|
|
|
.. note:: A separate table is needed because one user Id could map
|
|
to several name identifiers; in case there are several identity
|
|
providers.
|
|
|
|
Sessions are also commonly stored in databases; just add a session dump column
|
|
to the existing session table:
|
|
|
|
========== ================= =============
|
|
Session Id misc session data Session dump
|
|
========== ================= =============
|
|
6744066 ... <Session> ...
|
|
3338824 ... <Session> ...
|
|
========== ================= =============
|
|
|
|
Likewise sessions should be mapped to name identifiers.
|
|
|
|
=============== ==========
|
|
Name Identifier Session Id
|
|
=============== ==========
|
|
AQWWRRS... 3338824
|
|
=============== ==========
|
|
|
|
|
|
API Reference
|
|
=============
|
|
|
|
- LassoLogin_
|
|
- LassoLogout_
|
|
- LassoIdentity_
|
|
- LassoServer_
|
|
- LassoSession_
|
|
|
|
|
|
.. _SAML2 Profiles Specification:
|
|
http://docs.oasis-open.org/security/saml/v2.0/saml-profiles-2.0-os.pdf
|
|
|
|
.. _LassoLogin: /documentation/api-reference/lassologin.html
|
|
.. _LassoLogout: /documentation/api-reference/lassologout.html
|
|
.. _LassoIdentity: /documentation/api-reference/lassoidentity.html
|
|
.. _LassoServer: /documentation/api-reference/lassoserver.html
|
|
.. _LassoSession: /documentation/api-reference/lassosession.html
|
|
|