0

this question is referring to this specific question.

I use pundit as my authorization gem and I want that only the user info that belongs to user X can be downloaded by user X. Right now i have http://localhost:3000/download.csv as my link to recieve the user info. But if I am logged in in to another user, for example user/2, I can still enter the url and download user/3 data.

What I have right now:

user_policy.rb

class UserPolicy < ApplicationPolicy

  def profile?
    true
  end

  def download?

  end

  private

  def user_or_admin
    user.id == record.id || user.admin?
  end


end

application_policy.rb

class ApplicationPolicy
  attr_reader :user, :record

  def initialize(user, record)
    @user = user
    @record = record
  end

  def index?
    false
  end

  def show?
    scope.where(:id => record.id).exists?
  end

  def create?
    create?
  end

  def new?
    create?
  end

  def update?
    false
  end

  def edit?
    update?
  end

  def destroy?
    false
  end

  class Scope
    attr_reader :user, :scope

    def initialize(user, scope)
      @user = user
      @scope = scope
    end

    def resolve
      scope.all
    end
  end
end

This is my user_controller.rb # this is not the users_controller, those are different views

    class UserController < ApplicationController
  prepend_view_path(File.join(Rails.root, 'app/views/user/'))


  layout 'application'


  def index

  end

  def billing
  end

  def plan
  end

  def profile
    @user = User.find(params[:id])
    @user_posts = @user.posts.order('created_at DESC')
  end
end

def download
  @user = User.find(params[:id])

  respond_to do |format|
    format.html
    format.csv { send_data @user.csv, filename: "userinfo-#{Date.today}.csv" }
  end
end

  def support
    @user = User.find(params[:id])
    @user_posts = @user.posts.order('created_at DESC')
  end

  def notifications
  end

  def show
    @user = User.find(params[:id])
    @posts = current_user.posts.order('created_at DESC')
    @user_posts = @user.posts.order('created_at DESC')
  end

Update: As suggested I tried implementing a new download action in my user_controller and tried to use this code in my view:

<p><%= link_to("Export data as CSV", download_path(@user, format: :csv), { :controller => :user, :action => :download }, class: "btn btn-success") %></p> 

but this throws the following error:

Recieve Unknown action - The action 'download' could not be found for UserController

routes.rb

        Rails.application.routes.draw do

  get 'legal/privacy'

  get :datenschutz, to: 'legal#terms_of_service'


  devise_for :users, path_names: { sign_in: 'login', sign_out: 'logout', sign_up: 'registrieren', edit: 'bearbeiten' }, :controllers => { registrations: 'registrations' }

  get '/users/mitteilungen/:id' => 'user#notifications'
  get '/users/:id/artikel/', :to => 'user#support', :as => :artikel
  get '/users/plan' => 'user#plan'
  get '/users/billing' => 'user#billing'
  get '/users/:id', :to => 'user#profile', :as => :user
  get 'download', :to => 'user#download', :controller => :user, action: :download, :as => :download


  resources :posts, path: :news do
    resources :comments, path: :kommentare do
    end
  end


  devise_scope :user do
    resources :posts, path: :news do
      resources :comments do

      end
    end
  end
  root to: "posts#index", as: :root

end
benl96
  • 274
  • 3
  • 18
  • You could separate the csv download into another controller action/route (something like `download`) then authorize it separately from `profile` show with a `download?` method in the policy. – Mark Merritt Jul 28 '18 at 21:03
  • I created a download action inside my user controller, how would I tell my link_to to use that?

    <%= link_to("Export data as CSV", user_path(@user, format: :csv), { :controller => :user, :action => :download }, class: "btn btn-success") %>

    doesn't seem to work
    – benl96 Jul 28 '18 at 21:35
  • You have to create a custom route that points to your download action. If you get stuck on that I'll write up an answer. – Mark Merritt Jul 28 '18 at 22:02
  • I created the following route get 'download', :to => 'user#download', but I get The action 'download' could not be found for UserController as a result on clicking the button. Also I have to remove the class argument since I can only permit 3 arguments, how would I solve that one as well? Thanks for helping me with that! – benl96 Jul 28 '18 at 22:28
  • Please post your routes.rb – Mark Merritt Jul 29 '18 at 01:51
  • Added that part. – benl96 Jul 29 '18 at 02:11

2 Answers2

0

Your condition is wrong @user.id = user.id.

user IS @user. I think you meant user.id = record.id

record is an instance that you are authorizing, for example:

def show
  @user = User.find(params[:id])
  authorize @user
  ...
end

authorize @user is basically shortcut for: UserPolicy.new(@user, current_user).show?

BTW I am not sure but I think you can safely omit .id: user == record

UPD. I re-read your policy. ApplicationPolicy generally looks like this:

class ApplicationPolicy
  attr_reader :user, :record

  def initialize(user, record)
    @user = user
    @record = record
  end
end

You overrided constructor. Why? Let it stay the way Pundit intends it to be

UPD2. This is what I expect your UserPolicy to look like:

class UserPolicy < ApplicationPolicy
  def profile? #this is the action I have the button in to export data
    return true if user_or_admin?
  end

  private

  def user_or_admin?
    user.id == record.id || user.admin?
  end
end
Nondv
  • 769
  • 6
  • 11
  • You mean def user_or_admin user.id == record.id || user.admin? end ? But I didn't define record as user yet, how would it recognize record as a user in that specific action? BTW: I still have the unknown action error – benl96 Jul 29 '18 at 11:49
  • @benl96 yep, that's what I mean. But actually let me update my answer – Nondv Jul 29 '18 at 11:51
  • By overriding constructor I guess you mean the attr_reader? I removed it from my user_policy. Will update my question and add my application_policy as well. – benl96 Jul 29 '18 at 11:57
  • @benl96 I advice you to read more about Pundit. The problem is that how you use the gem. – Nondv Jul 29 '18 at 12:00
  • How does a unknown action has to do with pundit? Sorry for might misunderstanding, but I have the pundit documentation open and can't seem to find my mistake or something that I might messed up on implementation. – benl96 Jul 29 '18 at 12:09
  • Unknown action error is irrelevant to the question and appeared when you tried to solve original issue. This is what I understand from reading it – Nondv Jul 29 '18 at 12:09
  • I think I've lost the thread. I didn't have any problems with my authorization, although your suggestion seem to make sense to me. In all the other questions similiar to mine that I've looked up nothing really clears what kind of problem I'm having right now. Need to look that up – benl96 Jul 29 '18 at 12:18
0

We discussed too much in comments to my original answer. I wanna clarify my point.

You wrote:

I use pundit as my authorization gem and I want that only the userdata that belongs to user X can be downloaded by user X. Right now i have http://localhost:3000/download/x.csv as my link to recieve the user data. But if I am logged in in to another user, for example user/:id 2, I can still enter the url and download users/3 data.

So the problem is that authorization doesnt work. Am I right?

After that you wrote:

Update: As suggested I tried implementing a new download action in my user_controller and tried to use this code in my view: ...

but this throws the following error: ...

So problem with your routing is outside of original question and should be solved in different one. Anyway, you can check out this question.

So. If your problem is authorization - just write your policies right and use them in your controller. This would be all.

Nondv
  • 769
  • 6
  • 11
  • Sorry, yes you are correct. I didn't think about it that way. But I haven't even gotten to the authorization yet. If I don't use the download method and just use the button itself it works just fine. However, when I try to put that into the download method to authorize it afterwards the error throws. The controller and the download action are clearly there, so I don't get why I keep getting this error. Like you said, the policies should be written right and I think I did that now. – benl96 Jul 29 '18 at 12:48