mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
(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:
parent
a3cc9db9cb
commit
ad4d506f82
@ -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'];
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
26
security/LoginAttempt.php
Normal 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
|
||||
);
|
||||
|
||||
}
|
||||
?>
|
@ -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");
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
?>
|
||||
?>
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
?>
|
Loading…
Reference in New Issue
Block a user