I'm trying to integrate BorisMorel LdapBundle with FOSUser following this guide: Using a custom user manager with LDAP authentication
My objective is to authenticate by using LDAP and then storing the LDAP data in my fosuser table.
I've got to the point where the data is correctly retrieved from the LDAP and stored in the fosuser table. I don't want it to store the password on the database, because there's no use on it, as I prefer the password being validated using the LDAP on each login.
The problem is when I get to this point:
C:\xampp\htdocs\Symfony2\vendor\symfony\symfony\src\Symfony\Component\Security\Core\Authentication\Provider\UserAuthenticationProvider.php
In function:
public function authenticate(TokenInterface $token)
It calls the function:
$this->checkAuthentication($user, $token);
Which resolves this function:
checkAuthentication(UserInterface $user, UsernamePasswordToken $token)
Which is in this file:
C:\xampp\htdocs\Symfony2\vendor\symfony\symfony\src\Symfony\Component\Security\Core\Authentication\Provider\DaoAuthenticationProvider.php
Because it checks the password using this:
encoderFactory->getEncoder($user)->isPasswordValid($user->getPassword(), $presentedPassword, $user->getSalt())) {
throw new BadCredentialsException('The presented password is
}
As the password on the fosuser table is empty, the authentication always fails, even if it already passed the LdapBundle test.
What I'm missing? I'm guessing that the wrong validator is being used, but it's not described in the BorisMorel resources!.
Please help!
Related
Background: I am trying to set up single sign on (SSO) for users such that they can authenticate to my website and not have to authenticate a second time to our third-party MSP's website. Ideally, the user clicks a link on our website and is taken to the third-party site already logged in and landing on the dashboard (if the account doesn't exist, it is created during this step). We are not using SAML for authentication as a security feature, so all that we need the SAML code for is just producing cookies that prevent the user from having to log in again when he/she gets to our vendor's site. This third party MSP does not support authentication via API or web service and therefore I have been tasked with implementing SAML, their only supported SSO method. I am new to SAML (but not PHP or development) and have been learning as I go. I am told it will support the goals described above.
I initially tried using LDAP as the authentication source as this is what I use for authentication to my website, but this resulted in me getting directed to a login page with no discernible way to instead just pass parameters to SimpleSAMLphp to tell it "the user is already authenticated, all I need you to do is give me valid cookies so I can get past the third party website's authentication checks".
So I switched to writing a custom authentication module. I opened up the GitHub for SimpleSAMLphp and used the "UserPassBase" class as an example to create my own authentication module that inherits from the "Source" class. Because I don't need to re-authenticate the user against LDAP a second time since they're already logged in to our website, I created a simple "authenticate" function that just sets the $state['Attributes'] array.
Here is the code for my custom module:
<?php
namespace SimpleSAML\Module\productauth\Auth\Source;
use SimpleSAML\Auth;
/**
Author: Joey
Class developed to be used as a custom authentication module for simpleSAMLphp. This class will take an existing session from a product website and use it to create a SAML session and redirect to a website.
**/
class ProductAuth extends \SimpleSAML\Auth\Source {
const STAGEID = '\SimpleSAML\Module\productauth\Auth\ProductAuth.state';
const AUTHID = '\SimpleSAML\Module\productauth\Auth\ProductAuth.AuthId';
private $user;
public function __construct($info, $config) { // parameters aren't used, just filler from base class
$info = array("AuthId" => "productauth");
parent::__construct($info, $config);
}
public function login($user, $redirectURL) {
$this->user = $user; // normally I'd set this in the constructor, but the overload has my hands tied as far as function definitions go
$this->initLogin($redirectURL); // calls authenticate function and then, if no exceptions, parent::loginCompleted which redirects to the given URL
}
public function authenticate(&$state) { // called by parent::initLogin
$state[self::AUTHID] = $this->authId;
$state['Attributes'] = [
'uid' => [$this->user->uid],
'givenName' => [$this->user->givenName],
'sn' => [$this->user->sn],
'mail' => [$this->user->mail]
];
$id = Auth\State::saveState($state, self::STAGEID);
}
}
?>
I am calling it from a controller class on my website:
private function goToTrainingSite() {
require_once("../third-party-libs/simplesamlphp/_include.php");
global $TRAINING_URL;
$user = $_SESSION['subject']->user;
$samlObj = new SimpleSAML\Module\productauth\Auth\Source\ProductAuth(array(), array());
$samlObj->login($user, $TRAINING_URL);
}
I mimicked the flow of the "UserPassBase" class (https://github.com/simplesamlphp/simplesamlphp/blob/master/modules/core/lib/Auth/UserPassBase.php), but it seems that despite all of my authentication working and setting a SimpleSAMLAuth cookie, when the parent::loginCompleted function in the "Source" class (https://github.com/simplesamlphp/simplesamlphp/blob/master/lib/SimpleSAML/Auth/Source.php) runs, it redirected me to the third party site. I then see the following in the logs:
SAML2.0 - IdP.SSOService: incoming authentication request: [REDACTED DATA]
Session: 'productauth' not valid because we are not authenticated.
I have been trying for 3 days to figure out why it seems as though despite setting SimpleSAML session cookies with a completed, successful authentication, that upon receiving the auth request from the SP, my SimpleSAMLphp code just pretends to not know about the completed auth and tries to authenticate again... but because it is not being called from my code, it doesn't have access to the $user variable which contains all of the attributes I need to place on the user when he/she authenticates to this third party website. It seems that when it receives an authentication request, my SimpleSAMLphp installation starts a new session and tries a brand new authentication.
I have delved into a lot of the code of SimpleSAMLphp and tried to understand what is going on, but it seems that there is just no reasonable way to authenticate by calling an authentication source from PHP code and being able to skip the SP-initiated authentication. I have tried:
Using the SimpleSAML API (https://simplesamlphp.org/docs/stable/simplesamlphp-sp-api) to call my authentication source, but there seems to be no way to pass that $user variable I need the attributes from.
Trying to load the cookies in the "Session" class when it is checking for valid sessions... but it seems like the cookies from the successful auth session initiated by my code are just gone and nowhere to be found.
I decided to stop focusing on trying to get the $user variable and the data I needed to the second authentication, and instead focus on WHY the second authentication was even happening. I looked at the cookies and thought about how the data was being retrieved, and made a correct hunch that our application's custom session handler might be at fault for SimpleSAMLphp's inability to recognize the first authentication. Our custom session handler stores our sessions in the database, but SimpleSAMLphp expects to use the default PHP session handler to manage its session. Therefore, my first authentication was being sent to the database and when SimpleSAMLphp started looking for it where PHP sessions are usually stored, it didn't see it and assumed it needed to kick off another authentication session from scratch.
Using SimpleSAMLphp's documentation for service providers and a lot of my own debugging, I changed the function in my controller like so:
private function goToTrainingSite() {
require_once ("../third-party-libs/simplesamlphp/_include.php");
global $TRAINING_URL;
$joeySiteSession = $_SESSION;
$user = $_SESSION ['subject']->user; // save user to variable before the Joey's Site session is closed
session_write_close (); // close Joey's Site session to allow SimpleSAMLphp session to open
session_set_save_handler ( new SessionHandler (), true ); // stop using SessionHandlerJoey and use default PHP handler for SimpleSAMLphp
$samlObj = new SimpleSAML\Module\joeysiteauth\Auth\Source\JoeySiteAuth ( array (), array () );
$samlObj->login ( $user, function () { return;} ); // use custom authentication module to set atttributes and everything SimpleSAMLphp needs in the auth session/cookie
$session = \SimpleSAML\Session::getSessionFromRequest ();
$session->cleanup (); // must call this function when we are done with SimpleSAMLphp session and intend to use our Joey's Site session again
session_write_close ();
$_SESSION = $joeySiteSession; // restore Joey's Site session
header ( "Location: {$TRAINING_URL}" );
}
I have a need to manually encrypt the password in the same way Symfony does it.
$user->setPlainPassword($password);
$userManager->updateUser($user);
This is application code that saves the user password. But it is obviously encrypted (not plain as method says).
How can I manually get the same result for given password?
EDIT1:
The problem is that I want to authenticate user manually in users controller code. I have readable username and password that comes as parameters. I want to return 401 if they to not exist in database.
Symfony comes with a useful command to encode a password the same way it does:
bin/console security:encode-password 'your_plain_password' 'AppBundle\Entity\YourUserClass'
Try this in your controller action:
$encoder = $this->get('security.encoder_factory')->getEncoder($userClass);
$encodedPassword = $encoder->encodePassword($plainPassword);
In newer Symfony versions (5.3+) you can do it by running CLI command:
bin/console security:hash-password
I'm currently implementing OAuth2 in my website to keep an user logged in inside an Android app. I want to change the default OAuth2 user database to my own user database. Unfortunately I can't find out how to do that. It should be possible with overriding classes and without changing the code in the core library, but how to do it?
This is what I have in my server.php file:
// Autoloading (composer is preferred, but for this example let's just do this)
require_once('/src/OAuth2/Autoloader.php');
OAuth2\Autoloader::register();
$storage = new OAuth2\Storage\Pdo(array('dsn' => 'mysql:host=xxxx;dbname=xxxx', 'username' => 'xxxx', 'password' => 'xxxx'));
// Pass a storage object or array of storage objects to the OAuth2 server class
$server = new OAuth2\Server($storage);
// Add the "Password / User Credentials" grant type
$server->addGrantType(new OAuth2\GrantType\UserCredentials($storage));
So here I want to use to my own user table instead of the default oauth_users table. And because the passwords are salted I need to have a different password check too. I am using the BShaffer OAuth2 Library: https://github.com/bshaffer/oauth2-server-php
With this library it's easy to write custom code so you don't have to touch the core of this library.
For this problem you'll have to create a custom storage class that implements the UserCredentialsInterface. There are two methods in here which you need to implement yourself
public function checkUserCredentials()
public function getUserDetails()
Here you can implement your logic for checking user details and fetching user details.
After this you'll need to add this storage to the oAuth server like this:
$server = new OAuth2\Server($storage);
$userStorage = new YourCustomUserStorage();
$server->addStorage($userStorage, 'user_credentials');
You'll also need to pass this storage to any Grant type you're adding to the server, in your case it looks like this:
$server->addGrantType(new OAuth2\GrantType\UserCredentials($userStorage));
I add a user to the database.
I encode their password using bcrypt encryption:
$factory = $this->get('security.encoder_factory');
$encoder = $factory->getEncoder($user);
$password = $encoder->encodePassword($user->getPassword(),$user->getSalt());
$postData->setPassword($password);
This all persists to the database just fine.
On my User Entity my getSalt() method returns null:
public function getSalt(){
return null;
}
Now when I try to authenticate the symfony logs tells me that I am entering bad credentials.
However...
If i use this site to encrypt my password with bcrypt:
http://bcrypthashgenerator.apphb.com/
Then enter the result into the database manually and try and authenticate with the users password i have updated with the results from the above link,
It works fine.
So there is something I have not set, that when I am encrypting the password that symfony cannot authenticate against it.
I have tried the setting a salt on the getSalt(), setting it to an empty string.
EDIT
I am using php 5.5.9
I have also tried using the BCryptPasswordEncoder($cost) class,
setting the cost on the __construct the same as what's in my security.yml
and it still does not authenticate.
EDIT
I have also tried installing this class via composer to as suggested here.
EDIT
If I use the php method:
password_hash('testertester',PASSWORD_BCRYPT,array('cost'=>13));
It authenticates perfectly.
So its something to do deeper in Symfony. I'll keep digging.
Still no luck :-/
I am scouring the web to no avail so far.
Any help would be greatly appreciated.
Ad
I was passing in the existing entity object not the submitted data object!!
$postData = $form->getdata();
$factory = $this->get('security.encoder_factory');
$encoder = $factory->getEncoder($user);
$password = $encoder->encodePassword($user->getPassword(),$user->getSalt());
$postData->setPassword($password);
Note this line here:
$user->getPassword() needed to be $postData->getPassword()
$password = $encoder->encodePassword($postData->getPassword(),$user->getSalt());
You live and learn ay!
Task
What I need is, to authenticate user based on Oracle DB user system:
User enters to login page
Enters username and password
Application tries to establish connection with given parameters
If successful, makes this connection persistent over application
I'm using Yii framework.
What I've done ...
I removed db component from protected/config/main.php file. Based on Larry Ulman's blog post replaced authenticate (which located in protected/components/UserIdentity.php) with following one
public function authenticate()
{
$dsn='oci:dbname=XE;charset=UTF8';
$connection=new CDbConnection($dsn,$this->username,$this->password);
$connection->active=true;
return $connection->getConnectionStatus();
}
Well it didn't establish any connection. BTW in server I tested with oracle client. All works well.
Questions are
I'm not sure if I'm doing it in right manner.
How to make established db connection persistent over whole application?
BTW
My development environment
Oracle DB and Apache 2.2.4 + PHP 5.4 webserver working on Windows Server 2003.
It took some time to understand what you want. So basically it's like:
A user tries to log in with username/password
To authenticate the user, you check, whether you can establish a DB connection using that same username/password as DB credentials
If the connection could be opened, the user should have a db component available, that uses this same connection credentials throughout the user session
Your approach to create a custom UserIdentity is right. They are meant for doing the authentication. I'm not sure, why your code there fails, but you're on the right track. Maybe getConnectionStatus() does not work right for Oracle. You could try to execute another simple SQL command for a test.
If you've managed to test for a successful connection, you should store the username and password in the user session, e.g.
Yii::app()->user->setState('__db_username',$this->username);
Yii::app()->user->setState('__db_password',$this->password);
Now to have the db component available after login is a bit tricky. You can take different approaches. I'll describe two:
1. Create a custom CDbConnection
class DynamicDbConnection extends CDbConnection
{
public function init()
{
$this->username = Yii::app()->user->getState('__db_username');
$this->password = Yii::app()->user->getState('__db_password');
if(!$this->username || !$this->password) {
throw new CException('No username/password available! User not logged in?');
}
parent::init();
}
}
Now you can configure this as db component in your main.php without username and password, of course:
'components' => array(
'db' => array(
'class' => 'DynamicDbConnection',
'connectionString' =>'oci:dbname=XE;charset=UTF8',
),
),
2. Adding a CDbConnection from Controller::init()
You could add a db component from your base controllers init() method:
class Controller extends CController
{
public function init()
{
$username = Yii::app()->user->getState('__db_username');
$password = Yii::app()->user->getState('__db_password');
if($username && $password) {
Yii::app()->setComponent('db', array(
'username' => $username,
'password' => $password,
));
}
parent::init();
}
You also still need a base db component configuration in your main.php for this.
Both approaches may need a little tweaking as i've not tested this. But you should get the basic idea.