(merged from branches/roa. use "svn log -c <changeset> -g <module-svn-path>" for detailed commit message)

git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/trunk@60287 467b73ca-7a2a-4603-9d3b-597d59a354a9
This commit is contained in:
Ingo Schommer 2008-08-11 00:14:48 +00:00
parent a3cc9db9cb
commit ad4d506f82
8 changed files with 177 additions and 15 deletions

View File

@ -335,4 +335,22 @@ class HTTPRequest extends Object implements ArrayAccess {
function allParsed() {
return sizeof($this->dirParts) <= $this->unshiftedButParsedParts;
}
/**
* Returns the client IP address which
* originated this request.
*
* @return string
*/
function getIP() {
if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
//check ip from share internet
return $_SERVER['HTTP_CLIENT_IP'];
} elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
//to check ip is pass from proxy
return $_SERVER['HTTP_X_FORWARDED_FOR'];
} else {
return $_SERVER['REMOTE_ADDR'];
}
}
}

View File

@ -177,4 +177,13 @@ class RequestHandlingData extends ViewableData {
$r->setStatuscode($errorCode);
return $r;
}
/**
* Returns the HTTPRequest object that this controller is using.
*
* @return HTTPRequest
*/
function getRequest() {
return $this->request;
}
}

View File

@ -599,7 +599,7 @@ class DataObject extends ViewableData implements DataObjectInterface {
// New records have their insert into the base data table done first, so that they can pass the
// generated primary key on to the rest of the manipulation
if(!$this->record['ID'] && isset($ancestry[0])) {
if((!isset($this->record['ID']) || !$this->record['ID']) && isset($ancestry[0])) {
$baseTable = $ancestry[0];
DB::query("INSERT INTO `{$baseTable}` SET Created = NOW()");
@ -2212,17 +2212,14 @@ class DataObject extends ViewableData implements DataObjectInterface {
$defaultRecords = $this->stat('default_records');
if(!empty($defaultRecords)) {
// Populate with default data if table is empty
$baseClass = ClassInfo::baseDataClass($this->class);
if($baseClass) {
$hasData = (DB::query("SELECT ID FROM `{$baseClass}`")->value());
if(!$hasData) {
foreach($defaultRecords as $record) {
$obj = new $baseClass($record);
$obj->write();
}
Database::alteration_message("Added default records to $baseClass table","created");
$hasData = DataObject::get_one($this->class);
if(!$hasData) {
$className = $this->class;
foreach($defaultRecords as $record) {
$obj = new $className($record);
$obj->write();
}
Database::alteration_message("Added default records to $className table","created");
}
}

View File

@ -6,9 +6,19 @@
* @subpackage actions
*/
class FormAction extends FormField {
protected $extraData;
protected $action;
/**
* Enables the use of <button> instead of <input>
* in {@link Field()} - for more customizeable styling.
*
* @var boolean $useButtonTag
*/
public $useButtonTag = false;
/**
* Create a new action button.
* @param action The method to call when the button is clicked
@ -46,7 +56,12 @@ class FormAction extends FormField {
function Field() {
$titleAttr = $this->description ? "title=\"" . Convert::raw2att($this->description) . "\"" : '';
return "<input class=\"action " . $this->extraClass() . "\" id=\"" . $this->id() . "\" type=\"submit\" name=\"$this->action\" value=\"" . $this->attrTitle() . "\" $titleAttr />\n";
if($this->useButtonTag) {
return "<button class=\"action " . $this->extraClass() . "\" id=\"" . $this->id() . "\" type=\"submit\" name=\"$this->action\" $titleAttr />" . $this->attrTitle() . "</button>\n";
} else {
return "<input class=\"action " . $this->extraClass() . "\" id=\"" . $this->id() . "\" type=\"submit\" name=\"$this->action\" value=\"" . $this->attrTitle() . "\" $titleAttr />\n";
}
}
/**

26
security/LoginAttempt.php Normal file
View File

@ -0,0 +1,26 @@
<?php
/**
* Record all login attempts through the {@link LoginForm} object.
* This behaviour is disabled by default.
*
* Enable through a setting in your _config.php:
* <code>
* Security::set_login_recording(true);
* </code>
*
* @package sapphire
* @subpackage security
*/
class LoginAttempt extends DataObject {
static $db = array(
'Email' => 'Varchar(255)',
'Status' => "Enum('Success,Failure')",
);
static $has_one = array(
'Member' => 'Member', // only linked if the member actually exists
);
}
?>

View File

@ -34,6 +34,23 @@ class MemberAuthenticator extends Authenticator {
$member = null;
}
}
// Optionally record every login attempt as a {@link LoginAttempt} object
if(Security::login_recording()) {
$attempt = new LoginAttempt();
if($member) {
// successful login (member is existing with matching password)
$attempt->MemberID = $member->ID;
$attempt->Status = 'Success';
} else {
// failed login - we're trying to see if a user exists with this email (disregarding wrong passwords)
$existingMember = DataObject::get_one("Member", "Email = '$SQL_user'");
if($existingMember) $attempt->MemberID = $existingMember->ID;
$attempt->Status = 'Failure';
}
$attempt->Email = $RAW_data['Email'];
$attempt->write();
}
if($member) {
Session::clear("BackURL");

View File

@ -80,6 +80,14 @@ class Security extends Controller {
return Security::$wordlist;
}
/**
* Enable or disable recording of login attempts
* through the {@link LoginRecord} object.
*
* @var boolean $login_recording
*/
protected static $login_recording = false;
/**
* Set location of word list file
*
@ -896,8 +904,25 @@ class Security extends Controller {
(($permissionFields = DB::fieldList('Permission')) && isset($permissionFields['Type'])) &&
(($memberFields = DB::fieldList('Member')) && isset($memberFields['RememberLoginToken']));
}
/**
* Enable or disable recording of login attempts
* through the {@link LoginRecord} object.
*
* @param boolean $bool
*/
public static function set_login_recording($bool) {
self::$login_recording = (bool)$bool;
}
/**
* @return boolean
*/
public static function login_recording() {
return self::$login_recording;
}
}
?>
?>

View File

@ -128,6 +128,42 @@ class SecurityTest extends SapphireTest {
$member2 = DataObject::get_by_id("Member", $this->idFromFixture('Member', 'noexpiry'));
$this->assertNotNull($member2->LockedOutUntil);
}
function testUnsuccessfulLoginAttempts() {
Security::set_login_recording(true);
/* UNSUCCESSFUL ATTEMPTS WITH WRONG PASSWORD FOR EXISTING USER ARE LOGGED */
$this->get('Security/login');
$this->doTestLoginFormFunctional('sam@silverstripe.com', 'wrongpassword');
$attempt = DataObject::get_one('LoginAttempt', 'Email = "sam@silverstripe.com"');
$this->assertTrue(is_object($attempt));
$member = DataObject::get_one('Member', 'Email = "sam@silverstripe.com"');
$this->assertEquals($attempt->Status, 'Failure');
$this->assertEquals($attempt->Email, 'sam@silverstripe.com');
$this->assertEquals($attempt->Member(), $member);
/* UNSUCCESSFUL ATTEMPTS WITH NONEXISTING USER ARE LOGGED */
$this->get('Security/login');
$this->doTestLoginFormFunctional('wronguser@silverstripe.com', 'wrongpassword');
$attempt = DataObject::get_one('LoginAttempt', 'Email = "wronguser@silverstripe.com"');
$this->assertTrue(is_object($attempt));
$this->assertEquals($attempt->Status, 'Failure');
$this->assertEquals($attempt->Email, 'wronguser@silverstripe.com');
}
function testSuccessfulLoginAttempts() {
Security::set_login_recording(true);
/* SUCCESSFUL ATTEMPTS ARE LOGGED */
$this->get('Security/login');
$this->doTestLoginFormFunctional('sam@silverstripe.com', '1nitialPassword');
$attempt = DataObject::get_one('LoginAttempt', 'Email = "sam@silverstripe.com"');
$member = DataObject::get_one('Member', 'Email = "sam@silverstripe.com"');
$this->assertTrue(is_object($attempt));
$this->assertEquals($attempt->Status, 'Success');
$this->assertEquals($attempt->Email, 'sam@silverstripe.com');
$this->assertEquals($attempt->Member(), $member);
}
/**
* Execute a log-in form using Director::test().
@ -144,11 +180,30 @@ class SecurityTest extends SapphireTest {
);
}
/**
* Execute a log-in form using Director::test().
* Helper method for the tests above
*/
function doTestLoginFormFunctional($email, $password) {
$this->submitForm(
"MemberLoginForm_LoginForm",
null,
array(
'Email' => $email,
'Password' => $password,
'AuthenticationMethod' => 'MemberAuthenticator',
'action_dologin' => 1,
'BackURL' => 'test/link'
)
);
}
/**
* Get the error message on the login form
*/
function loginErrorMessage($session) {
return $session->inst_get('FormInfo.MemberLoginForm_LoginForm.formError.message');
}
}
}
}
?>