Compare commits

...

3 commits

29 changed files with 1530 additions and 128 deletions

View file

@ -27,7 +27,8 @@
"ext-pdo": "^7.3",
"ext-json": "*",
"ext-yaml": "^2.0",
"league/mime-type-detection": "^1.11"
"league/mime-type-detection": "^1.11",
"apereo/phpcas": "^1.6"
},
"require-dev": {
"phpstan/phpstan": "^1.9"

View file

@ -134,6 +134,140 @@ db:
#datetime_format: '%Y-%m-%d %H:%M:%S' # Exemple : 2018-10-12 18:06:59
#
# Authentication
#
auth:
# Enabled authentication
enabled: false
# Methods to authenticate users
methods:
- form
- http
#- cas
# User backends
backends:
- ldap
#
# Login form
#
login_form:
# Display link for other authentication methods
# Note: method as key and label as value
display_other_methods:
http: "HTTP"
cas: "SSO"
#
# HTTP Authentication Configuration
#
http:
# HTTP Auth methods :
# * AUTHORIZATION : use HTTP_AUTHORIZATION environnement. This mode could be use with PHP FPM.
# Specific configuration is need in Apache to use this mode :
# RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
# * REMOTE_USER : use REMOTE_USER environnement variable to retreive authenticated user's login
# This method could be only used with $http_auth_trust_without_password_challenge
# enabled.
# * PHP_AUTH : use PHP_AUTH_USER and PHP_AUTH_PW environnement variables (only available
# using mod_php with Apache. (default)
method: 'PHP_AUTH'
# Trust HTTP server authentication
# If enabled, the application will no check user credentials and only retreive user's login
# from HTTP server.
#trust_without_password_challenge: true
# Realm (use when force HTTP login, optional)
#realm: "Authentication required"
#
# CAS Configuration
#
cas:
# CAS host (just the domain name)
host: 'idp.example.com'
# CAS context (the root path, default: '/idp/cas')
# Example: for 'http://idp.example.com/idp/cas', put '/idp/cas'
context: '/idp/cas'
# CAS HTTP port (default: 443)
#port: 443
# CAS procotol version
# Posssible values: "1.0", "2.0" (default), "3.0" or "S1" (SAML1)
#version: '2.0'
# CAS server SSL certificate validation (set to false to disable)
ca_cert_certificate_path: "/etc/ssl/certs/ca-certificates.crt"
# CAS Debug log file
#debug_log_file: "${root_directory_path}/data/logs/cas.log"
# CAS Logout
#logout: true # Enable CAS logout on app logout
#logout_url: "https://my.example.fr/logout/" # Specify custom CAS logout URL
# CAS Fake authenticated user
#fake_authenticated_user: 'myusername'
#
# LDAP user backend
#
ldap:
# LDAP host (required, multiple hosts could be specified with comma separator)
host: 'ldap://localhost'
# LDAP port (optional)
#port: 389
# Enable STARTTLS (optional, default: false)
#starttls: false
# LDAP directory base DN (required)
basedn: 'o=example'
# LDAP bind DN (optional)
#bind_dn: 'uid=eesyphp,ou=sysaccounts,${auth.ldap.basedn}'
# LDAP bind password (optional)
#bind_password: 'secret'
# User search filter by username. The keyword "[username]" will be replace before search by
# the looked username (default: "uid=[username]")
#user_filter_by_uid: 'uid=[username]'
# User base DN
user_basedn: 'ou=people,${auth.ldap.basedn}'
# Bind with username instead of user DN (optional, default: false)
#bind_with_username: true
# LDAP user attributes to retreive with their properties:
# [LDAP attr name]:
# name: [map name] # optional, default: LDAP attr name
# type: [type of value] # optional, default: 'string', possible values: string, bool, int, float
# multivalued: true # optional, default: false
# default: null # optional, default: null
user_attributes:
uid:
name: 'login'
multivalued: false
default: null
cn:
name: 'name'
multivalued: false
default: null
mail:
type: 'string'
# PEAR Net_LDAP2 library path (optional, default: Net/LDAP2.php)
#netldap2_path: 'Net/LDAP2.php'
#
# Email configuration
#

View file

@ -1,7 +1,7 @@
msgid ""
msgstr ""
"Project-Id-Version: \n"
"POT-Creation-Date: 2023-02-14 01:17+0100\n"
"POT-Creation-Date: 2023-02-25 04:54+0100\n"
"PO-Revision-Date: \n"
"Last-Translator: Benjamin Renard <brenard@zionetrix.net>\n"
"Language-Team: \n"
@ -70,6 +70,22 @@ msgstr "Une exception est survenue en exécutant la commande %s"
msgid "Unable to connect to the database."
msgstr "Impossible de se connecter à la base de données."
#: Auth/Form.php:32
msgid "Invalid username or password."
msgstr "Nom d'utilisateur ou mot de passe invalide."
#: Auth/Http.php:34 Url.php:176
msgid "Authentication required"
msgstr "Authentification requise"
#: Auth/Http.php:115 Auth/Http.php:118 Url.php:180
msgid "Access denied"
msgstr "Accès interdit"
#: Auth/Http.php:119 Auth/Http.php:123
msgid "You must login to access this page."
msgstr "Vous devez vous connecter pour accéder à cette page."
#: Email.php:141
#, php-format
msgid "</hr><p><small>Mail initialy intended for %s.</small></p>"
@ -102,35 +118,27 @@ msgstr ""
"\n"
"%s: %s"
#: Tpl.php:355
#: Tpl.php:394
msgid "No template specified."
msgstr "Aucun template spécifié."
#: Tpl.php:386
#: Tpl.php:425
msgid "An error occurred while displaying this page."
msgstr "Une erreur est survenue en affichant cette page."
#: Url.php:141
#: Url.php:172
msgid "Bad request"
msgstr "Mauvaise requête"
#: Url.php:142
#: Url.php:173
msgid "Invalid request."
msgstr "Requête invalide."
#: Url.php:145
msgid "Authentication required"
msgstr "Authentification requise"
#: Url.php:146
#: Url.php:177
msgid "You have to be authenticated to access to this page."
msgstr "Vous devez être authentifié pour accéder à cette page."
#: Url.php:149
msgid "Access denied"
msgstr "Accès interdit"
#: Url.php:150
#: Url.php:181
msgid ""
"You do not have access to this application. If you think this is an error, "
"please contact support."
@ -138,25 +146,25 @@ msgstr ""
"Vous n'avez pas accès à cette application. Si vous pensez qu'il s'agit d'une "
"erreur, merci de prendre contact avec le support."
#: Url.php:153
#: Url.php:184
msgid "Whoops ! Page not found"
msgstr "Oups ! Page introuvable"
#: Url.php:154
#: Url.php:185
msgid "The requested page can not be found."
msgstr "La page demandée est introuvable."
#: Url.php:162
#: Url.php:193
msgid "Error"
msgstr "Erreur"
#: Url.php:163
#: Url.php:194
msgid "An unknown error occurred. If problem persist, please contact support."
msgstr ""
"Une erreur inconnue est survenue. Si le problème persiste, merci de prendre "
"contact avec le support."
#: Url.php:226
#: Url.php:257
msgid ""
"Unable to determine the requested page. If the problem persists, please "
"contact support."
@ -164,7 +172,7 @@ msgstr ""
"Impossible de déterminer la page demandée. Si le problème persiste, merci de "
"prendre contact avec le support."
#: Url.php:376
#: Url.php:411
msgid ""
"Unable to determine the requested page (loop detected). If the problem "
"persists, please contact support."
@ -172,22 +180,19 @@ msgstr ""
"Impossible de déterminer la page demandée (boucle détectée). Si le problème "
"persiste, merci de prendre contact avec le support."
#: Url.php:407
#: Url.php:441
msgid "This request cannot be processed."
msgstr "Cette requête ne peut être traitée."
#: Url.php:420
msgid ""
"Authentication required but force_authentication function is not defined."
msgstr ""
"Authentification requise mais la fonction force_authentication n'est pas "
"définie."
#: Url.php:451
msgid "Authentication required but fail to authenticate you."
msgstr "Authentification requise mais impossible pour vous authentifier."
#: Url.php:429
#: Url.php:460
msgid "This request could not be processed correctly."
msgstr "Cette requête n'a put être traitée correctement."
#: I18n.php:122 App.php:120
#: I18n.php:122 App.php:124
msgid "Hello world !"
msgstr "Bonjour tout le monde !"
@ -379,10 +384,18 @@ msgstr "Impossible d'écrire le fichier du catalogue JS %s (%s)."
msgid "%s JS catalog writed (%s)."
msgstr "Catalogue JS %s créé (%s)."
#: App.php:122
#: App.php:126
msgid "Hello world!"
msgstr "Salut tout le monde !"
#: App.php:137
msgid "Disconnected"
msgstr "Déconnecté"
#: App.php:139
msgid "You are now disconnected."
msgstr "Vous êtes maintenant déconnecté."
#: static/js/myconfirm.js:4 static/js/myconfirm.js:171
#: static/js/myconfirm.js:200
msgid "Confirmation"
@ -460,3 +473,29 @@ msgstr ""
"framework EesyPHP. Configurez votre propre dossier de templates et créer le "
"fichier <em>homepage.tpl</em> pour l'écraser. Vous pouvez également écraser "
"le gestionnaire de l'URL racine de l'application web."
#: templates/empty.tpl:60
msgid "Logout"
msgstr "Déconnexion"
#: templates/login.tpl:2
msgid "Connection"
msgstr "Connexion"
#: templates/login.tpl:7
msgid "Username"
msgstr "Nom d'utilisateur"
#: templates/login.tpl:11
msgid "Password"
msgstr "Mot de passe"
#: templates/login.tpl:14
msgid "Submit"
msgstr "Envoyer"
#~ msgid ""
#~ "Authentication required but force_authentication function is not defined."
#~ msgstr ""
#~ "Authentification requise mais la fonction force_authentication n'est pas "
#~ "définie."

View file

@ -1,7 +1,7 @@
msgid ""
msgstr ""
"POT-Creation-Date: 2023-02-14 01:17+0100\n"
"PO-Revision-Date: 2023-02-14 01:17+0100\n"
"POT-Creation-Date: 2023-02-25 04:54+0100\n"
"PO-Revision-Date: 2023-02-25 04:54+0100\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"

View file

@ -1,7 +1,7 @@
msgid ""
msgstr ""
"POT-Creation-Date: 2023-02-14 01:17+0100\n"
"PO-Revision-Date: 2023-02-14 01:17+0100\n"
"POT-Creation-Date: 2023-02-25 04:54+0100\n"
"PO-Revision-Date: 2023-02-25 04:54+0100\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
@ -61,6 +61,22 @@ msgstr ""
msgid "Unable to connect to the database."
msgstr ""
#: Auth/Form.php:32
msgid "Invalid username or password."
msgstr ""
#: Auth/Http.php:34 Url.php:176
msgid "Authentication required"
msgstr ""
#: Auth/Http.php:115 Auth/Http.php:118 Url.php:180
msgid "Access denied"
msgstr ""
#: Auth/Http.php:119 Auth/Http.php:123
msgid "You must login to access this page."
msgstr ""
#: Email.php:141
#, php-format
msgid "</hr><p><small>Mail initialy intended for %s.</small></p>"
@ -87,82 +103,73 @@ msgid ""
"%s: %s"
msgstr ""
#: Tpl.php:355
#: Tpl.php:394
msgid "No template specified."
msgstr ""
#: Tpl.php:386
#: Tpl.php:425
msgid "An error occurred while displaying this page."
msgstr ""
#: Url.php:141
#: Url.php:172
msgid "Bad request"
msgstr ""
#: Url.php:142
#: Url.php:173
msgid "Invalid request."
msgstr ""
#: Url.php:145
msgid "Authentication required"
msgstr ""
#: Url.php:146
#: Url.php:177
msgid "You have to be authenticated to access to this page."
msgstr ""
#: Url.php:149
msgid "Access denied"
msgstr ""
#: Url.php:150
#: Url.php:181
msgid ""
"You do not have access to this application. If you think this is an error, "
"please contact support."
msgstr ""
#: Url.php:153
#: Url.php:184
msgid "Whoops ! Page not found"
msgstr ""
#: Url.php:154
#: Url.php:185
msgid "The requested page can not be found."
msgstr ""
#: Url.php:162
#: Url.php:193
msgid "Error"
msgstr ""
#: Url.php:163
#: Url.php:194
msgid "An unknown error occurred. If problem persist, please contact support."
msgstr ""
#: Url.php:226
#: Url.php:257
msgid ""
"Unable to determine the requested page. If the problem persists, please "
"contact support."
msgstr ""
#: Url.php:376
#: Url.php:411
msgid ""
"Unable to determine the requested page (loop detected). If the problem "
"persists, please contact support."
msgstr ""
#: Url.php:407
#: Url.php:441
msgid "This request cannot be processed."
msgstr ""
#: Url.php:420
msgid ""
"Authentication required but force_authentication function is not defined."
#: Url.php:451
msgid "Authentication required but fail to authenticate you."
msgstr ""
#: Url.php:429
#: Url.php:460
msgid "This request could not be processed correctly."
msgstr ""
#: I18n.php:122 App.php:120
#: I18n.php:122 App.php:124
msgid "Hello world !"
msgstr ""
@ -321,10 +328,18 @@ msgstr ""
msgid "%s JS catalog writed (%s)."
msgstr ""
#: App.php:122
#: App.php:126
msgid "Hello world!"
msgstr ""
#: App.php:137
msgid "Disconnected"
msgstr ""
#: App.php:139
msgid "You are now disconnected."
msgstr ""
#: static/js/myconfirm.js:4 static/js/myconfirm.js:171
#: static/js/myconfirm.js:200
msgid "Confirmation"
@ -395,3 +410,23 @@ msgid ""
"it. You could also overwrite the URL handler for the root of the web "
"application."
msgstr ""
#: templates/empty.tpl:60
msgid "Logout"
msgstr ""
#: templates/login.tpl:2
msgid "Connection"
msgstr ""
#: templates/login.tpl:7
msgid "Username"
msgstr ""
#: templates/login.tpl:11
msgid "Password"
msgstr ""
#: templates/login.tpl:14
msgid "Submit"
msgstr ""

View file

@ -53,6 +53,22 @@ msgstr ""
msgid "Unable to connect to the database."
msgstr ""
#: Auth/Form.php:32
msgid "Invalid username or password."
msgstr ""
#: Auth/Http.php:34 Url.php:176
msgid "Authentication required"
msgstr ""
#: Auth/Http.php:115 Auth/Http.php:118 Url.php:180
msgid "Access denied"
msgstr ""
#: Auth/Http.php:119 Auth/Http.php:123
msgid "You must login to access this page."
msgstr ""
#: Email.php:141
#, php-format
msgid "</hr><p><small>Mail initialy intended for %s.</small></p>"
@ -79,82 +95,73 @@ msgid ""
"%s: %s"
msgstr ""
#: Tpl.php:355
#: Tpl.php:394
msgid "No template specified."
msgstr ""
#: Tpl.php:386
#: Tpl.php:425
msgid "An error occurred while displaying this page."
msgstr ""
#: Url.php:141
#: Url.php:172
msgid "Bad request"
msgstr ""
#: Url.php:142
#: Url.php:173
msgid "Invalid request."
msgstr ""
#: Url.php:145
msgid "Authentication required"
msgstr ""
#: Url.php:146
#: Url.php:177
msgid "You have to be authenticated to access to this page."
msgstr ""
#: Url.php:149
msgid "Access denied"
msgstr ""
#: Url.php:150
#: Url.php:181
msgid ""
"You do not have access to this application. If you think this is an error, "
"please contact support."
msgstr ""
#: Url.php:153
#: Url.php:184
msgid "Whoops ! Page not found"
msgstr ""
#: Url.php:154
#: Url.php:185
msgid "The requested page can not be found."
msgstr ""
#: Url.php:162
#: Url.php:193
msgid "Error"
msgstr ""
#: Url.php:163
#: Url.php:194
msgid "An unknown error occurred. If problem persist, please contact support."
msgstr ""
#: Url.php:226
#: Url.php:257
msgid ""
"Unable to determine the requested page. If the problem persists, please "
"contact support."
msgstr ""
#: Url.php:376
#: Url.php:411
msgid ""
"Unable to determine the requested page (loop detected). If the problem "
"persists, please contact support."
msgstr ""
#: Url.php:407
#: Url.php:441
msgid "This request cannot be processed."
msgstr ""
#: Url.php:420
msgid ""
"Authentication required but force_authentication function is not defined."
#: Url.php:451
msgid "Authentication required but fail to authenticate you."
msgstr ""
#: Url.php:429
#: Url.php:460
msgid "This request could not be processed correctly."
msgstr ""
#: I18n.php:122 App.php:120
#: I18n.php:122 App.php:124
msgid "Hello world !"
msgstr ""
@ -313,6 +320,14 @@ msgstr ""
msgid "%s JS catalog writed (%s)."
msgstr ""
#: App.php:122
#: App.php:126
msgid "Hello world!"
msgstr ""
#: App.php:137
msgid "Disconnected"
msgstr ""
#: App.php:139
msgid "You are now disconnected."
msgstr ""

View file

@ -19,6 +19,14 @@ msgstr ""
msgid "Back"
msgstr ""
#: templates/logout.tpl:5
msgid "Disconnected"
msgstr ""
#: templates/logout.tpl:6
msgid "You are now disconnected."
msgstr ""
#: templates/homepage.tpl:5
msgid "Hello, world!"
msgstr ""
@ -30,3 +38,31 @@ msgid ""
"it. You could also overwrite the URL handler for the root of the web "
"application."
msgstr ""
#: templates/empty.tpl:60
msgid "Logout"
msgstr ""
#: templates/login.tpl:2
msgid "Connection"
msgstr ""
#: templates/login.tpl:7
msgid "Username"
msgstr ""
#: templates/login.tpl:11
msgid "Password"
msgstr ""
#: templates/login.tpl:14
msgid "Submit"
msgstr ""
#: templates/must_login.tpl:5
msgid "Access denied"
msgstr ""
#: templates/must_login.tpl:6
msgid "You must login to access this page."
msgstr ""

View file

@ -8,8 +8,22 @@ parameters:
- example/includes/config.local.php
- example/data/tmp/templates_c
universalObjectCratesClasses:
- EesyPHP\HookEvent
- EesyPHP\UrlRequest
- EesyPHP\Auth\User
ignoreErrors:
-
message: "#Property EesyPHP\\\\Auth\\\\Ldap::\\$connection has unknown class Net_LDAP2 as its type\\.#"
path: src/Auth/Ldap.php
-
message: "#Call to method search\\(\\) on an unknown class Net_LDAP2\\.#"
path: src/Auth/Ldap.php
-
message: "#Call to static method connect\\(\\) on an unknown class Net_LDAP2\\.#"
path: src/Auth/Ldap.php
-
message: "#Call to static method escape\\(\\) on an unknown class Net_LDAP2_Filter\\.#"
path: src/Auth/Ldap.php
-
message: "#Instantiated class Mail_mime not found\\.#"
path: src/Email.php

View file

@ -64,6 +64,10 @@ class App {
Url::init();
Url :: add_url_handler('#^$#', array('EesyPHP\\App', 'handle_homepage'));
}
if (Auth :: enabled()) {
Auth :: init();
Url :: add_url_handler('#^logout$#', array('EesyPHP\\App', 'handle_logout'), null, false);
}
if (self :: get('mail.enabled', true, 'bool'))
Email :: init();
if (self :: get('i18n.enabled', true, 'bool'))
@ -122,4 +126,17 @@ class App {
echo "<h1>".I18n::_("Hello world!")."</h1>";
}
/**
* Default logout handler
* @param UrlRequest $request
* @return void
*/
public static function handle_logout($request) {
Auth::logout();
if (Tpl::initialized())
Tpl :: display("logout.tpl", I18n::_("Disconnected"));
else
echo "<h1>".I18n::_("You are now disconnected.")."</h1>";
}
}

