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)
Related
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
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 running Rails application, using ActiveAdmin and its models to autenticate users. Now I'm interested in moving to an ActiveDirectory authentication, so my users can validate wiht the domain's users.
I've been trying adauth and it looks like a great gem, but I'm a little bit lost when trying to "mix" this gem with my ActiveAdmin authentication. I'm pretty sure I'm not the first one in doing it, so any help would be appreciated.
Thanks!
I finally was able to manage to integrate AD in ActiveAdmin.
Here's what I did, in case someone is interested:
Include gem 'adauth' in your gems
Execute bundle install
Execute rails g adauth:config
Configure the config/initializers/adauth.rb for your AD connection. For example, if your domain is example.com, you must include:
c.domain = "example.com"
c.server = "IP address of your domain controller"
c.base = "dc=example, dc=com"
Execute rails g adauth:sessions
Modify your application_controller.rb. Mine was:
class ApplicationController< ActionController::Base
protect_from_forgery
helper_method :current_user
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
end
def authenticate_user!
if current_user.nil?
redirect_to '/sessions/new', :error => "Invalid Login"
end
end
end
Execute rails g adauth:user_model user install_adauth.
This creates the migration install_adauth, but for some reason it was empty. I had to fill it myself with:
class InstallAdauth < ActiveRecord::Migration
def up
create_table :users do |u|
u.string 'login'
u.text 'group_strings'
u.string 'name'
u.string 'ou_strings'
end
end
def down
drop_table :users
end
end
Execute rake db:migrate
Modify your sessions_controller.rb. Mine was:
class SessionsController < ApplicationController
def new
redirect_to '/admin' if current_user
end
def create
ldap_user = Adauth.authenticate(params[:username], params[:password])
if ldap_user
user = User.return_and_create_with_adauth(ldap_user)
session[:user_id] = user.id
redirect_to '/admin'
else
redirect_to '/sessions/new', :error => "Invalid Login"
end
end
def destroy
session[:user_id] = nil
redirect_to '/sessions/new'
end
end
So far the validation through ActiveAdmin still works. To switch to ActiveDirectory we must change the file initializers/active_admin.rb
# config.authentication_method = :authenticate_admin_user!
config.authentication_method = :authenticate_user!
#config.current_user_method = :current_admin_user
config.current_user_method = :current_user
In my case, I needed to restart Apache too.
If anytime we want to switch back to ActiveAdmin, we just need to undo the last change
My project had 2 models: AdminUser and User.
I'd removed the AdminUser model (and db tables associated) and now I'm using my User model with an admin? method (I've had a boolean admin field in the users table)
ActiveAdmin documentation:
You can skip the Devise user class all together by using the
skip-users flag:
$> rails generate active_admin:install --skip-users
NOTE: If you don’t use the default user settings, you will need to
configure the settings in config/intializers/active_admin.rb to suite
your needs.
Here's what I've found in the initializer:
config.authentication_method = :authenticate_admin_user!
config.current_user_method = :current_admin_user
So, I've modified application_controller.rb as:
def authenticate_admin_user!
render(:file => "#{Rails.root}/public/403.html", :status => 403, :layout => false) and return if user_signed_in? && !current_user.admin?
authenticate_user!
end
def current_admin_user
return nil if user_signed_in? && !current_user.admin?
current_user
end
And in routes.rb:
devise_for :admin_users, ActiveAdmin::Devise.config.merge(:class_name => 'User')
How can I configure ActiveAdmin to access admin section with the ActiveAdmin default path: /admin?
My objective is to have 1 User model, but 2 separate signin pages:
/users/sign_in (default devise signin)
/admin (ActiveAdmin signin)
For now, when I try to access /admin, I'm redirected to /users/sign_in page :-(
Thx for your advices...
Since you skip generating new user model, replace any admin_user string in the generated stuff from ActiveAdmin to user. So instead of :authenticate_admin_user! replace it with :authenticate_user! and so on in migration files: instead of admin_user_id put user_id same for admin_user_type ... etc!
How can I restrict access to ALL devise controllers by IP address'? I am trying to allow only users from a specific IP address to view the admin interface / pages.
I found this approach. Which is to include a restrict_access method in the before filter. However, its a bit repetitive if I have to copy this method on all the Devise controllers that I currently use.
Is there a better approach?
class Admin::SessionsController < Devise::SessionsController
before_filter :restrict_access
# Needed to restrict access to a set of IP's only. We don't want random users trying to access the admin interface
def restrict_access
if Rails.env == 'development' or Rails.env == 'test'
whitelist = ['59.120.201.20', '59.120.201.21'].freeze
else
whitelist = ['59.120.201.20', '59.120.201.21'].freeze
end
unless whitelist.include? request.remote_ip
redirect_to root_path, :notice => 'Access denied!'
end
end
...
Build a class like the following and place it in RAILS_ROOT/lib/blacklist_constraint.rb.
class BlacklistConstraint
def initialize
if Rails.env == 'development' or Rails.env == 'test'
#whitelist = ['59.120.201.20', '59.120.201.21'].freeze
else
#whitelist = ['59.120.201.20', '59.120.201.21'].freeze
end
end
def matches?(request)
!#whitelist.include?(request.remote_ip)
end
end
... and in your routes.rb file...
match "*", :constraints => BlacklistConstraint.new, :controller => "blacklist", :action => "my_access_denied_action"
You may need to load the class in an initializer, or modify your config.autoload_paths += %W(#{Rails.root}/lib) in config/application.rb (Rails3.x).
I believe all of the Devise Controller extend your Application controller, so you could put the method in the ApplicationController as a protected method then you only need to call the
before_filter :restrict_access
on each devise controller.