diff --git a/core/control/ContentController.php b/core/control/ContentController.php index 937695801..ba17be469 100644 --- a/core/control/ContentController.php +++ b/core/control/ContentController.php @@ -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 = "Published Site"; $stageLink = "Draft Site"; $message = "
Archived site from
" . $dateObj->Nice() . "
"; - + } else if(Versioned::current_stage() == 'Stage') { $stageLink = "Draft Site"; $liveLink = "Published Site"; @@ -235,7 +238,7 @@ JS $stageLink = "Draft Site"; $message = "
PUBLISHED SITE
"; } - + if($member) { $firstname = Convert::raw2xml($member->FirstName); $surname = Convert::raw2xml($member->Surame); @@ -254,7 +257,7 @@ JS
$logInMessage
- +
View page in:
$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;

Click here to delete the install files.

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, diff --git a/security/Authenticator.php b/security/Authenticator.php index b0527cf85..34056562c 100644 --- a/security/Authenticator.php +++ b/security/Authenticator.php @@ -1,5 +1,10 @@ + * + * @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); } ?> \ No newline at end of file diff --git a/security/Member.php b/security/Member.php index a90713931..9200e4b65 100644 --- a/security/Member.php +++ b/security/Member.php @@ -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 remember login token 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. + * A B C will result in firstname A B and surname C + * + * @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: * + * + * $fields->push( + * new CheckboxSetField( + * "NewsletterSubscriptions", + * "Receive email notification of events in ", + * $sourceitems = DataObject::get("NewsletterType")->toDropDownMap("GroupID","Title"), + * $selectedgroups = $member->Groups()->Map("ID","ID") + * ) + * ); + * * * On the form handler: - $groups = $member->Groups(); - $checkboxfield = $form->Fields()->fieldByName("NewsletterSubscriptions"); - $groups->setByCheckboxSetField($checkboxfield); * + * + * $groups = $member->Groups(); + * $checkboxfield = $form->Fields()->fieldByName("NewsletterSubscriptions"); + * $groups->setByCheckboxSetField($checkboxfield); + * + * + * @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 {

Your password has been changed. Please keep this email, for future reference.

'; } -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; } } + + ?> \ No newline at end of file diff --git a/security/MemberAuthenticator.php b/security/MemberAuthenticator.php index 5368529cc..8c63605cd 100644 --- a/security/MemberAuthenticator.php +++ b/security/MemberAuthenticator.php @@ -1,5 +1,13 @@ + */ + + + /** * 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"); } } diff --git a/security/MemberLoginForm.php b/security/MemberLoginForm.php index fa54d0e84..55a303f0b 100644 --- a/security/MemberLoginForm.php +++ b/security/MemberLoginForm.php @@ -1,4 +1,11 @@ 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 $checkCurrentUser 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 { diff --git a/security/OpenIDAuthenticator.php b/security/OpenIDAuthenticator.php index 375e1fb1e..d924584ce 100644 --- a/security/OpenIDAuthenticator.php +++ b/security/OpenIDAuthenticator.php @@ -1,5 +1,13 @@ + */ + + + /** * 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 { "", "", $form_html, + "

Click "Continue" to login. You are only seeing this because you appear to have JavaScript disabled.

", ""); 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 */ -class SessionWrapper { +class SessionWrapper extends Auth_Yadis_PHPSession { /** * Set a session key/value pair. diff --git a/security/OpenIDLoginForm.php b/security/OpenIDLoginForm.php index 90fd3f45d..aba95eedb 100644 --- a/security/OpenIDLoginForm.php +++ b/security/OpenIDLoginForm.php @@ -1,5 +1,12 @@ + */ + + /** * 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 $checkCurrentUser 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(); } } diff --git a/security/Security.php b/security/Security.php index 03296ecf4..d61283875 100644 --- a/security/Security.php +++ b/security/Security.php @@ -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 = "

$message

"; + } else { + $message = "

$message

"; + } + + $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" => "

Enter your e-mail address and we will send you a password

", + "Content" => + "

Enter your e-mail address and we will send you a password

", "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" => "

Thank you, your password has been sent to '$email'.

", + "Content" => + "

Thank you, your password has been sent to '$email'.

", )); //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;