306
src/Auth.php Normal file
View file

@ -0,0 +1,306 @@
<?php
namespace EesyPHP;
class Auth {
/**
* Initialized methods
* @var array<string,string>
*/
private static $methods = array();
/**
* Method name used to authenticate current user
* @var string|null
*/
private static $logged_method = null;
/**
* Initialized backends
* @var array<string,string>
*/
private static $backends = array();
/**
* Current authenticated user
* @var \EesyPHP\Auth\User|null
*/
private static $user = null;
/**
* Initialize
* @return void
*/
public static function init() {
if (!self :: enabled()) return;
self :: $methods = array();
foreach(App::get('auth.methods', array(), 'array') as $method) {
if (!$method || !is_string($method)) {
Log::warning(
'Auth Init: Invalid auth method retreive from configuration, ignore it: %s',
vardump($method));
continue;
}
$class = (
$method[0] == '\\'?
$method:
"\\EesyPHP\\Auth\\".ucfirst($method)
);
if (!class_exists($class)) {
Log::warning(
"Auth Init: Unknown auth method '%s' retreived from configuration, ignore it",
$method
);
continue;
}
$parents = class_parents($class);
if (!is_array($parents) || !in_array('EesyPHP\\Auth\\Method', $parents)) {
Log::warning(
'Auth Init: Auth method %s class (%s) do not derivate from \\EesyPHP\\Auth\\Method '.
'class, ignore it.',
$method, $class);
continue;
}
if (!call_user_func(array($class, 'init'))) {
Log::warning(
'Auth Init: fail to initialize auth method %s, ignore it',
$method, $class);
continue;
}
Log::trace('Auth method %s initialized (class %s)', $method, $class);
self :: $methods[strtolower($method)] = $class;
}
self :: $backends = array();
foreach(App::get('auth.backends', array(), 'array') as $backend) {
if (!$backend || !is_string($backend)) {
Log::warning(
'Auth Init: Invalid auth backend retreive from configuration, ignore it: %s',
vardump($backend));
continue;
}
$class = (
$backend[0] == '\\'?
$backend:
"\\EesyPHP\\Auth\\".ucfirst($backend)
);
if (!class_exists($class)) {
Log::warning(
"Auth Init: Unknown auth backend '%s' retreived from configuration, ignore it",
$backend
);
continue;
}
$parents = class_parents($class);
if (!is_array($parents) || !in_array('EesyPHP\\Auth\\Backend', $parents)) {
Log::warning(
'Auth Init: Auth backend %s class (%s) do not derivate from \\EesyPHP\\Auth\\Backend '.
'class, ignore it.',
$backend, $class);
continue;
}
if (!call_user_func(array($class, 'init'))) {
Log::warning(
'Auth Init: fail to initialize auth backend %s, ignore it',
$backend, $class);
continue;
}
Log::trace('Auth backend %s initialized (class %s)', $backend, $class);
self :: $backends[strtolower($backend)] = $class;
}
}
/**
* Check if authentication is enabled
* @return bool
*/
public static function enabled() {
if (!is_null(App::get('auth.enabled', null, 'bool')))
return App::get('auth.enabled', false, 'bool');
if (App::get('auth.methods', array(), 'array') && App::get('auth.backends', array(), 'array'))
return true;
Log :: trace('Authentication is disabled');
return false;
}
/**
* Check if a authentification method is enabled
* @param string $method
* @return bool
*/
public static function method_is_enabled($method) {
return array_key_exists(strtolower($method), self :: $methods);
}
/**
* Get user by username
* @param string $username
* @param boolean $first Return only the first matched user (if authorized, optional, default: true)
* @return \EesyPHP\Auth\User|array<\EesyPHP\Auth\User>|null|false The user object if found, null it not, false in case of error
*/
public static function get_user($username, $first=true) {
if (!self :: $backends) {
Log :: warning("No auth backend registered, can't retreive user");
return false;
}
$users = array();
foreach (self :: $backends as $backend) {
$user = call_user_func(array($backend, 'get_user'), $username);
if ($user) $users[] = $user;
}
if (!$users) return null;
if (count($users) > 1 && !App::get('auth.allow_multiple_match', false, 'bool')) {
Log :: error('Multiple user found for username "%s":\n%s', $username, vardump($users));
return false;
}
return $first?$users[0]:$users;
}
/**
* Search user by username and check its password
* @param string $username The username
* @param string $password The password to check
* @return \EesyPHP\Auth\User|null|false
*/
public static function authenticate($username, $password) {
$users = self :: get_user($username, false);
if (!$users) return $users === false?false:null;
$auth_users = array();
foreach ($users as $user) {
if ($user->check_password($password))
$auth_users[] = $user;
}
if (!$auth_users) return null;
if (
count($auth_users) > 1
&& !App::get(
'auth.allow_multiple_match_with_valid_password',
App::get('auth.allow_multiple_match', false, 'bool'),
'bool'
)
) {
Log :: error(
'Multiple user match for username "%s" and provided password:\n%s',
$username, vardump($auth_users));
return false;
}
return $auth_users[0];
}
/**
* Log user
* @param bool|string $force Force user authentication: could specified desired method or true
* to use the first one (optional, default: false)
* @param null|string $with_method Specify with which method(s) login user (optional, default: all methods)
* @return \EesyPHP\Auth\User|null|false
*/
public static function login($force=false, $with_method=null) {
// Check if already logged in
if (self :: $user)
return self :: $user;
// Check if logged in session
if (isset($_SESSION['user']) && isset($_SESSION['auth_method'])) {
$user = unserialize($_SESSION['user']);
if (is_a($user, '\\EesyPHP\\Auth\\User')) {
self :: $user = $user;
self :: $logged_method = (
array_key_exists($_SESSION['auth_method'], self :: $methods)?
$_SESSION['auth_method']:null
);
Log :: debug(
'User %s authenticated from session (method %s and backend %s)',
$user->username,
self :: $logged_method?self :: $logged_method:'unknown',
$user->backend);
return $user;
}
Log::warning('Invalid user data in session, drop it');
// Otherwise, drop user in session
unset($_SESSION['user']);
unset($_SESSION['auth_method']);
}
if (!self :: $methods) {
Log :: warning("No auth method registered, can't authenticate users");
return false;
}
// Otherwise, log without enforcing by using registered methods
foreach (self :: $methods as $method => $class) {
if ($with_method && !in_array($method, array_map('strtolower', ensure_is_array($with_method))))
continue;
$user = call_user_func(array($class, 'login'));
if ($user) {
self :: set_user($user, $method);
return $user;
}
}
// If still not logged and force mode enable, force login using specified method (or the first one)
if ($force) {
$method = (
is_string($force) && array_key_exists($force, self :: $methods)?
$force:key(self :: $methods)
);
Log::debug('Force authentication using method %s', $method);
$user = call_user_func(array(self :: $methods[$method], 'login'), true);
if ($user) {
self :: set_user($user, $method);
return $user;
}
return false;
}
return null;
}
/**
* Helper to set current authenticated user
* @param \EesyPHP\Auth\User $user The current authenticated user object
* @param string $method Method used to authenticate the user
* @return void
*/
private static function set_user($user, $method) {
Log :: debug(
'User %s authenticated using method %s and backend %s',
$user->username, $method, $user->backend);
self :: $user = $user;
self :: $logged_method = $method;
$_SESSION['user'] = serialize($user);
$_SESSION['auth_method'] = $method;
Hook :: trigger('logged_in', array('method' => $method, 'user' => $user));
}
/**
* Logout
* @return void
*/
public static function logout() {
$method = (
self :: $logged_method?
self :: $logged_method:
(isset($_SESSION['auth_method'])?$_SESSION['auth_method']:null)
);
self :: $user = null;
self :: $logged_method = null;
if (isset($_SESSION['user']))
unset($_SESSION['user']);
if (isset($_SESSION['auth_method']))
unset($_SESSION['auth_method']);
if ($method)
call_user_func(array(self :: $methods[$method], 'logout'));
}
/**
* Get current authenticated user
* @return \EesyPHP\Auth\User|null
*/
public static function user() {
return self :: $user;
}
}

