Page MenuHomeDevCentral

No OneTemporary

diff --git a/workspaces/src/Engines/Auth/Actions/GivePermissionUserAction.php b/workspaces/src/Engines/Auth/Actions/GivePermissionUserAction.php
index 6c5e1cb..c2419b1 100644
--- a/workspaces/src/Engines/Auth/Actions/GivePermissionUserAction.php
+++ b/workspaces/src/Engines/Auth/Actions/GivePermissionUserAction.php
@@ -1,139 +1,142 @@
<?php
/**
* _, __, _, _ __, _ _, _, _
* / \ |_) (_ | | \ | /_\ |\ |
* \ / |_) , ) | |_/ | | | | \|
* ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
*
* Give permission user action class
*
* @package ObsidianWorkspaces
* @subpackage Auth
* @author Sébastien Santoro aka Dereckson <dereckson@espace-win.org>
* @license http://www.opensource.org/licenses/bsd-license.php BSD
* @filesource
*
*/
namespace Waystone\Workspaces\Engines\Auth\Actions;
use Waystone\Workspaces\Engines\Auth\Permission;
use Waystone\Workspaces\Engines\Auth\UserAction;
use Waystone\Workspaces\Engines\Framework\Resources;
use Waystone\Workspaces\Engines\Serialization\ArrayDeserializable;
use Exception;
use InvalidArgumentException;
use JsonSerializable;
/**
* User action to grant user a permission
*/
class GivePermissionUserAction extends UserAction
implements ArrayDeserializable, JsonSerializable {
/**
* @var string The permission name
*/
public $permissionName;
/**
* @var int The permission flag
*/
public $permissionFlag = 1;
/**
* @var string The target resource type
*/
public $resourceType;
/**
* @var string The target resource identifier
*/
public $resourceIdentifier;
/**
* Executes the user action
*/
public function run () {
- $id = Resources::resolveID($this->resourceType, $this->resourceIdentifier);
+ $id = $this->context->resources->resolveID(
+ $this->resourceType,
+ $this->resourceIdentifier,
+ );
if ($id->isNone()) {
throw new Exception("Can't resolve resource "
. $this->resourceType . " " . $this->resourceIdentifier);
}
$this->targetUser->setPermission(
$this->resourceType, $id->getValue(),
$this->permissionName, $this->permissionFlag,
);
}
/**
* Loads a GivePermissionUserAction instance from an associative array.
*
* @param object $data The associative array to deserialize
*
* @return GivePermissionUserAction The deserialized instance
*/
public static function loadFromArray (mixed $data) : self {
// Validate mandatory data
if (!array_key_exists("resource", $data)) {
throw new InvalidArgumentException("A resource property, with two mandatory type and id property is required.");
}
if (!array_key_exists("permission", $data)) {
throw new InvalidArgumentException("A permission property, with a mandatory name property and a facultative flag property is required.");
}
$resource = $data["resource"];
$permission = $data["permission"];
if (!array_key_exists("name", $permission)) {
throw new InvalidArgumentException("Permission name is required.");
}
if (!array_key_exists("type", $resource)) {
throw new InvalidArgumentException("Resource type is required.");
}
if (!array_key_exists("id", $resource)) {
throw new InvalidArgumentException("Resource id is required.");
}
// Build instance
$instance = new GivePermissionUserAction();
$instance->resourceType =
Permission::getResourceTypeLetterFromCode($resource["type"]);
$instance->resourceIdentifier = $resource["id"];
$instance->permissionName = $permission["name"];
if (array_key_exists("flag", $permission)) {
$instance->permissionFlag = $permission["flag"];
}
return $instance;
}
/**
* Serializes the object to a value that can be serialized natively by
* json_encode().
*
* @return array The serializable value
*/
public function jsonSerialize () : array {
$type = Permission::getResourceTypeCodeFromLetter($this->resourceType);
return [
"resource" => [
"type" => $type,
"id" => $this->resourceIdentifier
],
"permission" => [
"name" => $this->permissionName,
"flag" => $this->permissionFlag,
],
];
}
}
diff --git a/workspaces/src/Engines/Auth/AuthenticationMethod.php b/workspaces/src/Engines/Auth/AuthenticationMethod.php
index 1240366..c3c086f 100644
--- a/workspaces/src/Engines/Auth/AuthenticationMethod.php
+++ b/workspaces/src/Engines/Auth/AuthenticationMethod.php
@@ -1,281 +1,288 @@
<?php
/**
* _, __, _, _ __, _ _, _, _
* / \ |_) (_ | | \ | /_\ |\ |
* \ / |_) , ) | |_/ | | | | \|
* ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
*
* Authentication method class
*
* @package ObsidianWorkspaces
* @subpackage Auth
* @author Sébastien Santoro aka Dereckson <dereckson@espace-win.org>
* @license http://www.opensource.org/licenses/bsd-license.php BSD
* @filesource
*/
namespace Waystone\Workspaces\Engines\Auth;
use Waystone\Workspaces\Engines\Auth\Actions\AddToGroupUserAction;
use Waystone\Workspaces\Engines\Auth\Actions\GivePermissionUserAction;
use Waystone\Workspaces\Engines\Framework\Context;
-use Waystone\Workspaces\Engines\Serialization\ArrayDeserializable;
+use Waystone\Workspaces\Engines\Serialization\ArrayDeserializableWithContext;
use Keruald\OmniTools\DataTypes\Option\None;
use Keruald\OmniTools\DataTypes\Option\Option;
use Keruald\OmniTools\DataTypes\Option\Some;
use Language;
use Message;
use User;
use Exception;
use InvalidArgumentException;
/**
* Authentication method class
*
* This class has to be extended to implement custom authentication methods.
*/
-abstract class AuthenticationMethod implements ArrayDeserializable {
+abstract class AuthenticationMethod implements ArrayDeserializableWithContext {
/**
* @var User The local user matching the authentication
*/
public $localUser;
/**
* @var string The username
*/
public $name;
/**
* @var string The e-mail address
*/
public $email;
/**
* @var string The authentication method identifiant
*/
public $id;
/**
* @var string The remote identity provider user identifiant
*/
public $remoteUserId;
/**
* @var Message The localized authentication login message
*/
public $loginMessage;
/**
* @var boolean Determines if the authentication method could be used to
* register new users
*/
public $canCreateUser = false;
/**
* @var Array Actions to execute if a user is created, each instance a
* member of UserAction
*/
public $createUserActions = [];
/**
* @var Context The site context
*/
public $context;
/**
* @var Message The localized authentication error message
*/
public $loginError;
/**
* Gets authentication link for this method
*/
public abstract function getAuthenticationLink ();
/**
* Handles request
*/
public abstract function handleRequest ();
/**
* Runs actions planned on user create
*/
protected function runCreateUserActions () {
foreach ($this->createUserActions as $action) {
+ $action->context = $this->context;
$action->targetUser = $this->localUser;
$action->run();
}
}
/**
* Finds user from available data
*
* @return Option<User> the user if a user has been found; otherwise, false.
*/
private function findUser () : Option {
+ $users = $this->context->userRepository;
+
if ($this->remoteUserId != '') {
- $user = User::getUserFromRemoteIdentity(
- $this->id, $this->remoteUserId,
- );
+ $user = $users->getUserFromRemoteIdentity(
+ $this->id, $this->remoteUserId,
+ );
- if ($user !== null) {
- return new Some($user);
+ if ($user->isSome()) {
+ return $user;
}
}
if ($this->email != '') {
- $user = User::get_user_from_email($this->email);
+ $user = $users->getUserFromEmail($this->email);
+
if ($user->isSome()) {
return $user;
}
}
return new None;
}
/**
* Signs in or creates a new user
*
* @return boolean true if user has been successfully logged in; otherwise,
* false.
*/
public function signInOrCreateUser () {
// At this stage, if we don't already have a user instance,
// we're fetching it by remote user id or mail.
//
// If no result is returned, we're creating a new user if needed.
//
// Finally, we proceed to log in.
if ($this->localUser === null) {
$user = $this->findUser();
if ($user->isSome()) {
$this->localUser = $user->getValue();
}
}
if ($this->localUser === null) {
if (!$this->canCreateUser) {
$this->loginError =
Language::get("ExternalLoginCantCreateAccount");
return false;
} else {
$this->createUser();
if ($this->localUser === null) {
throw new Exception("Can't sign in: after correct remote authentication, an error occurred creating locally a new user.");
}
}
}
$this->signIn($this->localUser);
return true;
}
/**
* Signs in the specified user
*
* @param User The user to log in
*/
public function signIn (User $user) {
$this->context->session->user_login($user->id);
}
/**
* Creates a new user based on the authentication provisioning information
*
* @return User The user created
*/
public function createUser () {
if (!$this->canCreateUser) {
throw new Exception("Can't create user: the canCreateUser property is set at false.");
}
- $user = User::create();
+ $user = User::create($this->context->db);
$user->name = $this->name;
$user->email = $this->email;
$user->save_to_database();
$user->setRemoteIdentity(
$this->id, $this->remoteUserId,
);
$this->localUser = $user;
$this->runCreateUserActions();
}
/**
* Gets authentication method from ID
*
* @param string $id The authentication method id
* @param Context $context The site context
*
* @return AuthenticationMethod The authentication method matching the id
*/
public static function getFromId ($id, $context) {
if ($context->workspace != null) {
foreach (
$context->workspace->configuration->authenticationMethods as
$authenticationMethod
) {
if ($authenticationMethod->id == $id) {
return $authenticationMethod;
}
}
}
return null;
}
/**
* Loads an AuthenticationMethod instance from a generic array.
* Typically used to deserialize a configuration.
*
* @param array $data The associative array to deserialize
+ * @param mixed $context The application context
*
* @return AuthenticationMethod The deserialized instance
* @throws InvalidArgumentException|Exception
*/
- public static function loadFromArray (array $data) : self {
+ public static function loadFromArray (array $data, mixed $context) : self {
$instance = new static;
+ $instance->context = $context;
+
if (!array_key_exists("id", $data)) {
throw new InvalidArgumentException("Authentication method id is required.");
}
$instance->id = $data["id"];
$message = $data["loginMessage"] ?? Language::get("SignIn");
$instance->loginMessage = new Message($message);
if (array_key_exists("createUser", $data)) {
$createUser = $data["createUser"];
if (array_key_exists("enabled", $createUser)) {
$instance->canCreateUser = ($createUser["enabled"] === true);
}
$addToGroups = $createUser["addToGroups"] ?? [];
foreach ($addToGroups as $actionData) {
$instance->createUserActions[] =
AddToGroupUserAction::loadFromArray($actionData);
}
$givePermissions = $createUser["givePermissions"] ?? [];
foreach ($createUser["givePermissions"] as $actionData) {
$instance->createUserActions[] =
GivePermissionUserAction::loadFromArray($actionData);
}
}
return $instance;
}
}
diff --git a/workspaces/src/Engines/Auth/Methods/AzharProvider.php b/workspaces/src/Engines/Auth/Methods/AzharProvider.php
index 5618a1e..f9225c0 100644
--- a/workspaces/src/Engines/Auth/Methods/AzharProvider.php
+++ b/workspaces/src/Engines/Auth/Methods/AzharProvider.php
@@ -1,225 +1,226 @@
<?php
/**
* _, __, _, _ __, _ _, _, _
* / \ |_) (_ | | \ | /_\ |\ |
* \ / |_) , ) | |_/ | | | | \|
* ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
*
* Azhàr provider authentication method class
*
* @package ObsidianWorkspaces
* @subpackage Auth
* @author Sébastien Santoro aka Dereckson <dereckson@espace-win.org>
* @license http://www.opensource.org/licenses/bsd-license.php BSD
* @filesource
*/
namespace Waystone\Workspaces\Engines\Auth\Methods;
use Waystone\Workspaces\Engines\Auth\AuthenticationMethod;
use Language;
use stdClass;
/**
* Azhàr provider authentication method class
*
* Azhàr sends a document providing authentication and registration of new
* users. It's signed by a shared secret key.
*/
class AzharProvider extends AuthenticationMethod {
/**
* @var string Shared secret key
*/
public $secretKey;
/**
* @var string Client key, to identify the consumer application.
*/
public $clientKey;
/**
* @var string The Azhàr identity provider login URL
*/
public $url;
/**
* Handles user login request
*/
public function handleRequest () {
$action = array_key_exists('action', $_GET) ? $_GET['action'] : '';
$sessionKey =
array_key_exists('sessionKey', $_GET) ? $_GET['sessionKey'] : '';
if ($action == "user.login.azhar.initialize") {
//Redirects user to Azhàr SSO service
$callbackUrl =
get_server_url() . get_url($this->context->workspace->code)
. '?action=user.login.azhar.success&authenticationMethodId='
. $this->id;
$url = $this->url . '?mode=provider&key=' . $this->clientKey
. '&sessionKey=' . $this->getSessionKey()
. '&url=' . urlencode($callbackUrl);
header('Location: ' . $url);
exit;
} elseif ($action == "user.login.azhar.success") {
//User claims to have logged in, we can get authentication information
$reply = $this->fetchInformation();
if (!$this->isDocumentLegit($reply)) {
$this->loginError = Language::get('ExternalLoginNotLegitReply');
return;
}
if ($reply->status == "SUCCESS") {
//Creates user or login
$this->name = $reply->username;
$this->email = $reply->email;
$this->remoteUserId = $reply->localUserId;
$this->signInOrCreateUser();
return;
} elseif ($reply->status == "ERROR_USER_SIDE") {
switch ($reply->code) {
case 'NO_USER_VISIT':
case 'NOT_LOGGED_IN':
$this ->loginError = Language::get('ExternalLoginNotRemotelyLoggedIn');
return;
}
} elseif ($reply->status == "ERROR_BETWEEN_US") {
switch ($reply->code) {
case 'SESSION_BADSECRET':
$this->loginError = sprintf(Language::get('ExternalLoginTechnicalDifficulty'), $reply->code);
return;
}
}
$this->loginError = '<p>An unknown error has been received:</p><pre>' . print_r($reply, true) . '</pre><p>Please notify technical support about this new error message, so we can handle it in the future.</p>';
} else {
$this->loginError = '<p>Unknown action: $action</p>';
}
}
/**
* Gets Azhàr provider session key
*
* This key allows us as consumer to fetch information, and Azhàr as provider to store it.
*
* @return string the session key
*/
public function getSessionKey () {
$hash = md5($this->id);
if (!isset($_SESSION['Auth-$hash']['SessionKey'])) {
$url = $this->url . '?mode=provider.announce&key=' . $this->clientKey
. '&url=n/a';
$reply = self::query($url);
$this->setSessionSecret($reply->sessionSecret);
$_SESSION['Auth-$hash']['SessionKey'] = $reply->sessionKey;
}
return $_SESSION['Auth-$hash']['SessionKey'];
}
/**
* Gets Azhàr provider session secret
*
* @return string the session secret
*/
private function getSessionSecret () {
$hash = md5($this->id);
return $_SESSION['Auth-$hash']['SessionSecret'];
}
/**
* Sets Azhàr provider session secret
*
* @param string $secret the session secret
*/
private function setSessionSecret ($secret) {
$hash = md5($this->id);
$_SESSION['Auth-$hash']['SessionSecret'] = $secret;
}
/**
* Gets Azhàr external authentication link
*
* @retrun string the login link
*/
public function getAuthenticationLink () {
$url = get_server_url() . get_url($this->context->workspace->code)
. '?action=user.login.azhar.initialize&authenticationMethodId=' . $this->id;
return $url;
}
/**
* Determines if the document received has been signed by the correct shared secret key.
*
* @return boolean true if the document is legit; otherwise, false.
*/
function isDocumentLegit ($document) {
$hash = '';
$claimedHash = null;
foreach ($document as $key => $value) {
if ($key == 'hash') {
$claimedHash = $value;
continue;
}
$hash .= md5($key . $value);
}
$salt = '$2y$10$' . substr($this->secretKey, 0, 22);
$computedHash = crypt($hash, $salt);
return $claimedHash === $computedHash;
}
/**
* Fetches information document
*
* @return stdClass The Azhàr identity provider information about the current login operation
*/
function fetchInformation () {
$url = $this->url . '?mode=provider.fetch&key=' . $this->clientKey
. '&sessionSecret=' . $this->getSessionSecret()
. '&sessionKey=' . $this->getSessionKey()
. '&url=n/a';
return self::query($url);
}
/**
* Gets the contents of the specified URL and decode the JSON reply
*
* @param string $url The URL to the JSON document to query.
*
* @return stdClass The reply
*/
public static function query ($url) {
$data = file_get_contents($url);
return json_decode($data);
}
/**
* Loads an AzharProvider instance from a generic array.
* Typically used to deserialize a configuration.
*
* @param array $data The associative array to deserialize
+ * @param mixed $context The application context
*
* @return AzharProvider The deserialized instance
*/
- public static function loadFromArray (array $data) : self {
- $instance = parent::loadFromArray($data);
+ public static function loadFromArray (array $data, mixed $context) : self {
+ $instance = parent::loadFromArray($data, $context);
$instance->url = $data["url"];
$instance->secretKey = $data["secretKey"];
$instance->clientKey = $data["clientKey"];
return $instance;
}
}
diff --git a/workspaces/src/Engines/Auth/UserAction.php b/workspaces/src/Engines/Auth/UserAction.php
index aee1388..adcd9ea 100644
--- a/workspaces/src/Engines/Auth/UserAction.php
+++ b/workspaces/src/Engines/Auth/UserAction.php
@@ -1,46 +1,50 @@
<?php
/**
* _, __, _, _ __, _ _, _, _
* / \ |_) (_ | | \ | /_\ |\ |
* \ / |_) , ) | |_/ | | | | \|
* ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
*
* User action class
*
* @package ObsidianWorkspaces
* @subpackage Auth
* @author Sébastien Santoro aka Dereckson <dereckson@espace-win.org>
* @license http://www.opensource.org/licenses/bsd-license.php BSD
* @filesource
*
*/
namespace Waystone\Workspaces\Engines\Auth;
+use Waystone\Workspaces\Engines\Framework\Context;
+
use User;
/**
* User action class, to be extended to implement an action related to user
*/
abstract class UserAction {
/**
* @var User the target action user
*/
public $targetUser;
+ public ?Context $context = null;
+
/**
* Initializes a new instance of an UserAction object
*
* @param User $targetUser the target action user
*/
public function __construct ($targetUser = null) {
$this->targetUser = $targetUser;
}
/**
* Executes the user action
*/
abstract public function run ();
}
diff --git a/workspaces/src/Engines/Framework/Application.php b/workspaces/src/Engines/Framework/Application.php
index b448ae6..99f4616 100644
--- a/workspaces/src/Engines/Framework/Application.php
+++ b/workspaces/src/Engines/Framework/Application.php
@@ -1,27 +1,35 @@
<?php
namespace Waystone\Workspaces\Engines\Framework;
use Keruald\Database\Database;
use Waystone\Workspaces\Engines\Errors\ErrorHandling;
+use Waystone\Workspaces\Engines\Users\UserRepository;
class Application {
public static function init () : void {
Environment::init();
ErrorHandling::init();
}
public static function getContext(array $config) : Context {
$context = new Context();
$context->config = $config;
$context->db = Database::load($config["sql"]);
- $context->session = Session::load($context->db);
+ $context->userRepository = new UserRepository($context->db);
+ $context->resources = new Resources(
+ $context->userRepository,
+ );
+ $context->session = Session::load(
+ $context->db,
+ $context->userRepository,
+ );
$context->url = get_current_url_fragments();
$context->initializeTemplateEngine($context->config['Theme']);
return $context;
}
}
diff --git a/workspaces/src/Engines/Framework/Context.php b/workspaces/src/Engines/Framework/Context.php
index 96df73e..7ef609e 100644
--- a/workspaces/src/Engines/Framework/Context.php
+++ b/workspaces/src/Engines/Framework/Context.php
@@ -1,105 +1,113 @@
<?php
/**
* _, __, _, _ __, _ _, _, _
* / \ |_) (_ | | \ | /_\ |\ |
* \ / |_) , ) | |_/ | | | | \|
* ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
*
* Application context class
*
* @package ObsidianWorkspaces
* @subpackage Controller
* @author Sébastien Santoro aka Dereckson <dereckson@espace-win.org>
* @license http://www.opensource.org/licenses/bsd-license.php BSD
* @filesource
*/
namespace Waystone\Workspaces\Engines\Framework;
use Keruald\Database\DatabaseEngine;
use Smarty\Smarty;
use User;
+use Waystone\Workspaces\Engines\Users\UserRepository;
use Waystone\Workspaces\Engines\Workspaces\WorkSpace;
/**
* Context class
*
* This class describes the site context.
*/
class Context {
/**
* @var ?WorkSpace the workspace currently enabled
*/
public ?WorkSpace $workspace = null;
/**
* @var DatabaseEngine the database
*/
public DatabaseEngine $db;
/**
* @var array the configuration
*/
public array $config;
+ /**
+ * @var UserRepository the users already loaded from database
+ */
+ public UserRepository $userRepository;
+
+ public Resources $resources;
+
/**
* @var User the user currently logged in
*/
public User $user;
/**
* @var Session the current session
*/
public Session $session;
/**
* @var string[] the URL fragments
*/
public array $url;
/**
* @var Smarty the template engine
*/
public Smarty $templateEngine;
///
/// Helper methods
///
/**
* Gets application root directory
*
* @return string|false the application root directory
*/
public function getApplicationRootDirectory () : string|false {
return getcwd();
}
///
/// Templates
///
/**
* Initializes the template engine
*
* @param string $theme the theme for the templates
*/
public function initializeTemplateEngine (string $theme) : void {
$smarty = new Smarty();
$current_dir = static::getApplicationRootDirectory();
$smarty
->setTemplateDir("$current_dir/skins/$theme")
->setCacheDir($this->config["Content"]["Cache"])
->setCompileDir($this->config["Content"]["Cache"] . "/compiled")
->setConfigDir($current_dir);
$smarty->config_vars += [
"StaticContentURL" => $this->config["StaticContentURL"],
];
$this->templateEngine = $smarty;
}
}
diff --git a/workspaces/src/Engines/Framework/Repository.php b/workspaces/src/Engines/Framework/Repository.php
new file mode 100644
index 0000000..f8ec4c3
--- /dev/null
+++ b/workspaces/src/Engines/Framework/Repository.php
@@ -0,0 +1,41 @@
+<?php
+
+namespace Waystone\Workspaces\Engines\Framework;
+
+use Keruald\Database\DatabaseEngine;
+use Keruald\OmniTools\Collections\HashMap;
+use Keruald\OmniTools\DataTypes\Option\Option;
+
+abstract class Repository {
+
+ ///
+ /// Properties
+ ///
+
+ protected DatabaseEngine $db;
+
+ /**
+ * @var HashMap A map of objects already loaded from the database
+ */
+ protected HashMap $table;
+
+ ///
+ /// Constructor
+ ///
+
+ public function __construct (DatabaseEngine $db) {
+ $this->db = $db;
+ $this->table = new HashMap();
+ }
+
+ ///
+ /// Table
+ ///
+
+ protected function lookupInTable (string $property, string $value) : Option {
+ return $this->table
+ ->filter(fn($item) => $item->$property == $value)
+ ->first();
+ }
+
+}
diff --git a/workspaces/src/Engines/Framework/Resources.php b/workspaces/src/Engines/Framework/Resources.php
index 6412445..ec51b63 100644
--- a/workspaces/src/Engines/Framework/Resources.php
+++ b/workspaces/src/Engines/Framework/Resources.php
@@ -1,50 +1,55 @@
<?php
namespace Waystone\Workspaces\Engines\Framework;
+use Waystone\Workspaces\Engines\Users\UserRepository;
use Waystone\Workspaces\Engines\Workspaces\Workspace;
use Keruald\OmniTools\DataTypes\Option\None;
use Keruald\OmniTools\DataTypes\Option\Option;
use Keruald\OmniTools\DataTypes\Option\Some;
-use User;
use UserGroup;
use InvalidArgumentException;
class Resources {
+ public function __construct (
+ private UserRepository $users,
+ ) {
+ }
+
/**
* @return Option<int>
*/
- public static function resolveID (string $resource_type, string $identifier) : Option {
+ public function resolveID (string $resource_type, string $identifier) : Option {
//Trivial cases: already an ID, null or void ID
if (is_numeric($identifier)) {
return new Some((int)$identifier);
}
if (!$identifier) {
return new None;
}
//Searches identifier
switch ($resource_type) {
case 'U':
- return User::resolveUserID($identifier);
+ return $this->users->resolveUserID($identifier);
case 'G':
$group = UserGroup::fromCode($identifier);
return new Some($group->id);
case 'W':
$workspace = Workspace::fromCode($identifier);
return new Some($workspace->id);
default:
throw new InvalidArgumentException("Unknown resource type: $resource_type", E_USER_ERROR);
}
}
}
diff --git a/workspaces/src/Engines/Framework/Session.php b/workspaces/src/Engines/Framework/Session.php
index 73f6eb0..eacaaf5 100755
--- a/workspaces/src/Engines/Framework/Session.php
+++ b/workspaces/src/Engines/Framework/Session.php
@@ -1,314 +1,325 @@
<?php
/**
* _, __, _, _ __, _ _, _, _
* / \ |_) (_ | | \ | /_\ |\ |
* \ / |_) , ) | |_/ | | | | \|
* ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
*
* Session
*
* This class uses a singleton pattern, as we only need one single instance.
* Cf. http://www.php.net/manual/en/language.oop5.patterns.php
*
* @package ObsidianWorkspaces
* @subpackage Keruald
* @author Sébastien Santoro aka Dereckson <dereckson@espace-win.org>
* @license http://www.opensource.org/licenses/bsd-license.php BSD
* @filesource
*
*/
namespace Waystone\Workspaces\Engines\Framework;
+use Waystone\Workspaces\Engines\Errors\ErrorHandling;
+use Waystone\Workspaces\Engines\Users\UserRepository;
+
use Keruald\Database\DatabaseEngine;
+
use User;
-use Waystone\Workspaces\Engines\Errors\ErrorHandling;
/**
* Session class
*/
class Session {
/**
* @var string session ID
*/
public $id;
/**
* @var string remote client IP
*/
public $ip;
public DatabaseEngine $db;
+ private UserRepository $users;
+
/*
* @var Session current session instance
*/
private static $instance;
/*
* Gets or initializes current session instance
*
* @return Session current session instance
*/
- public static function load (DatabaseEngine $db) {
+ public static function load (
+ DatabaseEngine $db,
+ UserRepository $users,
+ ) {
if (!isset(self::$instance)) {
- self::$instance = new self($db);
+ self::$instance = new self($db, $users);
}
return self::$instance;
}
/**
* Initializes a new instance of Session object
*/
- private function __construct (DatabaseEngine $db) {
+ private function __construct (
+ DatabaseEngine $db,
+ UserRepository $users,
+ ) {
$this->db = $db;
+ $this->users = $users;
//Starts PHP session, and gets id
session_start();
$_SESSION['ID'] = session_id();
$this->id = $_SESSION['ID'];
//Gets remote client IP
$this->ip = self::get_ip();
//Updates or creates the session in database
$this->update();
}
/**
* Gets remote client IP address
*
* @return string IP
*/
public static function get_ip () {
//mod_proxy + mod_rewrite (old pluton url scheme) will define 127.0.0.1
//in REMOTE_ADDR, and will store ip in HTTP_X_FORWARDED_FOR variable.
//Some ISP/orgz proxies also use this setting.
if (array_key_exists('HTTP_X_FORWARDED_FOR', $_SERVER)) {
return $_SERVER['HTTP_X_FORWARDED_FOR'];
}
//Standard cases
return $_SERVER['REMOTE_ADDR'];
}
/**
* Cleans up session
* i. deletes expired session
* ii. sets offline relevant sessions
*/
public function clean_old_sessions () {
global $Config;
$db = $this->db;
//Gets session and online status lifetime (in seconds)
//If not specified in config, sets default 5 and 120 minutes values
$onlineDuration = array_key_exists('OnlineDuration', $Config)
? $Config['OnlineDuration'] : 300;
$sessionDuration = array_key_exists('SessionDuration', $Config)
? $Config['SessionDuration'] : 7200;
$resource = array_key_exists('ResourceID', $Config) ? '\''
. $db->escape($Config['ResourceID'])
. '\''
: 'default';
//Deletes expired sessions
$sql = "DELETE FROM " . TABLE_SESSIONS
. " WHERE session_resource = $resource AND TIMESTAMPDIFF(SECOND, session_updated, NOW()) > $sessionDuration";
if (!$db->query($sql)) {
ErrorHandling::messageAndDie(SQL_ERROR,
"Can't delete expired sessions", '', __LINE__, __FILE__, $sql);
}
//Online -> offline
$sql = "UPDATE " . TABLE_SESSIONS
. " SET session_resource = $resource AND session_online = 0 WHERE TIMESTAMPDIFF(SECOND, session_updated, NOW()) > $onlineDuration";
if (!$db->query($sql)) {
ErrorHandling::messageAndDie(SQL_ERROR,
'Can\'t update sessions online statuses', '', __LINE__,
__FILE__, $sql);
}
}
/**
* Updates or creates a session in the database
*/
public function update () {
global $Config;
$db = $this->db;
//Cleans up session
//To boost SQL performances, try a random trigger
// e.g. if (rand(1, 100) < 3) self::clean_old_sessions();
//or comment this line and execute a cron script you launch each minute.
$this->clean_old_sessions();
//Saves session in database.
//If the session already exists, it updates the field online and updated.
$id = $db->escape($this->id);
$resource = array_key_exists('ResourceID', $Config) ? '\''
. $db->escape($Config['ResourceID'])
. '\''
: 'default';
$user_id = $db->escape(ANONYMOUS_USER);
$sql = "INSERT INTO " . TABLE_SESSIONS
. " (session_id, session_ip, session_resource, user_id) VALUES ('$id', '$this->ip', $resource, '$user_id') ON DUPLICATE KEY UPDATE session_online = 1";
if (!$db->query($sql)) {
ErrorHandling::messageAndDie(SQL_ERROR,
'Can\'t save current session', '', __LINE__, __FILE__, $sql);
}
}
/**
* Gets the number of online users
*
* @return int the online users count
*/
public function count_online () {
//Keeps result for later method call
static $count = -1;
if ($count == -1) {
//Queries sessions table
global $Config;
$db = $this->db;
$resource = array_key_exists('ResourceID', $Config) ? '\''
. $db->escape($Config['ResourceID'])
. '\''
: 'default';
$sql = "SELECT count(*) FROM " . TABLE_SESSIONS
. " WHERE session_resource = $resource AND session_online = 1";
$count =
(int)$db->queryScalar($sql, "Can't count online users");
}
//Returns number of users online
return $count;
}
/**
* Gets the value of a custom session table field
*
* @param string $info the field to get
*
* @return string the session specified field's value
*/
public function get_info ($info) {
$db = $this->db;
$id = $db->escape($this->id);
$sql = "SELECT `$info` FROM " . TABLE_SESSIONS
. " WHERE session_id = '$id'";
return $db->queryScalar($sql, "Can't get session $info info");
}
/**
* Sets the value of a custom session table field to the specified value
*
* @param string $info the field to update
* @param string $value the value to set
*/
public function set_info ($info, $value) {
$db = $this->db;
$value =
($value === null) ? 'NULL' : "'" . $db->escape($value) . "'";
$id = $db->escape($this->id);
$sql = "UPDATE " . TABLE_SESSIONS
. " SET `$info` = $value WHERE session_id = '$id'";
if (!$db->query($sql)) {
ErrorHandling::messageAndDie(SQL_ERROR,
"Can't set session $info info", '', __LINE__, __FILE__, $sql);
}
}
/**
* Gets logged user information
*
* @return User the logged user information
*/
public function get_logged_user () {
$db = $this->db;
//Gets session information
$id = $db->escape($this->id);
$sql = "SELECT * FROM " . TABLE_SESSIONS . " WHERE session_id = '$id'";
if (!$result = $db->query($sql)) {
ErrorHandling::messageAndDie(SQL_ERROR,
"Can't query session information", '', __LINE__, __FILE__,
$sql);
}
$row = $db->fetchRow($result);
//Gets user instance
require_once('includes/objects/user.php');
- $user = new User($row['user_id']);
+ $user = new User($row['user_id'], $db);
//Adds session property to this user instance
$user->session = $row;
//Returns user instance
return $user;
}
/**
* Cleans session
*
* This method is to be called when an event implies a session destroy
*/
public function clean () {
//Destroys $_SESSION array values, help ID
foreach ($_SESSION as $key => $value) {
if ($key != 'ID') {
unset($_SESSION[$key]);
}
}
}
/**
* Updates the session in a user login context
*
* @param string $user_id the user ID
*/
public function user_login ($user_id) {
$db = $this->db;
//Sets specified user ID in sessions table
$user_id = $db->escape($user_id);
$id = $db->escape($this->id);
$sql = "UPDATE " . TABLE_SESSIONS
. " SET user_id = '$user_id' WHERE session_id = '$id'";
if (!$db->query($sql)) {
ErrorHandling::messageAndDie(SQL_ERROR,
"Can't set logged in status", '', __LINE__, __FILE__, $sql);
}
}
/**
* Updates the session in a user logout context
*/
public function user_logout () {
$db = $this->db;
//Sets anonymous user in sessions table
$user_id = $db->escape(ANONYMOUS_USER);
$id = $db->escape($this->id);
$sql = "UPDATE " . TABLE_SESSIONS
. " SET user_id = '$user_id' WHERE session_id = '$id'";
if (!$db->query($sql)) {
ErrorHandling::messageAndDie(SQL_ERROR,
"Can't set logged out status", '', __LINE__, __FILE__, $sql);
}
//Cleans session
$this->clean();
}
}
-
diff --git a/workspaces/src/Engines/Users/UserRepository.php b/workspaces/src/Engines/Users/UserRepository.php
new file mode 100644
index 0000000..d3315fc
--- /dev/null
+++ b/workspaces/src/Engines/Users/UserRepository.php
@@ -0,0 +1,134 @@
+<?php
+
+namespace Waystone\Workspaces\Engines\Users;
+
+use Waystone\Workspaces\Engines\Errors\ErrorHandling;
+use Waystone\Workspaces\Engines\Framework\Repository;
+
+use Keruald\Database\Exceptions\SqlException;
+use Keruald\OmniTools\DataTypes\Option\None;
+use Keruald\OmniTools\DataTypes\Option\Option;
+use Keruald\OmniTools\DataTypes\Option\Some;
+
+use User;
+
+class UserRepository extends Repository {
+
+ ///
+ /// Find user in database
+ ///
+
+ public function resolveUserID (string $expression) : Option {
+ return $this->getUserFromUsername($expression)
+ ->orElse(fn() => $this->getUserFromEmail($expression))
+ ->map(fn($user) => $user->id);
+ }
+
+ /**
+ * @return Option<User>
+ */
+ private function getByProperty (string $property, mixed $value) : Option {
+ $value = $this->db->escape($value);
+ $sql = "SELECT * FROM " . TABLE_USERS . " WHERE $property = '$value'";
+ if (!$result = $this->db->query($sql)) {
+ ErrorHandling::messageAndDie(SQL_ERROR, "Can't get user", '', __LINE__, __FILE__, $sql);
+ }
+
+ $row = $this->db->fetchRow($result);
+
+ if (!$row) {
+ return new None;
+ }
+
+ $user = new User(null, $this->db);
+ $user->load_from_row($row);
+ $this->table[$user->id] = $user;
+
+ return new Some($user);
+ }
+
+ /**
+ * Gets user from specified e-mail
+ *
+ * @return Option<User> the user matching the specified e-mail; None, if the mail were not found.
+ */
+ public function getUserFromEmail (string $mail) : Option {
+ return $this->lookupInTable("email", $mail)
+ ->orElse(fn () => $this->getByProperty("user_email", $mail));
+ }
+
+ /**
+ * @return Option<User>
+ */
+ public function getUserFromUsername (string $username) : Option {
+ return $this->lookupInTable("name", $username)
+ ->orElse(fn () => $this->getByProperty("username", $username));
+ }
+
+ /**
+ * Gets user from remote identity provider identifiant
+ *
+ * @param string $authType The authentication method type
+ * @param string $remoteUserId The remote user identifier
+ * @return Option<User> the user matching the specified identity provider and identifiant; None if no user were found.
+ */
+ public function getUserFromRemoteIdentity (string $authType, string $remoteUserId) : Option {
+ $authType = $this->db->escape($authType);
+ $remoteUserId = $this->db->escape($remoteUserId);
+ $sql = "SELECT user_id FROM " . TABLE_USERS_AUTH . " WHERE "
+ . "auth_type = '$authType' AND auth_identity = '$remoteUserId'";
+
+ try {
+ $result = $this->db->queryScalar($sql);
+ } catch (SqlException $ex) {
+ ErrorHandling::messageAndDie(SQL_ERROR, $ex->getMessage(), "Can't get user", __LINE__, __FILE__, $sql);
+ }
+
+ return Option::from($result)
+ ->map(fn($user_id) => $this->get($user_id));
+ }
+
+ ///
+ /// Registration facilities
+ ///
+
+ /**
+ * Checks if a username is still available
+ */
+ public function isAvailableUsername (string $login) : bool {
+ $login = $this->db->escape($login);
+
+ $sql = "SELECT COUNT(*) FROM " . TABLE_USERS
+ . " WHERE username = '$login'";
+
+ try {
+ $result = $this->db->queryScalar($sql);
+ } catch (SqlException $ex) {
+ ErrorHandling::messageAndDie(SQL_ERROR, "Can't check if the specified login is available", '', __LINE__, __FILE__, $sql);
+ }
+
+ return $result == 0;
+ }
+
+ ///
+ /// Load object
+ ///
+
+ /**
+ * Gets an instance of the class from the table or loads it from database.
+ *
+ * @param int $id the user ID
+ * @return User the user instance
+ */
+ public function get (int $id) : User {
+ if ($this->table->has($id)) {
+ return $this->table[$id];
+ }
+
+ $user = new User($id, $this->db);
+ $this->table[$id] = $user;
+
+ return $user;
+ }
+
+}
diff --git a/workspaces/src/Engines/Workspaces/Workspace.php b/workspaces/src/Engines/Workspaces/Workspace.php
index b59a201..a68b265 100644
--- a/workspaces/src/Engines/Workspaces/Workspace.php
+++ b/workspaces/src/Engines/Workspaces/Workspace.php
@@ -1,280 +1,280 @@
<?php
/**
* _, __, _, _ __, _ _, _, _
* / \ |_) (_ | | \ | /_\ |\ |
* \ / |_) , ) | |_/ | | | | \|
* ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
*
* Workspace class
*
* @package ObsidianWorkspaces
* @subpackage Workspaces
* @author Sébastien Santoro aka Dereckson <dereckson@espace-win.org>
* @license http://www.opensource.org/licenses/bsd-license.php BSD
* @filesource
*/
namespace Waystone\Workspaces\Engines\Workspaces;
use Waystone\Workspaces\Engines\Errors\ErrorHandling;
use Waystone\Workspaces\Engines\Framework\Context;
use Cache;
use Language;
use User;
use Exception;
use LogicException;
/**
* Workspace class
*
* This class maps the workspaces table.
*/
class Workspace {
public $id;
public $code;
public $name;
public $created;
public $description;
/**
* @var WorkspaceConfiguration The workspace configuration
*/
public $configuration;
/**
* Initializes a new instance
*
* @param int $id the primary key
*/
function __construct ($id = null) {
if ($id) {
$this->id = $id;
$this->load_from_database();
}
}
/**
* Loads the object Workspace (ie fill the properties) from the $_POST array
*/
function load_from_form () {
if (array_key_exists('code', $_POST)) {
$this->code = $_POST['code'];
}
if (array_key_exists('name', $_POST)) {
$this->name = $_POST['name'];
}
if (array_key_exists('created', $_POST)) {
$this->created = $_POST['created'];
}
if (array_key_exists('description', $_POST)) {
$this->description = $_POST['description'];
}
}
/**
* Loads the object zone (ie fill the properties) from the $row array
*/
function load_from_row ($row) {
$this->id = $row['workspace_id'];
$this->code = $row['workspace_code'];
$this->name = $row['workspace_name'];
$this->created = $row['workspace_created'];
$this->description = $row['workspace_description'];
}
/**
* Loads the specified workspace from code
*
* @param string $code The workspace code
*
* @return Workspace The specified workspace instance
*/
public static function fromCode ($code) {
global $db;
$code = $db->escape($code);
$sql = "SELECT * FROM " . TABLE_WORKSPACES . " WHERE workspace_code = '"
. $code . "'";
if (!$result = $db->query($sql)) {
ErrorHandling::messageAndDie(SQL_ERROR,
"Unable to query workspaces", '', __LINE__, __FILE__, $sql);
}
if (!$row = $db->fetchRow($result)) {
throw new Exception("Workspace unknown: " . $code);
}
$workspace = new Workspace();
$workspace->load_from_row($row);
return $workspace;
}
/**
* Loads the object Workspace (ie fill the properties) from the database
*/
function load_from_database () {
global $db;
$id = $db->escape($this->id);
$sql = "SELECT * FROM " . TABLE_WORKSPACES . " WHERE workspace_id = '"
. $id . "'";
if (!$result = $db->query($sql)) {
ErrorHandling::messageAndDie(SQL_ERROR,
"Unable to query workspaces", '', __LINE__, __FILE__, $sql);
}
if (!$row = $db->fetchRow($result)) {
$this->lastError = "Workspace unknown: " . $this->id;
return false;
}
$this->load_from_row($row);
return true;
}
/**
* Saves to database
*/
function save_to_database () {
global $db;
$id = $this->id ? "'" . $db->escape($this->id) . "'" : 'NULL';
$code = $db->escape($this->code);
$name = $db->escape($this->name);
$created = $db->escape($this->created);
$description = $db->escape($this->description);
//Updates or inserts
$sql = "REPLACE INTO " . TABLE_WORKSPACES
. " (`workspace_id`, `workspace_code`, `workspace_name`, `workspace_created`, `workspace_description`) VALUES ('$id', '$code', '$name', '$created', '$description')";
if (!$db->query($sql)) {
ErrorHandling::messageAndDie(SQL_ERROR, "Unable to save", '',
__LINE__, __FILE__, $sql);
}
if (!$this->id) {
//Gets new record id value
$this->id = $db->nextId();
}
}
/**
* Determines if the specified user has access to the current workspace
*
* @param User $user The user to check
*
* @return boolean true if the user has access to the current workspace ;
* otherwise, false.
*/
public function userCanAccess (User $user) {
if ($this->id === false || $this->id === null || $this->id === '') {
throw new LogicException("The workspace must has a valid id before to call userCanAccess.");
}
foreach ($user->get_workspaces() as $workspace) {
if ($workspace->id == $this->id) {
return true;
}
}
return false;
}
/**
* Loads configuration
*
* @param Context $context The site context
*/
public function loadConfiguration (Context $context) {
global $Config;
$dir = $Config['Content']['Workspaces'] . '/' . $this->code;
// Load JSON configuration file
$file = $dir . '/workspace.conf';
if (file_exists($file)) {
$this->configuration =
WorkspaceConfiguration::loadFromFile($file, $context);
return;
}
// Load YAML configuration file
$file = $dir . '/workspace.yml';
if (file_exists($file)) {
$this->configuration =
WorkspaceConfiguration::loadFromYamlFile($file, $context);
return;
}
$exceptionMessage =
sprintf(Language::get('NotConfiguredWorkspace'), $file);
throw new Exception($exceptionMessage);
}
/**
* Gets workspaces specified user has access to.
*
* @param int $user_id The user to get his workspaces
*
* @return Workspace[] A list of workspaces
*/
public static function get_user_workspaces ($user_id) {
global $db;
//Gets the workspaces list from cache, as this complex request could take 100ms
//and is called on every page.
$cache = Cache::load();
if (!$workspaces = unserialize($cache->get("workspaces-$user_id"))) {
- $clause = User::get_permissions_clause_from_user_id($user_id);
+ $clause = User::get_permissions_clause_from_user_id($user_id, $db);
$sql = "SELECT DISTINCT w.*
FROM " . TABLE_PERMISSIONS . " p, " . TABLE_WORKSPACES . " w
WHERE p.target_resource_type = 'W' AND
p.target_resource_id = w.workspace_id AND
p.permission_name = 'accessLevel' AND
p.permission_flag > 0 AND
($clause)";
if (!$result = $db->query($sql)) {
ErrorHandling::messageAndDie(SQL_ERROR,
"Can't get user workspaces", '', __LINE__, __FILE__, $sql);
}
$workspaces = [];
while ($row = $db->fetchRow($result)) {
$workspace = new Workspace();
$workspace->id = $row['workspace_id'];
$workspace->load_from_row($row);
$workspaces[] = $workspace;
}
$cache->set("workspaces-$user_id", serialize($workspaces));
}
return $workspaces;
}
/**
* Determines if a string matches an existing workspace code.
*
* @param string $code The workspace code to check
*
* @return boolean If the specified code matches an existing workspace,
* true; otherwise, false.
*/
public static function is_workspace ($code) {
global $db;
$code = $db->escape($code);
$sql = "SELECT count(*) FROM " . TABLE_WORKSPACES
. " WHERE workspace_code = '$code'";
if (!$result = $db->query($sql)) {
ErrorHandling::messageAndDie(SQL_ERROR,
"Can't check workspace code", '', __LINE__, __FILE__, $sql);
}
$row = $db->fetchRow($result);
return ($row[0] == 1);
}
}
diff --git a/workspaces/src/Engines/Workspaces/WorkspaceConfiguration.php b/workspaces/src/Engines/Workspaces/WorkspaceConfiguration.php
index 117c5d6..92022cf 100644
--- a/workspaces/src/Engines/Workspaces/WorkspaceConfiguration.php
+++ b/workspaces/src/Engines/Workspaces/WorkspaceConfiguration.php
@@ -1,269 +1,269 @@
<?php
/**
* _, __, _, _ __, _ _, _, _
* / \ |_) (_ | | \ | /_\ |\ |
* \ / |_) , ) | |_/ | | | | \|
* ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
*
* Workspace configuration class
*
* @package ObsidianWorkspaces
* @subpackage Workspaces
* @author Sébastien Santoro aka Dereckson <dereckson@espace-win.org>
* @license http://www.opensource.org/licenses/bsd-license.php BSD
* @filesource
*/
namespace Waystone\Workspaces\Engines\Workspaces;
use Exception;
use Keruald\Yaml\Parser as YamlParser;
use Keruald\Yaml\Tags\EnvTag;
use Waystone\Workspaces\Engines\Apps\ApplicationConfiguration;
use Waystone\Workspaces\Engines\Auth\AuthenticationMethod;
use Waystone\Workspaces\Engines\Exceptions\WorkspaceException;
use Waystone\Workspaces\Engines\Framework\Context;
use Waystone\Workspaces\Engines\Serialization\ArrayDeserializableWithContext;
/**
* Workspace configuration class
*
* This class maps the workspaces table.
*/
class WorkspaceConfiguration implements ArrayDeserializableWithContext {
/**
* @var array applications (each element is an instance of
* ApplicationConfiguration)
*/
public $applications = [];
/**
* @var array authentication methods for this workspace (each element is an
* instance of AuthenticationMethod)
*/
public $authenticationMethods = [];
/**
* @var array disclaimers (each element a string)
*/
public $disclaimers = [];
/**
* @var array collections (each key a string to the collection name, each
* value a string to the collection document type)
*/
public $collections = [];
/**
* Determines if internal Obsidian Workspaces authentication can be used to
* login on this workspace URL
*
* @return boolean True if a user not logged in Obsidian Workspaces going
* to a workspace URL should be offered to login through
* Obsidian ; otherwise, false.
*/
public $allowInternalAuthentication = true;
/**
* @var string The overall custom header to prepend to the header site
*/
public $header = '';
/**
* @var string The overall custom footer to append to the footer site
*/
public $footer = '';
/**
* Get applications controllers binds for this workspace
*/
public function getControllersBinds () {
$controllers = [];
foreach ($this->applications as $application) {
$controllers[$application->bind] = $application;
}
return $controllers;
}
/**
* Determines if the URL fragment matches a controller bound to it.
*
* @param ApplicationConfiguration $applicationConfiguration The
* application
* configuration
*
* @return boolean true if the URL fragment matches an application
* controller's bind
*/
public function hasControllerBind ($url, &$applicationConfiguration) {
foreach ($this->applications as $application) {
if ($application->bind == $url) {
$applicationConfiguration = $application;
return true;
}
}
return false;
}
/**
* Loads a WorkspaceConfiguration instance from an array
*
* @param array $data The array to deserialize
* @param mixed $context The application context
*
* @return WorkspaceConfiguration The deserialized instance
* @throws WorkspaceException
*/
public static function loadFromArray (
array $data,
mixed $context
) : self {
$instance = new WorkspaceConfiguration();
// Parse applications to load in the workspace
$applications = $data["applications"] ?? [];
foreach ($applications as $applicationData) {
if (!array_key_exists("name", $applicationData)) {
throw new WorkspaceException("Missing required property: application name");
}
$controllerClass = $applicationData["name"];
if (!class_exists($controllerClass)) {
trigger_error("Application controller doesn't exist: $controllerClass.",
E_USER_WARNING);
continue;
}
$configurationClass = $controllerClass . "Configuration";
if (!class_exists($configurationClass)) {
$configurationClass = ApplicationConfiguration::class;
}
$instance->applications[] = [$configurationClass, "loadFromArray"]($applicationData);
}
// Parse custom authentication methods for this workspace
if (array_key_exists("login", $data)) {
$instance->allowInternalAuthentication = false;
foreach ($data["login"] as $authData) {
if ($authData["type"] == "internal") {
$instance->allowInternalAuthentication = true;
continue;
}
$auth = self::loadAuthenticationMethod($authData, $context);
$instance->authenticationMethods[] = $auth;
}
}
// Parse collections the workspace applications can access
$collections = $data->collections ?? [];
foreach ($collections as $collection) {
if (!property_exists($collection, 'name')) {
throw new WorkspaceException("A collection has been declared without name in the workspace configuration.");
}
$name = $collection->name;
if (!property_exists($collection, 'global')
|| !$collection->global) {
$name =
WorkspaceConfiguration::getCollectionNameWithPrefix($context->workspace,
$name);
}
if (property_exists($collection, 'documentType')) {
$type = $collection->documentType;
if (!class_exists($type)) {
throw new WorkspaceException("CollectionDocument children class doesn't exist: $type. If you've just added authentication code, update includes/autoload.php file to register your new classes.");
}
} else {
$type = null;
}
$instance->collections[$name] = $type;
}
// Customization
$instance->disclaimers = $data->disclaimers ?? [];
$instance->header = $data["header"] ?? "";
$instance->footer = $data["footer"] ?? "";
return $instance;
}
private static function loadAuthenticationMethod (
array $authData,
Context $context,
) : AuthenticationMethod {
if (!array_key_exists("type", $authData)) {
throw new WorkspaceException("Missing required property: login type");
}
$class = $authData["type"];
if (!class_exists($class)) {
throw new WorkspaceException("Authentication method doesn't exist: $class.");
}
try {
- $authenticationMethod = $class::loadFromArray($authData);
+ $authenticationMethod = $class::loadFromArray($authData, $context);
$authenticationMethod->context = $context;
} catch (Exception $ex) {
throw new WorkspaceException(
"Can't load authentication method: " . $ex->getMessage(), 0, $ex
);
}
return $authenticationMethod;
}
/**
* Gets the full name of a collection, with the workspace prefix
*
* @param Workspace $workspace The current workspace
* @param string $name The collection name
*
* @return string The full name of the collection
*/
public static function getCollectionNameWithPrefix (
Workspace $workspace,
string $name
) {
return $workspace->code . '-' . $name;
}
/**
* Loads a WorkspaceConfiguration instance deserializing a JSON file
*/
public static function loadFromFile ($file, $context) {
$object = json_decode(file_get_contents($file), true);
if ($object === null) {
throw new Exception("Can't parse configuration file: "
. json_last_error_msg());
}
return self::loadFromArray($object, $context);
}
/**
* @throws WorkspaceException
*/
public static function loadFromYamlFile (
string $file,
Context $context
) : self {
$parser = new YamlParser();
$parser->withTagClass(EnvTag::class);
try {
$value = $parser->parseFile($file);
}
catch (Exception $ex) {
throw new WorkspaceException("Can't parse configuration file: "
. $ex->getMessage(), 0, $ex);
}
return self::loadFromArray($value, $context);
}
}
diff --git a/workspaces/src/includes/objects/user.php b/workspaces/src/includes/objects/user.php
index cdfb665..21e5202 100755
--- a/workspaces/src/includes/objects/user.php
+++ b/workspaces/src/includes/objects/user.php
@@ -1,445 +1,343 @@
<?php
/**
* _, __, _, _ __, _ _, _, _
* / \ |_) (_ | | \ | /_\ |\ |
* \ / |_) , ) | |_/ | | | | \|
* ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
*
* User class
*
* @package ObsidianWorkspaces
* @subpackage Model
* @author Sébastien Santoro aka Dereckson <dereckson@espace-win.org>
* @license http://www.opensource.org/licenses/bsd-license.php BSD
* @filesource
*
*/
use Waystone\Workspaces\Engines\Errors\ErrorHandling;
use Waystone\Workspaces\Engines\Workspaces\Workspace;
-use Keruald\OmniTools\DataTypes\Option\None;
-use Keruald\OmniTools\DataTypes\Option\Option;
-use Keruald\OmniTools\DataTypes\Option\Some;
+use Keruald\Database\DatabaseEngine;
/**
* User class
*/
class User {
- public $id;
+ public ?int $id;
public $name;
public $password;
public $active = 0;
public $email;
public $regdate;
public array $session = [];
public string $lastError;
- /**
- * @var Array An array of users already loaded, the username as user id
- */
- public static $hashtableById = [];
-
/**
* @var array|null An array of the workspaces the user has access to, each element an instance of the Workspace object. As long as the field hasn't been initialized by get_workspaces, null.
*/
private $workspaces = null;
+ private DatabaseEngine $db;
+
/*
* Initializes a new instance
*
* @param int $id the primary key
*/
- function __construct ($id = null) {
+ function __construct ($id = null, DatabaseEngine $db = null) {
+ $this->id = $id;
+ $this->db = $db;
+
if ($id) {
- $this->id = $id;
$this->load_from_database();
}
}
- /**
- * Initializes a new User instance if needed or get already available one.
- *
- * @param iint $id the user ID
- * @return User the user instance
- */
- static function get ($id = NULL) {
- if ($id && array_key_exists($id, User::$hashtableById)) {
- return self::$hashtableById[$id];
- }
-
- $user = new self($id);
- return $user;
- }
-
/**
* Loads the object User (ie fill the properties) from the $_POST array
*/
function load_from_form () {
if (array_key_exists('name', $_POST)) $this->name = $_POST['name'];
if (array_key_exists('password', $_POST)) $this->password = $_POST['password'];
if (array_key_exists('active', $_POST)) $this->active = $_POST['active'];
if (array_key_exists('actkey', $_POST)) $this->actkey = $_POST['actkey'];
if (array_key_exists('email', $_POST)) $this->email = $_POST['email'];
if (array_key_exists('regdate', $_POST)) $this->regdate = $_POST['regdate'];
}
/**
* Loads the object User (ie fill the properties) from the database
*/
function load_from_database () {
- global $db;
- $sql = "SELECT * FROM " . TABLE_USERS . " WHERE user_id = '" . $this->id . "'";
+ $db = $this->db;
+
+ $id = $this->db->escape($this->id);
+ $sql = "SELECT * FROM " . TABLE_USERS . " WHERE user_id = '" . $id . "'";
if ( !($result = $db->query($sql)) ) ErrorHandling::messageAndDie(SQL_ERROR, "Unable to query users", '', __LINE__, __FILE__, $sql);
if (!$row = $db->fetchRow($result)) {
$this->lastError = "User unknown: " . $this->id;
return false;
}
$this->load_from_row($row);
return true;
}
/**
* Loads the object User (ie fill the properties) from the database row
*/
function load_from_row ($row) {
$this->id = $row['user_id'];
$this->name = $row['username'];
$this->password = $row['user_password'];
$this->active = $row['user_active'] ? true : false;
$this->email = $row['user_email'];
$this->regdate = $row['user_regdate'];
-
- //Puts object in hashtable, so it's accessible in future call of
- //this run through User::get($id).
- self::$hashtableById[$this->id] = $this;
- }
-
- private static function fromRow (array $row) : User {
- $user = new User();
- $user->load_from_row($row);
-
- return $user;
}
/**
* Saves to database
*/
function save_to_database () {
- global $db;
+ $db = $this->db;
$id = $this->id ? "'" . $db->escape($this->id) . "'" : 'NULL';
$name = $db->escape($this->name);
$password = $db->escape($this->password);
$active = $this->active ? 1 : 0;
$email = $db->escape($this->email);
$regdate = $this->regdate ? "'" . $db->escape($this->regdate) . "'" : 'NULL';
//Updates or inserts
$sql = "REPLACE INTO " . TABLE_USERS . " (`user_id`, `username`, `user_password`, `user_active`, `user_email`, `user_regdate`) VALUES ($id, '$name', '$password', $active, '$email', $regdate)";
if (!$db->query($sql)) {
ErrorHandling::messageAndDie(SQL_ERROR, "Unable to save user", '', __LINE__, __FILE__, $sql);
}
if (!$this->id) {
//Gets new record id value
$this->id = $db->nextId();
}
}
/**
* Updates the specified field in the database record
*/
function save_field ($field) {
- global $db;
+ $db = $this->db;
+
if (!$this->id) {
ErrorHandling::messageAndDie(GENERAL_ERROR, "You're trying to update a record not yet saved in the database");
}
$id = $db->escape($this->id);
$value = $db->escape($this->$field);
$sql = "UPDATE " . TABLE_USERS . " SET `$field` = '$value' WHERE user_id = '$id'";
if (!$db->query($sql)) {
ErrorHandling::messageAndDie(SQL_ERROR, "Unable to save $field field", '', __LINE__, __FILE__, $sql);
}
}
//
// USER MANAGEMENT FUNCTIONS
//
/**
* Generates a unique user id
*/
function generate_id () {
- global $db;
+ $db = $this->db;
do {
$this->id = mt_rand(2001, 9999);
$sql = "SELECT COUNT(*) FROM " . TABLE_USERS . " WHERE user_id = $this->id";
if (!$result = $db->query($sql)) {
ErrorHandling::messageAndDie(SQL_ERROR, "Can't check if a user id is free", '', __LINE__, __FILE__, $sql);
}
$row = $db->fetchRow($result);
} while ($row[0]);
}
/**
* Fills password field with encrypted version
* of the specified clear password
*/
public function set_password ($newpassword) {
$this->password = md5($newpassword);
}
- /**
- * Checks if a login is available
- *
- * @param string $login the login to check
- * @return boolean true if the login is available; otherwise, false.
- */
- public static function is_available_login ($login) {
- global $db;
- $sql = "SELECT COUNT(*) FROM " . TABLE_USERS . " WHERE username = '$login'";
- if (!$result = $db->query($sql)) {
- ErrorHandling::messageAndDie(SQL_ERROR, "Can't check if the specified login is available", '', __LINE__, __FILE__, $sql);
- }
- $row = $db->fetchRow($result);
- return ($row[0] == 0);
- }
-
/**
* Initializes a new User instance ready to have its property filled
*
* @return User the new user instance
*/
- public static function create () {
- $user = new User();
+ public static function create (DatabaseEngine $db) {
+ $user = new User(null, $db);
$user->generate_id();
$user->active = true;
$user->regdate = time();
return $user;
}
- /**
- * @return Option<User>
- */
- private static function getByProperty ($property, $value) : Option {
- global $db;
-
- $value = $db->escape($value);
- $sql = "SELECT * FROM " . TABLE_USERS . " WHERE $property = '$value'";
- if (!$result = $db->query($sql)) {
- ErrorHandling::messageAndDie(SQL_ERROR, "Can't get user", '', __LINE__, __FILE__, $sql);
- }
-
- if ($row = $db->fetchRow($result)) {
- return new Some(User::fromRow($row));
- }
-
- return new None;
- }
-
- /**
- * Gets user from specified e-mail
- *
- * @return Option<User> the user matching the specified e-mail; None, if the mail were not found.
- */
- public static function get_user_from_email ($mail) : Option {
- return self::getByProperty("user_email", $mail);
- }
-
- public static function get_user_from_username ($username) : Option {
- return self::getByProperty("username", $username);
- }
-
- public static function resolveUserID ($expression) : Option {
- return self::get_user_from_username($expression)
- ->orElse(self::get_user_from_email($expression))
- ->map(fn($user) => $user->id);
- }
-
//
// REMOTE IDENTITY PROVIDERS
//
- /**
- * Gets user from remote identity provider identifiant
- *
- * @param $authType The authentication method type
- * @param $remoteUserId The remote user identifier
- * @return User the user matching the specified identity provider and identifiant; null if no user were found.
- */
- public static function getUserFromRemoteIdentity ($authType, $remoteUserId) {
- global $db;
-
- $authType = $db->escape($authType);
- $remoteUserId = $db->escape($remoteUserId);
- $sql = "SELECT user_id FROM " . TABLE_USERS_AUTH . " WHERE "
- . "auth_type = '$authType' AND auth_identity = '$remoteUserId'";
- if (!$result = $db->query($sql)) {
- ErrorHandling::messageAndDie(SQL_ERROR, "Can't get user", '', __LINE__, __FILE__, $sql);
- }
-
- if ($row = $db->fetchRow($result)) {
- return User::get($row['user_id']);
- }
-
- return null;
- }
-
/**
* Sets user's remote identity provider identifiant
*
* @param $authType The authentication method type
* @param $remoteUserId The remote user identifier
* */
public function setRemoteIdentity ($authType, $remoteUserId, $properties = null) {
- global $db;
+ $db = $this->db;
+
$authType = $db->escape($authType);
$remoteUserId = $db->escape($remoteUserId);
$properties = ($properties === NULL) ? 'NULL' : "'" . $db->escape($properties) . "'";
$sql = "INSERT INTO " . TABLE_USERS_AUTH . " (auth_type, auth_identity, auth_properties, user_id) "
. "VALUES ('$authType', '$remoteUserId', $properties, $this->id)";
if (!$db->query($sql)) {
ErrorHandling::messageAndDie(SQL_ERROR, "Can't set user remote identity provider information", '', __LINE__, __FILE__, $sql);
}
}
//
// INTERACTION WITH OTHER OBJECTS
//
/**
* Gets the groups where the current user has access to.
*
* @return array an array containing group_id, matching groups the current user has access to.
*/
public function get_groups () {
- return self::get_groups_from_user_id($this->id);
+ return self::get_groups_from_user_id($this->id, $this->db);
}
/**
* Determines if the user is a member of the specified group
*
* @param UserGroup $group The group to check
*/
public function isMemberOfGroup (UserGroup $group) {
- global $db;
+ $db = $this->db;
+
$sql = "SELECT count(*) FROM users_groups_members WHERE group_id = $group->id AND user_id = $this->id";
if (!$result = $db->query($sql)) {
ErrorHandling::messageAndDie(SQL_ERROR, "Can't determine if the user belongs to the group", '', __LINE__, __FILE__, $sql);
}
$row = $db->fetchRow($result);
return $row[0] == 1;
}
/**
* Adds user to the specified group
*
* @param UserGroup $group The group where to add the user
* @parap boolean $isAdmin if true, set the user admin; otherwise, set it regular user.
*/
public function addToGroup (UserGroup $group, $isAdmin = false) {
- global $db;
+ $db = $this->db;
+
$isAdmin = $isAdmin ? 1 : 0;
$sql = "REPLACE INTO users_groups_members VALUES ($group->id, $this->id, $isAdmin)";
if (!$db->query($sql)) {
ErrorHandling::messageAndDie(SQL_ERROR, "Can't add user to group", '', __LINE__, __FILE__, $sql);
}
}
/**
* Gets the SQL permission clause to select resources where the user is the subject.
*
* @return string The SQL WHERE clause
*/
public function get_permissions_clause () {
- return self::get_permissions_clause_from_user_id($this->id);
+ return self::get_permissions_clause_from_user_id($this->id, $this->db);
}
/**
* Gets workspaces this user has access to.
*
* @return Array A list of workspaces
*/
public function get_workspaces () {
if ($this->workspaces === null) {
$this->workspaces = Workspace::get_user_workspaces($this->id);
}
return $this->workspaces;
}
/**
* Sets user permission
*
* @param string $resourceType The target resource type
* @param int $resourceId The target resource ID
* @param string $permissionName The permission name
* @param int $permissionFlag The permission flag (facultative; by default, 1)
*/
public function setPermission ($resourceType, $resourceId, $permissionName, $permissionFlag = 1) {
- global $db;
+ $db = $this->db;
+
$resourceType = $db->escape($resourceType);
if (!is_numeric($resourceId)) {
throw new Exception("Resource ID must be a positive or null integer, and not $resourceId.");
}
$permissionName = $db->escape($permissionName);
if (!is_numeric($permissionFlag)) {
throw new Exception("Permission flag must be a positive or null integer, and not $permissionFlag.");
}
$sql = "REPLACE INTO permissions
(subject_resource_type, subject_resource_id,
target_resource_type, target_resource_id,
permission_name, permission_flag)
VALUES
('U', $this->id,
'$resourceType', $resourceId,
'$permissionName', $permissionFlag)";
if (!$db->query($sql)) {
ErrorHandling::messageAndDie(SQL_ERROR, "Can't set user permission", '', __LINE__, __FILE__, $sql);
}
}
/**
* Gets the groups where a user has access to.
*
* @param int $user_id the user to get the groups list
* @return array an array containing group_id, matching groups the specified user has access to.
*/
- public static function get_groups_from_user_id ($user_id) {
- global $db;
+ public static function get_groups_from_user_id ($user_id, DatabaseEngine $db) {
$sql = "SELECT group_id FROM " . TABLE_UGROUPS_MEMBERS . " WHERE user_id = " . $user_id;
if (!$result = $db->query($sql)) {
ErrorHandling::messageAndDie(SQL_ERROR, "Can't get user groups", '', __LINE__, __FILE__, $sql);
}
$gids = array();
while ($row = $db->fetchRow($result)) {
$gids[] = $row['group_id'];
}
return $gids;
}
/**
* Gets the SQL permission clause to select resources where the specified user is the subject.
*
* @param $user_id The user ID
* @return string The SQL WHERE clause
*/
- public static function get_permissions_clause_from_user_id ($user_id) {
+ public static function get_permissions_clause_from_user_id ($user_id, DatabaseEngine $db) {
$clause = "subject_resource_type = 'U' AND subject_resource_id = $user_id";
- if ($groups = self::get_groups_from_user_id ($user_id)) {
+ if ($groups = self::get_groups_from_user_id ($user_id, $db)) {
$clause = "($clause) OR (subject_resource_type = 'G' AND subject_resource_id = ";
$clause .= join(") OR (subject_resource_type = 'G' AND subject_resource_id = ", $groups);
$clause .= ')';
}
return $clause;
}
}

File Metadata

Mime Type
text/x-diff
Expires
Wed, Mar 18, 12:48 (1 d, 17 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
3539816
Default Alt Text
(78 KB)

Event Timeline