I'm using ZF2 in combination with ZFCUser and bjyauthorize. I have a landing page which should be globally accessable. All other pages need to be behind a login.
At first I blamed bjyauthorize for not letting guest users access my landing page. But after some discussions it seems that ZFCUser is blocking the way.
My question is: How can I tell ZFCUser not to block one page/action?
Edit:
My Application/Module.php looks like in this post. When I add my app myApp to the whitlist, I can access my landing page but all other actions from myApp as well.
Any ideas how to alter the condition that I can match the URL or just whitlist my frontend-action?
Maybe I could add a second route to my landing page. But that's not a clean solution, right?
If you insist on checking authentication in the onBoostrap method you could do something like this:
class Module
{
protected $whitelist = array(
'zfcuser/login' => array('login'),
'your-landing-route' => array('your-landing-action'),
);
public function onBootstrap($e)
{
$app = $e->getApplication();
$em = $app->getEventManager();
$sm = $app->getServiceManager();
$list = $this->whitelist;
$auth = $sm->get('zfcuser_auth_service');
$em->attach(MvcEvent::EVENT_ROUTE, function($e) use ($list, $auth) {
$match = $e->getRouteMatch();
// No route match, this is a 404
if (!$match instanceof RouteMatch) {
return;
}
// Route and action is whitelisted
$routeName = $match->getMatchedRouteName();
$action = $match->getParam("action");
if(array_key_exists($routeName,$list) && in_array($action,$list[$routeName])) {
return;
}
// User is authenticated
if ($auth->hasIdentity()) {
return;
}
// Redirect to the user login page, as an example
$router = $e->getRouter();
$url = $router->assemble(array(), array(
'name' => 'zfcuser/login'
));
$response = $e->getResponse();
$response->getHeaders()->addHeaderLine('Location', $url);
$response->setStatusCode(302);
return $response;
}, -100);
}
}
I've just changed the code a little but so your white list also contains specific actions. Then we can check the action parameter to be a little bit more specific with your white listing.
I don't know if this is the best way to do it, I'm just showing you how you can do it.
I don't think you even need to check authentication when using BjyAuthorize as you can just use resource checks. If a user has anything other than a guest role then they are a real user and are authenticated. Again, I'm not 100% on that but I do know that I don't use ZfcUser authentication checks in my application which uses BjyAuthorize. I just use route guards to specify the role level needed for a aparticular route.
Maybe somebody else could clarify this?
Related
Im working on quite a big app using slim 3 as my router.
The menu structure is like this. Some routes need to be grouped and some not (i think). Im not sure how i should do this.
Example menu structure. But it will hold alot more items. Is grouping a good idea here? I mean, as you see not all "clients" has the "settings" sub page/route, but it may be will in the future. How would one write the grouping logic for this?
client 1
info
settings
loremipsum
loremipsum
client 2
info
loremipsum
loremipsum
client 3
info
loremipsum
loremipsum
You could implement a small middleware and check the persmissions per client.
The setting "determineRouteBeforeAppMiddleware" must be set to true.
use Slim\App;
use Slim\Http\Request;
use Slim\Http\Response;
$app = new App([
'settings' => [
// Must be set to true to get access to route within middleware
'determineRouteBeforeAppMiddleware' => true
]
]);
Then add this middleware and customize it:
$container = $app->getContainer();
// Simple Route Access Control Middleware
$app->add(function (Request $request, Response $response, $next) use ($container) {
// Retrieving Current Route
/* #var \Slim\Route $route */
$route = $request->getAttribute('route');
if (!$route) {
return $next($request, $response);
}
$name = $route->getName();
$groups = $route->getGroups();
$methods = $route->getMethods();
$arguments = $route->getArguments();
// Do something with that information
// Check the permissions here...
$routePermission = $container->get(RoutePermission::class);
if (!$routePermission->isRouteAllowed($name, $arguments['client'])) {
// Permission denied
return $response->withStatus(403, 'Forbidden');
} else {
// OK :-)
return $next($request, $response);
}
});
Lets say that my page has 10 sections, in 6 of them I have to check if the user is logged in, and if not, redirect him to the "login/register" page.
I found myself repeating this code in the controller of those 6 pages:
public function actionthatneedsauthAction()
{
$sl = $this->getServiceLocator();
$authService = $sl->get('doctrine.authenticationservice.orm_default');
$user = $authService->getStorage()->read(); //is the user logged in?
if ($user) { //auth successful
//-------------/*CODE FOR THIS SPECIFIC CONTROLLER GOES HERE*/--------
return new ViewModel(array(
'user' => $user,
'somethingelse' => $somethingelse
));
} else { //auth denied
return $this->redirect()->toRoute(user, array('action' => 'login'));
}
}
I tried to encapsulate that into a service, called islogged (this is a model, not a controller), but I couldn't make it work because I couldn't find a way to redirect to a controller from inside a model, I only know how to redirect to a controller via another controller.
So in my usermanager.php I had a function like this one:
public function islogged()
{
$sl = $this->getServiceLocator();
$authService = $sl->get('doctrine.authenticationservice.orm_default');
$user = $authService->getStorage()->read(); //is the user logged in?
if ($user) { //auth successful
return $user;
} else {
/*
redirect to the login screen, dont know how to do it,
this code doesnt work here:
return $this->redirect()->toRoute(NULL, array(
'controller' => 'user',
'action' => 'login'
));
*/
}
}
so the idea was that in my controllers I only had to write:
$user = islogged();
and all the code repetition I mentioned won't be necessary anymore.
Is it a good practice what I tried to do with the usermanager.php islogged function?
If it is a good practice, how am I supposed to redirect to a controller from inside a model?
If is not a good practice, which will be the way to avoid all that code repetition that I'm having in my controllers?
I know that I can put the authentication step into the onboostrap() but in that case the auth will be triggered for all of my pages, and I just want it in some of them.
I would advise you to implement Doctrine Authentication with official DoctrineModule Authentication described in the docs folder of the repo.
Read this - Link to DoctrineModule Authentication
Then you can handle your Authentication check via the zf2 own Controller and View Helpers identity. See example in the docs here.
I use this ACL Module on my apps: https://github.com/ZF-Commons/zfc-rbac
My Customer overview controller then looks as so:
<?php
namespace RoleBasedCustomer\Controller;
use RoleBasedUser\Service\AuthenticationService;
use RoleBasedUser\Service\UserService;
use RoleBasedUser\Controller\AbstractMultiModelController;
class OverviewController extends AbstractMultiModelController
{
public function __construct(
AuthenticationService $authService,
UserService $userService
) {
$this->authService = $authService;
$this->userService = $userService;
}
public function indexAction()
{
if ( ! $this->authService->hasIdentity() ) {
return $this->redirect()->toRoute('customer/login');
}
}
}
The only thing i had to do is replace these two lines:
$authService = $this->getServiceLocator()
->get('doctrine.authenticationservice.orm_default');
$user = $authService->getStorage()->read(); //is the user logged in?
with this one:
$user = $this->identity();
I study ZF2 and have problem with login process. I have two modules login and moduleExample.
Login module is based on http://samsonasik.wordpress.com/2012/10/23/zend-framework-2-create-login-authentication-using-authenticationservice-with-rememberme/
I can redirect moduleExample to login route with condition hasIdentity() in Controllers, but can I set redirecting to this module at one place? Controllers will probably be more. I've already tried in onBootstrap method (Module.php), but then it is redirected everywhere (in all modules).
Where I can do what I describe?
It is possible to handle the login redirection at one place, one way is to define a custom event which is called in the routing event chain. To do this, you have to add the handler in your Module.php (preferably in the authentication module):
class Module
{
public function onBootstrap(MvcEvent $e)
{
$eventManager = $e->getApplication()->getEventManager();
$sm = $e->getApplication()->getServiceManager();
$moduleRouteListener = new ModuleRouteListener();
$moduleRouteListener->attach($eventManager);
//attach event here
$eventManager->attach('route', array($this, 'checkUserAuth'), 2);
}
public function checkUserAuth(MvcEvent $e)
{
$router = $e->getRouter();
$matchedRoute = $router->match($e->getRequest());
//this is a whitelist for routes that are allowed without authentication
//!!! Your authentication route must be whitelisted
$allowedRoutesConfig = array(
'auth'
);
if (!isset($matchedRoute) || in_array($matchedRoute->getMatchedRouteName(), $allowedRoutesConfig)) {
// no auth check required
return;
}
$seviceManager = $e->getApplication()->getServiceManager();
$authenticationService = $seviceManager->get('Zend\Authentication\AuthenticationService');
$identity = $authenticationService->getIdentity();
if (! $identity) {
//redirect to login route...
$response = $e->getResponse();
$response->setStatusCode(302);
//this is the login screen redirection url
$url = $e->getRequest()->getBaseUrl() . '/auth/login';
$response->getHeaders()->addHeaderLine('Location', $url);
$app = $e->getTarget();
//dont do anything other - just finish here
$app->getEventManager()->trigger(MvcEvent::EVENT_FINISH, $e);
$e->stopPropagation();
}
}
}
what you tried is correct, so then you just need this little bit here
$controller = $e->getTarget();
$params = $e->getApplication()->getMvcEvent()->getRouteMatch()->getParams();
if(!$params['controller'] == 'your login conroller path e.g. RouteFolder/Controller/LoginControllerName'){
return $controller->redirect()->toRoute('your redirect route')
}
do a
print_r($params['controller'])
and see what it returns and you will understand what i mean
so the event onBoostrap wont redirect you if your current location is the controller where the user comes to login
I have two modules (default and mobile) the module mobile is a rewrite the default portal in jquery mobile but with much less controllers and actions!
I thought of write a controller plugin that check if controller and action exist in module mobile, if not I would like overwrite the module mobile to default.
I try this:
public function dispatchLoopStartup(Zend_Controller_Request_Abstract $request)
{
$dispatcher = Zend_Controller_Front::getInstance()->getDispatcher();
if ($request->getModuleName() == 'mobile') {
if (!$dispatcher->isDispatchable($request)) {
// Controller or action not exists
$request->setModuleName('default');
}
}
return $request;
}
but $dispatcher->isDispatchable($request) return always true though the action not exist! :S
and i receive "Action foo does not exist and was not trapped in __call()"
How can I do?
Thanks
Have you ever wondered how to check if a controller/action exist in zend FM from any side of app ? Here is the code
$front = Zend_Controller_Front::getInstance();
$dispatcher = $front->getDispatcher();
$test = new Zend_Controller_Request_Http();
$test->setParams(array(
'action' => 'index',
'controller' => 'content',
)
);
if($dispatcher->isDispatchable($test)) {
echo "yes-its a controller";
//$this->_forward('about-us', 'content'); // Do whatever you want
} else {
echo "NO- its not a Controller";
}
EDIT
Check like this way
$classMethods = get_class_methods($className);
if(!in_array("__call", $classMethods) &&
!in_array($this->getActionMethod($request), $classMethods))
return false;
and also please see detail link
I would suggest you make a static or dynamic routes either via config resource manager, bootstrap or via front controller plugin:
Example of defining static routes in Bootstrap.php:
public function _initRoutes()
{
$front = Zend_Controller_Front::getInstance();
$router = $front->getRouter(); // default Zend MVC routing will be preserved
// create first route that will point from nonexistent action in mobile module to existing action in default module
$route = new Zend_Controller_Router_Route_Static(
'mobile/some-controller/some-action', // specify url to controller and action that dont exist in "mobile" module
array(
'module' => 'default', // redirect to "default" module
'controller' => 'some-controller',
'action' => 'some-action', // this action exists in "some-controller" in "default" module
)
);
$router->addRoute('mobile-redirect-1', $route); // first param is the name of route, not url, this allows you to override existing routes like default route
// repeat process for another route
}
This would effectively route request for /mobile/some-controller/some-action to /default/some-controller/some-action
some-controller and some-action should be replaced with proper controller and action names.
I was using static routing which is ok if you route to exact urls, but since most applications use additional params in url for controller actions to use, it is better to use dynamic routes.
In above example simply change route creation class to Zend_Controller_Router_Route and route url to "mobile/some-controller/some-action/*" and every request will be routed dynamically like in example:
/mobile/some-contoller/some-action/param1/55/param2/66
will point to
/default/some-controller/some-action/param1/55/param2/66
For more info about routing in ZF1 check this link
Does anyone have an example or any idea how one would implement the FOSRestBundle together along with the FOSUserBundle. I have a Web app already developed with Symfony 2 and the FOSUserBundle, but I would like to add the FOSRestBundle for an api layer. I want to be able to pass it a username and password and receive some type of token from the FOSUserBundle that represents the logged in user that I can then pass and forth between other api calls. Does anyone know of a good way to do this?
FOSUserBundle should be natively "restful" meaning that it may follow the REST recommandations.
However, it is not designed to work natively with FOSRestBundle, the simplest way to do that is to override the UsersController in your Bundle and adapt your actions.
For example, to allow RESTFul registration, you may write the following action:
public function postUsersAction()
{
$form = $this->container->get('fos_user.registration.form');
$formHandler = $this->container->get('fos_user.registration.form.handler');
$confirmationEnabled = $this->container->getParameter('fos_user.registration.confirmation.enabled');
$process = $formHandler->process($confirmationEnabled);
if ($process) {
$user = $form->getData();
$authUser = false;
if ($confirmationEnabled) {
} else {
$authUser = true;
}
$response = new Response();
if ($authUser) {
/* #todo Implement authentication */
//$this->authenticateUser($user, $response);
}
$response->setStatusCode(Codes::HTTP_CREATED);
$response->headers->set(
'Location',
$this->generateUrl(
'api_users_get_user',
array('user' => $user->getId()),
true
)
);
return $response;
}
return RestView::create($form, Codes::HTTP_BAD_REQUEST);
}