33
src/Auth/Backend.php Normal file
View file

@ -0,0 +1,33 @@
<?php
namespace EesyPHP\Auth;
class Backend {
/**
* Initialize
* @return boolean
*/
public static function init() {
return true;
}
/**
* Retreive a user by its username
* @param string $username
* @return \EesyPHP\Auth\User|null|false The user object if found, null it not, false in case of error
*/
public static function get_user($username) {
return null;
}
/**
* Check a user password
* @param \EesyPHP\Auth\User $user The user object
* @param string $password The password to check
* @return boolean
*/
public static function check_password($user, $password) {
return false;
}
}

132
src/Auth/Cas.php Normal file
View file

@ -0,0 +1,132 @@
<?php
namespace EesyPHP\Auth;
use EesyPHP\App;
use EesyPHP\Auth;
use EesyPHP\Log;
use EesyPHP\Url;
use phpCAS;
class Cas extends Method {
/**
* Fake authenticated user login
* @var string|null
*/
private static $fake_authenticated_user = null;
/**
* Initialize
* @return boolean
*/
public static function init() {
self :: $fake_authenticated_user = App :: get(
'auth.cas.fake_authenticated_user', null, 'string');
if (self :: $fake_authenticated_user) return true;
if (App::get('auth.cas.debug_log_file'))
phpCAS::setDebug(App::get('auth.cas.debug_log_file'));
if (!App::get('auth.cas.host')) {
Log :: error('CAS host not configured. Check your configuration!');
return false;
}
$cas_version = App :: get('auth.cas.version', '2.0', 'string');
$supported_cas_versions = phpCAS::getSupportedProtocols();
if (!array_key_exists($cas_version, $supported_cas_versions)) {
Log :: error(
'Unsupported CAS version (%s). Check your configuration!',
$cas_version
);
return false;
}
// Init phpCAS client
phpCAS::client(
$cas_version,
App :: get('auth.cas.host'),
App :: get('auth.cas.port', 443, 'int'),
App :: get('auth.cas.context', '/idp/cas', 'string'),
Url :: get_absolute_url("/")
);
if (App :: get('auth.cas.ca_cert_certificate_path'))
phpCAS::setCasServerCACert(App :: get('auth.cas.ca_cert_certificate_path'));
else
phpCAS::setNoCasServerValidation();
Url :: add_url_handler(
'#^login/cas_callback$#', array('EesyPHP\\Auth\\Cas', 'handle_cas_callback'), null, false);
return true;
}
/**
* Compute CAS callback URL
* @return string
*/
private static function get_cas_callback_url() {
return Url :: get_absolute_url(
'login/cas_callback?next='.(
isset($_REQUEST['next'])?
$_REQUEST['next']:
urlencode(Url :: get_current_url())
)
);
}
/**
* Log user
* @param bool $force Force user authentication
* @return \EesyPHP\Auth\User|null
*/
public static function login($force=false) {
if (!phpCAS :: isAuthenticated() && $force) {
$_SESSION['cas_callback_url'] = self :: get_cas_callback_url();
phpCAS :: setFixedServiceURL($_SESSION['cas_callback_url']);
phpCAS :: forceAuthentication();
}
$user = (
phpCAS :: isAuthenticated()?
Auth :: get_user(phpCAS :: getUser()):
null
);
if ($force && !$user)
Log :: fatal('Fail to authenticate you');
return $user;
}
/**
* Logout
* @return void
*/
public static function logout() {
if (App :: get('auth.cas.logout', true, 'bool') && !self :: $fake_authenticated_user) {
if (App :: get('auth.cas.logout_url')) {
Url :: redirect(App :: get('auth.cas.logout_url'));
exit();
}
phpCAS::logout();
}
else {
session_unset();
session_destroy();
}
}
/**
* The CAS callback view
* @param \EesyPHP\UrlRequest $request
* @return void
*/
public static function handle_cas_callback($request) {
if (isset($_SESSION['cas_callback_url'])) {
phpCAS :: setFixedServiceURL($_SESSION['cas_callback_url']);
unset($_SESSION['cas_callback_url']);
}
$user = Auth :: login(false, 'Cas');
if ($user)
Url :: redirect(isset($_REQUEST['next'])?urldecode($_REQUEST['next']):null);
Log :: fatal('No CAS ticket or fail to authenticate you');
}
}

