I'm somewhat new to Laravel and am having issues authenticating users the way I want. I currently have a user table and a companies table in my database.
I have seen numerous examples of authenticating with multiple tables based on whether the user is an admin or not using gaurds, but I looking to authenticate all users in the following way:
User's email and password match (fields in user table)
User's status is currently active (field in user table)
User belongs to company with active status (field in companies table)
Since the time I posted this question I've been researching as much as possible and finally ended up with a satisfactory solution. I'm not sure if it is the cleanest way possible, but it works for me. If anyone else can benefit from this, here are the steps I took:
1) I added the following code to app\User.php model:
# company / user relationship
public function company() {
return $this->belongsTo('App\Company', 'comp_id');
}
# determine if company is active
public function activeCompany() {
$comp_stat = $this->company()->first(['status'])->toArray();
return ($comp_stat >= 1) ? true : false;
}
I then modified the handle method in app\Http\Middleware\Authenticate.php with the following:
public function handle($request, Closure $next, $guard = null) {
if (Auth::guard($guard)->guest()) {
if ($request->ajax() || $request->wantsJson()) {
return response('Unauthorized.', 401);
} else {
return redirect()->guest('login');
}
}
/* begin modified section */
if (Auth::check()) {
# logout if company is inactive
if (!auth()->user()->activeCompany()) {
Auth::logout();
return redirect()->guest('login')->with('comp-status-error', true);
}
# logout if user is inactive
if (auth()->user()->status != 1) {
Auth::logout();
return redirect()->guest('login')->with('user-status-error', true);
}
}
/* end modified section */
return $next($request);
}
So, technically the user already gets authenticated before the company and user status checks, which is why you have to call Auth::logout() manually. This is also the reason why it feels a little "dirty" to me, but again, I couldn't figure out any other way and so I had to do what worked! I encourage anyone to comment if they see a better way to accomplish this.
Related
I have an AuthController, where I have extended the getCredentials method so the user needs to be both existant and active to be able to login. This is the method:
protected function getCredentials(Request $request) {
$credentials = $request->only($this->loginUsername(), 'password');
return array_add($credentials, 'status', '1');
}
I would also like the failed login messages to be different, depending on whether or not the user is active, so the user knows if he is failing his username / password, or just because he hasn't activated his account yet.
I could override the login method of the AuthenticatesUser trait, but it seems overkill to duplicate all the logic just to change that.
Can I extend the sendFailedLoginResponse method to make some sort of validation there, based on the previous Auth::guard->attempt() call? I mean, does that method leave any information behind that allows me to know, after the call has been made, what made the attempt return false?
Or how would I approach this, without having to override a method completely just to make a simple validation?
Thank you.
public function authenticated(Request $request, User $user ) {
// The user was authenticated check if it the active column is true or not
if($user->active == false){
//Store some flash message here saying he's not account is not active
//Log him out after.
Auth::logout();
}
return redirect()->intended( $this->redirectPath() );
}
We have used RBAC to implement simple role based permissions for CRUD, but now we need to also add a 'visibility' functionality which makes it possible to limit content visibility (R) to only registered users or only the content owners.
So, how can we limit content visibility on different levels, for example
PUBLIC: anybody can see the content, including anonymous
INTERNAL: only registered users can see the content
PRIVATE: only the creator can see the content
What would be the best way to implement this, it looks like RBAC does not have a straightforward way of dealing with this.
I think that the problem can be solved by using defaultScope in models. Thus, before giving the content, we can check the current role of the user data and give the necessary conditions.
public static function find()
{
$userRoleArray = \Yii::$app->authManager->getRolesByUser(Yii::$app->user->getId());
$userRole = current($userRoleArray)->name;
if ($userRole == 'admin') {
return parent::find()->where("Your condition");
} elseif ($userRole == 'moderator') {
return parent::find()->where("Your condition");
}
}
you can make a permission function and run in each function that will take user role as argument and returns true or redirect to not allowed page.
Here is something I tried but you can modify according to your need.
public function allowUser($min_level) {
//-1 no login required 0..3: admin level
$userRole = //get user role;
$current_level = -1;
if (Yii::$app->user->isGuest)
$current_level = 0;
else
$current_level = userRole;
if ($min_level > $current_level) {
$this->redirect(array("/pages/not-allowed"),true);
}
}
Using a many many relational query with users having many clients and clients having many users. Trying to view a record of a particular client for a particular user. And if that client is not associated with that user, redirect to a different page.
// the relation in the client model
public function relations()
{
// NOTE: you may need to adjust the relation name and the related
// class name for the relations automatically generated below.
return array(
'owners'=>array(self::MANY_MANY, 'User','owner_client(owner_id, client_id)'),
);
}
//the relation in the user model
public function relations()
{
return array(
'clients'=>array(self::MANY_MANY, 'Clients','owner_client(owner_id, client_id)'),
);
}
//determine if user can view this client
//client record
$client_record = Clients::model()->findByPk($id);
//many query to find users
$users = $client_record->owners;
//if user id is not found in array, redirect
if (!in_array(Yii::app()->user->id, $users))
{
$this->redirect(array('/site/dashboard'));
}
The above code redirects, even though I know the client is related to the user logged in
When you call $users = $client_record->owners;, what you're getting back is an array of all your user models that are associated with the current client. As a result, you're comparing integers to objects, which means your in_array() condition will always fail.
What I recommend is that you build a conditional query to do your verification check. Something like this should work:
$model = Clients::model()->with(
array(
'owners'=>array(
'select'=>'owner_id',
'condition'=>'user.id = '.Yii::app()->user->id,
),
)
)->findByPk($id);
if ($model === null) {
$this->redirect(array('/site/dashboard'));
}
I m testing a class that saves a model. This model has a behavior where it saves the user of the record which I m going to insert, with this method
public function beforeSave($event) {
if (($this->getOwner()->getTableSchema()->getColumn($this->campoUsuarioCreacion)!==null))
$this->getOwner()->{$this->campoUsuarioCreacion} = Yii::app()->user->id;
if ($this->getOwner()->getTableSchema()->getColumn($this->campoUsuarioModificacion)!==null)
$this->getOwner()->{$this->campoUsuarioModificacion} = Yii::app()->user->id;
return parent::beforeSave();
}
But when I m testing, there is a problem with Yii::app()->user->id. I think that the problem is that no user is logged in. So, How can I solve the problem, without copying again the class with a harcoded user id? Is there a way to set the app user id?
Without broaching the issue of whether or not the user is logged in or Yii::app()->user->id is defined, you need to add this to the bottom of your function:
return parent::beforeSave();
eg:
public function beforeSave($event) {
if (($this->getOwner()->getTableSchema()->getColumn($this->campoUsuarioCreacion)!==null))
$this->getOwner()->{$this->campoUsuarioCreacion} = Yii::app()->user->id;
if ($this->getOwner()->getTableSchema()->getColumn($this->campoUsuarioModificacion)!==null)
$this->getOwner()->{$this->campoUsuarioModificacion} = Yii::app()->user->id;
return parent::beforeSave();
}
I have login form with input text fields:
Group Name
User Name
User Password
I have two tables
groups
id
name
users
id
name
group_id
I have its mapping entities and associations.
But user name not unique within table users, because different groups can include users with equal names. Therefore i need:
find group by name in table groups
find user by name in table users with condition where group_id=<group_id>
How to do it correctly in Zend Framework 2 using Doctrine 2?
All official documentation and examples depict situation, where identity property is single column (example).
Sorry for my bad language. Thanks.
Instead of making my own implementation of Doctrine's authentication services i decide to implement it via form validation inside isValid() method of my authentication form.
Example:
<?php
namespace My\Form\Namespace;
use Zend\Form\Form;
use Zend\ServiceManager\ServiceLocatorInterface;
use Zend\InputFilter\InputFilterProviderInterface;
class Auth extends Form implement InputFilterProviderInterface
{
protected $_em;
public function __construct(ServiceLocatorInterface $sm)
{
parent::__construct('auth');
// inject Doctrine's Entity Manager
$this->_em = $sm->get('Doctrine\ORM\EntityManager');
// login field
$this->add(...);
// password field
$this->add(...);
// group_name field
$this->add(...);
}
public function getInputFilterSpecification()
{
//Input filter specification here
...
}
public function isValid()
{
/*
* input filter validations
*/
if (!parent::isValid())
return false;
/*
* group exists validation
*/
$group = $this->_em
->getRepository('<Group\Entity\Namespace>')
->findOneBy(array(
'name' => $this->get('group_name')->getValue(),
));
if (!$group){
$this->get('group_name')
->setMessages(array(
'Group not found',
));
return false;
}
/*
* user exists validation
*/
$user = $this->_em
->getRepository('<User\Entity\Namespace>')
->findOneBy(array(
'group_id' => $group->getId(),
'name' => $this->get('login')->getValue(),
));
if (!$user){
/*
* It's not good idea to tell that user not found,
* so let it be password error
*/
$this->get('password')
->setMessages(array(
'Login or password wrong',
));
return false;
}
/*
* password validation
*/
$password = $this->get('password')->getValue();
// assume that password hash just md5 of password string
if (md5($password) !== $user->getPassword()){
$this->get('password')
->setMessages(array(
'Login or password wrong',
));
return false;
}
return true;
}
}
Inside controller it is enough to call $form->isValid() to make sure that user entered correct authentication data.
I have the same problem.
I have to do two authentications in same application, because my boss doesn't wanna two databases. So, I had to make two user tables and two login pages.
One route to admin -> /admin/login
And the front-end for other users -> /login
I've tried to put on more authenticate in doctrine authentication array but it didn't work.
I think I'll open a issue on doctrine github page.