mlanthaler: Switched to an authenticator and a form class to be able to add other authentication methods. (merged from branches/gsoc)

mlanthaler: The missing authenticator base class...  (merged from branches/gsocmlanthaler: Switched to an authenticator and a form class to be able to add other authentication methods.  (merged from branches/gsoc)
mlanthaler: The missing authenticator base class...  (merged from branches/gsoc))


git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/trunk@41729 467b73ca-7a2a-4603-9d3b-597d59a354a9
This commit is contained in:
Ingo Schommer 2007-09-14 03:12:21 +00:00
parent f7646412f4
commit a377a67e54
6 changed files with 329 additions and 174 deletions

View File

@ -457,6 +457,35 @@ JS;
$start = $_REQUEST['ctf']['start'] - 1; $start = $_REQUEST['ctf']['start'] - 1;
return $this->PopupBaseLink() . "&methodName={$_REQUEST['methodName']}&ctf[childID]={$item->ID}&ctf[start]={$start}"; return $this->PopupBaseLink() . "&methodName={$_REQUEST['methodName']}&ctf[childID]={$item->ID}&ctf[start]={$start}";
} }
/**
* Method handles pagination in asset popup.
*
* @return Object DataObjectSet
*/
function pagination() {
$this->pageSize = 10;
$currentItem = $this->PopupCurrentItem();
$result = new DataObjectSet();
if($currentItem < 6) {
$offset = 1;
} elseif($this->totalCount - $currentItem <= 4) {
$offset = $currentItem - (10 - ($this->totalCount - $currentItem));
$offset = $offset <= 0 ? 1 : $offset;
} else {
$offset = $currentItem - 5;
}
for($i = $offset;$i <= $offset + $this->pageSize && $i <= $this->totalCount;$i++) {
$start = $i - 1;
$item = $this->unpagedSourceItems->getOffset($i-1);
$links['link'] = $this->PopupBaseLink() . "&methodName={$_REQUEST['methodName']}&ctf[childID]={$item->ID}&ctf[start]={$start}";
$links['number'] = $i;
$links['active'] = $i == $currentItem ? false : true;
$result->push(new ArrayData($links));
}
return $result;
}

View File

@ -0,0 +1,33 @@
<?php
/**
* Abstract base class for an authentication method
*
* This class is used as a base class for the different authentication
* methods like {@link MemberAuthenticator} or {@link OpenIDAuthenticator}.
*
* @author Markus Lanthaler <markus@silverstripe.com>
*/
abstract class Authenticator extends Object
{
/**
* Method to authenticate an user
*
* @param array $RAW_data Raw data to authenticate the user
* @return bool|Member Returns FALSE if authentication fails, otherwise
* the member object
*/
public abstract function authenticate(array $RAW_data);
/**
* Method that creates the login form for this authentication method
*
* @return Form Returns the login form to use with this authentication
* method
*/
public abstract function getLoginForm();
}
?>

View File

@ -0,0 +1,43 @@
<?php
/**
* Authenticator for the default "member" method
*
* @author Markus Lanthaler <markus@silverstripe.com>
*/
class MemberAuthenticator extends Authenticator {
/**
* Method to authenticate an user
*
* @param array $RAW_data Raw data to authenticate the user
* @return bool|Member Returns FALSE if authentication fails, otherwise
* the member object
*/
public function authenticate(array $RAW_data) {
$SQL_user = Convert::raw2sql($RAW_data['Email']);
$SQL_password = Convert::raw2sql($RAW_data['Password']);
$member = DataObject::get_one(
"Member", "Email = '$SQL_user' And Password = '$SQL_password'");
if($member) {
Session::clear("BackURL");
}
return $member;
}
/**
* Method that creates the login form for this authentication method
*
* @return Form Returns the login form to use with this authentication
* method
*/
public function getLoginForm() {
return Object::create("MemberLoginForm", $this, "LoginForm");
}
}
?>

View File