76
src/Auth/Form.php Normal file
View file

@ -0,0 +1,76 @@
<?php
namespace EesyPHP\Auth;
use EesyPHP\App;
use EesyPHP\Auth;
use EesyPHP\Hook;
use EesyPHP\Url;
use EesyPHP\Tpl;
class Form extends Method {
/**
* Initialize
* @return boolean
*/
public static function init() {
Url :: add_url_handler('#^login$#', array('EesyPHP\\Auth\\Form', 'handle_login'), null, false);
Hook :: register('logged_in', array('\\EesyPHP\\Auth\\Form', 'logged_in_hook'));
return true;
}
/**
* Log user
* @param bool $force Force user authentication
* @return \EesyPHP\Auth\User|null
*/
public static function login($force=false) {
$user = null;
if (isset($_REQUEST['username']) && isset($_REQUEST['password'])) {
$user = Auth :: authenticate($_REQUEST['username'], $_REQUEST['password']);
if (!$user) Tpl::add_error(_('Invalid username or password.'));
}
if ($force && !$user) {
if (Url :: get_current_url() != 'login')
Url :: redirect('login?next='.urlencode(Url :: get_current_url()));
return null;
}
return $user;
}
/**
* The login form view
* @param \EesyPHP\UrlRequest $request
* @return void
*/
public static function handle_login($request) {
$user = Auth :: login(false, 'Form');
$display_other_methods = array();
foreach (App::get('auth.login_form.display_other_methods', array(), 'array') as $method => $label)
if (Auth::method_is_enabled($method))
$display_other_methods[$method] = $label;
if (
!$user && isset($_REQUEST['method']) &&
array_key_exists($_REQUEST['method'], $display_other_methods)
) {
$user = Auth :: login($_REQUEST['method'], $_REQUEST['method']);
}
if ($user)
Url :: redirect(isset($_REQUEST['next'])?urldecode($_REQUEST['next']):null);
else
Tpl :: assign('next', (isset($_REQUEST['next'])?urldecode($_REQUEST['next']):''));
Tpl :: assign('display_other_methods', $display_other_methods);
Tpl :: display('login.tpl', 'Connection');
}
/**
* Logged in hook
* @param \EesyPHP\HookEvent $event
* @return void
*/
public static function logged_in_hook($event) {
if ($event->method == 'Form' && isset($_REQUEST['next']))
Url :: redirect(urldecode($_REQUEST['next']));
}
}

