I am new to Zend & working on Zend2, I have cron job functionality to make some automatic notifications. For this the functionality is ready & it was set up in Cron (Linux server).
Now when ever a call is made to these functions they are getting redirected to Login action. Now I should allow these specific notification functions to get rid of this authentication process.
In cakephp we have $this->Auth->allow('index') which allows to work without login action. Is there a way to do similar to this in zend 2?
I've a link similar to this. But it doesn't say where to mention the action name in the ACL
This is the way I do it, using custom acl management code:
In my User module, I place the Acl config to manage the access to the resources for 3 differents roles: guest, member, admin.
The module.config.php has the following attribute "acl":
'acl' => array(
'role' => array(
'guest' => null,
'member' => array('guest'),
'admin' => null,
),
// List of modules to apply the ACL. This is how we can specify if we have to protect the pages in our current module.
'modules' => array(
'User',
'Application'
),
'resource_aliases' => array(
'User\Controller\Account' => 'account',
...
),
'resource' => array(
// resource -> single parent
'account' => null,
'log' => null
...
),
'allow' => array(
array('guest', 'log', array('in', 'out')),
array('guest', 'account', array('register', 'verify', 'recovery', 'verificationprogress')),
...
array('admin', null, null), // the admin can do anything with the accounts
),
'deny' => array(
),
'defaults' => array(
'guest_role' => 'guest',
'member_role' => 'member',
'admin_role' => 'admin',
),
)
In the onBootstrap method of my Module.php:
...
$eventManager = $event->getApplication()->getEventManager();
$eventManager->attach(MvcEvent::EVENT_ROUTE, array($this, 'protectPage'), -100);
...
The protectPage function looks like this:
public function protectPage(MvcEvent $event) {
$match = $event->getRouteMatch();
if (!$match) {
//onDispatchError do the job
}
$controller = $match->getParam('controller');
$action = $match->getParam('action');
$namespace = $match->getParam('__NAMESPACE__');
$parts = explode('\\', $namespace);
$moduleNamespace = $parts[0];
$services = $event->getApplication()->getServiceManager();
$config = $services->get('config');
// check if the current module wants to use the ACL
$aclModules = $config['acl']['modules'];
if (!empty($aclModules) && !in_array($moduleNamespace, $aclModules)) {
return;
}
$auth = $services->get('auth');
$acl = $services->get('acl');
// get the role of the current user
$session = new Container("appData");
$role = "guest";
if (isset($session->user->role))
$role = $session->user->role;
// Get the short name of the controller and use it as resource name
// Example: User\Controller\Course -> course
$resourceAliases = $config['acl']['resource_aliases'];
if (isset($resourceAliases[$controller])) {
$resource = $resourceAliases[$controller];
} else {
$resource = strtolower(substr($controller, strrpos($controller, '\\') + 1));
}
// If a resource is not in the ACL add it
if (!$acl->hasResource($resource)) {
$acl->addResource($resource);
}
try {
//if the role is allow to pass
if ($acl->isAllowed($role, $resource, $action)) {
//do whatever you need since the use is allowed to access this resource
}else{
//send the user to log/in resource
}
} catch (AclException $ex) {
// #todo: log in the warning log the missing resource
}
}
I hope it helps.
Related
I have a site that needs to allow multiple URL structures. For example:
www.examplesite.com/people/add // <-- example company
www.otherexample.com/xyz/people/add // <-- "xyz" company (not location based)
www.otherexample.com/florida/abc/people/add //<-- "abc" company (location based)
Each URL should be able to detect which company it is based on the URL.
So far, I've been able to parse out the URL just fine to determine which company it is, but how to I add these extra /florida/abc/ parts to the routes to allow the rest of the app to work?
I've tried a number of things including setting a variable to the '/florida/abc' (or whatever it is) at the top of the routes file, then adding that before each route, but that doesn't handle every controller/action and seems very hit or miss/buggy.
I also use the admin prefix, so for example, it would also need to work like this:
www.otherexample.com/admin/florida/abc/people/add
My assumption is that I need to use the routes.php file, but I can't determine within that how I can make this happen.
I used that approach in the web application farm.ba (not more maintained by the owner).
What I did:
Create table "nodes" with fields id, slug, model, foreign_key, type, ..
Create custom route (1),(2) class that handles Node model
After save post, store and cache slug in Node Model
After deleting the post, delete the cache and node records
This works much like wordpress routing, allows you to enter custom slug, etc.
EDIT:
Create custom route class in App/Lib/Routing/Router/MultiRoute.php like:
<?php
App::uses('CakeRoute', 'Routing/Route');
/**
* MultiRoute
*/
class MultiRoute extends CakeRoute
{
public function parse($url)
{
// debug($url); '/florida/abc/people/add'
// Add custom params
$params = array(
'location' => null,
'company' => null,
'controller' => 'peoples',
);
$params += parent::parse($url);
// debug($params);
/**
* array(
* 'location' => null,
* 'company' => null,
* 'controller' => 'peoples',
* 'named' => array(),
* 'pass' => array(
* (int) 0 => 'florida', // location
* (int) 1 => 'abc', //company
* (int) 2 => 'people', // controller
* (int) 3 => 'add' // action, default index
* ),
* 'action' => 'index',
* 'plugin' => null
* )
*
*/
// reverse passed params
$pass = array_reverse($params['pass']);
// debug($pass);
/**
* array(
* (int) 0 => 'add',
* (int) 1 => 'people',
* (int) 2 => 'abc',
* (int) 3 => 'florida'
* )
*/
if(isset($pass[3])) { $params['location'] = $pass[3]; }
if(isset($pass[2])) { $params['company'] = $pass[2]; }
// if you need load model and find by slug, etc...
return $params;
}
public function match($url)
{
// implement your code
$params = parent::match($url);
return $params;
}
}
in routes.php
App::uses('MultiRoute', 'Lib/Routing/Route');
Router::connect('/admin/*',
array('admin' => true),// we set controller name in MultiRoute class
array('routeClass' => 'MultiRoute')
);
Router::connect('/*',
array(),// we set controller name in MultiRoute class
array('routeClass' => 'MultiRoute')
);
In your controller find results using extra request params, like:
$this->request->location;
$this->request->company;
I hope this is helpful.
Creating a route for each case seems the way to go:
Router::connect('/people/add', array('controller' => 'people', 'action' => 'add'));
Router::connect('/:company/people/add', array('controller' => 'people', 'action' => 'add'), array('pass' => array('company'), 'company' => '[a-z]+'));
Router::connect('/:location/:company/people/add', array('controller' => 'people', 'action' => 'add'), array('pass' => array('company', 'location'), 'company' => '[a-z]+', 'location' => '[a-z]+'));
Then the controller can receive these values:
public function add($company = '', $location = '') {
var_dump($company, $location); exit;
}
Mind the regex in routes and amend to match your incoming data.
I am trying to make a system (in Zend Framework 2) to validate a user registration's email by sending an email with a link with a token (for example: http://example.com/user/autenticate/verify/abG12Fdss67j3kgfdds4jdpa74FiP9) so if the token is found in the database the pre-registered account moves to VERIFIED status.
I am using a route in module.config.php like this:
'verify' => array(
'type' => 'Segment',
'options' => array(
'route' => '/user/autenticate/verify/:token',
'defaults' => array(
'__NAMESPACE__' => 'User\Controller',
'controller' => 'Autenticate',
'action' => 'verify',
),
'constraints' => array(
'token' => '[a-zA-Z0-9]{30}'
),
),
),
then in AutenticateController.php, the following action method:
public function verifyAction()
{
sleep(3); // Delay against brute attack (is it useful?)
$token = $this->params()->fromRoute('token');
$registerverification = new RegisterVerification();
try {
$registerverification = $this->getRegisterVerificationTable()->getRegisterVerification($token);
// If arrives here (no exception) means that the token was in the database
$aux = $this->getRegisterVerificationTable()->deleteRegisterVerification($token);
$user = new User();
$user = $this->getUserTable()->getUser((int)$registerverification->id);
$user->verified = date("Y-m-d H:i:s");
$this->getUserTable()->saveUser($user);
$this->flashMessenger()->addMessage("Now your account is active");
} catch (\Exception $e) { // Could not find row: $token
$this->flashMessenger()->addMessage($e->getMessage());
}
return array();
}
And a verify.phtml like this:
<?php
echo $this->flashMessenger()->render();
?>
This is working, but not on the first attempt, but only after refreshing the URL (http://example.com/user/autenticate/verify/abG12Fdss67j3kgfdds4jdpa74FiP9).
Can anyone help me on what do I have to do to make the method verifyAction() of AutenticateController.php being executed the first time the URL is called?
The flash messenger is designed to show messages on the next request, so you probably want to redirect to another URL after adding your success message. That may be the only issue (otherwise please let us know what happens on the first request).
I am using the Silex / Symfony security service and try to implement a automatic login when the specific parameters are passed in the request query.
I've looked into the modules and also search on the internet for a solution and always found something like the following:
$user = (new \Portal\UserProvider($app['databases']['read']))->loadUserByUsername($subscriber_id);
$token = new \Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken($user, $user->getPassword(), 'secured', $user->getRoles());
$app['security.token_storage']->setToken($token);
Unfortunately, this does not work for my app. I don't know whats wrong but the security module keeps redirecting me to /login/ as specified in the registration process:
/**
* Registers the security firewall.
*/
private function registerSecurity()
{
$this->register(new \Silex\Provider\SecurityServiceProvider(), array(
'security.firewalls' => array(
'login' => array(
'pattern' => '^/(login/|terms|imprint|animation|error)',
),
'secured' => array(
'pattern' => '^/',
'form' => array(
'login_path' => '/login/',
'check_path' => '/login_check'
),
'logout' => array(
'logout_path' => '/logout'
),
'users' => $this->share(function () {
return new \Portal\UserProvider($this['databases']['read']);
}),
),
'unsecured' => array(
'anonymous' => true
),
),
'security.encoder.digest' => $this->share(function () {
return new \Symfony\Component\Security\Core\Encoder\MessageDigestPasswordEncoder('sha1', false, 1);
}),
'security.access_rules' => array(
array('^/login', 'ROLE_GUEST'),
),
'security.role_hierarchy' => $this->share(function () {
return array();
})
));
$this->boot();
}
Is there anything I have to consider about
reloading
order of registering the SecurityServiceProvider, SessionServiceProvider
this manual token setting
?
You're using the 'form' authentication provider, but this won't work (or maybe I'm not understanding you correctly?). In order to be able to:
try to implement a automatic login when the specific parameters are passed in the request query
You need to hook into the Security service. In order to do that you have to create a Listener and register it. You'll also need a Provider
This is not an easy path as the security component works with many concepts.
You can see a working example in this repo (which implements an OAuth service)
If your security flow is easy and you don't need roles, you can just use a before middleware (and forget about the security component) like so:
<?php
$app->before(function (Request $request, Application $app) {
$session = $request->getSession();
if (false === $session->get('logged', false)) {
if (null !== $request->get('blah', null)) {
$session->set('logged', true);
}
else {
return new RedirectResponse('/login-url');
}
}
});
You could use Silex's guard. It works well with get Request. And standard form could be use as complement.
In your secured Security.firwall, add the guard parameter :
"guard" => array ("authenticator" => array("app.myauthenticator") )
And create your custom class, to validate login.
Just read the Silex cookbook.
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.
I want want dispatch another controller action when Zf2 raise route not found exception to display my custom page ( i dont want to display a custom error page). I am working on dynamic url from database in Zf2 which will occur only when route not found.
i added a event in bootstrap function
$event->attach('route', array($this, 'loadConfiguration'), 2);
in loadConfiguration function i added to load
public function loadConfiguration(MvcEvent $e){
$application = $e->getApplication();
$sm = $application->getServiceManager();
$router = $sm->get('router');
$request = $sm->get('request');
$matchedRoute = $router->match($request);
if (null == $matchedRoute) {
$request_uri = $e->getRequest()->getrequestUri();
$dbAdaptor = $e->getApplication()->getServiceManager()->get('Zend\Db\Adapter\Adapter');
$url_table = new UrlMappingTable($dbAdaptor);
$url_data = $url_table->find($request_uri);
$controller = $url_data['controller'];
$action = $url_data['action'];
$id = $url_data['post_id'];
$original_url = $url_data['original_url'];
$alias = $sm->get('Application\Router\Alias');
$alias->setNavigation($original_url);
if(isset($url_data)){
$url = $e->getRouter ()->assemble (array('controller' => $controller,
'action' => $action ,
'id' => $id,
), array (
'name' => 'myurl'
) );
}
print 'no route match';
}
}
after getting the controller and action i just want the dispatcher to forward this controller.
I needed something similar for my project. I ended up just adding a 'catchall' rule in module.config.php
i.e.
'router' => array(
'routes' => array(
'catchAll' => array(
'type' => 'regex',
'options' => array(
'regex' => '/(?<page>.+)',
'defaults' => array(
'controller' => 'Project\Controller\MyController',
'action' => 'customPage',
),
'spec' => '/%page%',
),
),
//Other route items ...
),
//Other stuff ...
)
Place this as the first item in the routes array so it has the lowest precedence. Then you can have your customePageAction to do whatever you want!
Just something not really answering your question:
I am working on dynamic url from database in Zf2 which will occur only when route not found.
There are two ways you can achieve this much more efficiently. If there aren't much routes, you can load them before the route event. For example, you query on bootstrap all your database routes and inject them into the route stack.
Another way is creating a "catch all" route which always will match after all routes failed. Then you don't have a "route not found" but your default route is matched. This will then look for a matching database record. If none found, you just return a 404 response.
In case #1, the controller mapped by your database route is directly dispatched. In the second case, you are in your "database controller" and want to dispatch your controller mapped by the database route, you use the forward plugin:
public function matchAction()
{
// Fetch route from db here
if (!$match) {
$this->getResponse()->setStatusCode(404);
return;
}
return $this->forward($match->getController, array(
'action' => $match->getAction()
));
}