Yii2 - Restrict access for unconfirmed accounts - php

I am trying to implement email account verification.
If a user has not confirmed their email, they can still log in, but they should not be able to access any actions in the account module. So for example, if they try to access:
/account/profile/edit
/account/listing/add
it should redirect the user to /account/default/confirm, which displays a message saying:
"You have not yet confirmed your account, please click the link in the confirmation email, or click here to resend the confirmation email".
I have tried the following:
BaseController:
class BaseController extends Controller
{
protected function findUser($id)
{
if (($model = User::findOne(['id' => $id, 'deleted_at' => null])) !== null) {
if ($model->confirmed_at == null) {
return $this->redirect(['/account/default/confirm']);
}
return $model;
} else {
throw new NotFoundHttpException('The requested page does not exist.');
}
}
}
ProfileController:
class ProfileController extends BaseController
{
public function actionEdit()
{
$user = $this->findUser(Yii::$app->user->id);
$profile = $user->profile; // getProfile() relation in User model
return $this->render('index', [
'profile' => $profile,
]);
}
}
The problem I am having is that it gives me an error:
"Trying to get property 'profile' of non-object".
I think the reason for the error is because it seems to be assigning the redirect to $user, instead of actually terminating the request at the redirect.
I know instead of doing return $this->redirect() in findUser() I can do it in the controller action, but then I would have to do this for every action. Is there a better way of doing this? Maybe some kind of access rules or behaviour?

Here try to check !empty() before $model access like
class BaseController extends Controller
{
protected function findUser($id)
{
if (($model = User::findOne(['id' => $id, 'deleted_at' => null])) !== null) {
if (!empty($model->confirmed_at)) {
return $model;
}
return $this->redirect(['/account/default/confirm']);
} else {
throw new NotFoundHttpException('The requested page does not exist.');
}
}
}

$this->redirect() will return response object - it looks like really bad design if such method may return completely unrelated object (Response or User). You probably should call Application::end() to terminate application, so redirection will take effect without continuing execution of controller action.
protected function findUser($id) {
if (($model = User::findOne(['id' => $id, 'deleted_at' => null])) !== null) {
if ($model->confirmed_at == null) {
$this->redirect(['/account/default/confirm']);
Yii::$app->end();
}
return $model;
}
throw new NotFoundHttpException('The requested page does not exist.');
}

Related

How do I capture a protected page's url in query string?

Beware with me for a second as I try to lay the background to my issue.
So I having using the python web framework Flask close to a year now and it has a wonderful extension called Flask-Login that helps provide user session management kind of like this in laravel.
Having said all that, there is a certain feature in Flask-Login that provides the functionality that when a user is not logged or signed in and tries to access that a page that requires one to be authenticated for example /create_post, they will be redirected back to the login page with that page encoded in the query string like /login?next=%2Fcreate_post.
Am trying to implement the same feature in a laravel project that am working on so I can redirect the user to the page they probably wanted to go to in the first place or to a different route in case that query string doesn't exist and I cannot seem to find where to put my code to do just that and I don't want to mess with anything in the vendor directory(because of the obvious issues that come with that), and I have tried manipulating the file app/Http/Middleware/RedirectIfAuthenticated.php by doing what is below but with no success.
public function handle($request, Closure $next, $guard = null)
{
if (Auth::guard($guard)->check()) {
return redirect('/');
}
$previous_url = url()->previous(); // how do I insert this in query string
return $next($request);
}
Will I have to create my own middleware or is there another way of implementing this kind of feature in laravel?
NOTE: I am not using the default laravel authentication system. I have created my own controller SessionsController to handle logins which contains the below code.
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\User;
class SessionsController extends Controller
{
public function __construct()
{
$this->middleware('auth')->except(['create', 'login']);
}
public function create()
{
$data = [
'title' => 'Login',
'body_class' => 'hold-transition login-page',
];
return view('auth.login', $data);
}
public function login(Request $request)
{
$this->validate($request, [
'username' => 'required',
'password' => 'required',
]);
$user = User::checkCredentials($request->username, $request->password);
if (!$user) {
return back()->with([
'class' => 'alert-danger',
'message' => 'Please check your credentials',
]);
}
// set session active flag to true
$user->session_active = true;
$user->save();
auth()->login($user);
return redirect()->route('dashboard');
}
public function destroy()
{
$user = auth()->user();
$user->last_login = date('Y-m-d H:i:s');
$user->session_active = false;
$user->save();
auth()->logout();
return redirect()->route('login')->with([
'class' => 'alert-success',
'message' => 'You logged out successfully',
]);
}
}
Thank you.
I managed to somewhat solve my issue even though I didn't use query strings as I had wanted.
I create a helper function get_previous_url as shown below
/**
* Gets the previous url
*
* #return null|string
*/
function get_previous_url()
{
$host = $_SERVER['HTTP_HOST'];
$previous_url = url()->previous();
// check if previous url is from the same host
if (!str_contains($previous_url, $host)) {
return null;
}
// get the previous url route
list(, $route) = explode($host, $previous_url);
// make sure the route is not the index, login or logout route
if (in_array(substr($route, 1), ['', 'login', 'logout'])) {
$route = '';
}
return $route;
}
And then I called the same function in my SessionsController class in the create method by doing this
public function create()
{
$previous_url = get_previous_url();
if ($previous_url) {
session(['previous_url' => $previous_url]);
}
...
}
And then I changed my login method to
public function login(Request $request)
{
...
$redirect = redirect()->route('dashboard'); // '/'
if (session()->has('previous_url')) {
$redirect = redirect(session()->pull('previous_url'));
}
return $redirect;
}