137
src/Auth/Http.php Normal file
View file

@ -0,0 +1,137 @@
<?php
namespace EesyPHP\Auth;
use EesyPHP\App;
use EesyPHP\Auth;
use EesyPHP\I18n;
use EesyPHP\Log;
use EesyPHP\Tpl;
use EesyPHP\Url;
use function EesyPHP\vardump;
class Http extends Method {
/**
* Method to retreive HTTP credentials
* @var string
*/
private static $method;
/**
* HTTP realm string (use to compute WWW-Authenticate HTTP header)
* @var string
*/
private static $realm;
/**
* Initialize
* @return boolean
*/
public static function init() {
self :: $method = App::get('auth.http.method', 'PHP_AUTH', 'string');
self :: $realm = App::get(
'auth.http.realm', _('Authentication required'), 'string');
return true;
}
/**
* Retreive HTTP authentication data
* @return array|false array('username' => '[login]', 'password' => '[password]') or false
*/
private static function get_auth_data() {
switch(self :: $method) {
case 'AUTHORIZATION':
Log :: debug("Auth HTTP: use AUTHORIZATION method");
if (isset($_SERVER['HTTP_AUTHORIZATION']) && !empty($_SERVER['HTTP_AUTHORIZATION'])) {
$auth_data = explode(':', base64_decode(substr($_SERVER['HTTP_AUTHORIZATION'], 6)));
if (is_array($auth_data) && count($auth_data) == 2) {
return array(
'username' => $auth_data[0],
'password' => $auth_data[1],
);
}
else
Log :: error("Fail to parse HTTP_AUTHORIZATION environnement variable.");
}
break;
case 'REMOTE_USER':
Log :: debug("Auth HTTP : use REMOTE_USER method");
if (isset($_SERVER['REMOTE_USER']) && !empty($_SERVER['REMOTE_USER'])) {
return array(
'username' => $_SERVER['REMOTE_USER'],
'password' => false,
);
}
break;
case 'PHP_AUTH':
default:
Log :: debug("Auth HTTP : use PHP_AUTH method");
if (isset($_SERVER['PHP_AUTH_USER']) && !empty($_SERVER['PHP_AUTH_USER'])) {
return array(
'username' => $_SERVER['PHP_AUTH_USER'],
'password' => $_SERVER['PHP_AUTH_PW'],
);
}
break;
}
Log :: trace("HTTP::get_auth_data(): no auth data found\n".vardump($_SERVER));
return false;
}
/**
* Log user
* @param bool $force Force user authentication
* @return \EesyPHP\Auth\User|null
*/
public static function login($force=false) {
$auth_data = self :: get_auth_data();
if (!$auth_data) {
if ($force) self :: force_login();
return null;
}
if (App :: get('auth.http.trust_without_password_challenge', false, 'bool'))
$user = Auth :: get_user($auth_data['username']);
else
$user = Auth :: authenticate($auth_data['username'], $auth_data['password']);
if (!$user && $force)
self :: force_login();
return $user;
}
/**
* Force HTTP user authentification
* @return void
*/
public static function force_login() {
header('HTTP/1.1 401 Authorization Required');
header(
sprintf('WWW-Authenticate: Basic realm="%s"', addslashes(self :: $realm))
);
if (Tpl::initialized()) {
Tpl :: display("must_login.tpl", I18n::_("Access denied"));
}
else {
printf("<h1>%s</h1>", I18n::_("Access denied"));
printf("<p>%s</p>", I18n::_("You must login to access this page."));
printf(
"<p><a href='%s'>%s</a></p>",
Url :: public_root_url(),
I18n::_("You must login to access this page.")
);
}
exit();
}
/**
* Logout
* @return void
*/
public static function logout() {
self :: force_login();
}
}

