577 lines
21 KiB
PHP
577 lines
21 KiB
PHP
<?php
|
|
|
|
/** DomFramework
|
|
* @package domframework
|
|
* @author Dominique Fournier <dominique@fournier38.fr>
|
|
* @license BSD
|
|
*/
|
|
|
|
namespace Domframework;
|
|
|
|
/** 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 integer|self 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 "<pre>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 "<tt>Unset the JSON Web Token '$tokenName'</tt><br/>\n";
|
|
}
|
|
if (
|
|
! key_exists("CONTENT_TYPE", $_SERVER) ||
|
|
$_SERVER["CONTENT_TYPE"] !== "application/json"
|
|
) {
|
|
echo "<script>localStorage.removeItem('$tokenName');</script>\n";
|
|
}
|
|
$algorithm = "HS256";
|
|
$cipherKey = null;
|
|
$cacheDir = "data/jwtCache";
|
|
$authjwt = new Authjwt();
|
|
$authjwt->serverKey = $this->authServers["authjwt"]["serverKey"];
|
|
if (isset($this->authServers["authjwt"]["cipherKey"])) {
|
|
$authjwt->cipherKey = $this->authServers["authjwt"]["cipherKey"];
|
|
}
|
|
if (isset($this->authServers["authjwt"]["algorithm"])) {
|
|
$authjwt->algorithm = $this->authServers["authjwt"]["algorithm"];
|
|
}
|
|
if (isset($this->authServers["authjwt"]["cacheDir"])) {
|
|
$authjwt->cacheDir = $this->authServers["authjwt"]["cacheDir"];
|
|
}
|
|
$authjwt->logout();
|
|
}
|
|
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 "<script>localStorage.setItem('$tokenName','$token');" .
|
|
"</script>\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
|
|
$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"] = $auth;
|
|
if (is_array($auth)) {
|
|
$payloadArray = $auth;
|
|
}
|
|
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 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__ . "\\" . ucfirst($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);
|
|
}
|
|
}
|