yii2 disable inbuilt authentication - yii2

I have written my own Users model which extends ActiveRecord and implements IdentityInterface and also defined tablename. and implemented all methods.
Users.php
public static function tableName()
{
return 'users';
}
// other methods are also present like rules() , getId() etc.
UserController.php
public function actionLogin()
{
$this->layout = 'blank';
if (!Yii::$app->myuser->isGuest) {
return 'hello';
}
$model = new UserLoginForm();
if ($model->load(Yii::$app->request->post()) && $model->login()) {
return $this->redirect(['user/view',
'id' => $model->getUser()->id,
]);
} else {
$model->password = '';
return $this->render('login', [
'model' => $model,
]);
}
}
UserLoginForm.php
<?php
namespace backend\models;
use Yii;
use yii\base\Model;
class UserLoginForm extends Model
{
public $username;
public $password;
public $rememberMe = true;
private $_user;
/**
* {#inheritdoc}
*/
public function rules()
{
return [
// username and password are both required
[['username', 'password'], 'required'],
// rememberMe must be a boolean value
['rememberMe', 'boolean'],
// password is validated by validatePassword()
['password', 'validatePassword'],
];
}
/**
* Validates the password.
* This method serves as the inline validation for password.
*
* #param string $attribute the attribute currently being validated
* #param array $params the additional name-value pairs given in the rule
*/
public function validatePassword($attribute, $params)
{
if (!$this->hasErrors()) {
$user = $this->getUser();
if (!$user || !$user->validatePassword($this->password)) {
$this->addError($attribute, 'Incorrect username or password.');
}
}
}
/**
* Logs in a user using the provided username and password.
*
* #return bool whether the user is logged in successfully
*/
public function login()
{
if ($this->validate()) {
return Yii::$app->myuser->login($this->getUser());
}
return false;
}
/**
* Finds user by [[username]]
*
* #return Users|null
*/
public function getUser()
{
if ($this->_user === null) {
$this->_user = Users::findByUsername($this->username);
}
return $this->_user;
}
}
And in backend/config/main.php
'myuser' => [
'class' => 'yii\web\User',
'identityClass' => 'backend\models\Users',
'enableAutoLogin' => true,
'identityCookie' => ['name' => '_identity-backend_user', 'httpOnly' => true],
],
But after successful login, i get following error
The table does not exist: {{%user}}
I found that it is calling common/models/User.php class which by default present in advanced template. But why is it calling this class ? I want to use my own Users.php model. Please somone help me to fix this problem.

The class used for authentication is determined by the user application component, according to the authentication section of the Yii2 guide:
The user application component manages the user authentication status. It requires you to specify an identity class which contains the actual authentication logic. In the following application configuration, the identity class for user is configured to be app\models\User whose implementation is explained in the next subsection:
When you configure a new myuser component, the framework still uses the user component as it is configured by default, update your code, so it overwrites the user component, instead of creating a new myuser one.
'user' => [
// Points to your custom class
'identityClass' => 'backend\models\Users',
'enableAutoLogin' => true,
'identityCookie' => ['name' => '_identity-backend', 'httpOnly' => true],
],

Related

Laravel API resources, get only the latest occurrence from a collection