Yii2 - Return Response during beforeAction

I am building a test API. I have created a Controller Page which extends from yii\rest\Controller. Actions needs to send a response.
To access actions in this controller, a service_id value needs to be posted. If present I need to evaluate if that service_id exists, if it is active and belongs to the user logged in. If validation fails, I need to send a response.
I am trying to do it using beforeAction(), but the problem is that return data is used to validate if action should continue or not.
So my temporary solution is saving service object in a Class attribute to evaluate it in the action and return response.
class PageController extends Controller
{
public $service;
public function beforeAction($action)
{
parent::beforeAction($action);
if (Yii::$app->request->isPost) {
$data = Yii::$app->request->post();
$userAccess = new UserAccess();
$userAccess->load($data);
$service = $userAccess->getService();
$this->service = $service;
}
return true;
}
public function actionConnect()
{
$response = null;
if (empty($this->service)) {
$response['code'] = 'ERROR';
$response['message'] = 'Service does not exist';
return $response;
}
}
}
But I can potentially have 20 actions which require this validation, is there a way to return the response from the beforeAction method to avoid repeating code?
You can setup response in beforeAction() and return false to avoid action call:
public function beforeAction($action) {
if (Yii::$app->request->isPost) {
$userAccess = new UserAccess();
$userAccess->load(Yii::$app->request->post());
$this->service = $userAccess->getService();
if (empty($this->service)) {
$this->asJson([
'code' => 'ERROR',
'message' => 'Service does not exist',
]);
return false;
}
}
return parent::beforeAction($action);
}
maybe paste in beforeAction after $this->service = $service;
if (empty($this->service)) {
echo json_encode(['code' => 'ERROR', 'message' => 'Service does not exist']);
exit;
}

Authorize users based on roles in CakePHP 3