260
src/Auth/Ldap.php Normal file
View file

@ -0,0 +1,260 @@
<?php
namespace EesyPHP\Auth;
use EesyPHP\App;
use EesyPHP\Auth\User;
use EesyPHP\Config;
use EesyPHP\Log;
use function EesyPHP\ensure_is_array;
use function EesyPHP\cast;
use function EesyPHP\vardump;
use PEAR;
use Net_LDAP2;
use Net_LDAP2_Filter;
class Ldap extends Backend {
/**
* LDAP configuration as expected by Net_LDAP2
* @var array
*/
private static $ldap_config;
/**
* Net_LDAP2 connection (if connected)
* @var Net_LDAP2|null
*/
private static $connection = null;
/**
* Default LDAP user attributes configuration
* @var array<string,array>
*/
private static $default_user_attributes = array(
'uid' => array(
'name' => 'login',
'type' => 'string',
'multivalued' => false,
'default' => null,
),
'mail' => array(
'type' => 'string',
'multivalued' => false,
'default' => null,
),
'cn' => array(
'name' => 'name',
'type' => 'string',
'multivalued' => false,
'default' => null,
),
);
/**
* Initialize
* @return bool
*/
public static function init() {
if (!class_exists('Net_LDAP2')) {
$path = App::get('auth.ldap.netldap2_path', 'Net/LDAP2.php', 'string');
if (!@include($path)) {
Log::error('Fail to load Net_LDAP2 (%s)', $path);
return false;
}
}
foreach(array('host', 'basedn') as $param) {
if (!App::get("auth.ldap.$param")) {
Log :: error('LDAP %s not configured. Check your configuration!', $param);
return false;
}
}
self :: $ldap_config = array (
'host' => implode(' ', App :: get('auth.ldap.host', array(), 'array')),
'basedn' => App :: get('auth.ldap.basedn', null, 'string'),
'binddn' => App :: get('auth.ldap.bind_dn', null, 'string'),
'bindpw' => App :: get('auth.ldap.bind_password', null, 'string'),
'starttls' => App :: get('starttls', false, 'bool'),
);
if ($port = App :: get('auth.ldap.port', null, 'int'))
self :: $ldap_config['port'] = $port;
return true;
}
/**
* Connect on the LDAP directory
* @return bool
*/
private static function connect() {
if (is_a(self :: $connection, 'Net_LDAP2')) return true;
Log :: debug(
'Connect on LDAP host "%s" as %s (base DN="%s")',
self :: $ldap_config['host'],
isset(self :: $ldap_config['binddn'])?self :: $ldap_config['binddn']:"anonymous",
self :: $ldap_config['basedn']
);
// @phpstan-ignore-next-line
self :: $connection = Net_LDAP2::connect(self :: $ldap_config);
// @phpstan-ignore-next-line
if (PEAR::isError(self :: $connection)) {
Log :: error(
'Could not connect to LDAP server (%s): %s',
self :: $ldap_config['host'], self :: $connection->getMessage());
self :: $connection = null;
return false;
}
return true;
}
/**
* Make a search in the LDAP directory
* @param string $filter The LDAP filter string
* @param array|null $attrs Expected attributes (optional, default: all existing attributes)
* @param string|null $basedn The base DN of the search (optional, default: configured root base
* DN of the LDAP connection)
* @param string|array<string>|null $sorted If defined, sort return objects by specified attribute(s)
* @param array|null $options Search options as expected by NetLDAP::search() (optional, default: null)
*/
public static function search($filter, $attrs=null, $basedn=null, $sorted=null, $options=null) {
if (!self :: connect()) return false;
$options = is_array($options)?$options:array();
if (!is_null($attrs))
$options['attributes'] = $attrs;
Log :: debug(
'Run search in LDAP directory with filter "%s" on base DN "%s"',
$filter, $basedn?$basedn:"unset");
$search = self :: $connection -> search($basedn, $filter, $options);
// @phpstan-ignore-next-line
if (PEAR::isError($search)) {
Log :: error(
'Error occured searching in LDAP with filter "%s" on base DN "%s": %s',
$filter, $basedn?$basedn:"unset", $search->getMessage()
);
return false;
}
$entries = (
$sorted?
$search -> sorted(ensure_is_array($sorted)):
$search -> entries()
);
$result = array();
foreach ($entries as $entry)
$result[$entry->dn()] = $entry -> getValues();
return $result;
}
/**
* Cast an LDAP value
* @param mixed $value The raw LDAP value
* @param string $type The expected type: see cast() for supported types, but boolean value will
* be casted as LDAP boolean string.
* @return mixed The casted value
*/
public static function cast($value, $type) {
switch($type) {
case 'bool':
case 'boolean':
return $value == 'TRUE';
case 'array_of_bool':
case 'array_of_boolean':
$values = array();
foreach(ensure_is_array($value) as $value)
$values[] = $value == 'TRUE';
return $values;
default:
return cast($value, $type);
}
}
/**
* Retreive LDAP attribute value(s) from LDAP entry
* @param array<string,mixed> $entry The LDAP entry
* @param string $attr The LDAP attribute name
* @param bool $all_values Return all values or just the first one (optional, default: false)
* @param mixed $default The default value to return if the LDAP attribute is undefined
* (optional, default: an empty array if $all_values, null otherwise)
* @param string|null $cast The expected type of value (optional, default: string)
*/
public static function get_attr($entry, $attr, $all_values=False, $default=null, $cast=null) {
$values = self :: cast(
isset($entry[$attr])?ensure_is_array($entry[$attr]):array(),
"array_of_".($cast?$cast:'string')
);
if ($values)
return $all_values?$values:$values[0];
if ($all_values)
return !is_null($default)?$default:array();
return $default;
}
/**
* Retreive a user by its username
* @param string $username
* @return \EesyPHP\Auth\User|null|false The user object if found, null it not, false in case of error
*/
public static function get_user($username) {
$attrs = App::get('auth.ldap.user_attributes', self :: $default_user_attributes, 'array');
$users = self :: search(
str_replace(
'[username]', Net_LDAP2_Filter::escape($username),
App::get('auth.ldap.user_filter_by_uid', 'uid=[username]', 'string')
),
array_keys($attrs),
App::get('auth.ldap.user_basedn', null, 'string')
);
if (!is_array($users)) {
Log::warning('An error occured looking for user "%s" in LDAP directory', $username);
return false;
}
if (!$users) {
Log::debug('User "%s" not found in LDAP directory', $username);
return null;
}
if (count($users) > 1) {
Log::warning(
'More than on users found with username "%s": %s',
$username, implode(' / ', array_keys($users))
);
}
$dn = key($users);
$info = array('dn' => $dn);
foreach($attrs as $attr => $attr_config) {
$info[Config::get("name", $attr, 'string', false, $attr_config)] = self :: get_attr(
$users[$dn],
$attr,
Config::get("multivalued", false, 'bool', false, $attr_config),
Config::get("default", null, null, false, $attr_config)
);
}
Log::debug('User "%s" found in LDAP directory (%s):\n%s', $username, $dn, vardump($info));
return new User($username, '\\EesyPHP\\Auth\\LDAP', $info);
}
/**
* Check a user password
* @param \EesyPHP\Auth\User $user The user object
* @param string $password The password to check
* @return boolean
*/
public static function check_password($user, $password) {
$config = self :: $ldap_config;
$config['binddn'] = (
App::get('auth.ldap.bind_with_username', false, 'bool')?
$user->username:
$user->dn
);
$config['bindpw'] = $password;
$result = Net_LDAP2::connect($config);
// @phpstan-ignore-next-line
return !PEAR::isError($result);
}
}

