2007-07-19 12:40:28 +02:00
< ? php
/**
* Implements a basic security model
2012-04-12 08:02:46 +02:00
* @ package framework
2008-02-25 03:10:37 +01:00
* @ subpackage security
2007-07-19 12:40:28 +02:00
*/
class Security extends Controller {
2011-02-13 23:14:51 +01:00
static $allowed_actions = array (
'index' ,
'login' ,
'logout' ,
'basicauthlogin' ,
'lostpassword' ,
'passwordsent' ,
'changepassword' ,
2011-02-21 22:53:58 +01:00
'LoginForm' ,
'ChangePasswordForm' ,
'LostPasswordForm' ,
2011-02-13 23:14:51 +01:00
);
2007-09-14 05:12:21 +02:00
2007-07-19 12:40:28 +02:00
/**
2007-10-02 23:57:12 +02:00
* Default user name . Only used in dev - mode by { @ link setDefaultAdmin ()}
*
* @ var string
* @ see setDefaultAdmin ()
2007-07-19 12:40:28 +02:00
*/
2007-09-16 19:39:41 +02:00
protected static $default_username ;
2007-09-14 05:12:21 +02:00
2007-07-19 12:40:28 +02:00
/**
2007-10-02 23:57:12 +02:00
* Default password . Only used in dev - mode by { @ link setDefaultAdmin ()}
*
* @ var string
* @ see setDefaultAdmin ()
2007-07-19 12:40:28 +02:00
*/
2007-09-16 19:39:41 +02:00
protected static $default_password ;
2007-09-14 05:12:21 +02:00
2007-09-15 23:51:37 +02:00
/**
* If set to TRUE to prevent sharing of the session across several sites
* in the domain .
*
* @ var bool
*/
2007-07-19 12:40:28 +02:00
protected static $strictPathChecking = false ;
2007-09-14 05:12:21 +02:00
2007-09-15 23:51:37 +02:00
/**
2009-11-06 03:23:21 +01:00
* The password encryption algorithm to use by default .
* This is an arbitrary code registered through { @ link PasswordEncryptor } .
2007-09-15 23:51:37 +02:00
*
* @ var string
*/
2012-05-02 03:51:29 +02:00
protected static $encryptionAlgorithm = 'blowfish' ;
2007-09-15 23:51:37 +02:00
2008-10-08 04:00:12 +02:00
/**
* Showing " Remember me " - checkbox
* on loginform , and saving encrypted credentials to a cookie .
*
* @ var bool
*/
public static $autologin_enabled = true ;
2007-10-25 03:51:53 +02:00
/**
* Location of word list to use for generating passwords
*
* @ var string
*/
2009-09-04 01:36:45 +02:00
protected static $wordlist = './wordlist.txt' ;
2007-10-25 03:51:53 +02:00
2011-03-22 23:43:49 +01:00
static $template = 'BlankPage' ;
2008-04-05 01:04:16 +02:00
/**
* Template thats used to render the pages .
*
* @ var string
*/
public static $template_main = 'Page' ;
2008-06-25 06:05:28 +02:00
/**
* Default message set used in permission failures .
*
* @ var array | string
*/
protected static $default_message_set = '' ;
2007-10-25 03:51:53 +02:00
/**
* Get location of word list file
*/
static function get_word_list () {
return Security :: $wordlist ;
}
2008-08-11 02:14:48 +02:00
/**
* Enable or disable recording of login attempts
* through the { @ link LoginRecord } object .
*
* @ var boolean $login_recording
*/
protected static $login_recording = false ;
2010-10-15 05:43:30 +02:00
/**
* @ var boolean If set to TRUE or FALSE , { @ link database_is_ready ()}
* will always return FALSE . Used for unit testing .
*/
static $force_database_is_ready = null ;
2012-04-11 14:29:35 +02:00
/**
* When the database has once been verified as ready , it will not do the
* checks again .
*
* @ var bool
*/
2012-04-11 17:22:55 +02:00
static $database_is_ready = false ;
2012-04-11 14:29:35 +02:00
2007-10-25 03:51:53 +02:00
/**
* Set location of word list file
*
* @ param string $wordListFile Location of word list file
*/
static function set_word_list ( $wordListFile ) {
Security :: $wordlist = $wordListFile ;
}
2008-06-25 06:05:28 +02:00
/**
* Set the default message set used in permissions failures .
*
* @ param string | array $messageSet
*/
static function set_default_message_set ( $messageSet ) {
self :: $default_message_set = $messageSet ;
}
2007-09-15 23:51:37 +02:00
2007-07-19 12:40:28 +02:00
/**
2007-09-14 21:10:18 +02:00
* Register that we ' ve had a permission failure trying to view the given page
*
2007-07-19 12:40:28 +02:00
* This will redirect to a login page .
* If you don ' t provide a messageSet , a default will be used .
2007-09-14 21:10:18 +02:00
*
2007-11-07 03:33:09 +01:00
* @ param Controller $controller The controller that you were on to cause the permission
2007-09-14 21:10:18 +02:00
* failure .
* @ param string | array $messageSet The message to show to the user . This
* can be a string , or a map of different
* messages for different contexts .
* If you pass an array , you can use the
* following keys :
* - default : The default message
* - logInAgain : The message to show
* if the user has just
* logged out and the
* - alreadyLoggedIn : The message to
* show if the user
* is already logged
* in and lacks the
* permission to
* access the item .
2009-07-09 05:20:32 +02:00
*
* The alreadyLoggedIn value can contain a '%s' placeholder that will be replaced with a link
* to log in .
2007-07-19 12:40:28 +02:00
*/
2007-11-09 04:42:04 +01:00
static function permissionFailure ( $controller = null , $messageSet = null ) {
2012-06-15 05:17:32 +02:00
self :: set_ignore_disallowed_actions ( true );
2009-07-09 05:20:32 +02:00
if ( ! $controller ) $controller = Controller :: curr ();
2008-11-03 14:50:06 +01:00
if ( Director :: is_ajax ()) {
API CHANGE: Renamed conflicting classes to have an "SS_" namespace, and renamed existing "SS" namespace to "SS_". The affected classes are: HTTPRequest, HTTPResponse, Query, Database, SSBacktrace, SSCli, SSDatetime, SSDatetimeTest, SSLog, SSLogTest, SSLogEmailWriter, SSLogErrorEmailFormatter, SSLogErrorFileFormatter, SSLogFileWriter and SSZendLog.
MINOR: Replaced usage of renamed classes with the new namespaced name.
From: Andrew Short <andrewjshort@gmail.com>
git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/trunk@90075 467b73ca-7a2a-4603-9d3b-597d59a354a9
2009-10-26 04:06:31 +01:00
$response = ( $controller ) ? $controller -> getResponse () : new SS_HTTPResponse ();
2008-11-03 14:50:06 +01:00
$response -> setStatusCode ( 403 );
2010-04-12 03:45:52 +02:00
if ( ! Member :: currentUser ()) $response -> setBody ( 'NOTLOGGEDIN:' );
2008-11-03 14:50:06 +01:00
return $response ;
} else {
// Prepare the messageSet provided
if ( ! $messageSet ) {
if ( self :: $default_message_set ) {
$messageSet = self :: $default_message_set ;
} else {
$messageSet = array (
'default' => _t (
'Security.NOTEPAGESECURED' ,
2009-07-09 05:20:32 +02:00
" That page is secured. Enter your credentials below and we will send "
. " you right along. "
2008-11-03 14:50:06 +01:00
),
'alreadyLoggedIn' => _t (
'Security.ALREADYLOGGEDIN' ,
2009-07-09 05:20:32 +02:00
" You don't have access to this page. If you have another account that "
2010-10-19 02:55:20 +02:00
. " can access that page, you can log in again below. " ,
2012-04-14 00:16:22 +02:00
2009-07-09 05:20:32 +02:00
" %s will be replaced with a link to log in. "
2008-11-03 14:50:06 +01:00
),
'logInAgain' => _t (
'Security.LOGGEDOUT' ,
2009-07-09 05:20:32 +02:00
" You have been logged out. If you would like to log in again, enter "
. " your credentials below. "
2008-11-03 14:50:06 +01:00
)
);
}
2008-06-25 06:05:28 +02:00
}
2007-09-14 05:12:21 +02:00
2008-11-03 14:50:06 +01:00
if ( ! is_array ( $messageSet )) {
$messageSet = array ( 'default' => $messageSet );
}
2007-09-14 05:12:21 +02:00
2008-11-03 14:50:06 +01:00
// Work out the right message to show
2009-07-09 05:20:32 +02:00
if ( Member :: currentUser ()) {
API CHANGE: Renamed conflicting classes to have an "SS_" namespace, and renamed existing "SS" namespace to "SS_". The affected classes are: HTTPRequest, HTTPResponse, Query, Database, SSBacktrace, SSCli, SSDatetime, SSDatetimeTest, SSLog, SSLogTest, SSLogEmailWriter, SSLogErrorEmailFormatter, SSLogErrorFileFormatter, SSLogFileWriter and SSZendLog.
MINOR: Replaced usage of renamed classes with the new namespaced name.
From: Andrew Short <andrewjshort@gmail.com>
git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/trunk@90075 467b73ca-7a2a-4603-9d3b-597d59a354a9
2009-10-26 04:06:31 +01:00
$response = ( $controller ) ? $controller -> getResponse () : new SS_HTTPResponse ();
2009-07-09 05:20:32 +02:00
$response -> setStatusCode ( 403 );
2010-04-12 02:51:21 +02:00
//If 'alreadyLoggedIn' is not specified in the array, then use the default
//which should have been specified in the lines above
if ( isset ( $messageSet [ 'alreadyLoggedIn' ]))
$message = $messageSet [ 'alreadyLoggedIn' ];
else
$message = $messageSet [ 'default' ];
2010-10-19 02:55:20 +02:00
// Somewhat hackish way to render a login form with an error message.
$me = new Security ();
$form = $me -> LoginForm ();
$form -> sessionMessage ( $message , 'warning' );
Session :: set ( 'MemberLoginForm.force_message' , 1 );
$formText = $me -> login ();
2009-07-09 05:20:32 +02:00
2010-10-19 02:55:20 +02:00
$response -> setBody ( $formText );
2009-07-09 05:20:32 +02:00
return $response ;
2008-11-03 14:50:06 +01:00
} else {
$message = $messageSet [ 'default' ];
}
Session :: set ( " Security.Message.message " , $message );
Session :: set ( " Security.Message.type " , 'warning' );
Session :: set ( " BackURL " , $_SERVER [ 'REQUEST_URI' ]);
// TODO AccessLogEntry needs an extension to handle permission denied errors
// Audit logging hook
2012-05-23 11:50:02 +02:00
$controller -> extend ( 'permissionDenied' , $member );
2008-11-28 06:29:52 +01:00
2012-05-23 11:50:02 +02:00
$controller -> redirect ( " Security/login?BackURL= " . urlencode ( $_SERVER [ 'REQUEST_URI' ]));
2007-07-19 12:40:28 +02:00
}
2007-08-17 05:09:46 +02:00
return ;
2007-07-19 12:40:28 +02:00
}
2007-09-14 19:04:11 +02:00
/**
* Get the login form to process according to the submitted data
*/
2007-09-16 02:32:48 +02:00
protected function LoginForm () {
2008-04-26 08:31:52 +02:00
if ( isset ( $this -> requestParams [ 'AuthenticationMethod' ])) {
2007-09-15 02:08:23 +02:00
$authenticator = trim ( $_REQUEST [ 'AuthenticationMethod' ]);
2007-09-16 02:44:30 +02:00
$authenticators = Authenticator :: get_authenticators ();
2007-09-15 02:08:23 +02:00
if ( in_array ( $authenticator , $authenticators )) {
2008-10-08 04:00:12 +02:00
return call_user_func ( array ( $authenticator , 'get_login_form' ), $this );
2007-09-14 19:04:11 +02:00
}
}
2010-10-13 03:36:12 +02:00
else {
if ( $authenticator = Authenticator :: get_default_authenticator ()) {
return call_user_func ( array ( $authenticator , 'get_login_form' ), $this );
}
}
2007-09-15 02:08:23 +02:00
user_error ( 'Passed invalid authentication method' , E_USER_ERROR );
2007-07-19 12:40:28 +02:00
}
2007-09-14 19:04:11 +02:00
/**
* Get the login forms for all available authentication methods
*
2007-09-14 21:10:18 +02:00
* @ return array Returns an array of available login forms ( array of Form
* objects ) .
*
2007-09-14 19:04:11 +02:00
* @ todo Check how to activate / deactivate authentication methods
*/
2007-09-16 02:32:48 +02:00
protected function GetLoginForms ()
2007-09-14 19:04:11 +02:00
{
$forms = array ();
2007-09-15 02:08:23 +02:00
2007-09-16 02:44:30 +02:00
$authenticators = Authenticator :: get_authenticators ();
2007-09-15 02:08:23 +02:00
foreach ( $authenticators as $authenticator ) {
array_push ( $forms ,
2007-09-16 02:44:30 +02:00
call_user_func ( array ( $authenticator , 'get_login_form' ),
2007-09-15 02:08:23 +02:00
$this ));
}
2007-09-14 19:04:11 +02:00
return $forms ;
}
/**
* Get a link to a security action
*
* @ param string $action Name of the action
2007-09-14 21:10:18 +02:00
* @ return string Returns the link to the given action
2007-09-14 19:04:11 +02:00
*/
2012-01-10 04:23:42 +01:00
public function Link ( $action = null ) {
2007-07-19 12:40:28 +02:00
return " Security/ $action " ;
}
2007-09-14 19:04:11 +02:00
2007-07-19 12:40:28 +02:00
/**
2007-09-14 19:04:11 +02:00
* Log the currently logged in user out
*
2007-09-14 05:12:21 +02:00
* @ param bool $redirect Redirect the user back to where they came .
2007-09-14 19:04:11 +02:00
* - If it ' s false , the code calling logout () is
* responsible for sending the user where - ever
* they should go .
2007-07-19 12:40:28 +02:00
*/
2007-09-16 02:32:48 +02:00
public function logout ( $redirect = true ) {
2010-10-19 02:47:05 +02:00
$member = Member :: currentUser ();
if ( $member ) $member -> logOut ();
2007-09-14 21:10:18 +02:00
2011-10-29 06:06:19 +02:00
if ( $redirect ) $this -> redirectBack ();
2007-07-19 12:40:28 +02:00
}
2007-09-14 05:12:21 +02:00
2007-09-14 19:04:11 +02:00
/**
2007-09-14 21:10:18 +02:00
* Show the " login " page
2007-09-14 19:04:11 +02:00
*
2007-09-14 21:10:18 +02:00
* @ return string Returns the " login " page as HTML code .
2007-09-14 19:04:11 +02:00
*/
2007-09-16 02:32:48 +02:00
public function login () {
2009-03-04 04:44:11 +01:00
// Event handler for pre-login, with an option to let it break you out of the login form
$eventResults = $this -> extend ( 'onBeforeSecurityLogin' );
// If there was a redirection, return
2012-05-23 11:50:02 +02:00
if ( $this -> redirectedTo ()) return ;
API CHANGE: Renamed conflicting classes to have an "SS_" namespace, and renamed existing "SS" namespace to "SS_". The affected classes are: HTTPRequest, HTTPResponse, Query, Database, SSBacktrace, SSCli, SSDatetime, SSDatetimeTest, SSLog, SSLogTest, SSLogEmailWriter, SSLogErrorEmailFormatter, SSLogErrorFileFormatter, SSLogFileWriter and SSZendLog.
MINOR: Replaced usage of renamed classes with the new namespaced name.
From: Andrew Short <andrewjshort@gmail.com>
git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/trunk@90075 467b73ca-7a2a-4603-9d3b-597d59a354a9
2009-10-26 04:06:31 +01:00
// If there was an SS_HTTPResponse object returned, then return that
2009-03-04 04:44:11 +01:00
else if ( $eventResults ) {
foreach ( $eventResults as $result ) {
API CHANGE: Renamed conflicting classes to have an "SS_" namespace, and renamed existing "SS" namespace to "SS_". The affected classes are: HTTPRequest, HTTPResponse, Query, Database, SSBacktrace, SSCli, SSDatetime, SSDatetimeTest, SSLog, SSLogTest, SSLogEmailWriter, SSLogErrorEmailFormatter, SSLogErrorFileFormatter, SSLogFileWriter and SSZendLog.
MINOR: Replaced usage of renamed classes with the new namespaced name.
From: Andrew Short <andrewjshort@gmail.com>
git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/trunk@90075 467b73ca-7a2a-4603-9d3b-597d59a354a9
2009-10-26 04:06:31 +01:00
if ( $result instanceof SS_HTTPResponse ) return $result ;
2009-03-04 04:44:11 +01:00
}
}
2007-09-14 21:13:12 +02:00
$customCSS = project () . '/css/tabs.css' ;
if ( Director :: fileExists ( $customCSS )) {
Requirements :: css ( $customCSS );
}
2011-03-22 23:43:49 +01:00
if ( class_exists ( 'SiteTree' )) {
$tmpPage = new Page ();
$tmpPage -> Title = _t ( 'Security.LOGIN' , 'Log in' );
$tmpPage -> URLSegment = " Security " ;
// Disable ID-based caching of the log-in page by making it a random number
$tmpPage -> ID = - 1 * rand ( 1 , 10000000 );
$controller = new Page_Controller ( $tmpPage );
2012-05-01 04:43:52 +02:00
$controller -> setDataModel ( $this -> model );
2011-03-22 23:43:49 +01:00
$controller -> init ();
//Controller::$currentController = $controller;
} else {
$controller = $this ;
}
2007-07-19 12:40:28 +02:00
2007-09-27 23:13:59 +02:00
$content = '' ;
$forms = $this -> GetLoginForms ();
if ( ! count ( $forms )) {
user_error ( 'No login-forms found, please use Authenticator::register_authenticator() to add one' , E_USER_ERROR );
}
// only display tabs when more than one authenticator is provided
// to save bandwidth and reduce the amount of custom styling needed
if ( count ( $forms ) > 1 ) {
2007-09-16 19:30:12 +02:00
// Needed because the <base href=".."> in the template makes problems
// with the tabstrip library otherwise
$link_base = Director :: absoluteURL ( $this -> Link ( " login " ));
2008-11-18 02:48:37 +01:00
2012-03-24 04:38:57 +01:00
Requirements :: javascript ( FRAMEWORK_DIR . '/thirdparty/jquery/jquery.js' );
Requirements :: javascript ( FRAMEWORK_DIR . '/thirdparty/jquery-ui/jquery-ui.js' );
2009-11-21 03:22:36 +01:00
2012-03-24 04:38:57 +01:00
Requirements :: javascript ( FRAMEWORK_DIR . '/thirdparty/jquery-entwine/dist/jquery.entwine-dist.js' );
2009-11-21 03:22:36 +01:00
2011-04-25 11:24:55 +02:00
Requirements :: css ( THIRDPARTY_DIR . '/jquery-ui-themes/smoothness/jquery-ui.css' );
2009-11-21 03:22:41 +01:00
2012-03-24 04:38:57 +01:00
Requirements :: css ( FRAMEWORK_DIR . '/css/Security_login.css' );
2009-11-21 03:30:24 +01:00
2012-03-24 04:38:57 +01:00
Requirements :: javascript ( FRAMEWORK_DIR . '/javascript/TabSet.js' );
2008-11-18 02:48:37 +01:00
2007-09-16 19:30:12 +02:00
$content = '<div id="Form_EditForm">' ;
2009-11-21 03:22:36 +01:00
$content .= '<div class="ss-tabset">' ;
$content .= '<ul>' ;
2007-09-16 19:30:12 +02:00
$content_forms = '' ;
2007-09-14 19:04:11 +02:00
2007-09-16 02:44:30 +02:00
foreach ( $forms as $form ) {
2009-11-21 03:22:36 +01:00
$content .= " <li><a href= \" # { $form -> FormName () } _tab \" > { $form -> getAuthenticator () -> get_name () } </a></li> \n " ;
2007-09-16 02:44:30 +02:00
$content_forms .= '<div class="tab" id="' . $form -> FormName () . '_tab">' . $form -> forTemplate () . " </div> \n " ;
}
2009-11-21 03:22:36 +01:00
$content .= " </ul> \n " . $content_forms . " \n </div> \n </div> \n " ;
2007-09-27 23:13:59 +02:00
} else {
$content .= $forms [ 0 ] -> forTemplate ();
}
if ( strlen ( $message = Session :: get ( 'Security.Message.message' )) > 0 ) {
$message_type = Session :: get ( 'Security.Message.type' );
if ( $message_type == 'bad' ) {
$message = " <p class= \" message $message_type\ " > $message </ p > " ;
2007-09-14 21:10:18 +02:00
} else {
2007-09-27 23:13:59 +02:00
$message = " <p> $message </p> " ;
2007-09-14 21:10:18 +02:00
}
2007-07-19 12:40:28 +02:00
2007-09-27 23:13:59 +02:00
$customisedController = $controller -> customise ( array (
" Content " => $message ,
2007-12-02 22:19:54 +01:00
" Form " => $content ,
2007-09-27 23:13:59 +02:00
));
} else {
$customisedController = $controller -> customise ( array (
2009-06-18 10:22:27 +02:00
" Form " => $content ,
2007-09-27 23:13:59 +02:00
));
}
2008-11-10 04:51:35 +01:00
Session :: clear ( 'Security.Message' );
2007-09-27 23:13:59 +02:00
// custom processing
2011-03-18 03:01:09 +01:00
return $customisedController -> renderWith ( array ( 'Security_login' , 'Security' , $this -> stat ( 'template_main' ), 'BlankPage' ));
2007-07-19 12:40:28 +02:00
}
2007-10-02 06:32:11 +02:00
function basicauthlogin () {
$member = BasicAuth :: requireLogin ( " SilverStripe login " , 'ADMIN' );
$member -> LogIn ();
}
2008-09-24 06:17:33 +02:00
2007-09-14 19:04:11 +02:00
/**
2007-09-14 21:10:18 +02:00
* Show the " lost password " page
2007-09-14 19:04:11 +02:00
*
2007-09-16 02:32:48 +02:00
* @ return string Returns the " lost password " page as HTML code .
2007-09-14 19:04:11 +02:00
*/
2007-09-16 02:32:48 +02:00
public function lostpassword () {
2011-03-22 23:43:49 +01:00
if ( class_exists ( 'SiteTree' )) {
$tmpPage = new Page ();
$tmpPage -> Title = _t ( 'Security.LOSTPASSWORDHEADER' , 'Lost Password' );
$tmpPage -> URLSegment = 'Security' ;
$tmpPage -> ID = - 1 ; // Set the page ID to -1 so we dont get the top level pages as its children
$controller = new Page_Controller ( $tmpPage );
$controller -> init ();
} else {
$controller = $this ;
}
2007-09-14 05:12:21 +02:00
2007-07-19 12:40:28 +02:00
$customisedController = $controller -> customise ( array (
2007-10-25 04:47:45 +02:00
'Content' =>
'<p>' .
_t (
'Security.NOTERESETPASSWORD' ,
'Enter your e-mail address and we will send you a link with which you can reset your password'
) .
'</p>' ,
2007-09-16 02:32:48 +02:00
'Form' => $this -> LostPasswordForm (),
2007-07-19 12:40:28 +02:00
));
2007-08-23 08:43:40 +02:00
//Controller::$currentController = $controller;
2011-03-18 03:01:09 +01:00
return $customisedController -> renderWith ( array ( 'Security_lostpassword' , 'Security' , $this -> stat ( 'template_main' ), 'BlankPage' ));
2007-07-19 12:40:28 +02:00
}
2007-09-14 19:04:11 +02:00
2007-09-16 02:32:48 +02:00
/**
* Factory method for the lost password form
*
* @ return Form Returns the lost password form
*/
public function LostPasswordForm () {
2012-04-04 16:59:30 +02:00
return MemberLoginForm :: create ( $this ,
2008-10-16 13:08:52 +02:00
'LostPasswordForm' ,
2011-05-11 09:51:54 +02:00
new FieldList (
2009-02-02 00:49:53 +01:00
new EmailField ( 'Email' , _t ( 'Member.EMAIL' , 'Email' ))
2008-10-16 13:08:52 +02:00
),
2011-05-11 09:51:54 +02:00
new FieldList (
2008-10-16 13:08:52 +02:00
new FormAction (
'forgotPassword' ,
_t ( 'Security.BUTTONSEND' , 'Send me the password reset link' )
)
),
false
);
2007-09-16 02:32:48 +02:00
}
2007-09-14 19:04:11 +02:00
/**
2008-10-24 04:23:53 +02:00
* Show the " password sent " page , after a user has requested
* to reset their password .
2007-09-14 19:04:11 +02:00
*
API CHANGE: Renamed conflicting classes to have an "SS_" namespace, and renamed existing "SS" namespace to "SS_". The affected classes are: HTTPRequest, HTTPResponse, Query, Database, SSBacktrace, SSCli, SSDatetime, SSDatetimeTest, SSLog, SSLogTest, SSLogEmailWriter, SSLogErrorEmailFormatter, SSLogErrorFileFormatter, SSLogFileWriter and SSZendLog.
MINOR: Replaced usage of renamed classes with the new namespaced name.
From: Andrew Short <andrewjshort@gmail.com>
git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/trunk@90075 467b73ca-7a2a-4603-9d3b-597d59a354a9
2009-10-26 04:06:31 +01:00
* @ param SS_HTTPRequest $request The SS_HTTPRequest for this action .
2007-09-14 21:10:18 +02:00
* @ return string Returns the " password sent " page as HTML code .
2007-09-14 19:04:11 +02:00
*/
2008-09-19 01:00:36 +02:00
public function passwordsent ( $request ) {
2011-03-22 23:43:49 +01:00
if ( class_exists ( 'SiteTree' )) {
$tmpPage = new Page ();
$tmpPage -> Title = _t ( 'Security.LOSTPASSWORDHEADER' );
$tmpPage -> URLSegment = 'Security' ;
$tmpPage -> ID = - 1 ; // Set the page ID to -1 so we dont get the top level pages as its children
$controller = new Page_Controller ( $tmpPage );
$controller -> init ();
} else {
$controller = $this ;
}
2007-09-14 05:12:21 +02:00
2011-03-25 00:01:50 +01:00
$email = Convert :: raw2xml ( rawurldecode ( $request -> param ( 'ID' )) . '.' . $request -> getExtension ());
2007-07-19 12:40:28 +02:00
$customisedController = $controller -> customise ( array (
2012-05-01 21:44:54 +02:00
'Title' => _t ( 'Security.PASSWORDSENTHEADER' , " Password reset link sent to ' { email}' " , array ( 'email' => $email )),
2007-09-16 02:32:48 +02:00
'Content' =>
2007-10-25 04:47:45 +02:00
" <p> " .
2012-05-01 21:44:54 +02:00
_t ( 'Security.PASSWORDSENTTEXT' , " Thank you! A reset link has been sent to ' { email}', provided an account exists for this email address. " , array ( 'email' => $email )) .
2007-10-25 04:47:45 +02:00
" </p> " ,
2010-10-15 05:06:53 +02:00
'Email' => $email
2007-07-19 12:40:28 +02:00
));
2007-08-23 08:43:40 +02:00
//Controller::$currentController = $controller;
2011-03-18 03:01:09 +01:00
return $customisedController -> renderWith ( array ( 'Security_passwordsent' , 'Security' , $this -> stat ( 'template_main' ), 'BlankPage' ));
2007-07-19 12:40:28 +02:00
}
2007-09-14 05:12:21 +02:00
2007-09-16 02:32:48 +02:00
/**
* Create a link to the password reset form
*
* @ param string $autoLoginHash The auto login hash
*/
public static function getPasswordResetLink ( $autoLoginHash ) {
$autoLoginHash = urldecode ( $autoLoginHash );
2012-04-12 02:09:39 +02:00
$selfControllerClass = __CLASS__ ;
$selfController = new $selfControllerClass ();
return $selfController -> Link ( 'changepassword' ) . " ?h= $autoLoginHash " ;
2007-09-16 02:32:48 +02:00
}
2007-09-16 19:39:41 +02:00
2007-09-16 02:32:48 +02:00
/**
2010-12-09 22:18:49 +01:00
* Show the " change password " page .
* This page can either be called directly by logged - in users
* ( in which case they need to provide their old password ),
* or through a link emailed through { @ link lostpassword ()} .
* In this case no old password is required , authentication is ensured
* through the Member . AutoLoginHash property .
*
* @ see ChangePasswordForm
2007-09-16 02:32:48 +02:00
*
* @ return string Returns the " change password " page as HTML code .
*/
public function changepassword () {
2011-03-22 23:43:49 +01:00
if ( class_exists ( 'SiteTree' )) {
$tmpPage = new Page ();
$tmpPage -> Title = _t ( 'Security.CHANGEPASSWORDHEADER' , 'Change your password' );
$tmpPage -> URLSegment = 'Security' ;
$tmpPage -> ID = - 1 ; // Set the page ID to -1 so we dont get the top level pages as its children
$controller = new Page_Controller ( $tmpPage );
$controller -> init ();
} else {
$controller = $this ;
}
2007-09-16 02:32:48 +02:00
2010-12-09 22:18:49 +01:00
// First load with hash: Redirect to same URL without hash to avoid referer leakage
2008-08-11 07:18:18 +02:00
if ( isset ( $_REQUEST [ 'h' ]) && Member :: member_from_autologinhash ( $_REQUEST [ 'h' ])) {
2010-12-09 22:18:49 +01:00
// The auto login hash is valid, store it for the change password form.
// Temporary value, unset in ChangePasswordForm
2007-09-16 02:32:48 +02:00
Session :: set ( 'AutoLoginHash' , $_REQUEST [ 'h' ]);
2010-12-09 22:18:49 +01:00
return $this -> redirect ( $this -> Link ( 'changepassword' ));
// Redirection target after "First load with hash"
} elseif ( Session :: get ( 'AutoLoginHash' )) {
2007-09-16 02:32:48 +02:00
$customisedController = $controller -> customise ( array (
'Content' =>
2007-10-25 04:47:45 +02:00
'<p>' .
_t ( 'Security.ENTERNEWPASSWORD' , 'Please enter a new password.' ) .
'</p>' ,
2007-09-16 02:32:48 +02:00
'Form' => $this -> ChangePasswordForm (),
));
} elseif ( Member :: currentUser ()) {
// let a logged in user change his password
$customisedController = $controller -> customise ( array (
2007-10-25 04:47:45 +02:00
'Content' => '<p>' . _t ( 'Security.CHANGEPASSWORDBELOW' , 'You can change your password below.' ) . '</p>' ,
2007-09-16 02:32:48 +02:00
'Form' => $this -> ChangePasswordForm ()));
} else {
// show an error message if the auto login hash is invalid and the
// user is not logged in
if ( isset ( $_REQUEST [ 'h' ])) {
2007-10-25 04:47:45 +02:00
$customisedController = $controller -> customise (
array ( 'Content' =>
2012-05-01 21:44:54 +02:00
_t (
'Security.NOTERESETLINKINVALID' ,
'<p>The password reset link is invalid or expired.</p><p>You can request a new one <a href="{link1}">here</a> or change your password after you <a href="{link2}">logged in</a>.</p>' ,
array ( 'link1' => $this -> Link ( 'lostpassword' ), 'link2' => $this -> link ( 'login' ))
2007-10-25 04:47:45 +02:00
)
)
);
2007-09-16 02:32:48 +02:00
} else {
2007-10-25 04:47:45 +02:00
self :: permissionFailure (
$this ,
_t ( 'Security.ERRORPASSWORDPERMISSION' , 'You must be logged in in order to change your password!' )
);
2007-09-16 17:31:44 +02:00
return ;
2007-09-16 02:32:48 +02:00
}
}
2011-03-18 03:01:09 +01:00
return $customisedController -> renderWith ( array ( 'Security_changepassword' , 'Security' , $this -> stat ( 'template_main' ), 'BlankPage' ));
2007-09-16 02:32:48 +02:00
}
2008-09-24 06:17:33 +02:00
2007-09-14 19:04:11 +02:00
/**
2007-09-14 21:10:18 +02:00
* Factory method for the lost password form
2007-09-14 19:04:11 +02:00
*
2007-09-14 21:10:18 +02:00
* @ return Form Returns the lost password form
2007-09-14 19:04:11 +02:00
*/
2007-09-16 02:32:48 +02:00
public function ChangePasswordForm () {
return new ChangePasswordForm ( $this , 'ChangePasswordForm' );
2007-07-19 12:40:28 +02:00
}
/**
2010-04-12 23:16:26 +02:00
* Return an existing member with administrator privileges , or create one of necessary .
*
* Will create a default 'Administrators' group if no group is found
* with an ADMIN permission . Will create a new 'Admin' member with administrative permissions
* if no existing Member with these permissions is found .
*
* Important : Any newly created administrator accounts will NOT have valid
* login credentials ( Email / Password properties ), which means they can ' t be used for login
* purposes outside of any default credentials set through { @ link Security :: setDefaultAdmin ()} .
*
* @ return Member
2007-07-19 12:40:28 +02:00
*/
2010-04-12 23:16:26 +02:00
static function findAnAdministrator () {
2009-12-16 06:43:35 +01:00
// coupling to subsites module
2010-05-25 05:45:27 +02:00
$origSubsite = null ;
if ( is_callable ( 'Subsite::changeSubsite' )) {
$origSubsite = Subsite :: currentSubsiteID ();
Subsite :: changeSubsite ( 0 );
}
2009-11-22 06:16:38 +01:00
$member = null ;
2009-12-16 06:43:35 +01:00
// find a group with ADMIN permission
2011-10-29 06:47:19 +02:00
$adminGroup = DataObject :: get ( 'Group' ) -> where ( " \" Permission \" . \" Code \" = 'ADMIN' " )
-> sort ( " \" Group \" . \" ID \" " ) -> innerJoin ( " Permission " , " \" Group \" . \" ID \" = \" Permission \" . \" GroupID \" " ) -> First ();
2010-05-25 05:45:27 +02:00
if ( is_callable ( 'Subsite::changeSubsite' )) {
Subsite :: changeSubsite ( $origSubsite );
}
2009-11-22 06:16:38 +01:00
2009-12-16 06:43:35 +01:00
if ( $adminGroup ) {
2009-11-22 06:16:38 +01:00
$member = $adminGroup -> Members () -> First ();
2007-07-19 12:40:28 +02:00
}
2007-09-14 05:12:21 +02:00
2007-07-19 12:40:28 +02:00
if ( ! $adminGroup ) {
2010-08-03 03:05:27 +02:00
singleton ( 'Group' ) -> requireDefaultRecords ();
2007-07-19 12:40:28 +02:00
}
2009-11-22 06:16:38 +01:00
if ( ! $member ) {
2010-08-03 03:05:27 +02:00
singleton ( 'Member' ) -> requireDefaultRecords ();
2009-11-22 06:16:38 +01:00
$member = Permission :: get_members_by_permission ( 'ADMIN' ) -> First ();
2007-07-19 12:40:28 +02:00
}
2007-09-14 05:12:21 +02:00
return $member ;
2007-07-19 12:40:28 +02:00
}
2007-09-14 05:12:21 +02:00
2007-09-14 19:04:11 +02:00
2007-07-19 12:40:28 +02:00
/**
2007-10-02 23:57:12 +02:00
* Set a default admin in dev - mode
*
2010-04-12 23:16:26 +02:00
* This will set a static default - admin which is not existing
2007-09-14 21:10:18 +02:00
* as a database - record . By this workaround we can test pages in dev - mode
* with a unified login . Submitted login - credentials are first checked
2010-04-12 23:16:26 +02:00
* against this static information in { @ link Security :: authenticate ()} .
2007-09-14 05:12:21 +02:00
*
2007-10-02 23:57:12 +02:00
* @ param string $username The user name
2010-04-12 23:16:26 +02:00
* @ param string $password The password ( in cleartext )
2007-07-19 12:40:28 +02:00
*/
2007-09-16 02:32:48 +02:00
public static function setDefaultAdmin ( $username , $password ) {
2007-09-16 19:39:41 +02:00
// don't overwrite if already set
2007-10-02 23:57:12 +02:00
if ( self :: $default_username || self :: $default_password ) {
return false ;
}
2007-09-14 05:12:21 +02:00
2007-09-16 19:39:41 +02:00
self :: $default_username = $username ;
self :: $default_password = $password ;
2007-07-19 12:40:28 +02:00
}
2007-09-17 23:51:42 +02:00
/**
* Checks if the passed credentials are matching the default - admin .
* Compares cleartext - password set through Security :: setDefaultAdmin () .
*
* @ param string $username
* @ param string $password
* @ return bool
*/
2007-09-27 23:13:59 +02:00
public static function check_default_admin ( $username , $password ) {
2007-09-17 23:51:42 +02:00
return (
2007-10-23 03:18:39 +02:00
self :: $default_username === $username
&& self :: $default_password === $password
&& self :: has_default_admin ()
2007-09-17 23:51:42 +02:00
);
}
2007-10-23 03:18:39 +02:00
/**
* Check that the default admin account has been set .
*/
public static function has_default_admin () {
2007-10-24 03:15:53 +02:00
return ! empty ( self :: $default_username ) && ! empty ( self :: $default_password );
2007-10-23 03:18:39 +02:00
}
2007-09-14 19:04:11 +02:00
2007-07-19 12:40:28 +02:00
/**
2007-09-14 21:10:18 +02:00
* Set strict path checking
*
2007-09-16 02:44:30 +02:00
* This prevents sharing of the session across several sites in the
* domain .
2007-09-14 05:12:21 +02:00
*
2007-09-14 21:10:18 +02:00
* @ param boolean $strictPathChecking To enable or disable strict patch
* checking .
2007-07-19 12:40:28 +02:00
*/
2007-09-16 02:32:48 +02:00
public static function setStrictPathChecking ( $strictPathChecking ) {
2007-07-19 12:40:28 +02:00
self :: $strictPathChecking = $strictPathChecking ;
}
2007-09-14 05:12:21 +02:00
2007-09-14 19:04:11 +02:00
/**
2007-09-14 21:10:18 +02:00
* Get strict path checking
2007-09-14 19:04:11 +02:00
*
2007-09-14 21:10:18 +02:00
* @ return boolean Status of strict path checking
2007-09-14 19:04:11 +02:00
*/
2007-09-16 02:32:48 +02:00
public static function getStrictPathChecking () {
2007-07-19 12:40:28 +02:00
return self :: $strictPathChecking ;
}
2007-09-15 23:51:37 +02:00
/**
* Set the password encryption algorithm
*
* @ param string $algorithm One of the available password encryption
2009-11-06 03:23:21 +01:00
* algorithms determined by { @ link Security :: get_encryption_algorithms ()}
* @ return bool Returns TRUE if the passed algorithm was valid , otherwise FALSE .
*/
public static function set_password_encryption_algorithm ( $algorithm ) {
if ( ! array_key_exists ( $algorithm , PasswordEncryptor :: get_encryptors ())) return false ;
2007-09-15 23:51:37 +02:00
self :: $encryptionAlgorithm = $algorithm ;
return true ;
}
2009-11-06 03:23:21 +01:00
2007-09-15 23:51:37 +02:00
/**
2009-11-06 03:23:21 +01:00
* @ return String
2007-09-15 23:51:37 +02:00
*/
2009-11-06 03:23:21 +01:00
public static function get_password_encryption_algorithm () {
return self :: $encryptionAlgorithm ;
2007-09-15 23:51:37 +02:00
}
/**
2009-11-06 03:23:21 +01:00
* Encrypt a password according to the current password encryption settings .
2007-09-15 23:51:37 +02:00
* If the settings are so that passwords shouldn ' t be encrypted , the
* result is simple the clear text password with an empty salt except when
* a custom algorithm ( $algorithm parameter ) was passed .
*
* @ param string $password The password to encrypt
* @ param string $salt Optional : The salt to use . If it is not passed , but
2009-11-06 03:23:21 +01:00
* needed , the method will automatically create a
* random salt that will then be returned as return value .
2007-09-16 16:27:27 +02:00
* @ param string $algorithm Optional : Use another algorithm to encrypt the
2009-11-06 03:23:21 +01:00
* password ( so that the encryption algorithm can be changed over the time ) .
* @ param Member $member Optional
2007-09-15 23:51:37 +02:00
* @ return mixed Returns an associative array containing the encrypted
2009-11-06 03:23:21 +01:00
* password and the used salt in the form :
* < code >
* array (
* 'password' => string ,
* 'salt' => string ,
* 'algorithm' => string ,
* 'encryptor' => PasswordEncryptor instance
* )
* </ code >
* If the passed algorithm is invalid , FALSE will be returned .
2007-09-15 23:51:37 +02:00
*
* @ see encrypt_passwords ()
* @ see set_password_encryption_algorithm ()
2009-11-06 03:23:21 +01:00
*/
static function encrypt_password ( $password , $salt = null , $algorithm = null , $member = null ) {
if (
// if the password is empty, don't encrypt
strlen ( trim ( $password )) == 0
// if no algorithm is provided and no default is set, don't encrypt
2012-03-09 03:31:33 +01:00
|| ( ! $algorithm )
2009-11-06 03:23:21 +01:00
) {
$algorithm = 'none' ;
2007-09-15 23:51:37 +02:00
} else {
2009-11-06 03:23:21 +01:00
// Fall back to the default encryption algorithm
if ( ! $algorithm ) $algorithm = self :: $encryptionAlgorithm ;
}
2008-03-11 02:30:49 +01:00
2009-11-06 03:23:21 +01:00
$e = PasswordEncryptor :: create_for_algorithm ( $algorithm );
2007-09-15 23:51:37 +02:00
2009-11-06 03:23:13 +01:00
// New salts will only need to be generated if the password is hashed for the first time
$salt = ( $salt ) ? $salt : $e -> salt ( $password );
return array (
2010-04-12 07:00:05 +02:00
'password' => $e -> encrypt ( $password , $salt , $member ),
2009-11-06 03:23:13 +01:00
'salt' => $salt ,
'algorithm' => $algorithm ,
'encryptor' => $e
);
2007-09-15 23:51:37 +02:00
}
2007-11-15 23:29:10 +01:00
/**
* Checks the database is in a state to perform security checks .
2010-04-12 23:17:07 +02:00
* See { @ link DatabaseAdmin -> init ()} for more information .
*
2007-11-15 23:29:10 +01:00
* @ return bool
*/
public static function database_is_ready () {
2010-10-15 05:43:30 +02:00
// Used for unit tests
if ( self :: $force_database_is_ready !== NULL ) return self :: $force_database_is_ready ;
2012-04-11 14:29:35 +02:00
if ( self :: $database_is_ready ) return self :: $database_is_ready ;
2010-10-15 05:43:30 +02:00
2009-01-08 00:00:54 +01:00
$requiredTables = ClassInfo :: dataClassesFor ( 'Member' );
$requiredTables [] = 'Group' ;
$requiredTables [] = 'Permission' ;
2010-04-12 23:17:07 +02:00
foreach ( $requiredTables as $table ) {
// if any of the tables aren't created in the database
if ( ! ClassInfo :: hasTable ( $table )) return false ;
2012-03-20 23:53:38 +01:00
// HACK: DataExtensions aren't applied until a class is instantiated for
// the first time, so create an instance here.
singleton ( $table );
2010-04-12 23:17:07 +02:00
// if any of the tables don't have all fields mapped as table columns
$dbFields = DB :: fieldList ( $table );
if ( ! $dbFields ) return false ;
$objFields = DataObject :: database_fields ( $table );
$missingFields = array_diff_key ( $objFields , $dbFields );
if ( $missingFields ) return false ;
}
2012-04-11 14:29:35 +02:00
self :: $database_is_ready = true ;
2009-01-08 00:00:54 +01:00
2010-04-12 23:17:07 +02:00
return true ;
2007-11-15 23:29:10 +01:00
}
2008-08-11 02:14:48 +02:00
/**
* Enable or disable recording of login attempts
* through the { @ link LoginRecord } object .
*
* @ param boolean $bool
*/
public static function set_login_recording ( $bool ) {
self :: $login_recording = ( bool ) $bool ;
}
/**
* @ return boolean
*/
public static function login_recording () {
return self :: $login_recording ;
}
2008-09-18 05:53:36 +02:00
protected static $default_login_dest = " " ;
/**
* Set the default login dest
* This is the URL that users will be redirected to after they log in ,
* if they haven ' t logged in en route to access a secured page .
*
* By default , this is set to the homepage
*/
public static function set_default_login_dest ( $dest ) {
self :: $default_login_dest = $dest ;
}
/**
* Get the default login dest
*/
public static function default_login_dest () {
return self :: $default_login_dest ;
}
2007-09-15 23:51:37 +02:00
2012-06-15 05:17:32 +02:00
protected static $ignore_disallowed_actions = false ;
/**
* Set to true to ignore access to disallowed actions , rather than returning permission failure
* Note that this is just a flag that other code needs to check with Security :: ignore_disallowed_actions ()
* @ param $flag True or false
*/
public static function set_ignore_disallowed_actions ( $flag ) {
self :: $ignore_disallowed_actions = $flag ;
}
public static function ignore_disallowed_actions () {
return self :: $ignore_disallowed_actions ;
}
2011-03-25 00:01:50 +01:00
}