*/ require_once ("domframework/auth.php"); require_once ("domframework/authparams.php"); require_once ("domframework/authsession.php"); require_once ("domframework/ratelimit.php"); require_once ("domframework/ratelimitfile.php"); /** All the authentication protocol */ class authentication { /** The email of the authenticated/nonauthenticated user */ //private $email = "anonymous"; /** The debug of the authentication methods */ public $debug = 0; /** The route object */ private $route; /** Number of authentication maximum by minute */ public $ratelimitAuth = 3; /** Directory to store the ratelimit files */ public $ratelimitDir = "/tmp/ratelimit/"; /** The rest authentication methods. Can be post, session, http, shibboleth, * jwt * Attention : session case = CSRF ! */ public $restMethods = array ("http", "jwt"); /** The html authentication methods. Can be : post, session, http, shibboleth, * jwt * The "post" is already used when using verifAuthLoginPage method (usually * only in authentication page) */ public $htmlMethods = array ("session"); /** The authentication methods. Can be ldap, sympa...*/ public $authMethods = array (); /** The authentication servers configuration * array ("authXXXX" => array ( * array ("ldapserver" => "ldaps://server.domain.fr", * "ldapport" => 636, * "ldaptimeout" => 5, * "ldapauth" => "uid=XXX,dc=domain,dc=fr", * "ldappwd" => "XXX", * "ldapbase" => "", * "ldapfilter" => "(mail=%s)", * "ldapfield" => "mail", * "ldapfiltersearch" => "(objectClass=inetOrgPerson)" * ), * ), * ); */ public $authServers = array (); /** The application Name displayed on authentication page */ public $appName = null; /** The class and method to use to log the errors */ public $loggingFunc; /** The constructor * @param object $route The route object */ public function __construct ($route) // {{{ { $this->route = $route; $this->loggingFunc = array ($this, "logging"); } // }}} /* public function email () { return $this->email; }*/ /** Setter/Getter for debug * @param integer|null $debug The debug value to get/set * @return the actual value or this */ public function debug ($debug = null) // {{{ { if ($debug === null) return $this->debug; $this->debug = intval ($debug); return $this; } // }}} /** Disconnect the user * @param string|null $url The url to be redirected after a valid * logout */ public function logout ($url = "") // {{{ { // TODO : Foreach authentication methods->logout (); if (session_id () === "") session_start (); if ($this->debug) echo "
LOGOUT\n";
    $authsession = new authsession ();
    $param = $authsession->getdetails ();
    if ($this->debug) echo "Logout for '".$param["email"]."'\n";
    call_user_func ($this->loggingFunc,
                    LOG_NOTICE,
                    "Logout for '".$param["email"]."'");
    $authsession->logout ();
    unset ($_SESSION["domframework"]["authentication"]);
    if (isset ($this->authServers["authjwt"]["serverKey"]))
    {
      $tokenName = "DFKJWT";
      if (isset ($this->authServers["authjwt"]["tokenName"]))
        $tokenName = $this->authServers["authjwt"]["tokenName"];
      // Unset the JSON Web Token as the authentication
      if ($this->route->debug)
        echo "Unset the JSON Web Token '$tokenName'
\n"; echo "\n"; } if ($this->debug) echo "Redirect to authentication page"; if ($this->debug) $this->route->debug = $this->debug; if ($url === "" || $url === null) { $_SESSION["domframework"]["authentication"]["message"] = dgettext ("domframework", "You have been logged out"); $this->route->redirect ("/authentication", ""); } else $this->route->redirect ($url); } // }}} /** Display the login page * @param string|null $url The url to be redirected after a valid * authentication */ public function pageHTML ($url = "") // {{{ { // If the user is already connected, redirect to the main page of the site if (session_id () === "") session_start (); $auth = new auth (); $authparams = new authparams (array ("session")); if (isset ($_SESSION["domframework"]["authentication"]["message"])) $message = $_SESSION["domframework"]["authentication"]["message"]; else $message = ""; unset ($_SESSION["domframework"]["authentication"]["message"]); $alreadyAuth = false; if ($authparams->email !== "anonymous") $alreadyAuth = $authparams->email; if ($this->appName !== null) $auth->appName = $this->appName; @header ('X-Frame-Options: SAMEORIGIN'); echo $auth->pageHTML ($this->route->baseURL(), $message, $url, $alreadyAuth); } // }}} /** Check the authentication page * @param string|null $url The url to be redirected after a valid * authentication */ public function verifAuthLoginPage ($url = "") // {{{ { if (session_id () === "") session_start (); if ($this->debug) echo "Call verifAuthLoginPage ($url) : Start\n"; // rate-limit the connections $ratelimiter = new ratelimitfile (); // 3 connections by minutes $ratelimiter->maxEntries = $this->ratelimitAuth; $ratelimiter->storageDir = $this->ratelimitDir; if (isset ($_SERVER["HTTP_X_FORWARDED_FOR"])) $ipClient = $_SERVER["HTTP_X_FORWARDED_FOR"]; else $ipClient = $_SERVER["REMOTE_ADDR"]; if ($ratelimiter->set ("loggin-$ipClient") === false) { if ($this->debug) echo "Call verifAuthLoginPage ($url) : Ratelimit\n"; call_user_func ($this->loggingFunc, LOG_WARNING, "Ratelimiting for $ipClient"); $_SESSION["domframework"]["authentication"]["message"] = dgettext ("domframework", "Too much connections"); if ($url === "") { $this->route->redirect ("/authentication", ""); } else { $this->route->redirect ("/authentication/$url", ""); } } $authparams = new authparams (array ("post")); $res = $this->verifAuth ($authparams->email, $authparams->password); if (! is_array ($res)) { if ($this->debug) echo "Call verifAuthLoginPage ($url) : ERROR\n"; // Authentication error // Redirect to login page after logout call_user_func ($this->loggingFunc, LOG_WARNING, "Logging error for '$authparams->email' (HTML) : $res"); $authsession = new authsession (); $authsession->logout (); $baseURL = $this->route->baseURL (); $_SESSION["domframework"]["authentication"]["message"] = $res; // Check the provided URL before using it if ($url === "") { $this->route->redirect ("/authentication", ""); } else { $this->route->redirect ("/authentication/$url", ""); } } // Login OK : save in SESSION and go to main page if ($this->debug) echo "Call verifAuthLoginPage ($url) : USER OK\n"; call_user_func ($this->loggingFunc, LOG_NOTICE, "Logging in for '$authparams->email'"); if (! array_key_exists ("lastname", $res)) $res["lastname"] = null; if (! array_key_exists ("firstname", $res)) $res["firstname"] = null; $session = new authsession (); $session->savedata ($authparams->email, $authparams->password, $res["lastname"], $res["firstname"]); if (isset ($this->authServers["authjwt"]["serverKey"])) { // Set the JSON Web Token as the authentication is valid $tokenName = "DFKJWT"; if (isset ($this->authServers["authjwt"]["tokenName"])) $tokenName = $this->authServers["authjwt"]["tokenName"]; $token = $this->createJwtToken ($authparams->email); echo "\n"; } if ($url === "") $this->route->redirect ("/", ""); else $this->route->redirect ("/$url", ""); } // }}} /** Check all the REST API * @param boolean|null $savePassword return the user password if the * authentication is valid * @return array The details provided by the authentication mecanism */ public function verifAuthREST ($savePassword = false) // {{{ { if ($this->debug) echo "=== entering verifAuthREST (restMethods=". print_r ($this->restMethods, true).")\n"; $authparams = new authparams ($this->restMethods); $res = array ("email"=>"anonymous", "password"=>"anonymous"); if ($authparams->email !== "anonymous" && $authparams->password !== "anonymous") { $res = $this->verifAuth ($authparams->email, $authparams->password); } if (! is_array ($res)) { call_user_func ($this->loggingFunc, LOG_WARNING, "Logging error for '$authparams->email' (REST) : $res"); // Authentication error throw new \Exception (dgettext ("domframework", "Authentication error"), 403); } if ($savePassword === true && $authparams->email !== "anonymous") $res["password"] = $authparams->password; return $res; } // }}} /** Return the JSON Web Token * @param string|array $auth The user data to store in JSON Web Token cache. * The $this->authServers["authjwt"]["algorithm"], * $this->authServers["authjwt"]["cipherKey"] and * $this->authServers["authjwt"]["serverKey"] can be set */ public function createJwtToken ($auth) // {{{ { if (isset ($this->authServers["authjwt"]["serverKey"])) { // Set the JSON Web Token as the authentication is valid require_once ("domframework/authjwt.php"); $algorithm = "HS256"; $cipherKey = null; $cacheDir = "data/jwtCache"; if (isset ($this->authServers["authjwt"]["algorithm"])) $algorithm = $this->authServers["authjwt"]["algorithm"]; if (isset ($this->authServers["authjwt"]["cipherKey"])) $cipherKey = $this->authServers["authjwt"]["cipherKey"]; if (isset ($this->authServers["authjwt"]["cacheDir"])) $cacheDir = $this->authServers["authjwt"]["cacheDir"]; $payloadArray = array(); $payloadArray["email"] = $payload; if (is_array ($payload)) $payloadArray = $payload; if (! key_exists ("email", $payloadArray) || $payloadArray["email"] === "anonymous") throw new \Exception ("JWT Must authenticate", 401); $authjwt = new authjwt (); $authjwt->serverKey = $this->authServers["authjwt"]["serverKey"]; $authjwt->cipherKey = $cipherKey; $authjwt->algorithm = $algorithm; $authjwt->cacheDir = $cacheDir; return $authjwt->createJwtToken ($payloadArray); } } // }}} /** Check all the others pages of the site * @return array The details provided by the authentication mecanism */ public function verifAuthHTML () // {{{ { // Do not force the session_start ! We don't want the cookie on all the // pages if ($this->debug) echo "=== entering verifAuthHTML (htmlMethods=". print_r ($this->htmlMethods, true).")\n"; $authparams = new authparams ($this->htmlMethods); // Don't ask to the provider if anonymous is known if ($authparams->email === "anonymous" || $authparams->email === null) { if ($this->debug) echo "Anonymous\n"; return array ("email"=>"anonymous", "lastname"=>"Anonymous", "firstname"=>"", "password"=>""); } if ($this->debug) echo "verifAuth ($authparams->email, $authparams->password)\n"; $res = $this->verifAuth ($authparams->email, $authparams->password); if (! is_array ($res)) { // Authentication error if ($this->debug) echo "Previous session not found"; $msg = dgettext ("domframework", "Previous session not found"); $_SESSION["domframework"]["authentication"]["message"] = $msg; call_user_func ($this->loggingFunc, LOG_WARNING, "Previous session not found for '$authparams->email'"); $url = $this->route->requestURL(); $this->route->redirect ("/authentication/$url"); } return $res; } // }}} /** Do the real authentication process on all the providers defined in the * properties of the class. * @param string $email The email to check * @param string $password The password to check * @return an array containing the user data if the authentication is * correct, * an exception if noting is found */ private function verifAuth ($email, $password) // {{{ { if ($this->debug) echo "Entering in verifAuth ($email, xxxxxxxx)\n"; if (! is_array ($this->authMethods) || count ($this->authMethods) === 0) throw new \Exception ("No authentication method defined", 500); if ($this->debug < 2 && isset ($_SESSION["domframework"]["authentication"]["lastcheck"]) && $_SESSION["domframework"]["authentication"]["lastcheck"] + 180 < time ()) { // Test the authentication each 3 minutes if there is a session, else // return the previous values if ($this->debug) echo "verifAuth : using auth cache (push in debug=2 to skip)\n"; return $_SESSION["domframework"]["authentication"]["authcache"]; } $authServers = $this->authServers; foreach ($this->authMethods as $method) { if ($this->debug) echo "Authentication method=$method\n"; if (! is_string ($method)) throw new \Exception ("The authentication method is not a string", 500); $classname = "auth$method"; require_once ("domframework/$classname.php"); if (! array_key_exists ($classname, $authServers)) throw new \Exception ("No authentication server '$classname' enabled", 500); // If only one server is defined, the parameters can directely be pushed // to the classname if (! is_array (reset ($authServers[$classname]))) { $authServers[$classname] = array ($authServers[$classname]); } if ($this->debug >= 2) echo "Authentication method=$method : authServers=". var_export ($authServers[$classname])."\n"; if (! is_array ($authServers[$classname]) || count ($authServers[$classname]) === 0) throw new \Exception ("No authentication server defined for method ". "'$method'", 500); foreach ($authServers[$classname] as $key=>$serversParam) { if ($this->debug) echo "Test auth server $method # $classname # $key\n"; if (! is_array ($serversParam)) throw new \Exception ("Auth Server $key configuration error : ". "not an array", 500); $class = __NAMESPACE__."\\$classname"; $authmethod = new $class (); foreach ($serversParam as $param=>$value) { if ($this->debug) { if (is_array ($value)) echo "Add param '$param' to auth$method : ". var_export ($value, true)."\n"; else echo "Add param '$param' with value '$value' to auth$method\n"; } $authmethod->$param = $value; } $authmethod->connect (); try { $authmethod->authentication ($email, $password); $_SESSION["domframework"]["authentication"]["authcache"] = $authmethod->getdetails (); $_SESSION["domframework"]["authentication"]["lastcheck"] = time (); return $authmethod->getdetails (); } catch (\Exception $e) { call_user_func ($this->loggingFunc, LOG_DEBUG, "Authentication error for '$email' : ". "$classname : ".$e->getMessage()); } } } return dgettext ("domframework", "Bad login/password"); } // }}} /** Add the authentication routes to the routing model for HTML * authentication. Not needed if using shibboleth, HTTP auth... */ public function routes () // {{{ { $authObj = $this; $route=$this->route; $this->route ->get ("authentication/logout({url})?", function ($url) use ($authObj) { if (session_id () === "") session_start (); $authObj->logout ($url); }) ->get ("authentication", function () use ($route) { $route->redirect ("/authentication/"); }) ->get ("authentication/({url})?", function ($url) use ($authObj) { if (session_id () === "") session_start (); $authObj->pageHTML ($url); exit; }) ->post ("authentication/({url})?", function ($url) use ($authObj) { if (session_id () === "") session_start (); $authObj->verifAuthLoginPage ($url); exit; }) ; $this->route->authenticationURL = "/authentication/"; } // }}} /** The default method to display the error messages. * Do not display the debug messages, and write the errors on screen * @param integer $priority The priority of the message * @param string $message The message to log */ private function logging ($priority, $message) // {{{ { if ($this->debug === 0 && $priority > 4) return; file_put_contents ("/tmp/auth.log", "$priority : $message\n", FILE_APPEND); } // }}} }