@ -1,116 +1,159 @@
<?php <?php
/** /**
* Standard log-in form. * Log-in form for the "member" authentication method
*/ */
class LoginForm extends Form { class MemberLoginForm extends Form {
function __construct($controller, $name, $fields = null, $actions = null, $checkCurrentUser = true) { /**
if(isset($_REQUEST['BackURL'])) { * Constructor
$backURL = $_REQUEST['BackURL']; *
} else { * @param $controller
$backURL = Session::get('BackURL'); * @param $name
Session::clear("BackURL"); * @param $fields
} * @param $actions
* @param $checkCurrentUser
if($checkCurrentUser && Member::currentUserID()) { */
$fields = new FieldSet(); function __construct($controller, $name, $fields = null, $actions = null, $checkCurrentUser = true) {
$actions = new FieldSet(new FormAction("logout", "Log in as someone else"));
} else { $customCSS = project() . '/css/member_login.css';
if(!$fields) { if(Director::fileExists($customCSS)) {
$fields = new FieldSet( Requirements::css($customCSS);
new TextField("Email", "Email address", Session::get('SessionForms.LoginForm.Email')), }
new EncryptField("Password", "Password"),
new CheckboxField("Remember", "Remember me next time?",true) if(isset($_REQUEST['BackURL'])) {
); $backURL = $_REQUEST['BackURL'];
} } else {
if(!$actions) { $backURL = Session::get('BackURL');
$actions = new FieldSet( //Session::clear("BackURL"); don't clear the back URL here! Should be used until the right password is entered!
new FormAction("dologin", "Log in"), }
new FormAction("forgotPassword", "I've lost my password")
); if($checkCurrentUser && Member::currentUserID()) {
} $fields = new FieldSet();
} $actions = new FieldSet(new FormAction("logout", "Log in as someone else"));
} else {
if(isset($backURL)) { if(!$fields) {
$fields->push(new HiddenField('BackURL', 'BackURL', $backURL)); $fields = new FieldSet(
} new HiddenField("AuthenticationMethod", null, "Member"),
new TextField("Email", "Email address", Session::get('SessionForms.MemberLoginForm.Email')),
parent::__construct($controller, $name, $fields, $actions); new EncryptField("Password", "Password"),
} new CheckboxField("Remember", "Remember me next time?",true)
);
protected function getMessageFromSession() { }
parent::getMessageFromSession(); if(!$actions) {
if(($member = Member::currentUser()) && !Session::get('LoginForm.force_message')) { $actions = new FieldSet(
$this->message = "You're logged in as $member->FirstName."; new FormAction("dologin", "Log in"),
} new FormAction("forgotPassword", "I've lost my password")
Session::set('LoginForm.force_message', false); );
} }
}
public function dologin($data) {
if($this->performLogin($data)){ if(isset($backURL)) {
if(isset($_REQUEST['BackURL']) && $backURL = $_REQUEST['BackURL']) { $fields->push(new HiddenField('BackURL', 'BackURL', $backURL));
Session::clear("BackURL"); }
Director::redirect($backURL);
}else parent::__construct($controller, $name, $fields, $actions);
Director::redirectBack(); }
}else{
if($badLoginURL = Session::get("BadLoginURL")){ /**
Director::redirect($badLoginURL); * Get message from session
}else{ */
Director::redirectBack(); protected function getMessageFromSession() {
} parent::getMessageFromSession();
} if(($member = Member::currentUser()) && !Session::get('MemberLoginForm.force_message')) {
} $this->message = "You're logged in as $member->FirstName.";
}
public function logout(){ Session::set('MemberLoginForm.force_message', false);
$s = new Security(); }
return $s->logout();
}
/**
/* check the membership * Login form handler method
*
* if one of them or both don't match, set the fields which are unmatched with red star * * This method is called when the user clicks on "Log in"
*/ *
public function performLogin($data){ * @param array $data Submitted data
if($member = Security::authenticate($data['Email'], $data['Password'])) { */
$firstname = Convert::raw2xml($member->FirstName); public function dologin($data) {
$this->sessionMessage("Welcome Back, {$firstname}", "good"); if($this->performLogin($data)){
$member->LogIn();
if(isset($data['Remember'])) { if($backURL = $_REQUEST['BackURL']) {
// Deliberately obscure... Session::clear("BackURL");
Cookie::set('alc_enc',base64_encode("$data[Email]:$data[Password]")); Director::redirect($backURL);
} }else
return $member; Director::redirectBack();
}else{
} else {
$this->sessionMessage("That doesn't seem to be the right email address or password. Please try again.", "bad"); if($badLoginURL = Session::get("BadLoginURL")){
return null; Director::redirect($badLoginURL);
} }else{
} Director::redirectBack();
}
}
}
function forgotPassword($data) {
$SQL_data = Convert::raw2sql($data);
if($data['Email'] && $member = DataObject::get_one("Member", "Member.Email = '$SQL_data[Email]'")) { /**
if(!$member->Password) { * Log out
$member->createNewPassword(); *
$member->write(); * @todo Figure out for what this method is used!
} */
public function logout(){
$member->sendInfo('forgotPassword'); $s = new Security();
Director::redirect('Security/passwordsent/' . urlencode($data['Email'])); return $s->logout();
}
} else if($data['Email']) {
$this->sessionMessage("Sorry, but I don't recognise the email address. Maybe you need to sign up, or perhaps you used another email address?", "bad");
Director::redirectBack(); /**
* Check the membership
} else { *
Director::redirect("Security/lostpassword"); * If one of them or both don't match, set the fields which are unmatched with red star *
*/
} public function performLogin($data){
} if($member = MemberAuthenticator::authenticate($data)) {
} $firstname = Convert::raw2xml($member->FirstName);
$this->sessionMessage("Welcome Back, {$firstname}", "good");
$member->LogIn();
?> if(isset($data['Remember'])) {
// Deliberately obscure...
Cookie::set('alc_enc',base64_encode("$data[Email]:$data[Password]"));
}
return $member;
} else {
$this->sessionMessage("That doesn't seem to be the right email address or password. Please try again.", "bad");
return null;
}
}
/**
* Forgot password form handler method
*
* This method is called when the user clicks on "Log in"
*
* @param array $data Submitted data
*/
function forgotPassword($data) {
$SQL_data = Convert::raw2sql($data);
if($data['Email'] && $member = DataObject::get_one("Member", "Member.Email = '$SQL_data[Email]'")) {
if(!$member->Password) {
$member->createNewPassword();
$member->write();
}
$member->sendInfo('forgotPassword');
Director::redirect('Security/passwordsent/' . urlencode($data['Email']));
} else if($data['Email']) {
$this->sessionMessage("Sorry, but I don't recognise the email address. Maybe you need to sign up, or perhaps you used another email address?", "bad");
Director::redirectBack();
} else {
Director::redirect("Security/lostpassword");
}
}
}
?>

