Restrict devise users to their own view/association - ruby-on-rails

I am using devise for authentication and have an association between users (has_many :products) and products model (belongs_to :user).
My routes file is
resources :users do
resources :products
end
Now what happens is, user with id 3 at /users/3/products can also see whats at /users/4/products. I want to restrict that. I dont want /users/3/products to able to see whats at /users/4/products and so on (not specific to these two users but for all). How do I do it ? Should I have a Users Controller? I dont have it right now. If i have the controller, how do I do it? I was thinking maybe redirect it?
thanks

You could add a before_filter in your products controller:
class ProductsController < ApplicationController
before_filter :user_is_current_user
...
private
def user_is_current_user
unless current_user.id == params[:user_id]
flash[:notice] = "You may only view your own products."
redirect_to root_path
end
end
end
Also, in the products controller you could retrieve only products belonging to the current_user:
def index
#products = current_user.products # will fetch all products with a user_id matching the current user's
end
If you used the above you wouldn't really need a user's ID in the URL, you could use a path like /users/products or just /products.

Related

Using devise for user registration/login redirect user to a different form page based on their response

I am using devise for user registration/login, after the user has successfully signed up, I want to show a page/a dialog box and redirect to another page based on user response. How can I do that?
User Model (By devise)
username
password
Student Model
name
student_id
Teacher Model
name
grade
First_page:
signup signin links
Signup link will show the devise views/devise/registrations/new.html.erb page.
After successful signup, it takes the user to root page. I have defined the root page in routes.rb:
` Rails.application.routes.draw do
devise_for :users
resources :students, :teachers
# For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
root to: "students#index"
end `
At this point, the application doesn't have any idea who the user is.
So, I want to get the identity information(student/teacher) from the user.
How will I get this information?
Student/Teacher controller:
`class StudentsController < ApplicationController
before_action :authenticate_user!, only: [:new, :create]
def index
#students = Student.all
end
def new
#student = Student.new
end
def create
current_user.create_student(student_params)
redirect_to root_path
end
private
def student_params
params.require(:student).permit(:name, :skypid)
end
end`
After the user has successfully signed in, I want to ask if the user is a student or teacher. Based on what they select, redirect them to a student form page or teacher form page.
How can I do that in rails?
Thank you
You can write a custom after_sign_in_path_for function in your ApplicationController assuming you're using all the default Devise controllers otherwise. Any named path helper or other route that it returns will be where the user is redirected, so you could do something simple like always redirect to a selection page that presents the options and handles the choice on a subsequent action:
def after_sign_in_path_for(resource)
user_type_selection_path # whatever route in your app manages the selection
end
Alternately, you could invoke a custom method on the user model in that function to make a choice right there:
def after_sign_in_path_for(resource)
resource.student? ? student_path : teacher_path
end
You could hybridize these of course as well to do the latter when the selection has already been made and redirect otherwise, with something similar to the following:
def after_sign_in_path_for(resource)
if resource.user_type_chosen?
resource.student? ? student_path : teacher_path
else
user_type_selection_path
end
Bear in mind that none of those functions or paths are real, since I can't be more specific on the information you've provided, but hopefully this gets you headed in the right direction. The after_sign_in_path_for hook is your primary tool here, unless you get into the world of overriding the default devise controllers and interrupting the usual workflow there to accommodate this step, which doesn't seem strictly necessary by your description.

Assign Projects to multiple Users

I'm just getting started in rails and I'm stuck with a little problem.
Imagine you have to Types of Users: Client and Admin. Both are stored in the same User Model with an boolean (admin=true/false). That's how I differentiate between those. So now there are Projects. These are just posts with multiple attributes like Name, Description, Category...
I want my admins to see all the Projects and he should have the ability to edit those project but at the same time I want to show the Client User only the Projects he is assigned to and he shouldn't be able to edit those.
So concluding: Admin -> See all posts and be able to edit and assign Clients to them
Client -> See Projects he is assigned to but not able to edit those. He should only see the ones he is assigned to.
How do I do it?
Make a Project belong to a User. That entails:
generate a migration to add the DB column (run this command):
$ rails g migration AddUserToProjects user:references
add the association to your Project model (app/models/project.rb):
belongs_to :user
add the association to your User model (app/models/user.rb):
has_many :projects
In your Projects controller, you want to have actions for the following:
a list of projects for a user
an admin's list of all projects
Those could look like this (app/controllers/projects_controller.rb):
class ProjectsController < ApplicationController
before_action :authenticate_user!
before_action :verify_admin_status, :only => [:admin_list]
def user_list
#projects = current_user.projects
end
def admin_list
#projects = Project.all
end
private
def verify_admin_status
unless current_user.admin
raise ActionController::RoutingError.new('Not Found') # or whatever other error you want, i.e. 403
end
end
end
Add the routes to your config/routes.rb file:
get 'projects/mine', :to => 'projects#user_list'
get 'projects/admin', :to => 'projects#admin_list'
Take a similar approach for defining the edit actions: check that the user who's trying to access it is an admin by using the verify_admin_status callback, then select (or update) the Project record.
check in your views and controller actions
if current_user.admin