Hi I am developing an api in laravel for an online course system. In this scheme I have a standard table for users, a table for courses and a pivot table that relates courses and users according to which they sign up for each course.
This last table also carries the events related to the progress of each user in the course, that is, Subscribed, Progress x%, Completed, Approved, so that each user can have multiple entries in the course_users table.
So far everything is clear and everything is fine, the point is that at a certain moment I need to return a json object with the information of the courses and pointed users, this can be clearly achieved using resource collection in the following way:
CourseCollection.php
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\ResourceCollection;
use App\Http\Resources\CargoResource;
class CourseCollection extends ResourceCollection
{
/**
* Transform the resource collection into an array.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
return [
'data' => CourseResource::collection($this->collection),
'links' => [
'self' => 'link-value',
],
];
}
}
CourseResource.php
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class CourseResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
return [
'id' => $this->id,
'title'=> $this->title,
'description'=> $this->description,
'price'=> $this->price,
'users' => CourseUserResource::collection($this->whenLoaded('users'))
];
}
}
CourseUserResource.php
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class CourseUserResource extends JsonResource
{
public function toArray($request)
{
return [
'course_id'=> $this-> course_id,
'user_id'=> $this->user_id,
'event'=> $this->event,
'event_date' => $this->created_at->format('Y-m-d')
];
}
}
The problem to be solved is that with this scheme I obtain a collection of events for each user and course, but what I am needing is only the last event of each user, to know what their status is in relation to the course.
I am analyzing the option to perform the query by sql and then manually build the json object, but I would like to have a "laravel style" solution
Any ideas will be welcome!
Added Models & Controllers for clarification
class Course extends Model
{
protected $fillable = [
'title',
'slug',
'description',
'course_category_id',
'price',
'published'
];
...
public function history()
{
return $this->belongsToMany(CourseUser::class, 'course_id', 'id')->latest();
}
public function scopePublished($query)
{
return $query->where('published', 1);
}
}
class CourseUser extends Model
{
protected $fillable = [
'course_id',
'user_id',
'event'
];
}
class SearchController extends ApiController
{
public function search(Request $request)
{
$results = Course::with('history')
->published
->where('title', 'like', $request->filter['title'])
->where('description', 'like', $request->filter['description'])
->get();
if (! count($results) > 0) {
return $this->sendResponse(
__('No results for your query.'),
[
'code'=>204,
'message'=> __('There are no results for your search criteria.')
],
204
);
}
return new CourseCollection($results);
}
}

Email/password login in yii2

Is that possible to use yii2 login and rbac with email/password instead of username/password? my custom wo_user table does not contains username field, instead, it contains wo_email and wo_password.
1) Add findByEmail() function in your User model
public static function findByEmail($email){
return static::find()->where(['wo_email' => $email, 'status' => self::STATUS_ACTIVE])->one();
}
2) Change rules() and getUser() functions in LoginForm model
public function rules()
{
return [
[['wo_email', 'wo_password'], 'required'],
['rememberMe', 'boolean'],
['wo_password', 'validatePassword'],
];
}
protected function getUser()
{
if ($this->_user === null) {
$this->_user = User::findByEmail($this->wo_email);
}
return $this->_user;
}

yii2- saving datas to multiple rows from one form

I am so new to the yii2 framework, I have a form that I want to save each party score to correspond to each party abbreviation. I don't know how to go about it. here is my code.
the model -
namespace app\models;
use Yii;
/**
* This is the model class for table "announced_pu_results".
*
* #property int $result_id
* #property string $polling_unit_uniqueid
* #property string $party_abbreviation
* #property int $party_score
* #property string $entered_by_user
* #property string $date_entered
* #property string $user_ip_address
*/
class AnnouncedPuResults extends \yii\db\ActiveRecord
{
/**
* #inheritdoc
*/
public static function tableName()
{
return 'announced_pu_results';
}
/**
* #inheritdoc
*/
public function rules()
{
return [
[['polling_unit_uniqueid', 'party_abbreviation', 'party_score', 'entered_by_user', 'date_entered', 'user_ip_address'], 'required'],
[['party_score'], 'integer'],
[['date_entered'], 'safe'],
[['polling_unit_uniqueid', 'entered_by_user', 'user_ip_address'], 'string', 'max' => 50],
// [['party_abbreviation'], 'string', 'max' => 4],
];
}
public function getPollingUnit()
{
return $this->hasOne(PollingUnit::className(), ['id' => 'polling_unit_uniqueid']);
}
/**
* #inheritdoc
*/
public function attributeLabels()
{
return [
'result_id' => 'Result ID',
'polling_unit_uniqueid' => 'Polling Unit Uniqueid',
'party_abbreviation' => 'Party Abbreviation',
'party_score' => 'Party Score',
'entered_by_user' => 'Entered By User',
'date_entered' => 'Date Entered',
'user_ip_address' => 'User Ip Address',
];
}
}
the controller action is create ---
public function actionCreate()
{
$model = new AnnouncedPuResults();
if ($model->load(Yii::$app->request->post()) ){
//&& $model->save()) {
$post = yii::$app->request->post('AnnouncedPuResults');
$party_score = $post['party_score'];
$count = count($party_score);
for($i =0 ; $i < $count; $i++) {
$model->party_abbreviation = $post['party_abbreviation'];
$model->polling_unit_uniqueid = $post['polling_unit_uniqueid'];
$model->entered_by_user = $post['entered_by_user'];
$model->date_entered = $post['date_entered'];
$model->party_score = $post['party_score'];
$model->save();
}
return $this->redirect(['view', 'id' => $model->result_id]);
}else{
return $this->render('create', [
'model' => $model,
]);
}
}
this is the partial view form for the action create----
the screenshot of my form is attached
I want to save the results from my select boxes and the inputs the number of times they are selected to multiple rows in the database at a time
this is how the database looks like
please help me out, I hope my question is understandable.

Yii2 property mapping to tablename

I use Yii2 2.0.9 basic template and I try to set up my class.
I my class I use references of other classes in my property.
/**
*
*#property Contact contact
*/
class User extends ActiveRecord {
public static function tableName() {
return "user";
}
/**
* This is want I need
*/
public function databaseMapping(){
return [
"contact" => "contact_id"
];
}
}
Is there in Yii2 a solution for my problem?
Thanks Marvin Thör
In Grails I can write this:
class User {
Contact contact
Boolean passwordExpired
static mapping = {
contact(column: 'contact_id')
passwordExpired(column: 'password_expired')
}
}
User user = new User();
user.passwordExpired = true
user.contact = new Contact();
and I want the same
You might want to use the method attributeLabels() inside your model class to define label names to show to the end user.
public function attributeLabels() {
return [
'contact_id' => 'Contact',
];
}
However, there are times like when creating a RESTful API using Yii2 that you need to return a json with fields with specific field names. For these ocasions, you can use the fields() method:
public function fields() {
return [
'contact' => 'contact_id',
];
}
This method returns the list of fields that should be returned by default by toArray(). You can check more about it HERE.
Change your labels and db column remain unchanged.
public function attributeLabels()
{
return [
'contact_id' => Yii::t('app', 'Use your name here'),
];
}

Restricting controller action to creator of post in Yii2

Is there an easy way to restrict a controller action to the owner/creator of the post without using full blown RBAC?
Right now I'm doing this for every controller:
public function actionUpdate( $id ) {
$model = $this->findModel( $id );
if ( $model->user_id != Yii::$app->user->identity->id ) {
throw new NotFoundHttpException( 'The requested page does not exist.' );
}
}
But I think there must be a better way to restrict certain controllers to the users who created the $model thats being edited.
1) The recommended way is to use RBAC and rules. It's covered well in official docs in according dedicated section.
Example of rule that checks if author id matches current user id passed via params:
namespace app\rbac;
use yii\rbac\Rule;
/**
* Checks if authorID matches user passed via params
*/
class AuthorRule extends Rule
{
public $name = 'isAuthor';
/**
* #param string|integer $user the user ID.
* #param Item $item the role or permission that this rule is associated with
* #param array $params parameters passed to ManagerInterface::checkAccess().
* #return boolean a value indicating whether the rule permits the role or permission it is associated with.
*/
public function execute($user, $item, $params)
{
return isset($params['post']) ? $params['post']->createdBy == $user : false;
}
}
Then you need to tie it with existing permission (can be done in migration or with extensions):
$auth = Yii::$app->authManager;
// add the rule
$rule = new \app\rbac\AuthorRule;
$auth->add($rule);
// add the "updateOwnPost" permission and associate the rule with it.
$updateOwnPost = $auth->createPermission('updateOwnPost');
$updateOwnPost->description = 'Update own post';
$updateOwnPost->ruleName = $rule->name;
$auth->add($updateOwnPost);
// "updateOwnPost" will be used from "updatePost"
$auth->addChild($updateOwnPost, $updatePost);
// allow "author" to update their own posts
$auth->addChild($author, $updateOwnPost);
Then you can check if you user can update post like this:
use yii\web\ForbiddenHttpException;
use Yii;
public function actionUpdate($id)
{
$model = $this->findModel($id);
if (!Yii::$app->user->can('updatePost', ['post' => $model])) {
throw new ForbiddenHttpException('You are not allowed to edit this post');
}
...
}
Also note that in case you found model first and user has no access to edit it, logically it's better to throw 403 Forbidden exception rather than 404, since it's found, but not allowed for editing.
Don't forget to include rule like that in AccessControl behavior:
[
'allow' => true,
'actions' => ['update'],
'roles' => ['#'],
],
It means that update action of this controller can be only accessed by authorized users excluding guests.
2) If for some reason you don't want to use RBAC, you can use your approach:
use yii\web\ForbiddenHttpException;
public function actionUpdate($id)
{
$model = $this->findModel($id);
if ($model->user_id != Yii::$app->user->id ) {
throw new ForbiddenHttpException('You are not allowed to edit this post.');
}
...
}
To improve this you can abstract from this check by moving this logic to helper method:
namespace app\posts\components;
use Yii;
class PostPermission
{
/**
* #param $model Post
* #return boolean
*/
public static function allowedToUpdate($model)
{
return $model->user_id = Yii:$app->user->id;
}
}
Then call it like that:
use app\posts\components\PostPermission;
use yii\web\ForbiddenHttpException;
if (!PostPermission::allowedToUpdate($model) {
throw new ForbiddenHttpException('You are not allowed to edit this post.');
}
It's just an example, method doesn't have to be static, you can construct instance using $model.
You can just directly create method in Post model, but it's better to not pollute model with such logic.
3) Another alternative that I can advise is to restrict scope initially to current user when finding model:
use yii\web\NotFoundHttpException;
/**
* #param integer $id
* #return Post
* #throws NotFoundHttpException
*/
protected function findModel($id)
{
$model = Post::find(['id'=> $id, 'user_id' => Yii::$app->user->id])->one();
if ($model) {
return $model;
} else {
throw new NotFoundHttpException('This post does not exist.');
}
}
This can be improved for site administrators:
use yii\web\NotFoundHttpException;
/**
* #param integer $id
* #return Post
* #throws NotFoundHttpException
*/
protected function findModel($id)
{
$query = Post::find()->where(['id' => $id]);
if (!Yii::$app->user->is_admin) { // replace with your own check
$query->andWhere(['user_id' => Yii::$app->user->id]);
}
$model = $query->one();
if ($model) {
return $model;
} else {
throw new NotFoundHttpException('This post does not exist.');
}
}
Then you only write:
public function actionUpdate($id)
{
$model = $this->findModel($id);
...
}
That way in both cases (model not found and not allowed for editing by current user), 404 Not Found exception will be raised. From other side, nothing is wrong with that, because technically for this user this model does not exist (since he is not author of it).
We can use
AccessControlFilter
for restricting controller action instead of RBAC. This below code will give access to the actionUpdate if it is only pass the denyCallback.
use yii\filters\AccessControl;
class SiteController extends Controller
{
public function behaviors()
{
return [
'access' => [
'class' => AccessControl::className(),
'only' => ['update','delete'],
'rules' => [
[
'actions' => ['update'],
'allow' => false,
'denyCallback' => function ($rule, $action) { //PHP callable that should be called when this rule will deny the access.
//Write your logic here to deny the action
throw new \Exception('You are not allowed to access this page');
}
],
],
],
];
}
public function actionUpdate()
{
return $this->render('update');
}
}
For your reference https://github.com/yiisoft/yii2/blob/master/docs/guide/security-authorization.md