I have an app where the User is allowed to access their :name, :lastname, :email attributes only once - during the initial account setup. After this (for security reasons) they are not allowed to change either of these attributes; only an Admin can.
def user_params
if current_user && current_user.admin?
params.require(:user).permit(:name,
:surname,
:admin)
else
params.require(:user).permit()
end
end
Then during user.create I allow the User to complete the registration form and access these protected attributes, but during user.update, only an Admin can change that information.
Since user_params is being called for each method (new, create, edit, update, show, destroy), it won't allow the user to write these attributes and set up an account if I restrict access to these attributes to Admins only after account creation/verification.
Any ideas on how to solve this problem? Or am I just missing something about strong_parameters?
user_params is just a helper method so you don't have to repeat the same code in all of the actions. If the code in create is different from the code in update, just create another helper method:
def user_create_params
if current_user && current_user.admin?
params.require(:user).permit(:name, :surname, :admin)
else
params.require(:user).permit(:name, :surname)
end
end
def user_update_params
if current_user && current_user.admin?
params.require(:user).permit(:name, :surname, :admin)
else
params.require(:user).permit()
end
end
Related
We have a multi-tentant rails app that very closely resembels the "from scratch" approach that Ryan Bates explains/teaches.
Authentication from scratch.
Multi-tenancy from scratch.
We have an account model that has a sub-domain attribute and uses scoping to separate the data.
#ApplicationController
around_filter :scope_current_account
private
def current_account
if request.subdomain.present? && request.subdomain != 'www' && request.subdomain != 'ndt-staging'
#account ||= Account.find_by_subdomain!(request.subdomain)
end
end
helper_method :current_account
def scope_current_account
if request.subdomain.present? && request.subdomain != 'www' && request.subdomain != 'ndt-staging'
Account.current_id = current_account.id
end
yield
ensure
Account.current_id = nil
end
The models:
#Account.rb
has_many :users, :inverse_of => :account, :dependent => :destroy
#User.rb
belongs_to :account, :inverse_of => :users
default_scope { where(account_id: Account.current_id) }
My questino is: What is the best way to manage users application wide.. meaning User.scoped and User.unscoped?
The first thing that comes to mind is to add an admin_password attribute to the User model. Set the password with an environment variable, and at User/Account creation add the admin password value into a hidden field.
(the account new action also builds a user and creates a user account)
#AccountsController
def new
#account = Account.new
#account.users.build(params[:user])
end
The biggest problem I see with this approach is the authentication. I would need to re-write things so that if the admin_password is correct, the normal password attribute will not be checked. If the admin_password is incorrect the password attribute will be used.
As a side note, I've looked at plugins like acts_as_tenant and devise, but would rather build these parts myself.
I could be going down the wrong path here, that is why I am asking for recommended solutions/ideas. Thank you in advance for those :)
#SessionsController
def create
user = User.find_by_email(params[:email].downcase)
if user && user.authenticate(params[:password])
sign_in user
redirect_to welcome_path
else
flash.now[:error] = 'Invalid email/password combination' # Not quite right!
render 'new'
end
end
I solved this using the cancan gem, and creating an admin user in the account create action.
I have a Rails 3.2.3 application and I am using MiniTest and Capybara to run my integration tests. I rolled my own authentication and created a current_user helper_method in my application_controller.rb.
My application layout file only displays certain links, like logout, etc., when a user is logged in.
But the current_user method does not work during tests. It looks like this:
class ApplicationController < ActionController::Base
protect_from_forgery
private
def authenticate
if current_user.blank?
redirect_to root_url, :alert => "You must first log in."
end
end
def authenticate_admin
unless current_user and current_user.admin?
redirect_to root_url, :alert => "You must be logged in as an administrator to access this feature."
end
end
def current_user
begin
#current_user ||= User.find(session[:user_id]) if session[:user_id]
rescue
nil
end
end
helper_method :current_user
end
So in my application.html.erb file there is:
<% unless current_user.blank? %>
<li><%= link_to logout_path %></li>
So this works when I test it through the browser, but not in my test. "current_user" ends up being nil.
I am new to BDD, but is there something that prevents sessions from being created during tests? What am I missing?
NOTE: helper methods defined in controllers are not included.
from here.
The solution is to organize them in a dedicated helper class.
I am new to Ruby on Rails. I am learning it through Michel Hartl's reference doc available on the internet. However i ran into a problem during sign-in of users, I suspect the before_save is not calling the create_remember_token method. I am trying to debug this issue from a very long time. My code is as follows:
user.rb file:
# == Schema Information
#
# Table name: users
#
# id :integer(4) not null, primary key
# name :string(255)
# email :string(255)
# created_at :datetime not null
# updated_at :datetime not null
# password_digest :string(255)
# username :string(255)
# remember_token :string(255)
#
class User < ActiveRecord::Base
def to_param
username
end
attr_accessible :name, :email, :password, :password_confirmation, :username
has_secure_password
before_save { |user| user.email = email.downcase }
before_save :create_remember_token
private
def create_remember_token
self.remember_token = SecureRandom.urlsafe_base64
end
end
session_helper.rb
module SessionsHelper
def sign_in(user)
cookies.permanent[:remember_token] = user.remember_token
current_user = user
end
def current_user=(user)
#current_user = user
end
def current_user
#current_user ||= user_from_remember_token
end
def signed_in?
!current_user.nil?
end
private
def user_from_remember_token
remember_token = cookies[:remember_token]
User.find_by_remember_token(remember_token) unless remember_token.nil?
end
end
sessions_controller.rb:
class SessionsController < ApplicationController
def new
end
def create
user=User.find_by_username(params[:session][:username])
if user && user.authenticate(params[:session][:password])
sign_in user
redirect_to "/#/#{params[:session][:username]}"
else
flash.now[:error] = "Login failed! Please try again!"
render 'new'
end
end
def destroy
end
end
Please help me and let me know as to where the problem exists.
Cheers :)
What exactly is the problem that you are having during sign in?
The reason I ask is that the create_remember_token method is not supposed to be called during the sign-in process. It gets called when the user is saved (sign-up).
The sign-in process just fetches the token from the users table and copies it into the permanent cookie. The sign-in process looks roughly like this:
sign_in user --> session_helper.rb --> def sign_in(user) --> cookies.permanent[:remember_token] = user.remember_token --> back to session_controller.rb --> redirect_to ...
The before_save callbacks should only be called when the user is created (ie. when they sign up, not when they sign in). You don't show the whole sign-up/user creation flow in your code sample, but i assume it exists.
You can test whether create_remember_token is called (when the user is created) by running this command from the Rails console:
User._save_callbacks.select { |cb| cb.kind.eql?(:before) }.collect(&:filter).include?(:create_remember_token)
That should return true if and only if the create_remember_token function is being run on save. More details in the 'Debugging callbacks' section of the Callbacks API Documentation.
If the callback is called during the signup flow, the remember_token should be saved to the DB then. You can verify that by inspecting the user with the Rails console (User.last, after creating your test user).
What does your sign-up/user creation flow look like? What repro steps produce the problem you're describing?
You might have not used csrf meta tag in your layout. Try removing ' protect from forgery ' in application_controller.rb in controllers folder. Then try running the app again, if it works then you didnt add tags to your layout.
I had posted this question a year ago.
Now I am using the Devise gem for authentication. Everything is working as expected.
Thanks
In my app I want to implement roles and permissions in them in such way, that users admins can create new roles in their browsers. So I need permissions table and roles table, where every role is a combination of permissions. For now I used CanCan gem, but this behavior is not default for this gem and finally I got very complicated structure of roles and checking permissions. Can anybody tell me what gem provides this behavior or maybe I should make it without any gems?
See Abilities in Database and Ability for Other Users from the CanCan wiki:
class Ability
include CanCan::Ability
def initialize(user)
can do |action, subject_class, subject|
user.permissions.find_all_by_action(aliases_for_action(action)).any? do |permission|
permission.subject_class == subject_class.to_s &&
(subject.nil? || permission.subject_id.nil? || permission.subject_id == subject.id)
end
end
end
end
EDIT
Some load optimizations:
class Ability
include CanCan::Ability
def initialize(user, context = nil)
if context.nil?
can do |action, subject_class, subject|
user.permissions.find_all_by_action(aliases_for_action(action)).any? do |permission|
permission.subject_class == subject_class.to_s &&
(subject.nil? || permission.subject_id.nil? || permission.subject_id == subject.id)
end
elsif context == :post
can :manage, Post, :id => y
elsif context == :users
can :manage, User, :id => x
end
...
And in controllers:
class UsersController
protected
def current_ability
Ability.new(current_user, :users)
class PostsController
protected
def current_ability
Ability.new(current_user, :posts)
I'm trying to layout a Rails app using Devise for authentication. I'd like to have an initial landing page where people could enter then email address. I'd like to create an account as soon as I get the email address and then let the user finalize the authentication process later. Is there some documentation that would would show how to do this?
Thanks!
I ended up solving this by using the "devise_invitable" gem. The only thing that I had to extend to get this working was to make sure that a user didn't need to be authenticated to send an invitation. Over-ride the invitations contoller and it's working great:
class InvitationsController < Devise::InvitationsController
include Devise::Controllers::InternalHelpers
skip_filter :authenticate_inviter!
skip_filter :authenticate!
def current_inviter
#current_inviter ||= User.new(params[:user])
end
end
I'm not sure if there is good documentation for how to do this, but it would not be hard. Just don't require authentication on your landing page, or on the post from the login form on that page. Collect an email address in the login form. Send the user a mail to the address they log in with. In the email, include the 'forgot password' link (renamed to 'click here to sign in' ... or whatever) to force the user to login and choose a password. Does that work for you, or did i miss something?
I realize this is late, but this might be helpful to others. I had a similar requirement as you. Basically, I wanted to have a user enter an email address, and persist the user as a guest. Once the user is promoted to a 'regular' user, I re-enable password authentication. First, I created a warden strategy based on the devise-gullible gem. I made a modification to the authenticate method:
class Guest < Authenticatable
def authenticate!
resource = mapping.to.find_for_database_authentication(authentication_hash)
if resource
if resource.respond_to?(:guest?) and resource.guest?
success!(resource)
else
fail(:regular_user)
end
else
fail(:invalid)
end
end
end
I define a guest user in my user model as follows:
def guest?
self.roles.length == 0
end
I'm using CanCan with devise and a HABTM to handle the roles. Currently, I have a 'regular' user and an 'admin' user. A user is a guest if he has no assigned roles yet.
Then, you need to override the devise registrations controller:
class Users::RegistrationsController < Devise::RegistrationsController
prepend_before_filter :allow_params_authentication!, :only => :create
def create
resource = warden.authenticate(:guest,
{ scope: resource_name,
recall: "#{controller_path}#new" })
if resource
set_flash_message(:notice, :signed_in) if is_navigational_format?
sign_in(resource_name, resource)
respond_with resource, :location => after_sign_in_path_for(resource)
elsif warden.winning_strategy.message == :regular_user
set_flash_message :notice, :regular_user if is_navigational_format?
redirect_to new_session_path(resource_name)
else
super
end
end
end
Note that the idea is that we attempt to authenticate a user running the guest strategy only. If he is already registered as a guest, just sign him in normally. If the guest strategy fails because he is registered, but is now a regular user, redirect to the normal sign in page.
Now it is possible to persist some limited information I may wish to collect from a guest, and not require him to have to do a full sign up. I use a normal User model:
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable, :confirmable,
:recoverable, :rememberable, :trackable, :omniauthable,
:token_authenticatable
# Setup accessible (or protected) attributes for your model
attr_accessible :email, :password, :password_confirmation, :remember_me
has_and_belongs_to_many :roles
def self.new_with_session(params, session)
super.tap do |user|
if user.password.blank?
user.password = Devise.friendly_token[0,31]
# Also, we don't need to do email confirmation in this case.
# The user will have the guest role, and we'll do a confirmation
# when we promote him to a 'regular' user.
user.skip_confirmation!
end
end
end
def has_role?(role)
!!self.roles.find_by_name(role.to_s.camelize)
end
end
Note that I auto-generate a password in build_with_session. You can send a auth token at the time of your choosing, and then require the user to set a new password at that time. You'll also want to then change his role so that he becomes a regular user (or do whatever it is you want to note he is no longer a guest).
Here's my partial that appears on the front page:
<%= simple_form_for(resource,
as: resource_name,
url: user_registration_path,
defaults: { required: false },
html: { class: 'well' },
wrapper: :bootstrap,
validate: true) do |f| %>
<%= f.error_notification %>
<fieldset>
<legend>New here? Let's get started!</legend>
<%= f.input :email, placeholder: 'user#example.com', validate: { uniqueness: false } %>
<%= f.button :submit, "Get my quote!", class: 'btn-primary' %>
</fieldset>
<% end %>
So, this form functions as both a guest registration and login form. The only think I don't really care for so far is the the registrations controller is handling an authentication, but I didn't see an immediate way around this.
I implemented this strategy in my local lib directory. Be sure to set your load path appropriately, and add require 'devise_guest' to config/initializers/devise.rb.