I'm trying to allow users to show the edit button only if the profile belongs to them. Currently, they're only allowed to edit the profile if it belongs to them but I can't seem to hide the button. I have this so far
<% if request.original_url == request.base_url + "current_user.id" %>
<%= link_to "Edit Profile", edit_user_path(current_user), class: "btn btn-primary btn-xs" %>
<% end %>
This is what I'm trying to compare:
request.original_url => localhost:3000/users/random_user
request.base_url + "users/" + current_user.id => localhost:3000/users/current_user
Thanks in advance.
Authorization
To give you some perspective, you'll be looking for something called authorization.
This is different from authentication because it deals with permissions, rather than identifying your identity. I'll get into how this works in a minute.
To solve your problem, here's what you need to do:
<%= link_to "Edit Profile", edit_user_path(current_user), class: "btn btn-primary btn-xs", if user_signed_in? && current_user == #user %>
I'm guessing you're showing this on a user#show action, which can be invoked using the following code:
#app/controllers/users_controller.rb
class UsersController < ApplicationController
def show
#user = User.find params[:id]
end
end
This means that if you have the following routes:
#config/routes.rb
resources :users
you'll have access to #user and current_user. It's important to note that current_user != #user. Although weezing's answer is succinct, it does not validate whether the user is the one which owns the page authorized; just that the user is authenticated
Thus, you have several specifications:
You need to know if the user is actually logged in
You need to make sure your logged-in user has the authorization to edit the profile (IE is it theirs)
I would highly recommend looking into the use of gems such as CanCanCan or Pundit. I'll show you CanCanCan:
#app/models/ability.rb
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user (not logged in)
can :edit, Article, id: user.id
end
end
#app/views/users/show.html.erb
<%= link_to "Edit", edit_user_path(#user) if can? :edit, #user %>
There is a great resource here.
This should work (little bit simpler):
<% if current_user %>
<%= link_to "Edit Profile", edit_user_path(current_user), class: "btn btn-primary btn-xs" %>
<% end %>
Related
I am using devise gem. My project's purpose user can upload files & delete thier own file but not other user's file. So, I write "user?" method for ensure that correct user can only show the delete button. I also ensure the 'correct_user' method for delete file. but now I'm faceing this problem "NoMethodError at /upload_files.."
"undefined method `user?' "
Here is my upload_files_controller.rb file:
class UploadFilesController < ApplicationController
before_action :logged_in
before_action :correct_user, only: :destroy
def index
#upload_files = UploadFile.all
end
def new
#upload_file = UploadFile.new
end
def create
#upload_file = current_user.upload_files.build(upload_params)
if #upload_file.save
redirect_to upload_files_path, notice: "The file #{#upload_file.name} has been uploaded."
else
render "new"
end
end
def destroy
upload_file = UploadFile.find(params[:id]).destroy
redirect_to upload_files_path, notice: "The file #{upload_file.name} has been deleted."
end
def user?(check_user)
check_user == current_user.id
end
private
def upload_params
params.require(:upload_file).permit(:name, :upload_file)
end
def logged_in
if admin_signed_in?
return true
else
authenticate_user!
end
end
def correct_user
#upload_file = current_user.upload_files.find_by(id: params[:id])
redirect_to root_url if #upload_file.nil?
end
end
Here is my upload_files/index.html.erb file:
<% #upload_files.each do |file| %>
<tr>
<td><%= file.name %></td>
<td><%= link_to "Download File", file.file_name_url %></td>
<%if admin_signed_in? %>
<td><%= button_to "Delete", file, method: :delete, class: "btn btn-danger", confirm: "Are you sure that you wish to delete #{file.name}?" %></td>
<% else user?(file.user_id) %>
<td><%= button_to "Delete", file, method: :delete, class: "btn btn-danger", confirm: "Are you sure that you wish to delete #{file.name}?" %></td>
<% end %>
</tr>
<% end %>
what did I wrong? please show me a way.
Thanks,
Mezbah
You should place your user? method in helper.
About your view code, why don't you use boolean alternative?
<% if admin_signed_in? || user?(file.user_id) %>
<td><%= button_to "Delete", file, method: :delete, class: "btn btn-danger", confirm: "Are you sure that you wish to delete #{file.name}?" %></td>
<% end %>
Marek is right, however, you may wish to use an authorization gem such as CanCanCan for this
There's a great Railscast about authorization here:
To give you a brief synopsis, authorization is the authority that a user has to CRUD an object. Authentication (Devise) is for giving the user "permission" to use various features in the application; authorization is allowing the user to edit / change data depending on their level of access
Your choice of trying to add a button so that users will be able to remove their own object. This is perfect CanCanCan territory:
--
CanCanCan
This gem was originally called "CanCan", but as Ryan Bates has gone on leave, some of the Rails community took it upon themselves to make their own gem, calling it CanCanCan
The way it works is relatively simple:
Have an "ability" model to define user abilities
Call the can? method to determine if a user can partake in a particular action
This means that you'll be able to make an extensible piece of functionality which will grant your users access to specific objects as required. Here's how:
> rails g cancan:ability
This will create the ability model to define all the methods:
#app/models/ability.rb
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user (not logged in)
if user.admin?
can :manage, :all
else
can :read, :all
end
end
end
This will give you the ability to then call the can? method on your various objects:
<td>
<% if admin_signed_in? || (can? :destroy, file) %>
<%= button_to "Delete", file, method: :delete, class: "btn btn-danger", confirm: "Are you sure that you wish to delete #{file.name}?" %>
<% end %>
</td>
I would add the following method to your application_controller.rb. That makes that method available in all controllers and views within your application.
# application_controller.rb
def current_user?(user)
current_user == user
end
helper_method :current_user?
Use that method like this in your view:
# in view
<% if admin_signed_in? || current_user?(file.user) %>
<td>
<%= button_to('Delete', file,
method: :delete,
class: 'btn btn-danger',
confirm: "Are you sure that you wish to delete #{file.name}?") %>
</td>
<% end %>
I am trying to setup so that users will get a "not authorized" message if they click edit for a profile that is not theirs. This message should of course not appear for admins since admins can edit all profiles. I previously done this on Permission.rb, however I got rid of the file to go with a more basic user roles/authorization.
I don't see how I can implement what I had previously on Permission.rb for my current files. I have tried some solutions but they don't add up. If someone could point me in the right direction that will be great. Also I am doing this all from scratch, user authentication/authorization.
index.html.erb:
<% #users.each do |user| %>
<li>
<% if current_user.admin? || current_user == #user %>
<% end %>
<%= link_to "Edit #{user} profile", user %>
| <%= link_to "delete", user, method: :delete,
data: { confirm: "You sure?"} %>
</li>
<% end %>
Why are you giving the chance for users to edit other peoples profiles?
First, you should have a if statement in your view where you show the link for the edit page. I guess this is showing up on a profile of every user, so i suppose the code in your controller is something like this:
def show
#user = User.find(params[:id])
end
Then in your view you should have something like this:
<% if current_user.admin? || current_user == #user %>
<%= link_to 'Edit Profile' , edit_user_path(#user) %>
<% end %>
There is also a case if someone tries to 'force' their way in, just like trying to type a url www.yourapplication.com/users/6/edit you could write a before_filter method in your controller:
before_filter :check_privileges, only => [:edit, :update]
and then write a method in called check_privileges
def check_privileges
unless current_user.admin? || current_user.id == params[:id]
flash[:warning] = 'not authorized!'
redirect_to root_path
end
end
EDIT: After the questioner edited his code, i'm showing the mistake:
You are putting the end too soon:
<% #users.each do |user| %>
<li>
<%= link_to user.name, user %>
<% if current_user.admin? || current_user == #user %>
<%= link_to "Edit #{user} profile", user %>
| <%= link_to "delete", user, method: :delete,
data: { confirm: "You sure?"} %>
<% end %>
</li>
<% end %>
From what I understand it would create a security flaw if I were to let my admin boolean in my users model be listed in attr_accessible. However I want a link on my show page that lets existing admin users grant admin privileges to other users. I was wondering how to go about doing this? My code in my show view for a user at the moment includes:
<% if current_user.admin? && #user.admin == false %>
<%= link_to "Make Administrator", '#',
data: { confirm: "Make this user an admin?" }, class: "btn btn-large btn-primary" %>
<% elsif current_user.admin? && #user.admin%>
<%= link_to "Remove Administrator", '#', class: "btn btn-large btn-danger" %>
<% end %>
I'm not entirely sure what to put instead of the '#'? #user.toggle!(:admin) doesn't seem to work so any pointers would be appreciated. Thank you in advance!
First, since this is an action this should be a button, not an link.
<%= button_to user_path(#user), :method => :put ... %>
controller code
def update
if params[:admin]
user.update_attribute(:admin, true)
redirect_to ...
end
...
end
or if you prefer to keep your logic in the model
def update
...
if params[:admin]
#user.make_admin
end
...
end
model code
def make_admin
self.update_column(:admin, true)
end
How can I set an ability to allow a user to only edit their own profile? The edit link is placed in their own show page like so:
<% if can? :update, User %>
<div class="button">
<%= link_to 'edit my profile', edit_user_path(#user) %>
</div>
<% end %>
The ability currently looks like this:
if user.role == "author"
can :create, Review
can :update, Review do |review|
review.try(:user) == user
end
can :update, User, :id => user.id
end
I have load_and_authorize_resource in the user controller too.
But this doesn't work, the user (with role of author) can still see and use the edit button on all users show pages.
What am I doing wrong here?
Thanks very much for any help its much appreciated!
Should be:
<% if can? :update, #user %>
You need to pass the actual object instance instead of the class.
When a user is looking at a user profile page, if the current user == the user profile I want to show an edit button...
So I have the following in the view:
<% if can? :update, #user %>
<%= link_to 'Edit', edit_user_registration_path %>
<% end %>
And then in CanCan I have the following:
def initialize(user)
.
.
.
can :update, User do |user2|
user2.try(:id) == user.id
end
But this is always equaling yes. not sure why? ideas
I change the CanCan user var to current_user and that did it. I think the two users were confusing each other.