2007-07-19 12:40:28 +02:00
< ? php
/**
* Implements a basic security model
2008-02-25 03:10:37 +01:00
* @ package sapphire
* @ subpackage security
2007-07-19 12:40:28 +02:00
*/
class Security extends Controller {
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
/**
* Should passwords be stored encrypted ?
*
* @ var bool
*/
protected static $encryptPasswords = true ;
/**
* The password encryption algorithm to use if { @ link $encryptPasswords }
* is set to TRUE .
*
* @ var string
*/
protected static $encryptionAlgorithm = 'sha1' ;
/**
* Should a salt be used for the password encryption ?
*
* @ var bool
*/
protected static $useSalt = true ;
2007-10-25 03:51:53 +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
*/
protected static $wordlist = '/usr/share/silverstripe/wordlist.txt' ;
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 ;
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 .
2007-07-19 12:40:28 +02:00
*/
2007-11-09 04:42:04 +01:00
static function permissionFailure ( $controller = null , $messageSet = null ) {
2008-11-03 14:50:06 +01:00
if ( Director :: is_ajax ()) {
$response = ( $controller ) ? $controller -> getResponse () : new HTTPResponse ();
$response -> setStatusCode ( 403 );
$response -> setBody ( 'NOTLOGGEDIN:' );
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' ,
" That page is secured. Enter your credentials below and we will send you right along. "
),
'alreadyLoggedIn' => _t (
'Security.ALREADYLOGGEDIN' ,
" You don't have access to this page. If you have another account that can access that page, you can log in below. "
),
'logInAgain' => _t (
'Security.LOGGEDOUT' ,
" You have been logged out. If you would like to log in again, enter your credentials below. "
)
);
}
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
if ( Member :: currentUserID ()) {
2009-02-02 00:49:53 +01:00
$message = isset ( $messageSet [ 'alreadyLoggedIn' ]) ? $messageSet [ 'alreadyLoggedIn' ] : $messageSet [ 'default' ];
if ( $member = Member :: currentUser ()) {
$member -> logOut ();
}
2008-11-03 14:50:06 +01:00
} else if ( substr ( Director :: history (), 0 , 15 ) == 'Security/logout' ) {
2009-02-02 00:49:53 +01:00
$message = $messageSet [ 'logInAgain' ] ? $messageSet [ 'logInAgain' ] : $messageSet [ 'default' ];
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
if ( $controller ) $controller -> extend ( 'permissionDenied' , $member );
2008-11-28 06:29:52 +01:00
2009-03-10 23:17:26 +01:00
Director :: 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
}
}
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
*/
2007-09-16 02:32:48 +02:00
public static 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 ) {
2007-09-14 21:10:18 +02:00
if ( $member = Member :: currentUser ())
$member -> logOut ();
if ( $redirect )
Director :: 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
if ( Director :: redirected_to ()) return ;
// If there was an HTTPResponse object returned, then return that
else if ( $eventResults ) {
foreach ( $eventResults as $result ) {
if ( $result instanceof HTTPResponse ) return $result ;
}
}
2007-09-14 21:13:12 +02:00
$customCSS = project () . '/css/tabs.css' ;
if ( Director :: fileExists ( $customCSS )) {
Requirements :: css ( $customCSS );
}
$tmpPage = new Page ();
2008-02-25 03:10:37 +01:00
$tmpPage -> Title = _t ( 'Security.LOGIN' , 'Log in' );
2007-07-19 12:40:28 +02:00
$tmpPage -> URLSegment = " Security " ;
2007-12-02 22:19:54 +01:00
$tmpPage -> ID = - 1 ; // Set the page ID to -1 so we dont get the top level pages as its children
2007-07-19 12:40:28 +02:00
2008-08-09 05:29:30 +02:00
$controller = new Page_Controller ( $tmpPage );
2007-07-19 12:40:28 +02:00
$controller -> init ();
2007-09-15 04:05:23 +02:00
//Controller::$currentController = $controller;
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 ) {
ENHANCEMENT Introduced constants for system paths like /sapphire in preparation for a more flexible directory reorganisation. Instead of hardcoding your path, please use the following constants: BASE_PATH, BASE_URL, SAPPHIRE_DIR, SAPPHIRE_PATH, CMS_DIR, CMS_PATH, THIRDPARTY_DIR, THIRDPARTY_PATH, ASSETS_DIR, ASSETS_PATH, THEMES_DIR, THEMES_PATH
git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/trunk@63154 467b73ca-7a2a-4603-9d3b-597d59a354a9
2008-09-27 18:02:38 +02:00
Requirements :: javascript ( THIRDPARTY_DIR . " /loader.js " );
Requirements :: javascript ( THIRDPARTY_DIR . " /prototype.js " );
Requirements :: javascript ( THIRDPARTY_DIR . " /behaviour.js " );
Requirements :: javascript ( THIRDPARTY_DIR . " /prototype_improvements.js " );
Requirements :: javascript ( THIRDPARTY_DIR . " /scriptaculous/effects.js " );
Requirements :: css ( SAPPHIRE_DIR . " /css/Form.css " );
2007-09-27 23:13:59 +02:00
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
Requirements :: javascript ( THIRDPARTY_DIR . " /jquery/jquery.js " );
2008-11-22 04:33:00 +01:00
Requirements :: javascript ( THIRDPARTY_DIR . " /jquery/jquery_improvements.js " );
2008-11-18 02:48:37 +01:00
Requirements :: javascript ( THIRDPARTY_DIR . '/jquery/plugins/livequery/jquery.livequery.js' );
Requirements :: javascript ( THIRDPARTY_DIR . " /tabstrip/tabstrip.js " );
Requirements :: css ( THIRDPARTY_DIR . " /tabstrip/tabstrip.css " );
2007-09-16 19:30:12 +02:00
$content = '<div id="Form_EditForm">' ;
$content .= '<ul class="tabstrip">' ;
$content_forms = '' ;
2007-09-14 19:04:11 +02:00
2007-09-16 02:44:30 +02:00
foreach ( $forms as $form ) {
$content .= " <li><a href= \" $link_base # { $form -> FormName () } _tab \" > { $form -> getAuthenticator () -> get_name () } </a></li> \n " ;
$content_forms .= '<div class="tab" id="' . $form -> FormName () . '_tab">' . $form -> forTemplate () . " </div> \n " ;
}
$content .= " </ul> \n " . $content_forms . " \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 (
2007-12-02 22:19:54 +01:00
" Content " => $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
2009-02-02 00:49:53 +01:00
return $customisedController -> renderWith ( array ( 'Security_login' , 'Security' , $this -> stat ( 'template_main' )));
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 () {
2008-09-29 20:49:55 +02:00
Requirements :: javascript ( THIRDPARTY_DIR . '/prototype.js' );
Requirements :: javascript ( THIRDPARTY_DIR . '/behaviour.js' );
Requirements :: javascript ( THIRDPARTY_DIR . '/loader.js' );
Requirements :: javascript ( THIRDPARTY_DIR . '/prototype_improvements.js' );
Requirements :: javascript ( THIRDPARTY_DIR . '/scriptaculous/effects.js' );
2007-09-14 05:12:21 +02:00
2007-07-19 12:40:28 +02:00
$tmpPage = new Page ();
2007-10-25 04:47:45 +02:00
$tmpPage -> Title = _t ( 'Security.LOSTPASSWORDHEADER' , 'Lost Password' );
2007-09-16 02:32:48 +02:00
$tmpPage -> URLSegment = 'Security' ;
2007-07-19 12:40:28 +02:00
$controller = new Page_Controller ( $tmpPage );
2007-11-06 06:23:00 +01:00
$controller -> init ();
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;
2009-02-02 00:49:53 +01:00
return $customisedController -> renderWith ( array ( 'Security_lostpassword' , 'Security' , $this -> stat ( 'template_main' )));
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 () {
2008-10-16 13:08:52 +02:00
return new MemberLoginForm (
$this ,
'LostPasswordForm' ,
new FieldSet (
2009-02-02 00:49:53 +01:00
new EmailField ( 'Email' , _t ( 'Member.EMAIL' , 'Email' ))
2008-10-16 13:08:52 +02:00
),
new FieldSet (
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
*
2008-10-24 04:23:53 +02:00
* @ param HTTPRequest $request The 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 ) {
2008-09-29 20:49:55 +02:00
Requirements :: javascript ( THIRDPARTY_DIR . '/behaviour.js' );
Requirements :: javascript ( THIRDPARTY_DIR . '/loader.js' );
Requirements :: javascript ( THIRDPARTY_DIR . '/prototype.js' );
Requirements :: javascript ( THIRDPARTY_DIR . '/prototype_improvements.js' );
Requirements :: javascript ( THIRDPARTY_DIR . '/scriptaculous/effects.js' );
2007-09-14 05:12:21 +02:00
2007-07-19 12:40:28 +02:00
$tmpPage = new Page ();
2007-10-25 04:47:45 +02:00
$tmpPage -> Title = _t ( 'Security.LOSTPASSWORDHEADER' );
2007-09-16 02:32:48 +02:00
$tmpPage -> URLSegment = 'Security' ;
2009-03-10 23:17:26 +01:00
$tmpPage -> ID = - 1 ; // Set the page ID to -1 so we dont get the top level pages as its children
2007-07-19 12:40:28 +02:00
$controller = new Page_Controller ( $tmpPage );
2007-11-06 06:23:00 +01:00
$controller -> init ();
2007-09-14 05:12:21 +02:00
2008-12-04 23:38:32 +01:00
$email = Convert :: raw2xml ( $request -> param ( 'ID' ) . '.' . $request -> getExtension ());
2008-10-24 04:23:53 +02:00
2007-07-19 12:40:28 +02:00
$customisedController = $controller -> customise ( array (
2007-10-25 04:47:45 +02:00
'Title' => sprintf ( _t ( 'Security.PASSWORDSENTHEADER' , " Password reset link sent to '%s' " ), $email ),
2007-09-16 02:32:48 +02:00
'Content' =>
2007-10-25 04:47:45 +02:00
" <p> " .
sprintf ( _t ( 'Security.PASSWORDSENTTEXT' , " Thank you! The password reset link has been sent to '%s'. " ), $email ) .
" </p> " ,
2007-07-19 12:40:28 +02:00
));
2007-08-23 08:43:40 +02:00
//Controller::$currentController = $controller;
2009-02-02 00:49:53 +01:00
return $customisedController -> renderWith ( array ( 'Security_passwordsent' , 'Security' , $this -> stat ( 'template_main' )));
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 );
return self :: Link ( 'changepassword' ) . " ?h= $autoLoginHash " ;
}
2007-09-16 19:39:41 +02:00
2007-09-16 02:32:48 +02:00
/**
* Show the " change password " page
*
* @ return string Returns the " change password " page as HTML code .
*/
public function changepassword () {
$tmpPage = new Page ();
2007-10-25 04:47:45 +02:00
$tmpPage -> Title = _t ( 'Security.CHANGEPASSWORDHEADER' , 'Change your password' );
2007-09-16 02:32:48 +02:00
$tmpPage -> URLSegment = 'Security' ;
2009-03-10 23:17:26 +01:00
$tmpPage -> ID = - 1 ; // Set the page ID to -1 so we dont get the top level pages as its children
2007-09-16 02:32:48 +02:00
$controller = new Page_Controller ( $tmpPage );
2007-11-06 06:23:00 +01:00
$controller -> init ();
2007-09-16 02:32:48 +02:00
2008-08-11 07:18:18 +02:00
if ( isset ( $_REQUEST [ 'h' ]) && Member :: member_from_autologinhash ( $_REQUEST [ 'h' ])) {
2007-09-16 02:32:48 +02:00
// The auto login hash is valid, store it for the change password form
Session :: set ( 'AutoLoginHash' , $_REQUEST [ 'h' ]);
$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' =>
sprintf (
_t ( 'Security.NOTERESETLINKINVALID' ,
2008-10-17 17:20:31 +02:00
'<p>The password reset link is invalid or expired.</p><p>You can request a new one <a href="%s">here</a> or change your password after you <a href="%s">logged in</a>.</p>'
2007-10-25 04:47:45 +02:00
),
$this -> Link ( 'lostpassword' ),
$this -> link ( 'login' )
)
)
);
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
}
}
2007-10-25 04:38:35 +02:00
//Controller::$currentController = $controller;
2009-02-02 00:49:53 +01:00
return $customisedController -> renderWith ( array ( 'Security_changepassword' , 'Security' , $this -> stat ( 'template_main' )));
2007-09-16 02:32:48 +02:00
}
2008-09-24 06:17:33 +02:00
/**
* Security / ping can be visited with ajax to keep a session alive .
* This is used in the CMS .
*/
function ping () {
return 1 ;
}
2007-09-16 02:32:48 +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
}
/**
2007-09-14 21:10:18 +02:00
* Authenticate using the given email and password , returning the
* appropriate member object if
*
* @ return bool | Member Returns FALSE if authentication fails , otherwise
* the member object
2007-10-02 23:57:12 +02:00
* @ see setDefaultAdmin ()
2007-07-19 12:40:28 +02:00
*/
2007-09-16 02:32:48 +02:00
public static function authenticate ( $RAW_email , $RAW_password ) {
2007-07-19 12:40:28 +02:00
$SQL_email = Convert :: raw2sql ( $RAW_email );
$SQL_password = Convert :: raw2sql ( $RAW_password );
// Default login (see {@setDetaultAdmin()})
2007-09-16 19:39:41 +02:00
if (( $RAW_email == self :: $default_username ) && ( $RAW_password == self :: $default_password )
&& ! empty ( self :: $default_username ) && ! empty ( self :: $default_password )) {
2007-07-19 12:40:28 +02:00
$member = self :: findAnAdministrator ();
} else {
2008-11-24 10:31:14 +01:00
$member = DataObject :: get_one ( " Member " , " \" Email \" = ' $SQL_email ' AND \" Password \" IS NOT NULL " );
2007-12-02 22:35:30 +01:00
if ( $member && ( $member -> checkPassword ( $RAW_password ) == false )) {
$member = null ;
}
2007-07-19 12:40:28 +02:00
}
2007-09-14 05:12:21 +02:00
2007-07-19 12:40:28 +02:00
return $member ;
}
2007-09-14 19:04:11 +02:00
2007-07-19 12:40:28 +02:00
/**
* Return a member with administrator privileges
2007-09-14 21:10:18 +02:00
*
* @ return Member Returns a member object that has administrator
* privileges .
2007-07-19 12:40:28 +02:00
*/
2007-09-16 02:32:48 +02:00
static function findAnAdministrator ( $username = 'admin' , $password = 'password' ) {
2009-03-12 00:03:28 +01:00
$permission = DataObject :: get_one ( " Permission " , " \" Code \" = 'ADMIN' " , true , " \" Permission \" . \" ID \" " );
2007-09-16 17:31:44 +02:00
2007-08-16 08:36:24 +02:00
$adminGroup = null ;
2008-11-23 01:31:06 +01:00
if ( $permission ) $adminGroup = DataObject :: get_one ( " Group " , " \" Group \" . \" ID \" = ' { $permission -> GroupID } ' " , true , " \" Group \" . \" ID \" " );
2007-07-19 12:40:28 +02:00
if ( $adminGroup ) {
if ( $adminGroup -> Members () -> First ()) {
$member = $adminGroup -> Members () -> First ();
}
}
2007-09-14 05:12:21 +02:00
2007-07-19 12:40:28 +02:00
if ( ! $adminGroup ) {
$adminGroup = Object :: create ( 'Group' );
$adminGroup -> Title = 'Administrators' ;
$adminGroup -> Code = " administrators " ;
$adminGroup -> write ();
Permission :: grant ( $adminGroup -> ID , " ADMIN " );
}
2007-07-20 01:15:05 +02:00
if ( ! isset ( $member )) {
2007-07-19 12:40:28 +02:00
$member = Object :: create ( 'Member' );
$member -> FirstName = $member -> Surname = 'Admin' ;
$member -> Email = $username ;
$member -> Password = $password ;
$member -> write ();
$member -> Groups () -> add ( $adminGroup );
}
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
*
2007-09-14 21:10:18 +02:00
* This will set a static default - admin ( e . g . " td " ) which is not existing
* as a database - record . By this workaround we can test pages in dev - mode
* with a unified login . Submitted login - credentials are first checked
* against this static information in { @ authenticate ()} .
2007-09-14 05:12:21 +02:00
*
2007-10-02 23:57:12 +02:00
* @ param string $username The user name
* @ 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 if passwords should be encrypted or not
*
* @ param bool $encrypt Set to TRUE if you want that all ( new ) passwords
* will be stored encrypted , FALSE if you want to
* store the passwords in clear text .
*/
public static function encrypt_passwords ( $encrypt ) {
self :: $encryptPasswords = ( bool ) $encrypt ;
}
/**
* Get a list of all available encryption algorithms
*
* @ return array Returns an array of strings containing all supported
* encryption algorithms .
*/
public static function get_encryption_algorithms () {
2008-02-25 03:10:37 +01:00
$result = function_exists ( 'hash_algos' ) ? hash_algos () : array ();
if ( count ( $result ) == 0 ) {
if ( function_exists ( 'md5' )) $result [] = 'md5' ;
if ( function_exists ( 'sha1' )) $result [] = 'sha1' ;
} else {
foreach ( $result as $i => $algorithm ) {
if ( preg_match ( '/,/' , $algorithm )) {
unset ( $result [ $i ]);
}
}
}
2008-03-11 02:30:49 +01:00
// Support for MySQL password() and old_password() functions. These aren't recommended unless you need them,
// but can be helpful for migrating legacy user-sets into a SilverStripe application.
// Since DB::getConn() doesn't exist yet, we need to look at $databaseConfig. Gack!
global $databaseConfig ;
if ( $databaseConfig [ 'type' ] == 'MySQLDatabase' ) {
$result [] = 'password' ;
$result [] = 'old_password' ;
}
2007-09-15 23:51:37 +02:00
return $result ;
}
/**
* Set the password encryption algorithm
*
* @ param string $algorithm One of the available password encryption
* algorithms determined by
* { @ link Security :: get_encryption_algorithms ()}
* @ param bool $use_salt Set to TRUE if a random salt should be used to
* encrypt the passwords , otherwise FALSE
* @ return bool Returns TRUE if the passed algorithm was valid , otherwise
* FALSE .
*/
public static function set_password_encryption_algorithm ( $algorithm ,
$use_salt ) {
if ( in_array ( $algorithm , self :: get_encryption_algorithms ()) == false )
return false ;
self :: $encryptionAlgorithm = $algorithm ;
self :: $useSalt = ( bool ) $use_salt ;
return true ;
}
/**
* Get the the password encryption details
*
* The return value is an array of the following form :
* < code >
* array ( 'encrypt_passwords' => bool ,
* 'algorithm' => string ,
* 'use_salt' => bool )
* </ code >
*
* @ return array Returns an associative array containing all the
* password encryption relevant information .
*/
public static function get_password_encryption_details () {
return array ( 'encrypt_passwords' => self :: $encryptPasswords ,
'algorithm' => self :: $encryptionAlgorithm ,
'use_salt' => self :: $useSalt );
}
/**
* Encrypt a password
*
* Encrypt a password according to the current password encryption
* settings .
* Use { @ link Security :: get_password_encryption_details ()} to retrieve the
* current settings .
* 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
* 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
* password ( so that the encryption algorithm can
* be changed over the time ) .
2007-09-15 23:51:37 +02:00
* @ return mixed Returns an associative array containing the encrypted
* password and the used salt in the form
* < i > array ( 'encrypted_password' => string , 'salt' =>
* string , 'algorithm' => string ) </ i >.
* If the passed algorithm is invalid , FALSE will be
* returned .
*
* @ see encrypt_passwords ()
* @ see set_password_encryption_algorithm ()
* @ see get_password_encryption_details ()
*/
public static function encrypt_password ( $password , $salt = null ,
$algorithm = null ) {
if ( strlen ( trim ( $password )) == 0 ) {
// An empty password was passed, return an empty password an salt!
return array ( 'password' => null ,
'salt' => null ,
'algorithm' => 'none' );
2008-11-10 04:51:35 +01:00
} elseif (( ! $algorithm && self :: $encryptPasswords == false ) || ( $algorithm == 'none' )) {
2007-09-15 23:51:37 +02:00
// The password should not be encrypted
return array ( 'password' => substr ( $password , 0 , 64 ),
'salt' => null ,
'algorithm' => 'none' );
} elseif ( strlen ( trim ( $algorithm )) != 0 ) {
// A custom encryption algorithm was passed, check if we can use it
if ( in_array ( $algorithm , self :: get_encryption_algorithms ()) == false )
return false ;
} else {
// Just use the default encryption algorithm
$algorithm = self :: $encryptionAlgorithm ;
}
2008-03-11 02:30:49 +01:00
// Support for MySQL password() and old_password() authentication
if ( strtolower ( $algorithm ) == 'password' || strtolower ( $algorithm ) == 'old_password' ) {
$SQL_password = Convert :: raw2sql ( $password );
$enc = DB :: query ( " SELECT $algorithm (' $SQL_password ') " ) -> value ();
return array (
'password' => $enc ,
'salt' => null ,
'algorithm' => $algorithm ,
);
}
2007-09-15 23:51:37 +02:00
// If no salt was provided but we need one we just generate a random one
if ( strlen ( trim ( $salt )) == 0 )
$salt = null ;
if (( self :: $useSalt == true ) && is_null ( $salt )) {
$salt = sha1 ( mt_rand ()) . time ();
$salt = substr ( base_convert ( $salt , 16 , 36 ), 0 , 50 );
}
2008-03-11 02:30:49 +01:00
// Encrypt the password
2007-09-15 23:51:37 +02:00
if ( function_exists ( 'hash' )) {
$password = hash ( $algorithm , $password . $salt );
} else {
$password = call_user_func ( $algorithm , $password . $salt );
}
// Convert the base of the hexadecimal password to 36 to make it shorter
// In that way we can store also a SHA256 encrypted password in just 64
// letters.
$password = substr ( base_convert ( $password , 16 , 36 ), 0 , 64 );
return array ( 'password' => $password ,
'salt' => $salt ,
'algorithm' => $algorithm );
}
/**
* Encrypt all passwords
*
* Action to encrypt all * clear text * passwords in the database according
* to the current settings .
* If the current settings are so that passwords shouldn ' t be encrypted ,
* an explanation will be printed out .
*
* To run this action , the user needs to have administrator rights !
*/
2007-09-16 02:32:48 +02:00
public function encryptallpasswords () {
2007-09-15 23:51:37 +02:00
// Only administrators can run this method
2008-11-05 00:31:33 +01:00
if ( ! Permission :: check ( " ADMIN " )) {
2007-09-15 23:51:37 +02:00
Security :: permissionFailure ( $this ,
2008-02-25 03:10:37 +01:00
_t ( 'Security.PERMFAILURE' , ' This page is secured and you need administrator rights to access it .
Enter your credentials below and we will send you right along . ' ));
2007-09-15 23:51:37 +02:00
return ;
}
if ( self :: $encryptPasswords == false ) {
2008-02-25 03:10:37 +01:00
print '<h1>' . _t ( 'Security.ENCDISABLED1' , 'Password encryption disabled!' ) . " </h1> \n " ;
print '<p>' . _t ( 'Security.ENCDISABLED2' , 'To encrypt your passwords change your password settings by adding' ) . " \n " ;
print " <pre>Security::encrypt_passwords(true);</pre> \n " . _t ( 'Security.ENCDISABLED3' , 'to mysite/_config.php' ) . " </p> " ;
2007-09-15 23:51:37 +02:00
return ;
}
// Are there members with a clear text password?
$members = DataObject :: get ( " Member " ,
2008-11-24 10:31:14 +01:00
" \" PasswordEncryption \" = 'none' AND \" Password \" IS NOT NULL " );
2007-09-15 23:51:37 +02:00
if ( ! $members ) {
2008-02-25 03:10:37 +01:00
print '<h1>' . _t ( 'Security.NOTHINGTOENCRYPT1' , 'No passwords to encrypt' ) . " </h1> \n " ;
print '<p>' . _t ( 'Security.NOTHINGTOENCRYPT2' , 'There are no members with a clear text password that could be encrypted!' ) . " </p> \n " ;
2007-09-15 23:51:37 +02:00
return ;
}
// Encrypt the passwords...
2008-02-25 03:10:37 +01:00
print '<h1>' . _t ( 'Security.ENCRYPT' , 'Encrypting all passwords' ) . '</h1>' ;
print '<p>' . sprintf ( _t ( 'Security.ENCRYPTWITH' , 'The passwords will be encrypted using the "%s" algorithm' ), htmlentities ( self :: $encryptionAlgorithm ));
2007-09-15 23:51:37 +02:00
print ( self :: $useSalt )
2008-02-25 03:10:37 +01:00
? _t ( 'Security.ENCRYPTWITHSALT' , 'with a salt to increase the security.' ) . " </p> \n "
: _t ( 'Security.ENCRYPTWITHOUTSALT' , 'without using a salt to increase the security.' ) . " </p><p> \n " ;
2007-09-15 23:51:37 +02:00
foreach ( $members as $member ) {
// Force the update of the member record, as new passwords get
// automatically encrypted according to the settings, this will do all
// the work for us
$member -> forceChange ();
$member -> write ();
2008-02-25 03:10:37 +01:00
print ' ' . _t ( 'Security.ENCRYPTEDMEMBERS' , 'Encrypted credentials for member "' );
print htmlentities ( $member -> getTitle ()) . '" (' . _t ( 'Security.ID' , 'ID:' ) . ' ' . $member -> ID .
'; ' . _t ( 'Security.EMAIL' , 'E-Mail:' ) . ' ' . htmlentities ( $member -> Email ) . " )<br /> \n " ;
2007-09-15 23:51:37 +02:00
}
print '</p>' ;
}
2007-11-15 23:29:10 +01:00
/**
* Checks the database is in a state to perform security checks .
* @ return bool
*/
public static function database_is_ready () {
2009-01-08 00:00:54 +01:00
$requiredTables = ClassInfo :: dataClassesFor ( 'Member' );
$requiredTables [] = 'Group' ;
$requiredTables [] = 'Permission' ;
foreach ( $requiredTables as $table ) if ( ! ClassInfo :: hasTable ( $table )) return false ;
return (( $permissionFields = DB :: fieldList ( 'Permission' )) && isset ( $permissionFields [ 'Type' ])) &&
2007-11-15 23:29:10 +01:00
(( $memberFields = DB :: fieldList ( 'Member' )) && isset ( $memberFields [ 'RememberLoginToken' ]));
}
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
2007-07-19 12:40:28 +02:00
}
2007-09-14 19:04:11 +02:00
2009-02-02 00:49:53 +01:00
?>