I'm building a simple blog-style application. I really only need admin and non-admin users, so it seems like having a simple column in the user model called admin (boolean) will suffice.
I'm using Devise for authorization right now, and I've got the admin column added. I'm trying to set up my default admin user (myself) in seeds.rb, however admin comes out as false unless I add the admin column to attr_accessible. It seems like this would be a security concern, however, and I don't generally want admin users to be able to be created except by another admin. What's the correct, and safe, way to do this?
You want to handle setting the admin boolean internally. Don't expose it to mass-assignment.
Have your user model automatically default the first user (you) to an admin. Use a before_create method for this...
# models/user.rb
before_create :make_first_user_an_admin
def make_first_user_an_admin
self.admin = self.class.count == 0 # sets true if no users exist, false otherwise
end
Then use an instance method to set adminship...
# models/user.rb
def toggle_admin
self.admin = !self.admin
save
end
guard this method in your controller...
# controllers/users_controller.rb
def change_adminship
if current_user.admin
#user.toggle_admin
else
raise "Can't do that."
end
end
You are very correct to leave admin as not attr_accessible, this just disables setting it via mass-assignment. You can still set admin in your seeds by setting it singularly. Example:
user = User.new(:name => 'joe') ...
user.admin = true
user.save
Related
I have functionality of inactive account in my application for handling this i override active_for_authentication? method as below
def active_for_authentication?
super && activated?
end
But In my application super admin can also directly login in to other user account, whether it is active or not active
bypass_sign_in(User.find(resource.id))
I used above method for by pass sign in, it allows me to directly sign in only for activated user, when i login for non activated user it goes in infinite loop .
Any solutions to over come this issue or don't run active_for_authentication? callback when bypass_sign_in?
When admin logs in to another user account you can store some additional data in session, that makes it clear that this is the super admin mode.
def login_as(another_user)
return unless current_user.super_admin?
session[:super_admin_mode] = true
bypass_sign_in(another_user)
end
Unfortunately, you can't access session in Rails models, but you can store needed session information in some per-request global variable that is available in models. The solution might be like this:
module SessionInfo
def self.super_user_mode?
!!Thread.current[:super_user_mode]
end
def self.super_user_mode=(value)
Thread.current[:super_user_mode] = value
end
end
In the ApplicationController:
class ApplicationController < ActionController::Base
before_filter :store_session_info
private
def store_session_info
SessionInfo.super_user_mode = session[:super_admin_mode]
end
end
In the model:
def active_for_authentication?
super && (activated? || SessionInfo.super_user_mode?)
end
Also, you should make sure that the :super_admin_mode flag is removed from session when the super user logs out. Maybe it happens automatically, I am not sure. Maybe you will need to do it manually overriding Devise::SessionsController#destroy method (see the example below)
def destroy
session[:super_admin_mode] = nil
super
end
Also read this for better understanding of how devise handles session Stop Devise from clearing session
I recently came across a similar issue where I needed to allow an Admin to sign in as regular Users who were not active in Devise. I came up with the following solution that doesn't involve using Thread.current (which after looking into further online it seems like using Thread.current could be a precarious solution to this problem).
You can create a subclass of User called ProxyUser that has the active_for_authentication? return true. Something like this:
class ProxyUser < User
# If you have a type column on User then uncomment this line below
# as you dont want to expect ProxyUser to have type 'ProxyUser'
#
# self.inheritance_column = :_type_disabled
devise :database_authenticatable
def active_for_authentication?
true
end
end
Then in the controller you want something like this:
proxy_user = ProxyUser.find(params[:user_id])
sign_in :proxy_user, proxy_user
Also in your routes you will need devise to expect ProxyUser so include:
devise_for :proxy_users
And finally when you sign this user out (assuming you can sign the user out in your controller code) make sure to tell devise the scope of the sign out, so you would do
sign_out :proxy_user
And then finally note that in your app you may be expecting current_user in different places (such as if you use CanCanCan for authorization) and now when you sign in as a proxy_user your app will return current_user as nil. Your app will instead have an object called current_proxy_user that will be your signed-in ProxyUser object. There are many ways to handle the issues resulting from your current_user returning nil in this case (including overwriting current_user in your application controller).
In our application we have normal users. However, we want to be able to make invitations, to invite certain people. Note that an invitation is directly coupled to a user, as we want to be able to set certain settings for these users already. (We are mitigating clients from our old software to the new).
So:
An admin should be able to create a new user and change its settings.
When someone follows a link with their invitation_token, they should see a form where they can set a password for their account.
What I am having trouble with, is how to enable the admin to create an user account, bypassing the normal password validation. It would be a horrible solution if a default password would need to be set, as this would create a severe security flaw.
How to create a new User in Devise without providing a password?
There are at least two ways to do what you want:
Method 1:
Overload Devise's password_required? method
class User < ActiveRecord::Base
attr_accessor :skip_password_validation # virtual attribute to skip password validation while saving
protected
def password_required?
return false if skip_password_validation
super
end
end
Usage:
#user.skip_password_validation = true
#user.save
Method 2:
Disable validation with validate: false option:
user.save(validate: false)
This will skip validation of all fields (not only password). In this case you should make sure that all other fields are valid.
...
But I advise you to not create users without password in your particular case. I would create some additional table (for example, invitations) and store all required information including the fields that you want to be assigned to a user after confirmation.
TL;DR:
user.define_singleton_method(:password_required?) { false }
Fiddle:
class MockDeviseUser
protected
def password_required?
true
end
end
class User < MockDeviseUser
def is_password_required?
puts password_required?
end
end
unrequired_password_user = User.new
unrequired_password_user.define_singleton_method(:password_required?) { false }
unrequired_password_user.is_password_required?
regular_user = User.new
regular_user.is_password_required?
#false
#true
You can now use the DeviseInvitable gem for this.
It allows you to do exactly what you're asking.
If you need to completely overwrite the password requirement just define the following on your model:
def password_required?
false
end
How can I define a method between Users and Admins? I am using Omniauth Twitter for my users but there are actions that I want to make sure users can not do if they are not admins. I have bundled the gem Active admin but because I am not using devise, any user can currently access the Admin dashboard.
If it were me, I'd create a role attribute on your User model, and have it default to say "customer" or whatever a default user is, but have the ability to set some user's role to "admin". You should be able to set that using Active Admin's edit page. Than create a custom method on your User model, or maybe create a helper method, whatever you prefer...something like:
def admin?
if self.role == "admin"
true
else
false
end
That way you wrap sensitive info (like info on your dashboard) in a user.admin? if statement.
if user.admin?
..display stuff..
end
Or check out this: http://activeadmin.info/docs/13-authorization-adapter.html
I'm using devise for authentication. How can I set the sign-in scope? For example, say I only want to authenticate the user for the scope:
User.where(:active => true)
Am I being clear? It's simple, but I can elaborate more if needed.
(I realize there is a lockable module, but my actual scope isn't for active users, it's more like current_site.users, where the current_site is based off the domain)
Just overwrite these two methods in your User model to check if the active flag is true:
# Called by Devise to see if an user can currently be signed in
def active_for_authentication?
active? && super
end
# Called by Devise to get the proper error message when an user cannot be signed in
def inactive_message
!active? ? :deactivated : super
end
And in your devise.en.yml, add the proper error message:
devise:
failure:
deactivated: "Luke, I'm your father and your account was locked!"
You could use default_scope... but that might get in your way.
Why not override devise's find_for_database_authentication method? See the wiki.
I would like to manually create new Users, without forcing them to verify their email address.
The idea is to allow existing users to automatically add their friends without requiring their registration. It makes sense for the business case I'm working to solve.
How can this be achieved with Devise?
The skip_confirmation! method is available to any confirmable model.
#user = User.new params[:user]
#user.skip_confirmation! # Sets confirmed_at to Time.now, activating the account
#user.save
The user account will be activated though. If you don't want that, continue reading.
Devise uses conditional callbacks to generate the confirmation token and send the email. The callbacks will be called only if confirmation_required? returns true. Redefine it on your model:
def confirmation_required?
false
end
However, this will make the active_for_authentication? method always return true because it takes whether or not confirmation is required into account. We have to redefine that as well:
def active_for_authentication?
confirmed? || confirmation_period_valid?
end
This way, the account will stay inactive and no confirmation email will be sent. You will have to manually activate the user by calling confirm! on the record or just setting confirmed_at to any date.
It's quite a hack, but it should work.
For reference: confirmable.rb
I just want to add for future reference that since Devise 2.2 there is now a skip_confirmation_notification! method available as well which basically does everything from Matheus' post without redefining the methods in the model.