I would like to authorize users based on few roles. All visitors should be able to reach method show. So I wrote in AppController:
public function beforeFilter(Event $event) {
$this->Auth->allow(['show']);
}
It works.
In initialize() method of AppController I've got also:
$this->loadComponent('Auth', [
'authorize' => 'Controller'
]);
I would like to allow logged users with role "user" to reach all "index", and "add" methods, so I wrote in AppController:
public function isAuthorized($user) {
if (isset($user['role']) && $user['role'] === 'admin') {
return true;
}
if (isset($user['role']) && $user['role'] === 'user') {
$this->Auth->allow(['index', 'logout', 'add']);
}
return false;
}
Admin can reach all methods as expected. User logged with role "user" can't reach "index" or "add" method. How can I fix this?
Instead of using your logic to add additional Auth allows, just use the logic to determine if they're in an action they're allowed, by checking the action, and return true if they're authorized.
public function isAuthorized($user) {
// Admin allowed anywhere
if (isset($user['role']) && $user['role'] === 'admin') {
return true;
}
// 'user' allowed in specific actions
if (isset($user['role']) && $user['role'] === 'user') {
$allowedActions = ['index', 'logout', 'add'];
if(in_array($this->request->action, $allowedActions)) {
return true;
}
}
return false;
}
(obviously this code could be shortened to your liking, but it shows the concept)
I find this solution to be great and easier to maintain.
//in all controllers that you want to restrict access
public function isAuthorized($user)
{
//an array since we might want to add additional roles
$possibleRoles = array('admin');
return $this->confirmAuth($user['role'], $possibleRoles);
}
//in AppController
public function confirmAuth($userRole, $allowedRoles)
{
return in_array($userRole, $allowedRoles);
}

action delete with request page not exist

Hi i want to remove some data. I do view where i want to delete aditional data. This is my controller veiew becouse i want to make there button where i can delete data:
public function actionView($id) {
return $this->render('view', [
'model' => $this->findModel($id),
'userDate'=>$this->findData($id)
]);
}
public function actionDelet($id) {
$this->findData($id)->delete();
return $this->redirect(['index']);
}
public function findData($id){
if (($model = Profile::findOne($id)) !== null) {
$id=$model->Rel_UserData;
$user = UserData::find()->where(['Id' => $id])->one();
return $user;
} else {
throw new NotFoundHttpException('The requested page does not st.');
}
}
I guess solution is like that:
$item = $this->findData($id);
$item->delete();
You already have a relational function calling the UrRoyalUserData class: getRelRoyalUserData(). You can simplify your code doing:
public function findData($id)
{
if (($model = Profile::findOne($id)) !== null) {
$user = $model->relRoyalUserData;
return $user;
}
throw new NotFoundHttpException('The requested page does not st.');
}
Besides that, can you change your action and check what is returning?
public function actionDelet($id)
{
var_dump($this->findData($id));
}
If it throws you the same error, that means you dont have that Profile in your table. But if don't return a UrRoyalUserData class, the problem is you don't have any UrRoyalUserData related to that Profile.

Yii showing user type in view using user identity object

In my web application I need to show the type of user in the view in protected/views/layouts/main.php.
But I am getting this error:
"CException" ."Property "CWebUser.type" is not defined."
I am unable to get rid of this error , how to resolve this issue?
I am using this line of code to display the type of user
array('label'=>'Logout ('.Yii::app()->user->type.')', 'url'=>array('/site/logout'),
'visible'=>!Yii::app()->user->isGuest)
I tried by using user->user_type also but not working
My code for the UserIdentity class
class UserIdentity extends CUserIdentity
{
private $_id;
public function authenticate()
{
$user = User::model()->findByAttributes(array(
'email'=>$this->username));
if ($user === null) {
$this->errorCode=self::ERROR_USERNAME_INVALID;
} else if ($user->pass !==
hash_hmac('sha256', $this->password,
Yii::app()->params['encryptionKey']) ) {
$this->errorCode=self::ERROR_PASSWORD_INVALID;
} else {
$this->errorCode=self::ERROR_NONE;
$this->setState('type', $user->user_type);
$this->setState('id', $user->id);
$this->_id = $user->id;
}
return !$this->errorCode;
}
public function getId() {
return $this->_id;
}
}
Also since I am using Role based access control I have changed the code in user.php for assigning roles to users
My code to assign users type.
public function afterSave() {
if (!Yii::app()->authManager->isAssigned(
$this->type,$this->id)) {
Yii::app()->authManager->assign($this->type,
$this->id);
}
return parent::afterSave();
}
And I have used this code in my SiteController for assigning roles to users
$auth->assign($user->type,$user->id);
If I;m right in what's happening, there may be times when you're not logged in that Yii is trying to access the user settings. As you're not logged in you can't access them, hence the error. So in the label, check that user isset()
'label' => (isset(Yii::app()->user->type) ? Yii::app()->user->type : '')

Categories