34
src/Auth/Method.php Normal file
View file

@ -0,0 +1,34 @@
<?php
namespace EesyPHP\Auth;
use EesyPHP\Log;
class Method {
/**
* Initialize
* @return boolean
*/
public static function init() {
return true;
}
/**
* Log user
* @param bool $force Force user authentication
* @return \EesyPHP\Auth\User|null
*/
public static function login($force=false) {
Log :: fatal('login() is not implement for this authentication method.');
return null;
}
/**
* Logout
* @return void
*/
public static function logout() {
return;
}
}

86
src/Auth/User.php Normal file
View file

@ -0,0 +1,86 @@
<?php
namespace EesyPHP\Auth;
use EesyPHP\Log;
class User {
/**
* Username
* @var string
*/
private $username;
/**
* User backend class name
* @var string
*/
private $backend;
/**
* User info
* @var array<string,mixed>
*/
private $info;
/**
* Constructor
* @param string $username The username
* @param string $backend The backend class name
* @param array<string,mixed>|null $info User info (optional)
*/
public function __construct($username, $backend, $info=null) {
$this -> username = $username;
$this -> backend = $backend;
$this -> info = is_array($info)?$info:array();
}
/**
* Magic method to get a dynamic property
* @param string $key The property
* @return mixed
*/
public function __get($key) {
switch ($key) {
case 'username':
return $this -> username;
case 'backend':
return $this -> backend;
default:
if (array_key_exists($key, $this -> info))
return $this -> info[$key];
}
Log::warning(
'Ask for unknown user property %s:\n%s', $key, Log::get_debug_backtrace_context());
return null;
}
/**
* Magic method to check if a dynamic property is set
* @param string $key The property
* @return bool
*/
public function __isset($key) {
switch ($key) {
case 'username':
case 'backend':
return true;
default:
return array_key_exists($key, $this -> info);
}
}
/**
* Check user password
* @param string $password
* @return bool
*/
public function check_password($password) {
return call_user_func(
array($this -> backend, 'check_password'),
$this, $password
);
}
}

View file

