I am trying to plug in my own legacy password service into Symfony3 to passively migrate users from a legacy database table.
The legacy system has passwords hashe with the same hard-coded $salt variables used across all members (therefore my FOSUserBundle table currently has the salt column empty for all members that are to be migrated).
The legacy method uses:
sha1($salt1.$password.$salt2)
The new method is Symfony's FOSUserBundle standard bcrypt hash.
I am trying to implement it so that when a legacy user first logs in, Symfony will try to:
Log in using FOSUserBundle's standard bcrypt method.
If #1 did not succeed then try the legacy algorithm.
If #2 succeeeds the password hash and salt in the database table will be updated to comply with standard FOSUserBundle method
I have been reading around about how to plug in a service to get this working and I think the below that I have seems to be correct in theory - if not any corrections/guidance would be appreciated as I've not been able to test it!
However, I'm unsure how I should go about connecting it all into Symfony so that the normal FOSUserBundle processes will carry out steps 2 and 3 if step 1 fails
services.yml:
parameters:
custom-password-encoder:
class: AppBundle\Security\LegacyPasswordEncoder
security.yml:
security:
encoders:
#FOS\UserBundle\Model\UserInterface: bcrypt Commented out to try the following alternative to give password migrating log in
FOS\UserBundle\Model\UserInterface: { id: custom-password-encoder }
BCryptPasswordEncoder (standard FOSUserBundle):
class BCryptPasswordEncoder extends BasePasswordEncoder
{
/* .... */
/**
* {#inheritdoc}
*/
public function encodePassword($raw, $salt)
{
if ($this->isPasswordTooLong($raw)) {
throw new BadCredentialsException('Invalid password.');
}
$options = array('cost' => $this->cost);
if ($salt) {
// Ignore $salt, the auto-generated one is always the best
}
return password_hash($raw, PASSWORD_BCRYPT, $options);
}
/**
* {#inheritdoc}
*/
public function isPasswordValid($encoded, $raw, $salt)
{
return !$this->isPasswordTooLong($raw) && password_verify($raw, $encoded);
}
}
LegacyPasswordEncoder:
namespace AppBundle\Security;
use Symfony\Component\Security\Core\Encoder\BasePasswordEncoder;
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
class LegacyPasswordEncoder extends BasePasswordEncoder
{
/**
* {#inheritdoc}
*/
public function encodePassword($raw,$salt)
{
if ($this->isPasswordTooLong($raw)) {
throw new BadCredentialsException('Invalid password.');
}
list($salt1,$salt2) = explode(",",$salt);
return sha1($salt1.$raw.$salt2);
}
/**
* {#inheritdoc}
*/
public function isPasswordValid($encoded, $raw, $salt)
{
list($salt1,$salt2) = explode(",",$salt);
return !$this->isPasswordTooLong($raw) && $this->comparePasswords($encoded,sha1($salt1.$raw.$salt2));
}
}
The solution to your problem is to use the Symfony feature allowing to change the password hashing algorithm dynamically based on the user: https://symfony.com/doc/current/cookbook/security/named_encoders.html
This way, you can mark any non-migrated user as using a legacy algorithm. Then, when updating the password, you would reset the algorithm being used before saving the user, so that the new password is hashed using the new stronger algorithm
Start by mapping your user class to the desired encoder:
security:
hide_user_not_found: false
encoders:
Cerad\Bundle\UserBundle\Entity\User: # Replace with your user class
id: cerad_user.user_encoder # Replace with the service id for your encoder
That should be enough to get your encoder plugged in.
Then you need to actually write your custom encoder by extending BCryptPasswordEncoder and override the isPasswordValid method. And of course create a service for it. Lots to learn.
How to call BcryptPasswordEncorder followed by LegacyPasswordEncoder? You don't. At least not directly. Symfony does not have a chain password encoder. Instead, write your own encoder and implement the chaining yourself.
class MyEncoder extends BCryptPasswordEncoder
{
function isPasswordValid($encoded,$raw,$salt)
{
// Check the bcrypt
if (parent::isPasswordValid($encoded,$raw,$salt)) return true;
// Copied from legacy
list($salt1,$salt2) = explode(",",$salt);
return
!$this->isPasswordTooLong($raw) &&
$this>comparePasswords($encoded,sha1($salt1.$raw.$salt2));
And make sure you define your encoder under services and not parameter. Also be sure to pass the cost (default is 13) as a constructor argument.
Related
I have used laravel fortify without jetstream.
The 2FA is always returning false.
Recovery codes are working but the 6 digit code always return false
class TwoFactorAuthenticatedSessionController extends Controller
{
...
/**
* Attempt to authenticate a new session using the two factor authentication code.
*
* #param \Laravel\Fortify\Http\Requests\TwoFactorLoginRequest $request
* #return mixed
*/
public function store(TwoFactorLoginRequest $request)
{
$user = $request->challengedUser();
if ($code = $request->validRecoveryCode()) {
$user->replaceRecoveryCode($code);
} elseif (! $request->hasValidCode()) { // This always return false
return app(FailedTwoFactorLoginResponse::class);
}
$this->guard->login($user, $request->remember());
return app(TwoFactorLoginResponse::class);
}
}
$request->hasValidCode()
public function hasValidCode()
{
return $this->code && app(TwoFactorAuthenticationProvider::class)->verify(
decrypt($this->challengedUser()->two_factor_secret), $this->code
);
}
I haven't changed the fortify backend. I have only converted the frontend with bootstrap.
I came across this post whilst searching for for an answer to the same question. I eventually found that my servers time was out by about 2 minutes. I enabled a NTP to keep the time synchronized and 2FA in Laravel started working without issues.
I am implementing Laravel 5.3 Notifications at the moment which is working very nice.
At the moment I am using 'email' as a notifications channel but I want to add 'database' too. I am using different databases/connections for languages and want to store the notifications in a central database / Connection.
How do I use a different database connection for notifications?
I already tried creating a Notifications model but that did not work:
namespace App;
use Illuminate\Database\Eloquent\Model;
class Notifications extends Model
{
protected $connection = 'system';
}
Hackish solution. But tried and tested on a MongoDB connection.
What needs to be modified;
The Notifiable trait
The DatabaseNotification model
Optionally (nothing changes if you are using mysql) modify the HasNotifications trait
Modify the DatabaseNotificationCollection.Again this is useful for a non-mysql connection
Step One : Create a custom Notifiable Trait
Copy the contents from Illuminate\Notifications\Notifiable and create a new file in your custom path...say App\Overrides\Notifications\Notifiable.
Your file will feature two changes...the namespace and you have to load the RoutesNotifications trait since we are not copying it over.
<?php
namespace App\Overrides\Notifications;
use use Illuminate\Notifications\RoutesNotifications;
trait Notifiable{
//The rest of the code remains
}
Step Two : Create a custom DatabaseNotification model
Follow the same procedure as above and copy the contents of the Illuminate\Notifications\DatabaseNotification file to the custom path that we created above...App\Overrides\Notification\DatabaseNotification
This is a standard Eloquent model and the connection change actually happens here
<?php
namespace App\Overrides\Notification;
//Use this if on mongodb.otherwise use to Illuminate\Database\Eloquent\Model
use Jenssegers\Mongodb\Eloquent\Model;
use Illuminate\Notifications\DatabaseNotificationCollection;
class DatabaseNotification extends Model
{
protected $connection = 'YOUR_CONNECTION_NAME_GOES HERE';
}
As of this point this should work if you are on a mysql connection.
To try this out change the Notifiable trait on the user model to use App\Overrides\Notifications\Notifiable. The notifications will use the connection you specified.
Users of MongoDB will have to take extra steps since the most popular driver I know of does not yet support MorphMany relations which are put to use for Laravel notifications.
Since that is not the asked question we leave it at that :-)
On Laravel 5.7 based on #Bernard answer
User.php
<?php
namespace App;
// implement the override Notifiable trait
use App\Traits\Override\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable
{
use Notifiable;
}
Notifiable.php
<?php
namespace App\Traits\Override;
use Illuminate\Notifications\RoutesNotifications;
trait Notifiable
{
use HasDatabaseNotifications, RoutesNotifications;
}
HasDatabaseNotifications.php
<?php
namespace App\Traits\Override;
use App\Models\Override\MultiConnectionDatabaseNotification;
trait HasDatabaseNotifications
{
/**
* Get the entity's notifications.
*
* #return \Illuminate\Database\Eloquent\Relations\MorphMany
*/
public function notifications()
{
return $this->morphMany(MultiConnectionDatabaseNotification::class, 'notifiable')->orderBy('created_at', 'desc');
}
/**
* Get the entity's read notifications.
*
* #return \Illuminate\Database\Query\Builder
*/
public function readNotifications()
{
return $this->notifications()->whereNotNull('read_at');
}
/**
* Get the entity's unread notifications.
*
* #return \Illuminate\Database\Query\Builder
*/
public function unreadNotifications()
{
return $this->notifications()->whereNull('read_at');
}
}
MultiConnectionDatabaseNotification.php
<?php
namespace App\Models\Override;
use Illuminate\Notifications\DatabaseNotification as DatabaseNotification;
class MultiConnectionDatabaseNotification extends DatabaseNotification
{
// set your preferred connection here
protected $connection = 'oracle';
}
It's pretty simple, Just add protected $connection = 'YOUR CONNECTION NAME'; at Illuminate\Notifications\DatabaseNotification
That's all and it will work :)
You don't need to create new models if you are going to use one notification table with same connection.
My code will works if ur using different connection for USER model.
SOLVED! I created a $password property in my Entity class thinking I needed to. The password field is added to the Entity through the entire request. None of the code posted below had to be changed.
I am using friendsofcake/Crud to build a REST api. When I save my User models, I have an afterSave() event that does the password salting and hashing. The POST data sent to my UsersController:add() method includes a ['password'] parameter, but my UsersTable has ['hash','salt'] fields.
Basically, I need to know if and where the POST['password'] parameter is, and if it is available in the UsersTable afterSave() method. Otherwise, my system is hashing and salting empty password strings!
I am new to CakePHP and having trouble finding straightforward answers for 3.*.
Here is the afterSave() method in my UsersTable class.
/**
* Updates Meta information after being created.
*/
public function afterSave(Event $event, Entity $entity, ArrayObject $options)
{
if($entity->returnAfterSave) return;
// update the public id
if(empty($entity->public_id))
{
$hashids = new Hashids(static::HASH_SALT, static::HASH_LENGTH);
$entity->public_id = $hashids->encode($entity->id);
}
// generate a password salt
if(empty($entity->salt))
{
$entity->generateSalt();
}
// salt and hash the password
if(empty($entity->hash))
{
// NOTE: I figured since the form data
// is loaded into the entity, I created a $password property
// in my User Entity class. This assumption may be the problem??
// Update: It was a bad assumption. Removing said property solved issue.
$entity->hashPassword();
}
// save the changes
$entity->returnAfterSave = true;
$this->save($entity);
}
Also, I understand that this code will not properly save any new passwords, I will update the code for password changes after my creates are working properly.
Here is a question on the Caching Proxy design pattern.
Is it possible to create with PHP a dynamic Proxy Caching implementation for automatically adding cache behaviour to any object?
Here is an example
class User
{
public function load($login)
{
// Load user from db
}
public function getBillingRecords()
{
// a very heavy request
}
public function computeStatistics()
{
// a very heavy computing
}
}
class Report
{
protected $_user = null;
public function __construct(User $user)
{
$this->_user = $user;
}
public function generate()
{
$billing = $this->_user->getBillingRecords();
$stats = $this->_user->computeStatistics();
/*
...
Some rendering, and additionnal processing code
...
*/
}
}
you will notice that report will use some heavy loaded methods from User.
Now I want to add a cache system.
Instead of designing a classic caching system, I just wonder if it is possible to implement a caching system in a proxy design pattern with this kind of usage:
<?php
$cache = new Cache(new Memcache(...));
// This line will create an object User (or from a child class of User ex: UserProxy)
// each call to a method specified in 3rd argument will use the configured cache system in 2
$user = ProxyCache::create("User", $cache, array('getBillingRecords', 'computeStatistics'));
$user->load('johndoe');
// user is an instance of User (or a child class) so the contract is respected
$report = new report($user)
$report->generate(); // long execution time
$report->generate(); // quick execution time (using cache)
$report->generate(); // quick execution time (using cache)
each call to a proxyfied method will run something like:
<?php
$key = $this->_getCacheKey();
if ($this->_cache->exists($key) == false)
{
$records = $this->_originalObject->getBillingRecords();
$this->_cache->save($key, $records);
}
return $this->_cache->get($key);
Do you think it is something we could do with PHP? do you know if it is a standard pattern? How would you implement it?
It would require to
implement dynamically a new child class of the original object
replace the specified original methods with the cached one
instanciate a new kind of this object
I think PHPUnit does something like this with the Mock system...
You can use the decorator pattern with delegation and create a cache decorator that accepts any object then delegates all calls after it runs it through the cache.
Does that make sense?
I'm trying to create a user provider so that I can authenticate through an Active directory server.
The problem is that, unlike most other LDAP servers, Active directory doesn't allow to retrieve some user's password attribute, even encrypted.
Here is my User class :
class LdapUser implements UserInterface
{
private $username;
private $first_name;
private $last_name;
private $password;
private $salt;
private $roles;
public function __construct($username, $first_name, $last_name, $password, $salt, array $roles) {
$this->username = $username;
$this->first_name = $first_name;
$this->last_name = $last_name;
$this->password = $password;
$this->salt = $salt;
$this->roles = $roles;
}
...
}
And here is my loadUserByUsername method (In my UserProvider class) :
public function loadUserByUsername($username)
{
$server = "my_ldap_server";
$root_dn = "my_root_dn";
$root_pw = "my_root_pw";
$ds = ldap_connect($server);
if ($ds) {
ldap_bind($ds, $root_dn, $root_pw);
$search = ldap_search($ds, "my_branch", "(sAMAccountName=".$username.")", array("sn", "givenName"));
$info = ldap_get_entries($ds, $sr);
if($info['count'] > 0) {
$user = $info[0];
return new LdapUser($username, $user['givenName'][0], $user['sn'][0], '???PASSWORD???', '???SALT???', array('ROLE_USER'));
} else {
throw new UsernameNotFoundException(sprintf('Username "%s" does not exist.', $username));
}
ldap_close($ds);
} else {
echo "Connexion au serveur LDAP impossible";
}
}
As you can see, I can't pass the password to my LdapUser class, since it's not accessible through Active Directory.
However, I think it's still possible to authenticate te user, by doing a ldap_bind($username, $password) with the password entered by the user in the login form. The problem is I can't figure out how to access this password in my LdapUserProvider class.
I tried $_POST['password'] but I got an undefined index error...
Any help would be welcome :)
There are a few LDAP Authentication bundles ready to use.
As pointed out below, you can try FR3D/LdapBundle, but it needs FOSUserBundle to work. You can also try IMAG (BorisMorel/LdapBundle), if you don't want to (or don't need to) use FOSUserBundle. BorisMorel uses php-ldap to work, it requires no additional bundle nor any zend components.
Even if you don't want to use them, I'd suggest you check out BorisMorel's implementation for more information. The code's quite simple to understand as it's very straightforward.
Your code sets the authentication state of a connection by transmitting a bind request. Does this code attempt to search for an entry, retrieve the password from that entry and then bind as that distinguished name? If so, even if you could retrieve the password, a properly configured directory server will not allow the transmission of a pre-encoded password. Passwords should always be transmitted in clear text over a secure connection (TLS or SSL) so the server can perform password quality and history validation. Therefore, your code must know the clear-text password beforehand. Alternatively, a LDAP-compliant server might allow the use of a proxied authentication for certain entries.
see also
LDAP: Mastering Search Filters
LDAP: Search best practices
LDAP: Programming practices
Try FR3dLdapBundle
The 2.0.x branch has support with Active Directory if you install Zend\Ldap component from Zend Framework 2