How to secure user show page alongside user admin functions when using devise

I'm using devise and have let admins manage users with a Manage::UsersController.
This is locked down using cancan:
# models/ability.rb
def initialize(user)
if user admin?
can :manage, User
end
end
Normal users can have nothing to do with User other than through devise, so everything looks secure.
Now I want to give users a 'show' page for their own account information (rather than customising the devise 'edit' page). The advice (1,2,3) seems to be to add a users_controller with a show method.
I tried to give non-admins the ability to read only their own information with:
if user admin?
can :manage, User
else
can :read, User, :id => user.id # edited based on #Edwards's answer
end
However this doesn't seem to restrict access to Manage::UsersController#index, so it means that everybody can see a list of all users.
What's the simplest way to achieve this? I can see two options, (but I'm not sure either is right):
1) Prevent user access to Manage::UsersController#index
def index
#users = User.all
authorize! :manage, User # feels hackish because this is 'read' action
end
2) Over-write devise controller
Per this answer over-writing a devise controller could be a good approach, but the question is which controller (the registrations controller?) and how. One of my concerns with going this route is that the information I want to display relates to the User object but not devise specifically (i.e. user plan information etc.). I'm also worried about getting bogged down when trying to test this.
What do you recommend?
In your ability.rb you have
can :read, User, :user_id => user.id
The User model won't have a user_id - you want the logged in user to be able to see their own account - that is it has the same id as the current_user. Also, :read is an alias for [:index, :show], you only want :show. So,
can :show, User, :id => user.id
should do the job.
I would keep your registration and authentication as Devise controllers; then, create your own User controller that is not a devise controller.
In your own controller, let's call it a ProfilesController, you could only show the specific actions for the one profile (the current_user)
routes
resource :profile
profiles controller
class ProfilesController
respond_to :html
def show
#user = current_user
end
def edit
#user = current_user
end
def update
#user = current_user
#user.update_attributes(params[:user])
respond_with #user
end
end
Since it's always only editing YOU, it restricts the ability to edit or see others.

Users in a company can only view projects within their company in Rails..?

I have three models: users, organization, and projects.
Organization has_many users (with different roles) and users belong_to organization.
Users has_many projects, and projects belong_to both users and organizations (by putting #project.organization_id = current_user.organization_id in the def create method of the controller).
I put this bit in my projects_controller.rb file to make sure that users who are logged in can only view the projects that are associated with their organization.
def index
#projects = Project.where(organization_id:current_user.organization_id)
end
However, all this does is hide the other projects from the current_user. If he types in http://localhost:3000/projects/3 (a project that belongs to another user from another organization) he's still able to access it.
What should I put in the def show part of projects_controller to stop this from happening? I've hacked around a couple of things but I can't get it right without any errors. Also I'd like to not use CanCan if possible.
I tried putting a
before_filter :require_project_belong_to_organization, :only => [:show]
def require_project_belong_to_organization
#project = current_user.projects.find(params[:id])
end
but this is only returning a result if it was the user that created it. I need other users to be able to view it too, as long as they're in the same organization.
Try cancan.
Use a before_filter in you controller and check if the project's user is the same as the current_user
I would first make a function authorized? in the project model. Make sure that current_user is available to the model.
authorized?
self.organization_id == current_user.organization_id
end
Then in the controller:
def show
#project = Project.find(params[:id])
if #project
unless #project.authorized?
flash[:danger] = 'not authorized'
redirect_to....
end
else
flash.now[:danger] = 'Project was not found'
end
end

Rails scope find with current user

I'm using Rails 3 with Devise for user auth. Let's say I have a User model, with Devise enabled, and a Product model, and that a User has_many Products.
In my Products controller I'd like my find method to be scoped by current_user, ie.
#product = current_user.products.find(params[:id])
unless the user is an admin user, i.e. current_user.admin?
Right now, I'm running that code in almost every method, which seems messy:
if current_user.admin?
#product = Product.find(params[:id])
else
#product = current_user.products.find(params[:id])
end
Is there a more elegant/standard way of doing this?
I like to do this as follows:
class Product
scope :by_user, lambda { |user|
where(:owner_id => user.id) unless user.admin?
}
end
this allows you to write the following in your controller:
Product.by_user(current_user).find(params[:id])
If you're running this code in a lot of your controllers, you should probably make it a before filter, and define a method to do that in your ApplicationController:
before_filter :set_product, :except => [:destroy, :index]
def set_product
#product = current_user.admin? ? Product.find(params[:id]) : current_user.products.find(params[:id])
end
I don't know what you use to determine if a user is an admin or not (roles), but if you look into CanCan, it has an accessible_by scope that accepts an ability (an object that controls what users can and can't do) and returns records that user has access to based on permissions you write yourself. That is probably really what you want, but ripping out your permissions system and replacing it may or may not be feasible for you.
You could add a class method on Product with the user sent as an argument.
class Product < ActiveRecord::Base
...
def self.for_user(user)
user.admin? ? where({}) : where(:owner_id => user.id)
end
Then you can call it like this:
Product.for_user(current_user).find(params[:id])
PS: There's probably a better way to do the where({}).

Resources