diff options
author | Marvin Borner | 2018-06-08 20:03:25 +0200 |
---|---|---|
committer | Marvin Borner | 2018-06-08 20:03:25 +0200 |
commit | 92b7dd3335a6572debeacfb5faa82c63a5e67888 (patch) | |
tree | 7ebbca22595d542ec5e2912a24a0400ac8f6b113 /main/app/sprinkles/account/src | |
parent | 22a1bb27f94ea33042b0bdd35bef1a5cfa96cc0d (diff) |
Some minor fixes
Diffstat (limited to 'main/app/sprinkles/account/src')
47 files changed, 5704 insertions, 5704 deletions
diff --git a/main/app/sprinkles/account/src/Account.php b/main/app/sprinkles/account/src/Account.php index 9f43166..1faccf4 100644 --- a/main/app/sprinkles/account/src/Account.php +++ b/main/app/sprinkles/account/src/Account.php @@ -1,21 +1,21 @@ -<?php -/** - * UserFrosting (http://www.userfrosting.com) - * - * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) - */ - -namespace UserFrosting\Sprinkle\Account; - -use UserFrosting\System\Sprinkle\Sprinkle; - -/** - * Bootstrapper class for the 'account' sprinkle. - * - * @author Alex Weissman (https://alexanderweissman.com) - */ -class Account extends Sprinkle -{ - -} +<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Account;
+
+use UserFrosting\System\Sprinkle\Sprinkle;
+
+/**
+ * Bootstrapper class for the 'account' sprinkle.
+ *
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+class Account extends Sprinkle
+{
+
+}
diff --git a/main/app/sprinkles/account/src/Authenticate/AuthGuard.php b/main/app/sprinkles/account/src/Authenticate/AuthGuard.php index ce64bd7..9603a87 100644 --- a/main/app/sprinkles/account/src/Authenticate/AuthGuard.php +++ b/main/app/sprinkles/account/src/Authenticate/AuthGuard.php @@ -1,55 +1,55 @@ -<?php -/** - * UserFrosting (http://www.userfrosting.com) - * - * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) - */ - -namespace UserFrosting\Sprinkle\Account\Authenticate; - -use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\ServerRequestInterface; -use Slim\Http\Body; -use UserFrosting\Sprinkle\Account\Authenticate\Exception\AuthExpiredException; - -/** - * Middleware to catch requests that fail because they require user authentication. - * - * @author Alex Weissman (https://alexanderweissman.com) - */ -class AuthGuard -{ - /** - * @var Authenticator - */ - protected $authenticator; - - /** - * Constructor. - * - * @param $authenticator Authenticator The current authentication object. - */ - public function __construct($authenticator) { - $this->authenticator = $authenticator; - } - - /** - * Invoke the AuthGuard middleware, throwing an exception if there is no authenticated user in the session. - * - * @param \Psr\Http\Message\ServerRequestInterface $request PSR7 request - * @param \Psr\Http\Message\ResponseInterface $response PSR7 response - * @param callable $next Next middleware - * - * @return \Psr\Http\Message\ResponseInterface - */ - public function __invoke($request, $response, $next) { - if (!$this->authenticator->check()) { - throw new AuthExpiredException(); - } else { - return $next($request, $response); - } - - return $response; - } -} +<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Account\Authenticate;
+
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use Slim\Http\Body;
+use UserFrosting\Sprinkle\Account\Authenticate\Exception\AuthExpiredException;
+
+/**
+ * Middleware to catch requests that fail because they require user authentication.
+ *
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+class AuthGuard
+{
+ /**
+ * @var Authenticator
+ */
+ protected $authenticator;
+
+ /**
+ * Constructor.
+ *
+ * @param $authenticator Authenticator The current authentication object.
+ */
+ public function __construct($authenticator) {
+ $this->authenticator = $authenticator;
+ }
+
+ /**
+ * Invoke the AuthGuard middleware, throwing an exception if there is no authenticated user in the session.
+ *
+ * @param \Psr\Http\Message\ServerRequestInterface $request PSR7 request
+ * @param \Psr\Http\Message\ResponseInterface $response PSR7 response
+ * @param callable $next Next middleware
+ *
+ * @return \Psr\Http\Message\ResponseInterface
+ */
+ public function __invoke($request, $response, $next) {
+ if (!$this->authenticator->check()) {
+ throw new AuthExpiredException();
+ } else {
+ return $next($request, $response);
+ }
+
+ return $response;
+ }
+}
diff --git a/main/app/sprinkles/account/src/Authenticate/Authenticator.php b/main/app/sprinkles/account/src/Authenticate/Authenticator.php index 735a688..a4586e4 100644 --- a/main/app/sprinkles/account/src/Authenticate/Authenticator.php +++ b/main/app/sprinkles/account/src/Authenticate/Authenticator.php @@ -1,407 +1,407 @@ -<?php -/** - * UserFrosting (http://www.userfrosting.com) - * - * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) - */ - -namespace UserFrosting\Sprinkle\Account\Authenticate; - -use Birke\Rememberme\Authenticator as RememberMe; -use Birke\Rememberme\Storage\PDOStorage as RememberMePDO; -use Birke\Rememberme\Triplet as RememberMeTriplet; -use Illuminate\Database\Capsule\Manager as Capsule; -use UserFrosting\Session\Session; -use UserFrosting\Sprinkle\Account\Authenticate\Exception\AccountDisabledException; -use UserFrosting\Sprinkle\Account\Authenticate\Exception\AccountInvalidException; -use UserFrosting\Sprinkle\Account\Authenticate\Exception\AccountNotVerifiedException; -use UserFrosting\Sprinkle\Account\Authenticate\Exception\AuthCompromisedException; -use UserFrosting\Sprinkle\Account\Authenticate\Exception\AuthExpiredException; -use UserFrosting\Sprinkle\Account\Authenticate\Exception\InvalidCredentialsException; -use UserFrosting\Sprinkle\Account\Database\Models\User; -use UserFrosting\Sprinkle\Account\Facades\Password; -use UserFrosting\Sprinkle\Core\Util\ClassMapper; - -/** - * Handles authentication tasks. - * - * @author Alex Weissman (https://alexanderweissman.com) - * Partially inspired by Laravel's Authentication component: https://github.com/laravel/framework/blob/5.3/src/Illuminate/Auth/SessionGuard.php - */ -class Authenticator -{ - /** - * @var ClassMapper - */ - protected $classMapper; - - /** - * @var Session - */ - protected $session; - - /** - * @var Config - */ - protected $config; - - /** - * @var Cache - */ - protected $cache; - - /** - * @var bool - */ - protected $loggedOut = FALSE; - - /** - * @var RememberMePDO - */ - protected $rememberMeStorage; - - /** - * @var RememberMe - */ - protected $rememberMe; - - /** - * @var User - */ - protected $user; - - /** - * Indicates if the user was authenticated via a rememberMe cookie. - * - * @var bool - */ - protected $viaRemember = FALSE; - - /** - * Create a new Authenticator object. - * - * @param ClassMapper $classMapper Maps generic class identifiers to specific class names. - * @param Session $session The session wrapper object that will store the user's id. - * @param Config $config Config object that contains authentication settings. - * @param mixed $cache Cache service instance - */ - public function __construct(ClassMapper $classMapper, Session $session, $config, $cache) { - $this->classMapper = $classMapper; - $this->session = $session; - $this->config = $config; - $this->cache = $cache; - - // Initialize RememberMe storage - $this->rememberMeStorage = new RememberMePDO($this->config['remember_me.table']); - - // Get the actual PDO instance from Eloquent - $pdo = Capsule::connection()->getPdo(); - - $this->rememberMeStorage->setConnection($pdo); - - // Set up RememberMe - $this->rememberMe = new RememberMe($this->rememberMeStorage); - // Set cookie name - $cookieName = $this->config['session.name'] . '-' . $this->config['remember_me.cookie.name']; - $this->rememberMe->getCookie()->setName($cookieName); - - // Change cookie path - $this->rememberMe->getCookie()->setPath($this->config['remember_me.session.path']); - - // Set expire time, if specified - if ($this->config->has('remember_me.expire_time') && ($this->config->has('remember_me.expire_time') != NULL)) { - $this->rememberMe->getCookie()->setExpireTime($this->config['remember_me.expire_time']); - } - - $this->user = NULL; - - $this->viaRemember = FALSE; - } - - /** - * Attempts to authenticate a user based on a supplied identity and password. - * - * If successful, the user's id is stored in session. - */ - public function attempt($identityColumn, $identityValue, $password, $rememberMe = FALSE) { - // Try to load the user, using the specified conditions - $user = $this->classMapper->staticMethod('user', 'where', $identityColumn, $identityValue)->first(); - - if (!$user) { - throw new InvalidCredentialsException(); - } - - // Check that the user has a password set (so, rule out newly created accounts without a password) - if (!$user->password) { - throw new InvalidCredentialsException(); - } - - // Check that the user's account is enabled - if ($user->flag_enabled == 0) { - throw new AccountDisabledException(); - } - - // Check that the user's account is verified (if verification is required) - if ($this->config['site.registration.require_email_verification'] && $user->flag_verified == 0) { - throw new AccountNotVerifiedException(); - } - - // Here is my password. May I please assume the identify of this user now? - if (Password::verify($password, $user->password)) { - $this->login($user, $rememberMe); - return $user; - } else { - // We know the password is at fault here (as opposed to the identity), but lets not give away the combination in case of someone bruteforcing - throw new InvalidCredentialsException(); - } - } - - /** - * Determine if the current user is authenticated. - * - * @return bool - */ - public function check() { - return !is_null($this->user()); - } - - /** - * Determine if the current user is a guest (unauthenticated). - * - * @return bool - */ - public function guest() { - return !$this->check(); - } - - /** - * Process an account login request. - * - * This method logs in the specified user, allowing the client to assume the user's identity for the duration of the session. - * @param User $user The user to log in. - * @param bool $rememberMe Set to true to make this a "persistent session", i.e. one that will re-login even after the session expires. - * @odo Figure out a way to update the currentUser service to reflect the logged-in user *immediately* in the service provider. - * As it stands, the currentUser service will still reflect a "guest user" for the remainder of the request. - */ - public function login($user, $rememberMe = FALSE) { - $oldId = session_id(); - $this->session->regenerateId(TRUE); - - // Since regenerateId deletes the old session, we'll do the same in cache - $this->flushSessionCache($oldId); - - // If the user wants to be remembered, create Rememberme cookie - if ($rememberMe) { - $this->rememberMe->createCookie($user->id); - } else { - $this->rememberMe->clearCookie(); - } - - // Assume identity - $key = $this->config['session.keys.current_user_id']; - $this->session[$key] = $user->id; - - // Set auth mode - $this->viaRemember = FALSE; - - // User login actions - $user->onLogin(); - } - - /** - * Processes an account logout request. - * - * Logs the currently authenticated user out, destroying the PHP session and clearing the persistent session. - * This can optionally remove persistent sessions across all browsers/devices, since there can be a "RememberMe" cookie - * and corresponding database entries in multiple browsers/devices. See http://jaspan.com/improved_persistent_login_cookie_best_practice. - * - * @param bool $complete If set to true, will ensure that the user is logged out from *all* browsers on all devices. - */ - public function logout($complete = FALSE) { - $currentUserId = $this->session->get($this->config['session.keys.current_user_id']); - - // This removes all of the user's persistent logins from the database - if ($complete) { - $this->storage->cleanAllTriplets($currentUserId); - } - - // Clear the rememberMe cookie - $this->rememberMe->clearCookie(); - - // User logout actions - if ($currentUserId) { - $currentUser = $this->classMapper->staticMethod('user', 'find', $currentUserId); - if ($currentUser) { - $currentUser->onLogout(); - } - } - - $this->user = NULL; - $this->loggedOut = TRUE; - - $oldId = session_id(); - - // Completely destroy the session - $this->session->destroy(); - - // Since regenerateId deletes the old session, we'll do the same in cache - $this->flushSessionCache($oldId); - - // Restart the session service - $this->session->start(); - } - - /** - * Try to get the currently authenticated user, returning a guest user if none was found. - * - * Tries to re-establish a session for "remember-me" users who have been logged out due to an expired session. - * @return User|null - * @throws AuthExpiredException - * @throws AuthCompromisedException - * @throws AccountInvalidException - * @throws AccountDisabledException - */ - public function user() { - $user = NULL; - - if (!$this->loggedOut) { - - // Return any cached user - if (!is_null($this->user)) { - return $this->user; - } - - // If this throws a PDOException we catch it and return null than allowing the exception to propagate. - // This is because the error handler relies on Twig, which relies on a Twig Extension, which relies on the global current_user variable. - // So, we really don't want this method to throw any database exceptions. - try { - // Now, check to see if we have a user in session - $user = $this->loginSessionUser(); - - // If no user was found in the session, try to login via RememberMe cookie - if (!$user) { - $user = $this->loginRememberedUser(); - } - } catch (\PDOException $e) { - $user = NULL; - } - } - - return $this->user = $user; - } - - /** - * Determine whether the current user was authenticated using a remember me cookie. - * - * This function is useful when users are performing sensitive operations, and you may want to force them to re-authenticate. - * @return bool - */ - public function viaRemember() { - return $this->viaRemember; - } - - /** - * Attempt to log in the client from their rememberMe token (in their cookie). - * - * @return User|bool If successful, the User object of the remembered user. Otherwise, return false. - * @throws AuthCompromisedException The client attempted to log in with an invalid rememberMe token. - */ - protected function loginRememberedUser() { - /** @var \Birke\Rememberme\LoginResult $loginResult */ - $loginResult = $this->rememberMe->login(); - - if ($loginResult->isSuccess()) { - // Update in session - $this->session[$this->config['session.keys.current_user_id']] = $loginResult->getCredential(); - // There is a chance that an attacker has stolen the login token, - // so we store the fact that the user was logged in via RememberMe (instead of login form) - $this->viaRemember = TRUE; - } else { - // If $rememberMe->login() was not successful, check if the token was invalid as well. This means the cookie was stolen. - if ($loginResult->hasPossibleManipulation()) { - throw new AuthCompromisedException(); - } - } - - return $this->validateUserAccount($loginResult->getCredential()); - } - - /** - * Attempt to log in the client from the session. - * - * @return User|null If successful, the User object of the user in session. Otherwise, return null. - * @throws AuthExpiredException The client attempted to use an expired rememberMe token. - */ - protected function loginSessionUser() { - $userId = $this->session->get($this->config['session.keys.current_user_id']); - - // If a user_id was found in the session, check any rememberMe cookie that was submitted. - // If they submitted an expired rememberMe cookie, then we need to log them out. - if ($userId) { - if (!$this->validateRememberMeCookie()) { - $this->logout(); - throw new AuthExpiredException(); - } - } - - return $this->validateUserAccount($userId); - } - - /** - * Determine if the cookie contains a valid rememberMe token. - * - * @return bool - */ - protected function validateRememberMeCookie() { - $cookieValue = $this->rememberMe->getCookie()->getValue(); - if (!$cookieValue) { - return TRUE; - } - $triplet = RememberMeTriplet::fromString($cookieValue); - if (!$triplet->isValid()) { - return FALSE; - } - - return TRUE; - } - - /** - * Tries to load the specified user by id from the database. - * - * Checks that the account is valid and enabled, throwing an exception if not. - * @param int $userId - * @return User|null - * @throws AccountInvalidException - * @throws AccountDisabledException - */ - protected function validateUserAccount($userId) { - if ($userId) { - $user = $this->classMapper->staticMethod('user', 'find', $userId); - - // If the user doesn't exist any more, throw an exception. - if (!$user) { - throw new AccountInvalidException(); - } - - // If the user has been disabled since their last request, throw an exception. - if (!$user->flag_enabled) { - throw new AccountDisabledException(); - } - - return $user; - } else { - return NULL; - } - } - - /** - * Flush the cache associated with a session id - * - * @param string $id The session id - * @return bool - */ - public function flushSessionCache($id) { - return $this->cache->tags('_s' . $id)->flush(); - } -} +<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Account\Authenticate;
+
+use Birke\Rememberme\Authenticator as RememberMe;
+use Birke\Rememberme\Storage\PDOStorage as RememberMePDO;
+use Birke\Rememberme\Triplet as RememberMeTriplet;
+use Illuminate\Database\Capsule\Manager as Capsule;
+use UserFrosting\Session\Session;
+use UserFrosting\Sprinkle\Account\Authenticate\Exception\AccountDisabledException;
+use UserFrosting\Sprinkle\Account\Authenticate\Exception\AccountInvalidException;
+use UserFrosting\Sprinkle\Account\Authenticate\Exception\AccountNotVerifiedException;
+use UserFrosting\Sprinkle\Account\Authenticate\Exception\AuthCompromisedException;
+use UserFrosting\Sprinkle\Account\Authenticate\Exception\AuthExpiredException;
+use UserFrosting\Sprinkle\Account\Authenticate\Exception\InvalidCredentialsException;
+use UserFrosting\Sprinkle\Account\Database\Models\User;
+use UserFrosting\Sprinkle\Account\Facades\Password;
+use UserFrosting\Sprinkle\Core\Util\ClassMapper;
+
+/**
+ * Handles authentication tasks.
+ *
+ * @author Alex Weissman (https://alexanderweissman.com)
+ * Partially inspired by Laravel's Authentication component: https://github.com/laravel/framework/blob/5.3/src/Illuminate/Auth/SessionGuard.php
+ */
+class Authenticator
+{
+ /**
+ * @var ClassMapper
+ */
+ protected $classMapper;
+
+ /**
+ * @var Session
+ */
+ protected $session;
+
+ /**
+ * @var Config
+ */
+ protected $config;
+
+ /**
+ * @var Cache
+ */
+ protected $cache;
+
+ /**
+ * @var bool
+ */
+ protected $loggedOut = FALSE;
+
+ /**
+ * @var RememberMePDO
+ */
+ protected $rememberMeStorage;
+
+ /**
+ * @var RememberMe
+ */
+ protected $rememberMe;
+
+ /**
+ * @var User
+ */
+ protected $user;
+
+ /**
+ * Indicates if the user was authenticated via a rememberMe cookie.
+ *
+ * @var bool
+ */
+ protected $viaRemember = FALSE;
+
+ /**
+ * Create a new Authenticator object.
+ *
+ * @param ClassMapper $classMapper Maps generic class identifiers to specific class names.
+ * @param Session $session The session wrapper object that will store the user's id.
+ * @param Config $config Config object that contains authentication settings.
+ * @param mixed $cache Cache service instance
+ */
+ public function __construct(ClassMapper $classMapper, Session $session, $config, $cache) {
+ $this->classMapper = $classMapper;
+ $this->session = $session;
+ $this->config = $config;
+ $this->cache = $cache;
+
+ // Initialize RememberMe storage
+ $this->rememberMeStorage = new RememberMePDO($this->config['remember_me.table']);
+
+ // Get the actual PDO instance from Eloquent
+ $pdo = Capsule::connection()->getPdo();
+
+ $this->rememberMeStorage->setConnection($pdo);
+
+ // Set up RememberMe
+ $this->rememberMe = new RememberMe($this->rememberMeStorage);
+ // Set cookie name
+ $cookieName = $this->config['session.name'] . '-' . $this->config['remember_me.cookie.name'];
+ $this->rememberMe->getCookie()->setName($cookieName);
+
+ // Change cookie path
+ $this->rememberMe->getCookie()->setPath($this->config['remember_me.session.path']);
+
+ // Set expire time, if specified
+ if ($this->config->has('remember_me.expire_time') && ($this->config->has('remember_me.expire_time') != NULL)) {
+ $this->rememberMe->getCookie()->setExpireTime($this->config['remember_me.expire_time']);
+ }
+
+ $this->user = NULL;
+
+ $this->viaRemember = FALSE;
+ }
+
+ /**
+ * Attempts to authenticate a user based on a supplied identity and password.
+ *
+ * If successful, the user's id is stored in session.
+ */
+ public function attempt($identityColumn, $identityValue, $password, $rememberMe = FALSE) {
+ // Try to load the user, using the specified conditions
+ $user = $this->classMapper->staticMethod('user', 'where', $identityColumn, $identityValue)->first();
+
+ if (!$user) {
+ throw new InvalidCredentialsException();
+ }
+
+ // Check that the user has a password set (so, rule out newly created accounts without a password)
+ if (!$user->password) {
+ throw new InvalidCredentialsException();
+ }
+
+ // Check that the user's account is enabled
+ if ($user->flag_enabled == 0) {
+ throw new AccountDisabledException();
+ }
+
+ // Check that the user's account is verified (if verification is required)
+ if ($this->config['site.registration.require_email_verification'] && $user->flag_verified == 0) {
+ throw new AccountNotVerifiedException();
+ }
+
+ // Here is my password. May I please assume the identify of this user now?
+ if (Password::verify($password, $user->password)) {
+ $this->login($user, $rememberMe);
+ return $user;
+ } else {
+ // We know the password is at fault here (as opposed to the identity), but lets not give away the combination in case of someone bruteforcing
+ throw new InvalidCredentialsException();
+ }
+ }
+
+ /**
+ * Determine if the current user is authenticated.
+ *
+ * @return bool
+ */
+ public function check() {
+ return !is_null($this->user());
+ }
+
+ /**
+ * Determine if the current user is a guest (unauthenticated).
+ *
+ * @return bool
+ */
+ public function guest() {
+ return !$this->check();
+ }
+
+ /**
+ * Process an account login request.
+ *
+ * This method logs in the specified user, allowing the client to assume the user's identity for the duration of the session.
+ * @param User $user The user to log in.
+ * @param bool $rememberMe Set to true to make this a "persistent session", i.e. one that will re-login even after the session expires.
+ * @odo Figure out a way to update the currentUser service to reflect the logged-in user *immediately* in the service provider.
+ * As it stands, the currentUser service will still reflect a "guest user" for the remainder of the request.
+ */
+ public function login($user, $rememberMe = FALSE) {
+ $oldId = session_id();
+ $this->session->regenerateId(TRUE);
+
+ // Since regenerateId deletes the old session, we'll do the same in cache
+ $this->flushSessionCache($oldId);
+
+ // If the user wants to be remembered, create Rememberme cookie
+ if ($rememberMe) {
+ $this->rememberMe->createCookie($user->id);
+ } else {
+ $this->rememberMe->clearCookie();
+ }
+
+ // Assume identity
+ $key = $this->config['session.keys.current_user_id'];
+ $this->session[$key] = $user->id;
+
+ // Set auth mode
+ $this->viaRemember = FALSE;
+
+ // User login actions
+ $user->onLogin();
+ }
+
+ /**
+ * Processes an account logout request.
+ *
+ * Logs the currently authenticated user out, destroying the PHP session and clearing the persistent session.
+ * This can optionally remove persistent sessions across all browsers/devices, since there can be a "RememberMe" cookie
+ * and corresponding database entries in multiple browsers/devices. See http://jaspan.com/improved_persistent_login_cookie_best_practice.
+ *
+ * @param bool $complete If set to true, will ensure that the user is logged out from *all* browsers on all devices.
+ */
+ public function logout($complete = FALSE) {
+ $currentUserId = $this->session->get($this->config['session.keys.current_user_id']);
+
+ // This removes all of the user's persistent logins from the database
+ if ($complete) {
+ $this->storage->cleanAllTriplets($currentUserId);
+ }
+
+ // Clear the rememberMe cookie
+ $this->rememberMe->clearCookie();
+
+ // User logout actions
+ if ($currentUserId) {
+ $currentUser = $this->classMapper->staticMethod('user', 'find', $currentUserId);
+ if ($currentUser) {
+ $currentUser->onLogout();
+ }
+ }
+
+ $this->user = NULL;
+ $this->loggedOut = TRUE;
+
+ $oldId = session_id();
+
+ // Completely destroy the session
+ $this->session->destroy();
+
+ // Since regenerateId deletes the old session, we'll do the same in cache
+ $this->flushSessionCache($oldId);
+
+ // Restart the session service
+ $this->session->start();
+ }
+
+ /**
+ * Try to get the currently authenticated user, returning a guest user if none was found.
+ *
+ * Tries to re-establish a session for "remember-me" users who have been logged out due to an expired session.
+ * @return User|null
+ * @throws AuthExpiredException
+ * @throws AuthCompromisedException
+ * @throws AccountInvalidException
+ * @throws AccountDisabledException
+ */
+ public function user() {
+ $user = NULL;
+
+ if (!$this->loggedOut) {
+
+ // Return any cached user
+ if (!is_null($this->user)) {
+ return $this->user;
+ }
+
+ // If this throws a PDOException we catch it and return null than allowing the exception to propagate.
+ // This is because the error handler relies on Twig, which relies on a Twig Extension, which relies on the global current_user variable.
+ // So, we really don't want this method to throw any database exceptions.
+ try {
+ // Now, check to see if we have a user in session
+ $user = $this->loginSessionUser();
+
+ // If no user was found in the session, try to login via RememberMe cookie
+ if (!$user) {
+ $user = $this->loginRememberedUser();
+ }
+ } catch (\PDOException $e) {
+ $user = NULL;
+ }
+ }
+
+ return $this->user = $user;
+ }
+
+ /**
+ * Determine whether the current user was authenticated using a remember me cookie.
+ *
+ * This function is useful when users are performing sensitive operations, and you may want to force them to re-authenticate.
+ * @return bool
+ */
+ public function viaRemember() {
+ return $this->viaRemember;
+ }
+
+ /**
+ * Attempt to log in the client from their rememberMe token (in their cookie).
+ *
+ * @return User|bool If successful, the User object of the remembered user. Otherwise, return false.
+ * @throws AuthCompromisedException The client attempted to log in with an invalid rememberMe token.
+ */
+ protected function loginRememberedUser() {
+ /** @var \Birke\Rememberme\LoginResult $loginResult */
+ $loginResult = $this->rememberMe->login();
+
+ if ($loginResult->isSuccess()) {
+ // Update in session
+ $this->session[$this->config['session.keys.current_user_id']] = $loginResult->getCredential();
+ // There is a chance that an attacker has stolen the login token,
+ // so we store the fact that the user was logged in via RememberMe (instead of login form)
+ $this->viaRemember = TRUE;
+ } else {
+ // If $rememberMe->login() was not successful, check if the token was invalid as well. This means the cookie was stolen.
+ if ($loginResult->hasPossibleManipulation()) {
+ throw new AuthCompromisedException();
+ }
+ }
+
+ return $this->validateUserAccount($loginResult->getCredential());
+ }
+
+ /**
+ * Attempt to log in the client from the session.
+ *
+ * @return User|null If successful, the User object of the user in session. Otherwise, return null.
+ * @throws AuthExpiredException The client attempted to use an expired rememberMe token.
+ */
+ protected function loginSessionUser() {
+ $userId = $this->session->get($this->config['session.keys.current_user_id']);
+
+ // If a user_id was found in the session, check any rememberMe cookie that was submitted.
+ // If they submitted an expired rememberMe cookie, then we need to log them out.
+ if ($userId) {
+ if (!$this->validateRememberMeCookie()) {
+ $this->logout();
+ throw new AuthExpiredException();
+ }
+ }
+
+ return $this->validateUserAccount($userId);
+ }
+
+ /**
+ * Determine if the cookie contains a valid rememberMe token.
+ *
+ * @return bool
+ */
+ protected function validateRememberMeCookie() {
+ $cookieValue = $this->rememberMe->getCookie()->getValue();
+ if (!$cookieValue) {
+ return TRUE;
+ }
+ $triplet = RememberMeTriplet::fromString($cookieValue);
+ if (!$triplet->isValid()) {
+ return FALSE;
+ }
+
+ return TRUE;
+ }
+
+ /**
+ * Tries to load the specified user by id from the database.
+ *
+ * Checks that the account is valid and enabled, throwing an exception if not.
+ * @param int $userId
+ * @return User|null
+ * @throws AccountInvalidException
+ * @throws AccountDisabledException
+ */
+ protected function validateUserAccount($userId) {
+ if ($userId) {
+ $user = $this->classMapper->staticMethod('user', 'find', $userId);
+
+ // If the user doesn't exist any more, throw an exception.
+ if (!$user) {
+ throw new AccountInvalidException();
+ }
+
+ // If the user has been disabled since their last request, throw an exception.
+ if (!$user->flag_enabled) {
+ throw new AccountDisabledException();
+ }
+
+ return $user;
+ } else {
+ return NULL;
+ }
+ }
+
+ /**
+ * Flush the cache associated with a session id
+ *
+ * @param string $id The session id
+ * @return bool
+ */
+ public function flushSessionCache($id) {
+ return $this->cache->tags('_s' . $id)->flush();
+ }
+}
diff --git a/main/app/sprinkles/account/src/Authenticate/Exception/AccountDisabledException.php b/main/app/sprinkles/account/src/Authenticate/Exception/AccountDisabledException.php index 3ad4c59..314fcc3 100644 --- a/main/app/sprinkles/account/src/Authenticate/Exception/AccountDisabledException.php +++ b/main/app/sprinkles/account/src/Authenticate/Exception/AccountDisabledException.php @@ -1,22 +1,22 @@ -<?php -/** - * UserFrosting (http://www.userfrosting.com) - * - * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) - */ - -namespace UserFrosting\Sprinkle\Account\Authenticate\Exception; - -use UserFrosting\Support\Exception\HttpException; - -/** - * Disabled account exception. Used when an account has been disabled. - * - * @author Alex Weissman (https://alexanderweissman.com) - */ -class AccountDisabledException extends HttpException -{ - protected $defaultMessage = 'ACCOUNT.DISABLED'; - protected $httpErrorCode = 403; -} +<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Account\Authenticate\Exception;
+
+use UserFrosting\Support\Exception\HttpException;
+
+/**
+ * Disabled account exception. Used when an account has been disabled.
+ *
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+class AccountDisabledException extends HttpException
+{
+ protected $defaultMessage = 'ACCOUNT.DISABLED';
+ protected $httpErrorCode = 403;
+}
diff --git a/main/app/sprinkles/account/src/Authenticate/Exception/AccountInvalidException.php b/main/app/sprinkles/account/src/Authenticate/Exception/AccountInvalidException.php index fb06fae..bc42b62 100644 --- a/main/app/sprinkles/account/src/Authenticate/Exception/AccountInvalidException.php +++ b/main/app/sprinkles/account/src/Authenticate/Exception/AccountInvalidException.php @@ -1,22 +1,22 @@ -<?php -/** - * UserFrosting (http://www.userfrosting.com) - * - * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) - */ - -namespace UserFrosting\Sprinkle\Account\Authenticate\Exception; - -use UserFrosting\Support\Exception\HttpException; - -/** - * Invalid account exception. Used when an account has been removed during an active session. - * - * @author Alex Weissman (https://alexanderweissman.com) - */ -class AccountInvalidException extends HttpException -{ - protected $defaultMessage = 'ACCOUNT.INVALID'; - protected $httpErrorCode = 403; -} +<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Account\Authenticate\Exception;
+
+use UserFrosting\Support\Exception\HttpException;
+
+/**
+ * Invalid account exception. Used when an account has been removed during an active session.
+ *
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+class AccountInvalidException extends HttpException
+{
+ protected $defaultMessage = 'ACCOUNT.INVALID';
+ protected $httpErrorCode = 403;
+}
diff --git a/main/app/sprinkles/account/src/Authenticate/Exception/AccountNotVerifiedException.php b/main/app/sprinkles/account/src/Authenticate/Exception/AccountNotVerifiedException.php index 2a721bb..1548201 100644 --- a/main/app/sprinkles/account/src/Authenticate/Exception/AccountNotVerifiedException.php +++ b/main/app/sprinkles/account/src/Authenticate/Exception/AccountNotVerifiedException.php @@ -1,22 +1,22 @@ -<?php -/** - * UserFrosting (http://www.userfrosting.com) - * - * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) - */ - -namespace UserFrosting\Sprinkle\Account\Authenticate\Exception; - -use UserFrosting\Support\Exception\HttpException; - -/** - * Unverified account exception. Used when an account is required to complete email verification, but hasn't done so yet. - * - * @author Alex Weissman (https://alexanderweissman.com) - */ -class AccountNotVerifiedException extends HttpException -{ - protected $defaultMessage = 'ACCOUNT.UNVERIFIED'; - protected $httpErrorCode = 403; -} +<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Account\Authenticate\Exception;
+
+use UserFrosting\Support\Exception\HttpException;
+
+/**
+ * Unverified account exception. Used when an account is required to complete email verification, but hasn't done so yet.
+ *
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+class AccountNotVerifiedException extends HttpException
+{
+ protected $defaultMessage = 'ACCOUNT.UNVERIFIED';
+ protected $httpErrorCode = 403;
+}
diff --git a/main/app/sprinkles/account/src/Authenticate/Exception/AuthCompromisedException.php b/main/app/sprinkles/account/src/Authenticate/Exception/AuthCompromisedException.php index 52fd528..dd169bd 100644 --- a/main/app/sprinkles/account/src/Authenticate/Exception/AuthCompromisedException.php +++ b/main/app/sprinkles/account/src/Authenticate/Exception/AuthCompromisedException.php @@ -1,21 +1,21 @@ -<?php -/** - * UserFrosting (http://www.userfrosting.com) - * - * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) - */ - -namespace UserFrosting\Sprinkle\Account\Authenticate\Exception; - -use UserFrosting\Support\Exception\ForbiddenException; - -/** - * Compromised authentication exception. Used when we suspect theft of the rememberMe cookie. - * - * @author Alex Weissman (https://alexanderweissman.com) - */ -class AuthCompromisedException extends ForbiddenException -{ - protected $defaultMessage = 'ACCOUNT.SESSION_COMPROMISED'; -} +<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Account\Authenticate\Exception;
+
+use UserFrosting\Support\Exception\ForbiddenException;
+
+/**
+ * Compromised authentication exception. Used when we suspect theft of the rememberMe cookie.
+ *
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+class AuthCompromisedException extends ForbiddenException
+{
+ protected $defaultMessage = 'ACCOUNT.SESSION_COMPROMISED';
+}
diff --git a/main/app/sprinkles/account/src/Authenticate/Exception/AuthExpiredException.php b/main/app/sprinkles/account/src/Authenticate/Exception/AuthExpiredException.php index ab7cbdb..2345118 100644 --- a/main/app/sprinkles/account/src/Authenticate/Exception/AuthExpiredException.php +++ b/main/app/sprinkles/account/src/Authenticate/Exception/AuthExpiredException.php @@ -1,22 +1,22 @@ -<?php -/** - * UserFrosting (http://www.userfrosting.com) - * - * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) - */ - -namespace UserFrosting\Sprinkle\Account\Authenticate\Exception; - -use UserFrosting\Support\Exception\HttpException; - -/** - * Expired authentication exception. Used when the user needs to authenticate/reauthenticate. - * - * @author Alex Weissman (https://alexanderweissman.com) - */ -class AuthExpiredException extends HttpException -{ - protected $defaultMessage = 'ACCOUNT.SESSION_EXPIRED'; - protected $httpErrorCode = 401; -} +<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Account\Authenticate\Exception;
+
+use UserFrosting\Support\Exception\HttpException;
+
+/**
+ * Expired authentication exception. Used when the user needs to authenticate/reauthenticate.
+ *
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+class AuthExpiredException extends HttpException
+{
+ protected $defaultMessage = 'ACCOUNT.SESSION_EXPIRED';
+ protected $httpErrorCode = 401;
+}
diff --git a/main/app/sprinkles/account/src/Authenticate/Exception/InvalidCredentialsException.php b/main/app/sprinkles/account/src/Authenticate/Exception/InvalidCredentialsException.php index 78ea3de..8f73403 100644 --- a/main/app/sprinkles/account/src/Authenticate/Exception/InvalidCredentialsException.php +++ b/main/app/sprinkles/account/src/Authenticate/Exception/InvalidCredentialsException.php @@ -1,22 +1,22 @@ -<?php -/** - * UserFrosting (http://www.userfrosting.com) - * - * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) - */ - -namespace UserFrosting\Sprinkle\Account\Authenticate\Exception; - -use UserFrosting\Support\Exception\HttpException; - -/** - * Invalid credentials exception. Used when an account fails authentication for some reason. - * - * @author Alex Weissman (https://alexanderweissman.com) - */ -class InvalidCredentialsException extends HttpException -{ - protected $defaultMessage = 'USER_OR_PASS_INVALID'; - protected $httpErrorCode = 403; -} +<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Account\Authenticate\Exception;
+
+use UserFrosting\Support\Exception\HttpException;
+
+/**
+ * Invalid credentials exception. Used when an account fails authentication for some reason.
+ *
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+class InvalidCredentialsException extends HttpException
+{
+ protected $defaultMessage = 'USER_OR_PASS_INVALID';
+ protected $httpErrorCode = 403;
+}
diff --git a/main/app/sprinkles/account/src/Authenticate/Hasher.php b/main/app/sprinkles/account/src/Authenticate/Hasher.php index 5de939f..7ec832e 100644 --- a/main/app/sprinkles/account/src/Authenticate/Hasher.php +++ b/main/app/sprinkles/account/src/Authenticate/Hasher.php @@ -1,105 +1,105 @@ -<?php -/** - * UserFrosting (http://www.userfrosting.com) - * - * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) - */ - -namespace UserFrosting\Sprinkle\Account\Authenticate; - -/** - * Password hashing and validation class - * - * @author Alex Weissman (https://alexanderweissman.com) - */ -class Hasher -{ - /** - * Default crypt cost factor. - * - * @var int - */ - protected $defaultRounds = 10; - - /** - * Returns the hashing type for a specified password hash. - * - * Automatically detects the hash type: "sha1" (for UserCake legacy accounts), "legacy" (for 0.1.x accounts), and "modern" (used for new accounts). - * @param string $password the hashed password. - * @return string "sha1"|"legacy"|"modern". - */ - public function getHashType($password) { - // If the password in the db is 65 characters long, we have an sha1-hashed password. - if (strlen($password) == 65) { - return 'sha1'; - } else if (strlen($password) == 82) { - return 'legacy'; - } - - return 'modern'; - } - - /** - * Hashes a plaintext password using bcrypt. - * - * @param string $password the plaintext password. - * @param array $options - * @return string the hashed password. - * @throws HashFailedException - */ - public function hash($password, array $options = []) { - $hash = password_hash($password, PASSWORD_BCRYPT, [ - 'cost' => $this->cost($options), - ]); - - if (!$hash) { - throw new HashFailedException(); - } - - return $hash; - } - - /** - * Verify a plaintext password against the user's hashed password. - * - * @param string $password The plaintext password to verify. - * @param string $hash The hash to compare against. - * @param array $options - * @return boolean True if the password matches, false otherwise. - */ - public function verify($password, $hash, array $options = []) { - $hashType = $this->getHashType($hash); - - if ($hashType == 'sha1') { - // Legacy UserCake passwords - $salt = substr($hash, 0, 25); // Extract the salt from the hash - $inputHash = $salt . sha1($salt . $password); - - return (hash_equals($inputHash, $hash) === TRUE); - - } else if ($hashType == 'legacy') { - // Homegrown implementation (assuming that current install has been using a cost parameter of 12) - // Used for manual implementation of bcrypt. - // Note that this legacy hashing put the salt at the _end_ for some reason. - $salt = substr($hash, 60); - $inputHash = crypt($password, '$2y$12$' . $salt); - $correctHash = substr($hash, 0, 60); - - return (hash_equals($inputHash, $correctHash) === TRUE); - } - - // Modern implementation - return password_verify($password, $hash); - } - - /** - * Extract the cost value from the options array. - * - * @param array $options - * @return int - */ - protected function cost(array $options = []) { - return isset($options['rounds']) ? $options['rounds'] : $this->defaultRounds; - } -} +<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Account\Authenticate;
+
+/**
+ * Password hashing and validation class
+ *
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+class Hasher
+{
+ /**
+ * Default crypt cost factor.
+ *
+ * @var int
+ */
+ protected $defaultRounds = 10;
+
+ /**
+ * Returns the hashing type for a specified password hash.
+ *
+ * Automatically detects the hash type: "sha1" (for UserCake legacy accounts), "legacy" (for 0.1.x accounts), and "modern" (used for new accounts).
+ * @param string $password the hashed password.
+ * @return string "sha1"|"legacy"|"modern".
+ */
+ public function getHashType($password) {
+ // If the password in the db is 65 characters long, we have an sha1-hashed password.
+ if (strlen($password) == 65) {
+ return 'sha1';
+ } else if (strlen($password) == 82) {
+ return 'legacy';
+ }
+
+ return 'modern';
+ }
+
+ /**
+ * Hashes a plaintext password using bcrypt.
+ *
+ * @param string $password the plaintext password.
+ * @param array $options
+ * @return string the hashed password.
+ * @throws HashFailedException
+ */
+ public function hash($password, array $options = []) {
+ $hash = password_hash($password, PASSWORD_BCRYPT, [
+ 'cost' => $this->cost($options),
+ ]);
+
+ if (!$hash) {
+ throw new HashFailedException();
+ }
+
+ return $hash;
+ }
+
+ /**
+ * Verify a plaintext password against the user's hashed password.
+ *
+ * @param string $password The plaintext password to verify.
+ * @param string $hash The hash to compare against.
+ * @param array $options
+ * @return boolean True if the password matches, false otherwise.
+ */
+ public function verify($password, $hash, array $options = []) {
+ $hashType = $this->getHashType($hash);
+
+ if ($hashType == 'sha1') {
+ // Legacy UserCake passwords
+ $salt = substr($hash, 0, 25); // Extract the salt from the hash
+ $inputHash = $salt . sha1($salt . $password);
+
+ return (hash_equals($inputHash, $hash) === TRUE);
+
+ } else if ($hashType == 'legacy') {
+ // Homegrown implementation (assuming that current install has been using a cost parameter of 12)
+ // Used for manual implementation of bcrypt.
+ // Note that this legacy hashing put the salt at the _end_ for some reason.
+ $salt = substr($hash, 60);
+ $inputHash = crypt($password, '$2y$12$' . $salt);
+ $correctHash = substr($hash, 0, 60);
+
+ return (hash_equals($inputHash, $correctHash) === TRUE);
+ }
+
+ // Modern implementation
+ return password_verify($password, $hash);
+ }
+
+ /**
+ * Extract the cost value from the options array.
+ *
+ * @param array $options
+ * @return int
+ */
+ protected function cost(array $options = []) {
+ return isset($options['rounds']) ? $options['rounds'] : $this->defaultRounds;
+ }
+}
diff --git a/main/app/sprinkles/account/src/Authorize/AccessConditionExpression.php b/main/app/sprinkles/account/src/Authorize/AccessConditionExpression.php index e36f4f4..8a8225e 100644 --- a/main/app/sprinkles/account/src/Authorize/AccessConditionExpression.php +++ b/main/app/sprinkles/account/src/Authorize/AccessConditionExpression.php @@ -1,138 +1,138 @@ -<?php -/** - * UserFrosting (http://www.userfrosting.com) - * - * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) - */ - -namespace UserFrosting\Sprinkle\Account\Authorize; - -use Monolog\Logger; -use PhpParser\Lexer\Emulative as EmulativeLexer; -use PhpParser\Node; -use PhpParser\NodeTraverser; -use PhpParser\Parser as Parser; -use PhpParser\PrettyPrinter\Standard as StandardPrettyPrinter; -use PhpParser\Error as PhpParserException; -use Psr\Http\Message\ServerRequestInterface as Request; -use UserFrosting\Sprinkle\Account\Database\Models\User; - -/** - * AccessConditionExpression class - * - * This class models the evaluation of an authorization condition expression, as associated with permissions. - * A condition is built as a boolean expression composed of AccessCondition method calls. - * - * @author Alex Weissman (https://alexanderweissman.com) - */ -class AccessConditionExpression -{ - /** - * @var User A user object, which for convenience can be referenced as 'self' in access conditions. - */ - protected $user; - - /** - * @var ParserNodeFunctionEvaluator The node visitor, which evaluates access condition callbacks used in a permission condition. - */ - protected $nodeVisitor; - - /** - * @var \PhpParser\Parser The PhpParser object to use (initialized in the ctor) - */ - protected $parser; - - /** - * @var NodeTraverser The NodeTraverser object to use (initialized in the ctor) - */ - protected $traverser; - - /** - * @var StandardPrettyPrinter The PrettyPrinter object to use (initialized in the ctor) - */ - protected $prettyPrinter; - - /** - * @var Logger - */ - protected $logger; - - /** - * @var bool Set to true if you want debugging information printed to the auth log. - */ - protected $debug; - - /** - * Create a new AccessConditionExpression object. - * - * @param User $user A user object, which for convenience can be referenced as 'self' in access conditions. - * @param Logger $logger A Monolog logger, used to dump debugging info for authorization evaluations. - * @param bool $debug Set to true if you want debugging information printed to the auth log. - */ - public function __construct(ParserNodeFunctionEvaluator $nodeVisitor, User $user, Logger $logger, $debug = FALSE) { - $this->nodeVisitor = $nodeVisitor; - $this->user = $user; - $this->parser = new Parser(new EmulativeLexer); - $this->traverser = new NodeTraverser; - $this->traverser->addVisitor($nodeVisitor); - $this->prettyPrinter = new StandardPrettyPrinter; - $this->logger = $logger; - $this->debug = $debug; - } - - /** - * Evaluates a condition expression, based on the given parameters. - * - * The special parameter `self` is an array of the current user's data. - * This get included automatically, and so does not need to be passed in. - * @param string $condition a boolean expression composed of calls to AccessCondition functions. - * @param array[mixed] $params the parameters to be used when evaluating the expression. - * @return bool true if the condition is passed for the given parameters, otherwise returns false. - */ - public function evaluateCondition($condition, $params) { - // Set the reserved `self` parameters. - // This replaces any values of `self` specified in the arguments, thus preventing them from being overridden in malicious user input. - // (For example, from an unfiltered request body). - $params['self'] = $this->user->export(); - - $this->nodeVisitor->setParams($params); - - $code = "<?php $condition;"; - - if ($this->debug) { - $this->logger->debug("Evaluating access condition '$condition' with parameters:", $params); - } - - // Traverse the parse tree, and execute any callbacks found using the supplied parameters. - // Replace the function node with the return value of the callback. - try { - // parse - $stmts = $this->parser->parse($code); - - // traverse - $stmts = $this->traverser->traverse($stmts); - - // Evaluate boolean statement. It is safe to use eval() here, because our expression has been reduced entirely to a boolean expression. - $expr = $this->prettyPrinter->prettyPrintExpr($stmts[0]); - $expr_eval = "return " . $expr . ";\n"; - $result = eval($expr_eval); - - if ($this->debug) { - $this->logger->debug("Expression '$expr' evaluates to " . ($result == TRUE ? "true" : "false")); - } - - return $result; - } catch (PhpParserException $e) { - if ($this->debug) { - $this->logger->debug("Error parsing access condition '$condition':" . $e->getMessage()); - } - return FALSE; // Access fails if the access condition can't be parsed. - } catch (AuthorizationException $e) { - if ($this->debug) { - $this->logger->debug("Error parsing access condition '$condition':" . $e->getMessage()); - } - return FALSE; - } - } -} +<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Account\Authorize;
+
+use Monolog\Logger;
+use PhpParser\Lexer\Emulative as EmulativeLexer;
+use PhpParser\Node;
+use PhpParser\NodeTraverser;
+use PhpParser\Parser as Parser;
+use PhpParser\PrettyPrinter\Standard as StandardPrettyPrinter;
+use PhpParser\Error as PhpParserException;
+use Psr\Http\Message\ServerRequestInterface as Request;
+use UserFrosting\Sprinkle\Account\Database\Models\User;
+
+/**
+ * AccessConditionExpression class
+ *
+ * This class models the evaluation of an authorization condition expression, as associated with permissions.
+ * A condition is built as a boolean expression composed of AccessCondition method calls.
+ *
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+class AccessConditionExpression
+{
+ /**
+ * @var User A user object, which for convenience can be referenced as 'self' in access conditions.
+ */
+ protected $user;
+
+ /**
+ * @var ParserNodeFunctionEvaluator The node visitor, which evaluates access condition callbacks used in a permission condition.
+ */
+ protected $nodeVisitor;
+
+ /**
+ * @var \PhpParser\Parser The PhpParser object to use (initialized in the ctor)
+ */
+ protected $parser;
+
+ /**
+ * @var NodeTraverser The NodeTraverser object to use (initialized in the ctor)
+ */
+ protected $traverser;
+
+ /**
+ * @var StandardPrettyPrinter The PrettyPrinter object to use (initialized in the ctor)
+ */
+ protected $prettyPrinter;
+
+ /**
+ * @var Logger
+ */
+ protected $logger;
+
+ /**
+ * @var bool Set to true if you want debugging information printed to the auth log.
+ */
+ protected $debug;
+
+ /**
+ * Create a new AccessConditionExpression object.
+ *
+ * @param User $user A user object, which for convenience can be referenced as 'self' in access conditions.
+ * @param Logger $logger A Monolog logger, used to dump debugging info for authorization evaluations.
+ * @param bool $debug Set to true if you want debugging information printed to the auth log.
+ */
+ public function __construct(ParserNodeFunctionEvaluator $nodeVisitor, User $user, Logger $logger, $debug = FALSE) {
+ $this->nodeVisitor = $nodeVisitor;
+ $this->user = $user;
+ $this->parser = new Parser(new EmulativeLexer);
+ $this->traverser = new NodeTraverser;
+ $this->traverser->addVisitor($nodeVisitor);
+ $this->prettyPrinter = new StandardPrettyPrinter;
+ $this->logger = $logger;
+ $this->debug = $debug;
+ }
+
+ /**
+ * Evaluates a condition expression, based on the given parameters.
+ *
+ * The special parameter `self` is an array of the current user's data.
+ * This get included automatically, and so does not need to be passed in.
+ * @param string $condition a boolean expression composed of calls to AccessCondition functions.
+ * @param array[mixed] $params the parameters to be used when evaluating the expression.
+ * @return bool true if the condition is passed for the given parameters, otherwise returns false.
+ */
+ public function evaluateCondition($condition, $params) {
+ // Set the reserved `self` parameters.
+ // This replaces any values of `self` specified in the arguments, thus preventing them from being overridden in malicious user input.
+ // (For example, from an unfiltered request body).
+ $params['self'] = $this->user->export();
+
+ $this->nodeVisitor->setParams($params);
+
+ $code = "<?php $condition;";
+
+ if ($this->debug) {
+ $this->logger->debug("Evaluating access condition '$condition' with parameters:", $params);
+ }
+
+ // Traverse the parse tree, and execute any callbacks found using the supplied parameters.
+ // Replace the function node with the return value of the callback.
+ try {
+ // parse
+ $stmts = $this->parser->parse($code);
+
+ // traverse
+ $stmts = $this->traverser->traverse($stmts);
+
+ // Evaluate boolean statement. It is safe to use eval() here, because our expression has been reduced entirely to a boolean expression.
+ $expr = $this->prettyPrinter->prettyPrintExpr($stmts[0]);
+ $expr_eval = "return " . $expr . ";\n";
+ $result = eval($expr_eval);
+
+ if ($this->debug) {
+ $this->logger->debug("Expression '$expr' evaluates to " . ($result == TRUE ? "true" : "false"));
+ }
+
+ return $result;
+ } catch (PhpParserException $e) {
+ if ($this->debug) {
+ $this->logger->debug("Error parsing access condition '$condition':" . $e->getMessage());
+ }
+ return FALSE; // Access fails if the access condition can't be parsed.
+ } catch (AuthorizationException $e) {
+ if ($this->debug) {
+ $this->logger->debug("Error parsing access condition '$condition':" . $e->getMessage());
+ }
+ return FALSE;
+ }
+ }
+}
diff --git a/main/app/sprinkles/account/src/Authorize/AuthorizationException.php b/main/app/sprinkles/account/src/Authorize/AuthorizationException.php index 33f3d35..f93e847 100644 --- a/main/app/sprinkles/account/src/Authorize/AuthorizationException.php +++ b/main/app/sprinkles/account/src/Authorize/AuthorizationException.php @@ -1,24 +1,24 @@ -<?php -/** - * UserFrosting (http://www.userfrosting.com) - * - * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) - */ - -namespace UserFrosting\Sprinkle\Account\Authorize; - -use UserFrosting\Support\Exception\ForbiddenException; - -/** - * AuthorizationException class - * - * Exception for AccessConditionExpression. - * - * @author Alex Weissman (https://alexanderweissman.com) - * @see http://www.userfrosting.com/components/#authorization - */ -class AuthorizationException extends ForbiddenException -{ - -} +<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Account\Authorize;
+
+use UserFrosting\Support\Exception\ForbiddenException;
+
+/**
+ * AuthorizationException class
+ *
+ * Exception for AccessConditionExpression.
+ *
+ * @author Alex Weissman (https://alexanderweissman.com)
+ * @see http://www.userfrosting.com/components/#authorization
+ */
+class AuthorizationException extends ForbiddenException
+{
+
+}
diff --git a/main/app/sprinkles/account/src/Authorize/AuthorizationManager.php b/main/app/sprinkles/account/src/Authorize/AuthorizationManager.php index f9fb196..487881f 100644 --- a/main/app/sprinkles/account/src/Authorize/AuthorizationManager.php +++ b/main/app/sprinkles/account/src/Authorize/AuthorizationManager.php @@ -1,153 +1,153 @@ -<?php -/** - * UserFrosting (http://www.userfrosting.com) - * - * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) - */ - -namespace UserFrosting\Sprinkle\Account\Authorize; - -use Interop\Container\ContainerInterface; -use UserFrosting\Sprinkle\Account\Database\Models\User; - -/** - * AuthorizationManager class. - * - * Manages a collection of access condition callbacks, and uses them to perform access control checks on user objects. - * @author Alex Weissman (https://alexanderweissman.com) - */ -class AuthorizationManager -{ - /** - * @var ContainerInterface The global container object, which holds all your services. - */ - protected $ci; - - /** - * @var array[callable] An array of callbacks that accept some parameters and evaluate to true or false. - */ - protected $callbacks = []; - - /** - * Create a new AuthorizationManager object. - * - * @param ContainerInterface $ci The global container object, which holds all your services. - */ - public function __construct(ContainerInterface $ci, array $callbacks = []) { - $this->ci = $ci; - $this->callbacks = $callbacks; - } - - /** - * Register an authorization callback, which can then be used in permission conditions. - * - * To add additional callbacks, simply extend the `authorizer` service in your Sprinkle's service provider. - * @param string $name - * @param callable $callback - */ - public function addCallback($name, $callback) { - $this->callbacks[$name] = $callback; - return $this; - } - - /** - * Get all authorization callbacks. - * - * @return callable[] - */ - public function getCallbacks() { - return $this->callbacks; - } - - /** - * Checks whether or not a user has access on a particular permission slug. - * - * Determine if this user has access to the given $slug under the given $params. - * - * @param UserFrosting\Sprinkle\Account\Database\Models\User $user - * @param string $slug The permission slug to check for access. - * @param array $params [optional] An array of field names => values, specifying any additional data to provide the authorization module - * when determining whether or not this user has access. - * @return boolean True if the user has access, false otherwise. - */ - public function checkAccess(User $user, $slug, array $params = []) { - $debug = $this->ci->config['debug.auth']; - - if ($debug) { - $trace = array_slice(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3), 1); - $this->ci->authLogger->debug("Authorization check requested at: ", $trace); - $this->ci->authLogger->debug("Checking authorization for user {$user->id} ('{$user->user_name}') on permission '$slug'..."); - } - - if ($this->ci->authenticator->guest()) { - if ($debug) { - $this->ci->authLogger->debug("User is not logged in. Access denied."); - } - return FALSE; - } - - // The master (root) account has access to everything. - // Need to use loose comparison for now, because some DBs return `id` as a string. - - if ($user->id == $this->ci->config['reserved_user_ids.master']) { - if ($debug) { - $this->ci->authLogger->debug("User is the master (root) user. Access granted."); - } - return TRUE; - } - - // Find all permissions that apply to this user (via roles), and check if any evaluate to true. - $permissions = $user->getCachedPermissions(); - - if (empty($permissions) || !isset($permissions[$slug])) { - if ($debug) { - $this->ci->authLogger->debug("No matching permissions found. Access denied."); - } - return FALSE; - } - - $permissions = $permissions[$slug]; - - if ($debug) { - $this->ci->authLogger->debug("Found matching permissions: \n" . print_r($this->getPermissionsArrayDebugInfo($permissions), TRUE)); - } - - $nodeVisitor = new ParserNodeFunctionEvaluator($this->callbacks, $this->ci->authLogger, $debug); - $ace = new AccessConditionExpression($nodeVisitor, $user, $this->ci->authLogger, $debug); - - foreach ($permissions as $permission) { - $pass = $ace->evaluateCondition($permission->conditions, $params); - if ($pass) { - if ($debug) { - $this->ci->authLogger->debug("User passed conditions '{$permission->conditions}' . Access granted."); - } - return TRUE; - } - } - - if ($debug) { - $this->ci->authLogger->debug("User failed to pass any of the matched permissions. Access denied."); - } - - return FALSE; - } - - /** - * Remove extraneous information from the permission to reduce verbosity. - * - * @param array - * @return array - */ - protected function getPermissionsArrayDebugInfo($permissions) { - $permissionsInfo = []; - foreach ($permissions as $permission) { - $permissionData = array_only($permission->toArray(), ['id', 'slug', 'name', 'conditions', 'description']); - // Remove this until we can find an efficient way to only load these once during debugging - //$permissionData['roles_via'] = $permission->roles_via->pluck('id')->all(); - $permissionsInfo[] = $permissionData; - } - - return $permissionsInfo; - } -} +<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Account\Authorize;
+
+use Interop\Container\ContainerInterface;
+use UserFrosting\Sprinkle\Account\Database\Models\User;
+
+/**
+ * AuthorizationManager class.
+ *
+ * Manages a collection of access condition callbacks, and uses them to perform access control checks on user objects.
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+class AuthorizationManager
+{
+ /**
+ * @var ContainerInterface The global container object, which holds all your services.
+ */
+ protected $ci;
+
+ /**
+ * @var array[callable] An array of callbacks that accept some parameters and evaluate to true or false.
+ */
+ protected $callbacks = [];
+
+ /**
+ * Create a new AuthorizationManager object.
+ *
+ * @param ContainerInterface $ci The global container object, which holds all your services.
+ */
+ public function __construct(ContainerInterface $ci, array $callbacks = []) {
+ $this->ci = $ci;
+ $this->callbacks = $callbacks;
+ }
+
+ /**
+ * Register an authorization callback, which can then be used in permission conditions.
+ *
+ * To add additional callbacks, simply extend the `authorizer` service in your Sprinkle's service provider.
+ * @param string $name
+ * @param callable $callback
+ */
+ public function addCallback($name, $callback) {
+ $this->callbacks[$name] = $callback;
+ return $this;
+ }
+
+ /**
+ * Get all authorization callbacks.
+ *
+ * @return callable[]
+ */
+ public function getCallbacks() {
+ return $this->callbacks;
+ }
+
+ /**
+ * Checks whether or not a user has access on a particular permission slug.
+ *
+ * Determine if this user has access to the given $slug under the given $params.
+ *
+ * @param UserFrosting\Sprinkle\Account\Database\Models\User $user
+ * @param string $slug The permission slug to check for access.
+ * @param array $params [optional] An array of field names => values, specifying any additional data to provide the authorization module
+ * when determining whether or not this user has access.
+ * @return boolean True if the user has access, false otherwise.
+ */
+ public function checkAccess(User $user, $slug, array $params = []) {
+ $debug = $this->ci->config['debug.auth'];
+
+ if ($debug) {
+ $trace = array_slice(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3), 1);
+ $this->ci->authLogger->debug("Authorization check requested at: ", $trace);
+ $this->ci->authLogger->debug("Checking authorization for user {$user->id} ('{$user->user_name}') on permission '$slug'...");
+ }
+
+ if ($this->ci->authenticator->guest()) {
+ if ($debug) {
+ $this->ci->authLogger->debug("User is not logged in. Access denied.");
+ }
+ return FALSE;
+ }
+
+ // The master (root) account has access to everything.
+ // Need to use loose comparison for now, because some DBs return `id` as a string.
+
+ if ($user->id == $this->ci->config['reserved_user_ids.master']) {
+ if ($debug) {
+ $this->ci->authLogger->debug("User is the master (root) user. Access granted.");
+ }
+ return TRUE;
+ }
+
+ // Find all permissions that apply to this user (via roles), and check if any evaluate to true.
+ $permissions = $user->getCachedPermissions();
+
+ if (empty($permissions) || !isset($permissions[$slug])) {
+ if ($debug) {
+ $this->ci->authLogger->debug("No matching permissions found. Access denied.");
+ }
+ return FALSE;
+ }
+
+ $permissions = $permissions[$slug];
+
+ if ($debug) {
+ $this->ci->authLogger->debug("Found matching permissions: \n" . print_r($this->getPermissionsArrayDebugInfo($permissions), TRUE));
+ }
+
+ $nodeVisitor = new ParserNodeFunctionEvaluator($this->callbacks, $this->ci->authLogger, $debug);
+ $ace = new AccessConditionExpression($nodeVisitor, $user, $this->ci->authLogger, $debug);
+
+ foreach ($permissions as $permission) {
+ $pass = $ace->evaluateCondition($permission->conditions, $params);
+ if ($pass) {
+ if ($debug) {
+ $this->ci->authLogger->debug("User passed conditions '{$permission->conditions}' . Access granted.");
+ }
+ return TRUE;
+ }
+ }
+
+ if ($debug) {
+ $this->ci->authLogger->debug("User failed to pass any of the matched permissions. Access denied.");
+ }
+
+ return FALSE;
+ }
+
+ /**
+ * Remove extraneous information from the permission to reduce verbosity.
+ *
+ * @param array
+ * @return array
+ */
+ protected function getPermissionsArrayDebugInfo($permissions) {
+ $permissionsInfo = [];
+ foreach ($permissions as $permission) {
+ $permissionData = array_only($permission->toArray(), ['id', 'slug', 'name', 'conditions', 'description']);
+ // Remove this until we can find an efficient way to only load these once during debugging
+ //$permissionData['roles_via'] = $permission->roles_via->pluck('id')->all();
+ $permissionsInfo[] = $permissionData;
+ }
+
+ return $permissionsInfo;
+ }
+}
diff --git a/main/app/sprinkles/account/src/Authorize/ParserNodeFunctionEvaluator.php b/main/app/sprinkles/account/src/Authorize/ParserNodeFunctionEvaluator.php index e0db07d..af26d9a 100644 --- a/main/app/sprinkles/account/src/Authorize/ParserNodeFunctionEvaluator.php +++ b/main/app/sprinkles/account/src/Authorize/ParserNodeFunctionEvaluator.php @@ -1,189 +1,189 @@ -<?php -/** - * UserFrosting (http://www.userfrosting.com) - * - * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) - */ - -namespace UserFrosting\Sprinkle\Account\Authorize; - -use Monolog\Logger; -use PhpParser\Node; -use PhpParser\NodeVisitorAbstract; -use PhpParser\PrettyPrinter\Standard as StandardPrettyPrinter; - -/** - * ParserNodeFunctionEvaluator class - * - * This class parses access control condition expressions. - * - * @author Alex Weissman (https://alexanderweissman.com) - * @see http://www.userfrosting.com/components/#authorization - */ -class ParserNodeFunctionEvaluator extends NodeVisitorAbstract -{ - - /** - * @var array[callable] An array of callback functions to be used when evaluating a condition expression. - */ - protected $callbacks; - /** - * @var \PhpParser\PrettyPrinter\Standard The PrettyPrinter object to use (initialized in the ctor) - */ - protected $prettyPrinter; - /** - * @var array The parameters to be used when evaluating the methods in the condition expression, as an array. - */ - protected $params = []; - - /** - * @var Logger - */ - protected $logger; - - /** - * @var bool Set to true if you want debugging information printed to the auth log. - */ - protected $debug; - - /** - * Create a new ParserNodeFunctionEvaluator object. - * - * @param array $params The parameters to be used when evaluating the methods in the condition expression, as an array. - * @param Logger $logger A Monolog logger, used to dump debugging info for authorization evaluations. - * @param bool $debug Set to true if you want debugging information printed to the auth log. - */ - public function __construct($callbacks, $logger, $debug = FALSE) { - $this->callbacks = $callbacks; - $this->prettyPrinter = new StandardPrettyPrinter; - $this->logger = $logger; - $this->debug = $debug; - $this->params = []; - } - - public function leaveNode(Node $node) { - // Look for function calls - if ($node instanceof \PhpParser\Node\Expr\FuncCall) { - $eval = new \PhpParser\Node\Scalar\LNumber; - - // Get the method name - $callbackName = $node->name->toString(); - // Get the method arguments - $argNodes = $node->args; - - $args = []; - $argsInfo = []; - foreach ($argNodes as $arg) { - $argString = $this->prettyPrinter->prettyPrintExpr($arg->value); - - // Debugger info - $currentArgInfo = [ - 'expression' => $argString - ]; - // Resolve parameter placeholders ('variable' names (either single-word or array-dot identifiers)) - if (($arg->value instanceof \PhpParser\Node\Expr\BinaryOp\Concat) || ($arg->value instanceof \PhpParser\Node\Expr\ConstFetch)) { - $value = $this->resolveParamPath($argString); - $currentArgInfo['type'] = "parameter"; - $currentArgInfo['resolved_value'] = $value; - // Resolve arrays - } else if ($arg->value instanceof \PhpParser\Node\Expr\Array_) { - $value = $this->resolveArray($arg); - $currentArgInfo['type'] = "array"; - $currentArgInfo['resolved_value'] = print_r($value, TRUE); - // Resolve strings - } else if ($arg->value instanceof \PhpParser\Node\Scalar\String_) { - $value = $arg->value->value; - $currentArgInfo['type'] = "string"; - $currentArgInfo['resolved_value'] = $value; - // Resolve numbers - } else if ($arg->value instanceof \PhpParser\Node\Scalar\DNumber) { - $value = $arg->value->value; - $currentArgInfo['type'] = "float"; - $currentArgInfo['resolved_value'] = $value; - } else if ($arg->value instanceof \PhpParser\Node\Scalar\LNumber) { - $value = $arg->value->value; - $currentArgInfo['type'] = "integer"; - $currentArgInfo['resolved_value'] = $value; - // Anything else is simply interpreted as its literal string value - } else { - $value = $argString; - $currentArgInfo['type'] = "unknown"; - $currentArgInfo['resolved_value'] = $value; - } - - $args[] = $value; - $argsInfo[] = $currentArgInfo; - } - - if ($this->debug) { - if (count($args)) { - $this->logger->debug("Evaluating callback '$callbackName' on: ", $argsInfo); - } else { - $this->logger->debug("Evaluating callback '$callbackName'..."); - } - } - - // Call the specified access condition callback with the specified arguments. - if (isset($this->callbacks[$callbackName]) && is_callable($this->callbacks[$callbackName])) { - $result = call_user_func_array($this->callbacks[$callbackName], $args); - } else { - throw new AuthorizationException("Authorization failed: Access condition method '$callbackName' does not exist."); - } - - if ($this->debug) { - $this->logger->debug("Result: " . ($result ? "1" : "0")); - } - - return new \PhpParser\Node\Scalar\LNumber($result ? "1" : "0"); - } - } - - public function setParams($params) { - $this->params = $params; - } - - /** - * Resolve an array expression in a condition expression into an actual array. - * - * @param string $arg the array, represented as a string. - * @return array[mixed] the array, as a plain ol' PHP array. - */ - private function resolveArray($arg) { - $arr = []; - $items = (array)$arg->value->items; - foreach ($items as $item) { - if ($item->key) { - $arr[$item->key] = $item->value->value; - } else { - $arr[] = $item->value->value; - } - } - return $arr; - } - - /** - * Resolve a parameter path (e.g. "user.id", "post", etc) into its value. - * - * @param string $path the name of the parameter to resolve, based on the parameters set in this object. - * @throws Exception the path could not be resolved. Path is malformed or key does not exist. - * @return mixed the value of the specified parameter. - */ - private function resolveParamPath($path) { - $pathTokens = explode(".", $path); - $value = $this->params; - foreach ($pathTokens as $token) { - $token = trim($token); - if (is_array($value) && isset($value[$token])) { - $value = $value[$token]; - continue; - } else if (is_object($value) && isset($value->$token)) { - $value = $value->$token; - continue; - } else { - throw new AuthorizationException("Cannot resolve the path \"$path\". Error at token \"$token\"."); - } - } - return $value; - } -} +<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Account\Authorize;
+
+use Monolog\Logger;
+use PhpParser\Node;
+use PhpParser\NodeVisitorAbstract;
+use PhpParser\PrettyPrinter\Standard as StandardPrettyPrinter;
+
+/**
+ * ParserNodeFunctionEvaluator class
+ *
+ * This class parses access control condition expressions.
+ *
+ * @author Alex Weissman (https://alexanderweissman.com)
+ * @see http://www.userfrosting.com/components/#authorization
+ */
+class ParserNodeFunctionEvaluator extends NodeVisitorAbstract
+{
+
+ /**
+ * @var array[callable] An array of callback functions to be used when evaluating a condition expression.
+ */
+ protected $callbacks;
+ /**
+ * @var \PhpParser\PrettyPrinter\Standard The PrettyPrinter object to use (initialized in the ctor)
+ */
+ protected $prettyPrinter;
+ /**
+ * @var array The parameters to be used when evaluating the methods in the condition expression, as an array.
+ */
+ protected $params = [];
+
+ /**
+ * @var Logger
+ */
+ protected $logger;
+
+ /**
+ * @var bool Set to true if you want debugging information printed to the auth log.
+ */
+ protected $debug;
+
+ /**
+ * Create a new ParserNodeFunctionEvaluator object.
+ *
+ * @param array $params The parameters to be used when evaluating the methods in the condition expression, as an array.
+ * @param Logger $logger A Monolog logger, used to dump debugging info for authorization evaluations.
+ * @param bool $debug Set to true if you want debugging information printed to the auth log.
+ */
+ public function __construct($callbacks, $logger, $debug = FALSE) {
+ $this->callbacks = $callbacks;
+ $this->prettyPrinter = new StandardPrettyPrinter;
+ $this->logger = $logger;
+ $this->debug = $debug;
+ $this->params = [];
+ }
+
+ public function leaveNode(Node $node) {
+ // Look for function calls
+ if ($node instanceof \PhpParser\Node\Expr\FuncCall) {
+ $eval = new \PhpParser\Node\Scalar\LNumber;
+
+ // Get the method name
+ $callbackName = $node->name->toString();
+ // Get the method arguments
+ $argNodes = $node->args;
+
+ $args = [];
+ $argsInfo = [];
+ foreach ($argNodes as $arg) {
+ $argString = $this->prettyPrinter->prettyPrintExpr($arg->value);
+
+ // Debugger info
+ $currentArgInfo = [
+ 'expression' => $argString
+ ];
+ // Resolve parameter placeholders ('variable' names (either single-word or array-dot identifiers))
+ if (($arg->value instanceof \PhpParser\Node\Expr\BinaryOp\Concat) || ($arg->value instanceof \PhpParser\Node\Expr\ConstFetch)) {
+ $value = $this->resolveParamPath($argString);
+ $currentArgInfo['type'] = "parameter";
+ $currentArgInfo['resolved_value'] = $value;
+ // Resolve arrays
+ } else if ($arg->value instanceof \PhpParser\Node\Expr\Array_) {
+ $value = $this->resolveArray($arg);
+ $currentArgInfo['type'] = "array";
+ $currentArgInfo['resolved_value'] = print_r($value, TRUE);
+ // Resolve strings
+ } else if ($arg->value instanceof \PhpParser\Node\Scalar\String_) {
+ $value = $arg->value->value;
+ $currentArgInfo['type'] = "string";
+ $currentArgInfo['resolved_value'] = $value;
+ // Resolve numbers
+ } else if ($arg->value instanceof \PhpParser\Node\Scalar\DNumber) {
+ $value = $arg->value->value;
+ $currentArgInfo['type'] = "float";
+ $currentArgInfo['resolved_value'] = $value;
+ } else if ($arg->value instanceof \PhpParser\Node\Scalar\LNumber) {
+ $value = $arg->value->value;
+ $currentArgInfo['type'] = "integer";
+ $currentArgInfo['resolved_value'] = $value;
+ // Anything else is simply interpreted as its literal string value
+ } else {
+ $value = $argString;
+ $currentArgInfo['type'] = "unknown";
+ $currentArgInfo['resolved_value'] = $value;
+ }
+
+ $args[] = $value;
+ $argsInfo[] = $currentArgInfo;
+ }
+
+ if ($this->debug) {
+ if (count($args)) {
+ $this->logger->debug("Evaluating callback '$callbackName' on: ", $argsInfo);
+ } else {
+ $this->logger->debug("Evaluating callback '$callbackName'...");
+ }
+ }
+
+ // Call the specified access condition callback with the specified arguments.
+ if (isset($this->callbacks[$callbackName]) && is_callable($this->callbacks[$callbackName])) {
+ $result = call_user_func_array($this->callbacks[$callbackName], $args);
+ } else {
+ throw new AuthorizationException("Authorization failed: Access condition method '$callbackName' does not exist.");
+ }
+
+ if ($this->debug) {
+ $this->logger->debug("Result: " . ($result ? "1" : "0"));
+ }
+
+ return new \PhpParser\Node\Scalar\LNumber($result ? "1" : "0");
+ }
+ }
+
+ public function setParams($params) {
+ $this->params = $params;
+ }
+
+ /**
+ * Resolve an array expression in a condition expression into an actual array.
+ *
+ * @param string $arg the array, represented as a string.
+ * @return array[mixed] the array, as a plain ol' PHP array.
+ */
+ private function resolveArray($arg) {
+ $arr = [];
+ $items = (array)$arg->value->items;
+ foreach ($items as $item) {
+ if ($item->key) {
+ $arr[$item->key] = $item->value->value;
+ } else {
+ $arr[] = $item->value->value;
+ }
+ }
+ return $arr;
+ }
+
+ /**
+ * Resolve a parameter path (e.g. "user.id", "post", etc) into its value.
+ *
+ * @param string $path the name of the parameter to resolve, based on the parameters set in this object.
+ * @throws Exception the path could not be resolved. Path is malformed or key does not exist.
+ * @return mixed the value of the specified parameter.
+ */
+ private function resolveParamPath($path) {
+ $pathTokens = explode(".", $path);
+ $value = $this->params;
+ foreach ($pathTokens as $token) {
+ $token = trim($token);
+ if (is_array($value) && isset($value[$token])) {
+ $value = $value[$token];
+ continue;
+ } else if (is_object($value) && isset($value->$token)) {
+ $value = $value->$token;
+ continue;
+ } else {
+ throw new AuthorizationException("Cannot resolve the path \"$path\". Error at token \"$token\".");
+ }
+ }
+ return $value;
+ }
+}
diff --git a/main/app/sprinkles/account/src/Bakery/CreateAdminUser.php b/main/app/sprinkles/account/src/Bakery/CreateAdminUser.php index f928a2c..178c2b3 100644 --- a/main/app/sprinkles/account/src/Bakery/CreateAdminUser.php +++ b/main/app/sprinkles/account/src/Bakery/CreateAdminUser.php @@ -1,321 +1,321 @@ -<?php -/** - * UserFrosting (http://www.userfrosting.com) - * - * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) - */ - -namespace UserFrosting\Sprinkle\Account\Bakery; - -use Illuminate\Database\Capsule\Manager as Capsule; -use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\Console\Input\InputArgument; -use Symfony\Component\Console\Input\InputOption; -use UserFrosting\System\Bakery\BaseCommand; -use UserFrosting\System\Bakery\DatabaseTest; -use UserFrosting\System\Database\Model\Migrations; -use UserFrosting\Sprinkle\Account\Database\Models\User; -use UserFrosting\Sprinkle\Account\Database\Models\Role; -use UserFrosting\Sprinkle\Account\Facades\Password; - -/** - * Create root user CLI command. - * - * @author Alex Weissman (https://alexanderweissman.com) - */ -class CreateAdminUser extends BaseCommand -{ - use DatabaseTest; - - /** - * @var string[] Migration dependencies for this command to work - */ - protected $dependencies = [ - '\UserFrosting\Sprinkle\Account\Database\Migrations\v400\UsersTable', - '\UserFrosting\Sprinkle\Account\Database\Migrations\v400\RolesTable', - '\UserFrosting\Sprinkle\Account\Database\Migrations\v400\RoleUsersTable' - ]; - - /** - * {@inheritDoc} - */ - protected function configure() { - $this->setName("create-admin") - ->setDescription("Create the initial admin (root) user account"); - } - - /** - * {@inheritDoc} - */ - protected function execute(InputInterface $input, OutputInterface $output) { - $this->io->title("Root account setup"); - - // Need the database - try { - $this->io->writeln("<info>Testing database connection</info>", OutputInterface::VERBOSITY_VERBOSE); - $this->testDB(); - $this->io->writeln("Ok", OutputInterface::VERBOSITY_VERBOSE); - } catch (\Exception $e) { - $this->io->error($e->getMessage()); - exit(1); - } - - // Need migration table - if (!Capsule::schema()->hasColumn('migrations', 'id')) { - $this->io->error("Migrations doesn't appear to have been run! Make sure the database is properly migrated by using the `php bakery migrate` command."); - exit(1); - } - - // Make sure the required mirgations have been run - foreach ($this->dependencies as $migration) { - if (!Migrations::where('migration', $migration)->exists()) { - $this->io->error("Migration `$migration` doesn't appear to have been run! Make sure all migrations are up to date by using the `php bakery migrate` command."); - exit(1); - } - } - - // Make sure that there are no users currently in the user table - // We setup the root account here so it can be done independent of the version check - if (User::count() > 0) { - - $this->io->note("Table 'users' is not empty. Skipping root account setup. To set up the root account again, please truncate or drop the table and try again."); - - } else { - - $this->io->writeln("Please answer the following questions to create the root account:\n"); - - // Get the account details - $userName = $this->askUsername(); - $email = $this->askEmail(); - $firstName = $this->askFirstName(); - $lastName = $this->askLastName(); - $password = $this->askPassword(); - - // Ok, now we've got the info and we can create the new user. - $this->io->write("\n<info>Saving the root user details...</info>"); - $rootUser = new User([ - "user_name" => $userName, - "email" => $email, - "first_name" => $firstName, - "last_name" => $lastName, - "password" => Password::hash($password) - ]); - - $rootUser->save(); - - $defaultRoles = [ - 'user' => Role::where('slug', 'user')->first(), - 'group-admin' => Role::where('slug', 'group-admin')->first(), - 'site-admin' => Role::where('slug', 'site-admin')->first() - ]; - - foreach ($defaultRoles as $slug => $role) { - if ($role) { - $rootUser->roles()->attach($role->id); - } - } - - $this->io->success("Root user creation successful!"); - } - } - - /** - * Ask for the username - * - * @access protected - * @return void - */ - protected function askUsername() { - while (!isset($userName) || !$this->validateUsername($userName)) { - $userName = $this->io->ask("Choose a root username (1-50 characters, no leading or trailing whitespace)"); - } - return $userName; - } - - /** - * Validate the username. - * - * @access protected - * @param mixed $userName - * @return void - */ - protected function validateUsername($userName) { - // Validate length - if (strlen($userName) < 1 || strlen($userName) > 50) { - $this->io->error("Username must be between 1-50 characters"); - return FALSE; - } - - // Validate format - $options = [ - 'options' => [ - 'regexp' => "/^\S((.*\S)|)$/" - ] - ]; - $validate = filter_var($userName, FILTER_VALIDATE_REGEXP, $options); - if (!$validate) { - $this->io->error("Username can't have any leading or trailing whitespace"); - return FALSE; - } - - return TRUE; - } - - /** - * Ask for the email - * - * @access protected - * @return void - */ - protected function askEmail() { - while (!isset($email) || !$this->validateEmail($email)) { - $email = $this->io->ask("Enter a valid email address (1-254 characters, must be compatible with FILTER_VALIDATE_EMAIL)"); - } - return $email; - } - - /** - * Validate the email. - * - * @access protected - * @param mixed $email - * @return void - */ - protected function validateEmail($email) { - // Validate length - if (strlen($email) < 1 || strlen($email) > 254) { - $this->io->error("Email must be between 1-254 characters"); - return FALSE; - } - - // Validate format - if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { - $this->io->error("Email must be compatible with FILTER_VALIDATE_EMAIL"); - return FALSE; - } - - return TRUE; - } - - /** - * Ask for the first name - * - * @access protected - * @return void - */ - protected function askFirstName() { - while (!isset($firstName) || !$this->validateFirstName($firstName)) { - $firstName = $this->io->ask("Enter the user first name (1-20 characters)"); - } - return $firstName; - } - - /** - * validateFirstName function. - * - * @access protected - * @param mixed $name - * @return void - */ - protected function validateFirstName($firstName) { - // Validate length - if (strlen($firstName) < 1 || strlen($firstName) > 20) { - $this->io->error("First name must be between 1-20 characters"); - return FALSE; - } - - return TRUE; - } - - /** - * Ask for the last name - * - * @access protected - * @return void - */ - protected function askLastName() { - while (!isset($lastName) || !$this->validateLastName($lastName)) { - $lastName = $this->io->ask("Enter the user last name (1-30 characters)"); - } - return $lastName; - } - - /** - * validateLastName function. - * - * @access protected - * @param mixed $lastName - * @return void - */ - protected function validateLastName($lastName) { - // Validate length - if (strlen($lastName) < 1 || strlen($lastName) > 30) { - $this->io->error("Last name must be between 1-30 characters"); - return FALSE; - } - - return TRUE; - } - - /** - * Ask for the password - * - * @access protected - * @return void - */ - protected function askPassword() { - while (!isset($password) || !$this->validatePassword($password) || !$this->confirmPassword($password)) { - $password = $this->io->askHidden("Enter password (12-255 characters)"); - } - return $password; - } - - /** - * validatePassword function. - * - * @access protected - * @param mixed $password - * @return void - */ - protected function validatePassword($password) { - if (strlen($password) < 12 || strlen($password) > 255) { - $this->io->error("Password must be between 12-255 characters"); - return FALSE; - } - - return TRUE; - } - - /** - * confirmPassword function. - * - * @access protected - * @param mixed $passwordToConfirm - * @return void - */ - protected function confirmPassword($passwordToConfirm) { - while (!isset($password)) { - $password = $this->io->askHidden("Please re-enter the chosen password"); - } - return $this->validatePasswordConfirmation($password, $passwordToConfirm); - } - - /** - * validatePasswordConfirmation function. - * - * @access protected - * @param mixed $password - * @param mixed $passwordToConfirm - * @return void - */ - protected function validatePasswordConfirmation($password, $passwordToConfirm) { - if ($password != $passwordToConfirm) { - $this->io->error("Passwords do not match, please try again."); - return FALSE; - } - - return TRUE; - } +<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Account\Bakery;
+
+use Illuminate\Database\Capsule\Manager as Capsule;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputOption;
+use UserFrosting\System\Bakery\BaseCommand;
+use UserFrosting\System\Bakery\DatabaseTest;
+use UserFrosting\System\Database\Model\Migrations;
+use UserFrosting\Sprinkle\Account\Database\Models\User;
+use UserFrosting\Sprinkle\Account\Database\Models\Role;
+use UserFrosting\Sprinkle\Account\Facades\Password;
+
+/**
+ * Create root user CLI command.
+ *
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+class CreateAdminUser extends BaseCommand
+{
+ use DatabaseTest;
+
+ /**
+ * @var string[] Migration dependencies for this command to work
+ */
+ protected $dependencies = [
+ '\UserFrosting\Sprinkle\Account\Database\Migrations\v400\UsersTable',
+ '\UserFrosting\Sprinkle\Account\Database\Migrations\v400\RolesTable',
+ '\UserFrosting\Sprinkle\Account\Database\Migrations\v400\RoleUsersTable'
+ ];
+
+ /**
+ * {@inheritDoc}
+ */
+ protected function configure() {
+ $this->setName("create-admin")
+ ->setDescription("Create the initial admin (root) user account");
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ protected function execute(InputInterface $input, OutputInterface $output) {
+ $this->io->title("Root account setup");
+
+ // Need the database
+ try {
+ $this->io->writeln("<info>Testing database connection</info>", OutputInterface::VERBOSITY_VERBOSE);
+ $this->testDB();
+ $this->io->writeln("Ok", OutputInterface::VERBOSITY_VERBOSE);
+ } catch (\Exception $e) {
+ $this->io->error($e->getMessage());
+ exit(1);
+ }
+
+ // Need migration table
+ if (!Capsule::schema()->hasColumn('migrations', 'id')) {
+ $this->io->error("Migrations doesn't appear to have been run! Make sure the database is properly migrated by using the `php bakery migrate` command.");
+ exit(1);
+ }
+
+ // Make sure the required mirgations have been run
+ foreach ($this->dependencies as $migration) {
+ if (!Migrations::where('migration', $migration)->exists()) {
+ $this->io->error("Migration `$migration` doesn't appear to have been run! Make sure all migrations are up to date by using the `php bakery migrate` command.");
+ exit(1);
+ }
+ }
+
+ // Make sure that there are no users currently in the user table
+ // We setup the root account here so it can be done independent of the version check
+ if (User::count() > 0) {
+
+ $this->io->note("Table 'users' is not empty. Skipping root account setup. To set up the root account again, please truncate or drop the table and try again.");
+
+ } else {
+
+ $this->io->writeln("Please answer the following questions to create the root account:\n");
+
+ // Get the account details
+ $userName = $this->askUsername();
+ $email = $this->askEmail();
+ $firstName = $this->askFirstName();
+ $lastName = $this->askLastName();
+ $password = $this->askPassword();
+
+ // Ok, now we've got the info and we can create the new user.
+ $this->io->write("\n<info>Saving the root user details...</info>");
+ $rootUser = new User([
+ "user_name" => $userName,
+ "email" => $email,
+ "first_name" => $firstName,
+ "last_name" => $lastName,
+ "password" => Password::hash($password)
+ ]);
+
+ $rootUser->save();
+
+ $defaultRoles = [
+ 'user' => Role::where('slug', 'user')->first(),
+ 'group-admin' => Role::where('slug', 'group-admin')->first(),
+ 'site-admin' => Role::where('slug', 'site-admin')->first()
+ ];
+
+ foreach ($defaultRoles as $slug => $role) {
+ if ($role) {
+ $rootUser->roles()->attach($role->id);
+ }
+ }
+
+ $this->io->success("Root user creation successful!");
+ }
+ }
+
+ /**
+ * Ask for the username
+ *
+ * @access protected
+ * @return void
+ */
+ protected function askUsername() {
+ while (!isset($userName) || !$this->validateUsername($userName)) {
+ $userName = $this->io->ask("Choose a root username (1-50 characters, no leading or trailing whitespace)");
+ }
+ return $userName;
+ }
+
+ /**
+ * Validate the username.
+ *
+ * @access protected
+ * @param mixed $userName
+ * @return void
+ */
+ protected function validateUsername($userName) {
+ // Validate length
+ if (strlen($userName) < 1 || strlen($userName) > 50) {
+ $this->io->error("Username must be between 1-50 characters");
+ return FALSE;
+ }
+
+ // Validate format
+ $options = [
+ 'options' => [
+ 'regexp' => "/^\S((.*\S)|)$/"
+ ]
+ ];
+ $validate = filter_var($userName, FILTER_VALIDATE_REGEXP, $options);
+ if (!$validate) {
+ $this->io->error("Username can't have any leading or trailing whitespace");
+ return FALSE;
+ }
+
+ return TRUE;
+ }
+
+ /**
+ * Ask for the email
+ *
+ * @access protected
+ * @return void
+ */
+ protected function askEmail() {
+ while (!isset($email) || !$this->validateEmail($email)) {
+ $email = $this->io->ask("Enter a valid email address (1-254 characters, must be compatible with FILTER_VALIDATE_EMAIL)");
+ }
+ return $email;
+ }
+
+ /**
+ * Validate the email.
+ *
+ * @access protected
+ * @param mixed $email
+ * @return void
+ */
+ protected function validateEmail($email) {
+ // Validate length
+ if (strlen($email) < 1 || strlen($email) > 254) {
+ $this->io->error("Email must be between 1-254 characters");
+ return FALSE;
+ }
+
+ // Validate format
+ if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
+ $this->io->error("Email must be compatible with FILTER_VALIDATE_EMAIL");
+ return FALSE;
+ }
+
+ return TRUE;
+ }
+
+ /**
+ * Ask for the first name
+ *
+ * @access protected
+ * @return void
+ */
+ protected function askFirstName() {
+ while (!isset($firstName) || !$this->validateFirstName($firstName)) {
+ $firstName = $this->io->ask("Enter the user first name (1-20 characters)");
+ }
+ return $firstName;
+ }
+
+ /**
+ * validateFirstName function.
+ *
+ * @access protected
+ * @param mixed $name
+ * @return void
+ */
+ protected function validateFirstName($firstName) {
+ // Validate length
+ if (strlen($firstName) < 1 || strlen($firstName) > 20) {
+ $this->io->error("First name must be between 1-20 characters");
+ return FALSE;
+ }
+
+ return TRUE;
+ }
+
+ /**
+ * Ask for the last name
+ *
+ * @access protected
+ * @return void
+ */
+ protected function askLastName() {
+ while (!isset($lastName) || !$this->validateLastName($lastName)) {
+ $lastName = $this->io->ask("Enter the user last name (1-30 characters)");
+ }
+ return $lastName;
+ }
+
+ /**
+ * validateLastName function.
+ *
+ * @access protected
+ * @param mixed $lastName
+ * @return void
+ */
+ protected function validateLastName($lastName) {
+ // Validate length
+ if (strlen($lastName) < 1 || strlen($lastName) > 30) {
+ $this->io->error("Last name must be between 1-30 characters");
+ return FALSE;
+ }
+
+ return TRUE;
+ }
+
+ /**
+ * Ask for the password
+ *
+ * @access protected
+ * @return void
+ */
+ protected function askPassword() {
+ while (!isset($password) || !$this->validatePassword($password) || !$this->confirmPassword($password)) {
+ $password = $this->io->askHidden("Enter password (12-255 characters)");
+ }
+ return $password;
+ }
+
+ /**
+ * validatePassword function.
+ *
+ * @access protected
+ * @param mixed $password
+ * @return void
+ */
+ protected function validatePassword($password) {
+ if (strlen($password) < 12 || strlen($password) > 255) {
+ $this->io->error("Password must be between 12-255 characters");
+ return FALSE;
+ }
+
+ return TRUE;
+ }
+
+ /**
+ * confirmPassword function.
+ *
+ * @access protected
+ * @param mixed $passwordToConfirm
+ * @return void
+ */
+ protected function confirmPassword($passwordToConfirm) {
+ while (!isset($password)) {
+ $password = $this->io->askHidden("Please re-enter the chosen password");
+ }
+ return $this->validatePasswordConfirmation($password, $passwordToConfirm);
+ }
+
+ /**
+ * validatePasswordConfirmation function.
+ *
+ * @access protected
+ * @param mixed $password
+ * @param mixed $passwordToConfirm
+ * @return void
+ */
+ protected function validatePasswordConfirmation($password, $passwordToConfirm) {
+ if ($password != $passwordToConfirm) {
+ $this->io->error("Passwords do not match, please try again.");
+ return FALSE;
+ }
+
+ return TRUE;
+ }
}
\ No newline at end of file diff --git a/main/app/sprinkles/account/src/Controller/AccountController.php b/main/app/sprinkles/account/src/Controller/AccountController.php index 7373923..c4201a7 100644 --- a/main/app/sprinkles/account/src/Controller/AccountController.php +++ b/main/app/sprinkles/account/src/Controller/AccountController.php @@ -1,1271 +1,1271 @@ -<?php -/** - * UserFrosting (http://www.userfrosting.com) - * - * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) - */ - -namespace UserFrosting\Sprinkle\Account\Controller; - -use Carbon\Carbon; -use Illuminate\Database\Capsule\Manager as Capsule; -use Psr\Http\Message\ResponseInterface as Response; -use Psr\Http\Message\ServerRequestInterface as Request; -use Slim\Exception\NotFoundException as NotFoundException; -use UserFrosting\Fortress\RequestDataTransformer; -use UserFrosting\Fortress\RequestSchema; -use UserFrosting\Fortress\ServerSideValidator; -use UserFrosting\Fortress\Adapter\JqueryValidationAdapter; -use UserFrosting\Sprinkle\Account\Controller\Exception\SpammyRequestException; -use UserFrosting\Sprinkle\Account\Facades\Password; -use UserFrosting\Sprinkle\Account\Util\Util as AccountUtil; -use UserFrosting\Sprinkle\Core\Controller\SimpleController; -use UserFrosting\Sprinkle\Core\Mail\EmailRecipient; -use UserFrosting\Sprinkle\Core\Mail\TwigMailMessage; -use UserFrosting\Sprinkle\Core\Util\Captcha; -use UserFrosting\Support\Exception\BadRequestException; -use UserFrosting\Support\Exception\ForbiddenException; -use UserFrosting\Support\Exception\HttpException; - -/** - * Controller class for /account/* URLs. Handles account-related activities, including login, registration, password recovery, and account settings. - * - * @author Alex Weissman (https://alexanderweissman.com) - * @see http://www.userfrosting.com/navigating/#structure - */ -class AccountController extends SimpleController -{ - /** - * Check a username for availability. - * - * This route is throttled by default, to discourage abusing it for account enumeration. - * This route is "public access". - * Request type: GET - * - * @param Request $request - * @param Response $response - * @param array $args - * @return void - */ - public function checkUsername(Request $request, Response $response, $args) { - /** @var \UserFrosting\Sprinkle\Core\Alert\AlertStream $ms */ - $ms = $this->ci->alerts; - - // GET parameters - $params = $request->getQueryParams(); - - // Load request schema - $schema = new RequestSchema('schema://requests/check-username.yaml'); - - // Whitelist and set parameter defaults - $transformer = new RequestDataTransformer($schema); - $data = $transformer->transform($params); - - // Validate, and halt on validation errors. - $validator = new ServerSideValidator($schema, $this->ci->translator); - if (!$validator->validate($data)) { - // O: encapsulate the communication of error messages from ServerSideValidator to the BadRequestException - $e = new BadRequestException('Missing or malformed request data!'); - foreach ($validator->errors() as $idx => $field) { - foreach ($field as $eidx => $error) { - $e->addUserMessage($error); - } - } - throw $e; - } - - /** @var \UserFrosting\Sprinkle\Core\Throttle\Throttler $throttler */ - $throttler = $this->ci->throttler; - $delay = $throttler->getDelay('check_username_request'); - - // Throttle requests - if ($delay > 0) { - return $response->withStatus(429); - } - - /** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ - $classMapper = $this->ci->classMapper; - - /** @var \UserFrosting\I18n\MessageTranslator $translator */ - $translator = $this->ci->translator; - - // Log throttleable event - $throttler->logEvent('check_username_request'); - - if ($classMapper->staticMethod('user', 'findUnique', $data['user_name'], 'user_name')) { - $message = $translator->translate('USERNAME.NOT_AVAILABLE', $data); - return $response->write($message)->withStatus(200); - } else { - return $response->write('true')->withStatus(200); - } - } - - /** - * Processes a request to cancel a password reset request. - * - * This is provided so that users can cancel a password reset request, if they made it in error or if it was not initiated by themselves. - * Processes the request from the password reset link, checking that: - * 1. The provided token is associated with an existing user account, who has a pending password reset request. - * Request type: GET - * - * @param Request $request - * @param Response $response - * @param array $args - * @return void - */ - public function denyResetPassword(Request $request, Response $response, $args) { - // GET parameters - $params = $request->getQueryParams(); - - /** @var \UserFrosting\Sprinkle\Core\Alert\AlertStream $ms */ - $ms = $this->ci->alerts; - - /** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ - $classMapper = $this->ci->classMapper; - - $loginPage = $this->ci->router->pathFor('login'); - - // Load validation rules - $schema = new RequestSchema('schema://requests/deny-password.yaml'); - - // Whitelist and set parameter defaults - $transformer = new RequestDataTransformer($schema); - $data = $transformer->transform($params); - - // Validate, and halt on validation errors. Since this is a GET request, we need to redirect on failure - $validator = new ServerSideValidator($schema, $this->ci->translator); - if (!$validator->validate($data)) { - $ms->addValidationErrors($validator); - // 400 code + redirect is perfectly fine, according to user Dilaz in #laravel - return $response->withRedirect($loginPage, 400); - } - - $passwordReset = $this->ci->repoPasswordReset->cancel($data['token']); - - if (!$passwordReset) { - $ms->addMessageTranslated('danger', 'PASSWORD.FORGET.INVALID'); - return $response->withRedirect($loginPage, 400); - } - - $ms->addMessageTranslated('success', 'PASSWORD.FORGET.REQUEST_CANNED'); - return $response->withRedirect($loginPage); - } - - /** - * Processes a request to email a forgotten password reset link to the user. - * - * Processes the request from the form on the "forgot password" page, checking that: - * 1. The rate limit for this type of request is being observed. - * 2. The provided email address belongs to a registered account; - * 3. The submitted data is valid. - * Note that we have removed the requirement that a password reset request not already be in progress. - * This is because we need to allow users to re-request a reset, even if they lose the first reset email. - * This route is "public access". - * Request type: POST - * @odo require additional user information - * @odo prevent password reset requests for root account? - * - * @param Request $request - * @param Response $response - * @param array $args - * @return void - */ - public function forgotPassword(Request $request, Response $response, $args) { - /** @var \UserFrosting\Sprinkle\Core\Alert\AlertStream $ms */ - $ms = $this->ci->alerts; - - /** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ - $classMapper = $this->ci->classMapper; - - /** @var \UserFrosting\Support\Repository\Repository $config */ - $config = $this->ci->config; - - // Get POST parameters - $params = $request->getParsedBody(); - - // Load the request schema - $schema = new RequestSchema('schema://requests/forgot-password.yaml'); - - // Whitelist and set parameter defaults - $transformer = new RequestDataTransformer($schema); - $data = $transformer->transform($params); - - // Validate, and halt on validation errors. Failed validation attempts do not count towards throttling limit. - $validator = new ServerSideValidator($schema, $this->ci->translator); - if (!$validator->validate($data)) { - $ms->addValidationErrors($validator); - return $response->withStatus(400); - } - - // Throttle requests - - /** @var \UserFrosting\Sprinkle\Core\Throttle\Throttler $throttler */ - $throttler = $this->ci->throttler; - - $throttleData = [ - 'email' => $data['email'] - ]; - $delay = $throttler->getDelay('password_reset_request', $throttleData); - - if ($delay > 0) { - $ms->addMessageTranslated('danger', 'RATE_LIMIT_EXCEEDED', ['delay' => $delay]); - return $response->withStatus(429); - } - - // All checks passed! log events/activities, update user, and send email - // Begin transaction - DB will be rolled back if an exception occurs - Capsule::transaction(function () use ($classMapper, $data, $throttler, $throttleData, $config) { - - // Log throttleable event - $throttler->logEvent('password_reset_request', $throttleData); - - // Load the user, by email address - $user = $classMapper->staticMethod('user', 'where', 'email', $data['email'])->first(); - - // Check that the email exists. - // If there is no user with that email address, we should still pretend like we succeeded, to prevent account enumeration - if ($user) { - // Try to generate a new password reset request. - // Use timeout for "reset password" - $passwordReset = $this->ci->repoPasswordReset->create($user, $config['password_reset.timeouts.reset']); - - // Create and send email - $message = new TwigMailMessage($this->ci->view, 'mail/password-reset.html.twig'); - $message->from($config['address_book.admin']) - ->addEmailRecipient(new EmailRecipient($user->email, $user->full_name)) - ->addParams([ - 'user' => $user, - 'token' => $passwordReset->getToken(), - 'request_date' => Carbon::now()->format('Y-m-d H:i:s') - ]); - - $this->ci->mailer->send($message); - } - }); - - $ms->addMessageTranslated('success', 'PASSWORD.FORGET.REQUEST_SENT', ['email' => $data['email']]); - return $response->withStatus(200); - } - - /** - * Returns a modal containing account terms of service. - * - * This does NOT render a complete page. Instead, it renders the HTML for the form, which can be embedded in other pages. - * Request type: GET - * - * @param Request $request - * @param Response $response - * @param array $args - * @return void - */ - public function getModalAccountTos(Request $request, Response $response, $args) { - return $this->ci->view->render($response, 'modals/tos.html.twig'); - } - - /** - * Generate a random captcha, store it to the session, and return the captcha image. - * - * Request type: GET - * - * @param Request $request - * @param Response $response - * @param array $args - * @return void - */ - public function imageCaptcha(Request $request, Response $response, $args) { - $captcha = new Captcha($this->ci->session, $this->ci->config['session.keys.captcha']); - $captcha->generateRandomCode(); - - return $response->withStatus(200) - ->withHeader('Content-Type', 'image/png;base64') - ->write($captcha->getImage()); - } - - /** - * Processes an account login request. - * - * Processes the request from the form on the login page, checking that: - * 1. The user is not already logged in. - * 2. The rate limit for this type of request is being observed. - * 3. Email login is enabled, if an email address was used. - * 4. The user account exists. - * 5. The user account is enabled and verified. - * 6. The user entered a valid username/email and password. - * This route, by definition, is "public access". - * Request type: POST - * - * @param Request $request - * @param Response $response - * @param array $args - * @return void - */ - public function login(Request $request, Response $response, $args) { - /** @var \UserFrosting\Sprinkle\Core\Alert\AlertStream $ms */ - $ms = $this->ci->alerts; - - /** @var \UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */ - $currentUser = $this->ci->currentUser; - - /** @var \UserFrosting\Sprinkle\Account\Authenticate\Authenticator $authenticator */ - $authenticator = $this->ci->authenticator; - - // Return 200 success if user is already logged in - if ($authenticator->check()) { - $ms->addMessageTranslated('warning', 'LOGIN.ALREADY_COMPLETE'); - return $response->withStatus(200); - } - - /** @var \UserFrosting\Support\Repository\Repository $config */ - $config = $this->ci->config; - - // Get POST parameters - $params = $request->getParsedBody(); - - // Load the request schema - $schema = new RequestSchema('schema://requests/login.yaml'); - - // Whitelist and set parameter defaults - $transformer = new RequestDataTransformer($schema); - $data = $transformer->transform($params); - - // Validate, and halt on validation errors. Failed validation attempts do not count towards throttling limit. - $validator = new ServerSideValidator($schema, $this->ci->translator); - if (!$validator->validate($data)) { - $ms->addValidationErrors($validator); - return $response->withStatus(400); - } - - // Determine whether we are trying to log in with an email address or a username - $isEmail = filter_var($data['user_name'], FILTER_VALIDATE_EMAIL); - - // Throttle requests - - /** @var \UserFrosting\Sprinkle\Core\Throttle\Throttler $throttler */ - $throttler = $this->ci->throttler; - - $userIdentifier = $data['user_name']; - - $throttleData = [ - 'user_identifier' => $userIdentifier - ]; - - $delay = $throttler->getDelay('sign_in_attempt', $throttleData); - if ($delay > 0) { - $ms->addMessageTranslated('danger', 'RATE_LIMIT_EXCEEDED', [ - 'delay' => $delay - ]); - return $response->withStatus(429); - } - - // Log throttleable event - $throttler->logEvent('sign_in_attempt', $throttleData); - - // If credential is an email address, but email login is not enabled, raise an error. - // Note that we do this after logging throttle event, so this error counts towards throttling limit. - if ($isEmail && !$config['site.login.enable_email']) { - $ms->addMessageTranslated('danger', 'USER_OR_PASS_INVALID'); - return $response->withStatus(403); - } - - // Try to authenticate the user. Authenticator will throw an exception on failure. - /** @var \UserFrosting\Sprinkle\Account\Authenticate\Authenticator $authenticator */ - $authenticator = $this->ci->authenticator; - - $currentUser = $authenticator->attempt(($isEmail ? 'email' : 'user_name'), $userIdentifier, $data['password'], $data['rememberme']); - - $ms->addMessageTranslated('success', 'WELCOME', $currentUser->export()); - - // Set redirect, if relevant - $redirectOnLogin = $this->ci->get('redirect.onLogin'); - - return $redirectOnLogin($request, $response, $args); - } - - /** - * Log the user out completely, including destroying any "remember me" token. - * - * Request type: GET - * - * @param Request $request - * @param Response $response - * @param array $args - * @return void - */ - public function logout(Request $request, Response $response, $args) { - // Destroy the session - $this->ci->authenticator->logout(); - - // Return to home page - $config = $this->ci->config; - return $response->withStatus(302)->withHeader('Location', $config['site.uri.public']); - } - - /** - * Render the "forgot password" page. - * - * This creates a simple form to allow users who forgot their password to have a time-limited password reset link emailed to them. - * By default, this is a "public page" (does not require authentication). - * Request type: GET - * - * @param Request $request - * @param Response $response - * @param array $args - * @return void - */ - public function pageForgotPassword(Request $request, Response $response, $args) { - // Load validation rules - $schema = new RequestSchema('schema://requests/forgot-password.yaml'); - $validator = new JqueryValidationAdapter($schema, $this->ci->translator); - - return $this->ci->view->render($response, 'pages/forgot-password.html.twig', [ - 'page' => [ - 'validators' => [ - 'forgot_password' => $validator->rules('json', FALSE) - ] - ] - ]); - } - - - /** - * Render the account registration page for UserFrosting. - * - * This allows new (non-authenticated) users to create a new account for themselves on your website (if enabled). - * By definition, this is a "public page" (does not require authentication). - * Request type: GET - * - * @param Request $request - * @param Response $response - * @param array $args - * @return void - */ - public function pageRegister(Request $request, Response $response, $args) { - /** @var \UserFrosting\Support\Repository\Repository $config */ - $config = $this->ci->config; - - if (!$config['site.registration.enabled']) { - throw new NotFoundException($request, $response); - } - - /** @var \UserFrosting\Sprinkle\Account\Authenticate\Authenticator $authenticator */ - $authenticator = $this->ci->authenticator; - - // Redirect if user is already logged in - if ($authenticator->check()) { - $redirect = $this->ci->get('redirect.onAlreadyLoggedIn'); - - return $redirect($request, $response, $args); - } - - // Load validation rules - $schema = new RequestSchema('schema://requests/register.yaml'); - $validatorRegister = new JqueryValidationAdapter($schema, $this->ci->translator); - - return $this->ci->view->render($response, 'pages/register.html.twig', [ - 'page' => [ - 'validators' => [ - 'register' => $validatorRegister->rules('json', FALSE) - ] - ] - ]); - } - - /** - * Render the "resend verification email" page. - * - * This is a form that allows users who lost their account verification link to have the link resent to their email address. - * By default, this is a "public page" (does not require authentication). - * Request type: GET - * - * @param Request $request - * @param Response $response - * @param array $args - * @return void - */ - public function pageResendVerification(Request $request, Response $response, $args) { - // Load validation rules - $schema = new RequestSchema('schema://requests/resend-verification.yaml'); - $validator = new JqueryValidationAdapter($schema, $this->ci->translator); - - return $this->ci->view->render($response, 'pages/resend-verification.html.twig', [ - 'page' => [ - 'validators' => [ - 'resend_verification' => $validator->rules('json', FALSE) - ] - ] - ]); - } - - /** - * Reset password page. - * - * Renders the new password page for password reset requests. - * Request type: GET - * - * @param Request $request - * @param Response $response - * @param array $args - * @return void - */ - public function pageResetPassword(Request $request, Response $response, $args) { - // Insert the user's secret token from the link into the password reset form - $params = $request->getQueryParams(); - - // Load validation rules - note this uses the same schema as "set password" - $schema = new RequestSchema('schema://requests/set-password.yaml'); - $validator = new JqueryValidationAdapter($schema, $this->ci->translator); - - return $this->ci->view->render($response, 'pages/reset-password.html.twig', [ - 'page' => [ - 'validators' => [ - 'set_password' => $validator->rules('json', FALSE) - ] - ], - 'token' => isset($params['token']) ? $params['token'] : '', - ]); - } - - /** - * Render the "set password" page. - * - * Renders the page where new users who have had accounts created for them by another user, can set their password. - * By default, this is a "public page" (does not require authentication). - * Request type: GET - * - * @param Request $request - * @param Response $response - * @param array $args - * @return void - */ - public function pageSetPassword(Request $request, Response $response, $args) { - // Insert the user's secret token from the link into the password set form - $params = $request->getQueryParams(); - - // Load validation rules - $schema = new RequestSchema('schema://requests/set-password.yaml'); - $validator = new JqueryValidationAdapter($schema, $this->ci->translator); - - return $this->ci->view->render($response, 'pages/set-password.html.twig', [ - 'page' => [ - 'validators' => [ - 'set_password' => $validator->rules('json', FALSE) - ] - ], - 'token' => isset($params['token']) ? $params['token'] : '', - ]); - } - - /** - * Account settings page. - * - * Provides a form for users to modify various properties of their account, such as name, email, locale, etc. - * Any fields that the user does not have permission to modify will be automatically disabled. - * This page requires authentication. - * Request type: GET - * - * @param Request $request - * @param Response $response - * @param array $args - * @return void - */ - public function pageSettings(Request $request, Response $response, $args) { - /** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager */ - $authorizer = $this->ci->authorizer; - - /** @var \UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */ - $currentUser = $this->ci->currentUser; - - // Access-controlled page - if (!$authorizer->checkAccess($currentUser, 'uri_account_settings')) { - throw new ForbiddenException(); - } - - // Load validation rules - $schema = new RequestSchema('schema://requests/account-settings.yaml'); - $validatorAccountSettings = new JqueryValidationAdapter($schema, $this->ci->translator); - - $schema = new RequestSchema('schema://requests/profile-settings.yaml'); - $validatorProfileSettings = new JqueryValidationAdapter($schema, $this->ci->translator); - - /** @var \UserFrosting\Support\Repository\Repository $config */ - $config = $this->ci->config; - - // Get a list of all locales - $locales = $config->getDefined('site.locales.available'); - - return $this->ci->view->render($response, 'pages/account-settings.html.twig', [ - 'locales' => $locales, - 'page' => [ - 'validators' => [ - 'account_settings' => $validatorAccountSettings->rules('json', FALSE), - 'profile_settings' => $validatorProfileSettings->rules('json', FALSE) - ], - 'visibility' => ($authorizer->checkAccess($currentUser, 'update_account_settings') ? '' : 'disabled') - ] - ]); - } - - /** - * Render the account sign-in page for UserFrosting. - * - * This allows existing users to sign in. - * By definition, this is a "public page" (does not require authentication). - * Request type: GET - * - * @param Request $request - * @param Response $response - * @param array $args - * @return void - */ - public function pageSignIn(Request $request, Response $response, $args) { - /** @var \UserFrosting\Support\Repository\Repository $config */ - $config = $this->ci->config; - - /** @var \UserFrosting\Sprinkle\Account\Authenticate\Authenticator $authenticator */ - $authenticator = $this->ci->authenticator; - - // Redirect if user is already logged in - if ($authenticator->check()) { - $redirect = $this->ci->get('redirect.onAlreadyLoggedIn'); - - return $redirect($request, $response, $args); - } - - // Load validation rules - $schema = new RequestSchema('schema://requests/login.yaml'); - $validatorLogin = new JqueryValidationAdapter($schema, $this->ci->translator); - - return $this->ci->view->render($response, 'pages/sign-in.html.twig', [ - 'page' => [ - 'validators' => [ - 'login' => $validatorLogin->rules('json', FALSE) - ] - ] - ]); - } - - /** - * Processes a request to update a user's profile information. - * - * Processes the request from the user profile settings form, checking that: - * 1. They have the necessary permissions to update the posted field(s); - * 2. The submitted data is valid. - * This route requires authentication. - * Request type: POST - * - * @param Request $request - * @param Response $response - * @param array $args - * @return void - */ - public function profile(Request $request, Response $response, $args) { - /** @var \UserFrosting\Sprinkle\Core\Alert\AlertStream $ms */ - $ms = $this->ci->alerts; - - /** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager */ - $authorizer = $this->ci->authorizer; - - /** @var \UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */ - $currentUser = $this->ci->currentUser; - - // Access control for entire resource - check that the current user has permission to modify themselves - // See recipe "per-field access control" for dynamic fine-grained control over which properties a user can modify. - if (!$authorizer->checkAccess($currentUser, 'update_account_settings')) { - $ms->addMessageTranslated('danger', 'ACCOUNT.ACCESS_DENIED'); - return $response->withStatus(403); - } - - /** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ - $classMapper = $this->ci->classMapper; - - /** @var \UserFrosting\Support\Repository\Repository $config */ - $config = $this->ci->config; - - // POST parameters - $params = $request->getParsedBody(); - - // Load the request schema - $schema = new RequestSchema('schema://requests/profile-settings.yaml'); - - // Whitelist and set parameter defaults - $transformer = new RequestDataTransformer($schema); - $data = $transformer->transform($params); - - $error = FALSE; - - // Validate, and halt on validation errors. - $validator = new ServerSideValidator($schema, $this->ci->translator); - if (!$validator->validate($data)) { - $ms->addValidationErrors($validator); - $error = TRUE; - } - - // Check that locale is valid - $locales = $config->getDefined('site.locales.available'); - if (!array_key_exists($data['locale'], $locales)) { - $ms->addMessageTranslated('danger', 'LOCALE.INVALID', $data); - $error = TRUE; - } - - if ($error) { - return $response->withStatus(400); - } - - // Looks good, let's update with new values! - // Note that only fields listed in `profile-settings.yaml` will be permitted in $data, so this prevents the user from updating all columns in the DB - $currentUser->fill($data); - - $currentUser->save(); - - // Create activity record - $this->ci->userActivityLogger->info("User {$currentUser->user_name} updated their profile settings.", [ - 'type' => 'update_profile_settings' - ]); - - $ms->addMessageTranslated('success', 'PROFILE.UPDATED'); - return $response->withStatus(200); - } - - /** - * Processes an new account registration request. - * - * This is throttled to prevent account enumeration, since it needs to divulge when a username/email has been used. - * Processes the request from the form on the registration page, checking that: - * 1. The honeypot was not modified; - * 2. The master account has already been created (during installation); - * 3. Account registration is enabled; - * 4. The user is not already logged in; - * 5. Valid information was entered; - * 6. The captcha, if enabled, is correct; - * 7. The username and email are not already taken. - * Automatically sends an activation link upon success, if account activation is enabled. - * This route is "public access". - * Request type: POST - * Returns the User Object for the user record that was created. - * - * @param Request $request - * @param Response $response - * @param array $args - * @return void - */ - public function register(Request $request, Response $response, $args) { - /** @var \UserFrosting\Sprinkle\Core\Alert\AlertStream $ms */ - $ms = $this->ci->alerts; - - /** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ - $classMapper = $this->ci->classMapper; - - /** @var \UserFrosting\Support\Repository\Repository $config */ - $config = $this->ci->config; - - // Get POST parameters: user_name, first_name, last_name, email, password, passwordc, captcha, spiderbro, csrf_token - $params = $request->getParsedBody(); - - // Check the honeypot. 'spiderbro' is not a real field, it is hidden on the main page and must be submitted with its default value for this to be processed. - if (!isset($params['spiderbro']) || $params['spiderbro'] != 'http://') { - throw new SpammyRequestException('Possible spam received:' . print_r($params, TRUE)); - } - - // Security measure: do not allow registering new users until the master account has been created. - if (!$classMapper->staticMethod('user', 'find', $config['reserved_user_ids.master'])) { - $ms->addMessageTranslated('danger', 'ACCOUNT.MASTER_NOT_EXISTS'); - return $response->withStatus(403); - } - - // Check if registration is currently enabled - if (!$config['site.registration.enabled']) { - $ms->addMessageTranslated('danger', 'REGISTRATION.DISABLED'); - return $response->withStatus(403); - } - - /** @var \UserFrosting\Sprinkle\Account\Authenticate\Authenticator $authenticator */ - $authenticator = $this->ci->authenticator; - - // Prevent the user from registering if he/she is already logged in - if ($authenticator->check()) { - $ms->addMessageTranslated('danger', 'REGISTRATION.LOGOUT'); - return $response->withStatus(403); - } - - // Load the request schema - $schema = new RequestSchema('schema://requests/register.yaml'); - - // Whitelist and set parameter defaults - $transformer = new RequestDataTransformer($schema); - $data = $transformer->transform($params); - - $error = FALSE; - - // Validate request data - $validator = new ServerSideValidator($schema, $this->ci->translator); - if (!$validator->validate($data)) { - $ms->addValidationErrors($validator); - $error = TRUE; - } - - /** @var \UserFrosting\Sprinkle\Core\Throttle\Throttler $throttler */ - $throttler = $this->ci->throttler; - $delay = $throttler->getDelay('registration_attempt'); - - // Throttle requests - if ($delay > 0) { - return $response->withStatus(429); - } - - // Check if username or email already exists - if ($classMapper->staticMethod('user', 'findUnique', $data['user_name'], 'user_name')) { - $ms->addMessageTranslated('danger', 'USERNAME.IN_USE', $data); - $error = TRUE; - } - - if ($classMapper->staticMethod('user', 'findUnique', $data['email'], 'email')) { - $ms->addMessageTranslated('danger', 'EMAIL.IN_USE', $data); - $error = TRUE; - } - - // Check captcha, if required - if ($config['site.registration.captcha']) { - $captcha = new Captcha($this->ci->session, $this->ci->config['session.keys.captcha']); - if (!$data['captcha'] || !$captcha->verifyCode($data['captcha'])) { - $ms->addMessageTranslated('danger', 'CAPTCHA.FAIL'); - $error = TRUE; - } - } - - if ($error) { - return $response->withStatus(400); - } - - // Remove captcha, password confirmation from object data after validation - unset($data['captcha']); - unset($data['passwordc']); - - if ($config['site.registration.require_email_verification']) { - $data['flag_verified'] = FALSE; - } else { - $data['flag_verified'] = TRUE; - } - - // Load default group - $groupSlug = $config['site.registration.user_defaults.group']; - $defaultGroup = $classMapper->staticMethod('group', 'where', 'slug', $groupSlug)->first(); - - if (!$defaultGroup) { - $e = new HttpException("Account registration is not working because the default group '$groupSlug' does not exist."); - $e->addUserMessage('REGISTRATION.BROKEN'); - throw $e; - } - - // Set default group - $data['group_id'] = $defaultGroup->id; - - // Set default locale - $data['locale'] = $config['site.registration.user_defaults.locale']; - - // Hash password - $data['password'] = Password::hash($data['password']); - - // All checks passed! log events/activities, create user, and send verification email (if required) - // Begin transaction - DB will be rolled back if an exception occurs - Capsule::transaction(function () use ($classMapper, $data, $ms, $config, $throttler) { - // Log throttleable event - $throttler->logEvent('registration_attempt'); - - // Create the user - $user = $classMapper->createInstance('user', $data); - - // Store new user to database - $user->save(); - - // Create activity record - $this->ci->userActivityLogger->info("User {$user->user_name} registered for a new account.", [ - 'type' => 'sign_up', - 'user_id' => $user->id - ]); - - // Load default roles - $defaultRoleSlugs = $classMapper->staticMethod('role', 'getDefaultSlugs'); - $defaultRoles = $classMapper->staticMethod('role', 'whereIn', 'slug', $defaultRoleSlugs)->get(); - $defaultRoleIds = $defaultRoles->pluck('id')->all(); - - // Attach default roles - $user->roles()->attach($defaultRoleIds); - - // Verification email - if ($config['site.registration.require_email_verification']) { - // Try to generate a new verification request - $verification = $this->ci->repoVerification->create($user, $config['verification.timeout']); - - // Create and send verification email - $message = new TwigMailMessage($this->ci->view, 'mail/verify-account.html.twig'); - - $message->from($config['address_book.admin']) - ->addEmailRecipient(new EmailRecipient($user->email, $user->full_name)) - ->addParams([ - 'user' => $user, - 'token' => $verification->getToken() - ]); - - $this->ci->mailer->send($message); - - $ms->addMessageTranslated('success', 'REGISTRATION.COMPLETE_TYPE2', $user->toArray()); - } else { - // No verification required - $ms->addMessageTranslated('success', 'REGISTRATION.COMPLETE_TYPE1'); - } - }); - - return $response->withStatus(200); - } - - /** - * Processes a request to resend the verification email for a new user account. - * - * Processes the request from the resend verification email form, checking that: - * 1. The rate limit on this type of request is observed; - * 2. The provided email is associated with an existing user account; - * 3. The user account is not already verified; - * 4. The submitted data is valid. - * This route is "public access". - * Request type: POST - * - * @param Request $request - * @param Response $response - * @param array $args - * @return void - */ - public function resendVerification(Request $request, Response $response, $args) { - /** @var \UserFrosting\Sprinkle\Core\Alert\AlertStream $ms */ - $ms = $this->ci->alerts; - - /** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ - $classMapper = $this->ci->classMapper; - - /** @var \UserFrosting\Support\Repository\Repository $config */ - $config = $this->ci->config; - - // Get POST parameters - $params = $request->getParsedBody(); - - // Load the request schema - $schema = new RequestSchema('schema://requests/resend-verification.yaml'); - - // Whitelist and set parameter defaults - $transformer = new RequestDataTransformer($schema); - $data = $transformer->transform($params); - - // Validate, and halt on validation errors. Failed validation attempts do not count towards throttling limit. - $validator = new ServerSideValidator($schema, $this->ci->translator); - if (!$validator->validate($data)) { - $ms->addValidationErrors($validator); - return $response->withStatus(400); - } - - // Throttle requests - - /** @var \UserFrosting\Sprinkle\Core\Throttle\Throttler $throttler */ - $throttler = $this->ci->throttler; - - $throttleData = [ - 'email' => $data['email'] - ]; - $delay = $throttler->getDelay('verification_request', $throttleData); - - if ($delay > 0) { - $ms->addMessageTranslated('danger', 'RATE_LIMIT_EXCEEDED', ['delay' => $delay]); - return $response->withStatus(429); - } - - // All checks passed! log events/activities, create user, and send verification email (if required) - // Begin transaction - DB will be rolled back if an exception occurs - Capsule::transaction(function () use ($classMapper, $data, $throttler, $throttleData, $config) { - // Log throttleable event - $throttler->logEvent('verification_request', $throttleData); - - // Load the user, by email address - $user = $classMapper->staticMethod('user', 'where', 'email', $data['email'])->first(); - - // Check that the user exists and is not already verified. - // If there is no user with that email address, or the user exists and is already verified, - // we pretend like we succeeded to prevent account enumeration - if ($user && $user->flag_verified != '1') { - // We're good to go - record user activity and send the email - $verification = $this->ci->repoVerification->create($user, $config['verification.timeout']); - - // Create and send verification email - $message = new TwigMailMessage($this->ci->view, 'mail/resend-verification.html.twig'); - - $message->from($config['address_book.admin']) - ->addEmailRecipient(new EmailRecipient($user->email, $user->full_name)) - ->addParams([ - 'user' => $user, - 'token' => $verification->getToken() - ]); - - $this->ci->mailer->send($message); - } - }); - - $ms->addMessageTranslated('success', 'ACCOUNT.VERIFICATION.NEW_LINK_SENT', ['email' => $data['email']]); - return $response->withStatus(200); - } - - /** - * Processes a request to set the password for a new or current user. - * - * Processes the request from the password create/reset form, which should have the secret token embedded in it, checking that: - * 1. The provided secret token is associated with an existing user account; - * 2. The user has a password set/reset request in progress; - * 3. The token has not expired; - * 4. The submitted data (new password) is valid. - * This route is "public access". - * Request type: POST - * - * @param Request $request - * @param Response $response - * @param array $args - * @return void - */ - public function setPassword(Request $request, Response $response, $args) { - /** @var \UserFrosting\Sprinkle\Core\Alert\AlertStream $ms */ - $ms = $this->ci->alerts; - - /** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ - $classMapper = $this->ci->classMapper; - - /** @var \UserFrosting\Support\Repository\Repository $config */ - $config = $this->ci->config; - - // Get POST parameters - $params = $request->getParsedBody(); - - // Load the request schema - $schema = new RequestSchema('schema://requests/set-password.yaml'); - - // Whitelist and set parameter defaults - $transformer = new RequestDataTransformer($schema); - $data = $transformer->transform($params); - - // Validate, and halt on validation errors. Failed validation attempts do not count towards throttling limit. - $validator = new ServerSideValidator($schema, $this->ci->translator); - if (!$validator->validate($data)) { - $ms->addValidationErrors($validator); - return $response->withStatus(400); - } - - $forgotPasswordPage = $this->ci->router->pathFor('forgot-password'); - - // Ok, try to complete the request with the specified token and new password - $passwordReset = $this->ci->repoPasswordReset->complete($data['token'], [ - 'password' => $data['password'] - ]); - - if (!$passwordReset) { - $ms->addMessageTranslated('danger', 'PASSWORD.FORGET.INVALID', ['url' => $forgotPasswordPage]); - return $response->withStatus(400); - } - - $ms->addMessageTranslated('success', 'PASSWORD.UPDATED'); - - /** @var \UserFrosting\Sprinkle\Account\Authenticate\Authenticator $authenticator */ - $authenticator = $this->ci->authenticator; - - // Log out any existing user, and create a new session - if ($authenticator->check()) { - $authenticator->logout(); - } - - // Auto-login the user (without "remember me") - $user = $passwordReset->user; - $authenticator->login($user); - - $ms->addMessageTranslated('success', 'WELCOME', $user->export()); - return $response->withStatus(200); - } - - /** - * Processes a request to update a user's account information. - * - * Processes the request from the user account settings form, checking that: - * 1. The user correctly input their current password; - * 2. They have the necessary permissions to update the posted field(s); - * 3. The submitted data is valid. - * This route requires authentication. - * Request type: POST - * - * @param Request $request - * @param Response $response - * @param array $args - * @return void - */ - public function settings(Request $request, Response $response, $args) { - /** @var \UserFrosting\Sprinkle\Core\Alert\AlertStream $ms */ - $ms = $this->ci->alerts; - - /** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager */ - $authorizer = $this->ci->authorizer; - - /** @var \UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */ - $currentUser = $this->ci->currentUser; - - // Access control for entire resource - check that the current user has permission to modify themselves - // See recipe "per-field access control" for dynamic fine-grained control over which properties a user can modify. - if (!$authorizer->checkAccess($currentUser, 'update_account_settings')) { - $ms->addMessageTranslated('danger', 'ACCOUNT.ACCESS_DENIED'); - return $response->withStatus(403); - } - - /** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ - $classMapper = $this->ci->classMapper; - - /** @var \UserFrosting\Support\Repository\Repository $config */ - $config = $this->ci->config; - - // POST parameters - $params = $request->getParsedBody(); - - // Load the request schema - $schema = new RequestSchema('schema://requests/account-settings.yaml'); - - // Whitelist and set parameter defaults - $transformer = new RequestDataTransformer($schema); - $data = $transformer->transform($params); - - $error = FALSE; - - // Validate, and halt on validation errors. - $validator = new ServerSideValidator($schema, $this->ci->translator); - if (!$validator->validate($data)) { - $ms->addValidationErrors($validator); - $error = TRUE; - } - - // Confirm current password - if (!isset($data['passwordcheck']) || !Password::verify($data['passwordcheck'], $currentUser->password)) { - $ms->addMessageTranslated('danger', 'PASSWORD.INVALID'); - $error = TRUE; - } - - // Remove password check, password confirmation from object data after validation - unset($data['passwordcheck']); - unset($data['passwordc']); - - // If new email was submitted, check that the email address is not in use - if (isset($data['email']) && $data['email'] != $currentUser->email && $classMapper->staticMethod('user', 'findUnique', $data['email'], 'email')) { - $ms->addMessageTranslated('danger', 'EMAIL.IN_USE', $data); - $error = TRUE; - } - - if ($error) { - return $response->withStatus(400); - } - - // Hash new password, if specified - if (isset($data['password']) && !empty($data['password'])) { - $data['password'] = Password::hash($data['password']); - } else { - // Do not pass to model if no password is specified - unset($data['password']); - } - - // Looks good, let's update with new values! - // Note that only fields listed in `account-settings.yaml` will be permitted in $data, so this prevents the user from updating all columns in the DB - $currentUser->fill($data); - - $currentUser->save(); - - // Create activity record - $this->ci->userActivityLogger->info("User {$currentUser->user_name} updated their account settings.", [ - 'type' => 'update_account_settings' - ]); - - $ms->addMessageTranslated('success', 'ACCOUNT.SETTINGS.UPDATED'); - return $response->withStatus(200); - } - - /** - * Suggest an available username for a specified first/last name. - * - * This route is "public access". - * Request type: GET - * @odo Can this route be abused for account enumeration? If so we should throttle it as well. - * - * @param Request $request - * @param Response $response - * @param array $args - * @return void - */ - public function suggestUsername(Request $request, Response $response, $args) { - /** @var \UserFrosting\Sprinkle\Core\Alert\AlertStream $ms */ - $ms = $this->ci->alerts; - - /** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ - $classMapper = $this->ci->classMapper; - - $suggestion = AccountUtil::randomUniqueUsername($classMapper, 50, 10); - - // Be careful how you consume this data - it has not been escaped and contains untrusted user-supplied content. - // For example, if you plan to insert it into an HTML DOM, you must escape it on the client side (or use client-side templating). - return $response->withJson([ - 'user_name' => $suggestion - ], 200, JSON_PRETTY_PRINT); - } - - /** - * Processes an new email verification request. - * - * Processes the request from the email verification link that was emailed to the user, checking that: - * 1. The token provided matches a user in the database; - * 2. The user account is not already verified; - * This route is "public access". - * Request type: GET - * - * @param Request $request - * @param Response $response - * @param array $args - * @return void - */ - public function verify(Request $request, Response $response, $args) { - /** @var \UserFrosting\Sprinkle\Core\Alert\AlertStream $ms */ - $ms = $this->ci->alerts; - - /** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ - $classMapper = $this->ci->classMapper; - - /** @var \UserFrosting\Support\Repository\Repository $config */ - $config = $this->ci->config; - - $loginPage = $this->ci->router->pathFor('login'); - - // GET parameters - $params = $request->getQueryParams(); - - // Load request schema - $schema = new RequestSchema('schema://requests/account-verify.yaml'); - - // Whitelist and set parameter defaults - $transformer = new RequestDataTransformer($schema); - $data = $transformer->transform($params); - - // Validate, and halt on validation errors. This is a GET request, so we redirect on validation error. - $validator = new ServerSideValidator($schema, $this->ci->translator); - if (!$validator->validate($data)) { - $ms->addValidationErrors($validator); - // 400 code + redirect is perfectly fine, according to user Dilaz in #laravel - return $response->withRedirect($loginPage, 400); - } - - $verification = $this->ci->repoVerification->complete($data['token']); - - if (!$verification) { - $ms->addMessageTranslated('danger', 'ACCOUNT.VERIFICATION.TOKEN_NOT_FOUND'); - return $response->withRedirect($loginPage, 400); - } - - $ms->addMessageTranslated('success', 'ACCOUNT.VERIFICATION.COMPLETE'); - - // Forward to login page - return $response->withRedirect($loginPage); - } -} +<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Account\Controller;
+
+use Carbon\Carbon;
+use Illuminate\Database\Capsule\Manager as Capsule;
+use Psr\Http\Message\ResponseInterface as Response;
+use Psr\Http\Message\ServerRequestInterface as Request;
+use Slim\Exception\NotFoundException as NotFoundException;
+use UserFrosting\Fortress\RequestDataTransformer;
+use UserFrosting\Fortress\RequestSchema;
+use UserFrosting\Fortress\ServerSideValidator;
+use UserFrosting\Fortress\Adapter\JqueryValidationAdapter;
+use UserFrosting\Sprinkle\Account\Controller\Exception\SpammyRequestException;
+use UserFrosting\Sprinkle\Account\Facades\Password;
+use UserFrosting\Sprinkle\Account\Util\Util as AccountUtil;
+use UserFrosting\Sprinkle\Core\Controller\SimpleController;
+use UserFrosting\Sprinkle\Core\Mail\EmailRecipient;
+use UserFrosting\Sprinkle\Core\Mail\TwigMailMessage;
+use UserFrosting\Sprinkle\Core\Util\Captcha;
+use UserFrosting\Support\Exception\BadRequestException;
+use UserFrosting\Support\Exception\ForbiddenException;
+use UserFrosting\Support\Exception\HttpException;
+
+/**
+ * Controller class for /account/* URLs. Handles account-related activities, including login, registration, password recovery, and account settings.
+ *
+ * @author Alex Weissman (https://alexanderweissman.com)
+ * @see http://www.userfrosting.com/navigating/#structure
+ */
+class AccountController extends SimpleController
+{
+ /**
+ * Check a username for availability.
+ *
+ * This route is throttled by default, to discourage abusing it for account enumeration.
+ * This route is "public access".
+ * Request type: GET
+ *
+ * @param Request $request
+ * @param Response $response
+ * @param array $args
+ * @return void
+ */
+ public function checkUsername(Request $request, Response $response, $args) {
+ /** @var \UserFrosting\Sprinkle\Core\Alert\AlertStream $ms */
+ $ms = $this->ci->alerts;
+
+ // GET parameters
+ $params = $request->getQueryParams();
+
+ // Load request schema
+ $schema = new RequestSchema('schema://requests/check-username.yaml');
+
+ // Whitelist and set parameter defaults
+ $transformer = new RequestDataTransformer($schema);
+ $data = $transformer->transform($params);
+
+ // Validate, and halt on validation errors.
+ $validator = new ServerSideValidator($schema, $this->ci->translator);
+ if (!$validator->validate($data)) {
+ // O: encapsulate the communication of error messages from ServerSideValidator to the BadRequestException
+ $e = new BadRequestException('Missing or malformed request data!');
+ foreach ($validator->errors() as $idx => $field) {
+ foreach ($field as $eidx => $error) {
+ $e->addUserMessage($error);
+ }
+ }
+ throw $e;
+ }
+
+ /** @var \UserFrosting\Sprinkle\Core\Throttle\Throttler $throttler */
+ $throttler = $this->ci->throttler;
+ $delay = $throttler->getDelay('check_username_request');
+
+ // Throttle requests
+ if ($delay > 0) {
+ return $response->withStatus(429);
+ }
+
+ /** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
+ $classMapper = $this->ci->classMapper;
+
+ /** @var \UserFrosting\I18n\MessageTranslator $translator */
+ $translator = $this->ci->translator;
+
+ // Log throttleable event
+ $throttler->logEvent('check_username_request');
+
+ if ($classMapper->staticMethod('user', 'findUnique', $data['user_name'], 'user_name')) {
+ $message = $translator->translate('USERNAME.NOT_AVAILABLE', $data);
+ return $response->write($message)->withStatus(200);
+ } else {
+ return $response->write('true')->withStatus(200);
+ }
+ }
+
+ /**
+ * Processes a request to cancel a password reset request.
+ *
+ * This is provided so that users can cancel a password reset request, if they made it in error or if it was not initiated by themselves.
+ * Processes the request from the password reset link, checking that:
+ * 1. The provided token is associated with an existing user account, who has a pending password reset request.
+ * Request type: GET
+ *
+ * @param Request $request
+ * @param Response $response
+ * @param array $args
+ * @return void
+ */
+ public function denyResetPassword(Request $request, Response $response, $args) {
+ // GET parameters
+ $params = $request->getQueryParams();
+
+ /** @var \UserFrosting\Sprinkle\Core\Alert\AlertStream $ms */
+ $ms = $this->ci->alerts;
+
+ /** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
+ $classMapper = $this->ci->classMapper;
+
+ $loginPage = $this->ci->router->pathFor('login');
+
+ // Load validation rules
+ $schema = new RequestSchema('schema://requests/deny-password.yaml');
+
+ // Whitelist and set parameter defaults
+ $transformer = new RequestDataTransformer($schema);
+ $data = $transformer->transform($params);
+
+ // Validate, and halt on validation errors. Since this is a GET request, we need to redirect on failure
+ $validator = new ServerSideValidator($schema, $this->ci->translator);
+ if (!$validator->validate($data)) {
+ $ms->addValidationErrors($validator);
+ // 400 code + redirect is perfectly fine, according to user Dilaz in #laravel
+ return $response->withRedirect($loginPage, 400);
+ }
+
+ $passwordReset = $this->ci->repoPasswordReset->cancel($data['token']);
+
+ if (!$passwordReset) {
+ $ms->addMessageTranslated('danger', 'PASSWORD.FORGET.INVALID');
+ return $response->withRedirect($loginPage, 400);
+ }
+
+ $ms->addMessageTranslated('success', 'PASSWORD.FORGET.REQUEST_CANNED');
+ return $response->withRedirect($loginPage);
+ }
+
+ /**
+ * Processes a request to email a forgotten password reset link to the user.
+ *
+ * Processes the request from the form on the "forgot password" page, checking that:
+ * 1. The rate limit for this type of request is being observed.
+ * 2. The provided email address belongs to a registered account;
+ * 3. The submitted data is valid.
+ * Note that we have removed the requirement that a password reset request not already be in progress.
+ * This is because we need to allow users to re-request a reset, even if they lose the first reset email.
+ * This route is "public access".
+ * Request type: POST
+ * @odo require additional user information
+ * @odo prevent password reset requests for root account?
+ *
+ * @param Request $request
+ * @param Response $response
+ * @param array $args
+ * @return void
+ */
+ public function forgotPassword(Request $request, Response $response, $args) {
+ /** @var \UserFrosting\Sprinkle\Core\Alert\AlertStream $ms */
+ $ms = $this->ci->alerts;
+
+ /** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
+ $classMapper = $this->ci->classMapper;
+
+ /** @var \UserFrosting\Support\Repository\Repository $config */
+ $config = $this->ci->config;
+
+ // Get POST parameters
+ $params = $request->getParsedBody();
+
+ // Load the request schema
+ $schema = new RequestSchema('schema://requests/forgot-password.yaml');
+
+ // Whitelist and set parameter defaults
+ $transformer = new RequestDataTransformer($schema);
+ $data = $transformer->transform($params);
+
+ // Validate, and halt on validation errors. Failed validation attempts do not count towards throttling limit.
+ $validator = new ServerSideValidator($schema, $this->ci->translator);
+ if (!$validator->validate($data)) {
+ $ms->addValidationErrors($validator);
+ return $response->withStatus(400);
+ }
+
+ // Throttle requests
+
+ /** @var \UserFrosting\Sprinkle\Core\Throttle\Throttler $throttler */
+ $throttler = $this->ci->throttler;
+
+ $throttleData = [
+ 'email' => $data['email']
+ ];
+ $delay = $throttler->getDelay('password_reset_request', $throttleData);
+
+ if ($delay > 0) {
+ $ms->addMessageTranslated('danger', 'RATE_LIMIT_EXCEEDED', ['delay' => $delay]);
+ return $response->withStatus(429);
+ }
+
+ // All checks passed! log events/activities, update user, and send email
+ // Begin transaction - DB will be rolled back if an exception occurs
+ Capsule::transaction(function () use ($classMapper, $data, $throttler, $throttleData, $config) {
+
+ // Log throttleable event
+ $throttler->logEvent('password_reset_request', $throttleData);
+
+ // Load the user, by email address
+ $user = $classMapper->staticMethod('user', 'where', 'email', $data['email'])->first();
+
+ // Check that the email exists.
+ // If there is no user with that email address, we should still pretend like we succeeded, to prevent account enumeration
+ if ($user) {
+ // Try to generate a new password reset request.
+ // Use timeout for "reset password"
+ $passwordReset = $this->ci->repoPasswordReset->create($user, $config['password_reset.timeouts.reset']);
+
+ // Create and send email
+ $message = new TwigMailMessage($this->ci->view, 'mail/password-reset.html.twig');
+ $message->from($config['address_book.admin'])
+ ->addEmailRecipient(new EmailRecipient($user->email, $user->full_name))
+ ->addParams([
+ 'user' => $user,
+ 'token' => $passwordReset->getToken(),
+ 'request_date' => Carbon::now()->format('Y-m-d H:i:s')
+ ]);
+
+ $this->ci->mailer->send($message);
+ }
+ });
+
+ $ms->addMessageTranslated('success', 'PASSWORD.FORGET.REQUEST_SENT', ['email' => $data['email']]);
+ return $response->withStatus(200);
+ }
+
+ /**
+ * Returns a modal containing account terms of service.
+ *
+ * This does NOT render a complete page. Instead, it renders the HTML for the form, which can be embedded in other pages.
+ * Request type: GET
+ *
+ * @param Request $request
+ * @param Response $response
+ * @param array $args
+ * @return void
+ */
+ public function getModalAccountTos(Request $request, Response $response, $args) {
+ return $this->ci->view->render($response, 'modals/tos.html.twig');
+ }
+
+ /**
+ * Generate a random captcha, store it to the session, and return the captcha image.
+ *
+ * Request type: GET
+ *
+ * @param Request $request
+ * @param Response $response
+ * @param array $args
+ * @return void
+ */
+ public function imageCaptcha(Request $request, Response $response, $args) {
+ $captcha = new Captcha($this->ci->session, $this->ci->config['session.keys.captcha']);
+ $captcha->generateRandomCode();
+
+ return $response->withStatus(200)
+ ->withHeader('Content-Type', 'image/png;base64')
+ ->write($captcha->getImage());
+ }
+
+ /**
+ * Processes an account login request.
+ *
+ * Processes the request from the form on the login page, checking that:
+ * 1. The user is not already logged in.
+ * 2. The rate limit for this type of request is being observed.
+ * 3. Email login is enabled, if an email address was used.
+ * 4. The user account exists.
+ * 5. The user account is enabled and verified.
+ * 6. The user entered a valid username/email and password.
+ * This route, by definition, is "public access".
+ * Request type: POST
+ *
+ * @param Request $request
+ * @param Response $response
+ * @param array $args
+ * @return void
+ */
+ public function login(Request $request, Response $response, $args) {
+ /** @var \UserFrosting\Sprinkle\Core\Alert\AlertStream $ms */
+ $ms = $this->ci->alerts;
+
+ /** @var \UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */
+ $currentUser = $this->ci->currentUser;
+
+ /** @var \UserFrosting\Sprinkle\Account\Authenticate\Authenticator $authenticator */
+ $authenticator = $this->ci->authenticator;
+
+ // Return 200 success if user is already logged in
+ if ($authenticator->check()) {
+ $ms->addMessageTranslated('warning', 'LOGIN.ALREADY_COMPLETE');
+ return $response->withStatus(200);
+ }
+
+ /** @var \UserFrosting\Support\Repository\Repository $config */
+ $config = $this->ci->config;
+
+ // Get POST parameters
+ $params = $request->getParsedBody();
+
+ // Load the request schema
+ $schema = new RequestSchema('schema://requests/login.yaml');
+
+ // Whitelist and set parameter defaults
+ $transformer = new RequestDataTransformer($schema);
+ $data = $transformer->transform($params);
+
+ // Validate, and halt on validation errors. Failed validation attempts do not count towards throttling limit.
+ $validator = new ServerSideValidator($schema, $this->ci->translator);
+ if (!$validator->validate($data)) {
+ $ms->addValidationErrors($validator);
+ return $response->withStatus(400);
+ }
+
+ // Determine whether we are trying to log in with an email address or a username
+ $isEmail = filter_var($data['user_name'], FILTER_VALIDATE_EMAIL);
+
+ // Throttle requests
+
+ /** @var \UserFrosting\Sprinkle\Core\Throttle\Throttler $throttler */
+ $throttler = $this->ci->throttler;
+
+ $userIdentifier = $data['user_name'];
+
+ $throttleData = [
+ 'user_identifier' => $userIdentifier
+ ];
+
+ $delay = $throttler->getDelay('sign_in_attempt', $throttleData);
+ if ($delay > 0) {
+ $ms->addMessageTranslated('danger', 'RATE_LIMIT_EXCEEDED', [
+ 'delay' => $delay
+ ]);
+ return $response->withStatus(429);
+ }
+
+ // Log throttleable event
+ $throttler->logEvent('sign_in_attempt', $throttleData);
+
+ // If credential is an email address, but email login is not enabled, raise an error.
+ // Note that we do this after logging throttle event, so this error counts towards throttling limit.
+ if ($isEmail && !$config['site.login.enable_email']) {
+ $ms->addMessageTranslated('danger', 'USER_OR_PASS_INVALID');
+ return $response->withStatus(403);
+ }
+
+ // Try to authenticate the user. Authenticator will throw an exception on failure.
+ /** @var \UserFrosting\Sprinkle\Account\Authenticate\Authenticator $authenticator */
+ $authenticator = $this->ci->authenticator;
+
+ $currentUser = $authenticator->attempt(($isEmail ? 'email' : 'user_name'), $userIdentifier, $data['password'], $data['rememberme']);
+
+ $ms->addMessageTranslated('success', 'WELCOME', $currentUser->export());
+
+ // Set redirect, if relevant
+ $redirectOnLogin = $this->ci->get('redirect.onLogin');
+
+ return $redirectOnLogin($request, $response, $args);
+ }
+
+ /**
+ * Log the user out completely, including destroying any "remember me" token.
+ *
+ * Request type: GET
+ *
+ * @param Request $request
+ * @param Response $response
+ * @param array $args
+ * @return void
+ */
+ public function logout(Request $request, Response $response, $args) {
+ // Destroy the session
+ $this->ci->authenticator->logout();
+
+ // Return to home page
+ $config = $this->ci->config;
+ return $response->withStatus(302)->withHeader('Location', $config['site.uri.public']);
+ }
+
+ /**
+ * Render the "forgot password" page.
+ *
+ * This creates a simple form to allow users who forgot their password to have a time-limited password reset link emailed to them.
+ * By default, this is a "public page" (does not require authentication).
+ * Request type: GET
+ *
+ * @param Request $request
+ * @param Response $response
+ * @param array $args
+ * @return void
+ */
+ public function pageForgotPassword(Request $request, Response $response, $args) {
+ // Load validation rules
+ $schema = new RequestSchema('schema://requests/forgot-password.yaml');
+ $validator = new JqueryValidationAdapter($schema, $this->ci->translator);
+
+ return $this->ci->view->render($response, 'pages/forgot-password.html.twig', [
+ 'page' => [
+ 'validators' => [
+ 'forgot_password' => $validator->rules('json', FALSE)
+ ]
+ ]
+ ]);
+ }
+
+
+ /**
+ * Render the account registration page for UserFrosting.
+ *
+ * This allows new (non-authenticated) users to create a new account for themselves on your website (if enabled).
+ * By definition, this is a "public page" (does not require authentication).
+ * Request type: GET
+ *
+ * @param Request $request
+ * @param Response $response
+ * @param array $args
+ * @return void
+ */
+ public function pageRegister(Request $request, Response $response, $args) {
+ /** @var \UserFrosting\Support\Repository\Repository $config */
+ $config = $this->ci->config;
+
+ if (!$config['site.registration.enabled']) {
+ throw new NotFoundException($request, $response);
+ }
+
+ /** @var \UserFrosting\Sprinkle\Account\Authenticate\Authenticator $authenticator */
+ $authenticator = $this->ci->authenticator;
+
+ // Redirect if user is already logged in
+ if ($authenticator->check()) {
+ $redirect = $this->ci->get('redirect.onAlreadyLoggedIn');
+
+ return $redirect($request, $response, $args);
+ }
+
+ // Load validation rules
+ $schema = new RequestSchema('schema://requests/register.yaml');
+ $validatorRegister = new JqueryValidationAdapter($schema, $this->ci->translator);
+
+ return $this->ci->view->render($response, 'pages/register.html.twig', [
+ 'page' => [
+ 'validators' => [
+ 'register' => $validatorRegister->rules('json', FALSE)
+ ]
+ ]
+ ]);
+ }
+
+ /**
+ * Render the "resend verification email" page.
+ *
+ * This is a form that allows users who lost their account verification link to have the link resent to their email address.
+ * By default, this is a "public page" (does not require authentication).
+ * Request type: GET
+ *
+ * @param Request $request
+ * @param Response $response
+ * @param array $args
+ * @return void
+ */
+ public function pageResendVerification(Request $request, Response $response, $args) {
+ // Load validation rules
+ $schema = new RequestSchema('schema://requests/resend-verification.yaml');
+ $validator = new JqueryValidationAdapter($schema, $this->ci->translator);
+
+ return $this->ci->view->render($response, 'pages/resend-verification.html.twig', [
+ 'page' => [
+ 'validators' => [
+ 'resend_verification' => $validator->rules('json', FALSE)
+ ]
+ ]
+ ]);
+ }
+
+ /**
+ * Reset password page.
+ *
+ * Renders the new password page for password reset requests.
+ * Request type: GET
+ *
+ * @param Request $request
+ * @param Response $response
+ * @param array $args
+ * @return void
+ */
+ public function pageResetPassword(Request $request, Response $response, $args) {
+ // Insert the user's secret token from the link into the password reset form
+ $params = $request->getQueryParams();
+
+ // Load validation rules - note this uses the same schema as "set password"
+ $schema = new RequestSchema('schema://requests/set-password.yaml');
+ $validator = new JqueryValidationAdapter($schema, $this->ci->translator);
+
+ return $this->ci->view->render($response, 'pages/reset-password.html.twig', [
+ 'page' => [
+ 'validators' => [
+ 'set_password' => $validator->rules('json', FALSE)
+ ]
+ ],
+ 'token' => isset($params['token']) ? $params['token'] : '',
+ ]);
+ }
+
+ /**
+ * Render the "set password" page.
+ *
+ * Renders the page where new users who have had accounts created for them by another user, can set their password.
+ * By default, this is a "public page" (does not require authentication).
+ * Request type: GET
+ *
+ * @param Request $request
+ * @param Response $response
+ * @param array $args
+ * @return void
+ */
+ public function pageSetPassword(Request $request, Response $response, $args) {
+ // Insert the user's secret token from the link into the password set form
+ $params = $request->getQueryParams();
+
+ // Load validation rules
+ $schema = new RequestSchema('schema://requests/set-password.yaml');
+ $validator = new JqueryValidationAdapter($schema, $this->ci->translator);
+
+ return $this->ci->view->render($response, 'pages/set-password.html.twig', [
+ 'page' => [
+ 'validators' => [
+ 'set_password' => $validator->rules('json', FALSE)
+ ]
+ ],
+ 'token' => isset($params['token']) ? $params['token'] : '',
+ ]);
+ }
+
+ /**
+ * Account settings page.
+ *
+ * Provides a form for users to modify various properties of their account, such as name, email, locale, etc.
+ * Any fields that the user does not have permission to modify will be automatically disabled.
+ * This page requires authentication.
+ * Request type: GET
+ *
+ * @param Request $request
+ * @param Response $response
+ * @param array $args
+ * @return void
+ */
+ public function pageSettings(Request $request, Response $response, $args) {
+ /** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager */
+ $authorizer = $this->ci->authorizer;
+
+ /** @var \UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */
+ $currentUser = $this->ci->currentUser;
+
+ // Access-controlled page
+ if (!$authorizer->checkAccess($currentUser, 'uri_account_settings')) {
+ throw new ForbiddenException();
+ }
+
+ // Load validation rules
+ $schema = new RequestSchema('schema://requests/account-settings.yaml');
+ $validatorAccountSettings = new JqueryValidationAdapter($schema, $this->ci->translator);
+
+ $schema = new RequestSchema('schema://requests/profile-settings.yaml');
+ $validatorProfileSettings = new JqueryValidationAdapter($schema, $this->ci->translator);
+
+ /** @var \UserFrosting\Support\Repository\Repository $config */
+ $config = $this->ci->config;
+
+ // Get a list of all locales
+ $locales = $config->getDefined('site.locales.available');
+
+ return $this->ci->view->render($response, 'pages/account-settings.html.twig', [
+ 'locales' => $locales,
+ 'page' => [
+ 'validators' => [
+ 'account_settings' => $validatorAccountSettings->rules('json', FALSE),
+ 'profile_settings' => $validatorProfileSettings->rules('json', FALSE)
+ ],
+ 'visibility' => ($authorizer->checkAccess($currentUser, 'update_account_settings') ? '' : 'disabled')
+ ]
+ ]);
+ }
+
+ /**
+ * Render the account sign-in page for UserFrosting.
+ *
+ * This allows existing users to sign in.
+ * By definition, this is a "public page" (does not require authentication).
+ * Request type: GET
+ *
+ * @param Request $request
+ * @param Response $response
+ * @param array $args
+ * @return void
+ */
+ public function pageSignIn(Request $request, Response $response, $args) {
+ /** @var \UserFrosting\Support\Repository\Repository $config */
+ $config = $this->ci->config;
+
+ /** @var \UserFrosting\Sprinkle\Account\Authenticate\Authenticator $authenticator */
+ $authenticator = $this->ci->authenticator;
+
+ // Redirect if user is already logged in
+ if ($authenticator->check()) {
+ $redirect = $this->ci->get('redirect.onAlreadyLoggedIn');
+
+ return $redirect($request, $response, $args);
+ }
+
+ // Load validation rules
+ $schema = new RequestSchema('schema://requests/login.yaml');
+ $validatorLogin = new JqueryValidationAdapter($schema, $this->ci->translator);
+
+ return $this->ci->view->render($response, 'pages/sign-in.html.twig', [
+ 'page' => [
+ 'validators' => [
+ 'login' => $validatorLogin->rules('json', FALSE)
+ ]
+ ]
+ ]);
+ }
+
+ /**
+ * Processes a request to update a user's profile information.
+ *
+ * Processes the request from the user profile settings form, checking that:
+ * 1. They have the necessary permissions to update the posted field(s);
+ * 2. The submitted data is valid.
+ * This route requires authentication.
+ * Request type: POST
+ *
+ * @param Request $request
+ * @param Response $response
+ * @param array $args
+ * @return void
+ */
+ public function profile(Request $request, Response $response, $args) {
+ /** @var \UserFrosting\Sprinkle\Core\Alert\AlertStream $ms */
+ $ms = $this->ci->alerts;
+
+ /** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager */
+ $authorizer = $this->ci->authorizer;
+
+ /** @var \UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */
+ $currentUser = $this->ci->currentUser;
+
+ // Access control for entire resource - check that the current user has permission to modify themselves
+ // See recipe "per-field access control" for dynamic fine-grained control over which properties a user can modify.
+ if (!$authorizer->checkAccess($currentUser, 'update_account_settings')) {
+ $ms->addMessageTranslated('danger', 'ACCOUNT.ACCESS_DENIED');
+ return $response->withStatus(403);
+ }
+
+ /** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
+ $classMapper = $this->ci->classMapper;
+
+ /** @var \UserFrosting\Support\Repository\Repository $config */
+ $config = $this->ci->config;
+
+ // POST parameters
+ $params = $request->getParsedBody();
+
+ // Load the request schema
+ $schema = new RequestSchema('schema://requests/profile-settings.yaml');
+
+ // Whitelist and set parameter defaults
+ $transformer = new RequestDataTransformer($schema);
+ $data = $transformer->transform($params);
+
+ $error = FALSE;
+
+ // Validate, and halt on validation errors.
+ $validator = new ServerSideValidator($schema, $this->ci->translator);
+ if (!$validator->validate($data)) {
+ $ms->addValidationErrors($validator);
+ $error = TRUE;
+ }
+
+ // Check that locale is valid
+ $locales = $config->getDefined('site.locales.available');
+ if (!array_key_exists($data['locale'], $locales)) {
+ $ms->addMessageTranslated('danger', 'LOCALE.INVALID', $data);
+ $error = TRUE;
+ }
+
+ if ($error) {
+ return $response->withStatus(400);
+ }
+
+ // Looks good, let's update with new values!
+ // Note that only fields listed in `profile-settings.yaml` will be permitted in $data, so this prevents the user from updating all columns in the DB
+ $currentUser->fill($data);
+
+ $currentUser->save();
+
+ // Create activity record
+ $this->ci->userActivityLogger->info("User {$currentUser->user_name} updated their profile settings.", [
+ 'type' => 'update_profile_settings'
+ ]);
+
+ $ms->addMessageTranslated('success', 'PROFILE.UPDATED');
+ return $response->withStatus(200);
+ }
+
+ /**
+ * Processes an new account registration request.
+ *
+ * This is throttled to prevent account enumeration, since it needs to divulge when a username/email has been used.
+ * Processes the request from the form on the registration page, checking that:
+ * 1. The honeypot was not modified;
+ * 2. The master account has already been created (during installation);
+ * 3. Account registration is enabled;
+ * 4. The user is not already logged in;
+ * 5. Valid information was entered;
+ * 6. The captcha, if enabled, is correct;
+ * 7. The username and email are not already taken.
+ * Automatically sends an activation link upon success, if account activation is enabled.
+ * This route is "public access".
+ * Request type: POST
+ * Returns the User Object for the user record that was created.
+ *
+ * @param Request $request
+ * @param Response $response
+ * @param array $args
+ * @return void
+ */
+ public function register(Request $request, Response $response, $args) {
+ /** @var \UserFrosting\Sprinkle\Core\Alert\AlertStream $ms */
+ $ms = $this->ci->alerts;
+
+ /** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
+ $classMapper = $this->ci->classMapper;
+
+ /** @var \UserFrosting\Support\Repository\Repository $config */
+ $config = $this->ci->config;
+
+ // Get POST parameters: user_name, first_name, last_name, email, password, passwordc, captcha, spiderbro, csrf_token
+ $params = $request->getParsedBody();
+
+ // Check the honeypot. 'spiderbro' is not a real field, it is hidden on the main page and must be submitted with its default value for this to be processed.
+ if (!isset($params['spiderbro']) || $params['spiderbro'] != 'http://') {
+ throw new SpammyRequestException('Possible spam received:' . print_r($params, TRUE));
+ }
+
+ // Security measure: do not allow registering new users until the master account has been created.
+ if (!$classMapper->staticMethod('user', 'find', $config['reserved_user_ids.master'])) {
+ $ms->addMessageTranslated('danger', 'ACCOUNT.MASTER_NOT_EXISTS');
+ return $response->withStatus(403);
+ }
+
+ // Check if registration is currently enabled
+ if (!$config['site.registration.enabled']) {
+ $ms->addMessageTranslated('danger', 'REGISTRATION.DISABLED');
+ return $response->withStatus(403);
+ }
+
+ /** @var \UserFrosting\Sprinkle\Account\Authenticate\Authenticator $authenticator */
+ $authenticator = $this->ci->authenticator;
+
+ // Prevent the user from registering if he/she is already logged in
+ if ($authenticator->check()) {
+ $ms->addMessageTranslated('danger', 'REGISTRATION.LOGOUT');
+ return $response->withStatus(403);
+ }
+
+ // Load the request schema
+ $schema = new RequestSchema('schema://requests/register.yaml');
+
+ // Whitelist and set parameter defaults
+ $transformer = new RequestDataTransformer($schema);
+ $data = $transformer->transform($params);
+
+ $error = FALSE;
+
+ // Validate request data
+ $validator = new ServerSideValidator($schema, $this->ci->translator);
+ if (!$validator->validate($data)) {
+ $ms->addValidationErrors($validator);
+ $error = TRUE;
+ }
+
+ /** @var \UserFrosting\Sprinkle\Core\Throttle\Throttler $throttler */
+ $throttler = $this->ci->throttler;
+ $delay = $throttler->getDelay('registration_attempt');
+
+ // Throttle requests
+ if ($delay > 0) {
+ return $response->withStatus(429);
+ }
+
+ // Check if username or email already exists
+ if ($classMapper->staticMethod('user', 'findUnique', $data['user_name'], 'user_name')) {
+ $ms->addMessageTranslated('danger', 'USERNAME.IN_USE', $data);
+ $error = TRUE;
+ }
+
+ if ($classMapper->staticMethod('user', 'findUnique', $data['email'], 'email')) {
+ $ms->addMessageTranslated('danger', 'EMAIL.IN_USE', $data);
+ $error = TRUE;
+ }
+
+ // Check captcha, if required
+ if ($config['site.registration.captcha']) {
+ $captcha = new Captcha($this->ci->session, $this->ci->config['session.keys.captcha']);
+ if (!$data['captcha'] || !$captcha->verifyCode($data['captcha'])) {
+ $ms->addMessageTranslated('danger', 'CAPTCHA.FAIL');
+ $error = TRUE;
+ }
+ }
+
+ if ($error) {
+ return $response->withStatus(400);
+ }
+
+ // Remove captcha, password confirmation from object data after validation
+ unset($data['captcha']);
+ unset($data['passwordc']);
+
+ if ($config['site.registration.require_email_verification']) {
+ $data['flag_verified'] = FALSE;
+ } else {
+ $data['flag_verified'] = TRUE;
+ }
+
+ // Load default group
+ $groupSlug = $config['site.registration.user_defaults.group'];
+ $defaultGroup = $classMapper->staticMethod('group', 'where', 'slug', $groupSlug)->first();
+
+ if (!$defaultGroup) {
+ $e = new HttpException("Account registration is not working because the default group '$groupSlug' does not exist.");
+ $e->addUserMessage('REGISTRATION.BROKEN');
+ throw $e;
+ }
+
+ // Set default group
+ $data['group_id'] = $defaultGroup->id;
+
+ // Set default locale
+ $data['locale'] = $config['site.registration.user_defaults.locale'];
+
+ // Hash password
+ $data['password'] = Password::hash($data['password']);
+
+ // All checks passed! log events/activities, create user, and send verification email (if required)
+ // Begin transaction - DB will be rolled back if an exception occurs
+ Capsule::transaction(function () use ($classMapper, $data, $ms, $config, $throttler) {
+ // Log throttleable event
+ $throttler->logEvent('registration_attempt');
+
+ // Create the user
+ $user = $classMapper->createInstance('user', $data);
+
+ // Store new user to database
+ $user->save();
+
+ // Create activity record
+ $this->ci->userActivityLogger->info("User {$user->user_name} registered for a new account.", [
+ 'type' => 'sign_up',
+ 'user_id' => $user->id
+ ]);
+
+ // Load default roles
+ $defaultRoleSlugs = $classMapper->staticMethod('role', 'getDefaultSlugs');
+ $defaultRoles = $classMapper->staticMethod('role', 'whereIn', 'slug', $defaultRoleSlugs)->get();
+ $defaultRoleIds = $defaultRoles->pluck('id')->all();
+
+ // Attach default roles
+ $user->roles()->attach($defaultRoleIds);
+
+ // Verification email
+ if ($config['site.registration.require_email_verification']) {
+ // Try to generate a new verification request
+ $verification = $this->ci->repoVerification->create($user, $config['verification.timeout']);
+
+ // Create and send verification email
+ $message = new TwigMailMessage($this->ci->view, 'mail/verify-account.html.twig');
+
+ $message->from($config['address_book.admin'])
+ ->addEmailRecipient(new EmailRecipient($user->email, $user->full_name))
+ ->addParams([
+ 'user' => $user,
+ 'token' => $verification->getToken()
+ ]);
+
+ $this->ci->mailer->send($message);
+
+ $ms->addMessageTranslated('success', 'REGISTRATION.COMPLETE_TYPE2', $user->toArray());
+ } else {
+ // No verification required
+ $ms->addMessageTranslated('success', 'REGISTRATION.COMPLETE_TYPE1');
+ }
+ });
+
+ return $response->withStatus(200);
+ }
+
+ /**
+ * Processes a request to resend the verification email for a new user account.
+ *
+ * Processes the request from the resend verification email form, checking that:
+ * 1. The rate limit on this type of request is observed;
+ * 2. The provided email is associated with an existing user account;
+ * 3. The user account is not already verified;
+ * 4. The submitted data is valid.
+ * This route is "public access".
+ * Request type: POST
+ *
+ * @param Request $request
+ * @param Response $response
+ * @param array $args
+ * @return void
+ */
+ public function resendVerification(Request $request, Response $response, $args) {
+ /** @var \UserFrosting\Sprinkle\Core\Alert\AlertStream $ms */
+ $ms = $this->ci->alerts;
+
+ /** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
+ $classMapper = $this->ci->classMapper;
+
+ /** @var \UserFrosting\Support\Repository\Repository $config */
+ $config = $this->ci->config;
+
+ // Get POST parameters
+ $params = $request->getParsedBody();
+
+ // Load the request schema
+ $schema = new RequestSchema('schema://requests/resend-verification.yaml');
+
+ // Whitelist and set parameter defaults
+ $transformer = new RequestDataTransformer($schema);
+ $data = $transformer->transform($params);
+
+ // Validate, and halt on validation errors. Failed validation attempts do not count towards throttling limit.
+ $validator = new ServerSideValidator($schema, $this->ci->translator);
+ if (!$validator->validate($data)) {
+ $ms->addValidationErrors($validator);
+ return $response->withStatus(400);
+ }
+
+ // Throttle requests
+
+ /** @var \UserFrosting\Sprinkle\Core\Throttle\Throttler $throttler */
+ $throttler = $this->ci->throttler;
+
+ $throttleData = [
+ 'email' => $data['email']
+ ];
+ $delay = $throttler->getDelay('verification_request', $throttleData);
+
+ if ($delay > 0) {
+ $ms->addMessageTranslated('danger', 'RATE_LIMIT_EXCEEDED', ['delay' => $delay]);
+ return $response->withStatus(429);
+ }
+
+ // All checks passed! log events/activities, create user, and send verification email (if required)
+ // Begin transaction - DB will be rolled back if an exception occurs
+ Capsule::transaction(function () use ($classMapper, $data, $throttler, $throttleData, $config) {
+ // Log throttleable event
+ $throttler->logEvent('verification_request', $throttleData);
+
+ // Load the user, by email address
+ $user = $classMapper->staticMethod('user', 'where', 'email', $data['email'])->first();
+
+ // Check that the user exists and is not already verified.
+ // If there is no user with that email address, or the user exists and is already verified,
+ // we pretend like we succeeded to prevent account enumeration
+ if ($user && $user->flag_verified != '1') {
+ // We're good to go - record user activity and send the email
+ $verification = $this->ci->repoVerification->create($user, $config['verification.timeout']);
+
+ // Create and send verification email
+ $message = new TwigMailMessage($this->ci->view, 'mail/resend-verification.html.twig');
+
+ $message->from($config['address_book.admin'])
+ ->addEmailRecipient(new EmailRecipient($user->email, $user->full_name))
+ ->addParams([
+ 'user' => $user,
+ 'token' => $verification->getToken()
+ ]);
+
+ $this->ci->mailer->send($message);
+ }
+ });
+
+ $ms->addMessageTranslated('success', 'ACCOUNT.VERIFICATION.NEW_LINK_SENT', ['email' => $data['email']]);
+ return $response->withStatus(200);
+ }
+
+ /**
+ * Processes a request to set the password for a new or current user.
+ *
+ * Processes the request from the password create/reset form, which should have the secret token embedded in it, checking that:
+ * 1. The provided secret token is associated with an existing user account;
+ * 2. The user has a password set/reset request in progress;
+ * 3. The token has not expired;
+ * 4. The submitted data (new password) is valid.
+ * This route is "public access".
+ * Request type: POST
+ *
+ * @param Request $request
+ * @param Response $response
+ * @param array $args
+ * @return void
+ */
+ public function setPassword(Request $request, Response $response, $args) {
+ /** @var \UserFrosting\Sprinkle\Core\Alert\AlertStream $ms */
+ $ms = $this->ci->alerts;
+
+ /** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
+ $classMapper = $this->ci->classMapper;
+
+ /** @var \UserFrosting\Support\Repository\Repository $config */
+ $config = $this->ci->config;
+
+ // Get POST parameters
+ $params = $request->getParsedBody();
+
+ // Load the request schema
+ $schema = new RequestSchema('schema://requests/set-password.yaml');
+
+ // Whitelist and set parameter defaults
+ $transformer = new RequestDataTransformer($schema);
+ $data = $transformer->transform($params);
+
+ // Validate, and halt on validation errors. Failed validation attempts do not count towards throttling limit.
+ $validator = new ServerSideValidator($schema, $this->ci->translator);
+ if (!$validator->validate($data)) {
+ $ms->addValidationErrors($validator);
+ return $response->withStatus(400);
+ }
+
+ $forgotPasswordPage = $this->ci->router->pathFor('forgot-password');
+
+ // Ok, try to complete the request with the specified token and new password
+ $passwordReset = $this->ci->repoPasswordReset->complete($data['token'], [
+ 'password' => $data['password']
+ ]);
+
+ if (!$passwordReset) {
+ $ms->addMessageTranslated('danger', 'PASSWORD.FORGET.INVALID', ['url' => $forgotPasswordPage]);
+ return $response->withStatus(400);
+ }
+
+ $ms->addMessageTranslated('success', 'PASSWORD.UPDATED');
+
+ /** @var \UserFrosting\Sprinkle\Account\Authenticate\Authenticator $authenticator */
+ $authenticator = $this->ci->authenticator;
+
+ // Log out any existing user, and create a new session
+ if ($authenticator->check()) {
+ $authenticator->logout();
+ }
+
+ // Auto-login the user (without "remember me")
+ $user = $passwordReset->user;
+ $authenticator->login($user);
+
+ $ms->addMessageTranslated('success', 'WELCOME', $user->export());
+ return $response->withStatus(200);
+ }
+
+ /**
+ * Processes a request to update a user's account information.
+ *
+ * Processes the request from the user account settings form, checking that:
+ * 1. The user correctly input their current password;
+ * 2. They have the necessary permissions to update the posted field(s);
+ * 3. The submitted data is valid.
+ * This route requires authentication.
+ * Request type: POST
+ *
+ * @param Request $request
+ * @param Response $response
+ * @param array $args
+ * @return void
+ */
+ public function settings(Request $request, Response $response, $args) {
+ /** @var \UserFrosting\Sprinkle\Core\Alert\AlertStream $ms */
+ $ms = $this->ci->alerts;
+
+ /** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager */
+ $authorizer = $this->ci->authorizer;
+
+ /** @var \UserFrosting\Sprinkle\Account\Database\Models\User $currentUser */
+ $currentUser = $this->ci->currentUser;
+
+ // Access control for entire resource - check that the current user has permission to modify themselves
+ // See recipe "per-field access control" for dynamic fine-grained control over which properties a user can modify.
+ if (!$authorizer->checkAccess($currentUser, 'update_account_settings')) {
+ $ms->addMessageTranslated('danger', 'ACCOUNT.ACCESS_DENIED');
+ return $response->withStatus(403);
+ }
+
+ /** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
+ $classMapper = $this->ci->classMapper;
+
+ /** @var \UserFrosting\Support\Repository\Repository $config */
+ $config = $this->ci->config;
+
+ // POST parameters
+ $params = $request->getParsedBody();
+
+ // Load the request schema
+ $schema = new RequestSchema('schema://requests/account-settings.yaml');
+
+ // Whitelist and set parameter defaults
+ $transformer = new RequestDataTransformer($schema);
+ $data = $transformer->transform($params);
+
+ $error = FALSE;
+
+ // Validate, and halt on validation errors.
+ $validator = new ServerSideValidator($schema, $this->ci->translator);
+ if (!$validator->validate($data)) {
+ $ms->addValidationErrors($validator);
+ $error = TRUE;
+ }
+
+ // Confirm current password
+ if (!isset($data['passwordcheck']) || !Password::verify($data['passwordcheck'], $currentUser->password)) {
+ $ms->addMessageTranslated('danger', 'PASSWORD.INVALID');
+ $error = TRUE;
+ }
+
+ // Remove password check, password confirmation from object data after validation
+ unset($data['passwordcheck']);
+ unset($data['passwordc']);
+
+ // If new email was submitted, check that the email address is not in use
+ if (isset($data['email']) && $data['email'] != $currentUser->email && $classMapper->staticMethod('user', 'findUnique', $data['email'], 'email')) {
+ $ms->addMessageTranslated('danger', 'EMAIL.IN_USE', $data);
+ $error = TRUE;
+ }
+
+ if ($error) {
+ return $response->withStatus(400);
+ }
+
+ // Hash new password, if specified
+ if (isset($data['password']) && !empty($data['password'])) {
+ $data['password'] = Password::hash($data['password']);
+ } else {
+ // Do not pass to model if no password is specified
+ unset($data['password']);
+ }
+
+ // Looks good, let's update with new values!
+ // Note that only fields listed in `account-settings.yaml` will be permitted in $data, so this prevents the user from updating all columns in the DB
+ $currentUser->fill($data);
+
+ $currentUser->save();
+
+ // Create activity record
+ $this->ci->userActivityLogger->info("User {$currentUser->user_name} updated their account settings.", [
+ 'type' => 'update_account_settings'
+ ]);
+
+ $ms->addMessageTranslated('success', 'ACCOUNT.SETTINGS.UPDATED');
+ return $response->withStatus(200);
+ }
+
+ /**
+ * Suggest an available username for a specified first/last name.
+ *
+ * This route is "public access".
+ * Request type: GET
+ * @odo Can this route be abused for account enumeration? If so we should throttle it as well.
+ *
+ * @param Request $request
+ * @param Response $response
+ * @param array $args
+ * @return void
+ */
+ public function suggestUsername(Request $request, Response $response, $args) {
+ /** @var \UserFrosting\Sprinkle\Core\Alert\AlertStream $ms */
+ $ms = $this->ci->alerts;
+
+ /** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
+ $classMapper = $this->ci->classMapper;
+
+ $suggestion = AccountUtil::randomUniqueUsername($classMapper, 50, 10);
+
+ // Be careful how you consume this data - it has not been escaped and contains untrusted user-supplied content.
+ // For example, if you plan to insert it into an HTML DOM, you must escape it on the client side (or use client-side templating).
+ return $response->withJson([
+ 'user_name' => $suggestion
+ ], 200, JSON_PRETTY_PRINT);
+ }
+
+ /**
+ * Processes an new email verification request.
+ *
+ * Processes the request from the email verification link that was emailed to the user, checking that:
+ * 1. The token provided matches a user in the database;
+ * 2. The user account is not already verified;
+ * This route is "public access".
+ * Request type: GET
+ *
+ * @param Request $request
+ * @param Response $response
+ * @param array $args
+ * @return void
+ */
+ public function verify(Request $request, Response $response, $args) {
+ /** @var \UserFrosting\Sprinkle\Core\Alert\AlertStream $ms */
+ $ms = $this->ci->alerts;
+
+ /** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
+ $classMapper = $this->ci->classMapper;
+
+ /** @var \UserFrosting\Support\Repository\Repository $config */
+ $config = $this->ci->config;
+
+ $loginPage = $this->ci->router->pathFor('login');
+
+ // GET parameters
+ $params = $request->getQueryParams();
+
+ // Load request schema
+ $schema = new RequestSchema('schema://requests/account-verify.yaml');
+
+ // Whitelist and set parameter defaults
+ $transformer = new RequestDataTransformer($schema);
+ $data = $transformer->transform($params);
+
+ // Validate, and halt on validation errors. This is a GET request, so we redirect on validation error.
+ $validator = new ServerSideValidator($schema, $this->ci->translator);
+ if (!$validator->validate($data)) {
+ $ms->addValidationErrors($validator);
+ // 400 code + redirect is perfectly fine, according to user Dilaz in #laravel
+ return $response->withRedirect($loginPage, 400);
+ }
+
+ $verification = $this->ci->repoVerification->complete($data['token']);
+
+ if (!$verification) {
+ $ms->addMessageTranslated('danger', 'ACCOUNT.VERIFICATION.TOKEN_NOT_FOUND');
+ return $response->withRedirect($loginPage, 400);
+ }
+
+ $ms->addMessageTranslated('success', 'ACCOUNT.VERIFICATION.COMPLETE');
+
+ // Forward to login page
+ return $response->withRedirect($loginPage);
+ }
+}
diff --git a/main/app/sprinkles/account/src/Controller/Exception/SpammyRequestException.php b/main/app/sprinkles/account/src/Controller/Exception/SpammyRequestException.php index d66a16c..1f0cf4a 100644 --- a/main/app/sprinkles/account/src/Controller/Exception/SpammyRequestException.php +++ b/main/app/sprinkles/account/src/Controller/Exception/SpammyRequestException.php @@ -1,21 +1,21 @@ -<?php -/** - * UserFrosting (http://www.userfrosting.com) - * - * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) - */ - -namespace UserFrosting\Sprinkle\Account\Controller\Exception; - -use UserFrosting\Support\Exception\HttpException; - -/** - * Spammy request exception. Used when a bot has attempted to spam a public form, and fallen into our honeypot. - * - * @author Alex Weissman (https://alexanderweissman.com) - */ -class SpammyRequestException extends HttpException -{ - -} +<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Account\Controller\Exception;
+
+use UserFrosting\Support\Exception\HttpException;
+
+/**
+ * Spammy request exception. Used when a bot has attempted to spam a public form, and fallen into our honeypot.
+ *
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+class SpammyRequestException extends HttpException
+{
+
+}
diff --git a/main/app/sprinkles/account/src/Database/Migrations/v400/ActivitiesTable.php b/main/app/sprinkles/account/src/Database/Migrations/v400/ActivitiesTable.php index db02ec2..6fcb8aa 100644 --- a/main/app/sprinkles/account/src/Database/Migrations/v400/ActivitiesTable.php +++ b/main/app/sprinkles/account/src/Database/Migrations/v400/ActivitiesTable.php @@ -1,53 +1,53 @@ -<?php -/** - * UserFrosting (http://www.userfrosting.com) - * - * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) - */ - -namespace UserFrosting\Sprinkle\Account\Database\Migrations\v400; - -use UserFrosting\System\Bakery\Migration; -use Illuminate\Database\Schema\Blueprint; -use Illuminate\Database\Schema\Builder; - -/** - * Sessions table migration - * Version 4.0.0 - * - * See https://laravel.com/docs/5.4/migrations#tables - * @extends Migration - * @author Alex Weissman (https://alexanderweissman.com) - */ -class ActivitiesTable extends Migration -{ - /** - * {@inheritDoc} - */ - public function up() { - if (!$this->schema->hasTable('activities')) { - $this->schema->create('activities', function (Blueprint $table) { - $table->increments('id'); - $table->string('ip_address', 45)->nullable(); - $table->integer('user_id')->unsigned(); - $table->string('type', 255)->comment('An identifier used to track the type of activity.'); - $table->timestamp('occurred_at')->nullable(); - $table->text('description')->nullable(); - - $table->engine = 'InnoDB'; - $table->collation = 'utf8_unicode_ci'; - $table->charset = 'utf8'; - //$table->foreign('user_id')->references('id')->on('users'); - $table->index('user_id'); - }); - } - } - - /** - * {@inheritDoc} - */ - public function down() { - $this->schema->drop('activities'); - } +<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Account\Database\Migrations\v400;
+
+use UserFrosting\System\Bakery\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Database\Schema\Builder;
+
+/**
+ * Sessions table migration
+ * Version 4.0.0
+ *
+ * See https://laravel.com/docs/5.4/migrations#tables
+ * @extends Migration
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+class ActivitiesTable extends Migration
+{
+ /**
+ * {@inheritDoc}
+ */
+ public function up() {
+ if (!$this->schema->hasTable('activities')) {
+ $this->schema->create('activities', function (Blueprint $table) {
+ $table->increments('id');
+ $table->string('ip_address', 45)->nullable();
+ $table->integer('user_id')->unsigned();
+ $table->string('type', 255)->comment('An identifier used to track the type of activity.');
+ $table->timestamp('occurred_at')->nullable();
+ $table->text('description')->nullable();
+
+ $table->engine = 'InnoDB';
+ $table->collation = 'utf8_unicode_ci';
+ $table->charset = 'utf8';
+ //$table->foreign('user_id')->references('id')->on('users');
+ $table->index('user_id');
+ });
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function down() {
+ $this->schema->drop('activities');
+ }
}
\ No newline at end of file diff --git a/main/app/sprinkles/account/src/Database/Migrations/v400/GroupsTable.php b/main/app/sprinkles/account/src/Database/Migrations/v400/GroupsTable.php index 07583af..d8498f4 100644 --- a/main/app/sprinkles/account/src/Database/Migrations/v400/GroupsTable.php +++ b/main/app/sprinkles/account/src/Database/Migrations/v400/GroupsTable.php @@ -1,81 +1,81 @@ -<?php -/** - * UserFrosting (http://www.userfrosting.com) - * - * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) - */ - -namespace UserFrosting\Sprinkle\Account\Database\Migrations\v400; - -use Illuminate\Database\Schema\Blueprint; -use Illuminate\Database\Schema\Builder; -use UserFrosting\Sprinkle\Account\Database\Models\Group; -use UserFrosting\System\Bakery\Migration; - -/** - * Groups table migration - * "Group" now replaces the notion of "primary group" in earlier versions of UF. A user can belong to exactly one group. - * Version 4.0.0 - * - * See https://laravel.com/docs/5.4/migrations#tables - * @extends Migration - * @author Alex Weissman (https://alexanderweissman.com) - */ -class GroupsTable extends Migration -{ - /** - * {@inheritDoc} - */ - public function up() { - if (!$this->schema->hasTable('groups')) { - $this->schema->create('groups', function (Blueprint $table) { - $table->increments('id'); - $table->string('slug'); - $table->string('name'); - $table->text('description')->nullable(); - $table->string('icon', 100)->nullable(FALSE)->default('fa fa-user')->comment('The icon representing users in this group.'); - $table->timestamps(); - - $table->engine = 'InnoDB'; - $table->collation = 'utf8_unicode_ci'; - $table->charset = 'utf8'; - $table->unique('slug'); - $table->index('slug'); - }); - - // Add default groups - $groups = [ - 'terran' => new Group([ - 'slug' => 'terran', - 'name' => 'Terran', - 'description' => 'The terrans are a young species with psionic potential. The terrans of the Koprulu sector descend from the survivors of a disastrous 23rd century colonization mission from Earth.', - 'icon' => 'sc sc-terran' - ]), - 'zerg' => new Group([ - 'slug' => 'zerg', - 'name' => 'Zerg', - 'description' => 'Dedicated to the pursuit of genetic perfection, the zerg relentlessly hunt down and assimilate advanced species across the galaxy, incorporating useful genetic code into their own.', - 'icon' => 'sc sc-zerg' - ]), - 'protoss' => new Group([ - 'slug' => 'protoss', - 'name' => 'Protoss', - 'description' => 'The protoss, a.k.a. the Firstborn, are a sapient humanoid race native to Aiur. Their advanced technology complements and enhances their psionic mastery.', - 'icon' => 'sc sc-protoss' - ]) - ]; - - foreach ($groups as $slug => $group) { - $group->save(); - } - } - } - - /** - * {@inheritDoc} - */ - public function down() { - $this->schema->drop('groups'); - } -} +<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Account\Database\Migrations\v400;
+
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Database\Schema\Builder;
+use UserFrosting\Sprinkle\Account\Database\Models\Group;
+use UserFrosting\System\Bakery\Migration;
+
+/**
+ * Groups table migration
+ * "Group" now replaces the notion of "primary group" in earlier versions of UF. A user can belong to exactly one group.
+ * Version 4.0.0
+ *
+ * See https://laravel.com/docs/5.4/migrations#tables
+ * @extends Migration
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+class GroupsTable extends Migration
+{
+ /**
+ * {@inheritDoc}
+ */
+ public function up() {
+ if (!$this->schema->hasTable('groups')) {
+ $this->schema->create('groups', function (Blueprint $table) {
+ $table->increments('id');
+ $table->string('slug');
+ $table->string('name');
+ $table->text('description')->nullable();
+ $table->string('icon', 100)->nullable(FALSE)->default('fa fa-user')->comment('The icon representing users in this group.');
+ $table->timestamps();
+
+ $table->engine = 'InnoDB';
+ $table->collation = 'utf8_unicode_ci';
+ $table->charset = 'utf8';
+ $table->unique('slug');
+ $table->index('slug');
+ });
+
+ // Add default groups
+ $groups = [
+ 'terran' => new Group([
+ 'slug' => 'terran',
+ 'name' => 'Terran',
+ 'description' => 'The terrans are a young species with psionic potential. The terrans of the Koprulu sector descend from the survivors of a disastrous 23rd century colonization mission from Earth.',
+ 'icon' => 'sc sc-terran'
+ ]),
+ 'zerg' => new Group([
+ 'slug' => 'zerg',
+ 'name' => 'Zerg',
+ 'description' => 'Dedicated to the pursuit of genetic perfection, the zerg relentlessly hunt down and assimilate advanced species across the galaxy, incorporating useful genetic code into their own.',
+ 'icon' => 'sc sc-zerg'
+ ]),
+ 'protoss' => new Group([
+ 'slug' => 'protoss',
+ 'name' => 'Protoss',
+ 'description' => 'The protoss, a.k.a. the Firstborn, are a sapient humanoid race native to Aiur. Their advanced technology complements and enhances their psionic mastery.',
+ 'icon' => 'sc sc-protoss'
+ ])
+ ];
+
+ foreach ($groups as $slug => $group) {
+ $group->save();
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function down() {
+ $this->schema->drop('groups');
+ }
+}
diff --git a/main/app/sprinkles/account/src/Database/Migrations/v400/PasswordResetsTable.php b/main/app/sprinkles/account/src/Database/Migrations/v400/PasswordResetsTable.php index 47eb00d..932ab47 100644 --- a/main/app/sprinkles/account/src/Database/Migrations/v400/PasswordResetsTable.php +++ b/main/app/sprinkles/account/src/Database/Migrations/v400/PasswordResetsTable.php @@ -1,56 +1,56 @@ -<?php -/** - * UserFrosting (http://www.userfrosting.com) - * - * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) - */ - -namespace UserFrosting\Sprinkle\Account\Database\Migrations\v400; - -use Illuminate\Database\Schema\Blueprint; -use Illuminate\Database\Schema\Builder; -use UserFrosting\System\Bakery\Migration; - -/** - * password_resets table migration - * Manages requests for password resets. - * Version 4.0.0 - * - * See https://laravel.com/docs/5.4/migrations#tables - * @extends Migration - * @author Alex Weissman (https://alexanderweissman.com) - */ -class passwordResetsTable extends Migration -{ - /** - * {@inheritDoc} - */ - public function up() { - if (!$this->schema->hasTable('password_resets')) { - $this->schema->create('password_resets', function (Blueprint $table) { - $table->increments('id'); - $table->integer('user_id')->unsigned(); - $table->string('hash'); - $table->boolean('completed')->default(0); - $table->timestamp('expires_at')->nullable(); - $table->timestamp('completed_at')->nullable(); - $table->timestamps(); - - $table->engine = 'InnoDB'; - $table->collation = 'utf8_unicode_ci'; - $table->charset = 'utf8'; - //$table->foreign('user_id')->references('id')->on('users'); - $table->index('user_id'); - $table->index('hash'); - }); - } - } - - /** - * {@inheritDoc} - */ - public function down() { - $this->schema->drop('password_resets'); - } -} +<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Account\Database\Migrations\v400;
+
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Database\Schema\Builder;
+use UserFrosting\System\Bakery\Migration;
+
+/**
+ * password_resets table migration
+ * Manages requests for password resets.
+ * Version 4.0.0
+ *
+ * See https://laravel.com/docs/5.4/migrations#tables
+ * @extends Migration
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+class passwordResetsTable extends Migration
+{
+ /**
+ * {@inheritDoc}
+ */
+ public function up() {
+ if (!$this->schema->hasTable('password_resets')) {
+ $this->schema->create('password_resets', function (Blueprint $table) {
+ $table->increments('id');
+ $table->integer('user_id')->unsigned();
+ $table->string('hash');
+ $table->boolean('completed')->default(0);
+ $table->timestamp('expires_at')->nullable();
+ $table->timestamp('completed_at')->nullable();
+ $table->timestamps();
+
+ $table->engine = 'InnoDB';
+ $table->collation = 'utf8_unicode_ci';
+ $table->charset = 'utf8';
+ //$table->foreign('user_id')->references('id')->on('users');
+ $table->index('user_id');
+ $table->index('hash');
+ });
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function down() {
+ $this->schema->drop('password_resets');
+ }
+}
diff --git a/main/app/sprinkles/account/src/Database/Migrations/v400/PermissionRolesTable.php b/main/app/sprinkles/account/src/Database/Migrations/v400/PermissionRolesTable.php index 8e06cd6..dca6639 100644 --- a/main/app/sprinkles/account/src/Database/Migrations/v400/PermissionRolesTable.php +++ b/main/app/sprinkles/account/src/Database/Migrations/v400/PermissionRolesTable.php @@ -1,54 +1,54 @@ -<?php -/** - * UserFrosting (http://www.userfrosting.com) - * - * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) - */ - -namespace UserFrosting\Sprinkle\Account\Database\Migrations\v400; - -use Illuminate\Database\Schema\Blueprint; -use Illuminate\Database\Schema\Builder; -use UserFrosting\System\Bakery\Migration; - -/** - * Permission_roles table migration - * Many-to-many mapping between permissions and roles. - * Version 4.0.0 - * - * See https://laravel.com/docs/5.4/migrations#tables - * @extends Migration - * @author Alex Weissman (https://alexanderweissman.com) - */ -class PermissionRolesTable extends Migration -{ - /** - * {@inheritDoc} - */ - public function up() { - if (!$this->schema->hasTable('permission_roles')) { - $this->schema->create('permission_roles', function (Blueprint $table) { - $table->integer('permission_id')->unsigned(); - $table->integer('role_id')->unsigned(); - $table->timestamps(); - - $table->engine = 'InnoDB'; - $table->collation = 'utf8_unicode_ci'; - $table->charset = 'utf8'; - $table->primary(['permission_id', 'role_id']); - //$table->foreign('permission_id')->references('id')->on('permissions'); - //$table->foreign('role_id')->references('id')->on('roles'); - $table->index('permission_id'); - $table->index('role_id'); - }); - } - } - - /** - * {@inheritDoc} - */ - public function down() { - $this->schema->drop('permission_roles'); - } -} +<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Account\Database\Migrations\v400;
+
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Database\Schema\Builder;
+use UserFrosting\System\Bakery\Migration;
+
+/**
+ * Permission_roles table migration
+ * Many-to-many mapping between permissions and roles.
+ * Version 4.0.0
+ *
+ * See https://laravel.com/docs/5.4/migrations#tables
+ * @extends Migration
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+class PermissionRolesTable extends Migration
+{
+ /**
+ * {@inheritDoc}
+ */
+ public function up() {
+ if (!$this->schema->hasTable('permission_roles')) {
+ $this->schema->create('permission_roles', function (Blueprint $table) {
+ $table->integer('permission_id')->unsigned();
+ $table->integer('role_id')->unsigned();
+ $table->timestamps();
+
+ $table->engine = 'InnoDB';
+ $table->collation = 'utf8_unicode_ci';
+ $table->charset = 'utf8';
+ $table->primary(['permission_id', 'role_id']);
+ //$table->foreign('permission_id')->references('id')->on('permissions');
+ //$table->foreign('role_id')->references('id')->on('roles');
+ $table->index('permission_id');
+ $table->index('role_id');
+ });
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function down() {
+ $this->schema->drop('permission_roles');
+ }
+}
diff --git a/main/app/sprinkles/account/src/Database/Migrations/v400/PermissionsTable.php b/main/app/sprinkles/account/src/Database/Migrations/v400/PermissionsTable.php index bef8cdd..efc014b 100644 --- a/main/app/sprinkles/account/src/Database/Migrations/v400/PermissionsTable.php +++ b/main/app/sprinkles/account/src/Database/Migrations/v400/PermissionsTable.php @@ -1,260 +1,260 @@ -<?php -/** - * UserFrosting (http://www.userfrosting.com) - * - * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) - */ - -namespace UserFrosting\Sprinkle\Account\Database\Migrations\v400; - -use Illuminate\Database\Schema\Blueprint; -use Illuminate\Database\Schema\Builder; -use UserFrosting\Sprinkle\Account\Database\Models\Permission; -use UserFrosting\Sprinkle\Account\Database\Models\Role; -use UserFrosting\System\Bakery\Migration; - -/** - * Permissions table migration - * Permissions now replace the 'authorize_group' and 'authorize_user' tables. - * Also, they now map many-to-many to roles. - * Version 4.0.0 - * - * See https://laravel.com/docs/5.4/migrations#tables - * @extends Migration - * @author Alex Weissman (https://alexanderweissman.com) - */ -class PermissionsTable extends Migration -{ - /** - * {@inheritDoc} - */ - public $dependencies = [ - '\UserFrosting\Sprinkle\Account\Database\Migrations\v400\RolesTable', - '\UserFrosting\Sprinkle\Account\Database\Migrations\v400\PermissionRolesTable' - ]; - - /** - * {@inheritDoc} - */ - public function up() { - if (!$this->schema->hasTable('permissions')) { - $this->schema->create('permissions', function (Blueprint $table) { - $table->increments('id'); - $table->string('slug')->comment('A code that references a specific action or URI that an assignee of this permission has access to.'); - $table->string('name'); - $table->text('conditions')->comment('The conditions under which members of this group have access to this hook.'); - $table->text('description')->nullable(); - $table->timestamps(); - - $table->engine = 'InnoDB'; - $table->collation = 'utf8_unicode_ci'; - $table->charset = 'utf8'; - }); - } - } - - /** - * {@inheritDoc} - */ - public function down() { - $this->schema->drop('permissions'); - } - - /** - * {@inheritDoc} - */ - public function seed() { - // Skip this if table is not empty - if (Permission::count() == 0) { - - $defaultRoleIds = [ - 'user' => Role::where('slug', 'user')->first()->id, - 'group-admin' => Role::where('slug', 'group-admin')->first()->id, - 'site-admin' => Role::where('slug', 'site-admin')->first()->id - ]; - - // Add default permissions - $permissions = [ - 'create_group' => new Permission([ - 'slug' => 'create_group', - 'name' => 'Create group', - 'conditions' => 'always()', - 'description' => 'Create a new group.' - ]), - 'create_user' => new Permission([ - 'slug' => 'create_user', - 'name' => 'Create user', - 'conditions' => 'always()', - 'description' => 'Create a new user in your own group and assign default roles.' - ]), - 'create_user_field' => new Permission([ - 'slug' => 'create_user_field', - 'name' => 'Set new user group', - 'conditions' => "subset(fields,['group'])", - 'description' => 'Set the group when creating a new user.' - ]), - 'delete_group' => new Permission([ - 'slug' => 'delete_group', - 'name' => 'Delete group', - 'conditions' => "always()", - 'description' => 'Delete a group.' - ]), - 'delete_user' => new Permission([ - 'slug' => 'delete_user', - 'name' => 'Delete user', - 'conditions' => "!has_role(user.id,{$defaultRoleIds['site-admin']}) && !is_master(user.id)", - 'description' => 'Delete users who are not Site Administrators.' - ]), - 'update_account_settings' => new Permission([ - 'slug' => 'update_account_settings', - 'name' => 'Edit user', - 'conditions' => 'always()', - 'description' => 'Edit your own account settings.' - ]), - 'update_group_field' => new Permission([ - 'slug' => 'update_group_field', - 'name' => 'Edit group', - 'conditions' => 'always()', - 'description' => 'Edit basic properties of any group.' - ]), - 'update_user_field' => new Permission([ - 'slug' => 'update_user_field', - 'name' => 'Edit user', - 'conditions' => "!has_role(user.id,{$defaultRoleIds['site-admin']}) && subset(fields,['name','email','locale','group','flag_enabled','flag_verified','password'])", - 'description' => 'Edit users who are not Site Administrators.' - ]), - 'update_user_field_group' => new Permission([ - 'slug' => 'update_user_field', - 'name' => 'Edit group user', - 'conditions' => "equals_num(self.group_id,user.group_id) && !is_master(user.id) && !has_role(user.id,{$defaultRoleIds['site-admin']}) && (!has_role(user.id,{$defaultRoleIds['group-admin']}) || equals_num(self.id,user.id)) && subset(fields,['name','email','locale','flag_enabled','flag_verified','password'])", - 'description' => 'Edit users in your own group who are not Site or Group Administrators, except yourself.' - ]), - 'uri_account_settings' => new Permission([ - 'slug' => 'uri_account_settings', - 'name' => 'Account settings page', - 'conditions' => 'always()', - 'description' => 'View the account settings page.' - ]), - 'uri_activities' => new Permission([ - 'slug' => 'uri_activities', - 'name' => 'Activity monitor', - 'conditions' => 'always()', - 'description' => 'View a list of all activities for all users.' - ]), - 'uri_dashboard' => new Permission([ - 'slug' => 'uri_dashboard', - 'name' => 'Admin dashboard', - 'conditions' => 'always()', - 'description' => 'View the administrative dashboard.' - ]), - 'uri_group' => new Permission([ - 'slug' => 'uri_group', - 'name' => 'View group', - 'conditions' => 'always()', - 'description' => 'View the group page of any group.' - ]), - 'uri_group_own' => new Permission([ - 'slug' => 'uri_group', - 'name' => 'View own group', - 'conditions' => 'equals_num(self.group_id,group.id)', - 'description' => 'View the group page of your own group.' - ]), - 'uri_groups' => new Permission([ - 'slug' => 'uri_groups', - 'name' => 'Group management page', - 'conditions' => 'always()', - 'description' => 'View a page containing a list of groups.' - ]), - 'uri_user' => new Permission([ - 'slug' => 'uri_user', - 'name' => 'View user', - 'conditions' => 'always()', - 'description' => 'View the user page of any user.' - ]), - 'uri_user_in_group' => new Permission([ - 'slug' => 'uri_user', - 'name' => 'View user', - 'conditions' => "equals_num(self.group_id,user.group_id) && !is_master(user.id) && !has_role(user.id,{$defaultRoleIds['site-admin']}) && (!has_role(user.id,{$defaultRoleIds['group-admin']}) || equals_num(self.id,user.id))", - 'description' => 'View the user page of any user in your group, except the master user and Site and Group Administrators (except yourself).' - ]), - 'uri_users' => new Permission([ - 'slug' => 'uri_users', - 'name' => 'User management page', - 'conditions' => 'always()', - 'description' => 'View a page containing a table of users.' - ]), - 'view_group_field' => new Permission([ - 'slug' => 'view_group_field', - 'name' => 'View group', - 'conditions' => "in(property,['name','icon','slug','description','users'])", - 'description' => 'View certain properties of any group.' - ]), - 'view_group_field_own' => new Permission([ - 'slug' => 'view_group_field', - 'name' => 'View group', - 'conditions' => "equals_num(self.group_id,group.id) && in(property,['name','icon','slug','description','users'])", - 'description' => 'View certain properties of your own group.' - ]), - 'view_user_field' => new Permission([ - 'slug' => 'view_user_field', - 'name' => 'View user', - 'conditions' => "in(property,['user_name','name','email','locale','theme','roles','group','activities'])", - 'description' => 'View certain properties of any user.' - ]), - 'view_user_field_group' => new Permission([ - 'slug' => 'view_user_field', - 'name' => 'View user', - 'conditions' => "equals_num(self.group_id,user.group_id) && !is_master(user.id) && !has_role(user.id,{$defaultRoleIds['site-admin']}) && (!has_role(user.id,{$defaultRoleIds['group-admin']}) || equals_num(self.id,user.id)) && in(property,['user_name','name','email','locale','roles','group','activities'])", - 'description' => 'View certain properties of any user in your own group, except the master user and Site and Group Administrators (except yourself).' - ]) - ]; - - foreach ($permissions as $slug => $permission) { - $permission->save(); - } - - // Add default mappings to permissions - $roleUser = Role::where('slug', 'user')->first(); - if ($roleUser) { - $roleUser->permissions()->sync([ - $permissions['update_account_settings']->id, - $permissions['uri_account_settings']->id, - $permissions['uri_dashboard']->id - ]); - } - - $roleSiteAdmin = Role::where('slug', 'site-admin')->first(); - if ($roleSiteAdmin) { - $roleSiteAdmin->permissions()->sync([ - $permissions['create_group']->id, - $permissions['create_user']->id, - $permissions['create_user_field']->id, - $permissions['delete_group']->id, - $permissions['delete_user']->id, - $permissions['update_user_field']->id, - $permissions['update_group_field']->id, - $permissions['uri_activities']->id, - $permissions['uri_group']->id, - $permissions['uri_groups']->id, - $permissions['uri_user']->id, - $permissions['uri_users']->id, - $permissions['view_group_field']->id, - $permissions['view_user_field']->id - ]); - } - - $roleGroupAdmin = Role::where('slug', 'group-admin')->first(); - if ($roleGroupAdmin) { - $roleGroupAdmin->permissions()->sync([ - $permissions['create_user']->id, - $permissions['update_user_field_group']->id, - $permissions['uri_group_own']->id, - $permissions['uri_user_in_group']->id, - $permissions['view_group_field_own']->id, - $permissions['view_user_field_group']->id - ]); - } - } - } -} +<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Account\Database\Migrations\v400;
+
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Database\Schema\Builder;
+use UserFrosting\Sprinkle\Account\Database\Models\Permission;
+use UserFrosting\Sprinkle\Account\Database\Models\Role;
+use UserFrosting\System\Bakery\Migration;
+
+/**
+ * Permissions table migration
+ * Permissions now replace the 'authorize_group' and 'authorize_user' tables.
+ * Also, they now map many-to-many to roles.
+ * Version 4.0.0
+ *
+ * See https://laravel.com/docs/5.4/migrations#tables
+ * @extends Migration
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+class PermissionsTable extends Migration
+{
+ /**
+ * {@inheritDoc}
+ */
+ public $dependencies = [
+ '\UserFrosting\Sprinkle\Account\Database\Migrations\v400\RolesTable',
+ '\UserFrosting\Sprinkle\Account\Database\Migrations\v400\PermissionRolesTable'
+ ];
+
+ /**
+ * {@inheritDoc}
+ */
+ public function up() {
+ if (!$this->schema->hasTable('permissions')) {
+ $this->schema->create('permissions', function (Blueprint $table) {
+ $table->increments('id');
+ $table->string('slug')->comment('A code that references a specific action or URI that an assignee of this permission has access to.');
+ $table->string('name');
+ $table->text('conditions')->comment('The conditions under which members of this group have access to this hook.');
+ $table->text('description')->nullable();
+ $table->timestamps();
+
+ $table->engine = 'InnoDB';
+ $table->collation = 'utf8_unicode_ci';
+ $table->charset = 'utf8';
+ });
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function down() {
+ $this->schema->drop('permissions');
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function seed() {
+ // Skip this if table is not empty
+ if (Permission::count() == 0) {
+
+ $defaultRoleIds = [
+ 'user' => Role::where('slug', 'user')->first()->id,
+ 'group-admin' => Role::where('slug', 'group-admin')->first()->id,
+ 'site-admin' => Role::where('slug', 'site-admin')->first()->id
+ ];
+
+ // Add default permissions
+ $permissions = [
+ 'create_group' => new Permission([
+ 'slug' => 'create_group',
+ 'name' => 'Create group',
+ 'conditions' => 'always()',
+ 'description' => 'Create a new group.'
+ ]),
+ 'create_user' => new Permission([
+ 'slug' => 'create_user',
+ 'name' => 'Create user',
+ 'conditions' => 'always()',
+ 'description' => 'Create a new user in your own group and assign default roles.'
+ ]),
+ 'create_user_field' => new Permission([
+ 'slug' => 'create_user_field',
+ 'name' => 'Set new user group',
+ 'conditions' => "subset(fields,['group'])",
+ 'description' => 'Set the group when creating a new user.'
+ ]),
+ 'delete_group' => new Permission([
+ 'slug' => 'delete_group',
+ 'name' => 'Delete group',
+ 'conditions' => "always()",
+ 'description' => 'Delete a group.'
+ ]),
+ 'delete_user' => new Permission([
+ 'slug' => 'delete_user',
+ 'name' => 'Delete user',
+ 'conditions' => "!has_role(user.id,{$defaultRoleIds['site-admin']}) && !is_master(user.id)",
+ 'description' => 'Delete users who are not Site Administrators.'
+ ]),
+ 'update_account_settings' => new Permission([
+ 'slug' => 'update_account_settings',
+ 'name' => 'Edit user',
+ 'conditions' => 'always()',
+ 'description' => 'Edit your own account settings.'
+ ]),
+ 'update_group_field' => new Permission([
+ 'slug' => 'update_group_field',
+ 'name' => 'Edit group',
+ 'conditions' => 'always()',
+ 'description' => 'Edit basic properties of any group.'
+ ]),
+ 'update_user_field' => new Permission([
+ 'slug' => 'update_user_field',
+ 'name' => 'Edit user',
+ 'conditions' => "!has_role(user.id,{$defaultRoleIds['site-admin']}) && subset(fields,['name','email','locale','group','flag_enabled','flag_verified','password'])",
+ 'description' => 'Edit users who are not Site Administrators.'
+ ]),
+ 'update_user_field_group' => new Permission([
+ 'slug' => 'update_user_field',
+ 'name' => 'Edit group user',
+ 'conditions' => "equals_num(self.group_id,user.group_id) && !is_master(user.id) && !has_role(user.id,{$defaultRoleIds['site-admin']}) && (!has_role(user.id,{$defaultRoleIds['group-admin']}) || equals_num(self.id,user.id)) && subset(fields,['name','email','locale','flag_enabled','flag_verified','password'])",
+ 'description' => 'Edit users in your own group who are not Site or Group Administrators, except yourself.'
+ ]),
+ 'uri_account_settings' => new Permission([
+ 'slug' => 'uri_account_settings',
+ 'name' => 'Account settings page',
+ 'conditions' => 'always()',
+ 'description' => 'View the account settings page.'
+ ]),
+ 'uri_activities' => new Permission([
+ 'slug' => 'uri_activities',
+ 'name' => 'Activity monitor',
+ 'conditions' => 'always()',
+ 'description' => 'View a list of all activities for all users.'
+ ]),
+ 'uri_dashboard' => new Permission([
+ 'slug' => 'uri_dashboard',
+ 'name' => 'Admin dashboard',
+ 'conditions' => 'always()',
+ 'description' => 'View the administrative dashboard.'
+ ]),
+ 'uri_group' => new Permission([
+ 'slug' => 'uri_group',
+ 'name' => 'View group',
+ 'conditions' => 'always()',
+ 'description' => 'View the group page of any group.'
+ ]),
+ 'uri_group_own' => new Permission([
+ 'slug' => 'uri_group',
+ 'name' => 'View own group',
+ 'conditions' => 'equals_num(self.group_id,group.id)',
+ 'description' => 'View the group page of your own group.'
+ ]),
+ 'uri_groups' => new Permission([
+ 'slug' => 'uri_groups',
+ 'name' => 'Group management page',
+ 'conditions' => 'always()',
+ 'description' => 'View a page containing a list of groups.'
+ ]),
+ 'uri_user' => new Permission([
+ 'slug' => 'uri_user',
+ 'name' => 'View user',
+ 'conditions' => 'always()',
+ 'description' => 'View the user page of any user.'
+ ]),
+ 'uri_user_in_group' => new Permission([
+ 'slug' => 'uri_user',
+ 'name' => 'View user',
+ 'conditions' => "equals_num(self.group_id,user.group_id) && !is_master(user.id) && !has_role(user.id,{$defaultRoleIds['site-admin']}) && (!has_role(user.id,{$defaultRoleIds['group-admin']}) || equals_num(self.id,user.id))",
+ 'description' => 'View the user page of any user in your group, except the master user and Site and Group Administrators (except yourself).'
+ ]),
+ 'uri_users' => new Permission([
+ 'slug' => 'uri_users',
+ 'name' => 'User management page',
+ 'conditions' => 'always()',
+ 'description' => 'View a page containing a table of users.'
+ ]),
+ 'view_group_field' => new Permission([
+ 'slug' => 'view_group_field',
+ 'name' => 'View group',
+ 'conditions' => "in(property,['name','icon','slug','description','users'])",
+ 'description' => 'View certain properties of any group.'
+ ]),
+ 'view_group_field_own' => new Permission([
+ 'slug' => 'view_group_field',
+ 'name' => 'View group',
+ 'conditions' => "equals_num(self.group_id,group.id) && in(property,['name','icon','slug','description','users'])",
+ 'description' => 'View certain properties of your own group.'
+ ]),
+ 'view_user_field' => new Permission([
+ 'slug' => 'view_user_field',
+ 'name' => 'View user',
+ 'conditions' => "in(property,['user_name','name','email','locale','theme','roles','group','activities'])",
+ 'description' => 'View certain properties of any user.'
+ ]),
+ 'view_user_field_group' => new Permission([
+ 'slug' => 'view_user_field',
+ 'name' => 'View user',
+ 'conditions' => "equals_num(self.group_id,user.group_id) && !is_master(user.id) && !has_role(user.id,{$defaultRoleIds['site-admin']}) && (!has_role(user.id,{$defaultRoleIds['group-admin']}) || equals_num(self.id,user.id)) && in(property,['user_name','name','email','locale','roles','group','activities'])",
+ 'description' => 'View certain properties of any user in your own group, except the master user and Site and Group Administrators (except yourself).'
+ ])
+ ];
+
+ foreach ($permissions as $slug => $permission) {
+ $permission->save();
+ }
+
+ // Add default mappings to permissions
+ $roleUser = Role::where('slug', 'user')->first();
+ if ($roleUser) {
+ $roleUser->permissions()->sync([
+ $permissions['update_account_settings']->id,
+ $permissions['uri_account_settings']->id,
+ $permissions['uri_dashboard']->id
+ ]);
+ }
+
+ $roleSiteAdmin = Role::where('slug', 'site-admin')->first();
+ if ($roleSiteAdmin) {
+ $roleSiteAdmin->permissions()->sync([
+ $permissions['create_group']->id,
+ $permissions['create_user']->id,
+ $permissions['create_user_field']->id,
+ $permissions['delete_group']->id,
+ $permissions['delete_user']->id,
+ $permissions['update_user_field']->id,
+ $permissions['update_group_field']->id,
+ $permissions['uri_activities']->id,
+ $permissions['uri_group']->id,
+ $permissions['uri_groups']->id,
+ $permissions['uri_user']->id,
+ $permissions['uri_users']->id,
+ $permissions['view_group_field']->id,
+ $permissions['view_user_field']->id
+ ]);
+ }
+
+ $roleGroupAdmin = Role::where('slug', 'group-admin')->first();
+ if ($roleGroupAdmin) {
+ $roleGroupAdmin->permissions()->sync([
+ $permissions['create_user']->id,
+ $permissions['update_user_field_group']->id,
+ $permissions['uri_group_own']->id,
+ $permissions['uri_user_in_group']->id,
+ $permissions['view_group_field_own']->id,
+ $permissions['view_user_field_group']->id
+ ]);
+ }
+ }
+ }
+}
diff --git a/main/app/sprinkles/account/src/Database/Migrations/v400/PersistencesTable.php b/main/app/sprinkles/account/src/Database/Migrations/v400/PersistencesTable.php index 41378d3..c51461a 100644 --- a/main/app/sprinkles/account/src/Database/Migrations/v400/PersistencesTable.php +++ b/main/app/sprinkles/account/src/Database/Migrations/v400/PersistencesTable.php @@ -1,56 +1,56 @@ -<?php -/** - * UserFrosting (http://www.userfrosting.com) - * - * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) - */ - -namespace UserFrosting\Sprinkle\Account\Database\Migrations\v400; - -use Illuminate\Database\Schema\Blueprint; -use Illuminate\Database\Schema\Builder; -use UserFrosting\System\Bakery\Migration; - -/** - * Persistences table migration - * Many-to-many mapping between roles and users. - * Version 4.0.0 - * - * See https://laravel.com/docs/5.4/migrations#tables - * @extends Migration - * @author Alex Weissman (https://alexanderweissman.com) - */ -class PersistencesTable extends Migration -{ - /** - * {@inheritDoc} - */ - public function up() { - if (!$this->schema->hasTable('persistences')) { - $this->schema->create('persistences', function (Blueprint $table) { - $table->increments('id'); - $table->integer('user_id')->unsigned(); - $table->string('token', 40); - $table->string('persistent_token', 40); - $table->timestamp('expires_at')->nullable(); - $table->timestamps(); - - $table->engine = 'InnoDB'; - $table->collation = 'utf8_unicode_ci'; - $table->charset = 'utf8'; - //$table->foreign('user_id')->references('id')->on('users'); - $table->index('user_id'); - $table->index('token'); - $table->index('persistent_token'); - }); - } - } - - /** - * {@inheritDoc} - */ - public function down() { - $this->schema->drop('persistences'); - } -} +<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Account\Database\Migrations\v400;
+
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Database\Schema\Builder;
+use UserFrosting\System\Bakery\Migration;
+
+/**
+ * Persistences table migration
+ * Many-to-many mapping between roles and users.
+ * Version 4.0.0
+ *
+ * See https://laravel.com/docs/5.4/migrations#tables
+ * @extends Migration
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+class PersistencesTable extends Migration
+{
+ /**
+ * {@inheritDoc}
+ */
+ public function up() {
+ if (!$this->schema->hasTable('persistences')) {
+ $this->schema->create('persistences', function (Blueprint $table) {
+ $table->increments('id');
+ $table->integer('user_id')->unsigned();
+ $table->string('token', 40);
+ $table->string('persistent_token', 40);
+ $table->timestamp('expires_at')->nullable();
+ $table->timestamps();
+
+ $table->engine = 'InnoDB';
+ $table->collation = 'utf8_unicode_ci';
+ $table->charset = 'utf8';
+ //$table->foreign('user_id')->references('id')->on('users');
+ $table->index('user_id');
+ $table->index('token');
+ $table->index('persistent_token');
+ });
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function down() {
+ $this->schema->drop('persistences');
+ }
+}
diff --git a/main/app/sprinkles/account/src/Database/Migrations/v400/RoleUsersTable.php b/main/app/sprinkles/account/src/Database/Migrations/v400/RoleUsersTable.php index 4c7ca06..0820a02 100644 --- a/main/app/sprinkles/account/src/Database/Migrations/v400/RoleUsersTable.php +++ b/main/app/sprinkles/account/src/Database/Migrations/v400/RoleUsersTable.php @@ -1,54 +1,54 @@ -<?php -/** - * UserFrosting (http://www.userfrosting.com) - * - * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) - */ - -namespace UserFrosting\Sprinkle\Account\Database\Migrations\v400; - -use Illuminate\Database\Schema\Blueprint; -use Illuminate\Database\Schema\Builder; -use UserFrosting\System\Bakery\Migration; - -/** - * Role_users table migration - * Many-to-many mapping between roles and users. - * Version 4.0.0 - * - * See https://laravel.com/docs/5.4/migrations#tables - * @extends Migration - * @author Alex Weissman (https://alexanderweissman.com) - */ -class RoleUsersTable extends Migration -{ - /** - * {@inheritDoc} - */ - public function up() { - if (!$this->schema->hasTable('role_users')) { - $this->schema->create('role_users', function (Blueprint $table) { - $table->integer('user_id')->unsigned(); - $table->integer('role_id')->unsigned(); - $table->timestamps(); - - $table->engine = 'InnoDB'; - $table->collation = 'utf8_unicode_ci'; - $table->charset = 'utf8'; - $table->primary(['user_id', 'role_id']); - //$table->foreign('user_id')->references('id')->on('users'); - //$table->foreign('role_id')->references('id')->on('roles'); - $table->index('user_id'); - $table->index('role_id'); - }); - } - } - - /** - * {@inheritDoc} - */ - public function down() { - $this->schema->drop('role_users'); - } -} +<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Account\Database\Migrations\v400;
+
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Database\Schema\Builder;
+use UserFrosting\System\Bakery\Migration;
+
+/**
+ * Role_users table migration
+ * Many-to-many mapping between roles and users.
+ * Version 4.0.0
+ *
+ * See https://laravel.com/docs/5.4/migrations#tables
+ * @extends Migration
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+class RoleUsersTable extends Migration
+{
+ /**
+ * {@inheritDoc}
+ */
+ public function up() {
+ if (!$this->schema->hasTable('role_users')) {
+ $this->schema->create('role_users', function (Blueprint $table) {
+ $table->integer('user_id')->unsigned();
+ $table->integer('role_id')->unsigned();
+ $table->timestamps();
+
+ $table->engine = 'InnoDB';
+ $table->collation = 'utf8_unicode_ci';
+ $table->charset = 'utf8';
+ $table->primary(['user_id', 'role_id']);
+ //$table->foreign('user_id')->references('id')->on('users');
+ //$table->foreign('role_id')->references('id')->on('roles');
+ $table->index('user_id');
+ $table->index('role_id');
+ });
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function down() {
+ $this->schema->drop('role_users');
+ }
+}
diff --git a/main/app/sprinkles/account/src/Database/Migrations/v400/RolesTable.php b/main/app/sprinkles/account/src/Database/Migrations/v400/RolesTable.php index 20fe699..3a524e2 100644 --- a/main/app/sprinkles/account/src/Database/Migrations/v400/RolesTable.php +++ b/main/app/sprinkles/account/src/Database/Migrations/v400/RolesTable.php @@ -1,77 +1,77 @@ -<?php -/** - * UserFrosting (http://www.userfrosting.com) - * - * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) - */ - -namespace UserFrosting\Sprinkle\Account\Database\Migrations\v400; - -use Illuminate\Database\Schema\Blueprint; -use Illuminate\Database\Schema\Builder; -use UserFrosting\Sprinkle\Account\Database\Models\Role; -use UserFrosting\System\Bakery\Migration; - -/** - * Roles table migration - * Roles replace "groups" in UF 0.3.x. Users acquire permissions through roles. - * Version 4.0.0 - * - * See https://laravel.com/docs/5.4/migrations#tables - * @extends Migration - * @author Alex Weissman (https://alexanderweissman.com) - */ -class RolesTable extends Migration -{ - /** - * {@inheritDoc} - */ - public function up() { - if (!$this->schema->hasTable('roles')) { - $this->schema->create('roles', function (Blueprint $table) { - $table->increments('id'); - $table->string('slug'); - $table->string('name'); - $table->text('description')->nullable(); - $table->timestamps(); - - $table->engine = 'InnoDB'; - $table->collation = 'utf8_unicode_ci'; - $table->charset = 'utf8'; - $table->unique('slug'); - $table->index('slug'); - }); - - // Add default roles - $roles = [ - 'user' => new Role([ - 'slug' => 'user', - 'name' => 'User', - 'description' => 'This role provides basic user functionality.' - ]), - 'site-admin' => new Role([ - 'slug' => 'site-admin', - 'name' => 'Site Administrator', - 'description' => 'This role is meant for "site administrators", who can basically do anything except create, edit, or delete other administrators.' - ]), - 'group-admin' => new Role([ - 'slug' => 'group-admin', - 'name' => 'Group Administrator', - 'description' => 'This role is meant for "group administrators", who can basically do anything with users in their own group, except other administrators of that group.' - ]) - ]; - - foreach ($roles as $slug => $role) { - $role->save(); - } - } - } - - /** - * {@inheritDoc} - */ - public function down() { - $this->schema->drop('roles'); - } -} +<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Account\Database\Migrations\v400;
+
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Database\Schema\Builder;
+use UserFrosting\Sprinkle\Account\Database\Models\Role;
+use UserFrosting\System\Bakery\Migration;
+
+/**
+ * Roles table migration
+ * Roles replace "groups" in UF 0.3.x. Users acquire permissions through roles.
+ * Version 4.0.0
+ *
+ * See https://laravel.com/docs/5.4/migrations#tables
+ * @extends Migration
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+class RolesTable extends Migration
+{
+ /**
+ * {@inheritDoc}
+ */
+ public function up() {
+ if (!$this->schema->hasTable('roles')) {
+ $this->schema->create('roles', function (Blueprint $table) {
+ $table->increments('id');
+ $table->string('slug');
+ $table->string('name');
+ $table->text('description')->nullable();
+ $table->timestamps();
+
+ $table->engine = 'InnoDB';
+ $table->collation = 'utf8_unicode_ci';
+ $table->charset = 'utf8';
+ $table->unique('slug');
+ $table->index('slug');
+ });
+
+ // Add default roles
+ $roles = [
+ 'user' => new Role([
+ 'slug' => 'user',
+ 'name' => 'User',
+ 'description' => 'This role provides basic user functionality.'
+ ]),
+ 'site-admin' => new Role([
+ 'slug' => 'site-admin',
+ 'name' => 'Site Administrator',
+ 'description' => 'This role is meant for "site administrators", who can basically do anything except create, edit, or delete other administrators.'
+ ]),
+ 'group-admin' => new Role([
+ 'slug' => 'group-admin',
+ 'name' => 'Group Administrator',
+ 'description' => 'This role is meant for "group administrators", who can basically do anything with users in their own group, except other administrators of that group.'
+ ])
+ ];
+
+ foreach ($roles as $slug => $role) {
+ $role->save();
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function down() {
+ $this->schema->drop('roles');
+ }
+}
diff --git a/main/app/sprinkles/account/src/Database/Migrations/v400/UsersTable.php b/main/app/sprinkles/account/src/Database/Migrations/v400/UsersTable.php index 9c634e8..694da5b 100644 --- a/main/app/sprinkles/account/src/Database/Migrations/v400/UsersTable.php +++ b/main/app/sprinkles/account/src/Database/Migrations/v400/UsersTable.php @@ -1,68 +1,68 @@ -<?php -/** - * UserFrosting (http://www.userfrosting.com) - * - * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) - */ - -namespace UserFrosting\Sprinkle\Account\Database\Migrations\v400; - -use Illuminate\Database\Schema\Blueprint; -use Illuminate\Database\Schema\Builder; -use UserFrosting\System\Bakery\Migration; - -/** - * Users table migration - * Removed the 'display_name', 'title', 'secret_token', and 'flag_password_reset' fields, and added first and last name and 'last_activity_id'. - * Version 4.0.0 - * - * See https://laravel.com/docs/5.4/migrations#tables - * @extends Migration - * @author Alex Weissman (https://alexanderweissman.com) - */ -class UsersTable extends Migration -{ - /** - * {@inheritDoc} - */ - public function up() { - if (!$this->schema->hasTable('users')) { - $this->schema->create('users', function (Blueprint $table) { - $table->increments('id'); - $table->string('user_name', 50); - $table->string('email', 254); - $table->string('first_name', 20); - $table->string('last_name', 30); - $table->string('locale', 10)->default('en_US')->comment('The language and locale to use for this user.'); - $table->string('theme', 100)->nullable()->comment("The user theme."); - $table->integer('group_id')->unsigned()->default(1)->comment("The id of the user group."); - $table->boolean('flag_verified')->default(1)->comment("Set to 1 if the user has verified their account via email, 0 otherwise."); - $table->boolean('flag_enabled')->default(1)->comment("Set to 1 if the user account is currently enabled, 0 otherwise. Disabled accounts cannot be logged in to, but they retain all of their data and settings."); - $table->integer('last_activity_id')->unsigned()->nullable()->comment("The id of the last activity performed by this user."); - $table->string('password', 255); - $table->softDeletes(); - $table->timestamps(); - - $table->engine = 'InnoDB'; - $table->collation = 'utf8_unicode_ci'; - $table->charset = 'utf8'; - //$table->foreign('group_id')->references('id')->on('groups'); - //$table->foreign('last_activity_id')->references('id')->on('activities'); - $table->unique('user_name'); - $table->index('user_name'); - $table->unique('email'); - $table->index('email'); - $table->index('group_id'); - $table->index('last_activity_id'); - }); - } - } - - /** - * {@inheritDoc} - */ - public function down() { - $this->schema->drop('users'); - } -} +<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Account\Database\Migrations\v400;
+
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Database\Schema\Builder;
+use UserFrosting\System\Bakery\Migration;
+
+/**
+ * Users table migration
+ * Removed the 'display_name', 'title', 'secret_token', and 'flag_password_reset' fields, and added first and last name and 'last_activity_id'.
+ * Version 4.0.0
+ *
+ * See https://laravel.com/docs/5.4/migrations#tables
+ * @extends Migration
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+class UsersTable extends Migration
+{
+ /**
+ * {@inheritDoc}
+ */
+ public function up() {
+ if (!$this->schema->hasTable('users')) {
+ $this->schema->create('users', function (Blueprint $table) {
+ $table->increments('id');
+ $table->string('user_name', 50);
+ $table->string('email', 254);
+ $table->string('first_name', 20);
+ $table->string('last_name', 30);
+ $table->string('locale', 10)->default('en_US')->comment('The language and locale to use for this user.');
+ $table->string('theme', 100)->nullable()->comment("The user theme.");
+ $table->integer('group_id')->unsigned()->default(1)->comment("The id of the user group.");
+ $table->boolean('flag_verified')->default(1)->comment("Set to 1 if the user has verified their account via email, 0 otherwise.");
+ $table->boolean('flag_enabled')->default(1)->comment("Set to 1 if the user account is currently enabled, 0 otherwise. Disabled accounts cannot be logged in to, but they retain all of their data and settings.");
+ $table->integer('last_activity_id')->unsigned()->nullable()->comment("The id of the last activity performed by this user.");
+ $table->string('password', 255);
+ $table->softDeletes();
+ $table->timestamps();
+
+ $table->engine = 'InnoDB';
+ $table->collation = 'utf8_unicode_ci';
+ $table->charset = 'utf8';
+ //$table->foreign('group_id')->references('id')->on('groups');
+ //$table->foreign('last_activity_id')->references('id')->on('activities');
+ $table->unique('user_name');
+ $table->index('user_name');
+ $table->unique('email');
+ $table->index('email');
+ $table->index('group_id');
+ $table->index('last_activity_id');
+ });
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function down() {
+ $this->schema->drop('users');
+ }
+}
diff --git a/main/app/sprinkles/account/src/Database/Migrations/v400/VerificationsTable.php b/main/app/sprinkles/account/src/Database/Migrations/v400/VerificationsTable.php index e42114c..2c4d28f 100644 --- a/main/app/sprinkles/account/src/Database/Migrations/v400/VerificationsTable.php +++ b/main/app/sprinkles/account/src/Database/Migrations/v400/VerificationsTable.php @@ -1,56 +1,56 @@ -<?php -/** - * UserFrosting (http://www.userfrosting.com) - * - * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) - */ - -namespace UserFrosting\Sprinkle\Account\Database\Migrations\v400; - -use Illuminate\Database\Schema\Blueprint; -use Illuminate\Database\Schema\Builder; -use UserFrosting\System\Bakery\Migration; - -/** - * Verifications table migration - * Manages requests for email account verification. - * Version 4.0.0 - * - * See https://laravel.com/docs/5.4/migrations#tables - * @extends Migration - * @author Alex Weissman (https://alexanderweissman.com) - */ -class VerificationsTable extends Migration -{ - /** - * {@inheritDoc} - */ - public function up() { - if (!$this->schema->hasTable('verifications')) { - $this->schema->create('verifications', function (Blueprint $table) { - $table->increments('id'); - $table->integer('user_id')->unsigned(); - $table->string('hash'); - $table->boolean('completed')->default(0); - $table->timestamp('expires_at')->nullable(); - $table->timestamp('completed_at')->nullable(); - $table->timestamps(); - - $table->engine = 'InnoDB'; - $table->collation = 'utf8_unicode_ci'; - $table->charset = 'utf8'; - //$table->foreign('user_id')->references('id')->on('users'); - $table->index('user_id'); - $table->index('hash'); - }); - } - } - - /** - * {@inheritDoc} - */ - public function down() { - $this->schema->drop('verifications'); - } -} +<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Account\Database\Migrations\v400;
+
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Database\Schema\Builder;
+use UserFrosting\System\Bakery\Migration;
+
+/**
+ * Verifications table migration
+ * Manages requests for email account verification.
+ * Version 4.0.0
+ *
+ * See https://laravel.com/docs/5.4/migrations#tables
+ * @extends Migration
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+class VerificationsTable extends Migration
+{
+ /**
+ * {@inheritDoc}
+ */
+ public function up() {
+ if (!$this->schema->hasTable('verifications')) {
+ $this->schema->create('verifications', function (Blueprint $table) {
+ $table->increments('id');
+ $table->integer('user_id')->unsigned();
+ $table->string('hash');
+ $table->boolean('completed')->default(0);
+ $table->timestamp('expires_at')->nullable();
+ $table->timestamp('completed_at')->nullable();
+ $table->timestamps();
+
+ $table->engine = 'InnoDB';
+ $table->collation = 'utf8_unicode_ci';
+ $table->charset = 'utf8';
+ //$table->foreign('user_id')->references('id')->on('users');
+ $table->index('user_id');
+ $table->index('hash');
+ });
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function down() {
+ $this->schema->drop('verifications');
+ }
+}
diff --git a/main/app/sprinkles/account/src/Database/Models/Activity.php b/main/app/sprinkles/account/src/Database/Models/Activity.php index 4e5b609..8f6cd18 100644 --- a/main/app/sprinkles/account/src/Database/Models/Activity.php +++ b/main/app/sprinkles/account/src/Database/Models/Activity.php @@ -1,83 +1,83 @@ -<?php -/** - * UserFrosting (http://www.userfrosting.com) - * - * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) - */ - -namespace UserFrosting\Sprinkle\Account\Database\Models; - -use Illuminate\Database\Capsule\Manager as Capsule; -use UserFrosting\Sprinkle\Core\Database\Models\Model; - -/** - * Activity Class - * - * Represents a single user activity at a specified point in time. - * @author Alex Weissman (https://alexanderweissman.com) - * @property string ip_address - * @property int user_id - * @property string type - * @property datetime occurred_at - * @property string description - */ -class Activity extends Model -{ - /** - * @var string The name of the table for the current model. - */ - protected $table = "activities"; - - protected $fillable = [ - "ip_address", - "user_id", - "type", - "occurred_at", - "description" - ]; - - /** - * Joins the activity's user, so we can do things like sort, search, paginate, etc. - */ - public function scopeJoinUser($query) { - $query = $query->select('activities.*'); - - $query = $query->leftJoin('users', 'activities.user_id', '=', 'users.id'); - - return $query; - } - - /** - * Add clauses to select the most recent event of each type for each user, to the query. - * - * @return \Illuminate\Database\Query\Builder - */ - public function scopeMostRecentEvents($query) { - return $query->select('user_id', 'event_type', Capsule::raw('MAX(occurred_at) as occurred_at')) - ->groupBy('user_id') - ->groupBy('type'); - } - - /** - * Add clauses to select the most recent event of a given type for each user, to the query. - * - * @param string $type The type of event, matching the `event_type` field in the user_event table. - * @return \Illuminate\Database\Query\Builder - */ - public function scopeMostRecentEventsByType($query, $type) { - return $query->select('user_id', Capsule::raw('MAX(occurred_at) as occurred_at')) - ->where('type', $type) - ->groupBy('user_id'); - } - - /** - * Get the user associated with this activity. - */ - public function user() { - /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ - $classMapper = static::$ci->classMapper; - - return $this->belongsTo($classMapper->getClassMapping('user'), 'user_id'); - } -} +<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Account\Database\Models;
+
+use Illuminate\Database\Capsule\Manager as Capsule;
+use UserFrosting\Sprinkle\Core\Database\Models\Model;
+
+/**
+ * Activity Class
+ *
+ * Represents a single user activity at a specified point in time.
+ * @author Alex Weissman (https://alexanderweissman.com)
+ * @property string ip_address
+ * @property int user_id
+ * @property string type
+ * @property datetime occurred_at
+ * @property string description
+ */
+class Activity extends Model
+{
+ /**
+ * @var string The name of the table for the current model.
+ */
+ protected $table = "activities";
+
+ protected $fillable = [
+ "ip_address",
+ "user_id",
+ "type",
+ "occurred_at",
+ "description"
+ ];
+
+ /**
+ * Joins the activity's user, so we can do things like sort, search, paginate, etc.
+ */
+ public function scopeJoinUser($query) {
+ $query = $query->select('activities.*');
+
+ $query = $query->leftJoin('users', 'activities.user_id', '=', 'users.id');
+
+ return $query;
+ }
+
+ /**
+ * Add clauses to select the most recent event of each type for each user, to the query.
+ *
+ * @return \Illuminate\Database\Query\Builder
+ */
+ public function scopeMostRecentEvents($query) {
+ return $query->select('user_id', 'event_type', Capsule::raw('MAX(occurred_at) as occurred_at'))
+ ->groupBy('user_id')
+ ->groupBy('type');
+ }
+
+ /**
+ * Add clauses to select the most recent event of a given type for each user, to the query.
+ *
+ * @param string $type The type of event, matching the `event_type` field in the user_event table.
+ * @return \Illuminate\Database\Query\Builder
+ */
+ public function scopeMostRecentEventsByType($query, $type) {
+ return $query->select('user_id', Capsule::raw('MAX(occurred_at) as occurred_at'))
+ ->where('type', $type)
+ ->groupBy('user_id');
+ }
+
+ /**
+ * Get the user associated with this activity.
+ */
+ public function user() {
+ /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
+ $classMapper = static::$ci->classMapper;
+
+ return $this->belongsTo($classMapper->getClassMapping('user'), 'user_id');
+ }
+}
diff --git a/main/app/sprinkles/account/src/Database/Models/Group.php b/main/app/sprinkles/account/src/Database/Models/Group.php index f0a1e1f..abb0e36 100644 --- a/main/app/sprinkles/account/src/Database/Models/Group.php +++ b/main/app/sprinkles/account/src/Database/Models/Group.php @@ -1,68 +1,68 @@ -<?php -/** - * UserFrosting (http://www.userfrosting.com) - * - * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) - */ - -namespace UserFrosting\Sprinkle\Account\Database\Models; - -use Illuminate\Database\Capsule\Manager as Capsule; -use UserFrosting\Sprinkle\Core\Database\Models\Model; - -/** - * Group Class - * - * Represents a group object as stored in the database. - * - * @package UserFrosting - * @author Alex Weissman - * @see http://www.userfrosting.com/tutorials/lesson-3-data-model/ - * - * @property string slug - * @property string name - * @property string description - * @property string icon - */ -class Group extends Model -{ - /** - * @var string The name of the table for the current model. - */ - protected $table = "groups"; - - protected $fillable = [ - "slug", - "name", - "description", - "icon" - ]; - - /** - * @var bool Enable timestamps for this class. - */ - public $timestamps = TRUE; - - /** - * Delete this group from the database, along with any user associations - * - * @odo What do we do with users when their group is deleted? Reassign them? Or, can a user be "groupless"? - */ - public function delete() { - // Delete the group - $result = parent::delete(); - - return $result; - } - - /** - * Lazily load a collection of Users which belong to this group. - */ - public function users() { - /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ - $classMapper = static::$ci->classMapper; - - return $this->hasMany($classMapper->getClassMapping('user'), 'group_id'); - } -} +<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Account\Database\Models;
+
+use Illuminate\Database\Capsule\Manager as Capsule;
+use UserFrosting\Sprinkle\Core\Database\Models\Model;
+
+/**
+ * Group Class
+ *
+ * Represents a group object as stored in the database.
+ *
+ * @package UserFrosting
+ * @author Alex Weissman
+ * @see http://www.userfrosting.com/tutorials/lesson-3-data-model/
+ *
+ * @property string slug
+ * @property string name
+ * @property string description
+ * @property string icon
+ */
+class Group extends Model
+{
+ /**
+ * @var string The name of the table for the current model.
+ */
+ protected $table = "groups";
+
+ protected $fillable = [
+ "slug",
+ "name",
+ "description",
+ "icon"
+ ];
+
+ /**
+ * @var bool Enable timestamps for this class.
+ */
+ public $timestamps = TRUE;
+
+ /**
+ * Delete this group from the database, along with any user associations
+ *
+ * @odo What do we do with users when their group is deleted? Reassign them? Or, can a user be "groupless"?
+ */
+ public function delete() {
+ // Delete the group
+ $result = parent::delete();
+
+ return $result;
+ }
+
+ /**
+ * Lazily load a collection of Users which belong to this group.
+ */
+ public function users() {
+ /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
+ $classMapper = static::$ci->classMapper;
+
+ return $this->hasMany($classMapper->getClassMapping('user'), 'group_id');
+ }
+}
diff --git a/main/app/sprinkles/account/src/Database/Models/PasswordReset.php b/main/app/sprinkles/account/src/Database/Models/PasswordReset.php index 3fc4e3c..99b1920 100644 --- a/main/app/sprinkles/account/src/Database/Models/PasswordReset.php +++ b/main/app/sprinkles/account/src/Database/Models/PasswordReset.php @@ -1,74 +1,74 @@ -<?php -/** - * UserFrosting (http://www.userfrosting.com) - * - * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) - */ - -namespace UserFrosting\Sprinkle\Account\Database\Models; - -use Illuminate\Database\Capsule\Manager as Capsule; -use UserFrosting\Sprinkle\Core\Database\Models\Model; - -/** - * Password Reset Class - * - * Represents a password reset request for a specific user. - * @author Alex Weissman (https://alexanderweissman.com) - * @property int user_id - * @property hash token - * @property bool completed - * @property datetime expires_at - * @property datetime completed_at - */ -class PasswordReset extends Model -{ - /** - * @var string The name of the table for the current model. - */ - protected $table = "password_resets"; - - protected $fillable = [ - "user_id", - "hash", - "completed", - "expires_at", - "completed_at" - ]; - - /** - * @var bool Enable timestamps for PasswordResets. - */ - public $timestamps = TRUE; - - /** - * Stores the raw (unhashed) token when created, so that it can be emailed out to the user. NOT persisted. - */ - protected $token; - - /** - * @return string - */ - public function getToken() { - return $this->token; - } - - /** - * @param string $value - */ - public function setToken($value) { - $this->token = $value; - return $this; - } - - /** - * Get the user associated with this reset request. - */ - public function user() { - /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ - $classMapper = static::$ci->classMapper; - - return $this->belongsTo($classMapper->getClassMapping('user'), 'user_id'); - } -} +<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Account\Database\Models;
+
+use Illuminate\Database\Capsule\Manager as Capsule;
+use UserFrosting\Sprinkle\Core\Database\Models\Model;
+
+/**
+ * Password Reset Class
+ *
+ * Represents a password reset request for a specific user.
+ * @author Alex Weissman (https://alexanderweissman.com)
+ * @property int user_id
+ * @property hash token
+ * @property bool completed
+ * @property datetime expires_at
+ * @property datetime completed_at
+ */
+class PasswordReset extends Model
+{
+ /**
+ * @var string The name of the table for the current model.
+ */
+ protected $table = "password_resets";
+
+ protected $fillable = [
+ "user_id",
+ "hash",
+ "completed",
+ "expires_at",
+ "completed_at"
+ ];
+
+ /**
+ * @var bool Enable timestamps for PasswordResets.
+ */
+ public $timestamps = TRUE;
+
+ /**
+ * Stores the raw (unhashed) token when created, so that it can be emailed out to the user. NOT persisted.
+ */
+ protected $token;
+
+ /**
+ * @return string
+ */
+ public function getToken() {
+ return $this->token;
+ }
+
+ /**
+ * @param string $value
+ */
+ public function setToken($value) {
+ $this->token = $value;
+ return $this;
+ }
+
+ /**
+ * Get the user associated with this reset request.
+ */
+ public function user() {
+ /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
+ $classMapper = static::$ci->classMapper;
+
+ return $this->belongsTo($classMapper->getClassMapping('user'), 'user_id');
+ }
+}
diff --git a/main/app/sprinkles/account/src/Database/Models/Permission.php b/main/app/sprinkles/account/src/Database/Models/Permission.php index 3035e56..da4391f 100644 --- a/main/app/sprinkles/account/src/Database/Models/Permission.php +++ b/main/app/sprinkles/account/src/Database/Models/Permission.php @@ -1,117 +1,117 @@ -<?php -/** - * UserFrosting (http://www.userfrosting.com) - * - * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) - */ - -namespace UserFrosting\Sprinkle\Account\Database\Models; - -use Illuminate\Database\Capsule\Manager as Capsule; -use UserFrosting\Sprinkle\Core\Database\Models\Model; - -/** - * Permission Class. - * - * Represents a permission for a role or user. - * @author Alex Weissman (https://alexanderweissman.com) - * @property string slug - * @property string name - * @property string conditions - * @property string description - */ -class Permission extends Model -{ - /** - * @var string The name of the table for the current model. - */ - protected $table = "permissions"; - - protected $fillable = [ - "slug", - "name", - "conditions", - "description" - ]; - - /** - * @var bool Enable timestamps for this class. - */ - public $timestamps = TRUE; - - /** - * Delete this permission from the database, removing associations with roles. - * - */ - public function delete() { - // Remove all role associations - $this->roles()->detach(); - - // Delete the permission - $result = parent::delete(); - - return $result; - } - - /** - * Get a list of roles to which this permission is assigned. - * - * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany - */ - public function roles() { - /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ - $classMapper = static::$ci->classMapper; - - return $this->belongsToMany($classMapper->getClassMapping('role'), 'permission_roles', 'permission_id', 'role_id')->withTimestamps(); - } - - /** - * Query scope to get all permissions assigned to a specific role. - * - * @param \Illuminate\Database\Eloquent\Builder $query - * @param int $roleId - * @return \Illuminate\Database\Eloquent\Builder - */ - public function scopeForRole($query, $roleId) { - return $query->join('permission_roles', function ($join) use ($roleId) { - $join->on('permission_roles.permission_id', 'permissions.id') - ->where('role_id', $roleId); - }); - } - - /** - * Query scope to get all permissions NOT associated with a specific role. - * - * @param \Illuminate\Database\Eloquent\Builder $query - * @param int $roleId - * @return \Illuminate\Database\Eloquent\Builder - */ - public function scopeNotForRole($query, $roleId) { - return $query->join('permission_roles', function ($join) use ($roleId) { - $join->on('permission_roles.permission_id', 'permissions.id') - ->where('role_id', '!=', $roleId); - }); - } - - /** - * Get a list of users who have this permission, along with a list of roles through which each user has the permission. - * - * @return \UserFrosting\Sprinkle\Core\Database\Relations\BelongsToManyThrough - */ - public function users() { - /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ - $classMapper = static::$ci->classMapper; - - return $this->belongsToManyThrough( - $classMapper->getClassMapping('user'), - $classMapper->getClassMapping('role'), - 'permission_roles', - 'permission_id', - 'role_id', - 'role_users', - 'role_id', - 'user_id' - ); - } -} +<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Account\Database\Models;
+
+use Illuminate\Database\Capsule\Manager as Capsule;
+use UserFrosting\Sprinkle\Core\Database\Models\Model;
+
+/**
+ * Permission Class.
+ *
+ * Represents a permission for a role or user.
+ * @author Alex Weissman (https://alexanderweissman.com)
+ * @property string slug
+ * @property string name
+ * @property string conditions
+ * @property string description
+ */
+class Permission extends Model
+{
+ /**
+ * @var string The name of the table for the current model.
+ */
+ protected $table = "permissions";
+
+ protected $fillable = [
+ "slug",
+ "name",
+ "conditions",
+ "description"
+ ];
+
+ /**
+ * @var bool Enable timestamps for this class.
+ */
+ public $timestamps = TRUE;
+
+ /**
+ * Delete this permission from the database, removing associations with roles.
+ *
+ */
+ public function delete() {
+ // Remove all role associations
+ $this->roles()->detach();
+
+ // Delete the permission
+ $result = parent::delete();
+
+ return $result;
+ }
+
+ /**
+ * Get a list of roles to which this permission is assigned.
+ *
+ * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
+ */
+ public function roles() {
+ /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
+ $classMapper = static::$ci->classMapper;
+
+ return $this->belongsToMany($classMapper->getClassMapping('role'), 'permission_roles', 'permission_id', 'role_id')->withTimestamps();
+ }
+
+ /**
+ * Query scope to get all permissions assigned to a specific role.
+ *
+ * @param \Illuminate\Database\Eloquent\Builder $query
+ * @param int $roleId
+ * @return \Illuminate\Database\Eloquent\Builder
+ */
+ public function scopeForRole($query, $roleId) {
+ return $query->join('permission_roles', function ($join) use ($roleId) {
+ $join->on('permission_roles.permission_id', 'permissions.id')
+ ->where('role_id', $roleId);
+ });
+ }
+
+ /**
+ * Query scope to get all permissions NOT associated with a specific role.
+ *
+ * @param \Illuminate\Database\Eloquent\Builder $query
+ * @param int $roleId
+ * @return \Illuminate\Database\Eloquent\Builder
+ */
+ public function scopeNotForRole($query, $roleId) {
+ return $query->join('permission_roles', function ($join) use ($roleId) {
+ $join->on('permission_roles.permission_id', 'permissions.id')
+ ->where('role_id', '!=', $roleId);
+ });
+ }
+
+ /**
+ * Get a list of users who have this permission, along with a list of roles through which each user has the permission.
+ *
+ * @return \UserFrosting\Sprinkle\Core\Database\Relations\BelongsToManyThrough
+ */
+ public function users() {
+ /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
+ $classMapper = static::$ci->classMapper;
+
+ return $this->belongsToManyThrough(
+ $classMapper->getClassMapping('user'),
+ $classMapper->getClassMapping('role'),
+ 'permission_roles',
+ 'permission_id',
+ 'role_id',
+ 'role_users',
+ 'role_id',
+ 'user_id'
+ );
+ }
+}
diff --git a/main/app/sprinkles/account/src/Database/Models/Role.php b/main/app/sprinkles/account/src/Database/Models/Role.php index 4a58df0..f8e40b3 100644 --- a/main/app/sprinkles/account/src/Database/Models/Role.php +++ b/main/app/sprinkles/account/src/Database/Models/Role.php @@ -1,101 +1,101 @@ -<?php -/** - * UserFrosting (http://www.userfrosting.com) - * - * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) - */ - -namespace UserFrosting\Sprinkle\Account\Database\Models; - -use Illuminate\Database\Capsule\Manager as Capsule; -use UserFrosting\Sprinkle\Core\Database\Models\Model; - -/** - * Role Class - * - * Represents a role, which aggregates permissions and to which a user can be assigned. - * @author Alex Weissman (https://alexanderweissman.com) - * @property string slug - * @property string name - * @property string description - */ -class Role extends Model -{ - /** - * @var string The name of the table for the current model. - */ - protected $table = "roles"; - - protected $fillable = [ - "slug", - "name", - "description" - ]; - - /** - * @var bool Enable timestamps for this class. - */ - public $timestamps = TRUE; - - /** - * Delete this role from the database, removing associations with permissions and users. - * - */ - public function delete() { - // Remove all permission associations - $this->permissions()->detach(); - - // Remove all user associations - $this->users()->detach(); - - // Delete the role - $result = parent::delete(); - - return $result; - } - - /** - * Get a list of default roles. - */ - public static function getDefaultSlugs() { - /** @var UserFrosting\Config $config */ - $config = static::$ci->config; - - return array_map('trim', array_keys($config['site.registration.user_defaults.roles'], TRUE)); - } - - /** - * Get a list of permissions assigned to this role. - */ - public function permissions() { - /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ - $classMapper = static::$ci->classMapper; - - return $this->belongsToMany($classMapper->getClassMapping('permission'), 'permission_roles', 'role_id', 'permission_id')->withTimestamps(); - } - - /** - * Query scope to get all roles assigned to a specific user. - * - * @param \Illuminate\Database\Eloquent\Builder $query - * @param int $userId - * @return \Illuminate\Database\Eloquent\Builder - */ - public function scopeForUser($query, $userId) { - return $query->join('role_users', function ($join) use ($userId) { - $join->on('role_users.role_id', 'roles.id') - ->where('user_id', $userId); - }); - } - - /** - * Get a list of users who have this role. - */ - public function users() { - /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ - $classMapper = static::$ci->classMapper; - - return $this->belongsToMany($classMapper->getClassMapping('user'), 'role_users', 'role_id', 'user_id'); - } -} +<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Account\Database\Models;
+
+use Illuminate\Database\Capsule\Manager as Capsule;
+use UserFrosting\Sprinkle\Core\Database\Models\Model;
+
+/**
+ * Role Class
+ *
+ * Represents a role, which aggregates permissions and to which a user can be assigned.
+ * @author Alex Weissman (https://alexanderweissman.com)
+ * @property string slug
+ * @property string name
+ * @property string description
+ */
+class Role extends Model
+{
+ /**
+ * @var string The name of the table for the current model.
+ */
+ protected $table = "roles";
+
+ protected $fillable = [
+ "slug",
+ "name",
+ "description"
+ ];
+
+ /**
+ * @var bool Enable timestamps for this class.
+ */
+ public $timestamps = TRUE;
+
+ /**
+ * Delete this role from the database, removing associations with permissions and users.
+ *
+ */
+ public function delete() {
+ // Remove all permission associations
+ $this->permissions()->detach();
+
+ // Remove all user associations
+ $this->users()->detach();
+
+ // Delete the role
+ $result = parent::delete();
+
+ return $result;
+ }
+
+ /**
+ * Get a list of default roles.
+ */
+ public static function getDefaultSlugs() {
+ /** @var UserFrosting\Config $config */
+ $config = static::$ci->config;
+
+ return array_map('trim', array_keys($config['site.registration.user_defaults.roles'], TRUE));
+ }
+
+ /**
+ * Get a list of permissions assigned to this role.
+ */
+ public function permissions() {
+ /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
+ $classMapper = static::$ci->classMapper;
+
+ return $this->belongsToMany($classMapper->getClassMapping('permission'), 'permission_roles', 'role_id', 'permission_id')->withTimestamps();
+ }
+
+ /**
+ * Query scope to get all roles assigned to a specific user.
+ *
+ * @param \Illuminate\Database\Eloquent\Builder $query
+ * @param int $userId
+ * @return \Illuminate\Database\Eloquent\Builder
+ */
+ public function scopeForUser($query, $userId) {
+ return $query->join('role_users', function ($join) use ($userId) {
+ $join->on('role_users.role_id', 'roles.id')
+ ->where('user_id', $userId);
+ });
+ }
+
+ /**
+ * Get a list of users who have this role.
+ */
+ public function users() {
+ /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
+ $classMapper = static::$ci->classMapper;
+
+ return $this->belongsToMany($classMapper->getClassMapping('user'), 'role_users', 'role_id', 'user_id');
+ }
+}
diff --git a/main/app/sprinkles/account/src/Database/Models/User.php b/main/app/sprinkles/account/src/Database/Models/User.php index b401db2..cccd307 100644 --- a/main/app/sprinkles/account/src/Database/Models/User.php +++ b/main/app/sprinkles/account/src/Database/Models/User.php @@ -1,469 +1,469 @@ -<?php -/** - * UserFrosting (http://www.userfrosting.com) - * - * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) - */ - -namespace UserFrosting\Sprinkle\Account\Database\Models; - -use Carbon\Carbon; -use Illuminate\Database\Capsule\Manager as Capsule; -use Illuminate\Database\Eloquent\SoftDeletes; -use UserFrosting\Sprinkle\Account\Facades\Password; -use UserFrosting\Sprinkle\Core\Database\Models\Model; -use UserFrosting\Sprinkle\Core\Facades\Debug; - -/** - * User Class - * - * Represents a User object as stored in the database. - * - * @author Alex Weissman (https://alexanderweissman.com) - * @property int id - * @property string user_name - * @property string first_name - * @property string last_name - * @property string email - * @property string locale - * @property string theme - * @property int group_id - * @property bool flag_verified - * @property bool flag_enabled - * @property int last_activity_id - * @property timestamp created_at - * @property timestamp updated_at - * @property string password - * @property timestamp deleted_at - */ -class User extends Model -{ - use SoftDeletes; - - /** - * The name of the table for the current model. - * - * @var string - */ - protected $table = 'users'; - - /** - * Fields that should be mass-assignable when creating a new User. - * - * @var string[] - */ - protected $fillable = [ - 'user_name', - 'first_name', - 'last_name', - 'email', - 'locale', - 'theme', - 'group_id', - 'flag_verified', - 'flag_enabled', - 'last_activity_id', - 'password', - 'deleted_at' - ]; - - /** - * A list of attributes to hide by default when using toArray() and toJson(). - * - * @link https://laravel.com/docs/5.4/eloquent-serialization#hiding-attributes-from-json - * @var string[] - */ - protected $hidden = [ - 'password' - ]; - - /** - * The attributes that should be mutated to dates. - * - * @var string[] - */ - protected $dates = [ - 'deleted_at' - ]; - - protected $appends = [ - 'full_name' - ]; - - /** - * Cached dictionary of permissions for the user. - * - * @var array - */ - protected $cachedPermissions; - - /** - * Enable timestamps for Users. - * - * @var bool - */ - public $timestamps = TRUE; - - /** - * Determine if the property for this object exists. - * - * We add relations here so that Twig will be able to find them. - * See http://stackoverflow.com/questions/29514081/cannot-access-eloquent-attributes-on-twig/35908957#35908957 - * Every property in __get must also be implemented here for Twig to recognize it. - * @param string $name the name of the property to check. - * @return bool true if the property is defined, false otherwise. - */ - public function __isset($name) { - if (in_array($name, [ - 'group', - 'last_sign_in_time', - 'avatar' - ])) { - return TRUE; - } else { - return parent::__isset($name); - } - } - - /** - * Get a property for this object. - * - * @param string $name the name of the property to retrieve. - * @throws Exception the property does not exist for this object. - * @return string the associated property. - */ - public function __get($name) { - if ($name == 'last_sign_in_time') { - return $this->lastActivityTime('sign_in'); - } else if ($name == 'avatar') { - // Use Gravatar as the user avatar - $hash = md5(strtolower(trim($this->email))); - return 'https://www.gravatar.com/avatar/' . $hash . '?d=mm'; - } else { - return parent::__get($name); - } - } - - /** - * Get all activities for this user. - * - * @return \Illuminate\Database\Eloquent\Relations\HasMany - */ - public function activities() { - /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ - $classMapper = static::$ci->classMapper; - - return $this->hasMany($classMapper->getClassMapping('activity'), 'user_id'); - } - - /** - * Delete this user from the database, along with any linked roles and activities. - * - * @param bool $hardDelete Set to true to completely remove the user and all associated objects. - * @return bool true if the deletion was successful, false otherwise. - */ - public function delete($hardDelete = FALSE) { - /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ - $classMapper = static::$ci->classMapper; - - if ($hardDelete) { - // Remove all role associations - $this->roles()->detach(); - - // Remove all user activities - $classMapper->staticMethod('activity', 'where', 'user_id', $this->id)->delete(); - - // Remove all user tokens - $classMapper->staticMethod('password_reset', 'where', 'user_id', $this->id)->delete(); - $classMapper->staticMethod('verification', 'where', 'user_id', $this->id)->delete(); - - // Delete the user - $result = parent::forceDelete(); - } else { - // Soft delete the user, leaving all associated records alone - $result = parent::delete(); - } - - return $result; - } - - /** - * Determines whether a user exists, including checking soft-deleted records - * - * @deprecated since 4.1.7 This method conflicts with and overrides the Builder::exists() method. Use Model::findUnique instead. - * @param mixed $value - * @param string $identifier - * @param bool $checkDeleted set to true to include soft-deleted records - * @return User|null - */ - public static function exists($value, $identifier = 'user_name', $checkDeleted = TRUE) { - return static::findUnique($value, $identifier, $checkDeleted); - } - - /** - * Return a cache instance specific to that user - * - * @return \Illuminate\Contracts\Cache\Store - */ - public function getCache() { - return static::$ci->cache->tags('_u' . $this->id); - } - - /** - * Allows you to get the full name of the user using `$user->full_name` - * - * @return string - */ - public function getFullNameAttribute() { - return $this->first_name . ' ' . $this->last_name; - } - - /** - * Retrieve the cached permissions dictionary for this user. - * - * @return array - */ - public function getCachedPermissions() { - if (!isset($this->cachedPermissions)) { - $this->reloadCachedPermissions(); - } - - return $this->cachedPermissions; - } - - /** - * Retrieve the cached permissions dictionary for this user. - * - * @return User - */ - public function reloadCachedPermissions() { - $this->cachedPermissions = $this->buildPermissionsDictionary(); - - return $this; - } - - /** - * Get the amount of time, in seconds, that has elapsed since the last activity of a certain time for this user. - * - * @param string $type The type of activity to search for. - * @return int - */ - public function getSecondsSinceLastActivity($type) { - $time = $this->lastActivityTime($type); - $time = $time ? $time : '0000-00-00 00:00:00'; - $time = new Carbon($time); - - return $time->diffInSeconds(); - } - - /** - * Return this user's group. - * - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo - */ - public function group() { - /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ - $classMapper = static::$ci->classMapper; - - return $this->belongsTo($classMapper->getClassMapping('group'), 'group_id'); - } - - /** - * Returns whether or not this user is the master user. - * - * @return bool - */ - public function isMaster() { - $masterId = static::$ci->config['reserved_user_ids.master']; - - // Need to use loose comparison for now, because some DBs return `id` as a string - return ($this->id == $masterId); - } - - /** - * Get the most recent activity for this user, based on the user's last_activity_id. - * - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo - */ - public function lastActivity() { - /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ - $classMapper = static::$ci->classMapper; - - return $this->belongsTo($classMapper->getClassMapping('activity'), 'last_activity_id'); - } - - /** - * Find the most recent activity for this user of a particular type. - * - * @param string $type - * @return \Illuminate\Database\Eloquent\Builder - */ - public function lastActivityOfType($type = NULL) { - /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ - $classMapper = static::$ci->classMapper; - - $query = $this->hasOne($classMapper->getClassMapping('activity'), 'user_id'); - - if ($type) { - $query = $query->where('type', $type); - } - - return $query->latest('occurred_at'); - } - - /** - * Get the most recent time for a specified activity type for this user. - * - * @param string $type - * @return string|null The last activity time, as a SQL formatted time (YYYY-MM-DD HH:MM:SS), or null if an activity of this type doesn't exist. - */ - public function lastActivityTime($type) { - $result = $this->activities() - ->where('type', $type) - ->max('occurred_at'); - return $result ? $result : NULL; - } - - /** - * Performs tasks to be done after this user has been successfully authenticated. - * - * By default, adds a new sign-in activity and updates any legacy hash. - * @param mixed[] $params Optional array of parameters used for this event handler. - * @odo Transition to Laravel Event dispatcher to handle this - */ - public function onLogin($params = []) { - // Add a sign in activity (time is automatically set by database) - static::$ci->userActivityLogger->info("User {$this->user_name} signed in.", [ - 'type' => 'sign_in' - ]); - - // Update password if we had encountered an outdated hash - $passwordType = Password::getHashType($this->password); - - if ($passwordType != 'modern') { - if (!isset($params['password'])) { - Debug::debug('Notice: Unhashed password must be supplied to update to modern password hashing.'); - } else { - // Hash the user's password and update - $passwordHash = Password::hash($params['password']); - if ($passwordHash === NULL) { - Debug::debug('Notice: outdated password hash could not be updated because the new hashing algorithm is not supported. Are you running PHP >= 5.3.7?'); - } else { - $this->password = $passwordHash; - Debug::debug('Notice: outdated password hash has been automatically updated to modern hashing.'); - } - } - } - - // Save changes - $this->save(); - - return $this; - } - - /** - * Performs tasks to be done after this user has been logged out. - * - * By default, adds a new sign-out activity. - * @param mixed[] $params Optional array of parameters used for this event handler. - * @do Transition to Laravel Event dispatcher to handle this - */ - public function onLogout($params = []) { - static::$ci->userActivityLogger->info("User {$this->user_name} signed out.", [ - 'type' => 'sign_out' - ]); - - return $this; - } - - /** - * Get all password reset requests for this user. - * - * @return \Illuminate\Database\Eloquent\Relations\HasMany - */ - public function passwordResets() { - /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ - $classMapper = static::$ci->classMapper; - - return $this->hasMany($classMapper->getClassMapping('password_reset'), 'user_id'); - } - - /** - * Get all of the permissions this user has, via its roles. - * - * @return \UserFrosting\Sprinkle\Core\Database\Relations\BelongsToManyThrough - */ - public function permissions() { - /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ - $classMapper = static::$ci->classMapper; - - return $this->belongsToManyThrough( - $classMapper->getClassMapping('permission'), - $classMapper->getClassMapping('role'), - 'role_users', - 'user_id', - 'role_id', - 'permission_roles', - 'role_id', - 'permission_id' - ); - } - - /** - * Get all roles to which this user belongs. - * - * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany - */ - public function roles() { - /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ - $classMapper = static::$ci->classMapper; - - return $this->belongsToMany($classMapper->getClassMapping('role'), 'role_users', 'user_id', 'role_id')->withTimestamps(); - } - - /** - * Query scope to get all users who have a specific role. - * - * @param \Illuminate\Database\Eloquent\Builder $query - * @param int $roleId - * @return \Illuminate\Database\Eloquent\Builder - */ - public function scopeForRole($query, $roleId) { - return $query->join('role_users', function ($join) use ($roleId) { - $join->on('role_users.user_id', 'users.id') - ->where('role_id', $roleId); - }); - } - - /** - * Joins the user's most recent activity directly, so we can do things like sort, search, paginate, etc. - * - * @param \Illuminate\Database\Eloquent\Builder $query - * @return \Illuminate\Database\Eloquent\Builder - */ - public function scopeJoinLastActivity($query) { - $query = $query->select('users.*'); - - $query = $query->leftJoin('activities', 'activities.id', '=', 'users.last_activity_id'); - - return $query; - } - - /** - * Loads permissions for this user into a cached dictionary of slugs -> arrays of permissions, - * so we don't need to keep requerying the DB for every call of checkAccess. - * - * @return array - */ - protected function buildPermissionsDictionary() { - $permissions = $this->permissions()->get(); - $cachedPermissions = []; - - foreach ($permissions as $permission) { - $cachedPermissions[$permission->slug][] = $permission; - } - - return $cachedPermissions; - } -} +<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Account\Database\Models;
+
+use Carbon\Carbon;
+use Illuminate\Database\Capsule\Manager as Capsule;
+use Illuminate\Database\Eloquent\SoftDeletes;
+use UserFrosting\Sprinkle\Account\Facades\Password;
+use UserFrosting\Sprinkle\Core\Database\Models\Model;
+use UserFrosting\Sprinkle\Core\Facades\Debug;
+
+/**
+ * User Class
+ *
+ * Represents a User object as stored in the database.
+ *
+ * @author Alex Weissman (https://alexanderweissman.com)
+ * @property int id
+ * @property string user_name
+ * @property string first_name
+ * @property string last_name
+ * @property string email
+ * @property string locale
+ * @property string theme
+ * @property int group_id
+ * @property bool flag_verified
+ * @property bool flag_enabled
+ * @property int last_activity_id
+ * @property timestamp created_at
+ * @property timestamp updated_at
+ * @property string password
+ * @property timestamp deleted_at
+ */
+class User extends Model
+{
+ use SoftDeletes;
+
+ /**
+ * The name of the table for the current model.
+ *
+ * @var string
+ */
+ protected $table = 'users';
+
+ /**
+ * Fields that should be mass-assignable when creating a new User.
+ *
+ * @var string[]
+ */
+ protected $fillable = [
+ 'user_name',
+ 'first_name',
+ 'last_name',
+ 'email',
+ 'locale',
+ 'theme',
+ 'group_id',
+ 'flag_verified',
+ 'flag_enabled',
+ 'last_activity_id',
+ 'password',
+ 'deleted_at'
+ ];
+
+ /**
+ * A list of attributes to hide by default when using toArray() and toJson().
+ *
+ * @link https://laravel.com/docs/5.4/eloquent-serialization#hiding-attributes-from-json
+ * @var string[]
+ */
+ protected $hidden = [
+ 'password'
+ ];
+
+ /**
+ * The attributes that should be mutated to dates.
+ *
+ * @var string[]
+ */
+ protected $dates = [
+ 'deleted_at'
+ ];
+
+ protected $appends = [
+ 'full_name'
+ ];
+
+ /**
+ * Cached dictionary of permissions for the user.
+ *
+ * @var array
+ */
+ protected $cachedPermissions;
+
+ /**
+ * Enable timestamps for Users.
+ *
+ * @var bool
+ */
+ public $timestamps = TRUE;
+
+ /**
+ * Determine if the property for this object exists.
+ *
+ * We add relations here so that Twig will be able to find them.
+ * See http://stackoverflow.com/questions/29514081/cannot-access-eloquent-attributes-on-twig/35908957#35908957
+ * Every property in __get must also be implemented here for Twig to recognize it.
+ * @param string $name the name of the property to check.
+ * @return bool true if the property is defined, false otherwise.
+ */
+ public function __isset($name) {
+ if (in_array($name, [
+ 'group',
+ 'last_sign_in_time',
+ 'avatar'
+ ])) {
+ return TRUE;
+ } else {
+ return parent::__isset($name);
+ }
+ }
+
+ /**
+ * Get a property for this object.
+ *
+ * @param string $name the name of the property to retrieve.
+ * @throws Exception the property does not exist for this object.
+ * @return string the associated property.
+ */
+ public function __get($name) {
+ if ($name == 'last_sign_in_time') {
+ return $this->lastActivityTime('sign_in');
+ } else if ($name == 'avatar') {
+ // Use Gravatar as the user avatar
+ $hash = md5(strtolower(trim($this->email)));
+ return 'https://www.gravatar.com/avatar/' . $hash . '?d=mm';
+ } else {
+ return parent::__get($name);
+ }
+ }
+
+ /**
+ * Get all activities for this user.
+ *
+ * @return \Illuminate\Database\Eloquent\Relations\HasMany
+ */
+ public function activities() {
+ /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
+ $classMapper = static::$ci->classMapper;
+
+ return $this->hasMany($classMapper->getClassMapping('activity'), 'user_id');
+ }
+
+ /**
+ * Delete this user from the database, along with any linked roles and activities.
+ *
+ * @param bool $hardDelete Set to true to completely remove the user and all associated objects.
+ * @return bool true if the deletion was successful, false otherwise.
+ */
+ public function delete($hardDelete = FALSE) {
+ /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
+ $classMapper = static::$ci->classMapper;
+
+ if ($hardDelete) {
+ // Remove all role associations
+ $this->roles()->detach();
+
+ // Remove all user activities
+ $classMapper->staticMethod('activity', 'where', 'user_id', $this->id)->delete();
+
+ // Remove all user tokens
+ $classMapper->staticMethod('password_reset', 'where', 'user_id', $this->id)->delete();
+ $classMapper->staticMethod('verification', 'where', 'user_id', $this->id)->delete();
+
+ // Delete the user
+ $result = parent::forceDelete();
+ } else {
+ // Soft delete the user, leaving all associated records alone
+ $result = parent::delete();
+ }
+
+ return $result;
+ }
+
+ /**
+ * Determines whether a user exists, including checking soft-deleted records
+ *
+ * @deprecated since 4.1.7 This method conflicts with and overrides the Builder::exists() method. Use Model::findUnique instead.
+ * @param mixed $value
+ * @param string $identifier
+ * @param bool $checkDeleted set to true to include soft-deleted records
+ * @return User|null
+ */
+ public static function exists($value, $identifier = 'user_name', $checkDeleted = TRUE) {
+ return static::findUnique($value, $identifier, $checkDeleted);
+ }
+
+ /**
+ * Return a cache instance specific to that user
+ *
+ * @return \Illuminate\Contracts\Cache\Store
+ */
+ public function getCache() {
+ return static::$ci->cache->tags('_u' . $this->id);
+ }
+
+ /**
+ * Allows you to get the full name of the user using `$user->full_name`
+ *
+ * @return string
+ */
+ public function getFullNameAttribute() {
+ return $this->first_name . ' ' . $this->last_name;
+ }
+
+ /**
+ * Retrieve the cached permissions dictionary for this user.
+ *
+ * @return array
+ */
+ public function getCachedPermissions() {
+ if (!isset($this->cachedPermissions)) {
+ $this->reloadCachedPermissions();
+ }
+
+ return $this->cachedPermissions;
+ }
+
+ /**
+ * Retrieve the cached permissions dictionary for this user.
+ *
+ * @return User
+ */
+ public function reloadCachedPermissions() {
+ $this->cachedPermissions = $this->buildPermissionsDictionary();
+
+ return $this;
+ }
+
+ /**
+ * Get the amount of time, in seconds, that has elapsed since the last activity of a certain time for this user.
+ *
+ * @param string $type The type of activity to search for.
+ * @return int
+ */
+ public function getSecondsSinceLastActivity($type) {
+ $time = $this->lastActivityTime($type);
+ $time = $time ? $time : '0000-00-00 00:00:00';
+ $time = new Carbon($time);
+
+ return $time->diffInSeconds();
+ }
+
+ /**
+ * Return this user's group.
+ *
+ * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
+ */
+ public function group() {
+ /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
+ $classMapper = static::$ci->classMapper;
+
+ return $this->belongsTo($classMapper->getClassMapping('group'), 'group_id');
+ }
+
+ /**
+ * Returns whether or not this user is the master user.
+ *
+ * @return bool
+ */
+ public function isMaster() {
+ $masterId = static::$ci->config['reserved_user_ids.master'];
+
+ // Need to use loose comparison for now, because some DBs return `id` as a string
+ return ($this->id == $masterId);
+ }
+
+ /**
+ * Get the most recent activity for this user, based on the user's last_activity_id.
+ *
+ * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
+ */
+ public function lastActivity() {
+ /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
+ $classMapper = static::$ci->classMapper;
+
+ return $this->belongsTo($classMapper->getClassMapping('activity'), 'last_activity_id');
+ }
+
+ /**
+ * Find the most recent activity for this user of a particular type.
+ *
+ * @param string $type
+ * @return \Illuminate\Database\Eloquent\Builder
+ */
+ public function lastActivityOfType($type = NULL) {
+ /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
+ $classMapper = static::$ci->classMapper;
+
+ $query = $this->hasOne($classMapper->getClassMapping('activity'), 'user_id');
+
+ if ($type) {
+ $query = $query->where('type', $type);
+ }
+
+ return $query->latest('occurred_at');
+ }
+
+ /**
+ * Get the most recent time for a specified activity type for this user.
+ *
+ * @param string $type
+ * @return string|null The last activity time, as a SQL formatted time (YYYY-MM-DD HH:MM:SS), or null if an activity of this type doesn't exist.
+ */
+ public function lastActivityTime($type) {
+ $result = $this->activities()
+ ->where('type', $type)
+ ->max('occurred_at');
+ return $result ? $result : NULL;
+ }
+
+ /**
+ * Performs tasks to be done after this user has been successfully authenticated.
+ *
+ * By default, adds a new sign-in activity and updates any legacy hash.
+ * @param mixed[] $params Optional array of parameters used for this event handler.
+ * @odo Transition to Laravel Event dispatcher to handle this
+ */
+ public function onLogin($params = []) {
+ // Add a sign in activity (time is automatically set by database)
+ static::$ci->userActivityLogger->info("User {$this->user_name} signed in.", [
+ 'type' => 'sign_in'
+ ]);
+
+ // Update password if we had encountered an outdated hash
+ $passwordType = Password::getHashType($this->password);
+
+ if ($passwordType != 'modern') {
+ if (!isset($params['password'])) {
+ Debug::debug('Notice: Unhashed password must be supplied to update to modern password hashing.');
+ } else {
+ // Hash the user's password and update
+ $passwordHash = Password::hash($params['password']);
+ if ($passwordHash === NULL) {
+ Debug::debug('Notice: outdated password hash could not be updated because the new hashing algorithm is not supported. Are you running PHP >= 5.3.7?');
+ } else {
+ $this->password = $passwordHash;
+ Debug::debug('Notice: outdated password hash has been automatically updated to modern hashing.');
+ }
+ }
+ }
+
+ // Save changes
+ $this->save();
+
+ return $this;
+ }
+
+ /**
+ * Performs tasks to be done after this user has been logged out.
+ *
+ * By default, adds a new sign-out activity.
+ * @param mixed[] $params Optional array of parameters used for this event handler.
+ * @do Transition to Laravel Event dispatcher to handle this
+ */
+ public function onLogout($params = []) {
+ static::$ci->userActivityLogger->info("User {$this->user_name} signed out.", [
+ 'type' => 'sign_out'
+ ]);
+
+ return $this;
+ }
+
+ /**
+ * Get all password reset requests for this user.
+ *
+ * @return \Illuminate\Database\Eloquent\Relations\HasMany
+ */
+ public function passwordResets() {
+ /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
+ $classMapper = static::$ci->classMapper;
+
+ return $this->hasMany($classMapper->getClassMapping('password_reset'), 'user_id');
+ }
+
+ /**
+ * Get all of the permissions this user has, via its roles.
+ *
+ * @return \UserFrosting\Sprinkle\Core\Database\Relations\BelongsToManyThrough
+ */
+ public function permissions() {
+ /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
+ $classMapper = static::$ci->classMapper;
+
+ return $this->belongsToManyThrough(
+ $classMapper->getClassMapping('permission'),
+ $classMapper->getClassMapping('role'),
+ 'role_users',
+ 'user_id',
+ 'role_id',
+ 'permission_roles',
+ 'role_id',
+ 'permission_id'
+ );
+ }
+
+ /**
+ * Get all roles to which this user belongs.
+ *
+ * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
+ */
+ public function roles() {
+ /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
+ $classMapper = static::$ci->classMapper;
+
+ return $this->belongsToMany($classMapper->getClassMapping('role'), 'role_users', 'user_id', 'role_id')->withTimestamps();
+ }
+
+ /**
+ * Query scope to get all users who have a specific role.
+ *
+ * @param \Illuminate\Database\Eloquent\Builder $query
+ * @param int $roleId
+ * @return \Illuminate\Database\Eloquent\Builder
+ */
+ public function scopeForRole($query, $roleId) {
+ return $query->join('role_users', function ($join) use ($roleId) {
+ $join->on('role_users.user_id', 'users.id')
+ ->where('role_id', $roleId);
+ });
+ }
+
+ /**
+ * Joins the user's most recent activity directly, so we can do things like sort, search, paginate, etc.
+ *
+ * @param \Illuminate\Database\Eloquent\Builder $query
+ * @return \Illuminate\Database\Eloquent\Builder
+ */
+ public function scopeJoinLastActivity($query) {
+ $query = $query->select('users.*');
+
+ $query = $query->leftJoin('activities', 'activities.id', '=', 'users.last_activity_id');
+
+ return $query;
+ }
+
+ /**
+ * Loads permissions for this user into a cached dictionary of slugs -> arrays of permissions,
+ * so we don't need to keep requerying the DB for every call of checkAccess.
+ *
+ * @return array
+ */
+ protected function buildPermissionsDictionary() {
+ $permissions = $this->permissions()->get();
+ $cachedPermissions = [];
+
+ foreach ($permissions as $permission) {
+ $cachedPermissions[$permission->slug][] = $permission;
+ }
+
+ return $cachedPermissions;
+ }
+}
diff --git a/main/app/sprinkles/account/src/Database/Models/Verification.php b/main/app/sprinkles/account/src/Database/Models/Verification.php index f6697b6..f642d77 100644 --- a/main/app/sprinkles/account/src/Database/Models/Verification.php +++ b/main/app/sprinkles/account/src/Database/Models/Verification.php @@ -1,68 +1,68 @@ -<?php -/** - * UserFrosting (http://www.userfrosting.com) - * - * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) - */ - -namespace UserFrosting\Sprinkle\Account\Database\Models; - -use Illuminate\Database\Capsule\Manager as Capsule; -use UserFrosting\Sprinkle\Core\Database\Models\Model; - -/** - * Verification Class - * - * Represents a pending email verification for a new user account. - * @author Alex Weissman (https://alexanderweissman.com) - * @property int user_id - * @property hash token - * @property bool completed - * @property datetime expires_at - * @property datetime completed_at - */ -class Verification extends Model -{ - /** - * @var string The name of the table for the current model. - */ - protected $table = "verifications"; - - protected $fillable = [ - "user_id", - "hash", - "completed", - "expires_at", - "completed_at" - ]; - - /** - * @var bool Enable timestamps for Verifications. - */ - public $timestamps = TRUE; - - /** - * Stores the raw (unhashed) token when created, so that it can be emailed out to the user. NOT persisted. - */ - protected $token; - - public function getToken() { - return $this->token; - } - - public function setToken($value) { - $this->token = $value; - return $this; - } - - /** - * Get the user associated with this verification request. - */ - public function user() { - /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */ - $classMapper = static::$ci->classMapper; - - return $this->belongsTo($classMapper->getClassMapping('user'), 'user_id'); - } -} +<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Account\Database\Models;
+
+use Illuminate\Database\Capsule\Manager as Capsule;
+use UserFrosting\Sprinkle\Core\Database\Models\Model;
+
+/**
+ * Verification Class
+ *
+ * Represents a pending email verification for a new user account.
+ * @author Alex Weissman (https://alexanderweissman.com)
+ * @property int user_id
+ * @property hash token
+ * @property bool completed
+ * @property datetime expires_at
+ * @property datetime completed_at
+ */
+class Verification extends Model
+{
+ /**
+ * @var string The name of the table for the current model.
+ */
+ protected $table = "verifications";
+
+ protected $fillable = [
+ "user_id",
+ "hash",
+ "completed",
+ "expires_at",
+ "completed_at"
+ ];
+
+ /**
+ * @var bool Enable timestamps for Verifications.
+ */
+ public $timestamps = TRUE;
+
+ /**
+ * Stores the raw (unhashed) token when created, so that it can be emailed out to the user. NOT persisted.
+ */
+ protected $token;
+
+ public function getToken() {
+ return $this->token;
+ }
+
+ public function setToken($value) {
+ $this->token = $value;
+ return $this;
+ }
+
+ /**
+ * Get the user associated with this verification request.
+ */
+ public function user() {
+ /** @var UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
+ $classMapper = static::$ci->classMapper;
+
+ return $this->belongsTo($classMapper->getClassMapping('user'), 'user_id');
+ }
+}
diff --git a/main/app/sprinkles/account/src/Error/Handler/AuthCompromisedExceptionHandler.php b/main/app/sprinkles/account/src/Error/Handler/AuthCompromisedExceptionHandler.php index 4c3b100..ccefe72 100644 --- a/main/app/sprinkles/account/src/Error/Handler/AuthCompromisedExceptionHandler.php +++ b/main/app/sprinkles/account/src/Error/Handler/AuthCompromisedExceptionHandler.php @@ -1,34 +1,34 @@ -<?php -/** - * UserFrosting (http://www.userfrosting.com) - * - * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) - */ - -namespace UserFrosting\Sprinkle\Account\Error\Handler; - -use UserFrosting\Sprinkle\Core\Error\Handler\HttpExceptionHandler; - -/** - * Handler for AuthCompromisedExceptions. - * - * Warns the user that their account may have been compromised due to a stolen "remember me" cookie. - * @author Alex Weissman (https://alexanderweissman.com) - */ -class AuthCompromisedExceptionHandler extends HttpExceptionHandler -{ - /** - * Render a generic, user-friendly response without sensitive debugging information. - * - * @return ResponseInterface - */ - public function renderGenericResponse() { - $template = $this->ci->view->getEnvironment()->loadTemplate('pages/error/compromised.html.twig'); - - return $this->response - ->withStatus($this->statusCode) - ->withHeader('Content-type', $this->contentType) - ->write($template->render()); - } -} +<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Account\Error\Handler;
+
+use UserFrosting\Sprinkle\Core\Error\Handler\HttpExceptionHandler;
+
+/**
+ * Handler for AuthCompromisedExceptions.
+ *
+ * Warns the user that their account may have been compromised due to a stolen "remember me" cookie.
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+class AuthCompromisedExceptionHandler extends HttpExceptionHandler
+{
+ /**
+ * Render a generic, user-friendly response without sensitive debugging information.
+ *
+ * @return ResponseInterface
+ */
+ public function renderGenericResponse() {
+ $template = $this->ci->view->getEnvironment()->loadTemplate('pages/error/compromised.html.twig');
+
+ return $this->response
+ ->withStatus($this->statusCode)
+ ->withHeader('Content-type', $this->contentType)
+ ->write($template->render());
+ }
+}
diff --git a/main/app/sprinkles/account/src/Error/Handler/AuthExpiredExceptionHandler.php b/main/app/sprinkles/account/src/Error/Handler/AuthExpiredExceptionHandler.php index fd3ca1f..fb04bd1 100644 --- a/main/app/sprinkles/account/src/Error/Handler/AuthExpiredExceptionHandler.php +++ b/main/app/sprinkles/account/src/Error/Handler/AuthExpiredExceptionHandler.php @@ -1,50 +1,50 @@ -<?php -/** - * UserFrosting (http://www.userfrosting.com) - * - * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) - */ - -namespace UserFrosting\Sprinkle\Account\Error\Handler; - -use UserFrosting\Sprinkle\Core\Error\Handler\HttpExceptionHandler; - -/** - * Handler for AuthExpiredExceptions. - * - * Forwards the user to the login page when their session has expired. - * @author Alex Weissman (https://alexanderweissman.com) - */ -class AuthExpiredExceptionHandler extends HttpExceptionHandler -{ - /** - * Custom handling for requests that did not pass authentication. - */ - public function handle() { - // For auth expired exceptions, we always add messages to the alert stream. - $this->writeAlerts(); - - $response = $this->response; - - // For non-AJAX requests, we forward the user to the login page. - if (!$this->request->isXhr()) { - $uri = $this->request->getUri(); - $path = $uri->getPath(); - $query = $uri->getQuery(); - $fragment = $uri->getFragment(); - - $path = $path - . ($query ? '?' . $query : '') - . ($fragment ? '#' . $fragment : ''); - - $loginPage = $this->ci->router->pathFor('login', [], [ - 'redirect' => $path - ]); - - $response = $response->withRedirect($loginPage); - } - - return $response; - } -} +<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Account\Error\Handler;
+
+use UserFrosting\Sprinkle\Core\Error\Handler\HttpExceptionHandler;
+
+/**
+ * Handler for AuthExpiredExceptions.
+ *
+ * Forwards the user to the login page when their session has expired.
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+class AuthExpiredExceptionHandler extends HttpExceptionHandler
+{
+ /**
+ * Custom handling for requests that did not pass authentication.
+ */
+ public function handle() {
+ // For auth expired exceptions, we always add messages to the alert stream.
+ $this->writeAlerts();
+
+ $response = $this->response;
+
+ // For non-AJAX requests, we forward the user to the login page.
+ if (!$this->request->isXhr()) {
+ $uri = $this->request->getUri();
+ $path = $uri->getPath();
+ $query = $uri->getQuery();
+ $fragment = $uri->getFragment();
+
+ $path = $path
+ . ($query ? '?' . $query : '')
+ . ($fragment ? '#' . $fragment : '');
+
+ $loginPage = $this->ci->router->pathFor('login', [], [
+ 'redirect' => $path
+ ]);
+
+ $response = $response->withRedirect($loginPage);
+ }
+
+ return $response;
+ }
+}
diff --git a/main/app/sprinkles/account/src/Error/Handler/ForbiddenExceptionHandler.php b/main/app/sprinkles/account/src/Error/Handler/ForbiddenExceptionHandler.php index b418dde..0166d2a 100644 --- a/main/app/sprinkles/account/src/Error/Handler/ForbiddenExceptionHandler.php +++ b/main/app/sprinkles/account/src/Error/Handler/ForbiddenExceptionHandler.php @@ -1,31 +1,31 @@ -<?php -/** - * UserFrosting (http://www.userfrosting.com) - * - * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) - */ - -namespace UserFrosting\Sprinkle\Account\Error\Handler; - -use UserFrosting\Sprinkle\Core\Error\Handler\HttpExceptionHandler; -use UserFrosting\Support\Message\UserMessage; - -/** - * Handler for ForbiddenExceptions. Only really needed to override the default error message. - * - * @author Alex Weissman (https://alexanderweissman.com) - */ -class ForbiddenExceptionHandler extends HttpExceptionHandler -{ - /** - * Resolve a list of error messages to present to the end user. - * - * @return array - */ - protected function determineUserMessages() { - return [ - new UserMessage("ACCOUNT.ACCESS_DENIED") - ]; - } -} +<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Account\Error\Handler;
+
+use UserFrosting\Sprinkle\Core\Error\Handler\HttpExceptionHandler;
+use UserFrosting\Support\Message\UserMessage;
+
+/**
+ * Handler for ForbiddenExceptions. Only really needed to override the default error message.
+ *
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+class ForbiddenExceptionHandler extends HttpExceptionHandler
+{
+ /**
+ * Resolve a list of error messages to present to the end user.
+ *
+ * @return array
+ */
+ protected function determineUserMessages() {
+ return [
+ new UserMessage("ACCOUNT.ACCESS_DENIED")
+ ];
+ }
+}
diff --git a/main/app/sprinkles/account/src/Facades/Password.php b/main/app/sprinkles/account/src/Facades/Password.php index 0664b7a..ec300d3 100644 --- a/main/app/sprinkles/account/src/Facades/Password.php +++ b/main/app/sprinkles/account/src/Facades/Password.php @@ -1,28 +1,28 @@ -<?php -/** - * UserFrosting (http://www.userfrosting.com) - * - * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) - */ - -namespace UserFrosting\Sprinkle\Account\Facades; - -use UserFrosting\System\Facade; - -/** - * Implements facade for the "password" service - * - * @author Alex Weissman (https://alexanderweissman.com) - */ -class Password extends Facade -{ - /** - * Get the registered name of the component. - * - * @return string - */ - protected static function getFacadeAccessor() { - return 'passwordHasher'; - } -} +<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Account\Facades;
+
+use UserFrosting\System\Facade;
+
+/**
+ * Implements facade for the "password" service
+ *
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+class Password extends Facade
+{
+ /**
+ * Get the registered name of the component.
+ *
+ * @return string
+ */
+ protected static function getFacadeAccessor() {
+ return 'passwordHasher';
+ }
+}
diff --git a/main/app/sprinkles/account/src/Log/UserActivityDatabaseHandler.php b/main/app/sprinkles/account/src/Log/UserActivityDatabaseHandler.php index a1cd14f..eb57f43 100644 --- a/main/app/sprinkles/account/src/Log/UserActivityDatabaseHandler.php +++ b/main/app/sprinkles/account/src/Log/UserActivityDatabaseHandler.php @@ -1,33 +1,33 @@ -<?php -/** - * UserFrosting (http://www.userfrosting.com) - * - * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) - */ - -namespace UserFrosting\Sprinkle\Account\Log; - -use UserFrosting\Sprinkle\Core\Log\DatabaseHandler; - -/** - * Monolog handler for storing user activities to the database. - * - * @author Alex Weissman (https://alexanderweissman.com) - */ -class UserActivityDatabaseHandler extends DatabaseHandler -{ - /** - * {@inheritDoc} - */ - protected function write(array $record) { - $log = $this->classMapper->createInstance($this->modelName, $record['extra']); - $log->save(); - - if (isset($record['extra']['user_id'])) { - $user = $this->classMapper->staticMethod('user', 'find', $record['extra']['user_id']); - $user->last_activity_id = $log->id; - $user->save(); - } - } -} +<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Account\Log;
+
+use UserFrosting\Sprinkle\Core\Log\DatabaseHandler;
+
+/**
+ * Monolog handler for storing user activities to the database.
+ *
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+class UserActivityDatabaseHandler extends DatabaseHandler
+{
+ /**
+ * {@inheritDoc}
+ */
+ protected function write(array $record) {
+ $log = $this->classMapper->createInstance($this->modelName, $record['extra']);
+ $log->save();
+
+ if (isset($record['extra']['user_id'])) {
+ $user = $this->classMapper->staticMethod('user', 'find', $record['extra']['user_id']);
+ $user->last_activity_id = $log->id;
+ $user->save();
+ }
+ }
+}
diff --git a/main/app/sprinkles/account/src/Log/UserActivityProcessor.php b/main/app/sprinkles/account/src/Log/UserActivityProcessor.php index f1aa8c7..a5c0d98 100644 --- a/main/app/sprinkles/account/src/Log/UserActivityProcessor.php +++ b/main/app/sprinkles/account/src/Log/UserActivityProcessor.php @@ -1,44 +1,44 @@ -<?php -/** - * UserFrosting (http://www.userfrosting.com) - * - * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) - */ - -namespace UserFrosting\Sprinkle\Account\Log; - -use Monolog\Logger; - -/** - * Monolog processor for constructing the user activity message. - * - * @author Alex Weissman (https://alexanderweissman.com) - */ -class UserActivityProcessor -{ - /** - * @var int - */ - protected $userId; - - /** - * @param int $userId The id of the user for whom we will be logging activities. - */ - public function __construct($userId) { - $this->userId = $userId; - } - - public function __invoke(array $record) { - $additionalFields = [ - 'ip_address' => $_SERVER['REMOTE_ADDR'], - 'user_id' => $this->userId, - 'occurred_at' => $record['datetime'], - 'description' => $record['message'] - ]; - - $record['extra'] = array_replace_recursive($record['extra'], $additionalFields, $record['context']); - - return $record; - } -} +<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Account\Log;
+
+use Monolog\Logger;
+
+/**
+ * Monolog processor for constructing the user activity message.
+ *
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+class UserActivityProcessor
+{
+ /**
+ * @var int
+ */
+ protected $userId;
+
+ /**
+ * @param int $userId The id of the user for whom we will be logging activities.
+ */
+ public function __construct($userId) {
+ $this->userId = $userId;
+ }
+
+ public function __invoke(array $record) {
+ $additionalFields = [
+ 'ip_address' => $_SERVER['REMOTE_ADDR'],
+ 'user_id' => $this->userId,
+ 'occurred_at' => $record['datetime'],
+ 'description' => $record['message']
+ ];
+
+ $record['extra'] = array_replace_recursive($record['extra'], $additionalFields, $record['context']);
+
+ return $record;
+ }
+}
diff --git a/main/app/sprinkles/account/src/Repository/PasswordResetRepository.php b/main/app/sprinkles/account/src/Repository/PasswordResetRepository.php index 21ff548..06a37a8 100644 --- a/main/app/sprinkles/account/src/Repository/PasswordResetRepository.php +++ b/main/app/sprinkles/account/src/Repository/PasswordResetRepository.php @@ -1,34 +1,34 @@ -<?php -/** - * UserFrosting (http://www.userfrosting.com) - * - * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) - */ - -namespace UserFrosting\Sprinkle\Account\Repository; - -use UserFrosting\Sprinkle\Account\Facades\Password; - -/** - * Token repository class for password reset requests. - * - * @author Alex Weissman (https://alexanderweissman.com) - * @see https://learn.userfrosting.com/users/user-accounts - */ -class PasswordResetRepository extends TokenRepository -{ - /** - * {@inheritDoc} - */ - protected $modelIdentifier = 'password_reset'; - - /** - * {@inheritDoc} - */ - protected function updateUser($user, $args) { - $user->password = Password::hash($args['password']); - // DO: generate user activity? or do this in controller? - $user->save(); - } -} +<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Account\Repository;
+
+use UserFrosting\Sprinkle\Account\Facades\Password;
+
+/**
+ * Token repository class for password reset requests.
+ *
+ * @author Alex Weissman (https://alexanderweissman.com)
+ * @see https://learn.userfrosting.com/users/user-accounts
+ */
+class PasswordResetRepository extends TokenRepository
+{
+ /**
+ * {@inheritDoc}
+ */
+ protected $modelIdentifier = 'password_reset';
+
+ /**
+ * {@inheritDoc}
+ */
+ protected function updateUser($user, $args) {
+ $user->password = Password::hash($args['password']);
+ // DO: generate user activity? or do this in controller?
+ $user->save();
+ }
+}
diff --git a/main/app/sprinkles/account/src/Repository/TokenRepository.php b/main/app/sprinkles/account/src/Repository/TokenRepository.php index 5c2e34a..6b289bf 100644 --- a/main/app/sprinkles/account/src/Repository/TokenRepository.php +++ b/main/app/sprinkles/account/src/Repository/TokenRepository.php @@ -1,223 +1,223 @@ -<?php -/** - * UserFrosting (http://www.userfrosting.com) - * - * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) - */ - -namespace UserFrosting\Sprinkle\Account\Repository; - -use Carbon\Carbon; -use UserFrosting\Sprinkle\Account\Database\Models\User; -use UserFrosting\Sprinkle\Core\Database\Models\Model; -use UserFrosting\Sprinkle\Core\Util\ClassMapper; - -/** - * An abstract class for interacting with a repository of time-sensitive user tokens. - * - * User tokens are used, for example, to perform password resets and new account email verifications. - * @author Alex Weissman (https://alexanderweissman.com) - * @see https://learn.userfrosting.com/users/user-accounts - */ -abstract class TokenRepository -{ - - /** - * @var ClassMapper - */ - protected $classMapper; - - /** - * @var string - */ - protected $algorithm; - - /** - * @var string - */ - protected $modelIdentifier; - - /** - * Create a new TokenRepository object. - * - * @param ClassMapper $classMapper Maps generic class identifiers to specific class names. - * @param string $algorithm The hashing algorithm to use when storing generated tokens. - */ - public function __construct(ClassMapper $classMapper, $algorithm = 'sha512') { - $this->classMapper = $classMapper; - $this->algorithm = $algorithm; - } - - /** - * Cancels a specified token by removing it from the database. - * - * @param int $token The token to remove. - * @return Model|false - */ - public function cancel($token) { - // Hash the password reset token for the stored version - $hash = hash($this->algorithm, $token); - - // Find an incomplete reset request for the specified hash - $model = $this->classMapper - ->staticMethod($this->modelIdentifier, 'where', 'hash', $hash) - ->where('completed', FALSE) - ->first(); - - if ($model === NULL) { - return FALSE; - } - - $model->delete(); - - return $model; - } - - /** - * Completes a token-based process, invoking updateUser() in the child object to do the actual action. - * - * @param int $token The token to complete. - * @param mixed[] $userParams An optional list of parameters to pass to updateUser(). - * @return Model|false - */ - public function complete($token, $userParams = []) { - // Hash the token for the stored version - $hash = hash($this->algorithm, $token); - - // Find an unexpired, incomplete token for the specified hash - $model = $this->classMapper - ->staticMethod($this->modelIdentifier, 'where', 'hash', $hash) - ->where('completed', FALSE) - ->where('expires_at', '>', Carbon::now()) - ->first(); - - if ($model === NULL) { - return FALSE; - } - - // Fetch user for this token - $user = $this->classMapper->staticMethod('user', 'find', $model->user_id); - - if (is_null($user)) { - return FALSE; - } - - $this->updateUser($user, $userParams); - - $model->fill([ - 'completed' => TRUE, - 'completed_at' => Carbon::now() - ]); - - $model->save(); - - return $model; - } - - /** - * Create a new token for a specified user. - * - * @param User $user The user object to associate with this token. - * @param int $timeout The time, in seconds, after which this token should expire. - * @return Model The model (PasswordReset, Verification, etc) object that stores the token. - */ - public function create(User $user, $timeout) { - // Remove any previous tokens for this user - $this->removeExisting($user); - - // Compute expiration time - $expiresAt = Carbon::now()->addSeconds($timeout); - - $model = $this->classMapper->createInstance($this->modelIdentifier); - - // Generate a random token - $model->setToken($this->generateRandomToken()); - - // Hash the password reset token for the stored version - $hash = hash($this->algorithm, $model->getToken()); - - $model->fill([ - 'hash' => $hash, - 'completed' => FALSE, - 'expires_at' => $expiresAt - ]); - - $model->user_id = $user->id; - - $model->save(); - - return $model; - } - - /** - * Determine if a specified user has an incomplete and unexpired token. - * - * @param User $user The user object to look up. - * @param int $token Optionally, try to match a specific token. - * @return Model|false - */ - public function exists(User $user, $token = NULL) { - $model = $this->classMapper - ->staticMethod($this->modelIdentifier, 'where', 'user_id', $user->id) - ->where('completed', FALSE) - ->where('expires_at', '>', Carbon::now()); - - if ($token) { - // get token hash - $hash = hash($this->algorithm, $token); - $model->where('hash', $hash); - } - - return $model->first() ?: FALSE; - } - - /** - * Delete all existing tokens from the database for a particular user. - * - * @param User $user - * @return int - */ - protected function removeExisting(User $user) { - return $this->classMapper - ->staticMethod($this->modelIdentifier, 'where', 'user_id', $user->id) - ->delete(); - } - - /** - * Remove all expired tokens from the database. - * - * @return bool|null - */ - public function removeExpired() { - return $this->classMapper - ->staticMethod($this->modelIdentifier, 'where', 'completed', FALSE) - ->where('expires_at', '<', Carbon::now()) - ->delete(); - } - - /** - * Generate a new random token for this user. - * - * This generates a token to use for verifying a new account, resetting a lost password, etc. - * @param string $gen specify an existing token that, if we happen to generate the same value, we should regenerate on. - * @return string - */ - protected function generateRandomToken($gen = NULL) { - do { - $gen = md5(uniqid(mt_rand(), FALSE)); - } while ($this->classMapper - ->staticMethod($this->modelIdentifier, 'where', 'hash', hash($this->algorithm, $gen)) - ->first()); - return $gen; - } - - /** - * Modify the user during the token completion process. - * - * This method is called during complete(), and is a way for concrete implementations to modify the user. - * @param User $user the user object to modify. - * @return mixed[] $args the list of parameters that were supplied to the call to `complete()` - */ - abstract protected function updateUser($user, $args); -} +<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Account\Repository;
+
+use Carbon\Carbon;
+use UserFrosting\Sprinkle\Account\Database\Models\User;
+use UserFrosting\Sprinkle\Core\Database\Models\Model;
+use UserFrosting\Sprinkle\Core\Util\ClassMapper;
+
+/**
+ * An abstract class for interacting with a repository of time-sensitive user tokens.
+ *
+ * User tokens are used, for example, to perform password resets and new account email verifications.
+ * @author Alex Weissman (https://alexanderweissman.com)
+ * @see https://learn.userfrosting.com/users/user-accounts
+ */
+abstract class TokenRepository
+{
+
+ /**
+ * @var ClassMapper
+ */
+ protected $classMapper;
+
+ /**
+ * @var string
+ */
+ protected $algorithm;
+
+ /**
+ * @var string
+ */
+ protected $modelIdentifier;
+
+ /**
+ * Create a new TokenRepository object.
+ *
+ * @param ClassMapper $classMapper Maps generic class identifiers to specific class names.
+ * @param string $algorithm The hashing algorithm to use when storing generated tokens.
+ */
+ public function __construct(ClassMapper $classMapper, $algorithm = 'sha512') {
+ $this->classMapper = $classMapper;
+ $this->algorithm = $algorithm;
+ }
+
+ /**
+ * Cancels a specified token by removing it from the database.
+ *
+ * @param int $token The token to remove.
+ * @return Model|false
+ */
+ public function cancel($token) {
+ // Hash the password reset token for the stored version
+ $hash = hash($this->algorithm, $token);
+
+ // Find an incomplete reset request for the specified hash
+ $model = $this->classMapper
+ ->staticMethod($this->modelIdentifier, 'where', 'hash', $hash)
+ ->where('completed', FALSE)
+ ->first();
+
+ if ($model === NULL) {
+ return FALSE;
+ }
+
+ $model->delete();
+
+ return $model;
+ }
+
+ /**
+ * Completes a token-based process, invoking updateUser() in the child object to do the actual action.
+ *
+ * @param int $token The token to complete.
+ * @param mixed[] $userParams An optional list of parameters to pass to updateUser().
+ * @return Model|false
+ */
+ public function complete($token, $userParams = []) {
+ // Hash the token for the stored version
+ $hash = hash($this->algorithm, $token);
+
+ // Find an unexpired, incomplete token for the specified hash
+ $model = $this->classMapper
+ ->staticMethod($this->modelIdentifier, 'where', 'hash', $hash)
+ ->where('completed', FALSE)
+ ->where('expires_at', '>', Carbon::now())
+ ->first();
+
+ if ($model === NULL) {
+ return FALSE;
+ }
+
+ // Fetch user for this token
+ $user = $this->classMapper->staticMethod('user', 'find', $model->user_id);
+
+ if (is_null($user)) {
+ return FALSE;
+ }
+
+ $this->updateUser($user, $userParams);
+
+ $model->fill([
+ 'completed' => TRUE,
+ 'completed_at' => Carbon::now()
+ ]);
+
+ $model->save();
+
+ return $model;
+ }
+
+ /**
+ * Create a new token for a specified user.
+ *
+ * @param User $user The user object to associate with this token.
+ * @param int $timeout The time, in seconds, after which this token should expire.
+ * @return Model The model (PasswordReset, Verification, etc) object that stores the token.
+ */
+ public function create(User $user, $timeout) {
+ // Remove any previous tokens for this user
+ $this->removeExisting($user);
+
+ // Compute expiration time
+ $expiresAt = Carbon::now()->addSeconds($timeout);
+
+ $model = $this->classMapper->createInstance($this->modelIdentifier);
+
+ // Generate a random token
+ $model->setToken($this->generateRandomToken());
+
+ // Hash the password reset token for the stored version
+ $hash = hash($this->algorithm, $model->getToken());
+
+ $model->fill([
+ 'hash' => $hash,
+ 'completed' => FALSE,
+ 'expires_at' => $expiresAt
+ ]);
+
+ $model->user_id = $user->id;
+
+ $model->save();
+
+ return $model;
+ }
+
+ /**
+ * Determine if a specified user has an incomplete and unexpired token.
+ *
+ * @param User $user The user object to look up.
+ * @param int $token Optionally, try to match a specific token.
+ * @return Model|false
+ */
+ public function exists(User $user, $token = NULL) {
+ $model = $this->classMapper
+ ->staticMethod($this->modelIdentifier, 'where', 'user_id', $user->id)
+ ->where('completed', FALSE)
+ ->where('expires_at', '>', Carbon::now());
+
+ if ($token) {
+ // get token hash
+ $hash = hash($this->algorithm, $token);
+ $model->where('hash', $hash);
+ }
+
+ return $model->first() ?: FALSE;
+ }
+
+ /**
+ * Delete all existing tokens from the database for a particular user.
+ *
+ * @param User $user
+ * @return int
+ */
+ protected function removeExisting(User $user) {
+ return $this->classMapper
+ ->staticMethod($this->modelIdentifier, 'where', 'user_id', $user->id)
+ ->delete();
+ }
+
+ /**
+ * Remove all expired tokens from the database.
+ *
+ * @return bool|null
+ */
+ public function removeExpired() {
+ return $this->classMapper
+ ->staticMethod($this->modelIdentifier, 'where', 'completed', FALSE)
+ ->where('expires_at', '<', Carbon::now())
+ ->delete();
+ }
+
+ /**
+ * Generate a new random token for this user.
+ *
+ * This generates a token to use for verifying a new account, resetting a lost password, etc.
+ * @param string $gen specify an existing token that, if we happen to generate the same value, we should regenerate on.
+ * @return string
+ */
+ protected function generateRandomToken($gen = NULL) {
+ do {
+ $gen = md5(uniqid(mt_rand(), FALSE));
+ } while ($this->classMapper
+ ->staticMethod($this->modelIdentifier, 'where', 'hash', hash($this->algorithm, $gen))
+ ->first());
+ return $gen;
+ }
+
+ /**
+ * Modify the user during the token completion process.
+ *
+ * This method is called during complete(), and is a way for concrete implementations to modify the user.
+ * @param User $user the user object to modify.
+ * @return mixed[] $args the list of parameters that were supplied to the call to `complete()`
+ */
+ abstract protected function updateUser($user, $args);
+}
diff --git a/main/app/sprinkles/account/src/Repository/VerificationRepository.php b/main/app/sprinkles/account/src/Repository/VerificationRepository.php index d714dce..f7ee3e7 100644 --- a/main/app/sprinkles/account/src/Repository/VerificationRepository.php +++ b/main/app/sprinkles/account/src/Repository/VerificationRepository.php @@ -1,31 +1,31 @@ -<?php -/** - * UserFrosting (http://www.userfrosting.com) - * - * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) - */ - -namespace UserFrosting\Sprinkle\Account\Repository; - -/** - * Token repository class for new account verifications. - * - * @author Alex Weissman (https://alexanderweissman.com) - * @see https://learn.userfrosting.com/users/user-accounts - */ -class VerificationRepository extends TokenRepository -{ - /** - * {@inheritDoc} - */ - protected $modelIdentifier = 'verification'; - - /** - * {@inheritDoc} - */ - protected function updateUser($user, $args) { - $user->flag_verified = 1; - $user->save(); - } -} +<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Account\Repository;
+
+/**
+ * Token repository class for new account verifications.
+ *
+ * @author Alex Weissman (https://alexanderweissman.com)
+ * @see https://learn.userfrosting.com/users/user-accounts
+ */
+class VerificationRepository extends TokenRepository
+{
+ /**
+ * {@inheritDoc}
+ */
+ protected $modelIdentifier = 'verification';
+
+ /**
+ * {@inheritDoc}
+ */
+ protected function updateUser($user, $args) {
+ $user->flag_verified = 1;
+ $user->save();
+ }
+}
diff --git a/main/app/sprinkles/account/src/ServicesProvider/ServicesProvider.php b/main/app/sprinkles/account/src/ServicesProvider/ServicesProvider.php index 38d81d5..1615d2e 100644 --- a/main/app/sprinkles/account/src/ServicesProvider/ServicesProvider.php +++ b/main/app/sprinkles/account/src/ServicesProvider/ServicesProvider.php @@ -1,444 +1,444 @@ -<?php -/** - * UserFrosting (http://www.userfrosting.com) - * - * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) - */ - -namespace UserFrosting\Sprinkle\Account\ServicesProvider; - -use Birke\Rememberme\Authenticator as RememberMe; -use Illuminate\Database\Capsule\Manager as Capsule; -use Monolog\Formatter\LineFormatter; -use Monolog\Handler\ErrorLogHandler; -use Monolog\Handler\StreamHandler; -use Monolog\Logger; -use Psr\Http\Message\ResponseInterface as Response; -use Psr\Http\Message\ServerRequestInterface as Request; -use UserFrosting\Sprinkle\Account\Authenticate\Authenticator; -use UserFrosting\Sprinkle\Account\Authenticate\AuthGuard; -use UserFrosting\Sprinkle\Account\Authenticate\Hasher; -use UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager; -use UserFrosting\Sprinkle\Account\Database\Models\User; -use UserFrosting\Sprinkle\Account\Log\UserActivityDatabaseHandler; -use UserFrosting\Sprinkle\Account\Log\UserActivityProcessor; -use UserFrosting\Sprinkle\Account\Repository\PasswordResetRepository; -use UserFrosting\Sprinkle\Account\Repository\VerificationRepository; -use UserFrosting\Sprinkle\Account\Twig\AccountExtension; -use UserFrosting\Sprinkle\Core\Facades\Debug; -use UserFrosting\Sprinkle\Core\Log\MixedFormatter; - -/** - * Registers services for the account sprinkle, such as currentUser, etc. - * - * @author Alex Weissman (https://alexanderweissman.com) - */ -class ServicesProvider -{ - /** - * Register UserFrosting's account services. - * - * @param Container $container A DI container implementing ArrayAccess and container-interop. - */ - public function register($container) { - /** - * Extend the asset manager service to see assets for the current user's theme. - */ - $container->extend('assets', function ($assets, $c) { - - // Register paths for user theme, if a user is logged in - // We catch any authorization-related exceptions, so that error pages can be rendered. - try { - /** @var UserFrosting\Sprinkle\Account\Authenticate\Authenticator $authenticator */ - $authenticator = $c->authenticator; - $currentUser = $c->currentUser; - } catch (\Exception $e) { - return $assets; - } - - if ($authenticator->check()) { - $c->sprinkleManager->addResource('assets', $currentUser->theme); - } - - return $assets; - }); - - /** - * Extend the 'classMapper' service to register model classes. - * - * Mappings added: User, Group, Role, Permission, Activity, PasswordReset, Verification - */ - $container->extend('classMapper', function ($classMapper, $c) { - $classMapper->setClassMapping('user', 'UserFrosting\Sprinkle\Account\Database\Models\User'); - $classMapper->setClassMapping('group', 'UserFrosting\Sprinkle\Account\Database\Models\Group'); - $classMapper->setClassMapping('role', 'UserFrosting\Sprinkle\Account\Database\Models\Role'); - $classMapper->setClassMapping('permission', 'UserFrosting\Sprinkle\Account\Database\Models\Permission'); - $classMapper->setClassMapping('activity', 'UserFrosting\Sprinkle\Account\Database\Models\Activity'); - $classMapper->setClassMapping('password_reset', 'UserFrosting\Sprinkle\Account\Database\Models\PasswordReset'); - $classMapper->setClassMapping('verification', 'UserFrosting\Sprinkle\Account\Database\Models\Verification'); - return $classMapper; - }); - - /** - * Extends the 'errorHandler' service with custom exception handlers. - * - * Custom handlers added: ForbiddenExceptionHandler - */ - $container->extend('errorHandler', function ($handler, $c) { - // Register the ForbiddenExceptionHandler. - $handler->registerHandler('\UserFrosting\Support\Exception\ForbiddenException', '\UserFrosting\Sprinkle\Account\Error\Handler\ForbiddenExceptionHandler'); - // Register the AuthExpiredExceptionHandler - $handler->registerHandler('\UserFrosting\Sprinkle\Account\Authenticate\Exception\AuthExpiredException', '\UserFrosting\Sprinkle\Account\Error\Handler\AuthExpiredExceptionHandler'); - // Register the AuthCompromisedExceptionHandler. - $handler->registerHandler('\UserFrosting\Sprinkle\Account\Authenticate\Exception\AuthCompromisedException', '\UserFrosting\Sprinkle\Account\Error\Handler\AuthCompromisedExceptionHandler'); - return $handler; - }); - - /** - * Extends the 'localePathBuilder' service, adding any locale files from the user theme. - * - */ - $container->extend('localePathBuilder', function ($pathBuilder, $c) { - // Add paths for user theme, if a user is logged in - // We catch any authorization-related exceptions, so that error pages can be rendered. - try { - /** @var UserFrosting\Sprinkle\Account\Authenticate\Authenticator $authenticator */ - $authenticator = $c->authenticator; - $currentUser = $c->currentUser; - } catch (\Exception $e) { - return $pathBuilder; - } - - if ($authenticator->check()) { - // Add paths to locale files for user theme - $themePath = $c->sprinkleManager->addResource('locale', $currentUser->theme); - - // Add user locale - $pathBuilder->addLocales($currentUser->locale); - } - - return $pathBuilder; - }); - - /** - * Extends the 'view' service with the AccountExtension for Twig. - * - * Adds account-specific functions, globals, filters, etc to Twig, and the path to templates for the user theme. - */ - $container->extend('view', function ($view, $c) { - $twig = $view->getEnvironment(); - $extension = new AccountExtension($c); - $twig->addExtension($extension); - - // Add paths for user theme, if a user is logged in - // We catch any authorization-related exceptions, so that error pages can be rendered. - try { - /** @var UserFrosting\Sprinkle\Account\Authenticate\Authenticator $authenticator */ - $authenticator = $c->authenticator; - $currentUser = $c->currentUser; - } catch (\Exception $e) { - return $view; - } - - if ($authenticator->check()) { - $theme = $currentUser->theme; - $themePath = $c->sprinkleManager->addResource('templates', $theme); - if ($themePath) { - $loader = $twig->getLoader(); - $loader->prependPath($themePath); - // Add namespaced path as well - $loader->addPath($themePath, $theme); - } - } - - return $view; - }); - - /** - * Authentication service. - * - * Supports logging in users, remembering their sessions, etc. - */ - $container['authenticator'] = function ($c) { - $classMapper = $c->classMapper; - $config = $c->config; - $session = $c->session; - $cache = $c->cache; - - // Force database connection to boot up - $c->db; - - // Fix RememberMe table name - $config['remember_me.table.tableName'] = Capsule::connection()->getTablePrefix() . $config['remember_me.table.tableName']; - - $authenticator = new Authenticator($classMapper, $session, $config, $cache); - return $authenticator; - }; - - /** - * Sets up the AuthGuard middleware, used to limit access to authenticated users for certain routes. - */ - $container['authGuard'] = function ($c) { - $authenticator = $c->authenticator; - return new AuthGuard($authenticator); - }; - - /** - * Authorization check logging with Monolog. - * - * Extend this service to push additional handlers onto the 'auth' log stack. - */ - $container['authLogger'] = function ($c) { - $logger = new Logger('auth'); - - $logFile = $c->get('locator')->findResource('log://userfrosting.log', TRUE, TRUE); - - $handler = new StreamHandler($logFile); - - $formatter = new MixedFormatter(NULL, NULL, TRUE); - - $handler->setFormatter($formatter); - $logger->pushHandler($handler); - - return $logger; - }; - - /** - * Authorization service. - * - * Determines permissions for user actions. Extend this service to add additional access condition callbacks. - */ - $container['authorizer'] = function ($c) { - $config = $c->config; - - // Default access condition callbacks. Add more in your sprinkle by using $container->extend(...) - $callbacks = [ - /** - * Unconditionally grant permission - use carefully! - * @return bool returns true no matter what. - */ - 'always' => function () { - return TRUE; - }, - - /** - * Check if the specified values are identical to one another (strict comparison). - * @param mixed $val1 the first value to compare. - * @param mixed $val2 the second value to compare. - * @return bool true if the values are strictly equal, false otherwise. - */ - 'equals' => function ($val1, $val2) { - return ($val1 === $val2); - }, - - /** - * Check if the specified values are numeric, and if so, if they are equal to each other. - * @param mixed $val1 the first value to compare. - * @param mixed $val2 the second value to compare. - * @return bool true if the values are numeric and equal, false otherwise. - */ - 'equals_num' => function ($val1, $val2) { - if (!is_numeric($val1)) { - return FALSE; - } - if (!is_numeric($val2)) { - return FALSE; - } - - return ($val1 == $val2); - }, - - /** - * Check if the specified user (by user_id) has a particular role. - * - * @param int $user_id the id of the user. - * @param int $role_id the id of the role. - * @return bool true if the user has the role, false otherwise. - */ - 'has_role' => function ($user_id, $role_id) { - return Capsule::table('role_users') - ->where('user_id', $user_id) - ->where('role_id', $role_id) - ->count() > 0; - }, - - /** - * Check if the specified value $needle is in the values of $haystack. - * - * @param mixed $needle the value to look for in $haystack - * @param array[mixed] $haystack the array of values to search. - * @return bool true if $needle is present in the values of $haystack, false otherwise. - */ - 'in' => function ($needle, $haystack) { - return in_array($needle, $haystack); - }, - - /** - * Check if the specified user (by user_id) is in a particular group. - * - * @param int $user_id the id of the user. - * @param int $group_id the id of the group. - * @return bool true if the user is in the group, false otherwise. - */ - 'in_group' => function ($user_id, $group_id) { - $user = User::find($user_id); - return ($user->group_id == $group_id); - }, - - /** - * Check if the specified user (by user_id) is the master user. - * - * @param int $user_id the id of the user. - * @return bool true if the user id is equal to the id of the master account, false otherwise. - */ - 'is_master' => function ($user_id) use ($config) { - // Need to use loose comparison for now, because some DBs return `id` as a string - return ($user_id == $config['reserved_user_ids.master']); - }, - - /** - * Check if all values in the array $needle are present in the values of $haystack. - * - * @param array[mixed] $needle the array whose values we should look for in $haystack - * @param array[mixed] $haystack the array of values to search. - * @return bool true if every value in $needle is present in the values of $haystack, false otherwise. - */ - 'subset' => function ($needle, $haystack) { - return count($needle) == count(array_intersect($needle, $haystack)); - }, - - /** - * Check if all keys of the array $needle are present in the values of $haystack. - * - * This function is useful for whitelisting an array of key-value parameters. - * @param array[mixed] $needle the array whose keys we should look for in $haystack - * @param array[mixed] $haystack the array of values to search. - * @return bool true if every key in $needle is present in the values of $haystack, false otherwise. - */ - 'subset_keys' => function ($needle, $haystack) { - return count($needle) == count(array_intersect(array_keys($needle), $haystack)); - } - ]; - - $authorizer = new AuthorizationManager($c, $callbacks); - return $authorizer; - }; - - /** - * Loads the User object for the currently logged-in user. - */ - $container['currentUser'] = function ($c) { - $authenticator = $c->authenticator; - - return $authenticator->user(); - }; - - $container['passwordHasher'] = function ($c) { - $hasher = new Hasher(); - return $hasher; - }; - - /** - * Returns a callback that forwards to dashboard if user is already logged in. - */ - $container['redirect.onAlreadyLoggedIn'] = function ($c) { - /** - * This method is invoked when a user attempts to perform certain public actions when they are already logged in. - * - * Forward to user's landing page or last visited page - * @param \Psr\Http\Message\ServerRequestInterface $request - * @param \Psr\Http\Message\ResponseInterface $response - * @param array $args - * @return \Psr\Http\Message\ResponseInterface - */ - return function (Request $request, Response $response, array $args) use ($c) { - $redirect = $c->router->pathFor('dashboard'); - - return $response->withRedirect($redirect, 302); - }; - }; - - /** - * Returns a callback that handles setting the `UF-Redirect` header after a successful login. - */ - $container['redirect.onLogin'] = function ($c) { - /** - * This method is invoked when a user completes the login process. - * - * Returns a callback that handles setting the `UF-Redirect` header after a successful login. - * @param \Psr\Http\Message\ServerRequestInterface $request - * @param \Psr\Http\Message\ResponseInterface $response - * @param array $args - * @return \Psr\Http\Message\ResponseInterface - */ - return function (Request $request, Response $response, array $args) use ($c) { - // Backwards compatibility for the deprecated determineRedirectOnLogin service - if ($c->has('determineRedirectOnLogin')) { - $determineRedirectOnLogin = $c->determineRedirectOnLogin; - - return $determineRedirectOnLogin($response)->withStatus(200); - } - - /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager */ - $authorizer = $c->authorizer; - - $currentUser = $c->authenticator->user(); - - if ($authorizer->checkAccess($currentUser, 'uri_account_settings')) { - return $response->withHeader('UF-Redirect', $c->router->pathFor('settings')); - } else { - return $response->withHeader('UF-Redirect', $c->router->pathFor('index')); - } - }; - }; - - /** - * Repository for password reset requests. - */ - $container['repoPasswordReset'] = function ($c) { - $classMapper = $c->classMapper; - $config = $c->config; - - $repo = new PasswordResetRepository($classMapper, $config['password_reset.algorithm']); - return $repo; - }; - - /** - * Repository for verification requests. - */ - $container['repoVerification'] = function ($c) { - $classMapper = $c->classMapper; - $config = $c->config; - - $repo = new VerificationRepository($classMapper, $config['verification.algorithm']); - return $repo; - }; - - /** - * Logger for logging the current user's activities to the database. - * - * Extend this service to push additional handlers onto the 'userActivity' log stack. - */ - $container['userActivityLogger'] = function ($c) { - $classMapper = $c->classMapper; - $config = $c->config; - $session = $c->session; - - $logger = new Logger('userActivity'); - - $handler = new UserActivityDatabaseHandler($classMapper, 'activity'); - - // Note that we get the user id from the session, not the currentUser service. - // This is because the currentUser service may not reflect the actual user during login/logout requests. - $currentUserIdKey = $config['session.keys.current_user_id']; - $userId = isset($session[$currentUserIdKey]) ? $session[$currentUserIdKey] : $config['reserved_user_ids.guest']; - $processor = new UserActivityProcessor($userId); - - $logger->pushProcessor($processor); - $logger->pushHandler($handler); - - return $logger; - }; - } -} +<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Account\ServicesProvider;
+
+use Birke\Rememberme\Authenticator as RememberMe;
+use Illuminate\Database\Capsule\Manager as Capsule;
+use Monolog\Formatter\LineFormatter;
+use Monolog\Handler\ErrorLogHandler;
+use Monolog\Handler\StreamHandler;
+use Monolog\Logger;
+use Psr\Http\Message\ResponseInterface as Response;
+use Psr\Http\Message\ServerRequestInterface as Request;
+use UserFrosting\Sprinkle\Account\Authenticate\Authenticator;
+use UserFrosting\Sprinkle\Account\Authenticate\AuthGuard;
+use UserFrosting\Sprinkle\Account\Authenticate\Hasher;
+use UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager;
+use UserFrosting\Sprinkle\Account\Database\Models\User;
+use UserFrosting\Sprinkle\Account\Log\UserActivityDatabaseHandler;
+use UserFrosting\Sprinkle\Account\Log\UserActivityProcessor;
+use UserFrosting\Sprinkle\Account\Repository\PasswordResetRepository;
+use UserFrosting\Sprinkle\Account\Repository\VerificationRepository;
+use UserFrosting\Sprinkle\Account\Twig\AccountExtension;
+use UserFrosting\Sprinkle\Core\Facades\Debug;
+use UserFrosting\Sprinkle\Core\Log\MixedFormatter;
+
+/**
+ * Registers services for the account sprinkle, such as currentUser, etc.
+ *
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+class ServicesProvider
+{
+ /**
+ * Register UserFrosting's account services.
+ *
+ * @param Container $container A DI container implementing ArrayAccess and container-interop.
+ */
+ public function register($container) {
+ /**
+ * Extend the asset manager service to see assets for the current user's theme.
+ */
+ $container->extend('assets', function ($assets, $c) {
+
+ // Register paths for user theme, if a user is logged in
+ // We catch any authorization-related exceptions, so that error pages can be rendered.
+ try {
+ /** @var UserFrosting\Sprinkle\Account\Authenticate\Authenticator $authenticator */
+ $authenticator = $c->authenticator;
+ $currentUser = $c->currentUser;
+ } catch (\Exception $e) {
+ return $assets;
+ }
+
+ if ($authenticator->check()) {
+ $c->sprinkleManager->addResource('assets', $currentUser->theme);
+ }
+
+ return $assets;
+ });
+
+ /**
+ * Extend the 'classMapper' service to register model classes.
+ *
+ * Mappings added: User, Group, Role, Permission, Activity, PasswordReset, Verification
+ */
+ $container->extend('classMapper', function ($classMapper, $c) {
+ $classMapper->setClassMapping('user', 'UserFrosting\Sprinkle\Account\Database\Models\User');
+ $classMapper->setClassMapping('group', 'UserFrosting\Sprinkle\Account\Database\Models\Group');
+ $classMapper->setClassMapping('role', 'UserFrosting\Sprinkle\Account\Database\Models\Role');
+ $classMapper->setClassMapping('permission', 'UserFrosting\Sprinkle\Account\Database\Models\Permission');
+ $classMapper->setClassMapping('activity', 'UserFrosting\Sprinkle\Account\Database\Models\Activity');
+ $classMapper->setClassMapping('password_reset', 'UserFrosting\Sprinkle\Account\Database\Models\PasswordReset');
+ $classMapper->setClassMapping('verification', 'UserFrosting\Sprinkle\Account\Database\Models\Verification');
+ return $classMapper;
+ });
+
+ /**
+ * Extends the 'errorHandler' service with custom exception handlers.
+ *
+ * Custom handlers added: ForbiddenExceptionHandler
+ */
+ $container->extend('errorHandler', function ($handler, $c) {
+ // Register the ForbiddenExceptionHandler.
+ $handler->registerHandler('\UserFrosting\Support\Exception\ForbiddenException', '\UserFrosting\Sprinkle\Account\Error\Handler\ForbiddenExceptionHandler');
+ // Register the AuthExpiredExceptionHandler
+ $handler->registerHandler('\UserFrosting\Sprinkle\Account\Authenticate\Exception\AuthExpiredException', '\UserFrosting\Sprinkle\Account\Error\Handler\AuthExpiredExceptionHandler');
+ // Register the AuthCompromisedExceptionHandler.
+ $handler->registerHandler('\UserFrosting\Sprinkle\Account\Authenticate\Exception\AuthCompromisedException', '\UserFrosting\Sprinkle\Account\Error\Handler\AuthCompromisedExceptionHandler');
+ return $handler;
+ });
+
+ /**
+ * Extends the 'localePathBuilder' service, adding any locale files from the user theme.
+ *
+ */
+ $container->extend('localePathBuilder', function ($pathBuilder, $c) {
+ // Add paths for user theme, if a user is logged in
+ // We catch any authorization-related exceptions, so that error pages can be rendered.
+ try {
+ /** @var UserFrosting\Sprinkle\Account\Authenticate\Authenticator $authenticator */
+ $authenticator = $c->authenticator;
+ $currentUser = $c->currentUser;
+ } catch (\Exception $e) {
+ return $pathBuilder;
+ }
+
+ if ($authenticator->check()) {
+ // Add paths to locale files for user theme
+ $themePath = $c->sprinkleManager->addResource('locale', $currentUser->theme);
+
+ // Add user locale
+ $pathBuilder->addLocales($currentUser->locale);
+ }
+
+ return $pathBuilder;
+ });
+
+ /**
+ * Extends the 'view' service with the AccountExtension for Twig.
+ *
+ * Adds account-specific functions, globals, filters, etc to Twig, and the path to templates for the user theme.
+ */
+ $container->extend('view', function ($view, $c) {
+ $twig = $view->getEnvironment();
+ $extension = new AccountExtension($c);
+ $twig->addExtension($extension);
+
+ // Add paths for user theme, if a user is logged in
+ // We catch any authorization-related exceptions, so that error pages can be rendered.
+ try {
+ /** @var UserFrosting\Sprinkle\Account\Authenticate\Authenticator $authenticator */
+ $authenticator = $c->authenticator;
+ $currentUser = $c->currentUser;
+ } catch (\Exception $e) {
+ return $view;
+ }
+
+ if ($authenticator->check()) {
+ $theme = $currentUser->theme;
+ $themePath = $c->sprinkleManager->addResource('templates', $theme);
+ if ($themePath) {
+ $loader = $twig->getLoader();
+ $loader->prependPath($themePath);
+ // Add namespaced path as well
+ $loader->addPath($themePath, $theme);
+ }
+ }
+
+ return $view;
+ });
+
+ /**
+ * Authentication service.
+ *
+ * Supports logging in users, remembering their sessions, etc.
+ */
+ $container['authenticator'] = function ($c) {
+ $classMapper = $c->classMapper;
+ $config = $c->config;
+ $session = $c->session;
+ $cache = $c->cache;
+
+ // Force database connection to boot up
+ $c->db;
+
+ // Fix RememberMe table name
+ $config['remember_me.table.tableName'] = Capsule::connection()->getTablePrefix() . $config['remember_me.table.tableName'];
+
+ $authenticator = new Authenticator($classMapper, $session, $config, $cache);
+ return $authenticator;
+ };
+
+ /**
+ * Sets up the AuthGuard middleware, used to limit access to authenticated users for certain routes.
+ */
+ $container['authGuard'] = function ($c) {
+ $authenticator = $c->authenticator;
+ return new AuthGuard($authenticator);
+ };
+
+ /**
+ * Authorization check logging with Monolog.
+ *
+ * Extend this service to push additional handlers onto the 'auth' log stack.
+ */
+ $container['authLogger'] = function ($c) {
+ $logger = new Logger('auth');
+
+ $logFile = $c->get('locator')->findResource('log://userfrosting.log', TRUE, TRUE);
+
+ $handler = new StreamHandler($logFile);
+
+ $formatter = new MixedFormatter(NULL, NULL, TRUE);
+
+ $handler->setFormatter($formatter);
+ $logger->pushHandler($handler);
+
+ return $logger;
+ };
+
+ /**
+ * Authorization service.
+ *
+ * Determines permissions for user actions. Extend this service to add additional access condition callbacks.
+ */
+ $container['authorizer'] = function ($c) {
+ $config = $c->config;
+
+ // Default access condition callbacks. Add more in your sprinkle by using $container->extend(...)
+ $callbacks = [
+ /**
+ * Unconditionally grant permission - use carefully!
+ * @return bool returns true no matter what.
+ */
+ 'always' => function () {
+ return TRUE;
+ },
+
+ /**
+ * Check if the specified values are identical to one another (strict comparison).
+ * @param mixed $val1 the first value to compare.
+ * @param mixed $val2 the second value to compare.
+ * @return bool true if the values are strictly equal, false otherwise.
+ */
+ 'equals' => function ($val1, $val2) {
+ return ($val1 === $val2);
+ },
+
+ /**
+ * Check if the specified values are numeric, and if so, if they are equal to each other.
+ * @param mixed $val1 the first value to compare.
+ * @param mixed $val2 the second value to compare.
+ * @return bool true if the values are numeric and equal, false otherwise.
+ */
+ 'equals_num' => function ($val1, $val2) {
+ if (!is_numeric($val1)) {
+ return FALSE;
+ }
+ if (!is_numeric($val2)) {
+ return FALSE;
+ }
+
+ return ($val1 == $val2);
+ },
+
+ /**
+ * Check if the specified user (by user_id) has a particular role.
+ *
+ * @param int $user_id the id of the user.
+ * @param int $role_id the id of the role.
+ * @return bool true if the user has the role, false otherwise.
+ */
+ 'has_role' => function ($user_id, $role_id) {
+ return Capsule::table('role_users')
+ ->where('user_id', $user_id)
+ ->where('role_id', $role_id)
+ ->count() > 0;
+ },
+
+ /**
+ * Check if the specified value $needle is in the values of $haystack.
+ *
+ * @param mixed $needle the value to look for in $haystack
+ * @param array[mixed] $haystack the array of values to search.
+ * @return bool true if $needle is present in the values of $haystack, false otherwise.
+ */
+ 'in' => function ($needle, $haystack) {
+ return in_array($needle, $haystack);
+ },
+
+ /**
+ * Check if the specified user (by user_id) is in a particular group.
+ *
+ * @param int $user_id the id of the user.
+ * @param int $group_id the id of the group.
+ * @return bool true if the user is in the group, false otherwise.
+ */
+ 'in_group' => function ($user_id, $group_id) {
+ $user = User::find($user_id);
+ return ($user->group_id == $group_id);
+ },
+
+ /**
+ * Check if the specified user (by user_id) is the master user.
+ *
+ * @param int $user_id the id of the user.
+ * @return bool true if the user id is equal to the id of the master account, false otherwise.
+ */
+ 'is_master' => function ($user_id) use ($config) {
+ // Need to use loose comparison for now, because some DBs return `id` as a string
+ return ($user_id == $config['reserved_user_ids.master']);
+ },
+
+ /**
+ * Check if all values in the array $needle are present in the values of $haystack.
+ *
+ * @param array[mixed] $needle the array whose values we should look for in $haystack
+ * @param array[mixed] $haystack the array of values to search.
+ * @return bool true if every value in $needle is present in the values of $haystack, false otherwise.
+ */
+ 'subset' => function ($needle, $haystack) {
+ return count($needle) == count(array_intersect($needle, $haystack));
+ },
+
+ /**
+ * Check if all keys of the array $needle are present in the values of $haystack.
+ *
+ * This function is useful for whitelisting an array of key-value parameters.
+ * @param array[mixed] $needle the array whose keys we should look for in $haystack
+ * @param array[mixed] $haystack the array of values to search.
+ * @return bool true if every key in $needle is present in the values of $haystack, false otherwise.
+ */
+ 'subset_keys' => function ($needle, $haystack) {
+ return count($needle) == count(array_intersect(array_keys($needle), $haystack));
+ }
+ ];
+
+ $authorizer = new AuthorizationManager($c, $callbacks);
+ return $authorizer;
+ };
+
+ /**
+ * Loads the User object for the currently logged-in user.
+ */
+ $container['currentUser'] = function ($c) {
+ $authenticator = $c->authenticator;
+
+ return $authenticator->user();
+ };
+
+ $container['passwordHasher'] = function ($c) {
+ $hasher = new Hasher();
+ return $hasher;
+ };
+
+ /**
+ * Returns a callback that forwards to dashboard if user is already logged in.
+ */
+ $container['redirect.onAlreadyLoggedIn'] = function ($c) {
+ /**
+ * This method is invoked when a user attempts to perform certain public actions when they are already logged in.
+ *
+ * Forward to user's landing page or last visited page
+ * @param \Psr\Http\Message\ServerRequestInterface $request
+ * @param \Psr\Http\Message\ResponseInterface $response
+ * @param array $args
+ * @return \Psr\Http\Message\ResponseInterface
+ */
+ return function (Request $request, Response $response, array $args) use ($c) {
+ $redirect = $c->router->pathFor('dashboard');
+
+ return $response->withRedirect($redirect, 302);
+ };
+ };
+
+ /**
+ * Returns a callback that handles setting the `UF-Redirect` header after a successful login.
+ */
+ $container['redirect.onLogin'] = function ($c) {
+ /**
+ * This method is invoked when a user completes the login process.
+ *
+ * Returns a callback that handles setting the `UF-Redirect` header after a successful login.
+ * @param \Psr\Http\Message\ServerRequestInterface $request
+ * @param \Psr\Http\Message\ResponseInterface $response
+ * @param array $args
+ * @return \Psr\Http\Message\ResponseInterface
+ */
+ return function (Request $request, Response $response, array $args) use ($c) {
+ // Backwards compatibility for the deprecated determineRedirectOnLogin service
+ if ($c->has('determineRedirectOnLogin')) {
+ $determineRedirectOnLogin = $c->determineRedirectOnLogin;
+
+ return $determineRedirectOnLogin($response)->withStatus(200);
+ }
+
+ /** @var UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager */
+ $authorizer = $c->authorizer;
+
+ $currentUser = $c->authenticator->user();
+
+ if ($authorizer->checkAccess($currentUser, 'uri_account_settings')) {
+ return $response->withHeader('UF-Redirect', $c->router->pathFor('settings'));
+ } else {
+ return $response->withHeader('UF-Redirect', $c->router->pathFor('index'));
+ }
+ };
+ };
+
+ /**
+ * Repository for password reset requests.
+ */
+ $container['repoPasswordReset'] = function ($c) {
+ $classMapper = $c->classMapper;
+ $config = $c->config;
+
+ $repo = new PasswordResetRepository($classMapper, $config['password_reset.algorithm']);
+ return $repo;
+ };
+
+ /**
+ * Repository for verification requests.
+ */
+ $container['repoVerification'] = function ($c) {
+ $classMapper = $c->classMapper;
+ $config = $c->config;
+
+ $repo = new VerificationRepository($classMapper, $config['verification.algorithm']);
+ return $repo;
+ };
+
+ /**
+ * Logger for logging the current user's activities to the database.
+ *
+ * Extend this service to push additional handlers onto the 'userActivity' log stack.
+ */
+ $container['userActivityLogger'] = function ($c) {
+ $classMapper = $c->classMapper;
+ $config = $c->config;
+ $session = $c->session;
+
+ $logger = new Logger('userActivity');
+
+ $handler = new UserActivityDatabaseHandler($classMapper, 'activity');
+
+ // Note that we get the user id from the session, not the currentUser service.
+ // This is because the currentUser service may not reflect the actual user during login/logout requests.
+ $currentUserIdKey = $config['session.keys.current_user_id'];
+ $userId = isset($session[$currentUserIdKey]) ? $session[$currentUserIdKey] : $config['reserved_user_ids.guest'];
+ $processor = new UserActivityProcessor($userId);
+
+ $logger->pushProcessor($processor);
+ $logger->pushHandler($handler);
+
+ return $logger;
+ };
+ }
+}
diff --git a/main/app/sprinkles/account/src/Twig/AccountExtension.php b/main/app/sprinkles/account/src/Twig/AccountExtension.php index 287f879..fc94a1a 100644 --- a/main/app/sprinkles/account/src/Twig/AccountExtension.php +++ b/main/app/sprinkles/account/src/Twig/AccountExtension.php @@ -1,62 +1,62 @@ -<?php -/** - * UserFrosting (http://www.userfrosting.com) - * - * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) - */ - -namespace UserFrosting\Sprinkle\Account\Twig; - -use Interop\Container\ContainerInterface; -use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator; -use Slim\Http\Uri; - -/** - * Extends Twig functionality for the Account sprinkle. - * - * @author Alex Weissman (https://alexanderweissman.com) - */ -class AccountExtension extends \Twig_Extension implements \Twig_Extension_GlobalsInterface -{ - - protected $services; - protected $config; - - public function __construct(ContainerInterface $services) { - $this->services = $services; - $this->config = $services->config; - } - - public function getName() { - return 'userfrosting/account'; - } - - public function getFunctions() { - return array( - // Add Twig function for checking permissions during dynamic menu rendering - new \Twig_SimpleFunction('checkAccess', function ($slug, $params = []) { - $authorizer = $this->services->authorizer; - $currentUser = $this->services->currentUser; - - return $authorizer->checkAccess($currentUser, $slug, $params); - }), - new \Twig_SimpleFunction('checkAuthenticated', function () { - $authenticator = $this->services->authenticator; - return $authenticator->check(); - }) - ); - } - - public function getGlobals() { - try { - $currentUser = $this->services->currentUser; - } catch (\Exception $e) { - $currentUser = NULL; - } - - return [ - 'current_user' => $currentUser - ]; - } -} +<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Account\Twig;
+
+use Interop\Container\ContainerInterface;
+use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
+use Slim\Http\Uri;
+
+/**
+ * Extends Twig functionality for the Account sprinkle.
+ *
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+class AccountExtension extends \Twig_Extension implements \Twig_Extension_GlobalsInterface
+{
+
+ protected $services;
+ protected $config;
+
+ public function __construct(ContainerInterface $services) {
+ $this->services = $services;
+ $this->config = $services->config;
+ }
+
+ public function getName() {
+ return 'userfrosting/account';
+ }
+
+ public function getFunctions() {
+ return array(
+ // Add Twig function for checking permissions during dynamic menu rendering
+ new \Twig_SimpleFunction('checkAccess', function ($slug, $params = []) {
+ $authorizer = $this->services->authorizer;
+ $currentUser = $this->services->currentUser;
+
+ return $authorizer->checkAccess($currentUser, $slug, $params);
+ }),
+ new \Twig_SimpleFunction('checkAuthenticated', function () {
+ $authenticator = $this->services->authenticator;
+ return $authenticator->check();
+ })
+ );
+ }
+
+ public function getGlobals() {
+ try {
+ $currentUser = $this->services->currentUser;
+ } catch (\Exception $e) {
+ $currentUser = NULL;
+ }
+
+ return [
+ 'current_user' => $currentUser
+ ];
+ }
+}
diff --git a/main/app/sprinkles/account/src/Util/HashFailedException.php b/main/app/sprinkles/account/src/Util/HashFailedException.php index 765096b..580bed1 100644 --- a/main/app/sprinkles/account/src/Util/HashFailedException.php +++ b/main/app/sprinkles/account/src/Util/HashFailedException.php @@ -1,22 +1,22 @@ -<?php -/** - * UserFrosting (http://www.userfrosting.com) - * - * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) - */ - -namespace UserFrosting\Sprinkle\Account\Util; - -use UserFrosting\Support\Exception\HttpException; - -/** - * Password hash failure exception. Used when the supplied password could not be hashed for some reason. - * - * @author Alex Weissman (https://alexanderweissman.com) - */ -class HashFailedException extends HttpException -{ - protected $defaultMessage = 'PASSWORD.HASH_FAILED'; - protected $httpErrorCode = 500; -} +<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Account\Util;
+
+use UserFrosting\Support\Exception\HttpException;
+
+/**
+ * Password hash failure exception. Used when the supplied password could not be hashed for some reason.
+ *
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+class HashFailedException extends HttpException
+{
+ protected $defaultMessage = 'PASSWORD.HASH_FAILED';
+ protected $httpErrorCode = 500;
+}
diff --git a/main/app/sprinkles/account/src/Util/Util.php b/main/app/sprinkles/account/src/Util/Util.php index f8a0444..f3a08a2 100644 --- a/main/app/sprinkles/account/src/Util/Util.php +++ b/main/app/sprinkles/account/src/Util/Util.php @@ -1,39 +1,39 @@ -<?php -/** - * UserFrosting (http://www.userfrosting.com) - * - * @link https://github.com/userfrosting/UserFrosting - * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License) - */ - -namespace UserFrosting\Sprinkle\Account\Util; - -use UserFrosting\Sprinkle\Core\Util\Util as CoreUtil; - -/** - * Util Class - * - * Static utility functions for the account Sprinkle. - * - * @author Alex Weissman (https://alexanderweissman.com) - */ -class Util -{ - /** - * Generate a random, unique username from a list of adjectives and nouns. - */ - static public function randomUniqueUsername($classMapper, $maxLength, $maxTries = 10) { - for ($n = 1; $n <= 3; $n++) { - for ($m = 0; $m < 10; $m++) { - // Generate a random phrase with $n adjectives - $suggestion = CoreUtil::randomPhrase($n, $maxLength, $maxTries, '.'); - if (!$classMapper->staticMethod('user', 'where', 'user_name', $suggestion)->first()) { - return $suggestion; - } - } - } - - return ''; - } - -} +<?php
+/**
+ * UserFrosting (http://www.userfrosting.com)
+ *
+ * @link https://github.com/userfrosting/UserFrosting
+ * @license https://github.com/userfrosting/UserFrosting/blob/master/licenses/UserFrosting.md (MIT License)
+ */
+
+namespace UserFrosting\Sprinkle\Account\Util;
+
+use UserFrosting\Sprinkle\Core\Util\Util as CoreUtil;
+
+/**
+ * Util Class
+ *
+ * Static utility functions for the account Sprinkle.
+ *
+ * @author Alex Weissman (https://alexanderweissman.com)
+ */
+class Util
+{
+ /**
+ * Generate a random, unique username from a list of adjectives and nouns.
+ */
+ static public function randomUniqueUsername($classMapper, $maxLength, $maxTries = 10) {
+ for ($n = 1; $n <= 3; $n++) {
+ for ($m = 0; $m < 10; $m++) {
+ // Generate a random phrase with $n adjectives
+ $suggestion = CoreUtil::randomPhrase($n, $maxLength, $maxTries, '.');
+ if (!$classMapper->staticMethod('user', 'where', 'user_name', $suggestion)->first()) {
+ return $suggestion;
+ }
+ }
+ }
+
+ return '';
+ }
+
+}
|