authenticator_class = $authenticatorClassName; $customCSS = project() . '/css/member_login.css'; if(Director::fileExists($customCSS)) { Requirements::css($customCSS); } if(isset($_REQUEST['BackURL'])) { $backURL = $_REQUEST['BackURL']; } else { $backURL = Session::get('BackURL'); } if($checkCurrentUser && Member::currentUser() && Member::logged_in_session_exists()) { $fields = FieldList::create( HiddenField::create("AuthenticationMethod", null, $this->authenticator_class, $this) ); $actions = FieldList::create( FormAction::create("logout", _t('Member.BUTTONLOGINOTHER', "Log in as someone else")) ); } else { if(!$fields) { $label=singleton('Member')->fieldLabel(Member::config()->unique_identifier_field); $fields = FieldList::create( HiddenField::create("AuthenticationMethod", null, $this->authenticator_class, $this), // Regardless of what the unique identifer field is (usually 'Email'), it will be held in the // 'Email' value, below: $emailField = TextField::create("Email", $label, null, null, $this), PasswordField::create("Password", _t('Member.PASSWORD', 'Password')) ); if(Security::config()->remember_username) { $emailField->setValue(Session::get('SessionForms.MemberLoginForm.Email')); } else { // Some browsers won't respect this attribute unless it's added to the form $this->setAttribute('autocomplete', 'off'); $emailField->setAttribute('autocomplete', 'off'); } if(Security::config()->autologin_enabled) { $fields->push(CheckboxField::create( "Remember", _t('Member.REMEMBERME', "Remember me next time?") )); } } if(!$actions) { $actions = FieldList::create( FormAction::create('dologin', _t('Member.BUTTONLOGIN', "Log in")), LiteralField::create( 'forgotPassword', '

' . _t('Member.BUTTONLOSTPASSWORD', "I've lost my password") . '

' ) ); } } if(isset($backURL)) { $fields->push(HiddenField::create('BackURL', 'BackURL', $backURL)); } // Reduce attack surface by enforcing POST requests $this->setFormMethod('POST', true); parent::__construct($controller, $name, $fields, $actions); $this->setValidator(RequiredFields::create('Email', 'Password')); // Focus on the email input when the page is loaded $js = <<message = _t( 'Member.LOGGEDINAS', "You're logged in as {name}.", array('name' => $member->{$this->loggedInAsField}) ); } // Reset forced message if($forceMessage) { Session::set('MemberLoginForm.force_message', false); } return parent::getMessageFromSession(); } /** * Login form handler method * * This method is called when the user clicks on "Log in" * * @param array $data Submitted data */ public function dologin($data) { if($this->performLogin($data)) { $this->logInUserAndRedirect($data); } else { if(array_key_exists('Email', $data)){ Session::set('SessionForms.MemberLoginForm.Email', $data['Email']); Session::set('SessionForms.MemberLoginForm.Remember', isset($data['Remember'])); } if(isset($_REQUEST['BackURL'])) $backURL = $_REQUEST['BackURL']; else $backURL = null; if($backURL) Session::set('BackURL', $backURL); // Show the right tab on failed login $loginLink = Director::absoluteURL($this->controller->Link('login')); if($backURL) $loginLink .= '?BackURL=' . urlencode($backURL); $this->controller->redirect($loginLink . '#' . $this->FormName() .'_tab'); } } /** * Login in the user and figure out where to redirect the browser. * * The $data has this format * array( * 'AuthenticationMethod' => 'MemberAuthenticator', * 'Email' => 'sam@silverstripe.com', * 'Password' => '1nitialPassword', * 'BackURL' => 'test/link', * [Optional: 'Remember' => 1 ] * ) * * @param array $data * @return SS_HTTPResponse */ protected function logInUserAndRedirect($data) { Session::clear('SessionForms.MemberLoginForm.Email'); Session::clear('SessionForms.MemberLoginForm.Remember'); if(Member::currentUser()->isPasswordExpired()) { if(isset($_REQUEST['BackURL']) && $backURL = $_REQUEST['BackURL']) { Session::set('BackURL', $backURL); } $cp = ChangePasswordForm::create($this->controller, 'ChangePasswordForm'); $cp->sessionMessage( _t('Member.PASSWORDEXPIRED', 'Your password has expired. Please choose a new one.'), 'good' ); return $this->controller->redirect('Security/changepassword'); } // Absolute redirection URLs may cause spoofing if(!empty($_REQUEST['BackURL'])) { $url = $_REQUEST['BackURL']; if(Director::is_site_url($url) ) { $url = Director::absoluteURL($url); } else { // Spoofing attack, redirect to homepage instead of spoofing url $url = Director::absoluteBaseURL(); } return $this->controller->redirect($url); } // If a default login dest has been set, redirect to that. if ($url = Security::config()->default_login_dest) { $url = Controller::join_links(Director::absoluteBaseURL(), $url); return $this->controller->redirect($url); } // Redirect the user to the page where they came from $member = Member::currentUser(); if($member) { $firstname = Convert::raw2xml($member->FirstName); if(!empty($data['Remember'])) { Session::set('SessionForms.MemberLoginForm.Remember', '1'); $member->logIn(true); } else { $member->logIn(); } Session::set('Security.Message.message', _t('Member.WELCOMEBACK', "Welcome Back, {firstname}", array('firstname' => $firstname)) ); Session::set("Security.Message.type", "good"); } Controller::curr()->redirectBack(); } /** * Log out form handler method * * This method is called when the user clicks on "logout" on the form * created when the parameter $checkCurrentUser of the * {@link __construct constructor} was set to TRUE and the user was * currently logged in. */ public function logout() { $s = new Security(); $s->logout(); } /** * Try to authenticate the user * * @param array Submitted data * @return Member Returns the member object on successful authentication * or NULL on failure. */ public function performLogin($data) { $member = call_user_func_array(array($this->authenticator_class, 'authenticate'), array($data, $this)); if($member) { $member->LogIn(isset($data['Remember'])); return $member; } else { $this->extend('authenticationFailed', $data); return null; } } /** * Forgot password form handler method. * Called when the user clicks on "I've lost my password". * Extensions can use the 'forgotPassword' method to veto executing * the logic, by returning FALSE. In this case, the user will be redirected back * to the form without further action. It is recommended to set a message * in the form detailing why the action was denied. * * @param array $data Submitted data */ public function forgotPassword($data) { // minimum execution time for authenticating a member $minExecTime = self::config()->min_auth_time / 1000; $startTime = microtime(true); // Ensure password is given if(empty($data['Email'])) { $this->sessionMessage( _t('Member.ENTEREMAIL', 'Please enter an email address to get a password reset link.'), 'bad' ); $this->controller->redirect('Security/lostpassword'); return; } // Find existing member $member = Member::get()->filter("Email", $data['Email'])->first(); // Allow vetoing forgot password requests $results = $this->extend('forgotPassword', $member); if($results && is_array($results) && in_array(false, $results, true)) { $this->controller->redirect('Security/lostpassword'); } elseif ($member) { $token = $member->generateAutologinTokenAndStoreHash(); $e = Member_ForgotPasswordEmail::create(); $e->populateTemplate($member); $e->populateTemplate(array( 'PasswordResetLink' => Security::getPasswordResetLink($member, $token) )); $e->setTo($member->Email); $e->send(); $this->controller->redirect('Security/passwordsent/' . urlencode($data['Email'])); } elseif($data['Email']) { // Avoid information disclosure by displaying the same status, // regardless wether the email address actually exists $this->controller->redirect('Security/passwordsent/' . rawurlencode($data['Email'])); } else { $this->sessionMessage( _t('Member.ENTEREMAIL', 'Please enter an email address to get a password reset link.'), 'bad' ); $this->controller->redirect('Security/lostpassword'); } $waitFor = $minExecTime - (microtime(true) - $startTime); if ($waitFor > 0) { usleep($waitFor * 1000000); } } }