View File

@ -4,19 +4,19 @@
* Implements a basic security model * Implements a basic security model
*/ */
class Security extends Controller { class Security extends Controller {
/** /**
* @var $username String Only used in dev-mode by setDefaultAdmin() * @var $username String Only used in dev-mode by setDefaultAdmin()
*/ */
protected static $username; protected static $username;
/** /**
* @var $password String Only used in dev-mode by setDefaultAdmin() * @var $password String Only used in dev-mode by setDefaultAdmin()
*/ */
protected static $password; protected static $password;
protected static $strictPathChecking = false; protected static $strictPathChecking = false;
/** /**
* Register that we've had a permission failure trying to view the given page. * Register that we've had a permission failure trying to view the given page.
* This will redirect to a login page. * This will redirect to a login page.
@ -24,13 +24,13 @@ class Security extends Controller {
* @param messageSet The message to show to the user. This can be a string, or a map of different * @param 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: * messages for different contexts. If you pass an array, you can use the following keys:
* - default: The default message * - default: The default message
* - logInAgain: The message to show if the user has just logged out and the * - 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. * - alreadyLoggedIn: The message to show if the user is already logged in and lacks the permission to access the item.
* If you don't provide a messageSet, a default will be used. * If you don't provide a messageSet, a default will be used.
*/ */
static function permissionFailure($page = null, $messageSet = null) { static function permissionFailure($page = null, $messageSet = null) {
$loginForm = singleton('Security')->LoginForm(); $loginForm = singleton('Security')->LoginForm();
//user_error('debug', E_USER_ERROR); //user_error('debug', E_USER_ERROR);
// Prepare the messageSet provided // Prepare the messageSet provided
if(!$messageSet) { if(!$messageSet) {
@ -42,24 +42,24 @@ class Security extends Controller {
} else if(!is_array($messageSet)) { } else if(!is_array($messageSet)) {
$messageSet = array('default' => $messageSet); $messageSet = array('default' => $messageSet);
} }
// Work out the right message to show // Work out the right message to show
if(Member::currentUserID()) { if(Member::currentUserID()) {
// user_error( 'PermFailure with member', E_USER_ERROR ); // user_error( 'PermFailure with member', E_USER_ERROR );
$message = $messageSet['alreadyLoggedIn'] ? $messageSet['alreadyLoggedIn'] : $messageSet['default']; $message = $messageSet['alreadyLoggedIn'] ? $messageSet['alreadyLoggedIn'] : $messageSet['default'];
//$_SESSION['LoginForm']['force_form'] = true; //$_SESSION['LoginForm']['force_form'] = true;
//$_SESSION['LoginForm']['type'] = 'warning'; //$_SESSION['LoginForm']['type'] = 'warning';
Member::logout(); Member::logout();
} else if(substr(Director::history(),0,15) == 'Security/logout') { } else if(substr(Director::history(),0,15) == 'Security/logout') {
$message = $messageSet['logInAgain'] ? $messageSet['logInAgain'] : $messageSet['default']; $message = $messageSet['logInAgain'] ? $messageSet['logInAgain'] : $messageSet['default'];
} else { } else {
$message = $messageSet['default']; $message = $messageSet['default'];
} }
$loginForm->sessionMessage($message, 'warning'); $loginForm->sessionMessage($message, 'warning');
// $_SESSION['LoginForm']['message'] = $message; // $_SESSION['LoginForm']['message'] = $message;
@ -74,15 +74,13 @@ class Security extends Controller {
} }
function LoginForm() { function LoginForm() {
$customCSS = project() . '/css/login.css'; return MemberAuthenticator::GetLoginForm();
if(Director::fileExists($customCSS)) Requirements::css($customCSS);
return Object::create("LoginForm", $this, "LoginForm");
} }
function Link($action = null) { function Link($action = null) {
return "Security/$action"; return "Security/$action";
} }
/** /**
* @param bool $redirect Redirect the user back to where they came. * @param bool $redirect Redirect the user back to where they came.
* - If it's false, the code calling logout() is responsible for sending the user where-ever they should go. * - If it's false, the code calling logout() is responsible for sending the user where-ever they should go.
*/ */
function logout($redirect = true) { function logout($redirect = true) {
@ -91,25 +89,25 @@ class Security extends Controller {
Session::clear("loggedInAs"); Session::clear("loggedInAs");
if($redirect) Director::redirectBack(); if($redirect) Director::redirectBack();
} }
function login() { function login() {
Requirements::javascript("jsparty/behaviour.js"); Requirements::javascript("jsparty/behaviour.js");
Requirements::javascript("jsparty/loader.js"); Requirements::javascript("jsparty/loader.js");
Requirements::javascript("jsparty/prototype.js"); Requirements::javascript("jsparty/prototype.js");
Requirements::javascript("jsparty/prototype_improvements.js"); Requirements::javascript("jsparty/prototype_improvements.js");
Requirements::javascript("jsparty/scriptaculous/effects.js"); Requirements::javascript("jsparty/scriptaculous/effects.js");
$tmpPage = DataObject::get_one('Page', "URLSegment = 'home'"); $tmpPage = DataObject::get_one('Page', "URLSegment = 'home'");
$tmpPage->Title = "Log in"; $tmpPage->Title = "Log in";
$tmpPage->URLSegment = "Security"; $tmpPage->URLSegment = "Security";
$controller = new Page_Controller($tmpPage); $controller = new Page_Controller($tmpPage);
$controller->init(); $controller->init();
//Controller::$currentController = $controller; Controller::$currentController = $controller;
if(SSViewer::hasTemplate("Security_login")) { if(SSViewer::hasTemplate("Security_login")) {
return $controller->renderWith(array("Security_login", "Page")); return $controller->renderWith(array("Security_login", "Page"));
} else { } else {
$customisedController = $controller->customise(array( $customisedController = $controller->customise(array(
"Content" => $this->LoginForm()->forTemplate() "Content" => $this->LoginForm()->forTemplate()
@ -125,12 +123,12 @@ class Security extends Controller {
Requirements::javascript("jsparty/loader.js"); Requirements::javascript("jsparty/loader.js");
Requirements::javascript("jsparty/prototype_improvements.js"); Requirements::javascript("jsparty/prototype_improvements.js");
Requirements::javascript("jsparty/scriptaculous/effects.js"); Requirements::javascript("jsparty/scriptaculous/effects.js");
$tmpPage = new Page(); $tmpPage = new Page();
$tmpPage->Title = "Lost Password"; $tmpPage->Title = "Lost Password";
$tmpPage->URLSegment = "Security"; $tmpPage->URLSegment = "Security";
$controller = new Page_Controller($tmpPage); $controller = new Page_Controller($tmpPage);
$customisedController = $controller->customise(array( $customisedController = $controller->customise(array(
"Content" => "<p>Enter your e-mail address and we will send you a password</p>", "Content" => "<p>Enter your e-mail address and we will send you a password</p>",
"Form" => $this->LostPasswordForm(), "Form" => $this->LostPasswordForm(),
@ -146,12 +144,12 @@ class Security extends Controller {
Requirements::javascript("jsparty/prototype.js"); Requirements::javascript("jsparty/prototype.js");
Requirements::javascript("jsparty/prototype_improvements.js"); Requirements::javascript("jsparty/prototype_improvements.js");
Requirements::javascript("jsparty/scriptaculous/effects.js"); Requirements::javascript("jsparty/scriptaculous/effects.js");
$tmpPage = new Page(); $tmpPage = new Page();
$tmpPage->Title = "Lost Password"; $tmpPage->Title = "Lost Password";
$tmpPage->URLSegment = "Security"; $tmpPage->URLSegment = "Security";
$controller = new Page_Controller($tmpPage); $controller = new Page_Controller($tmpPage);
$email = $this->urlParams['ID']; $email = $this->urlParams['ID'];
$customisedController = $controller->customise(array( $customisedController = $controller->customise(array(
"Title" => "Password sent to '$email'", "Title" => "Password sent to '$email'",
@ -161,10 +159,10 @@ class Security extends Controller {
//Controller::$currentController = $controller; //Controller::$currentController = $controller;
return $customisedController->renderWith("Page"); return $customisedController->renderWith("Page");
} }
function LostPasswordForm() { function LostPasswordForm() {
return new LoginForm($this, "LostPasswordForm", new FieldSet( return new MemberLoginForm($this, "LostPasswordForm", new FieldSet(
new EmailField("Email", "Email address") new EmailField("Email", "Email address")
), new FieldSet( ), new FieldSet(
new FormAction("forgotPassword", "Send me my password") new FormAction("forgotPassword", "Send me my password")
@ -186,7 +184,7 @@ class Security extends Controller {
} else { } else {
$member = DataObject::get_one("Member", "Email = '$SQL_email' And Password = '$SQL_password'"); $member = DataObject::get_one("Member", "Email = '$SQL_email' And Password = '$SQL_password'");
} }
return $member; return $member;
} }
@ -204,7 +202,7 @@ class Security extends Controller {
$member = $adminGroup->Members()->First(); $member = $adminGroup->Members()->First();
} }
} }
if(!$adminGroup) { if(!$adminGroup) {
$adminGroup = Object::create('Group'); $adminGroup = Object::create('Group');
$adminGroup->Title = 'Administrators'; $adminGroup->Title = 'Administrators';
@ -221,37 +219,37 @@ class Security extends Controller {
$member->write(); $member->write();
$member->Groups()->add($adminGroup); $member->Groups()->add($adminGroup);
} }
return $member; return $member;
} }
/** /**
* This will set a static default-admin (e.g. "td") which is not existing as * 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 database-record. By this workaround we can test pages in dev-mode with
* a unified login. Submitted login-credentials are first checked against * a unified login. Submitted login-credentials are first checked against
* this static information in {@authenticate()}. * this static information in {@authenticate()}.
* *
* @param $username String * @param $username String
* @param $password String (Cleartext) * @param $password String (Cleartext)
*/ */
static function setDefaultAdmin( $username, $password ) { static function setDefaultAdmin( $username, $password ) {
if( self::$username || self::$password ) if( self::$username || self::$password )
return; return;
self::$username = $username; self::$username = $username;
self::$password = $password; self::$password = $password;
} }
/** /**
* Set strict path checking. This prevents sharing of the session * Set strict path checking. This prevents sharing of the session
* across several sites in the domain. * across several sites in the domain.
* *
* @param strictPathChecking boolean to enable or disable strict patch checking. * @param strictPathChecking boolean to enable or disable strict patch checking.
*/ */
static function setStrictPathChecking($strictPathChecking) { static function setStrictPathChecking($strictPathChecking) {
self::$strictPathChecking = $strictPathChecking; self::$strictPathChecking = $strictPathChecking;
} }
static function getStrictPathChecking() { static function getStrictPathChecking() {
return self::$strictPathChecking; return self::$strictPathChecking;
} }

View File

@ -11,26 +11,35 @@
<% if IsAddMode %> <% if IsAddMode %>
<% else %> <% else %>
<% if ShowPagination %> <% if ShowPagination %>
<table class="PageControls"> <div id="Pagination">
<tr> <% if PopupPrevLink %>
<td class="Left"> <div id="Pagination_Previous">
<% if PopupFirstLink %><a href="$PopupFirstLink" title="View first $NameSingular"><img src="cms/images/pagination/record-first.png" alt="View first $NameSingular" /></a> <a href="$PopupPrevLink"><img src="cms/images/pagination/previousArrow.png" /></a>
<% else %><img src="cms/images/pagination/record-first-g.png" alt="View first $NameSingular" /><% end_if %> <a href="$PopupPrevLink"><div>Previous</div></a>
<% if PopupPrevLink %><a href="$PopupPrevLink" title="View previous $NameSingular"><img src="cms/images/pagination/record-prev.png" alt="View previous $NameSingular" /></a> </div>
<% else %><img src="cms/images/pagination/record-prev-g.png" alt="View previous $NameSingular" /><% end_if %> <% end_if %>
</td> <% if TotalCount == 1 %>
<td class="Count"> <% else %>
Displaying $PopupCurrentItem of $TotalCount <% control pagination %>
</td> <% if active %>
<td class="Right"> <a href="$link">$number</a>
<% if PopupNextLink %><a href="$PopupNextLink" title="View next $NameSingular"><img src="cms/images/pagination/record-next.png" alt="View next $NameSingular" /></a> <% else %>
<% else %><img src="cms/images/pagination/record-next-g.png" alt="View next $NameSingular" /><% end_if %> <span>$number</span>
<% if PopupLastLink %><a href="$PopupLastLink" title="View last $NameSingular"><img src="cms/images/pagination/record-last.png" alt="View last $NameSingular" /></a> <% end_if %>
<% else %><img src="cms/images/pagination/record-last-g.png" alt="View last $NameSingular" /><% end_if %> <% end_control %>
</td> <% end_if %>
</tr> <% if PopupNextLink %>
</table> <div id="Pagination_Next">
<a href="$PopupNextLink"><img src="cms/images/pagination/nextArrow.png" /></a>
<a href="$PopupNextLink"><div>Next</div></a>
</div>
<% end_if %>
<% end_if %> <% end_if %>
<% end_if %> <% end_if %>
<script type="text/javascript">
divQ = $('Pagination').getElementsByTagName('div').length;
aQ = $('Pagination').getElementsByTagName('a').length - divQ + 1;
$('Pagination').style.width = aQ * 15 + 130 + "px";
</script>
</body> </body>
</html> </html>