mlanthaler:OpenID authentication works now. To use it simple create an user account without password and put your *complete* OpenID identifier in the email field, e.g. "http://markus-lanthaler.myopenid.com/".

You can get a free OpenID from http://www.myopenid.com  (merged from branches/gsoc)


git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/trunk@41785 467b73ca-7a2a-4603-9d3b-597d59a354a9
This commit is contained in:
Ingo Schommer 2007-09-14 19:10:18 +00:00
parent b0e9e89aad
commit fbc375a282
8 changed files with 956 additions and 415 deletions

View File

@ -7,9 +7,9 @@
* section of an application.
*
* On its own, content controller does very little. Its constructor is passed a {@link DataObject}
* which is stored in $this->dataRecord. Any unrecognised method calls, for example, Title()
* and Content(), will be passed along to the data record,
*
* which is stored in $this->dataRecord. Any unrecognised method calls, for example, Title()
* and Content(), will be passed along to the data record,
*
* Subclasses of ContentController are generally instantiated by ModelAsController; this will create
* a controller based on the URLSegment action variable, by looking in the SiteTree table.
*/
@ -26,7 +26,7 @@ class ContentController extends Controller {
parent::__construct();
}
public function Link($action = null) {
return Director::baseURL() . $this->RelativeLink($action);
}
@ -34,7 +34,7 @@ class ContentController extends Controller {
if($this->URLSegment){
if($action == "index") $action = "";
// '&' in a URL is apparently naughty
$action = preg_replace('/&/', '&', $action);
return $this->URLSegment . "/$action";
@ -42,10 +42,10 @@ class ContentController extends Controller {
user_error("ContentController::RelativeLink() No URLSegment given on a '$this->class' object. Perhaps you should overload it?", E_USER_WARNING);
}
}
//----------------------------------------------------------------------------------//
// These flexible data methods remove the need for custom code to do simple stuff
/*
* Return the children of the given page.
* $parentRef can be a page number or a URLSegment
@ -62,12 +62,12 @@ class ContentController extends Controller {
}
}
public function Page($url) {
$SQL_url = Convert::raw2sql($url);
return DataObject::get_one('SiteTree', "URLSegment = '$SQL_url'");
}
public function init() {
parent::init();
@ -84,7 +84,7 @@ class ContentController extends Controller {
singleton('SiteTree')->extend('contentcontrollerInit', $this);
Director::set_site_mode('site');
// Check permissions
if($this->dataRecord && !$this->dataRecord->can('View')) Security::permissionFailure($this);
@ -92,7 +92,7 @@ class ContentController extends Controller {
Requirements::themedCSS("typography");
Requirements::themedCSS("form");
}
/**
* Get the project name
*
@ -109,9 +109,9 @@ class ContentController extends Controller {
public function data() {
return $this->dataRecord;
}
/*--------------------------------------------------------------------------------*/
/**
* Returns a fixed navigation menu of the given level.
*/
@ -147,7 +147,7 @@ class ContentController extends Controller {
* Returns the page in the current page stack of the given level.
* Level(1) will return the main menu item that we're currently inside, etc.
*/
public function Level($level) {
$parent = $this->data();
$stack = array($parent);
@ -157,25 +157,28 @@ class ContentController extends Controller {
return isset($stack[$level-1]) ? $stack[$level-1] : null;
}
public function Menu($level) {
return $this->getMenu($level);
}
public function Section2() {
return $this->Level(2)->URLSegment;
}
/**
* Returns the default log-in form.
*/
*
* @todo Check if here should be returned just the default log-in form or
* all available log-in forms (also OpenID...)
*/
public function LoginForm() {
return Object::create('LoginForm', $this, "LoginForm");
return MemberAuthenticator::getLoginForm($this);
}
public function SilverStripeNavigator() {
$member = Member::currentUser();
if(Director::isDev() || ($member && $member->isCMSUser())) {
Requirements::css('sapphire/css/SilverStripeNavigator.css');
@ -188,8 +191,8 @@ class ContentController extends Controller {
var w = window.open(this.href,windowName(this.target));
w.focus();
return false;
}
}
}
}
});
function windowName(suffix) {
@ -213,9 +216,9 @@ JS
$thisPage = $this->Link();
$cmsLink = '';
}
$archiveLink = "";
if($date = Versioned::current_archived_date()) {
$dateObj = Object::create('Datetime', $date, null);
// $dateObj->setVal($date);
@ -224,7 +227,7 @@ JS
$liveLink = "<a href=\"$thisPage?stage=Live\" target=\"site\" style=\"left : -3px;\">Published Site</a>";
$stageLink = "<a href=\"$thisPage?stage=Stage\" target=\"site\" style=\"left : -1px;\">Draft Site</a>";
$message = "<div id=\"SilverStripeNavigatorMessage\">Archived site from<br>" . $dateObj->Nice() . "</div>";
} else if(Versioned::current_stage() == 'Stage') {
$stageLink = "<a class=\"current\">Draft Site</a>";
$liveLink = "<a href=\"$thisPage?stage=Live\" target=\"site\" style=\"left : -3px;\">Published Site</a>";
@ -235,7 +238,7 @@ JS
$stageLink = "<a href=\"$thisPage?stage=Stage\" target=\"site\" style=\"left : -1px;\">Draft Site</a>";
$message = "<div id=\"SilverStripeNavigatorMessage\">PUBLISHED SITE</div>";
}
if($member) {
$firstname = Convert::raw2xml($member->FirstName);
$surname = Convert::raw2xml($member->Surame);
@ -254,7 +257,7 @@ JS
<div id="logInStatus">
$logInMessage
</div>
<div id="switchView" class="bottomTabs">
<div class="blank"> View page in: </div>
$cmsLink
@ -291,15 +294,15 @@ HTML;
}
}
}
/**
* Throw an error to test the error system
*/
function throwerror() {
user_error("This is a test of the error handler - nothing to worry about.", E_USER_ERROR);
}
/**
* Throw a warning to test the error system
*/
@ -339,13 +342,13 @@ HTML;
<div style="margin-left: auto; margin-right: auto; width: 50%;"><p><a href="home/deleteinstallfiles" style="text-align: center;">Click here to delete the install files.</a></p></div></div>
HTML
);
return array(
"Title" => $title,
"Content" => $content,
);
}
function deleteinstallfiles() {
$title = new Varchar("Title");
$content = new HTMLText("Content");
@ -396,7 +399,7 @@ HTML
HTML
;
$content->setValue($tempcontent);
return array(
"Title" => $title,
"Content" => $content,

View File

@ -1,5 +1,10 @@
<?php
/**
* Authenticator base class
*/
/**
* Abstract base class for an authentication method
@ -8,6 +13,8 @@
* methods like {@link MemberAuthenticator} or {@link OpenIDAuthenticator}.
*
* @author Markus Lanthaler <markus@silverstripe.com>
*
* @todo Wouldn't be an interface be the better choice?
*/
abstract class Authenticator extends Object
{
@ -15,19 +22,24 @@ abstract class Authenticator extends Object
* Method to authenticate an user
*
* @param array $RAW_data Raw data to authenticate the user
* @param Form $form Optional: If passed, better error messages can be
* produced by using
* {@link Form::sessionMessage()}
* @return bool|Member Returns FALSE if authentication fails, otherwise
* the member object
*/
public abstract function authenticate(array $RAW_data);
public abstract function authenticate(array $RAW_data, Form $form = null);
/**
* Method that creates the login form for this authentication method
*
* @param Controller The parent controller, necessary to create the
* appropriate form action tag
* @return Form Returns the login form to use with this authentication
* method
*/
public abstract function getLoginForm();
public abstract static function getLoginForm(Controller $controller);
}
?>

View File

@ -6,20 +6,19 @@ class Member extends DataObject {
'Surname' => "Varchar",
'Email' => "Varchar",
'Password' => "Varchar",
'RememberLoginToken' => "Varchar(50)",
'NumVisit' => "Int",
'LastVisited' => 'Datetime',
'Bounced' => 'Boolean',
'AutoLoginHash' => 'Varchar(10)',
'AutoLoginExpired' => 'Datetime',
'BlacklistedEmail' => 'Boolean',
'Bounced' => 'Boolean',
'AutoLoginHash' => 'Varchar(10)',
'AutoLoginExpired' => 'Datetime',
'BlacklistedEmail' => 'Boolean',
);
static $belongs_many_many = array(
"Groups" => "Group",
);
static $has_one = array(
);
static $has_many = array(
'UnsubscribedRecords' => 'Member_UnsubscribeRecord'
);
@ -30,23 +29,86 @@ class Member extends DataObject {
'Email' => true,
);
/**
* Logs this member in.
* Logs this member in
*
* @param bool $remember If set to TRUE, the member will be logged in
* automatically the next time.
*/
function logIn() {
function logIn($remember = false) {
session_regenerate_id(true);
Session::set("loggedInAs", $this->ID);
$this->NumVisit++;
if($remember) {
$token = substr(md5(uniqid(rand(), true)), 0, 49 - strlen($this->ID));
$this->RememberLoginToken = $token;
Cookie::set('alc_enc', $this->ID . ':' . $token);
} else {
$this->RememberLoginToken = null;
Cookie::set('alc_enc', null);
Cookie::forceExpiry('alc_enc');
}
$this->write();
}
/**
*Logs this member in.
* Log the user in if the "remember login" cookie is set
*
* The <i>remember login token</i> will be changed on every successful
* auto-login.
*/
function logOut(){
Cookie::set('alc_enc',null);
Session::clear("loggedInAs");
static function autoLogin() {
if(isset($_COOKIE['alc_enc']) && !Session::get("loggedInAs")) {
list($uid, $token) = explode(':', $_COOKIE['alc_enc'], 2);
$uid = Convert::raw2sql($uid);
$token = Convert::raw2sql($token);
$member = DataObject::get_one(
"Member", "Member.ID = $uid And RememberLoginToken = '$token'");
if($member) {
session_regenerate_id(true);
Session::set("loggedInAs", $member->ID);
$token = substr(md5(uniqid(rand(), true)),
0, 49 - strlen($member->ID));
$member->RememberLoginToken = $token;
Cookie::set('alc_enc', $member->ID . ':' . $token);
$member->NumVisit++;
$member->write();
}
}
}
/**
* Logs this member out.
*/
function logOut() {
Session::clear("loggedInAs");
session_regenerate_id(true);
$this->RememberLoginToken = null;
Cookie::set('alc_enc', null);
Cookie::forceExpiry('alc_enc');
$this->write();
}
/**
* Generate an auto login hash
*
* @todo This is relative insecure, check if we should fix it
*/
function generateAutologinHash() {
$linkHash = sprintf('%10d', time() );
@ -59,31 +121,51 @@ class Member extends DataObject {
$this->write();
}
/**
* Log a member in with an auto login hash link
*/
static function autoLoginHash( $RAW_hash ) {
$SQL_hash = Convert::raw2sql( $RAW_hash );
static function autoLoginHash($RAW_hash) {
$SQL_hash = Convert::raw2sql($RAW_hash);
$member = DataObject::get_one('Member',"`AutoLoginHash`='$SQL_hash' AND `AutoLoginExpired` > NOW()");
if( $member )
if($member)
$member->logIn();
return $member;
}
function sendInfo($type = 'signup'){
/**
* Send signup, change password or forgot password informations to an user
*
* @param string $type Information type to send ("signup",
* "changePassword" or "forgotPassword")
*/
function sendInfo($type = 'signup') {
switch($type) {
case "signup": $e = new Member_SignupEmail(); break;
case "changePassword": $e = new Member_ChangePasswordEmail(); break;
case "forgotPassword": $e = new Member_ForgotPasswordEmail(); break;
case "signup":
$e = new Member_SignupEmail();
break;
case "changePassword":
$e = new Member_ChangePasswordEmail();
break;
case "forgotPassword":
$e = new Member_ForgotPasswordEmail();
break;
}
$e->populateTemplate($this);
$e->send();
}
/**
* Returns the fields for the member form
*
* @return FieldSet Returns a {@link FieldSet} containing the fields for
* the member form.
*/
function getMemberFormFields() {
return new FieldSet(
new TextField("FirstName", "First Name"),
@ -93,108 +175,84 @@ class Member extends DataObject {
);
}
/**
* Factory method for the member validator
*
* @return Member_Validator Returns an instance of a
* {@link Member_Validator} object.
*/
function getValidator() {
return new Member_Validator();
}
/**
* Returns the currenly logged in user
* @todo get_one() is a bit funky.
* Returns the current logged in user
*
* @return bool|Member Returns the member object of the current logged in
* user or FALSE.
*/
static function currentUser() {
self::autoLogin();
$id = Session::get("loggedInAs");
if(!$id) {
self::autoLogin();
$id = Session::get("loggedInAs");
}
// Return the details
if($id = Session::get("loggedInAs")) {
if($id) {
return DataObject::get_one("Member", "Member.ID = $id");
}
}
static function autoLogin() {
// Auto-login
if(isset($_COOKIE['alc_enc']) && !Session::get("loggedInAs")) {
// Deliberately obscure...
list($data['Email'], $data['Password']) = explode(':',base64_decode($_COOKIE['alc_enc']),2);
$lf = new LoginForm(null, null, null, null, false);
$lf->performLogin($data);
}
}
/**
* Get the ID of the current logged in user
*
* @return int Returns the ID of the current logged in user or 0.
*/
static function currentUserID() {
self::autoLogin();
$id = Session::get("loggedInAs");
if(!$id) {
self::autoLogin();
$id = Session::get("loggedInAs");
}
return is_numeric($id) ? $id : 0;
}
/**
* before the save of this member, the blacklisted email table is updated to ensure no
* promotional material is sent to the member. (newsletters)
* standard system messages are still sent such as receipts.
* Add the members email address to the blacklist
*
* With this method the blacklisted email table is updated to ensure that
* no promotional material is sent to the member (newsletters).
* Standard system messages are still sent such as receipts.
*
* @param bool $val Set to TRUE if the address should be added to the
* blacklist, otherwise to FALSE.
* @return
* @todo Check for what the parameter $val is needed! (Markus)
*/
function setBlacklistedEmail($val){
if($val && $this->Email){
function setBlacklistedEmail($val) {
if($val && $this->Email) {
$blacklisting = new Email_BlackList();
$blacklisting->BlockedEmail = $this->Email;
$blacklisting->MemberID = $this->ID;
$blacklisting->write();
}
return $this->setField("BlacklistedEmail",$val);
$this->setField("BlacklistedEmail", $val);
}
function onBeforeWrite() {
// If an email's filled out
if($this->Email) {
// Look for a record with the same email
if($this->ID) $idClause = "AND `Member`.ID <> $this->ID";
else $idClause = "";
$existingRecord = DataObject::get_one("Member", "Email = '" . addslashes($this->Email) . "' $idClause");
// Debug::message("Found an existing member for email $this->Email");
// If found
if($existingRecord) {
// Update this record to merge with that member
$newID = $existingRecord->ID;
if($this->ID) {
DB::query("UPDATE Group_Members SET MemberID = $newID WHERE MemberID = $this->ID");
}
$this->ID = $newID;
// Merge existing data into the local record
foreach($existingRecord->getAllFields() as $k => $v) {
if(!isset($this->changed[$k]) || !$this->changed[$k]) $this->record[$k] = $v;
}
}
}
parent::onBeforeWrite();
}
/**
* Check if the member is in one of the given groups
*/
public function inGroups( $groups ) {
foreach( $this->Groups() as $group )
$memberGroups[] = $group->Title;
return count( array_intersect( $memberGroups, $groups ) ) > 0;
}
public function inGroup( $groupID ) {
foreach( $this->Groups() as $group )
if( $groupID == $group->ID )
return true;
return false;
}
/*
* Generate a random password
* BDC - added randomiser to kick in if there's no words file on the filesystem.
*
* BDC - added randomiser to kick in if there's no words file on the
* filesystem.
*
* @return string Returns a random password.
*/
static function createNewPassword() {
if(file_exists('/usr/share/silverstripe/wordlist.txt')) {
@ -214,17 +272,105 @@ class Member extends DataObject {
return $output;
}
}
/**
* Event handler called before writing to the database
*
* If an email's filled out look for a record with the same email and if
* found update this record to merge with that member.
*/
function onBeforeWrite() {
if($this->Email) {
if($this->ID) {
$idClause = "AND `Member`.ID <> $this->ID";
} else {
$idClause = "";
}
$existingRecord = DataObject::get_one(
"Member", "Email = '" . addslashes($this->Email) . "' $idClause");
// Debug::message("Found an existing member for email $this->Email");
if($existingRecord) {
$newID = $existingRecord->ID;
if($this->ID) {
DB::query("UPDATE Group_Members SET MemberID = $newID WHERE MemberID = $this->ID");
}
$this->ID = $newID;
// Merge existing data into the local record
foreach($existingRecord->getAllFields() as $k => $v) {
if(!isset($this->changed[$k]) || !$this->changed[$k]) $this->record[$k] = $v;
}
}
}
parent::onBeforeWrite();
}
/**
* Check if the member is in one of the given groups
*
* @param array $groups Groups to check
* @return bool Returns TRUE if the member is in one of the given groups,
* otherwise FALSE.
*/
public function inGroups(array $groups) {
foreach($this->Groups() as $group)
$memberGroups[] = $group->Title;
return count(array_intersect($memberGroups, $groups)) > 0;
}
/**
* Check if the member is in the given group
*
* @param int $groupID ID of the group to check
* @return bool Returns TRUE if the member is in the given group,
* otherwise FALSE.
*/
public function inGroup($groupID) {
foreach($this->Groups() as $group) {
if($groupID == $group->ID)
return true;
}
return false;
}
/**
* Alias for {@link inGroup}
*
* @param int $groupID ID of the group to check
* @return bool Returns TRUE if the member is in the given group,
* otherwise FALSE.
* @see inGroup()
*/
public function isInGroup($groupID) {
return $this->inGroup($groupID);
}
/**
* Returns true if this user is an administrator.
* Administrators have access to everything. The lucky bastards! ;-)
*
*
* @return Returns TRUE if this user is an administrator.
* @todo Should this function really exists? Is not {@link isAdmin()} the
* only right name for this?
* @todo Is {@link Group}::CanCMSAdmin not deprecated?
*/
function _isAdmin() {
if($groups = $this->Groups()) {
foreach($groups as $group) if($group->CanCMSAdmin) return true;
foreach($groups as $group) {
if($group->CanCMSAdmin)
return true;
}
}
return Permission::check('ADMIN');
@ -245,41 +391,79 @@ class Member extends DataObject {
}
function _isCMSUser() {
if($groups = $this->Groups()) {
foreach($groups as $group) if($group->CanCMS) return true;
foreach($groups as $group) {
if($group->CanCMS)
return true;
}
}
}
//----------------------------------------------------------------------------------------//
//------------------- HELPER METHODS -----------------------------------//
/**
* Get the complete name of the member
*
* @return string Returns the first- and surname of the member. If the ID
* of the member is equal 0, only the surname is returned.
* @todo Check for what this method is used! (Markus)
*/
public function getTitle() {
if($this->getField('ID') === 0)
return $this->getField('Surname');
return $this->getField('Surname') . ', ' . $this->getField('FirstName');
}
/**
* Get the complete name of the member
*
* @return string Returns the first- and surname of the member.
*/
public function getName() {
return $this->FirstName . ' ' . $this->Surname;
}
public function setName( $name ) {
$nameParts = explode( ' ', $name );
$this->Surname = array_pop( $nameParts );
$this->FirstName = join( ' ', $nameParts );
/**
* Set first- and surname
*
* This method assumes that the last part of the name is the surname, e.g.
* <i>A B C</i> will result in firstname <i>A B</i> and surname <i>C</i>
*
* @param string $name The name
*/
public function setName($name) {
$nameParts = explode(' ', $name);
$this->Surname = array_pop($nameParts);
$this->FirstName = join(' ', $nameParts);
}
public function splitName( $name ) {
return $this->setName( $name );
/**
* Alias for {@link setName}
*
* @param string $name The name
* @see setName()
*/
public function splitName($name) {
return $this->setName($name);
}
//----------------------------------------------------------------------------------------//
//---------------------------------------------------------------------//
/**
* @todo Figure out what this function does and document it! (Markus)
*/
public function Groups() {
$groups = $this->getManyManyComponents("Groups");
$unsecure = DataObject::get("Group_Unsecure", "");
if($unsecure) foreach($unsecure as $unsecureItem) {
$groups->push($unsecureItem);
if($unsecure) {
foreach($unsecure as $unsecureItem) {
$groups->push($unsecureItem);
}
}
$groupIDs = $groups->column();
@ -303,17 +487,13 @@ class Member extends DataObject {
return $result;
}
public function isInGroup($groupID) {
$groups = $this->Groups();
foreach($groups as $group) {
if($group->ID == $groupID) return true;
}
return false;
}
/**
* @todo Figure out what this function does and document it! (Markus)
*/
public function map($filter = "", $sort = "", $blank="") {
$ret = new SQLMap(singleton('Member')->extendedSQL($filter, $sort));
if($blank){
if($blank) {
$blankMember = new Member();
$blankMember->Surname = $blank;
$blankMember->ID = 0;
@ -324,72 +504,102 @@ class Member extends DataObject {
}
public static function mapInGroups( $groups = null ) {
if( !$groups )
/**
* @todo Figure out what this function does and document it! (Markus)
*/
public static function mapInGroups($groups = null) {
if(!$groups)
return Member::map();
$groupIDList = array();
if( is_a( $groups, 'DataObjectSet' ) )
if(is_a($groups, 'DataObjectSet')) {
foreach( $groups as $group )
$groupIDList[] = $group->ID;
elseif( is_array( $groups ) )
} elseif(is_array($groups)) {
$groupIDList = $groups;
else
} else {
$groupIDList[] = $groups;
}
if( empty( $groupIDList ) )
if(empty($groupIDList))
return Member::map();
return new SQLMap( singleton('Member')->extendedSQL( "`GroupID` IN (" . implode( ',', $groupIDList ) . ")", "Surname, FirstName", "", "INNER JOIN `Group_Members` ON `MemberID`=`Member`.`ID`") );
return new SQLMap(singleton('Member')->extendedSQL(
"`GroupID` IN (" . implode( ',', $groupIDList ) .
")", "Surname, FirstName", "", "INNER JOIN `Group_Members` ON `MemberID`=`Member`.`ID`"));
}
/**
* Return a map of all members in the groups given that have CMS permissions
* Defaults to all groups with CMS permissions
* Get a map of all members in the groups given that have CMS permissions
*
* If no groups are passed, all groups with CMS permissions will be used.
*
* @param array $groups Groups to consider or NULL to use all groups with
* CMS permissions.
* @return SQLMap Returns a map of all members in the groups given that
* have CMS permissions.
*/
public static function mapInCMSGroups( $groups = null ) {
if( !$groups || $groups->Count() == 0 )
$groups = DataObject::get('Group',"", "", "INNER JOIN `Permission` ON `Permission`.GroupID = `Group`.ID AND `Permission`.Code IN ('ADMIN', 'CMS_ACCESS_AssetAdmin')");
public static function mapInCMSGroups($groups = null) {
if(!$groups || $groups->Count() == 0) {
$groups = DataObject::get('Group', "", "",
"INNER JOIN `Permission` ON `Permission`.GroupID = `Group`.ID AND `Permission`.Code IN ('ADMIN', 'CMS_ACCESS_AssetAdmin')");
}
$groupIDList = array();
if( is_a( $groups, 'DataObjectSet' ) )
foreach( $groups as $group )
if(is_a($groups, 'DataObjectSet')) {
foreach($groups as $group)
$groupIDList[] = $group->ID;
elseif( is_array( $groups ) )
} elseif(is_array($groups)) {
$groupIDList = $groups;
}
/*if( empty( $groupIDList ) )
return Member::map(); */
$filterClause = ($groupIDList) ? "`GroupID` IN (" . implode( ',', $groupIDList ) . ")" : "";
$filterClause = ($groupIDList)
? "`GroupID` IN (" . implode( ',', $groupIDList ) . ")"
: "";
return new SQLMap( singleton('Member')->extendedSQL( $filterClause, "Surname, FirstName", "", "INNER JOIN `Group_Members` ON `MemberID`=`Member`.`ID` INNER JOIN `Group` ON `Group`.`ID`=`GroupID`") );
return new SQLMap(singleton('Member')->extendedSQL($filterClause,
"Surname, FirstName", "",
"INNER JOIN `Group_Members` ON `MemberID`=`Member`.`ID` INNER JOIN `Group` ON `Group`.`ID`=`GroupID`"));
}
/**
* When passed an array of groups, and a component set of groups, this function
* will return the array of groups the member is NOT in.
* @param grouplist an array of group code names.
* @param memberGroups a component set of groups ( set to $this->groups() by default )
*/
public function memberNotInGroups($groupList,$memberGroups = null){
if(!$memberGroups) $memberGroups = $this->Groups();
foreach($memberGroups as $group){
if(in_array($group->Code,$groupList)){
$index = array_search($group->Code,$groupList);
/**
* Get the groups in which the member is NOT in
*
* When passed an array of groups, and a component set of groups, this
* function will return the array of groups the member is NOT in.
*
* @param array $groupList An array of group code names.
* @param array $memberGroups A component set of groups (if set to NULL,
* $this->groups() will be used)
* @return array Groups in which the member is NOT in.
*/
public function memberNotInGroups($groupList, $memberGroups = null){
if(!$memberGroups)
$memberGroups = $this->Groups();
foreach($memberGroups as $group) {
if(in_array($group->Code, $groupList)) {
$index = array_search($group->Code, $groupList);
unset($groupList[$index]);
}
}
return $groupList;
}
/**
* Return a FieldSet of fields that would appropriate for editing this member.
* Return a {@link FieldSet} of fields that would appropriate for editing
* this member.
*
* @return FieldSet Return a FieldSet of fields that would appropriate for
* editing this member.
*/
public function getCMSFields() {
$fields = new FieldSet(
@ -413,12 +623,19 @@ class Member extends DataObject {
return $fields;
}
function unsubscribeFromNewsletter( $newsletterType ) {
/**
* Unsubscribe from newsletter
*
* @param NewsletterType $newsletterType Newsletter type to unsubscribe
* from
*/
function unsubscribeFromNewsletter(NewsletterType $newsletterType) {
// record today's date in unsubscriptions
// this is a little bit redundant
$unsubscribeRecord = new Member_UnsubscribeRecord();
$unsubscribeRecord->unsubscribe( $this, $newsletterType );
$this->Groups()->remove( $newsletterType->GroupID );
// this is a little bit redundant
$unsubscribeRecord = new Member_UnsubscribeRecord();
$unsubscribeRecord->unsubscribe($this, $newsletterType);
$this->Groups()->remove($newsletterType->GroupID);
}
function requireDefaultRecords() {
@ -431,161 +648,208 @@ class Member extends DataObject {
}
}
/**
* Special kind of ComponentSet that has special methods for manipulating a user's membership
* Special kind of {@link ComponentSet} that has special methods for
* manipulating a user's membership
*/
class Member_GroupSet extends ComponentSet {
/**
* Control group membership with a number of checkboxes.
* - If the checkbox fields are present in $data, then the member will be added to the group with the same codename.
* - If the checkbox fields are *NOT* present in $data, then the member willb e removed from the group with the same codename.
* @param checkboxes an array list of the checkbox fieldnames (Only values are used.) eg array(0,1,2);
* @param data The form data. usually in the format array(0 => 2) (just pass the checkbox data from your form);
* - If the checkbox fields are present in $data, then the member will be
* added to the group with the same codename.
* - If the checkbox fields are *NOT* present in $data, then the member
* will be removed from the group with the same codename.
*
* @param array $checkboxes An array list of the checkbox fieldnames (only
* values are used). E.g. array(0, 1, 2)
* @param array $data The form data. Uually in the format array(0 => 2)
* (just pass the checkbox data from your form)
*/
function setByCheckboxes($checkboxes, $data) {
function setByCheckboxes(array $checkboxes, array $data) {
foreach($checkboxes as $checkbox) {
if($data[$checkbox]){
if($data[$checkbox]) {
$add[] = $checkbox;
}else{
} else {
$remove[] = $checkbox;
}
}
if($add)$this->addManyByCodename($add);
if($remove) $this->removeManyByCodename($remove);
if($add)
$this->addManyByCodename($add);
if($remove)
$this->removeManyByCodename($remove);
}
/**
* Allows you to set groups based on a checkboxsetfield.
* (pass the form element from your post data directly to this method, and it
* will update the groups and add and remove the member as appropriate)
* @param checkboxsetField - the CheckboxSetField (with data) from your form.
* Allows you to set groups based on a CheckboxSetField
*
* On the form setup
*
$fields->push(
new CheckboxSetField(
"NewsletterSubscriptions",
"Receive email notification of events in ",
$sourceitems = DataObject::get("NewsletterType")->toDropDownMap("GroupID","Title"),
$selectedgroups = $member->Groups()->Map("ID","ID")
)
);
* Pass the form element from your post data directly to this method, and
* it will update the groups and add and remove the member as appropriate.
*
* On the form setup:
*
* <code>
* $fields->push(
* new CheckboxSetField(
* "NewsletterSubscriptions",
* "Receive email notification of events in ",
* $sourceitems = DataObject::get("NewsletterType")->toDropDownMap("GroupID","Title"),
* $selectedgroups = $member->Groups()->Map("ID","ID")
* )
* );
* </code>
*
* On the form handler:
$groups = $member->Groups();
$checkboxfield = $form->Fields()->fieldByName("NewsletterSubscriptions");
$groups->setByCheckboxSetField($checkboxfield);
*
* <code>
* $groups = $member->Groups();
* $checkboxfield = $form->Fields()->fieldByName("NewsletterSubscriptions");
* $groups->setByCheckboxSetField($checkboxfield);
* </code>
*
* @param CheckboxSetField $checkboxsetfield The CheckboxSetField (with
* data) from your form.
*/
function setByCheckboxSetField($checkboxsetfield){
function setByCheckboxSetField(CheckboxSetField $checkboxsetfield) {
// Get the values from the formfield.
$values = $checkboxsetfield->Value();
$sourceItems = $checkboxsetfield->getSource();
if($sourceItems){
if($sourceItems) {
// If (some) values are present, add and remove as necessary.
if($values){
if($values) {
// update the groups based on the selections
foreach($sourceItems as $k => $item){
if(in_array($k,$values)){
foreach($sourceItems as $k => $item) {
if(in_array($k,$values)) {
$add[] = $k;
}else{
} else {
$remove[] = $k;
}
}
// else we should be removing all from the necessary groups.
}else{
} else {
$remove = $sourceItems;
}
if($add)$this->addManyByGroupID($add);
if($remove) $this->RemoveManyByGroupID($remove);
if($add)
$this->addManyByGroupID($add);
}else{
USER_ERROR("Member::setByCheckboxSetField() - No source items could be found for checkboxsetfield ". $checkboxsetfield->Name(),E_USER_WARNING);
if($remove)
$this->RemoveManyByGroupID($remove);
} else {
USER_ERROR("Member::setByCheckboxSetField() - No source items could be found for checkboxsetfield " .
$checkboxsetfield->Name(), E_USER_WARNING);
}
}
/**
* Adds this member to the groups based on the
* groupID.
* Adds this member to the groups based on the group IDs
*
* @param array $ids Group identifiers.
*/
function addManyByGroupID($groupIds){
$groups = $this->getGroupsFromIDs($groupIds);
if($groups){
foreach($groups as $group){
if($groups) {
foreach($groups as $group) {
$this->add($group);
}
}
}
/**
* Removes the member from many groups based on
* the group ID.
* Removes the member from many groups based on the group IDs
*
* @param array $ids Group identifiers.
*/
function removeManyByGroupID($groupIds){
function removeManyByGroupID($groupIds) {
$groups = $this->getGroupsFromIDs($groupIds);
if($groups){
foreach($groups as $group){
if($groups) {
foreach($groups as $group) {
$this->remove($group);
}
}
}
/**
* Returns the groups from an array of GroupIDs
* Returns the groups from an array of group IDs
*
* @param array $ids Group identifiers.
* @return mixed Returns the groups from the array of Group IDs.
*/
function getGroupsFromIDs($ids){
if($ids && count($ids) > 1){
return DataObject::get("Group","ID IN (". implode(",",$ids) .")");
}else{
return DataObject::get_by_id("Group",$ids[0]);
if($ids && count($ids) > 1) {
return DataObject::get("Group", "ID IN (" . implode(",", $ids) . ")");
} else {
return DataObject::get_by_id("Group", $ids[0]);
}
}
/**
* Adds this member to the groups passed.
* Adds this member to the groups based on the group codenames
*
* @param array $codenames Group codenames
*/
function addManyByCodename($codenames) {
$groups = $this->codenamesToGroups($codenames);
if($groups){
if($groups) {
foreach($groups as $group){
$this->add($group);
}
}
}
/**
* Removes this member from the groups passed.
* Removes this member from the groups based on the group codenames
*
* @param array $codenames Group codenames
*/
function removeManyByCodename($codenames) {
$groups = $this->codenamesToGroups($codenames);
if($groups){
foreach($groups as $group){
if($groups) {
foreach($groups as $group) {
$this->remove($group);
}
}
}
/**
* Helper function to return the appropriate group via a codename.
* Helper function to return the appropriate groups via a codenames
*
* @param array $codenames Group codenames
* @return array Returns the the appropriate groups.
*/
protected function codenamesToGroups($codenames) {
$list = "'" . implode("', '", $codenames) . "'";
$output = DataObject::get("Group", "Code IN ($list)");
// Some are missing - throw warnings
if(!$output || $output->Count() != sizeof($list)) {
foreach($codenames as $codename) $missing[$codename] = $codename;
if($output) foreach($output as $record) unset($missing[$record->Code]);
if($missing) user_error("The following group-codes aren't matched to any groups: " . implode(", ", $missing) . ". You probably need to link up the correct group codes in phpMyAdmin", E_USER_WARNING);
if(!$output || ($output->Count() != sizeof($list))) {
foreach($codenames as $codename)
$missing[$codename] = $codename;
if($output) {
foreach($output as $record)
unset($missing[$record->Code]);
}
if($missing)
user_error("The following group-codes aren't matched to any groups: " .
implode(", ", $missing) .
". You probably need to link up the correct group codes in phpMyAdmin",
E_USER_WARNING);
}
return $output;
@ -594,9 +858,12 @@ class Member_GroupSet extends ComponentSet {
/**
* Class used as template to send an email to new members
*/
class Member_SignupEmail extends Email_Template {
protected
$from = 'ask@perweek.co.nz',
$from = '', // setting a blank from address uses the site's default administrator email
$to = '$Email',
$subject = "Thanks for signing up",
$body = '
@ -644,17 +911,19 @@ class Member_SignupEmail extends Email_Template {
function MemberData() {
return $this->template_data->listOfFields(
"FirstName","Surname","Email",
"Phone","Mobile","Street",
"Suburb","City","Postcode","DriversLicense5A","DriversLicense5B"
"FirstName", "Surname", "Email",
"Phone", "Mobile", "Street",
"Suburb", "City", "Postcode", "DriversLicense5A", "DriversLicense5B"
);
}
}
/**
* Send an email saying that the password has been reset.
*/
/**
* Class used as template to send an email saying that the password has been
* changed
*/
class Member_ChangePasswordEmail extends Email_Template {
protected $from = ''; // setting a blank from address uses the site's default administrator email
protected $subject = "Your password has been changed";
@ -662,31 +931,57 @@ class Member_ChangePasswordEmail extends Email_Template {
protected $to = '$Email';
}
/**
* Class used as template to send the forgot password email
*/
class Member_ForgotPasswordEmail extends Email_Template {
protected $from = '';
protected $from = ''; // setting a blank from address uses the site's default administrator email
protected $subject = "Your password";
protected $ss_template = 'ForgotPasswordEmail';
protected $to = '$Email';
}
/**
* Record to keep track of which records a member has unsubscribed from and when
* Record to keep track of which records a member has unsubscribed from and
* when
*
* @todo Check if that email stuff ($from, $to, $subject, $body) is needed
* here! (Markus)
*/
class Member_UnsubscribeRecord extends DataObject {
static $has_one = array(
'NewsletterType' => 'NewsletterType',
'Member' => 'Member'
);
static $has_one = array(
'NewsletterType' => 'NewsletterType',
'Member' => 'Member'
);
/**
* Unsubscribe the member from a specific newsletter type
*
* @param int|Member $member Member object or ID
* @param int|NewsletterType $newsletterType Newsletter type object or ID
*/
function unsubscribe($member, $newsletterType) {
// $this->UnsubscribeDate()->setVal( 'now' );
$this->MemberID = (is_numeric($member))
? $member
: $member->ID;
$this->NewsletterTypeID = (is_numeric($newletterType))
? $newsletterType
: $newsletterType->ID;
$this->write();
}
function unsubscribe( $member, $newsletterType ) {
// $this->UnsubscribeDate()->setVal( 'now' );
$this->MemberID = ( is_numeric( $member ) ) ? $member : $member->ID;
$this->NewsletterTypeID = ( is_numeric( $newletterType ) ) ? $newsletterType : $newsletterType->ID;
$this->write();
}
protected
$from = 'ask@perweek.co.nz',
$from = '', // setting a blank from address uses the site's default administrator email
$to = '$Email',
$subject = "Your password has been changed",
$body = '
@ -698,9 +993,19 @@ class Member_UnsubscribeRecord extends DataObject {
<p>Your password has been changed. Please keep this email, for future reference.</p>';
}
class Member_Validator extends RequiredFields {
protected $customRequired = array('FirstName', 'Email', 'Password');
/**
* Member Validator
*/
class Member_Validator extends RequiredFields {
protected $customRequired = array('FirstName', 'Email'); //, 'Password');
/**
* Constructor
*/
public function __construct() {
$required = func_get_args();
if(isset($required[0]) && is_array($required[0])) {
@ -708,24 +1013,43 @@ class Member_Validator extends RequiredFields {
}
$required = array_merge($required, $this->customRequired);
parent::__construct($required);
parent::__construct($required);
}
/**
* Check if the submitted member data is valid
*
* Check if a member with that email doesn't already exist, or if it does
* that it is this member.
*
* @param array $data Submitted data
* @return bool Returns TRUE if the submitted data is valid, otherwise
* FALSE.
*/
function php($data) {
$valid = parent::php($data);
// Check if a member with that email doesn't already exist, or if it does that it is this member.
$member = DataObject::get_one('Member', "Email = '". Convert::raw2sql($data['Email']) ."'");
// if we are in a complex table field popup, use ctf[childID], else use ID
$id = (isset($_REQUEST['ctf']['childID'])) ? $_REQUEST['ctf']['childID'] : $_REQUEST['ID'];
$member = DataObject::get_one('Member',
"Email = '". Convert::raw2sql($data['Email']) ."'");
// if we are in a complex table field popup, use ctf[childID], else use
// ID
$id = (isset($_REQUEST['ctf']['childID']))
? $_REQUEST['ctf']['childID']
: $_REQUEST['ID'];
if(is_object($member) && $member->ID != $id) {
$emailField = $this->form->dataFieldByName('Email');
$this->validationError($emailField->id(), "There already exists a member with this email", "required");
$this->validationError($emailField->id(),
"There already exists a member with this email",
"required");
$valid = false;
}
return $valid;
}
}
?>

View File

@ -1,5 +1,13 @@
<?php
/**
* Member authenticator
*
* @author Markus Lanthaler <markus@silverstripe.com>
*/
/**
* Authenticator for the default "member" method
*
@ -11,18 +19,25 @@ class MemberAuthenticator extends Authenticator {
* Method to authenticate an user
*
* @param array $RAW_data Raw data to authenticate the user
* @param Form $form Optional: If passed, better error messages can be
* produced by using
* {@link Form::sessionMessage()}
* @return bool|Member Returns FALSE if authentication fails, otherwise
* the member object
*/
public function authenticate(array $RAW_data) {
public function authenticate(array $RAW_data, Form $form = null) {
$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'");
"Member", "Email = '$SQL_user' AND Password = '$SQL_password'");
if($member) {
Session::clear("BackURL");
} else if(!is_null($form)) {
$form->sessionMessage(
"That doesn't seem to be the right email address or password. Please try again.",
"bad");
}
return $member;
@ -32,11 +47,13 @@ class MemberAuthenticator extends Authenticator {
/**
* Method that creates the login form for this authentication method
*
* @param Controller The parent controller, necessary to create the
* appropriate form action tag
* @return Form Returns the login form to use with this authentication
* method
*/
public function getLoginForm() {
return Object::create("MemberLoginForm", $this, "LoginForm");
public static function getLoginForm(Controller $controller) {
return Object::create("MemberLoginForm", $controller, "LoginForm");
}
}

View File

@ -1,4 +1,11 @@
<?php
/**
* Log-in form for the "member" authentication method
*/
/**
* Log-in form for the "member" authentication method
*/
@ -7,13 +14,22 @@ class MemberLoginForm extends Form {
/**
* Constructor
*
* @param $controller
* @param $name
* @param $fields
* @param $actions
* @param $checkCurrentUser
* @param Controller $controller The parent controller, necessary to
* create the appropriate form action tag.
* @param string $name The method on the controller that will return this
* form object.
* @param FieldSet|FormField $fields All of the fields in the form - a
* {@link FieldSet} of {@link FormField}
* objects.
* @param FieldSet|FormAction $actions All of the action buttons in the
* form - a {@link FieldSet} of
* {@link FormAction} objects
* @param bool $checkCurrentUser If set to TRUE, it will be checked if a
* the user is currently logged in, and if
* so, only a logout button will be rendered
*/
function __construct($controller, $name, $fields = null, $actions = null, $checkCurrentUser = true) {
function __construct($controller, $name, $fields = null, $actions = null,
$checkCurrentUser = true) {
$customCSS = project() . '/css/member_login.css';
if(Director::fileExists($customCSS)) {
@ -24,19 +40,21 @@ class MemberLoginForm extends Form {
$backURL = $_REQUEST['BackURL'];
} else {
$backURL = Session::get('BackURL');
//Session::clear("BackURL"); don't clear the back URL here! Should be used until the right password is entered!
}
if($checkCurrentUser && Member::currentUserID()) {
$fields = new FieldSet();
$actions = new FieldSet(new FormAction("logout", "Log in as someone else"));
$actions = new FieldSet(new FormAction("logout",
"Log in as someone else"));
} else {
if(!$fields) {
$fields = new FieldSet(
new HiddenField("AuthenticationMethod", null, "Member"),
new TextField("Email", "Email address", Session::get('SessionForms.MemberLoginForm.Email')),
new TextField("Email", "Email address",
Session::get('SessionForms.MemberLoginForm.Email')),
new EncryptField("Password", "Password"),
new CheckboxField("Remember", "Remember me next time?",true)
new CheckboxField("Remember", "Remember me next time?",
Session::get('SessionForms.MemberLoginForm.Remember'))
);
}
if(!$actions) {
@ -60,7 +78,8 @@ class MemberLoginForm extends Form {
*/
protected function getMessageFromSession() {
parent::getMessageFromSession();
if(($member = Member::currentUser()) && !Session::get('MemberLoginForm.force_message')) {
if(($member = Member::currentUser()) &&
!Session::get('MemberLoginForm.force_message')) {
$this->message = "You're logged in as $member->FirstName.";
}
Session::set('MemberLoginForm.force_message', false);
@ -75,18 +94,21 @@ class MemberLoginForm extends Form {
* @param array $data Submitted data
*/
public function dologin($data) {
if($this->performLogin($data)){
if($this->performLogin($data)) {
Session::clear('SessionForms.MemberLoginForm.Email');
Session::clear('SessionForms.MemberLoginForm.Remember');
if($backURL = $_REQUEST['BackURL']) {
Session::clear("BackURL");
Session::clear('SessionForms.MemberLoginForm.Email');
Director::redirect($backURL);
} else
Director::redirectBack();
} else {
Session::set('SessionForms.MemberLoginForm.Email', $data['Email']);
if($badLoginURL = Session::get("BadLoginURL")){
Session::set('SessionForms.MemberLoginForm.Remember',
isset($data['Remember']));
if($badLoginURL = Session::get("BadLoginURL")) {
Director::redirect($badLoginURL);
} else {
Director::redirectBack();
@ -96,13 +118,16 @@ class MemberLoginForm extends Form {
/**
* Log out
* Log out form handler method
*
* @todo Figure out for what this method is used! Is it really used at all?
* This method is called when the user clicks on "logout" on the form
* created when the parameter <i>$checkCurrentUser</i> of the
* {@link __construct constructor} was set to TRUE and the user was
* currently logged in.
*/
public function logout(){
public function logout() {
$s = new Security();
return $s->logout();
$s->logout();
}
@ -113,20 +138,16 @@ class MemberLoginForm extends Form {
* @return Member Returns the member object on successful authentication
* or NULL on failure.
*/
public function performLogin($data){
if($member = MemberAuthenticator::authenticate($data)) {
public function performLogin($data) {
if($member = MemberAuthenticator::authenticate($data, $this)) {
$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]"));
}
Session::set("Security.Message.message", "Welcome Back, {$firstname}");
Session::set("Security.Message.type", "good");
$member->LogIn(isset($data['Remember']));
return $member;
} else {
$this->sessionMessage("That doesn't seem to be the right email address or password. Please try again.", "bad");
return null;
}
}
@ -135,13 +156,15 @@ class MemberLoginForm extends Form {
/**
* Forgot password form handler method
*
* This method is called when the user clicks on "Log in"
* This method is called when the user clicks on "I've lost my password"
*
* @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($data['Email'] && $member = DataObject::get_one("Member",
"Member.Email = '$SQL_data[Email]'")) {
if(!$member->Password) {
$member->createNewPassword();
$member->write();
@ -151,7 +174,9 @@ class MemberLoginForm extends Form {
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");
$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 {

View File

@ -1,5 +1,13 @@
<?php
/**
* OpenID authenticator and controller
*
* @author Markus Lanthaler <markus@silverstripe.com>
*/
/**
* Require the OpenID consumer code.
*/
@ -29,10 +37,13 @@ class OpenIDAuthenticator extends Authenticator {
* Method to authenticate an user
*
* @param array $RAW_data Raw data to authenticate the user
* @param Form $form Optional: If passed, better error messages can be
* produced by using
* {@link Form::sessionMessage()}
* @return bool|Member Returns FALSE if authentication fails, otherwise
* the member object
*/
public function authenticate(array $RAW_data) {
public function authenticate(array $RAW_data, Form $form = null) {
$openid = $RAW_data['OpenIDURL'];
$trust_root = Director::absoluteBaseURL();
@ -59,9 +70,27 @@ class OpenIDAuthenticator extends Authenticator {
// No auth request means we can't begin OpenID.
if(!$auth_request) {
displayError("Authentication error; not a valid OpenID.");
if(!is_null($form)) {
$form->sessionMessage("That doesn't seem to be a valid OpenID " .
"or i-name identifier. Please try again.",
"bad");
}
return false;
}
$SQL_user = Convert::raw2sql($auth_request->endpoint->claimed_id);
if(!($member = DataObject::get_one("Member", "Email = '$SQL_user'"))) {
if(!is_null($form)) {
$form->sessionMessage("Either your account is not enabled for " .
"OpenID/i-name authentication " .
"or the entered identifier is wrong. " .
"Please try again.",
"bad");
}
return false;
}
/**
* @todo Check if the POST request should be send directly (without rendering a form)
@ -97,6 +126,7 @@ class OpenIDAuthenticator extends Authenticator {
"</title></head>",
"<body onload='document.getElementById(\"".$form_id."\").submit()'>",
$form_html,
"<p>Click &quot;Continue&quot; to login. You are only seeing this because you appear to have JavaScript disabled.</p>",
"</body></html>");
print implode("\n", $page_contents);
@ -109,11 +139,13 @@ class OpenIDAuthenticator extends Authenticator {
/**
* Method that creates the login form for this authentication method
*
* @param Controller The parent controller, necessary to create the
* appropriate form action tag
* @return Form Returns the login form to use with this authentication
* method
*/
public function getLoginForm() {
return Object::create("OpenIDLoginForm", $this, "LoginForm");
public static function getLoginForm(Controller $controller) {
return Object::create("OpenIDLoginForm", $controller, "LoginForm");
}
}
@ -135,11 +167,96 @@ class OpenIDAuthenticator_Controller extends Controller {
function run($requestParams) {
parent::init();
if(isset($_GET['debug_profile'])) Profiler::mark("OpenIDAuthenticator_Controller");
if(isset($_GET['debug_profile']))
Profiler::mark("OpenIDAuthenticator_Controller");
die("Not implemented yet!");
if(isset($_GET['debug_profile'])) Profiler::unmark("OpenIDAuthenticator_Controller");
/**
* This is where the example will store its OpenID information.
* You should change this path if you want the example store to be
* created elsewhere. After you're done playing with the example
* script, you'll have to remove this directory manually.
*/
$store_path = TEMP_FOLDER;
if(!file_exists($store_path) && !mkdir($store_path)) {
print "Could not create the FileStore directory '$store_path'. ".
" Please check the effective permissions.";
exit(0);
}
$store = new Auth_OpenID_FileStore($store_path);
$consumer = new Auth_OpenID_Consumer($store, new SessionWrapper());
// Complete the authentication process using the server's response.
$response = $consumer->complete();
if($response->status == Auth_OpenID_CANCEL) {
Session::set("Security.Message.message",
"The verification was cancelled. Please try again.");
Session::set("Security.Message.type", "bad");
if(isset($_GET['debug_profile']))
Profiler::unmark("OpenIDAuthenticator_Controller");
Director::redirect("Security/login");
} else if($response->status == Auth_OpenID_FAILURE) {
Session::set("Security.Message.message", // use $response->message ??
"The OpenID/i-name authentication failed.");
Session::set("Security.Message.type", "bad");
if(isset($_GET['debug_profile']))
Profiler::unmark("OpenIDAuthenticator_Controller");
Director::redirect("Security/login");
} else if($response->status == Auth_OpenID_SUCCESS) {
$openid = $response->identity_url;
$user = $openid;
if($response->endpoint->canonicalID) {
$user = $response->endpoint->canonicalID;
}
if(isset($_GET['debug_profile']))
Profiler::unmark("OpenIDAuthenticator_Controller");
$SQL_user = Convert::raw2sql($user);
if($member = DataObject::get_one("Member", "Email = '$SQL_user'")) {
$firstname = Convert::raw2xml($member->FirstName);
Session::set("Security.Message.message", "Welcome Back, {$firstname}");
Session::set("Security.Message.type", "good");
$member->LogIn(
Session::get('SessionForms.OpenIDLoginForm.Remember'));
Session::clear('SessionForms.OpenIDLoginForm.OpenIDURL');
Session::clear('SessionForms.OpenIDLoginForm.Remember');
if($backURL = Session::get("BackURL")) {
Session::clear("BackURL");
Director::redirect($backURL);
} else {
Director::redirectBack();
}
} else {
Session::set("Security.Message.message",
"Login failed. Please try again.");
Session::set("Security.Message.type", "bad");
if($badLoginURL = Session::get("BadLoginURL")) {
Director::redirect($badLoginURL);
} else {
Director::redirectBack();
}
}
}
}
@ -166,7 +283,7 @@ class OpenIDAuthenticator_Controller extends Controller {
*
* @author Markus Lanthaler <markus@silverstripe.com>
*/
class SessionWrapper {
class SessionWrapper extends Auth_Yadis_PHPSession {
/**
* Set a session key/value pair.

View File

@ -1,5 +1,12 @@
<?php
/**
* OpenID log-in form
*
* @author Markus Lanthaler <markus@silverstripe.com>
*/
/**
* OpenID log-in form
@ -11,11 +18,19 @@ class OpenIDLoginForm extends Form {
/**
* Constructor
*
* @param $controller
* @param $name
* @param $fields
* @param $actions
* @param $checkCurrentUser
* @param Controller $controller The parent controller, necessary to
* create the appropriate form action tag.
* @param string $name The method on the controller that will return this
* form object.
* @param FieldSet|FormField $fields All of the fields in the form - a
* {@link FieldSet} of {@link FormField}
* objects.
* @param FieldSet|FormAction $actions All of the action buttons in the
* form - a {@link FieldSet} of
* {@link FormAction} objects
* @param bool $checkCurrentUser If set to TRUE, it will be checked if a
* the user is currently logged in, and if
* so, only a logout button will be rendered
*/
function __construct($controller, $name, $fields = null, $actions = null,
$checkCurrentUser = true) {
@ -28,19 +43,20 @@ class OpenIDLoginForm extends Form {
$backURL = $_REQUEST['BackURL'];
} else {
$backURL = Session::get('BackURL');
//Session::clear("BackURL"); don't clear the back URL here! Should be used until the right password is entered!
}
if($checkCurrentUser && Member::currentUserID()) {
$fields = new FieldSet();
$actions = new FieldSet(new FormAction("logout", "Log in as someone else"));
$actions = new FieldSet(new FormAction("logout",
"Log in as someone else"));
} else {
if(!$fields) {
$fields = new FieldSet(
new HiddenField("AuthenticationMethod", null, "OpenID"),
new TextField("OpenIDURL", "OpenID URL",
Session::get('SessionForms.OpenIDLoginForm.OpenIDURL')),
new CheckboxField("Remember", "Remember me next time?", true)
Session::get('SessionForms.OpenIDLoginForm.OpenIDURL')),
new CheckboxField("Remember", "Remember me next time?",
Session::get('SessionForms.OpenIDLoginForm.Remember'))
);
}
if(!$actions) {
@ -79,60 +95,34 @@ class OpenIDLoginForm extends Form {
* @param array $data Submitted data
*/
public function dologin($data) {
if($this->performLogin($data)){
Session::set('SessionForms.OpenIDLoginForm.Remember',
isset($data['Remember']));
if($backURL = $_REQUEST['BackURL']) {
Session::clear("BackURL");
Session::clear('SessionForms.OpenIDLoginForm.OpenIDURL');
Director::redirect($backURL);
} else
Director::redirectBack();
OpenIDAuthenticator::authenticate($data, $this);
// If the OpenID authenticator returns, an error occured!
Session::set('SessionForms.OpenIDLoginForm.OpenIDURL',
$data['OpenIDURL']);
if($badLoginURL = Session::get("BadLoginURL")){
Director::redirect($badLoginURL);
} else {
Session::set('SessionForms.OpenIDLoginForm.OpenIDURL', $data['OpenIDURL']);
if($badLoginURL = Session::get("BadLoginURL")){
Director::redirect($badLoginURL);
} else {
Director::redirectBack();
}
Director::redirectBack();
}
}
/**
* Log out
* Log out form handler method
*
* @todo Figure out for what this method is used! Is it really used at all?
* This method is called when the user clicks on "logout" on the form
* created when the parameter <i>$checkCurrentUser</i> of the
* {@link __construct constructor} was set to TRUE and the user was
* currently logged in.
*/
public function logout() {
$s = new Security();
return $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(array $data) {
if($member = OpenIDAuthenticator::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[OpenIDURL]"));
}
return $member;
} else {
$this->sessionMessage("Login failed. Please try again.", "bad");
return null;
}
$s->logout();
}
}

View File

@ -18,20 +18,30 @@ class Security extends Controller {
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.
* @param page The controller that you were on to cause the permission failure.
* @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:
* - 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.
* If you don't provide a messageSet, a default will be used.
*
* @param $page The controller that you were on to cause the permission
* 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.
*/
static function permissionFailure($page = null, $messageSet = null) {
// $loginForm = singleton('Security')->LoginForm();
//user_error('debug', E_USER_ERROR);
static function permissionFailure($page, $messageSet = null) {
// Prepare the messageSet provided
if(!$messageSet) {
$messageSet = array(
@ -47,24 +57,24 @@ class Security extends Controller {
if(Member::currentUserID()) {
// user_error( 'PermFailure with member', E_USER_ERROR );
$message = $messageSet['alreadyLoggedIn'] ? $messageSet['alreadyLoggedIn'] : $messageSet['default'];
//$_SESSION['LoginForm']['force_form'] = true;
//$_SESSION['LoginForm']['type'] = 'warning';
$message = $messageSet['alreadyLoggedIn']
? $messageSet['alreadyLoggedIn']
: $messageSet['default'];
Member::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 {
$message = $messageSet['default'];
}
/* $loginForm->sessionMessage($message, 'warning');
Session::set("SecurityMessage.Error.message", $message);
Session::set("SecurityMessage.Error.type", $type);*/
Session::set("Security.Message.message", $message);
Session::set("Security.Message.type", 'warning');
// $_SESSION['LoginForm']['message'] = $message;
Session::set("BackURL", $_SERVER['REQUEST_URI']);
if(Director::is_ajax()) {
@ -85,10 +95,10 @@ class Security extends Controller {
switch($_REQUEST['AuthenticationMethod'])
{
case 'Member':
return MemberAuthenticator::GetLoginForm();
return MemberAuthenticator::GetLoginForm($this);
break;
case 'OpenID':
return OpenIDAuthenticator::GetLoginForm();
return OpenIDAuthenticator::GetLoginForm($this);
break;
}
}
@ -99,13 +109,16 @@ class Security extends Controller {
/**
* Get the login forms for all available authentication methods
*
* @return array Returns an array of available login forms (array of Form
* objects).
*
* @todo Check how to activate/deactivate authentication methods
*/
function GetLoginForms()
{
$forms = array();
array_push($forms, MemberAuthenticator::GetLoginForm());
array_push($forms, OpenIDAuthenticator::GetLoginForm());
array_push($forms, MemberAuthenticator::GetLoginForm($this));
array_push($forms, OpenIDAuthenticator::GetLoginForm($this));
return $forms;
}
@ -115,6 +128,7 @@ class Security extends Controller {
* Get a link to a security action
*
* @param string $action Name of the action
* @return string Returns the link to the given action
*/
function Link($action = null) {
return "Security/$action";
@ -130,15 +144,18 @@ class Security extends Controller {
* they should go.
*/
function logout($redirect = true) {
Cookie::set('alc_enc',null);
Cookie::forceExpiry('alc_enc');
Session::clear("loggedInAs");
if($redirect) Director::redirectBack();
if($member = Member::currentUser())
$member->logOut();
if($redirect)
Director::redirectBack();
}
/**
* Show the "login" page
*
* @return string Returns the "login" page as HTML code.
*/
function login() {
Requirements::javascript("jsparty/behaviour.js");
@ -164,9 +181,23 @@ class Security extends Controller {
foreach($forms as $form)
$content .= $form->forTemplate();
$customisedController = $controller->customise(array(
"Content" => $content
));
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>";
} else {
$message = "<p>$message</p>";
}
$customisedController = $controller->customise(array(
"Content" => $message,
"Form" => $content
));
} else {
$customisedController = $controller->customise(array(
"Content" => $content
));
}
return $customisedController->renderWith("Page");
}
@ -174,7 +205,9 @@ class Security extends Controller {
/**
* Show the "lost password" page
*
* @return string Returns the "lost password " page as HTML code.
*/
function lostpassword() {
Requirements::javascript("jsparty/prototype.js");
@ -189,7 +222,8 @@ class Security extends Controller {
$controller = new Page_Controller($tmpPage);
$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(),
));
@ -199,7 +233,9 @@ class Security extends Controller {
/**
* Show the "password sent" page
*
* @return string Returns the "password sent" page as HTML code.
*/
function passwordsent() {
Requirements::javascript("jsparty/behaviour.js");
@ -216,7 +252,8 @@ class Security extends Controller {
$email = $this->urlParams['ID'];
$customisedController = $controller->customise(array(
"Title" => "Password sent to '$email'",
"Content" => "<p>Thank you, your password has been sent to '$email'.</p>",
"Content" =>
"<p>Thank you, your password has been sent to '$email'.</p>",
));
//Controller::$currentController = $controller;
@ -225,7 +262,9 @@ class Security extends Controller {
/**
* Factory method for the lost password form
*
* @return Form Returns the lost password form
*/
function LostPasswordForm() {
return new MemberLoginForm($this, "LostPasswordForm", new FieldSet(
@ -238,17 +277,23 @@ class Security extends Controller {
/**
* Authenticate using the given email and password, returning the appropriate member object if
* 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
*/
static function authenticate($RAW_email, $RAW_password) {
$SQL_email = Convert::raw2sql($RAW_email);
$SQL_password = Convert::raw2sql($RAW_password);
// Default login (see {@setDetaultAdmin()})
if( $RAW_email == self::$username && $RAW_password == self::$password && !empty( self::$username ) && !empty( self::$password ) ) {
if(($RAW_email == self::$username) && ($RAW_password == self::$password)
&& !empty(self::$username) && !empty(self::$password)) {
$member = self::findAnAdministrator();
} 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;
@ -257,8 +302,12 @@ class Security extends Controller {
/**
* Return a member with administrator privileges
*
* @return Member Returns a member object that has administrator
* privileges.
*/
static function findAnAdministrator($username = 'admin', $password = 'password') {
static function findAnAdministrator($username = 'admin',
$password = 'password') {
$permission = DataObject::get_one("Permission", "`Code` = 'ADMIN'");
$adminGroup = null;
@ -292,10 +341,10 @@ class Security extends Controller {
/**
* 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()}.
* 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()}.
*
* @param $username String
* @param $password String (Cleartext)
@ -310,10 +359,12 @@ class Security extends Controller {
/**
* Set strict path checking. This prevents sharing of the session
* across several sites in the domain.
* Set strict path checking
*
* @param strictPathChecking boolean to enable or disable strict patch checking.
* This prevents sharing of the session across several sites in the domain.
*
* @param boolean $strictPathChecking To enable or disable strict patch
* checking.
*/
static function setStrictPathChecking($strictPathChecking) {
self::$strictPathChecking = $strictPathChecking;
@ -321,7 +372,9 @@ class Security extends Controller {
/**
* Get strict path checking
*
* @return boolean Status of strict path checking
*/
static function getStrictPathChecking() {
return self::$strictPathChecking;