Is it possible to access parameters passed in the url in CanCan? I'm trying to authenticate guests based on a token in the url.
Thanks!
I recently achieved something similar using the method described here:
https://github.com/ryanb/cancan/wiki/Accessing-request-data
In my case, it looked something like this:
app/controllers/application_controller.rb:
class ApplicationController < ActionController::Base
...
def current_ability
#current_ability ||= Ability.new(current_user, params[:token])
end
end
app/models/ability.rb:
class Ability
include CanCan::Ability
def initialize(user, token=nil)
...
can :read, Article, :tokens => { :token => token }
...
end
end
Currently, we are using something similar to
authorize! action, resource, session[:access_token] in a before filter - from this page: http://spreecommerce.com/documentation/security.html
Related
I'm trying to integrate pundit with my active admin and devise configuration. But the app works weirdly. It takes in model/record as current_user.
my policy file:
class AdminUserPolicy
attr_reader :current_user, :model
def initialize(current_user, model)
Rails.logger.info '--------------- initialize called-------------------'
Rails.logger.info current_user
Rails.logger.info model
#current_user = current_user
#record = model
end
def index?
#current_user.admin?
end
end
controller:
controller do
include Pundit
protect_from_forgery
rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
before_action :authenticate_admin_user!
def index
authorize current_admin_user
super
end
private
def user_not_authorized
flash[:alert]="Access denied"
redirect_to (request.referrer || admin_root_path)
end
end
The log is as follows:
--------------- initialize called----------------------------------------
#<AdminUser:0x007f27733f8a80>
Completed 500 Internal Server Error in 364ms (ActiveRecord: 312.8ms)
NoMethodError (undefined method `admin?' for nil:NilClass):
app/policies/admin_user_policy.rb:13:in `index?'
app/admin/dashboard.rb:19:in `index'
Rendering /usr/local/rvm/gems/ruby-2.3.4/gems/actionpack-5.0.6/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb within rescues/layout
Rendering /usr/local/rvm/gems/ruby-2.3.4/gems/actionpack-5.0.6/lib/action_dispatch/middleware/templates/rescues/_source.html.erb
Rendered /usr/local/rvm/gems/ruby-2.3.4/gems/actionpack-5.0.6/lib/action_dispatch/middleware/templates/rescues/_source.html.erb (4.8ms)
Rendering /usr/local/rvm/gems/ruby-2.3.4/gems/actionpack-5.0.6/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb
Rendered /usr/local/rvm/gems/ruby-2.3.4/gems/actionpack-5.0.6/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb (2.5ms)
Rendering /usr/local/rvm/gems/ruby-2.3.4/gems/actionpack-5.0.6/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb
Rendered /usr/local/rvm/gems/ruby-2.3.4/gems/actionpack-5.0.6/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb (1.1ms)
Rendered /usr/local/rvm/gems/ruby-2.3.4/gems/actionpack-5.0.6/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb within rescues/layout (39.5ms)
The weird part is, according to logs, current_user=nil and model=#<AdminUser:0x007f27733f8a80>
I swapped current_user with model my policy file as
def index?
#record.admin?
end
And it works!
I don't understand this strange behaviour.
Pundit policy doc says that it calls the current_user method to retrieve what to send into the first argument of the initialize method inside the Policy class. If you have configured ActiveAdmin to retrieve the current logged in user by using current_admin_user, then you have to override the pundit default method in your ApplicationController class like so: Ref
class ApplicationController < ActionController::Base
// ...
def pundit_user
current_admin_user // or whatever based on ActiveAdmin initializer config
end
end
In order to make the defined policy working, you have to invoke authorize inside the controller action with the instance of the corresponding policy model. So if you have a PostPolicy and you want to authorize the update action, you have to do the following:
controller do
def update
#post = Post.find(params[:id])
authorize #post // the current user will be automatically sent to the PostPolicy
super
end
end
The authorize method automatically infers that Post will have a matching PostPolicy class, and instantiates this class, handing in the current user and the given record. It then infers from the action name, that it should call update? on this instance of the policy. In this case, you can imagine that authorize would have done something like this:
unless PostPolicy.new(current_user, #post).update?
raise Pundit::NotAuthorizedError, "not allowed to update? this #{#post.inspect}"
end
Having all these, in your case, if you want that a user should be authorized before viewing the list of all users, you can define the AdminUserPolicy like you have done already. Then in the index action of your AdminUserController,
controller do
def index
#users = AdminUser.all
authorize #users // NOT `authorize current_admin_user`
super
end
end
You can pass a second argument to authorize if the name of the permission you want to check doesn't match the action name. For example:
def publish
#post = Post.find(params[:id])
authorize #post, :update?
#post.publish!
redirect_to #post
end
Sorry if this is a noob question on oauth
I've implemented an oauth2 API with devise+doorkeeper based on the examples here: https://doorkeeper-provider.herokuapp.com/ and here: https://github.com/applicake/doorkeeper-devise-client
I want to be able to provide an API endpoint that returns a list of deals that's paginatable, the code is the following:
module Api::V1
class DealsController < ApiController
doorkeeper_for :index
doorkeeper_for :create, :scopes => [:write]
respond_to :json
def index
if params[:page].nil?
page = 1
else
page = params[:page].to_i
end
respond_with Deal.page(page).order("published DESC")
end
def create
respond_with 'api_v1', Deal.create!(params[:deal])
end
end
end
However, on the client side, I cannot pass a page param with something like this:
/explore/deals.json?page=3
The page param is not seen in the provider for some reason. Can someone help me please?
I realized the problem is in the api_controller of doorkeeper-devise-client
The page param isn't passed correctly. Making the following change fixes the problem:
class ApiController < ApplicationController
respond_to :json
def explore
api_call = params[:api]
if !params[:page].nil?
api_call << "/?page=#{params[:page]}"
end
#json = doorkeeper_access_token.get("api/v1/#{api_call}").parsed
respond_with #json
end
end
My rails application is based on accounts. So every user belongs to an account, every project and so on.
Currently I got routes like:
/mission-control
/tasks
/projects
And I'm getting the current account by the user. Since a user should be able to have permissions to many accounts I'd like to have the following routes:
/:account_id/mission-control
/:account_id/tasks
/:account_id/projects
I know I could write:
resource :accounts do
resource :tasks
end
but this would end up in e.g.
/accounts/1/tasks
Hope somebody could help me how to write routes for that!
Now I got the correct way:
At first I needed to define the scope like:
scope ":account_id" do
resources :tasks
resources :projects
end
Then, to make eversthing work, cause links within a loop like:
<%= link_to "Project", project %>
won't work, you need to set default url options in the application controller:
def default_url_options(options={})
if #current_account.present?
{ :account_id => #current_account.id }
else
{ :account_id => nil }
end
end
That fixes every No Route Matches Error for me. If there is no :account_id there will be no error, e.g. for that devise stuff.
For #Mohamad:
before_filter :set_current_account
# current account
def set_current_account
# get account by scoped :account_id
if params[:account_id]
#current_account = Account.find(params[:account_id])
return #current_account
end
# dont' raise the exception if we are in that devise stuff
if !devise_controller?
raise "Account not found."
end
end
That devise and error handling could be better tho. :S
You could do a scope like this:
scope ":account_id" do
resources :tasks
resources :projects
end
I am trying to create the user registration views and model on my website but I am having a small issue :
I am using devise and omniauth to get the facebook connect features working and it works,
But I want my facebook users when they sign in the first time to create their password,
That is also working, I redirect them to the filled sign up form and they only have to enter their password. But I want them to go to a second "sign_up form" named /views/registrations/new_facebook.html.erb where they can only enter their password and I will also add some other information,
I created the correct view and tested it but I have no idea how to create the correct routes to bypass Devise default
match '/facebook' => 'registrations#new', :as => 'new_facebook_user_registration'
I believe the issue is with match because that's what's not recognised,
If anyone can help me that would be great thanks,
I added my controller code for omniauth :
class OmniauthCallbacksController < Devise::OmniauthCallbacksController
def all
user = User.from_omniauth(request.env["omniauth.auth"])
if user.persisted?
flash[:success] = "Welcome back"
sign_in_and_redirect user
else
session["devise.user_attributes"] = user.attributes
redirect_to new_facebook_user_registration_url
end
end
alias_method :facebook, :all
end
How can I make the redirect_to new_facebook_user_registration_url actually work ?
devise_scope :user do
match "registrations/new_facebook" => "registrations#new_facebook"
end
That's the solution I copied in the registrations controller the new method and named it new_facebook and now everything is working as expected !
I think the issue is that you're not overriding the devise method that redirects to that path. Also according to the devise docs your routes should be set up with a "devise_for" call.
Here's the wiki page describing how to do what you are asking to do, although you may need a bit of custom logic to deal with cases that aren't facebook signups.
https://github.com/plataformatec/devise/wiki/How-To:-Redirect-to-a-specific-page-on-successful-sign-up-(registration)
Some example code from that page:
class RegistrationsController < Devise::RegistrationsController
protected
def after_sign_up_path_for(resource)
'/an/example/path'
end
end
and the one for routes:
devise_for :users, :controllers => { :registrations => "registrations" }
I want to override Devise's RegistrationsContollers' create action so that when a user signs up, I can associate a UserProfile model with that user.
So, following the guidelines in the Devise Readme, I override the action:
#File app/controllers/registrations_controller.rb:
class Users::RegistrationsController < Devise::RegistrationsController
def create
# some code here..
self.user_profiles.build #Error (no method `user_profiles`)
current_user.user_profiles.build #Error (current_user is nil)
some other way???
end
end
#File routes.rb:
devise_for :users, :controllers => { :registrations => 'users/registrations' }
Devise is creating a record in the users table, but how do I associate a UserProfile with that record?
I've tried googling but I simply can't get this to work! Any help is much appreciated.
(I'm now using Devise 1.1.5 on Rails 3.0.3)
SOLVED:
Adding solution for benefit of others:
#File app/controllers/registrations_controller.rb:
class Users::RegistrationsController < Devise::RegistrationsController
def create
super
#user.build_user_profile
#user.user_profile.some_data = 'abcd'
#user.save!
end
end
self refers to the contoller not the model in this context.
Also, does the user model have many UserProfiles? Otherwise if they don't (ie they only have one), then you should use #user.build_user_profile, not #user.user_profiles.build
I'd also recommend doing this at the model level, not the controller level, using a callback such as before_create or after_create, ie:
class User < AR
has_one :user_profile
after_create :build_profile
def build_profile
self.build_user_profile
...
end
end