Yii: How to authenticate using two different tables - authentication

Does anyone know how to do separate login forms and authenticate on two different tables?
I can't go with one table and different roles... client requested to do separate tables...
I've got user login system based on Yii framework login system. But now I have to do separate for admin user (administration module).

The way I solved this issue was to create two identical copies of this plugin from the Yii Framework library:
http://www.yiiframework.com/extension/yii-user/
Then I refactored it and called it "Customer" and changed the config so that it used a different table etc.
In the configuration options for Yii, I also included these options to keep the sessions separate (config/main.php):
'components' => array(
...
'user' => array(
// enable cookie-based authentication
'allowAutoLogin' => true,
'loginUrl' => array('/user/login'),
'class' => 'RWebUser', // added - possibly uses the Rights user manager
),
'customer' => array(
// enable cookie-based authentication
'allowAutoLogin' => true,
'loginUrl' => array('/customer/login'),
'stateKeyPrefix' => 'customer',
),
'customerUser' => array(
'class' => 'CWebUser',
'stateKeyPrefix' => 'customer',
'loginUrl' => array('/customer/login'),
),

You could add a property to your UserIdentity component called, for example, role. Then change the authenticate() method of UserIdentity so that it fetches from the account table corresponding to role. Now you need to make sure that UserIdentity->role is set before invoking UserIdentity->authenticate(). If you are following the yiic webapp template then this would be in SiteController. Two very easy ways (among others):
Have two different login pages, one for normal users and one for admins and each has its own URL. Implement it with two views and two login action methods in SiteController, each sets up UserIdentity->role appropriately before invoking UserIdentity->authenticate(). This approach duplicates code and you'll be able to see how to sort that out once it's working.
Use one login page with a form element (checkbox perhaps) that an admin user selects. This form's action method UserIdentity->role according to form state.

Related

yii-user extension, how to create a new relationship properly?

Hello everyone and thanks for reading , i have little problem , i installed the yii user extension.
I created another table that has a relationship with the user table.
Yii created the relationships with the "user" table automatically but when i try to use the relationship it gives me the below message;
include(Users.php): failed to open stream: No such file or directory
Could this be because User Model is not with the other models but instead in Modules/User.....
How can i make it work ?
eg
array(
'header' => __('Title'),
'name' => 'id_employer_contract',
'value' => '$data->user->username',//user is the name of relationship
),
When trying to access the User.php model from outside of the User module, the file has not been imported yet.
If you look at the init() method of the UserModule.php file, you'll see that the User.php file gets imported via the user.models.* statement.
There are a few different way to import this file from other parts of your system:
Add application.modules.user.models.* to the import section of your main.php. This will make it available everywhere in your application
Call Yii::import('application.modules.user.models.*'); right before the area of code depending on User.php
Call Yii::app()->getModule('user'); right before the area of code depending on User.php. This will call the init() method in the User module.
Look at "include(Users.php): failed"
You can change the relation mapping (auto generated by Gii) from this:
'user' => array(self::BELONGS_TO, 'Users', 'user_id')
To this:
'user' => array(self::BELONGS_TO, 'User', 'user_id')
That should do the trick.
In another version of Yii the according model might be YumUser instead of User when creating your relation, did the job for me:
'user' => array(self::BELONGS_TO, 'YumUser', 'user_id')
The problem that you have is that the Table name for users is called "users" so when yii is resolving this for model generation it would resolve the user model to be "Users" however the yii-user module extension has the model as "UserModel" in a file named User.php
This means that in essesnce the file named Users.php does not exist.
To resolve this just change the relationship in your new model to'user' => array(self::BELONGS_TO, 'User', 'User'), and not 'user' => array(self::BELONGS_TO, 'Users', 'User'),

Yii - CGridView 1-to-N Display issues

I've started Yii a month ago and am finding it very intuitive, yet somehow confusing regarding widgets.
In the app i'm developing, although I sometimes use Active Record, I cannot use it's relations as I am using MyIsam (and this cannot be changed) and it has no support for foreign keys.
My issue is I have a CGridView and want to add custom data to it, but am having issues.
It is a 1-to-many relationship with the FK in the right model, but as I said, I cannot use AR's magic.
I have this, an Applications Model and a Profile model. The Profile model has an application FK.
I got a function so when I'm rendering the CGrid I can fetch the name of each Application instead of its *id_app*.
public function appName($id){
$app= Yii::app()->db->createCommand()
->select('name')
->from('tbl_aplications a')
->where('a.id=:id_app', array(':id_app'=>$id))
->queryRow();
return $app;
}
In the auto-generated templates, in the Profile Admin.php view, I got:
$this->widget('zii.widgets.grid.CGridView', array(
'id'=>'profile-application-grid',
'dataProvider'=>$model->search(), //maybe the issue is with this criteria? It is currently 'as-is' after the template generation
'filter'=>$model,
'columns'=>array(
'id',
'name',
array(
'name'=>'id_app',
'header'=>'Aplication',
And here is my issue, I've tried (and all sorts of variations):
'value' => 'ProfileApplication::model()->appName($data->id_app)',
'value' => 'ProfileApplication::model()->appName(id_app)',
'value' => 'ProfileApplication::model()->appName("id_app")',
and all I get is null as a result because it is passing the actual string instead of each row's value. If I pass a direct ID to the function's query, it returns correct value, like ->where('a.id=:id_app', array(':id_app'=>3))
Is it the search criteria that needs to be altered?
I've found similar questions but all of them use AR such as Profile->application or something along those lines and I as I said, I cannot use it due to MyIsam restrictions.
Any tips are appreciated to this newcomer, or links to a solution regarding a similar issue .
To use the value attribute such as that you need, as PrplHaz4 said, a data provider. Then, the $data variable is what has the magic, and it must be used in a string because it is eval()'ed behind the scenes. Here is an example of what you are trying to do:
<?php $this->widget('zii.widgets.grid.CGridView', array(
'id'=>'item-grid',
'dataProvider'=>$model->search(),
'columns'=>array(
array(
'name' => 'id',
'header' => 'Item ID',
),
array(
'name' => 'category_search',
'header' => 'Category',
'value' => '$data->category->name',
),
),
)); ?>
That grabs the name attribute of the related item category model. In the relations() function of the item model:
return array(
'category'=>array(self::BELONGS_TO, 'ItemCategory', 'category_id'),
);
And in the relations() function of the item category model:
return array(
'items'=>array(self::HAS_MANY, 'Item', 'category_id'),
);
You should still be able to use ActiveRecord relations with MyISAM, I beleive the only difference is that with MyISAM, if you use a model generator (gii or cmd line), it will not automatically create the relations. Instead, you will have to specify the relation yourself in the Profile model. This is effectively creating a "soft" fk for use with AR.
public function relations()
{
return array(
'applications'=>array(self::HAS_MANY, 'Applications', 'id_app'),
);
}
That will not entirely solve your problem though, because you will need to use a dataprovider that brings the application models along with the profile models. Something like this:
$this->widget('zii.widgets.grid.CGridView', array(
'id'=>'profile-application-grid',
'dataProvider'=>Profile::model()->with('Applications'),
'filter'=>$model,
'columns'=>array(
'id',
'name',
array(
'name'=>'applications.name',
'header'=>'Application',
),

Multiple, custom URLs for Rails 3 resources without ID

In my app I've got various resources that I want to access via multiple URLs. For example, an invoice can be accessed via:
/invoices/:id
By the issuer of the invoice, and also via:
/pay/:payment_key
By the payer.
The latter URL does not require authentication (hence the secrefied payment_key).
The issue is how to get automatic URL helper methods for the custom URL. Usually you could use to_param to customize the resource URL (as described here), but that's not adequate in this case because I still want to retain the default resource URL.
If I create the helper methods by declaring a named route:
/pay/:payment_key, :as => :invoice_payment
Then I would expect invoice_payment_url(invoice) to include invoice.payment_key but it doesn't. Rails uses the invoice ID instead (similar to the behaviour reported here)
This seems like it's broken.
So I've been defining the url helpers for this resource manually.
def invoice_payment_path (invoice)
url_for :controller => "invoices",
:only_path => true,
:action => "pay",
:payment_key => invoice.payment_key
end
def invoice_payment_url (invoice)
url_for :controller => "invoices",
:only_path => false,
:action => "pay",
:payment_key => invoice.payment_key
end
Wondering if there is a DRYer way to do this?
RESTful is about resources. So are you sure the "payment" and "invoice" resources the same thing in your system? To me, it is more like a system design issue than a routing issue.
Another thought is using nested resources. You can view either "/payments/:id/invoices" or "/invoices/:id/payments", both make sense to me.
Yan

CanCan and Devise, restricting login based on Role

I've just finished setting up Devise on a single application, using a single User model with two scopes, so I can have an /admin/login as well as a regular /users/login path. This works pretty well, my config/routes.rb file looks like this:
devise_for :users,
:path_names => { :sign_in => 'login', :sign_out => 'logout' }
devise_for :admins,
:class_name => 'User',
:skip => [:passwords, :registrations, :confirmations, :sessions],
:controllers => { :sessions => 'admin/sessions' } do
get 'admin/login' => 'admin/sessions#new', :as => :new_admin_session
post 'admin/login' => 'admin/sessions#create', :as => :admin_session
delete 'admin/logout' => 'admin/sessions#destroy', :as => :destroy_admin_session
end
This works pretty fine and dandy, I can log in to each side of the application without affecting the other. That is, the session names are separate and logging into one does not log you into the other.
Now, I've set up CanCan with my Roles model, and an Ability model, and have these defined in my database and working.
Question is, I want to be able to fill out the form on admin/login, and receive an error message because my Role doesn't allow me to log into that area. How can I accomplish this?
I am a bit confused by your question. If you are filling out the form on admin/login, then presumably you have not logged in yet?
If that's the case, then there is no current_user or current_admin and therefore nothing is passed to CanCan yet.
I have a similar set-up in my app and maintain different accounts on each side of the app. My user account is different than my admin account. If I forget and try to login to the admin side using my regular user account, I simply receive an unknown user/password error from Devise.

submit from HAML form clears session data (Rails3)?

I have the following model:
Project
- name:text
- description:text
And this is snippet of the form I've written in HAML for new project:
%p Create a new project:
%form{:method => "post", :action=>"/projects/"}
%label{:for => "project-name[name]"} Name:
%input{:type=>'text', :size=>40, :name=>'projectname', :id=>'project-name'}
%br/
%label{:for => "project-description[description]"} Description:
%textarea{:rows=>'10',:cols=>'10',:name=>'projectdescription',:id=>'project-description'}
%br/
%input{:type=>'submit', :value=>'Create'}
when the user clicks submit, the session data seems to be cleared.
Here is why I suspect this:
I am using omniauth, when a user signs in, I set session[:user_id] = user.id
I have a redirect in the project controller for ensuring the user is always signed in for all actions
the index and new actions are handled properly.
instead of the create action being handled, the user is bounced to the sign in page (as per the before_filter)
I replicated the same functionality with erb files and there no issue. When I drop in the new.haml file the error shows up again.
Any ideas?
I believe that what's happening here, is that you have `protect_from_forgery' in your ApplicationController, but the csrf token is not sent to application. Check if you have a relevant rails.js included if it's AJAX call. And if you were using form_for, then the helper would automatically insert the hidden field with the csrf token.