I am a zf1 developer. I started zf2. I am creating a Authentication module. I created a Auth class as mentioned in the doc
<?php
namespace Application\Model;
use Zend\Authentication\Adapter\AdapterInterface;
use Zend\Authentication\Adapter\DbTable as AuthAdapter;
class Myauth implements AdapterInterface {
/**
* Sets username and password for authentication
*
* #return void
*/
public function __construct($username, $password) {
// Configure the instance with constructor parameters...
$authAdapter = new AuthAdapter($dbAdapter,
'users',
'username',
'password'
);
$authAdapter
->setTableName('users')
->setIdentityColumn('username')
->setCredentialColumn('password');
$result = $authAdapter->authenticate();
if (!$result->isValid()) {
// Authentication failed; print the reasons why
foreach ($result->getMessages() as $message) {
echo "$message\n";
}
} else {
// Authentication succeeded
// $result->getIdentity() === $username
}
}
}
Issue1 : How to get $dbAdapter here?
Issue2 : Is this correct way to create auth module?
I have a couple things to say:
1. About Database Adapter
This link shows you how to configure database adapter.
In config/autoload/global.php:
return array(
'db' => array(
'driver' => 'Pdo',
'dsn' => 'mysql:dbname=zf2tutorial;host=localhost',
'driver_options' => array(
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\''
),
),
'service_manager' => array(
'factories' => array(
'Zend\Db\Adapter\Adapter'
=> 'Zend\Db\Adapter\AdapterServiceFactory',
),
),
);
In config/autoload/local.php:
return array(
'db' => array(
'username' => 'YOUR USERNAME HERE',
'password' => 'YOUR PASSWORD HERE',
),
)
Now, from ServiceLocatorAware classes, you can get Database Adapter as
$dbAdapter = $this->getServiceLocator()->get('Zend\Db\Adapter\Adapter');
2. About Creating Authentication
Dude, why reinvent the square wheel? As mentioned here, ZfcUser is built to be a foundation for a very large percentage for Zend Framework 2 applications.
Nearly anything is customizable as mentioned here. A lot of modules are available such as ScnSocialAuth which have dependancy on ZfcUser and are really awesome.
As in ZF 2, Models are not ServiceLocatorAware classes, so you cannot use the solution in Ojjwal Ojha's answer.
You can:
1. Get dbAdapter in your Controller by calling:
$dbAdapter = $this->getServiceLocator()->get('Zend\Db\Adapter\Adapter');
Pass dbAdapter to your Model when you create it:
$model = new Model($dbAdapter);
Write an init function in your Model.
Related
in ZF2 it was possible to configurate multiple adapters like this in the module.config.php:
'db' => array(
'adapters'=>array(
'db1' => array(
'driver' => 'Pdo',
'dsn' => 'mysql:dbname=zf2;host=localhost',
'driver_options' => array(
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\''
),
'username' => 'zf2',
'password' => 'zf2test',
),
'db2' => array(
'driver' => 'Pdo',
'dsn' => 'mysql:dbname=zf1;host=localhost',
'driver_options' => array(
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\''
),
'username' => 'zf1',
'password' => 'zf1test',
),
)
),
In the controller factory I could get them via the ServiceManager:
class AlbumControllerFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $serviceLocator)
{
$albumTable = $serviceLocator->getServiceLocator()->get('Album\Model\AlbumTable');
$db1Adapter = $serviceLocator->getServiceLocator()->get('db1');
$db2Adapter = $serviceLocator->getServiceLocator()->get('db2');
return new AlbumController($albumTable, $db1Adapter, $db2Adapter);
}
}
Now I'm trying to do the same in Zend Framework 3 - but this nested array configuration doesn't work:
Fatal error: Uncaught exception 'Zend\Db\Adapter\Exception\InvalidArgumentException' with message 'createDriver expects a "driver" key to be present inside the parameters' in /var/www/USER/teckert/zf3/vendor/zendframework/zend-db/src/Adapter/Adapter.php:262
I think that in ZF 2 the adapters key are already handled when the dbAdapter is trying to create the driver - but this is not happening in ZF 3.
Any hints are warmly welcomed...
The manual of the zend-db with the adapters section wasn't clear enough for me
EDIT
According to this doc I've added the following snippet to the global config file:
'service_manager' => [
'abstract_factories' => [
\Zend\Db\Adapter\AdapterAbstractServiceFactory::class,
],
],
While trying to get the dbAdapter with $container->get('db1') in my AlbumTableFactory I get this error:
Unable to resolve service "db1 to a factory; are you certain you provided it during configuration?
Make sure you've added Zend\Db\Adapter\AdapterAbstractServiceFactory to the abstract_factories array of the ServiceManager configuration.
This abstract factory is responsible for instantiating the individual database adapters. Also, check whether your Album\Model\AlbumTable factory retrieves the database adapter with the correct name.
Okay I finally resolved the problem.
As mentioned by #Pieter I needed the following array content in my config.php:
'service_manager' => [
'abstract_factories' => [
\Zend\Db\Adapter\AdapterAbstractServiceFactory::class,
],
],
Additionally I had to change the process my classes and dependent factories are talking to each other.
AlbumControllerFactory is called and get's the dependent AlbumTable service ($container->get(AlbumTable::class);) which then triggers the AlbumTableFactory
AlbumTableFactory then is preparing the constructor injection for the AlbumTable with $tableGateway = $container->get(AlbumTableGateway::class);
Now I combined the logic from the AlbumTableFactory and the AlbumTableGatewayFactory into one AbstractFactoryInterface (I removed the AlbumTableGatewayFactory completely)
// AlbumTableFactory implements AbstractFactoryInterface
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
$dbAdapter = $container->get('db1');
$resultSetPrototype = new ResultSet();
$resultSetPrototype->setArrayObjectPrototype(new Album());
$tableGateway = new TableGateway('album', $dbAdapter, null, $resultSetPrototype);
return new AlbumTable($tableGateway);
}
I searched for this kind of connection but all the bundles do not work with symfony security 2.7 (Composer says packages problems) and symfony 3.0.
Note : I actually use Silex Framework.
I want to return a true or false response if the user is connected to the active directory.
I have this code for tests :
$app->register(new Silex\Provider\SecurityServiceProvider(), array(
'security.firewalls' => array(
'login' => array(
'pattern' => '^/login$',
),
'secured' => array(
'pattern' => '^/',
'anonymous' => false,
'logout' => true,
'form' => array('login_path' => '/login', 'check_path' => '/login_check'),
'users' => array(
// raw password is foo
'admin' => array('ROLE_ADMIN', '5FZ2Z8QIkA7UTZ4BYkoC+GsReLf569mSKDsfods6LYQ8t+a8EW9oaircfMpmaLbPBh4FOBiiFyLfuZmTSUwzZg=='),
),
),
),
));
The login with admin & foo worked but that's not what I want.
I want to replace this part of code :
'users' => array(
// raw password is foo
'admin' => array('ROLE_ADMIN', '5FZ2Z8QIkA7UTZ4BYkoC+GsReLf569mSKDsfods6LYQ8t+a8EW9oaircfMpmaLbPBh4FOBiiFyLfuZmTSUwzZg=='),
),
Or any others solutions to replace the value who allows the user to bypass the firewall and enter in the application.
Note :
I use adLDAP plugin to test the connection with the Active Directory (It's works perfectly).
Try to use LdapUserProvider for it. Remove 'users' => array(...) from secured config and add user provider definition for this zone.
$app['security.user_provider.secured'] = $app->share(function($app) {
return new \Symfony\Component\Security\Core\User\LdapUserProvider(
new \Symfony\Component\Ldap\LdapClient('ldap.server.com'),
'baseDn', // ex.: dc=example,dc=com
'searchDn' // ex.: CN={username},DC=example,DC=com
);
});
Or you can write your own user provider based on LdapUserProvider and adLDAP. Something like this:
namespace My\Namespace;
use Symfony\Component\Security\Core\User\LdapUserProvider;
use Symfony\Component\Security\Core\User\User;
class adLdapUserProvider extends LdapUserProvider
{
public function loadUserByUsername($username)
{
// you code, that returns user from ldap server
// $this->ldap - is object of adLDAP
$user = $this->ldap->user()->info($username);
return $this->loadUser($username, $user);
}
}
and change user provider definition
$app['security.user_provider.secured'] = $app->share(function($app) {
return new \My\Namespace\adLdapUserProvider(
new \adLDAP\adLDAP(...)
);
});
I do not know how to configure Zend \ Log with Doctrine2. Only allows you to write directly to the database via a connection adapter or write to a file.
May be it's too late to answer this question but better late than never.
I've found a good post which explains how to create a basic SQL Logger for ZF2 and Doctrine.
The approach is pretty simple :
1. Creating Logger class : Create the following class in your Module/Application/Log folder :
<?php
namespace Application\Log;
use Zend\Log\Logger;
use Doctrine\DBAL\Logging\DebugStack;
class SqlLogger extends DebugStack
{
protected $logger;
public function __construct(Logger $logger)
{
$this->logger = $logger;
}
public function stopQuery()
{
parent::stopQuery();
$q = $this->queries[$this->currentQuery];
$message = "Executed Query: " . print_r($q, true);
$this->logger->info($message);
}
}
The stopQuery() function which is called by Doctrine when it finiches sending the query to the database server, is
overrided so that it could write the current query to the Logger object.
2. Configuring the Logger : Add the following code in your config/autoload/global.php file, to make
the Logger accessible to the Service Manager using the name my_sql_logger :
'service_manager' => array(
'factories' => array(
'my_sql_logger' => function($sm) {
$log = new \Zend\Log\Logger();
$writer = new \Zend\Log\Writer\Stream('./data/logs/sql.log');
$log->addWriter($writer);
$sqllog = new \Application\Log\SqlLogger($log);
return $sqllog;
},
)
),
The Logger will write data to the data/logs/sql.log file. So, make sure that data/logs folder exists in your
application root directory.
3. Configuring Doctrine : Now you need to tell Doctrine to use the created Logger. Just add the following code
to your Doctrine configuration :
return array(
'doctrine' => array(
/*--------Add this code------------*/
'sql_logger_collector' => array(
'orm_default' => array(
'sql_logger' => 'my_sql_logger',
),
),
/*---------------------------------*/
'connection' => array(
'orm_default' => array(
'driverClass' => 'Doctrine\DBAL\Driver\PDOMySql\Driver',
'params' => array(
'host' => 'localhost',
'port' => '3306',
'user' => 'username',
'password' => 'password',
'dbname' => 'dbname',
),
),
),
),
);
With the above configuration of Zend\Log and Doctrine2, you'll get all the query data logged in the
data/log/sql.log file.
Please see this Sql Logger for ZF2 and Doctrine for more details.
CakePHP v.2.4...
I'm following this documentation trying to set up the Auth component to use my custom password hashing class:
App::uses('PHPassPasswordHasher', 'Controller/Component/Auth');
class AppController extends Controller {
// auth needed stuff
public $components = array(
'Session',
'Cookie',
'Auth' => array(
'authenticate' => array(
'Form' => array(
'fields' => array('username'=>'email', 'password'=>'password'),
'passwordHasher' => 'PHPass'
)
),
Inside my UsersController::login() I debug the return from $this->Auth->login(); and it always returns false, even when I submit the correct email / password.
(NOTE: It looks strange to me that the login() takes no parameters, but the docs seem to imply that it looks into the the request data automatically. And this would make sense if my configurations aren't correctly causing it to check the User.email field instead username.)
The post data from the submitted login form looks like this:
array(
'User' => array(
'password' => '*****',
'email' => 'whatever#example.com'
)
)
What am I missing?
Update2
I'm starting to suspect that the default hashing algorithm is getting used instead of my custom class. I tried to match the examples in the docs but they're quite vague on how to do this.
Here's the contents of app/Controller/Component/Auth/PHPassPasswordHasher.php
<?php
App::import('Vendor', 'PHPass/class-phpass'); //<--this exists and defines PasswordHash class
class PHPassPasswordHasher extends AbstractPasswordHasher {
public function hash($password) {
$hasher = new new PasswordHash( 8, true );
return $hasher->HashPassword($password);
}
public function check($password, $hashedPassword) {
debug('PHPassHasher'); die('Using custom hasher'); //<--THIS NEVER HAPPENS!
$hasher = new new PasswordHash( 8, true );
return $hasher->CheckPassword($password, $hashedPassword);
}
}
AHA! The debug() never appears... so I'm pretty sure the problem is with my custom hasher configuration(s).
Update3
Additional clue: I experimented by setting various default hashing algorithms (Ex: "Simple", "Blowfish") and creating users. The hashes which show up in the DB are all the same which tells me that my config settings are getting ignored completely.
Update4
I debugged $this->settings inside the constructor of /lib/Cake/Controller/Component/Auth/BaseAuthenticate.php and my custom hasher settings are in there:
array(
'fields' => array(
'password' => 'password',
'username' => 'email'
),
'userModel' => 'User',
'scope' => array(),
'recursive' => (int) 0,
'contain' => null,
'passwordHasher' => 'PHPass'
)
You need to rename your password hasher class to have the suffix "PasswordHasher", and only provide the non-suffixed name in the 'className' argument.
eg:
<?php
App::import('Vendor', 'PHPass/class-phpass'); //<--this exists and defines PasswordHash class
class PHPassHasherPasswordHasher extends AbstractPasswordHasher {
// functions
}
The example from the docs sets the classname to 'Simple', which then loads 'SimplePasswordHasher'.
You might find that having a name of PHPassHasherPasswordHasher is a bit silly, it's up to you what you want to call it. Perhaps PHPassPasswordHasher might be a bit more appropriate (and then use the classname argument 'PHPass').
EDIT: It seems as if Cake has issues when multiple capital letters are used one after the other (eg. PHPass), so the right way to do it is to change the password hasher class to the following:
<?php
App::import('Vendor', 'PHPass/class-phpass'); //<--this exists and defines PasswordHash class
class PhpassPasswordHasher extends AbstractPasswordHasher {
// functions
}
... and make sure the filename matches the classname: PhpassPasswordHasher.php.
Thanks to SDP for the discussion, I learnt something today!
According to the docs:
To configure different fields for user in $components array:
// Pass settings in $components array
public $components = array(
'Auth' => array(
'authenticate' => array(
'Form' => array(
'fields' => array(
'username' => 'email',
'password' => 'password'
)
)
)
)
);
Source
I finally got this working. We were on the right track by renaming the file/class to comply with Cake conventions. I had to go one step further and change the capitalization as well:
PHPassPasswordHasher.php --> PhpassPasswordHasher.php
class PHPassPasswordHasher... --> class PhpassPasswordHasher...
Phew!
ps: Many many thanks to #Ben Hitchcock for support on this.
First ZF2 application, getting there, but I think still missing a think or two when it comes to dependency injection and the ServiceManager.
I have a particular problem at the moment with a new database gateway class I'm writing. I won't to inject a database adapter, so I've implemented AdapterAwareInterface. But the setDbAdapter method is never called in my class. I'm wondering if someone would be so kind as to look at my code and suggest what might be going wrong (or what I'm missing!).
So, here is the class in which I implement AdapterAwareInterface.
<?php
namespace Foo\Database;
use Zend\Db\Adapter\Adapter;
use Zend\Db\Adapter\AdapterAwareInterface;
use Zend\Log\LoggerAwareInterface;
use Zend\Log\LoggerInterface;
class Gateway implements AdapterAwareInterface, LoggerAwareInterface
{
protected $logger = NULL;
protected $db = NULL;
public function setDbAdapter(Adapter $adapter)
{
$this->db = $adapter;
}
public function setLogger(LoggerInterface $logger)
{
$this->logger = $logger;
}
This is an extract from my module file showing how I configure my service manager:
public function getServiceConfig()
{
return array(
'factories' => array(
....
),
'invokables' => array(
'FooDatabaseGateway' => 'Foo\Database\Gateway',
),
'abstract_factories' => array(
'AbstractFeedParserFactory' => 'Bookmakers\Odds\Feeds\AbstractFeedParserFactory',
),
);
}
This is how I'm testing:
gateway = $this->getServiceLocator()->get('FooDatabaseGateway');
And this is part of my global config:
return array(
'db' => array(
'driver' => 'Pdo',
'dsn' => 'mysql:dbname=kickoff_manager;host=localhost',
'username' => '****',
'password' => '****',
'driver_options' => array(
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\''
),
),
'service_manager' => array(
'factories' => array(
'Zend\Db\Adapter\Adapter'
=> 'Zend\Db\Adapter\AdapterServiceFactory',
),
),
);
Many thanks for any help you can provide.
:wq
OK a fresh pair of eyes on this problem this morning. I think this is the write answer.. At least that is to say its working for me. If anyone wants to suggest an entirely different of better approach, then please do so :-).
So the bit is was missing was to use an initializer in my service manager config to call the setDbAdapter function on any class instances that implement AdapterAwareInterface. So in the array I return from getServiceConfig in my Module.php file, I have added the following entry:
public function getServiceConfig() {
return array(
'initializers' => array(
'db' => function($service, $sm)
{
if ($service instanceof AdapterAwareInterface)
{
$service->setDbAdapter($sm->get('Zend\Db\Adapter\Adapter'));
}
}....
I think what I'm missing while learning ZF2 is that there are a lot of building blocks to work with, but you've got to put a lot of them together yourself.
Things are looking good and I'm enjoying the Framework, but there is a lot to learn, and I'm still not convinced by using Server Manager injection rather than good old constructor injection!
:wq