Merge branch 'master' of github.com:silverstripe/sapphire

This commit is contained in:
Ingo Schommer 2012-05-02 14:17:47 +02:00
commit 6c859a7622
9 changed files with 163 additions and 14 deletions

View File

@ -11,4 +11,6 @@ PasswordEncryptor:
md5_v2.4:
PasswordEncryptor_PHPHash: md5
sha1_v2.4:
PasswordEncryptor_PHPHash: sha1
PasswordEncryptor_PHPHash: sha1
blowfish:
PasswordEncryptor_Blowfish:

View File

@ -33,6 +33,8 @@ class SS_Backtrace {
array('PasswordEncryptor_MySQLPassword', 'salt'),
array('PasswordEncryptor_MySQLOldPassword', 'encrypt'),
array('PasswordEncryptor_MySQLOldPassword', 'salt'),
array('PasswordEncryptor_Blowfish', 'encrypt'),
array('PasswordEncryptor_Blowfish', 'salt'),
);
/**

View File

@ -28,6 +28,16 @@ class GridFieldDetailForm implements GridField_URLHandler {
*/
protected $validator;
/**
* @var String
*/
protected $itemRequestClass;
/**
* @var function With two parameters: $form and $component
*/
protected $itemEditFormCallback;
function getURLHandlers($gridField) {
return array(
'item/$ID' => 'handleItem',
@ -64,11 +74,7 @@ class GridFieldDetailForm implements GridField_URLHandler {
$record = Object::create($gridField->getModelClass());
}
if(ClassInfo::exists(get_class($this) . "_ItemRequest")) {
$class = get_class($this) . "_ItemRequest";
} else {
$class = 'GridFieldDetailForm_ItemRequest';
}
$class = $this->getItemRequestClass();
$handler = Object::create($class, $gridField, $this, $record, $controller, $this->name);
$handler->setTemplate($this->template);
@ -120,6 +126,41 @@ class GridFieldDetailForm implements GridField_URLHandler {
public function getValidator() {
return $this->validator;
}
/**
* @param String
*/
public function setItemRequestClass($class) {
$this->itemRequestClass = $class;
return $this;
}
/**
* @return String
*/
public function getItemRequestClass() {
if($this->itemRequestClass) {
return $this->itemRequestClass;
} else if(ClassInfo::exists(get_class($this) . "_ItemRequest")) {
return get_class($this) . "_ItemRequest";
} else {
return 'GridFieldItemRequest_ItemRequest';
}
}
/**
* @param Closure $cb Make changes on the edit form after constructing it.
*/
public function setItemEditFormCallback(Closure $cb) {
$this->itemEditFormCallback = $cb;
}
/**
* @return Closure
*/
public function getItemEditFormCallback() {
return $this->itemEditFormCallback;
}
}
class GridFieldDetailForm_ItemRequest extends RequestHandler {
@ -269,6 +310,10 @@ class GridFieldDetailForm_ItemRequest extends RequestHandler {
// e.g. page/edit/show/6/ vs. page/edit/EditForm/field/MyGridField/....
$form->Backlink = $toplevelController->Link();
}
$cb = $this->component->getItemEditFormCallback();
if($cb) $cb($form, $this);
return $form;
}

View File

@ -94,12 +94,23 @@ class Group extends DataObject {
$permissionsField->setHiddenPermissions(SecurityAdmin::$hidden_permissions);
if($this->ID) {
$group = $this;
$config = new GridFieldConfig_RelationEditor();
$config->addComponents(new GridFieldExportButton('before'));
$config->addComponents(new GridFieldPrintButton('before'));
$config->getComponentByType('GridFieldAddExistingAutocompleter')
->setResultsFormat('$Title ($Email)')->setSearchFields(array('FirstName', 'Surname', 'Email'));
$config->getComponentByType('GridFieldDetailForm')->setValidator(new Member_Validator());
$config->getComponentByType('GridFieldDetailForm')
->setValidator(new Member_Validator())
->setItemEditFormCallback(function($form, $component) use($group) {
// If new records are created in a group context,
// set this group by default.
$record = $form->getRecord();
if($record && !$record->ID) {
$groupsField = $form->Fields()->dataFieldByName('DirectGroups');
if($groupsField) $groupsField->setValue($group->ID);
}
});
$memberList = GridField::create('Members',false, $this->Members(), $config)->addExtraClass('members_grid');
// @todo Implement permission checking on GridField
//$memberList->setPermissions(array('edit', 'delete', 'export', 'add', 'inlineadd'));

View File

@ -116,7 +116,50 @@ abstract class PasswordEncryptor {
}
/**
* This is the default class used for built-in hash types in PHP.
* Blowfish encryption - this is the default from SilverStripe 3.
* PHP 5.3+ will provide a php implementation if there is no system
* version available.
*
* @package framework
* @subpackage security
*/
class PasswordEncryptor_Blowfish extends PasswordEncryptor {
/**
* Cost of encryption.
* Higher costs will increase security, but also increase server load.
* If you are using basic auth, you may need to decrease this as encryption
* will be run on every request.
* Must be between 4 and 31.
*/
protected static $cost = 10;
function encrypt($password, $salt = null, $member = null) {
// We use $2y$ here instead of $2a$ - in PHP < 5.3.7, passwords
// with non-ascii characters will use a flawed version of the blowfish
// algorithm when specified with $2a$. $2y$ specifies non-flawed version
// in all cases.
// See https://bugs.php.net/bug.php?id=55477&edit=1
$method_and_salt = '$2y$' . $salt;
$encrypted_password = crypt($password, $method_and_salt);
// We *never* want to generate blank passwords. If something
// goes wrong, throw an exception.
if(strpos($encrypted_password, $method_and_salt) === false) {
throw new PasswordEncryptor_EncryptionFailed('Blowfish password encryption failed.');
}
// Remove the method and salt from the password, as the salt
// is stored in a separate column.
return substr($encrypted_password, strlen($method_and_salt));
}
function salt($password, $memeber = null) {
$generator = new RandomGenerator();
return self::$cost . '$' . substr($generator->generateHash('sha1'), 0, 21);
}
}
/**
* Encryption using built-in hash types in PHP.
* Please note that the implemented algorithms depend on the PHP
* distribution and architecture.
*
@ -240,3 +283,9 @@ class PasswordEncryptor_None extends PasswordEncryptor {
* @subpackage security
*/
class PasswordEncryptor_NotFoundException extends Exception {}
/**
* @package framework
* @subpackage security
*/
class PasswordEncryptor_EncryptionFailed extends Exception {}

View File

@ -50,7 +50,7 @@ class Security extends Controller {
*
* @var string
*/
protected static $encryptionAlgorithm = 'sha1_v2.4';
protected static $encryptionAlgorithm = 'blowfish';
/**
* Showing "Remember me"-checkbox

View File

@ -126,6 +126,32 @@ class GridFieldDetailFormTest extends FunctionalTest {
// Fourth level form would be a Category detail view
}
function testCustomItemRequestClass() {
$component = new GridFieldDetailForm();
$this->assertEquals('GridFieldDetailForm_ItemRequest', $component->getItemRequestClass());
$component->setItemRequestClass('GridFieldDetailFormTest_ItemRequest');
$this->assertEquals('GridFieldDetailFormTest_ItemRequest', $component->getItemRequestClass());
}
function testItemEditFormCallback() {
$category = new GridFieldDetailFormTest_Category();
$component = new GridFieldDetailForm();
$component->setItemEditFormCallback(function($form, $component) {
$form->Fields()->push(new HiddenField('Callback'));
});
// Note: A lot of scaffolding to execute the tested logic,
// due to the coupling of form creation with request handling (and its context)
$request = new GridFieldDetailForm_ItemRequest(
GridField::create('Categories', 'Categories'),
$component,
$category,
new Controller(),
'Form'
);
$form = $request->ItemEditForm();
$this->assertNotNull($form->Fields()->fieldByName('Callback'));
}
}
class GridFieldDetailFormTest_Person extends DataObject implements TestOnly {
@ -230,3 +256,6 @@ class GridFieldDetailFormTest_GroupController extends Controller implements Test
return new Form($this, 'Form', new FieldList($field), new FieldList());
}
}
class GridFieldDetailFormTest_ItemRequest extends GridFieldDetailForm_ItemRequest implements TestOnly {
}

View File

@ -44,13 +44,13 @@ class PasswordEncryptorTest extends SapphireTest {
$this->assertNotContains('test', array_keys(PasswordEncryptor::get_encryptors()));
}
function testEncrytorPHPHashWithArguments() {
function testEncryptorPHPHashWithArguments() {
Config::inst()->update('PasswordEncryptor', 'encryptors', array('test_md5'=>array('PasswordEncryptor_PHPHash'=>'md5')));
$e = PasswordEncryptor::create_for_algorithm('test_md5');
$this->assertEquals('md5', $e->getAlgorithm());
}
function testEncrytorPHPHash() {
function testEncryptorPHPHash() {
Config::inst()->update('PasswordEncryptor', 'encryptors', array('test_sha1'=>array('PasswordEncryptor_PHPHash'=>'sha1')));
$e = PasswordEncryptor::create_for_algorithm('test_sha1');
$password = 'mypassword';
@ -60,8 +60,19 @@ class PasswordEncryptorTest extends SapphireTest {
$e->encrypt($password, $salt)
);
}
function testEncryptorBlowfish() {
Config::inst()->update('PasswordEncryptor', 'encryptors', array('test_blowfish'=>array('PasswordEncryptor_Blowfish'=>'')));
$e = PasswordEncryptor::create_for_algorithm('test_blowfish');
$password = 'mypassword';
$salt = '10$mysaltmustbetwen2char';
$this->assertEquals(
crypt($password, '$2y$' . $salt),
'$2y$' . $salt . $e->encrypt($password, $salt)
);
}
function testEncrytorPHPHashCompare() {
function testEncryptorPHPHashCompare() {
Config::inst()->update('PasswordEncryptor', 'encryptors', array('test_sha1'=>array('PasswordEncryptor_PHPHash'=>'sha1')));
$e = PasswordEncryptor::create_for_algorithm('test_sha1');
$this->assertTrue($e->compare(sha1('mypassword'), sha1('mypassword')));
@ -74,7 +85,7 @@ class PasswordEncryptorTest extends SapphireTest {
* Handy command for reproducing via CLI on different architectures:
* php -r "echo(base_convert(sha1('mypassword'), 16, 36));"
*/
function testEncrytorLegacyPHPHashCompare() {
function testEncryptorLegacyPHPHashCompare() {
Config::inst()->update('PasswordEncryptor', 'encryptors', array('test_sha1legacy'=>array('PasswordEncryptor_LegacyPHPHash'=>'sha1')));
$e = PasswordEncryptor::create_for_algorithm('test_sha1legacy');
// precomputed hashes for 'mypassword' from different architectures

View File

@ -14,7 +14,7 @@ class EncryptAllPasswordsTaskTest extends SapphireTest {
$t->run(null);
$m = DataObject::get_by_id('Member', $m->ID);
$this->assertEquals($m->PasswordEncryption, 'sha1_v2.4');
$this->assertEquals($m->PasswordEncryption, 'blowfish');
$this->assertNotEquals($m->Password, 'plain');
$result = $m->checkPassword('plain');
$this->assertTrue($result->valid());