Files
DomFramework/authentication.php
Dominique Fournier 17168aaaef Update gettext : add spaces
DomCi : remove line too longs on all the files


git-svn-id: https://svn.fournier38.fr/svn/ProgSVN/trunk@5280 bf3deb0d-5f1a-0410-827f-c0cc1f45334c
2019-05-23 14:19:30 +00:00

469 lines
16 KiB
PHP

<?php
/** DomFramework
* @package domframework
* @author Dominique Fournier <dominique@fournier38.fr>
*/
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 http, session, post.
* Attention : session case = CSRF !
*/
public $restMethods = array ("http");
/** The html authentication methods. Can be http, session, post
* 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 name of the JSON Web Token set in localStorage of the client browser
* if the authentication is valid. Will be used later by JS on client with
* Bearer authentication for REST API.
*/
public $jwtName = null;
/** Add the server key used to create the JSON Web Token
*/
public $jwtServerKey = null;
/** The authentication servers configuration
* array ("authXXXX"=>array (
* array ("ldapserver"=>"ldaps://annuaire.grenoble.cnrs.fr",
* "ldapport"=>636,
* "ldaptimeout"=>5,
* "ldapauth"=>"uid=annuaire,ou=people,dc=grenoble,dc=cnrs,dc=fr",
* "ldappwd"=>";authANNUAIRE2013",
* "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;
}*/
/** Disconnect the user
* @param string|null $url The url to be redirected after a valid
* logout
*/
public function logout ($url = "")
// {{{
{
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 ($this->jwtName !== null)
{
// Unset the JSON Web Token as the authentication
if ($this->route->debug)
echo "<tt>Unset the JSON Web Token '$this->jwtName'</tt><br/>\n";
echo "<script>localStorage.removeItem('$this->jwtName');</script>\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 ();
$pre = 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 ($pre->email !== "anonymous")
$alreadyAuth = $pre->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 ($this->jwtName !== null)
{
// Set the JSON Web Token as the authentication is valid
if ($this->jwtServerKey === null)
throw new \Exception ("No authentication::jwtServerKey provided", 500);
require_once ("domframework/jwt.php");
$payloadArray = array();
$payloadArray['nbf'] = date ("Y-m-d H:i:s");
$payloadArray['exp'] = date ("Y-m-d H:i:s", time () + 86400);
$token = jwt::encode ($payloadArray, $this->jwtServerKey);
if ($this->route->debug)
echo "<tt>Set the JSON Web Token '$this->jwtName' with value '$token'".
"</tt><br/>\n";
echo "<script>localStorage.setItem('$this->jwtName','$token');".
"</script>\n";
}
if ($url === "")
$this->route->redirect ("/", "");
else
$this->route->redirect ("/$url", "");
}
// }}}
/** Check all the REST API
*/
public function verifAuthREST ()
// {{{
{
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);
}
return $res;
}
// }}}
/** Check all the others pages of the site
*/
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"];
}
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, $this->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 ($this->authServers[$classname])))
{
$this->authServers[$classname] = array ($this->authServers[$classname]);
}
if ($this->debug >= 2)
echo "Authentication method=$method : authServers=".
var_export ($this->authServers[$classname])."\n";
if (! is_array ($this->authServers[$classname]) ||
count ($this->authServers[$classname]) === 0)
throw new \Exception ("No authentication server defined for method ".
"'$method'", 500);
foreach ($this->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);
}
// }}}
}