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();
Related
I worte a plugin IdentityPlugin to check login status of a user. If the user session gets logout, I want to redirect them to login page. My code is given below.
public function checkLogin($logout=true,$next='login'){
if($this->auth->hasIdentity()){
}elseif ($logout){
return $this->getController()->redirect()->toRoute($next);
}
}
in my controller
// Check identity, if not found- redirect to login
$this->IdentityPlugin()->checkLogin();
Any idea?
You're returning a response to the controller but you're not returning it from the controller itself.
For example you could try this is your controller:
$check = $this->IdentityPlugin()->checkLogin();
if ($check instanceof Response) {
return $check;
}
A more complex solution could be to stop the propagation of the controller's MvcEvent, set whatever response you want, and return directly.
Hi you need to config you plugin in factories in module.config.php and pass service manager to __construct, like below:
'controller_plugins' => array(
'factories' => array(
'CheckLogin' => function($sm){
$checkLogin = new Application\Plugin\CheckLogin($sm);
return $checkLogin;
},
),
),
Then in your plugin you will be able to call all you need using service Manager:
namespace Application\Plugin;
use Zend\Mvc\Controller\Plugin\AbstractPlugin;
class CheckLogin extends AbstractPlugin
{
public function __construct($sm)
{
$auth = $sm->getServiceLocator()->get("Zend\Authentication\AuthenticationService");
if( !$auth->hasIdentity()){
$sm->getController()->plugin('redirect')->toUrl('/login');
}
}
}
I'm currently developing a restful/stateless api in cakephp which uses tokens (at the moment) and should use rolling tokens (like suggested here from g-shearer) in the future. My current implementation works, but i'm really concerned if i've implemented everything the right way (auth components especially custom auth components seem really confusing to me)
PS: I'm using the current version of cakephp (2.5.1).
1: I've created the file TokenAuthenticate.php in Controller/Component/Auth:
<?php
App::uses('BaseAuthenticate', 'Controller/Component/Auth');
class TokenAuthenticate extends BaseAuthenticate {
public function authenticate(CakeRequest $request, CakeResponse $response) {
}
public function getUser(CakeRequest $request) {
//set values from request headers
$publictoken = $request->header('Security-Public-Token');
$accesstoken = $request->header('Security-Access-Token');
$timestamp = $request->header('Security-Timestamp');
// check if required header fields are set
if (empty($publictoken) || empty($accesstoken) || empty($timestamp)) {
return false;
}
// init required token model
$Token = ClassRegistry::init('Token');
//check if token pair exists
if ($dbtoken = $Token->findByPublic($publictoken)) {
if ($accesstoken == md5($dbtoken['Token']['private'] . $timestamp)) {
//valid token - return user
$User = ClassRegistry::init('User');
$dbuser = $User->findById($dbtoken['Token']['user_id'])['User'];
return $dbuser;
} else {
//invalid token
return false;
}
} else {
//invalid token pair
return false;
}
}
public function unauthenticated(CakeRequest $request, CakeResponse $response) {
return true;
}
}
?>
then i've added the following to my controller:
class UsersController extends AppController {
public $uses = array('User', 'Token');
public $components = array('Auth' => array('authenticate' => array('Token')));
public function beforeFilter() {
parent::beforeFilter();
AuthComponent::$sessionKey = false;
$this->Auth->autoRedirect = false;
$this->Auth->allow('login', 'register');
}
in my actions i check the status like so:
if (!$this->Auth->loggedIn()) {
$this->set(array('error' => 'INVALID_AUTHENTIFICATION'));
$this->render('view');
}
So I can set a custom error and output it without being redirected to the login action (note the unauthenticated function in my tokenauthentication file which returns true - so cakephp does not redirect you)
I think the login process should happen in the authenticate function of my TokenAuthenticate file and not in the login action of my controller, or am i wrong? What is the correct way to achieve this goal?
PS: How would it be possible to add a new token pair (to every authenticated output) automatically with cakephp so the tokens are 'rolling'?
The whole api output is json encoded if that matters
also cakephp still sets a cookie sometimes even though i disabled this (AuthComponent::$sessionKey = false;). How to stop this?
EDIT: So I've added an beforeRender() function to my userscontroller and now the tokens are rolling (Y)
//renew tokens
public function beforeRender() {
//only add tokens if logged in
if ($this->Auth->loggedIn()) {
//find old token
$oldToken = $this->Token->findByUser_id($this->Auth->user('id'));
//delete old token
$this->Token->delete($oldToken['Token']['id']);
//create new token pair
$this->Token->create();
$this->Token->save(array(
'user_id' => $this->Auth->user('id'),
'public' => Security::hash(substr(str_shuffle('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!##$') , 0 , 15 )),
'private' => Security::hash(substr(str_shuffle('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!##$') , 0 , 15 ))
));
//set it for the view
$this->set(array('token' => $this->Token->read()));
}
}
Is this the right way to implement something like this? I always want to do things the right and 'perfect' way so any criticsm is welcome :)
I'am a new in Yii and I'm trying to do a RBAC control in my web application. I write an authorization and registration pages and everything working perfect. But when I trying to read current user's role using Yii::app()->user->role I'm getting an error with the following content:
PHP warning
include(User.php): failed to open stream: No such file or directory
I did everything like in official cookbook. Honestly, I don't know why this happens that's why if someone could help me to solve this problem I will be very appreciated.
Here I wrote the steps which I did when writing the role based user control.
As mentioned in official Yii cookbook I created a simple database table named users where I defined the role attribute. Then I write a WebUser class:
class WebUser extends CWebUser {
private $_model = null;
function getRole() {
if($user = $this->getModel()){
return $user->role;
}
}
private function getModel(){
if (!$this->isGuest && $this->_model === null){
$this->_model = User::model()->findByPk($this->id, array('select' => 'role'));
}
return $this->_model;
}
}
Next step I changed the default realization of the UserIdentity::authentificate() method, where I trying to assign role to the current user:
public function authenticate()
{
$users = Users::model()->find('LOWER(name)=?', array(strtolower($this->name)));
if($users === null)
$this->errorCode = self::ERROR_USERNAME_INVALID;
else if ($users->validatePassword(md5($this->password)))
$this->errorCode = self::ERROR_PASSWORD_INVALID;
else
{
$this->_id = $users->id;
$this->username = $users->name;
$auth=Yii::app()->authManager;
echo "user role = ".$users->role.", user id = ".$this->_id;
if(!$auth->isAssigned($users->role,$this->_id))
{
if($auth->assign($users->role,$this->_id))
{
Yii::app()->authManager->save();
}
}
$this->errorCode=self::ERROR_NONE;
}
return $this->errorCode == self::ERROR_NONE;
}
...
Finally, I declare all this components in the main web config file:
...
'import'=>array(
'application.models.*',
'application.components.*',
),
...
'components'=>array(
'user'=>array(
// enable cookie-based authentication
'class'=>'WebUser',
'allowAutoLogin'=>true,
),
'authManager' => array(
'class' => 'PhpAuthManager',
'defaultRoles' => array('guest'),
),
...
In getModel() you're using User::model(), but in authenticate() you're calling Users::model(), could it be that you're trying to call the wrong model class (it's Users instead of User)?
i'm currently writing a Application based on YII.
My action for index:
public function actionIndex() {
$data = array();
$data['server'] = Server::model()->findByPk(1);
$data['dataProvider'] = new CActiveDataProvider('ServerUserPermission', array('criteria' => array('condition' => 'serverID=:id', 'params' => array(':id' => 1))));
$this->render('index', $data);
}
my ajax action:
public function actionAddPermission($server) {
if(Util::checkServerPower($server, Permission::MODIFY_SERVER)) {
$perm = new ServerUserPermission;
$perm->userID = 1;
$perm->serverID = $server;
$perm->power = 10;
try {
if ($perm->save()) {
echo "OK";
} else {
echo Util::print_r($perm->getErrors());
}
} catch (Exception $e) {
echo 'Critical Error Code: ' . $e->getCode();
}
} else {
echo 'No Permissions';
}
}
My view links to the addPermission action by using a button:
echo CHtml::ajaxButton("Insert New Player", array('addPermission', 'server' => $server->serverID), array('success'=>'refresh'));
My function Util::checkServerPower(...) checks the current User of the Application. Consequence: Ajax requests in YII are handled by an Guest AuthWeb User, but i need to check whether the User is actually allowed to add permissions or not. I currently cannot think of a secured solution to protect malicious data send by other guests or not. Is it somehow possible to get the (server-side) userID of the Ajax-call?
Thanks anyway
sincerly
I would do it by using the built in access control and extending CWebUser.
It might seem lengthy but I think it's a clean solution. (We already have Yii::app()->user->isGuest and the like, so why not check all permissions here?)
1) Activate the access control filter.
(In one controller or in /components/controller.php for all your controllers at once)
public function filters()
{
return array( 'accessControl' ); // Tell Yii to use access rules for this controller
}
2) Add an access rule
In the concerned controller. (Sorry, I didn't bother with your index-action.)
public function accessRules()
{
return array(
[
'allow',
'actions'=>['AddPermission'],
'expression'=>'$user->has(Permission::MODIFY_SERVER)'
],
['deny'], // Deny everything else.
);
}
3) Extend CWebUser
// components/WebUser.php
class WebUser extends CWebUser {
public function has( $permission)
{
// Check database for permissions using Yii::app()->user->id
...
}
}
4) Configure your app to use your new WebUser instead of CWebUser
// config/main.php
'components'=>[
'user'=>[
'class' => 'WebUser',
],
],
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?