1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157: 158: 159: 160: 161: 162: 163: 164: 165: 166: 167: 168: 169: 170: 171: 172: 173: 174: 175: 176: 177: 178: 179: 180: 181: 182: 183: 184: 185: 186: 187: 188: 189: 190: 191: 192: 193: 194: 195: 196: 197: 198: 199: 200: 201: 202: 203: 204: 205: 206: 207: 208: 209: 210: 211: 212: 213: 214: 215: 216: 217: 218: 219: 220: 221: 222: 223: 224: 225: 226: 227: 228: 229: 230: 231: 232: 233: 234: 235: 236: 237: 238: 239: 240: 241: 242: 243: 244: 245: 246: 247: 248: 249: 250: 251: 252: 253: 254: 255: 256: 257: 258: 259: 260: 261: 262: 263: 264: 265: 266: 267: 268: 269: 270: 271: 272: 273: 274: 275: 276: 277: 278: 279: 280: 281: 282: 283: 284: 285: 286: 287: 288: 289: 290: 291: 292: 293: 294: 295: 296: 297: 298: 299: 300: 301: 302: 303: 304: 305: 306: 307: 308: 309: 310: 311: 312: 313: 314: 315: 316: 317: 318: 319: 320: 321: 322: 323: 324: 325: 326: 327: 328: 329: 330: 331: 332: 333: 334: 335: 336: 337: 338: 339: 340: 341: 342: 343: 344: 345: 346: 347: 348: 349: 350: 351: 352: 353: 354: 355: 356: 357: 358: 359: 360: 361: 362: 363: 364: 365: 366: 367: 368: 369: 370: 371: 372: 373: 374: 375: 376: 377: 378: 379: 380: 381: 382: 383: 384: 385: 386: 387: 388: 389: 390: 391: 392: 393: 394: 395: 396: 397: 398: 399: 400: 401: 402: 403: 404: 405: 406: 407: 408: 409: 410: 411: 412: 413: 414: 415: 416: 417: 418: 419: 420: 421: 422: 423: 424: 425: 426: 427: 428: 429: 430: 431: 432: 433: 434: 435: 436: 437: 438: 439: 440: 441: 442: 443: 444: 445: 446: 447:
<?php
/**
* MvcCore
*
* This source file is subject to the BSD 3 License
* For the full copyright and license information, please view
* the LICENSE.md file that are distributed with this source code.
*
* @copyright Copyright (c) 2016 Tom FlĂdr (https://github.com/mvccore/mvccore)
* @license https://mvccore.github.io/docs/mvccore/4.0.0/LICENCE.md
*/
namespace MvcCore\Ext;
class Auth {
/**
* MvcCore Extension - Auth - version:
* Comparation by PHP function version_compare();
* @see http://php.net/manual/en/function.version-compare.php
*/
const VERSION = '4.2.0';
/**
* Singleton instance of authentication extension service.
* @var \MvcCore\Ext\Auth
*/
protected static $instance = NULL;
/**
* User model isntace or null if user is not authenticated in session.
* @var \MvcCore\Ext\Auth\Virtual\User
*/
protected $user = NULL;
/**
* If user is authenticated in session, there is scompleted
* sign in form, else there is sign out form.
* @var \MvcCore\Ext\Auth\Virtual\Form
*/
protected $form = NULL;
/**
* Authentication configuration, there is possible to change
* any configuration option ne by one by any setter method
* multiple values or by \MvcCore\Ext\Auth::GetInstance()->Configure([...]) method.
* Config array is retyped to stdClass in __constructor().
* @var \stdClass
*/
protected $config = array(
'expirationSeconds' => 600, // 10 minutes
'userClass' => '\User',
'controllerClass' => '\Controller',
'signInFormClass' => '\SignInForm',
'signOutFormClass' => '\SignOutForm',
'signedInUrl' => '',
'signedOutUrl' => '',
'errorUrl' => '',
'signInRoute' => "#^/signin#",
'signOutRoute' => "#^/signout#",
'passwordHashSalt' => 'S3F8OI2P3X6ER1F6XY2Q9ZCY',
'translator' => NULL,
);
/**
* If true, authentication service allready try to load
* user from session and authentication detection os not
* necessary to run again. False by default.
* @var bool
*/
protected $userInitialized = FALSE;
/**
* Return singleton instance. If instance exists, return existing instance,
* if not, create new Auth service instance, store it and return it.
* @return \MvcCore\Ext\Auth
*/
public static function GetInstance () {
if (is_null(static::$instance)) {
static::$instance = new static();
}
return static::$instance;
}
/**
* Create new Auth service instance.
* For each configuration item- check if it is class definition
* and if it is, complete whole class definition.
*/
public function __construct () {
foreach ($this->config as $key => & $value) {
if (strpos($key, 'Class') !== FALSE) {
$value = __CLASS__ . $value;
}
}
$this->config = (object) $this->config;
}
/**
* Return configuration object.
* @return \stdClass
*/
public function & GetConfig () {
return $this->config;
}
/**
* Set up authorization service configuration.
* Each array key must have key by default configuration item.
* If configuration item is class, it's checked if it exists.
* @param array $config
* @return \MvcCore\Ext\Auth
*/
public function Configure ($config = array()) {
foreach ($config as $key => $value) {
if (isset($this->config->$key)) {
if (strpos($key, 'Class') !== FALSE) {
$this->_checkClass($value);
}
$this->config->$key = $value;
}
}
return $this;
}
/**
* Set authorization expiration seconds, 10 minutes by default.
* @param int $expirationSeconds
* @return \MvcCore\Ext\Auth
*/
public function SetExpirationSeconds ($expirationSeconds = 600) {
$this->config->expirationSeconds = $expirationSeconds;
return $this;
}
/**
* Set user's passwords hash salt, put here any string, every request the same.
* @param string $passwordHashSalt
* @return \MvcCore\Ext\Auth
*/
public function SetPasswordHashSalt ($passwordHashSalt = '') {
$this->config->passwordHashSalt = $passwordHashSalt;
return $this;
}
/**
* Set authorization service user class
* to get store username from session stored from previous
* requests for 10 minutes by default, by sign in action to compare
* sender credentials with any user from your custom place
* and by sign out action to remove username from session.
* It has to extend \MvcCore\Ext\Auth\Virtual\User.
* @param string $userClass
* @return \MvcCore\Ext\Auth
*/
public function SetUserClass ($userClass = '') {
$this->config->userClass = $this->_checkClass($userClass);
return $this;
}
/**
* Set authorization service controller class
* to handle signin and signout actions,
* it has to extend \MvcCore\Ext\Auth\Virtual\Controller.
* @param string $controllerClass
* @return \MvcCore\Ext\Auth
*/
public function SetControllerClass ($controllerClass = '') {
$this->config->controllerClass = $this->_checkClass($controllerClass);
return $this;
}
/**
* Set authorization service sign in form class,
* to create, render and submit sign in user.
* it has to implement \MvcCore\Ext\Auth\Virtual\Form.
* @param string $signInFormClass
* @return \MvcCore\Ext\Auth
*/
public function SetSignInFormClass ($signInFormClass = '') {
$this->config->signInFormClass = $this->_checkClass($signInFormClass);
return $this;
}
/**
* Set authorization service sign out form class,
* to create, render and submit sign out user.
* it has to implement \MvcCore\Ext\Auth\Virtual\Form.
* @param string $signInFormClass
* @return \MvcCore\Ext\Auth
*/
public function SetSignOutFormClass ($signOutFormClass = '') {
$this->config->signOutFormClass = $this->_checkClass($signOutFormClass);
return $this;
}
/**
* Set translator callable if you want to translate
* sign in and sign out forms labels, placeholders and error messages.
* @param callable $translator
* @return \MvcCore\Ext\Auth
*/
public function SetTranslator (callable $translator = NULL) {
$this->config->translator = $translator;
return $this;
}
/**
* Set url to redirect user after sign in process was successfull.
* By default signed in url is the same as current request url,
* internaly configured by default authentication service pre request handler.
* @param string $signedInUrl
* @return \MvcCore\Ext\Auth
*/
public function SetSignedInUrl ($signedInUrl ='') {
$this->config->signedInUrl = $signedInUrl;
return $this;
}
/**
* Set url to redirect user after sign out process was successfull.
* By default signed out url is the same as current request url,
* internaly configured by default authentication service pre request handler.
* @param string $signedOutUrl
* @return \MvcCore\Ext\Auth
*/
public function SetSignedOutUrl ($signedOutUrl ='') {
$this->config->signedOutUrl = $signedOutUrl;
return $this;
}
/**
* Set url to redirect user after sign in or sign out process was
* not successfull. By default signed in/out error url is the same as
* current request url, internaly configured by default
* authentication service pre request handler.
* @param string $errorUrl
* @return \MvcCore\Ext\Auth
*/
public function SetErrorUrl ($errorUrl ='') {
$this->config->errorUrl = $errorUrl;
return $this;
}
/**
* Set sign in route, where to navigate user browser after
* user clicks on submit button in sign in form and
* where to run authentication process.
* Route shoud be any pattern string without any groups,
* or route configuration array/stdClass or \MvcCore\Route
* instance. Sign in route is prepended before all routes
* in default service preroute handler.
* @param string|array|\stdClass|\MvcCore\Route $signInRoute
* @return \MvcCore\Ext\Auth
*/
public function SetSignInRoute ($signInRoute = NULL) {
$this->config->signInRoute = $signInRoute;
return $this;
}
/**
* Set sign out route, where to navigate user browser after
* user clicks on submit button in sign out form and
* where to run deauthentication process.
* Route shoud be any pattern string without any groups,
* or route configuration array/stdClass or \MvcCore\Route
* instance. Sign out route is prepended before all routes
* in default service preroute handler.
* @param string|array|\stdClass|\MvcCore\Route $signInRoute
* @return \MvcCore\Ext\Auth
*/
public function SetSignOutRoute ($signOutRoute = NULL) {
$this->config->signOutRoute = $signOutRoute;
return $this;
}
/**
* Return TRUE if user is authenticated/signed in.
* If user is not loaded yet, load the user internaly by $auth->GetUser();
* to start session and try to load user by session username record.
* @return bool
*/
public function IsAuthenticated () {
return !is_null($this->GetUser());
}
/**
* Get authenticated user instance reference or null if user is not authenticated.
* If user is not loaded yet, load the user internaly by $auth->GetUser();
* to start session and try to load user by session username record.
* @return \MvcCore\Ext\Auth\Virtual\User
*/
public function & GetUser () {
if (!$this->userInitialized && is_null($this->user)) {
$userClass = $this->config->userClass;
$this->user = $userClass::GetUserBySession();
$this->userInitialized = TRUE;
}
return $this->user;
}
/**
* Set user instance by you custom external authorization service.
* If user instance is not null, set internal $auth->userInitialized property
* to TRUE to not load user internaly again.
* @param \MvcCore\Ext\Auth\Virtual\User $user
* @return \MvcCore\Ext\Auth
*/
public function SetUser (\MvcCore\Ext\Auth\Virtual\User & $user) {
$this->user = $user;
if (!is_null($user)) $this->userInitialized = TRUE;
return $this;
}
/**
* Return completed signin/signout form instance.
* Form instance completiion is processed only once,
* created instance is stored in $auth->form property.
* This method is always called by you, your application
* to set form into you custom template to render it for user.
* If user is not authenticated, sign in form is returned and
* if user is authenticated, opposite sign out form is returned.
* @return \MvcCore\Ext\Auth\SignInForm|\MvcCore\Ext\Auth\SignOutForm|mixed
*/
public function & GetForm () {
if (is_null($this->form)) {
$controller = \MvcCore::GetInstance()->GetController();
if ($this->IsAuthenticated()) {
$this->form = new \MvcCore\Ext\Auth\SignOutForm($controller);
$this->form->Action = \MvcCore::GetInstance()->Url($this->config->signOutRoute->Name);
$this->form->SuccessUrl = $this->config->signedOutUrl;
} else {
$this->form = new \MvcCore\Ext\Auth\SignInForm($controller);
$this->form->Action = \MvcCore::GetInstance()->Url($this->config->signInRoute->Name);
$this->form->SuccessUrl = $this->config->signedInUrl;
}
$this->form->ErrorUrl = $this->config->errorUrl;
$this->form->SetTranslator($this->config->translator);
}
return $this->form;
}
/**
* Set sign in/sign out form instance.
* Use this method only if you need sometimes to
* complete different form to render.
* @param \MvcCore\Ext\Auth\Virtual\Form $form
* @return \MvcCore\Ext\Auth
*/
public function SetForm (& $form) {
$this->form = $form;
return $this;
}
/**
* Initialize necessary authentication service handlers.
* Call this method always in Bootstrap before request is routed by:
* MvcCore\Ext\Auth::GetInstance()->Init();
* @return \MvcCore\Ext\Auth
*/
public function Init () {
// add sing in or sing out forms routes, complete form success and error addresses
\MvcCore::AddPreRouteHandler(function (\MvcCore\Request & $request) {
$this->PrepareHandler($request);
});
return $this;
}
/**
* Process necessary operations before request is routed.
* This method is called internaly by default and it's called
* by \MvcCore pre route handler initialized in $auth->Init(); method.
*
* - Try to load user by stored session username from previous requests.
*
* - If controller class begins with substring containing this
* authentication class name, then it is obvious that controller
* has to have in route definition full class name defined by slash
* character in class name begin - so correct this controller class
* name if necessary to set up routes properly immediately on lines bellow.
* - If configured singin/out routes are still strings only, create
* from those strings new \MvcCore\Route instances into the same config
* place to add them into router immediately on lines bellow.
*
* - Set up sign in form success url, sign out form success url and error
* url for both ign in/out forms, as current request url by default.
* If any url is configured already, nothing is changed.
*
* - Set up sign in or sign out route into router, only route which
* is currently by authenticated/not authenticated user necessary
* to process in $router->Route() processing.
* @return void
*/
public function PrepareHandler () {
$this->GetUser();
$this->PrepareRoutes();
$this->PrepareAdresses();
$this->PrepareRouter();
}
/**
* Second prepare handler internal method:
* - If controller class begins with substring containing this
* authentication class name, then it is obvious that controller
* has to have in route definition full class name defined by slash
* character in class name begin - so correct this controller class
* name if necessary to set up routes properly immediately on lines bellow.
* - If configured singin/out routes are still strings only, create
* from those strings new \MvcCore\Route instances into the same config
* place to add them into router immediately on lines bellow.
* @return void
*/
public function PrepareRoutes () {
$authControllerClass = & $this->config->controllerClass;
if (strpos($authControllerClass, __CLASS__) === 0) {
$authControllerClass = '\\'.$authControllerClass;
}
$authenticated = $this->IsAuthenticated();
if (!$authenticated && is_string($this->config->signInRoute)) {
$this->config->signInRoute = \MvcCore\Route::GetInstance(array(
'name' => "$authControllerClass:SignIn",
'pattern' => $this->config->signInRoute,
));
}
if ($authenticated && is_string($this->config->signOutRoute)) {
$this->config->signOutRoute = \MvcCore\Route::GetInstance(array(
'name' => "$authControllerClass:SignOut",
'pattern' => $this->config->signOutRoute,
));
}
}
/**
* Third prepare handler internal method:
* - Set up sign in form success url, sign out form success url and error
* url for both ign in/out forms, as current request url by default.
* If any url is configured already, nothing is changed.
* @return void
*/
public function PrepareAdresses () {
$request = & \MvcCore::GetInstance()->GetRequest();
if (!$this->config->signedInUrl) $this->config->signedInUrl = $request->FullUrl;
if (!$this->config->signedOutUrl) $this->config->signedOutUrl = $request->FullUrl;
if (!$this->config->errorUrl) $this->config->errorUrl = $request->FullUrl;
}
/**
* Fourth prepare handler internal method:
* - Set up sign in or sign out route into router, only route which
* is currently by authenticated/not authenticated user necessary
* to process in $router->Route() processing.
* @return void
*/
public function PrepareRouter () {
if ($this->IsAuthenticated()) {
\MvcCore\Router::GetInstance()->AddRoute(
$this->config->signOutRoute, TRUE
);
} else {
\MvcCore\Router::GetInstance()->AddRoute(
$this->config->signInRoute, TRUE
);
}
}
/**
* Check if configured class exists and thrown exception if not.
* @param string $className
* @throws \Exception
* @return string
*/
private function _checkClass (& $className) {
if (!class_exists($className)) {
throw new \Exception("[".__CLASS__."] Configured class: '$className' doesn't exists.'");
}
return $className;
}
}