@ -437,7 +437,7 @@ class I18n {
// Extract messages from templates files using tsmarty2c.php
$result = run_external_command(
array(
App :: root_directory_path()."/vendor/smarty-gettext/smarty-gettext/tsmarty2c.php",
PHP_BINARY.' '.App :: root_directory_path()."/vendor/smarty-gettext/smarty-gettext/tsmarty2c.php",
basename($templates_directory),
),
null, // Pass nothing on STDIN

View file

@ -132,7 +132,7 @@ class Log {
* @return true
*/
public static function log($level, $message, ...$extra_args) {
global $auth_user, $argv;
global $argv;
if (!array_key_exists($level, self :: $levels)) $level = self :: $default_level;
if (self :: $levels[$level] < self :: $levels[self :: $level]) return true;
@ -162,8 +162,8 @@ class Log {
$_SERVER['REQUEST_URI'],
$_SERVER['REMOTE_ADDR'],
);
if (isset($auth_user))
$msg[] = ($auth_user['username']?$auth_user['username']:'anonymous');
if (Auth::enabled())
$msg[] = (Auth::user()?Auth::user()->username:'anonymous');
$msg[] = $level;
$msg[] = $message;
$msg = implode(' - ', $msg)."\n";

View file

@ -47,11 +47,8 @@ class SentryIntegration {
]);
\Sentry\configureScope(function (\Sentry\State\Scope $scope): void {
global $auth_user;
$scope->setUser([
'id' => isset($auth_user) && $auth_user?$auth_user['uid']:null,
'email' => isset($auth_user) && $auth_user?$auth_user['mail']:null,
'segment' => isset($auth_user) && $auth_user?$auth_user['type']:null,
'username' => Auth::user()?Auth::user()->username:null,
'ip_address' => php_sapi_name()=='cli'?null:$_SERVER['REMOTE_ADDR'],
]);
});

View file

@ -356,7 +356,6 @@ class Tpl {
* @return void
*/
protected static function define_common_variables($pagetitle=null) {
global $auth_user;
self :: assign('public_root_url', Url :: public_root_url());
self :: assign('pagetitle', $pagetitle);
self :: assign('main_pagetitle', App::get('main_pagetitle', null, 'string'));
@ -379,8 +378,8 @@ class Tpl {
self :: assign('TEXT_DOMAIN', I18n :: TEXT_DOMAIN);
// Authenticated user info
if (isset($auth_user))
self :: assign('auth_user', $auth_user);
if (Auth::user())
self :: assign('auth_user', Auth::user());
}
/**

View file

@ -6,6 +6,12 @@ use Exception;
class Url {
/**
* Current request
* @var UrlRequest|null
*/
public static $request = null;
/**
* Configured URL patterns :
*
@ -77,8 +83,8 @@ class Url {
* @param array|null $additional_info Array of information to pass to the URL handler
* @param boolean $authenticated Permit to define if this URL is accessible only for
* authenticated users (optional, default: true if the
* special force_authentication function is defined,
* false otherwise)
* EesyPHP Authentication feature is enabled, false
* otherwise)
* @param boolean $overwrite Allow overwrite if a command already exists with the
* same name (optional, default: false)
* @param boolean $api_mode Enable API mode (optional, default: false)
@ -88,12 +94,6 @@ class Url {
public static function add_url_handler($pattern, $handler=null, $additional_info=null,
$authenticated=null, $overwrite=true, $api_mode=false,
$http_methods=null) {
$authenticated = (
is_null($authenticated)?
function_exists('force_authentication'):
(bool)$authenticated
);
// Check HTTP methods parameter
if (is_null($http_methods))
$http_methods = array('GET', 'POST');
@ -422,8 +422,7 @@ class Url {
* Handle the current requested URL
*
* Note: if the route required that user is authenticated, this method will
* invoke the force_authentication() special function (or trigger a fatal error
* if it's not defined).
* invoke Auth::login() in force mode (or trigger a fatal error if fail).
*
* @param string|null $default_url The default URL if current one does not
* match with any configured pattern.
@ -433,34 +432,31 @@ class Url {
public static function handle_request($default_url=null) {
$sentry_span = new SentrySpan('http.handle_request', 'Handle the HTTP request');
$request = self :: get_request($default_url);
self :: $request = self :: get_request($default_url);
if (!is_callable($request -> handler)) {
if (!is_callable(self :: $request -> handler)) {
Log :: error(
"URL handler function %s does not exists !",
format_callable($request -> handler));
format_callable(self :: $request -> handler));
Log :: fatal(I18n::_("This request cannot be processed."));
}
if ($request -> api_mode)
if (self :: $request -> api_mode)
self :: $_api_mode = true;
if (Tpl :: initialized())
Tpl :: assign('request', $request);
Tpl :: assign('request', self :: $request );
// Check authentication (if need)
if($request -> authenticated)
if (function_exists('force_authentication'))
force_authentication();
else
Log :: fatal(I18n::_("Authentication required but force_authentication function is not defined."));
if(self :: $request -> authenticated && !Auth::login(true))
Log :: fatal(I18n::_("Authentication required but fail to authenticate you."));
try {
call_user_func($request -> handler, $request);
call_user_func(self :: $request -> handler, self :: $request );
}
catch (Exception $e) {
Log :: exception(
$e, "An exception occured running URL handler function %s()",
format_callable($request -> handler));
$e, "An exception occured running URL handler %s",
format_callable(self :: $request -> handler));
Log :: fatal(I18n::_("This request could not be processed correctly."));
}
$sentry_span->finish();

View file

@ -54,7 +54,7 @@ class UrlRequest {
$this -> handler = $handler_info['handler'];
$this -> authenticated = (
isset($handler_info['authenticated'])?
boolval($handler_info['authenticated']):true);
$handler_info['authenticated']:null);
$this -> api_mode = (
isset($handler_info['api_mode'])?
boolval($handler_info['api_mode']):false);
@ -78,7 +78,11 @@ class UrlRequest {
if ($key == 'handler')
return $this -> handler;
if ($key == 'authenticated')
return $this -> authenticated;
return (
is_null($this -> authenticated)?
Auth::enabled():
(bool)$this -> authenticated
);
if ($key == 'api_mode')
return $this -> api_mode;
if ($key == 'referer')

View file

@ -99,6 +99,13 @@ function ensure_is_array($value) {
* @return mixed The cast value
**/
function cast($value, $type, $split=false) {
if (strpos($type, 'array_of_') === 0) {
$type = substr($type, 9);
$values = array();
foreach(ensure_is_array($value) as $key => $value)
$values[$key] = cast($value, $type);
return $values;
}
switch($type) {
case 'bool':
case 'boolean':

View file

@ -53,11 +53,11 @@
<ul class="navbar-nav ml-md-auto">
<li class="nav-item dropdown">
<a class="nav-item nav-link dropdown-toggle mr-md-2" href="#" id="bd-versions" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<i class="fa fa-user" aria-hidden="true"></i> {$auth_user.name|escape:"htmlall"} <span class="caret"></span>
<i class="fa fa-user" aria-hidden="true"></i> {$auth_user->name|escape:"htmlall"} <span class="caret"></span>
</a>
<div class="dropdown-menu dropdown-menu-right">
{block name="navbar-user-menu"}{/block}
<a class="dropdown-item" href="?logout=1"><i class="fas fa-sign-out-alt"></i> Déconnexion</a>
<a class="dropdown-item" href="logout"><i class="fas fa-sign-out-alt"></i> {t domain=$CORE_TEXT_DOMAIN}Logout{/t}</a>
</div>
</li>
</ul>

22
templates/login.tpl Normal file
View file

@ -0,0 +1,22 @@
{extends file='Tpl:empty.tpl'}
{block name="pagetitle"}<h2 class="center"><i class="fas fa-sign-in-alt"></i> {t domain=$CORE_TEXT_DOMAIN}Connection{/t}</h2>{/block}
{block name="content"}
<form action="login" method="POST">
<input type="hidden" name="next" value="{$next|escape:"quotes"}"/>
<div class="form-group">
<label for="username">{t domain=$CORE_TEXT_DOMAIN}Username{/t}</label>
<input type="text" class="form-control" name="username" />
</div>
<div class="form-group">
<label for="password">{t domain=$CORE_TEXT_DOMAIN}Password{/t}</label>
<input type="password" class="form-control" name="password">
</div>
<button type="submit" class="btn btn-primary">{t domain=$CORE_TEXT_DOMAIN}Submit{/t}</button>
{foreach $display_other_methods as $method => $name}
<a class='btn btn-secondary' href='login?method={$method}&next={$next|escape:"url"}'>{$name}</a>
{/foreach}
</form>
{/block}
{*
# vim: autoindent expandtab tabstop=2 shiftwidth=2 softtabstop=2
*}

11
templates/logout.tpl Normal file
View file

@ -0,0 +1,11 @@
{extends file='Tpl:empty.tpl'}
{block name="pagetitle"}{/block}
{block name="content"}
<div class="jumbotron">
<h1 class="display-4">{t domain=$CORE_TEXT_DOMAIN}Disconnected{/t}</h1>
<p class="lead">{t escape=off domain=$CORE_TEXT_DOMAIN}You are now disconnected.{/t}</p>
</div>
{/block}
{*
# vim: autoindent expandtab tabstop=2 shiftwidth=2 softtabstop=2
*}

11
templates/must_login.tpl Normal file
View file

@ -0,0 +1,11 @@
{extends file='Tpl:empty.tpl'}
{block name="pagetitle"}{/block}
{block name="content"}
<div class="jumbotron">
<h1 class="display-4">{t domain=$CORE_TEXT_DOMAIN}Access denied{/t}</h1>
<p class="lead">{t escape=off domain=$CORE_TEXT_DOMAIN}You must login to access this page.{/t}</p>
</div>
{/block}
{*
# vim: autoindent expandtab tabstop=2 shiftwidth=2 softtabstop=2
*}