Permalink
Browse files

Add ability to specify roles that aren't allowed to login, related to p…

…rocesswire/processwire-requests#140 plus while I was in there, did some re-working of login related code in Session class and user management code in ProcessUser.module.
  • Loading branch information...
ryancramerdesign committed Dec 13, 2017
1 parent bac60dc commit 9b10232b732bd8fc61911a7919a14faa162e4794
View
@@ -331,6 +331,24 @@
*/
$config->userAuthHashType = 'sha1';
/**
* Names (string) or IDs (int) of roles that are not allowed to login
*
* Note that you must create these roles yourself in the admin. When a user has
* one of these named roles, $session->login() will not accept a login from them.
* This affects the admin login form and any other login forms that use ProcessWire’s
* session system.
*
* The default value specifies a role name of "login-disabled", meaning if you create
* a role with that name, and assign it to a user, that user will no longer be able
* to login.
*
* @var array
*
*/
$config->loginDisabledRoles = array(
'login-disabled'
);
/*** 4. TEMPLATE FILES **************************************************************************/
@@ -700,6 +718,14 @@
*/
$config->maxUrlSegments = 4;
/**
* Maximum length for any individual URL segment (default=128)
*
* @var int
*
*/
$config->maxUrlSegmentLength = 128;
/**
* Maximum URL/path slashes (depth) for request URLs
*
View
@@ -63,6 +63,7 @@
* @property bool $sessionChallenge Should login sessions have a challenge key? (for extra security, recommended) #pw-group-session
* @property bool $sessionFingerprint Should login sessions be tied to IP and user agent? 0 or false: Fingerprint off. 1 or true: Fingerprint on with default/recommended setting (currently 10). 2: Fingerprint only the remote IP. 4: Fingerprint only the forwarded/client IP (can be spoofed). 8: Fingerprint only the useragent. 10: Fingerprint the remote IP and useragent (default). 12: Fingerprint the forwarded/client IP and useragent. 14: Fingerprint the remote IP, forwarded/client IP and useragent (all). #pw-group-session
* @property int $sessionHistory Number of session entries to keep (default=0, which means off). #pw-group-session
* @property array $loginDisabledRoles Array of role name(s) or ID(s) of roles where login is disallowed. #pw-group-session
*
* @property string $prependTemplateFile PHP file in /site/templates/ that will be loaded before each page's template file (default=none) #pw-group-template-files
* @property string $appendTemplateFile PHP file in /site/templates/ that will be loaded after each page's template file (default=none) #pw-group-template-files
@@ -79,6 +80,7 @@
* @property string $pageNumUrlPrefix Prefix used for pagination URLs. Default is "page", resulting in "/page1", "/page2", etc. #pw-group-URLs
* @property array $pageNumUrlPrefixes Multiple prefixes that may be used for detecting pagination (internal use, for multi-language) #pw-group-URLs
* @property int $maxUrlSegments Maximum number of extra stacked URL segments allowed in a page's URL (including page numbers) #pw-group-URLs
* @property int $maxUrlSegmentLength Maximum length of any individual URL segment (default=128). #pw-group-URLs
* @property int $maxUrlDepth Maximum URL/path slashes (depth) for request URLs. (Min=10, Max=60) #pw-group-URLs
* @property string $wireInputOrder Order that variables with the $input API var are handled when you access $input->var. #pw-group-HTTP-and-input
*
View
@@ -449,15 +449,16 @@ public function pageNameTranslate($value, $maxLength = 128) {
* #pw-group-pages
*
* @param string $value Value to sanitize
* @param int $maxLength Maximum number of characters allowed
* @return string Sanitized value
*
*/
public function pageNameUTF8($value) {
public function pageNameUTF8($value, $maxLength = 128) {
if(!strlen($value)) return '';
// if UTF8 module is not enabled then delegate this call to regular pageName sanitizer
if($this->wire('config')->pageNameCharset != 'UTF8') return $this->pageName($value);
if($this->wire('config')->pageNameCharset != 'UTF8') return $this->pageName($value, false, $maxLength);
// we don't allow UTF8 page names to be prefixed with "xn-"
if(strpos($value, 'xn-') === 0) $value = substr($value, 3);
@@ -469,7 +470,7 @@ public function pageNameUTF8($value) {
$extras = array('.', '-', '_', ' ', ',', ';', ':', '(', ')', '!', '?', '&', '%', '$', '#', '@');
// proceed only if value has some non-ascii characters
if(ctype_alnum(str_replace($extras, '', $value))) return $this->pageName($value);
if(ctype_alnum(str_replace($extras, '', $value))) return $this->pageName($value, false, $maxLength);
// validate that all characters are in our whitelist
$whitelist = $this->wire('config')->pageNameWhitelist;
@@ -515,6 +516,8 @@ public function pageNameUTF8($value) {
// trim off any remaining separators/extras
$value = trim($value, '-_.');
if(mb_strlen($value) > $maxLength) $value = mb_substr($value, 0, $maxLength);
return $value;
}
View
@@ -17,13 +17,13 @@
*
* @see https://processwire.com/api/ref/session/ Session documentation
*
* @method User login() login($name, $pass) Login the user identified by $name and authenticated by $pass. Returns the user object on successful login or null on failure.
* @method User login() login($name, $pass, $force = false) Login the user identified by $name and authenticated by $pass. Returns the user object on successful login or null on failure.
* @method Session logout() logout() Logout the current user, and clear all session variables.
* @method void redirect() redirect($url, $http301 = true) Redirect this session to the specified URL.
* @method void init() Initialize session (called automatically by constructor) #pw-hooker
* @method bool authenticate(User $user, $pass) #pw-hooker
* @method bool isValidSession($userID) #pw-hooker
* @method bool allowLogin($name) #pw-hooker
* @method bool allowLogin($name, User $user = null) #pw-hooker
* @method void loginSuccess(User $user) #pw-hooker
* @method void loginFailure($name, $reason) #pw-hooker
* @method void logoutSuccess(User $user) #pw-hooker
@@ -746,27 +746,42 @@ public function getIP($int = false, $useClient = false) {
*
*/
public function ___login($name, $pass, $force = false) {
/** @var User|null $user */
$user = null;
/** @var Sanitizer $sanitizer */
$sanitizer = $this->wire('sanitizer');
/** @var Users $users */
$users = $this->wire('users');
/** @var int $guestUserID */
$guestUserID = $this->wire('config')->guestUserPageID;
$fail = true;
$failReason = '';
$user = null;
if(is_object($name) && $name instanceof User) {
$user = $name;
$name = $user->name;
} else {
$name = $this->wire('sanitizer')->pageNameUTF8($name);
$name = $sanitizer->pageNameUTF8($name);
}
if(!$this->allowLogin($name)) {
$this->loginFailure($name, "User is not allowed to login");
return null;
}
if(!strlen($name)) return null;
if(is_null($user)) {
$user = strlen($name) ? $this->wire('users')->get("name=$name") : null;
$user = $users->get('name=' . $sanitizer->selectorValue($name));
}
if( $user && $user->id
&& $user->id != $this->wire('config')->guestUserPageID
&& ($force === true || $this->authenticate($user, $pass))) {
if(!$user || !$user->id) {
$failReason = 'Unknown user';
} else if($user->id == $guestUserID) {
$failReason = 'Guest user may not login';
} else if(!$this->allowLogin($name, $user)) {
$failReason = 'Login not allowed';
} else if($force === true || $this->authenticate($user, $pass)) {
$this->trackChange('login', $this->wire('user'), $user);
session_regenerate_id(true);
@@ -780,7 +795,8 @@ public function ___login($name, $pass, $force = false) {
$this->set('_user', 'challenge', $challenge);
$secure = $this->config->sessionCookieSecure ? (bool) $this->config->https : false;
// set challenge cookie to last 30 days (should be longer than any session would feasibly last)
setcookie(session_name() . '_challenge', $challenge, time()+60*60*24*30, '/', $this->config->sessionCookieDomain, $secure, true); // PR #1264
setcookie(session_name() . '_challenge', $challenge, time()+60*60*24*30, '/',
$this->config->sessionCookieDomain, $secure, true);
}
if($this->config->sessionFingerprint) {
@@ -791,21 +807,19 @@ public function ___login($name, $pass, $force = false) {
$this->wire('user', $user);
$this->get('CSRF')->resetAll();
$this->loginSuccess($user);
$fail = false;
return $user;
} else {
if(!$user || !$user->id) {
$reason = "Unknown user: $name";
} else if($user->id == $this->wire('config')->guestUserPageID) {
$reason = "Guest user may not login";
} else {
$reason = "Invalid password";
}
$this->loginFailure($name, $reason);
// authentication failed
$failReason = 'Invalid password';
}
if($fail) {
$this->loginFailure($name, $failReason);
$user = null;
}
return null;
return $user;
}
/**
@@ -857,12 +871,34 @@ protected function ___loginFailure($name, $reason) {
* #pw-hooker
*
* @param string $name User login name
* @param User|null $user User object
* @return bool True if allowed to login, false if not (hooks may modify this)
*
*/
public function ___allowLogin($name) {
public function ___allowLogin($name, $user = null) {
$allow = true;
if(!strlen($name)) return false;
if(!$user || !$user instanceof User) {
$name = $this->wire('sanitizer')->pageNameUTF8($name);
$user = $this->wire('users')->get("name=" . $this->wire('sanitizer')->selectorValue($name));

This comment has been minimized.

Show comment
Hide comment
@mirzadabeer

mirzadabeer Dec 18, 2017

using single quotes in this context will be useful ("name="), since it is only string.

@mirzadabeer

mirzadabeer Dec 18, 2017

using single quotes in this context will be useful ("name="), since it is only string.

if(!$user || !$user->id) return false;
}
$xroles = $this->wire('config')->loginDisabledRoles;
if(!is_array($xroles) && !empty($xroles)) $xroles = array($xroles);
if($name) {}
return true;
if($user) {
if($user->isUnpublished()) {
$allow = false;
} else if(is_array($xroles)) {
foreach($xroles as $xrole) {
if($user->hasRole($xrole)) {
$allow = false;
break;
}
}
}
}
return $allow;
}
/**
View
@@ -278,6 +278,8 @@ public function urlSegments() {
*/
public function setUrlSegment($num, $value) {
$num = (int) $num;
$maxLength = $this->wire('config')->maxUrlSegmentLength;
if($maxLength < 1) $maxLength = 128;
if(is_null($value)) {
// unset
$n = 0;
@@ -289,10 +291,10 @@ public function setUrlSegment($num, $value) {
$this->urlSegments = $urlSegments;
} else if($this->wire('config')->pageNameCharset == 'UTF8') {
// set UTF8
$this->urlSegments[$num] = $this->wire('sanitizer')->pageNameUTF8($value);
$this->urlSegments[$num] = $this->wire('sanitizer')->pageNameUTF8($value, $maxLength);
} else {
// set ascii
$this->urlSegments[$num] = $this->wire('sanitizer')->name($value);
$this->urlSegments[$num] = $this->wire('sanitizer')->name($value, false, $maxLength);
}
}
@@ -139,8 +139,13 @@ class ProcessForgotPassword extends Process implements ConfigurableModule {
/** @var User $user */
$user = $this->users->get("name=" . $this->sanitizer->selectorValue($name));
if($user && $user->id && $user->email && !$user->isUnpublished()) {
// user was found, send them an email with reset link
$this->step2_sendEmail($user);
if($this->wire('session')->allowLogin($name, $user)) {
// user was found, send them an email with reset link
$this->step2_sendEmail($user);
} else {
$this->error($this->_('Specified account is not allowed to login so password may not be reset.'));
$this->wire('session')->redirect('./');
}
}
}
Oops, something went wrong.

0 comments on commit 9b10232

Please sign in to comment.