web/lib/admin/Authentication.php

Summary

Maintainability
A
2 hrs
Test Coverage
<?php
/*
 * *****************************************************************************
 * Contributions to this work were made on behalf of the GÉANT project, a 
 * project that has received funding from the European Union’s Framework 
 * Programme 7 under Grant Agreements No. 238875 (GN3) and No. 605243 (GN3plus),
 * Horizon 2020 research and innovation programme under Grant Agreements No. 
 * 691567 (GN4-1) and No. 731122 (GN4-2).
 * On behalf of the aforementioned projects, GEANT Association is the sole owner
 * of the copyright in all material which was developed by a member of the GÉANT
 * project. GÉANT Vereniging (Association) is registered with the Chamber of 
 * Commerce in Amsterdam with registration number 40535155 and operates in the 
 * UK as a branch of GÉANT Vereniging.
 * 
 * Registered office: Hoekenrode 3, 1102BR Amsterdam, The Netherlands. 
 * UK branch address: City House, 126-130 Hills Road, Cambridge CB2 1PQ, UK
 *
 * License: see the web/copyright.inc.php file in the file structure or
 *          <base_url>/copyright.php after deploying the software
 */

namespace web\lib\admin;

use Exception;

/**
 * This class handles admin user authentication.
 * 
 * @author Stefan Winter <stefan.winter@restena.lu>
 */
class Authentication extends \core\common\Entity {

    /**
     * initialise ourselves, and simpleSAMLphp
     */
    public function __construct() {
        parent::__construct();
        include_once \config\Master::AUTHENTICATION['ssp-path-to-autoloader'];
    }
    /**
     * finds out whether the user is already authenticated. Does not trigger an authentication if not.
     *
     * @return boolean auth state
     */
    public function isAuthenticated() {

        $authSimple = new \SimpleSAML\Auth\Simple(\config\Master::AUTHENTICATION['ssp-authsource']);
        $session = \SimpleSAML\Session::getSessionFromRequest();
        $status = $authSimple->isAuthenticated();
        $session->cleanup();
        return $status;
    }

    /**
     * authenticates a user.
     * 
     * @return void
     * @throws Exception
     */
    public function authenticate() {
        \core\common\Entity::intoThePotatoes();
        $loggerInstance = new \core\common\Logging();
        $authSimple = new \SimpleSAML\Auth\Simple(\config\Master::AUTHENTICATION['ssp-authsource']);
        $authSimple->requireAuth();
        $admininfo = $authSimple->getAttributes();
        $session = \SimpleSAML\Session::getSessionFromRequest();
        $session->cleanup();

        if (!isset($admininfo[\config\Master::AUTHENTICATION['ssp-attrib-identifier']][0])) {
            $failtext = "FATAL ERROR: we did not receive a unique user identifier from the authentication source!";
            echo $failtext;
            throw new Exception($failtext);
        }

        $user = $admininfo[\config\Master::AUTHENTICATION['ssp-attrib-identifier']][0];

        $_SESSION['user'] = $user;
        $_SESSION['name'] = $admininfo[\config\Master::AUTHENTICATION['ssp-attrib-name']][0] ?? _("Unnamed User");
        /*
         * This is a nice pathological test case for a user ID.
         *
         * */
        //$_SESSION['user'] = "<saml:NameID xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\" NameQualifier=\"https://idp.jisc.ac.uk/idp/shibboleth\" SPNameQualifier=\"https://cat-beta.govroam.uk/simplesaml/module.php/saml/sp/metadata.php/default-sp\" Format=\"urn:oasis:names:tc:SAML:2.0:nameid-format:persistent\">XXXXXXXXXXXXXXXX</saml:NameID>";


        $newNameReceived = FALSE;

        $userObject = new \core\User($user);

        $attribMapping = [
            "ssp-attrib-name" => "user:realname",
            "ssp-attrib-email" => "user:email"];

        foreach ($attribMapping as $SSPside => $CATside) {
            if (isset($admininfo[\config\Master::AUTHENTICATION[$SSPside]][0]) && (count($userObject->getAttributes($CATside)) == 0) && \config\Master::DB['USER']['readonly'] === FALSE) {
                $name = $admininfo[\config\Master::AUTHENTICATION[$SSPside]][0];
                $userObject->addAttribute($CATside, NULL, $name);
                $loggerInstance->writeAudit($_SESSION['user'], "NEW", "User - added $CATside from external auth source");
                if ($CATside == "user:realname") {
                    $newNameReceived = TRUE;
                }
            }
        }

        if (count($userObject->getAttributes('user:realname')) > 0 || $newNameReceived) { // we have a real name in the DB. We trust this more than a session one, so set it
            $nameArray = $userObject->getAttributes("user:realname");
            if (!empty($nameArray[0])) {
                $_SESSION['name'] = $nameArray[0]['value'];
            }
        }
        \core\common\Entity::outOfThePotatoes();
    }

    /**
     * deauthenticates the user.
     * Sends a SAML LogoutRequest to the IdP, which will kill the SSO session and return us to our own logout_check page.
     * 
     * @return void
     */
    public function deauthenticate() {

        $as = new \SimpleSAML\Auth\Simple(\config\Master::AUTHENTICATION['ssp-authsource']);
        $servername = htmlspecialchars(strip_tags(filter_input(INPUT_SERVER, 'SERVER_NAME')));
        $scriptself = htmlspecialchars(strip_tags(filter_input(INPUT_SERVER, 'PHP_SELF')));
        $url = "https://www.eduroam.org"; // fallback if something goes wrong during URL construction below
        $trailerPosition = strrpos($scriptself, "/inc/logout.php");
        if ($trailerPosition !== FALSE) {
            $base = substr($scriptself, 0, $trailerPosition);
            if ($base !== FALSE) {
                $url = "//$servername" . $base . "/logout_check.php";
            }
        }

        $as->logout([
            'ReturnTo' => $url,
            'ReturnStateParam' => 'LogoutState',
            'ReturnStateStage' => 'MyLogoutState',
        ]);
    }

}