Minimal authorization through OO design and pure Ruby classes

Overview

Pundit

Build Status Code Climate Inline docs Gem Version

Pundit provides a set of helpers which guide you in leveraging regular Ruby classes and object oriented design patterns to build a simple, robust and scalable authorization system.

Links:

Sponsored by:

Varvet

Installation

gem "pundit"

Include Pundit in your application controller:

class ApplicationController < ActionController::Base
  include Pundit
end

Optionally, you can run the generator, which will set up an application policy with some useful defaults for you:

rails g pundit:install

After generating your application policy, restart the Rails server so that Rails can pick up any classes in the new app/policies/ directory.

Policies

Pundit is focused around the notion of policy classes. We suggest that you put these classes in app/policies. This is a simple example that allows updating a post if the user is an admin, or if the post is unpublished:

class PostPolicy
  attr_reader :user, :post

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

  def update?
    user.admin? || !post.published?
  end
end

As you can see, this is just a plain Ruby class. Pundit makes the following assumptions about this class:

  • The class has the same name as some kind of model class, only suffixed with the word "Policy".
  • The first argument is a user. In your controller, Pundit will call the current_user method to retrieve what to send into this argument
  • The second argument is some kind of model object, whose authorization you want to check. This does not need to be an ActiveRecord or even an ActiveModel object, it can be anything really.
  • The class implements some kind of query method, in this case update?. Usually, this will map to the name of a particular controller action.

That's it really.

Usually you'll want to inherit from the application policy created by the generator, or set up your own base class to inherit from:

class PostPolicy < ApplicationPolicy
  def update?
    user.admin? or not record.published?
  end
end

In the generated ApplicationPolicy, the model object is called record.

Supposing that you have an instance of class Post, Pundit now lets you do this in your controller:

def update
  @post = Post.find(params[:id])
  authorize @post
  if @post.update(post_params)
    redirect_to @post
  else
    render :edit
  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

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

You can pass an argument to override the policy class if necessary. For example:

def create
  @publication = find_publication # assume this method returns any model that behaves like a publication
  # @publication.class => Post
  authorize @publication, policy_class: PublicationPolicy
  @publication.publish!
  redirect_to @publication
end

If you don't have an instance for the first argument to authorize, then you can pass the class. For example:

Policy:

class PostPolicy < ApplicationPolicy
  def admin_list?
    user.admin?
  end
end

Controller:

def admin_list
  authorize Post # we don't have a particular post to authorize
  # Rest of controller action
end

authorize returns the instance passed to it, so you can chain it like this:

Controller:

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

# return the record even for namespaced policies
def show
  @user = authorize [:admin, User.find(params[:id])]
end

You can easily get a hold of an instance of the policy through the policy method in both the view and controller. This is especially useful for conditionally showing links or buttons in the view:

<% if policy(@post).update? %>
  <%= link_to "Edit post", edit_post_path(@post) %>
<% end %>

Headless policies

Given there is a policy without a corresponding model / ruby class, you can retrieve it by passing a symbol.

# app/policies/dashboard_policy.rb
class DashboardPolicy < Struct.new(:user, :dashboard)
  # ...
end

Note that the headless policy still needs to accept two arguments. The second argument will just be the symbol :dashboard in this case which is what is passed as the record to authorize below.

# In controllers
authorize :dashboard, :show?
# In views
<% if policy(:dashboard).show? %>
  <%= link_to 'Dashboard', dashboard_path %>
<% end %>

Scopes

Often, you will want to have some kind of view listing records which a particular user has access to. When using Pundit, you are expected to define a class called a policy scope. It can look something like this:

class PostPolicy < ApplicationPolicy
  class Scope
    def initialize(user, scope)
      @user  = user
      @scope = scope
    end

    def resolve
      if user.admin?
        scope.all
      else
        scope.where(published: true)
      end
    end

    private

    attr_reader :user, :scope
  end

  def update?
    user.admin? or not record.published?
  end
end

Pundit makes the following assumptions about this class:

  • The class has the name Scope and is nested under the policy class.
  • The first argument is a user. In your controller, Pundit will call the current_user method to retrieve what to send into this argument.
  • The second argument is a scope of some kind on which to perform some kind of query. It will usually be an ActiveRecord class or a ActiveRecord::Relation, but it could be something else entirely.
  • Instances of this class respond to the method resolve, which should return some kind of result which can be iterated over. For ActiveRecord classes, this would usually be an ActiveRecord::Relation.

You'll probably want to inherit from the application policy scope generated by the generator, or create your own base class to inherit from:

class PostPolicy < ApplicationPolicy
  class Scope < Scope
    def resolve
      if user.admin?
        scope.all
      else
        scope.where(published: true)
      end
    end
  end

  def update?
    user.admin? or not record.published?
  end
end

You can now use this class from your controller via the policy_scope method:

def index
  @posts = policy_scope(Post)
end

def show
  @post = policy_scope(Post).find(params[:id])
end

Like with the authorize method, you can also override the policy scope class:

def index
  # publication_class => Post
  @publications = policy_scope(publication_class, policy_scope_class: PublicationPolicy::Scope)
end

In this case it is a shortcut for doing:

def index
  @publications = PublicationPolicy::Scope.new(current_user, Post).resolve
end

You can, and are encouraged to, use this method in views:

<% policy_scope(@user.posts).each do |post| %>
  <p><%= link_to post.title, post_path(post) %></p>
<% end %>

Ensuring policies and scopes are used

When you are developing an application with Pundit it can be easy to forget to authorize some action. People are forgetful after all. Since Pundit encourages you to add the authorize call manually to each controller action, it's really easy to miss one.

Thankfully, Pundit has a handy feature which reminds you in case you forget. Pundit tracks whether you have called authorize anywhere in your controller action. Pundit also adds a method to your controllers called verify_authorized. This method will raise an exception if authorize has not yet been called. You should run this method in an after_action hook to ensure that you haven't forgotten to authorize the action. For example:

class ApplicationController < ActionController::Base
  include Pundit
  after_action :verify_authorized
end

Likewise, Pundit also adds verify_policy_scoped to your controller. This will raise an exception similar to verify_authorized. However, it tracks if policy_scope is used instead of authorize. This is mostly useful for controller actions like index which find collections with a scope and don't authorize individual instances.

class ApplicationController < ActionController::Base
  include Pundit
  after_action :verify_authorized, except: :index
  after_action :verify_policy_scoped, only: :index
end

This verification mechanism only exists to aid you while developing your application, so you don't forget to call authorize. It is not some kind of failsafe mechanism or authorization mechanism. You should be able to remove these filters without affecting how your app works in any way.

Some people have found this feature confusing, while many others find it extremely helpful. If you fall into the category of people who find it confusing then you do not need to use it. Pundit will work just fine without using verify_authorized and verify_policy_scoped.

Conditional verification

If you're using verify_authorized in your controllers but need to conditionally bypass verification, you can use skip_authorization. For bypassing verify_policy_scoped, use skip_policy_scope. These are useful in circumstances where you don't want to disable verification for the entire action, but have some cases where you intend to not authorize.

def show
  record = Record.find_by(attribute: "value")
  if record.present?
    authorize record
  else
    skip_authorization
  end
end

Manually specifying policy classes

Sometimes you might want to explicitly declare which policy to use for a given class, instead of letting Pundit infer it. This can be done like so:

class Post
  def self.policy_class
    PostablePolicy
  end
end

Alternatively, you can declare an instance method:

class Post
  def policy_class
    PostablePolicy
  end
end

Just plain old Ruby

As you can see, Pundit doesn't do anything you couldn't have easily done yourself. It's a very small library, it just provides a few neat helpers. Together these give you the power of building a well structured, fully working authorization system without using any special DSLs or funky syntax or anything.

Remember that all of the policy and scope classes are just plain Ruby classes, which means you can use the same mechanisms you always use to DRY things up. Encapsulate a set of permissions into a module and include them in multiple policies. Use alias_method to make some permissions behave the same as others. Inherit from a base set of permissions. Use metaprogramming if you really have to.

Generator

Use the supplied generator to generate policies:

rails g pundit:policy post

Closed systems

In many applications, only logged in users are really able to do anything. If you're building such a system, it can be kind of cumbersome to check that the user in a policy isn't nil for every single permission. Aside from policies, you can add this check to the base class for scopes.

We suggest that you define a filter that redirects unauthenticated users to the login page. As a secondary defence, if you've defined an ApplicationPolicy, it might be a good idea to raise an exception if somehow an unauthenticated user got through. This way you can fail more gracefully.

class ApplicationPolicy
  def initialize(user, record)
    raise Pundit::NotAuthorizedError, "must be logged in" unless user
    @user   = user
    @record = record
  end

  class Scope
    attr_reader :user, :scope

    def initialize(user, scope)
      raise Pundit::NotAuthorizedError, "must be logged in" unless user
      @user = user
      @scope = scope
    end
  end
end

NilClassPolicy

To support a null object pattern you may find that you want to implement a NilClassPolicy. This might be useful where you want to extend your ApplicationPolicy to allow some tolerance of, for example, associations which might be nil.

class NilClassPolicy < ApplicationPolicy
  class Scope < Scope
    def resolve
      raise Pundit::NotDefinedError, "Cannot scope NilClass"
    end
  end

  def show?
    false # Nobody can see nothing
  end
end

Rescuing a denied Authorization in Rails

Pundit raises a Pundit::NotAuthorizedError you can rescue_from in your ApplicationController. You can customize the user_not_authorized method in every controller.

class ApplicationController < ActionController::Base
  include Pundit

  rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized

  private

  def user_not_authorized
    flash[:alert] = "You are not authorized to perform this action."
    redirect_to(request.referrer || root_path)
  end
end

Alternatively, you can globally handle Pundit::NotAuthorizedError's by having rails handle them as a 403 error and serving a 403 error page. Add the following to application.rb:

config.action_dispatch.rescue_responses["Pundit::NotAuthorizedError"] = :forbidden

Creating custom error messages

NotAuthorizedErrors provide information on what query (e.g. :create?), what record (e.g. an instance of Post), and what policy (e.g. an instance of PostPolicy) caused the error to be raised.

One way to use these query, record, and policy properties is to connect them with I18n to generate error messages. Here's how you might go about doing that.

class ApplicationController < ActionController::Base
 rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized

 private

 def user_not_authorized(exception)
   policy_name = exception.policy.class.to_s.underscore

   flash[:error] = t "#{policy_name}.#{exception.query}", scope: "pundit", default: :default
   redirect_to(request.referrer || root_path)
 end
end
en:
 pundit:
   default: 'You cannot perform this action.'
   post_policy:
     update?: 'You cannot edit this post!'
     create?: 'You cannot create posts!'

Of course, this is just an example. Pundit is agnostic as to how you implement your error messaging.

Manually retrieving policies and scopes

Sometimes you want to retrieve a policy for a record outside the controller or view. For example when you delegate permissions from one policy to another.

You can easily retrieve policies and scopes like this:

Pundit.policy!(user, post)
Pundit.policy(user, post)

Pundit.policy_scope!(user, Post)
Pundit.policy_scope(user, Post)

The bang methods will raise an exception if the policy does not exist, whereas those without the bang will return nil.

Customize Pundit user

In some cases your controller might not have access to current_user, or your current_user is not the method that should be invoked by Pundit. Simply define a method in your controller called pundit_user.

def pundit_user
  User.find_by_other_means
end

Policy Namespacing

In some cases it might be helpful to have multiple policies that serve different contexts for a resource. A prime example of this is the case where User policies differ from Admin policies. To authorize with a namespaced policy, pass the namespace into the authorize helper in an array:

authorize(post)                   # => will look for a PostPolicy
authorize([:admin, post])         # => will look for an Admin::PostPolicy
authorize([:foo, :bar, post])     # => will look for a Foo::Bar::PostPolicy

policy_scope(Post)                # => will look for a PostPolicy::Scope
policy_scope([:admin, Post])      # => will look for an Admin::PostPolicy::Scope
policy_scope([:foo, :bar, Post])  # => will look for a Foo::Bar::PostPolicy::Scope

If you are using namespaced policies for something like Admin views, it can be useful to override the policy_scope and authorize helpers in your AdminController to automatically apply the namespacing:

class AdminController < ApplicationController
  def policy_scope(scope)
    super([:admin, scope])
  end

  def authorize(record, query = nil)
    super([:admin, record], query)
  end
end

class Admin::PostController < AdminController
  def index
    policy_scope(Post)
  end

  def show
    post = authorize Post.find(params[:id])
  end
end

Additional context

Pundit strongly encourages you to model your application in such a way that the only context you need for authorization is a user object and a domain model that you want to check authorization for. If you find yourself needing more context than that, consider whether you are authorizing the right domain model, maybe another domain model (or a wrapper around multiple domain models) can provide the context you need.

Pundit does not allow you to pass additional arguments to policies for precisely this reason.

However, in very rare cases, you might need to authorize based on more context than just the currently authenticated user. Suppose for example that authorization is dependent on IP address in addition to the authenticated user. In that case, one option is to create a special class which wraps up both user and IP and passes it to the policy.

class UserContext
  attr_reader :user, :ip

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

class ApplicationController
  include Pundit

  def pundit_user
    UserContext.new(current_user, request.ip)
  end
end

Strong parameters

In Rails, mass-assignment protection is handled in the controller. With Pundit you can control which attributes a user has access to update via your policies. You can set up a permitted_attributes method in your policy like this:

# app/policies/post_policy.rb
class PostPolicy < ApplicationPolicy
  def permitted_attributes
    if user.admin? || user.owner_of?(post)
      [:title, :body, :tag_list]
    else
      [:tag_list]
    end
  end
end

You can now retrieve these attributes from the policy:

# app/controllers/posts_controller.rb
class PostsController < ApplicationController
  def update
    @post = Post.find(params[:id])
    if @post.update_attributes(post_params)
      redirect_to @post
    else
      render :edit
    end
  end

  private

  def post_params
    params.require(:post).permit(policy(@post).permitted_attributes)
  end
end

However, this is a bit cumbersome, so Pundit provides a convenient helper method:

# app/controllers/posts_controller.rb
class PostsController < ApplicationController
  def update
    @post = Post.find(params[:id])
    if @post.update_attributes(permitted_attributes(@post))
      redirect_to @post
    else
      render :edit
    end
  end
end

If you want to permit different attributes based on the current action, you can define a permitted_attributes_for_#{action} method on your policy:

# app/policies/post_policy.rb
class PostPolicy < ApplicationPolicy
  def permitted_attributes_for_create
    [:title, :body]
  end

  def permitted_attributes_for_edit
    [:body]
  end
end

If you have defined an action-specific method on your policy for the current action, the permitted_attributes helper will call it instead of calling permitted_attributes on your controller.

If you need to fetch parameters based on namespaces different from the suggested one, override the below method, in your controller, and return an instance of ActionController::Parameters.

def pundit_params_for(record)
  params.require(PolicyFinder.new(record).param_key)
end

For example:

# If you don't want to use require
def pundit_params_for(record)
  params.fetch(PolicyFinder.new(record).param_key, {})
end

# If you are using something like the JSON API spec
def pundit_params_for(_record)
  params.fetch(:data, {}).fetch(:attributes, {})
end

RSpec

Policy Specs

Pundit includes a mini-DSL for writing expressive tests for your policies in RSpec. Require pundit/rspec in your spec_helper.rb:

require "pundit/rspec"

Then put your policy specs in spec/policies, and make them look somewhat like this:

describe PostPolicy do
  subject { described_class }

  permissions :update?, :edit? do
    it "denies access if post is published" do
      expect(subject).not_to permit(User.new(admin: false), Post.new(published: true))
    end

    it "grants access if post is published and user is an admin" do
      expect(subject).to permit(User.new(admin: true), Post.new(published: true))
    end

    it "grants access if post is unpublished" do
      expect(subject).to permit(User.new(admin: false), Post.new(published: false))
    end
  end
end

An alternative approach to Pundit policy specs is scoping them to a user context as outlined in this excellent post and implemented in the third party pundit-matchers gem.

Scope Specs

Pundit does not provide a DSL for testing scopes. Just test it like a regular Ruby class!

External Resources

Other implementations

License

Licensed under the MIT license, see the separate LICENSE.txt file.

Comments
  • Allows additional methods to be used with Scopes

    Allows additional methods to be used with Scopes

    A proposal for issue #368.

    Adds an additional argument to the policy_scope methods that specifies the method on the scope that should be called. Defaults to :resolve for backward compatibility.

    This makes Scopes more flexible by allowing multiple scopes to be defined on a Scope class and making them available for use in controllers or views with existing helper methods.

    feature request 
    opened by davidbiehl 50
  • Allow overriding policy class on authorize method

    Allow overriding policy class on authorize method

    This PR allows to override the policy class on the authorize method. This is useful when you have a controller that manages different types of models that share a common interface and you want to use the same policy for that kind of behaviour.

    opened by pcriv 48
  • Namespaced policies

    Namespaced policies

    The app I'm working on has two sections: user and admin. There are multiple types of users and multiple types of admins, and different authentication solutions are used for each of them.

    Although all the application models are used by both admins and users, policies are very different. Handling both users and admins and all distinct types within single policy was not so clear and full of ifs.

    The way I handled this was to dynamically define policy_class on every ActiveRecord descendant in before filter. Something like this:

    def set_policy_namespace
      m = self.class.name.split("::").first
      active_record_descendants.each do |ar|
        ar.class.instance_eval do
          define_method(:policy_class) do
            "::Policies::#{m}::#{self.name}Policy"
          end
        end
      end
    end
    
    def active_record_descendants
      Rails.application.eager_load! unless Rails.application.config.cache_classes
      ActiveRecord::Base.descendants
    end
    

    So, if I'm in Admin::HomeController, Project model will have policy_class redefined to return ::Policies::Admin::ProjectPolicy.

    I've tried to demonstrate valid use case for this. Do you have any thoughts on how can this be made easier? I'm not sure whether or not it should be a part of gem (since it is somewhat rare).. I'm just asking for opinion.

    thanks

    opened by assembler 44
  • Authorizing controller actions with no associated model

    Authorizing controller actions with no associated model

    Pundit looks great for authorizing models but in some instances I need to authorize controller actions (with no associated model) as well. A simple check whether the user is logged in or not will suffice most of the time. However, sometimes the ability to view a controller action is based on a user role, i.e. user can view settings page if administrator but not if regular user.

    What are the best practices for these situations? Any clarifications would be appreciated on how all the authorization pieces fit together.

    opened by pbougie 31
  • Unable to access namespaced policy in view

    Unable to access namespaced policy in view

    Given a TagsController and an Admin::TagsController each with a corresponding policy, how does one access the admin policy from a view? I've got a global nav link I'd like to only expose to admins.

    policy(Tag).index? references the non-Admin namespace policy

    I assumed something like this would work: policy(Admin::Tag).index? However, Admin::Tag is not defined and thus, it throws an error.

    Ideally, I'd do something like so: policy(Tag, namespace: 'admin').index?

    opened by jrmehle 30
  • Add #query and #record properties to Pundit::NotAuthorizedError.

    Add #query and #record properties to Pundit::NotAuthorizedError.

    I don't see why Pundit::NotAuthorizedError doesn't provide the user with the query or record that "caused" the error to be raised. This PR adds these two properties.

    I think using the NotAuthorizedError could resolve the still-open question of handling custom error messages (see #68, #38, and #66 for earlier discussion). With this change, I as a user of this gem could choose to (but am not obligated to) use the query and record property to create my error message:

    class ApplicationController < ActionController::Base
      rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
    
      def user_not_authorized(exception)
        record_name = exception.record.class.to_s.downcase
        flash[:error] = I18n.t "pundit.#{record_name}.#{exception.query}"
      end
    end
    
    en:
      pundit:
        post:
          update?: 'You cannot edit this post!'
    

    I think one of the strong points about Pundit is that it is extremely transparent -- there's almost nothing magical about it. The fact that there's little consensus about how errors should be handled is a sign that we can't predict what developers will expect Pundit to do w.r.t. errors. Rather than surprise them, why not let developers choose how to handle errors on their own?

    Let me know what you think!

    opened by ucarion 28
  • Allow authorize to pass arguments to policy methods.

    Allow authorize to pass arguments to policy methods.

    authorize currently assumes that each policy has enough information to make a decision. This isn't always the case as described in the policy below.

    class ProjectPolicy
      # Determine if user is allowed to remove other_user from the project.
      #
      # @return [Boolean]
      def remove_user?(other_user)
        user != other_user && project.owned_by?(user)
      end
    end
    

    authorize has been patched to send any extra arguments to the policy which makes the above possible.

    class ProjectsController < ApplicationController
      include Pundit
    
       # This isn't RESTful but shows intent.
      def remove_user
        authorize project, :remove_user?, other_user
    
        project.remove_user other_user
        respond_with project
      end
    
       private
    
      def project
        @project ||= current_user.find_project params[:id]
      end
    
      def other_user
        @other_user ||= project.find_user params[:user_id]
      end
    end
    
    opened by coop 24
  • Issue with new namespace finding capabilities (using an array)

    Issue with new namespace finding capabilities (using an array)

    When calling various helper methods in Pundit e.g. policy([:admin, @user]).show?, the full array is being passed to the policy object, rather than just @user. Is this expected functionality? or should my policy objects be grabbing the last object in the array to use as the record in the policy.

    bug 
    opened by zach-taylor 23
  • Conversation about authorization, business logic, and custom error messages

    Conversation about authorization, business logic, and custom error messages

    Have been having an interesting conversation with some colleagues today and was wondering where you folks stand on it.

    The question is: where do you draw your line between authorization and business logic/context? Do you use Pundit purely do define who can do what based on their role, or do you bring your deeper contexts into the picture?

    Here's a plain english version of the scenario I'm dealing with:

    Our system has a User model, a Post model, and an Event model. Users have roles ("admin" and "user").

    • Post belongs_to Event, but can also stand alone.
    • Any user of any role can create Posts not associated with an Event.
    • Admin users can create Posts for any Event.
    • Regular users can only create Posts for Events that haven't passed (if they try to create an Event for a past Post, they should get a custom error message).

    Option 1 Put everything in a Pundit Policy

    If I were to do this all in Pundit, the PostPolicy and controller would look something like this:

    class PostPolicy
    
      def create?
        user.admin? || record.event_id.nil? || record.event.ends_at > Time.current
      end
    
    end
    
    class PostsController < ApplicationController
    
      before_action :set_post
    
      def create
        authorize @post
        if @post.save...
      end
    
      private
    
      def set_post
        @post = current_user.posts.build(post_params)
      end
    
    end
    

    This works... with caveats:

    1. Pro: the policy could be used within a view to fully represent the rules around creating a Post.
    2. Grey area: We're getting into tertiary data by looking at the record's Event association. This is a slippery slope.
    3. Con: We don't have the ability to return a custom error message in the case of an expired Event (do we?). If authorization fails for whatever reason, you can only generate a singular error message based off the exception on PostPolicy#create.

    Option 2 Create multiple policies

    class PostPolicy
    
    def create?
      record.event_id.nil? || record.event.ends_at > Time.current
    end
    
    class AdminPostPolicy
    
    def create?
      user.admin?
    end
    

    Assuming we could split policies cleanly along role lines, we might be able to do something like this, which opens up slightly more messaging options. Uncertain at this time, so this is the least fleshed out example.

    Option 3 Use Pundit purely for role checking (signed in, can create Posts) and put business logic in controllers

    class PostPolicy < ApplicationPolicy
      # ApplicationPolicy ensures user is logged in
    end
    
    class PostsController < ApplicationController
    
      before_action :set_post
      before_action :set_event
    
      def create
        authorize @post
        if @post.save...
      end
    
      private
    
      def set_post
        @post = current_user.posts.build(post_params)
      end
    
      def set_event
        return unless params[:post][:event_id].present?
        @post.event = Event.find(params[:post][:event_id])
        render_errors("custom message") unless current_user.admin? || @post.event.ends_at > Time.current
      end
    

    It gets the job done, and can be different if we have a PostsController under /admin, but can end up fairly wet. Also, we lose the singular authority on when a user can create a post if we need the same total logic in the view.

    Option 4 Use Pundit purely for role checking (signed in, can create Posts) and put business logic in service objects

    class PostPolicy < ApplicationPolicy
      # ApplicationPolicy ensures user is logged in
    end
    
    class PostsController < ApplicationController
    
      def create
        post_creator = PostCreator.new(post_params, current_user)
        authorize post_creator.post, :create?
        if post_creator.save
          render post_creator.post
        else
          render_errors(post_creator.errors)
        end
      end
    
    class PostCreator
    
      attr_accessor :post, :errors, :current_user
    
      def initialize(params, current_user)
        self.current_user = current_user
        self.post = current_user.posts.build(params)
      end
    
      def save
        if current_user.admin? || post.event_id.nil? || post.event.ends_at > Time.current
          post.save
        else
          self.errors = "custom message"
          false
        end
      end
    end
    

    Cleaner than controllers, but again you've split out your authority into two places. Also, I originally had the pundit authorize call within the service object but don't actually know if that's possible.

    Another option would be to pass in the current_user context to the model and run the event check as a validation, but that is exceedingly gross, and I find my models are getting thinner and thinner these days (basically just wrappers around the table with rudimentary validation).

    Feedback appreciated. Cheers.

    feature request 
    opened by uberllama 23
  • Support custom arguments for policy

    Support custom arguments for policy

    I would to allow only customer's owner to manage profiles (the customer is given by the controller):

    class ProfilePolicy
      def initialize(user, customer, record)
        @user = user
        @customer = customer
        @record = record
      end
    
      def create?
        @customer.owner == @user
      end
    end
    

    What I'm currently doing is:

    class ApplicationController
      def policy(record)
        policy = PolicyFinder.new(record).policy
        policy.new(current_user, current_customer, record) if policy
      end
    end
    

    If that's correctly, we should add a section on README explaining that.

    opened by sobrinho 21
  • We're working on getting back on track

    We're working on getting back on track

    Dear pundit-community,

    Sorry for the neglect as of lately! We're working on getting up to speed with current issues and pull requests.

    Sincerely,

    ~ The @varvet team

    opened by ingemar 18
  • Support authorization error flash messages when using turbo frames and streams?

    Support authorization error flash messages when using turbo frames and streams?

    In a perfect world I would like to do something like this:

      def user_not_authorized
        respond_to do |format|
          format.html do
            flash[:alert] = "You are not authorized to perform this action."
            redirect_back(fallback_location: root_path)
          end
          format.turbo_stream do
            flash.now[:alert] = "You are not authorized to perform this action."
            turbo_stream.prepend "flash", partial: "shared/flash"
          end
        end
      end
    

    And then if someone tried to perform an action they can't do, they would get a flash message on their current page / frame without a redirect.

    With the above code using Pundit v2.2.0, on authorization failure no alert message gets shown and no redirect happens. The request gets executed as the html format which I verified by printing a message to the terminal in that block.

    As is Pundit doesnโ€™t send the request as a turbo_stream so that format never gets a chance to execute. Is there a current workaround or official plans to support Hotwire Turbo Frames and Steams given it's a Rails 7 default?

    Thanks!

    opened by nickjj 0
  • Policy Finder `find` does not strip namespace.

    Policy Finder `find` does not strip namespace.

    This seems to be brought up before, most closely in #697.

    Background

    • I'm using Pundit via the Pundit Adapter in Active Admin.
    • My policies are namespaced under active_admin, resulting in policy classes like ActiveAdmin::BookPolicy.
    • I am using STI on the Book class, with types like RedBook and BlueBook. Both these classes have the self.policy_class defined to point back to the BookPolicy.

    The Pundit adapter uses the namespace method and sends an array with the namespace and a resource to Pundit - that eventually ends up calling PolicyFinder::find method with the array.

    Expected Behaviour

    I should be able to share policies between various "STI siblings", even if the policy is namespaced.

    Actual Behaviour

    This is not possible, at least when Pundit is abstracted through the Pundit Adapter.

    Issue

    When attempting to authorize an action on RedBook, I've tried these three scenarios:

    1. When self.policy_class is not defined - Pundit looks for ActiveAdmin::RedBookPolicy and fails [EXPECTED]
    2. When self.policy_class returns BookPolicy - rails says this class is not found [EXPECTED]
    3. When self.policy_class returns ActiveAdmin::BookPolicy - the find method does not strip the namespace, ending up with ActiveAdmin::ActiveAdmin::BookPolicy.

    So right now I'm not able to share the policy at all - I'd expect the third case to go through.

    More Info

    I'm not able to override methods because of the adapter abstraction. This seems to have been the solution most people used in the other issues. I think find should be able to understand whether this statement is actually required before executing it-

         if subject.is_a?(Array)
            modules = subject.dup
            last = modules.pop
            context = modules.map { |x| find_class_name(x) }.join("::")
            [context, find(last)].join("::") #this line might need to be conditional?
    

    I'm aware that I can create more policies for each type of Book, but my goal is to have a single policy that governs all Books.

    opened by arvind0598 1
  • Policy_scope: Don't alter query structure

    Policy_scope: Don't alter query structure

    Explanation: https://github.com/varvet/pundit/issues/748

    Edit: Just noticed the build failed. Likely because Pundit is not specific to ActiveRecord. Leaving it there just in case you have a better implementation in mind.

    opened by jamesst20 0
  • [Request] policy_scope should not alter joined table structure

    [Request] policy_scope should not alter joined table structure

    Hi,

    I believe there is a very small improvement that could be implemented in Pundit's policy_scope.

    Exemple of problem

      class Scope < ::ApplicationPolicy::Scope
        def resolve 
         # I know the joins are being useless, it's only for demo
          scope.left_joins(:user, project: :project_managers).where(user: { id: 9 })
        end
      end
    
    policy_scope(ProjectTime).where(date: @date).sum(:total)
    => 8.28111111111111
    
    policy_scope(ProjectTime).distinct.where(date: @date).sum(:total)
    => 6.519722222222221
    

    Problem

    Some results in the SQL query are duplicated because of the joined table.

    Solution

    An easy way to come around this issue is to wrap the policy in a subquery like so

    class Scope < ::ApplicationPolicy::Scope
        def resolve 
          scoped = scope.left_joins(:user, project: :project_managers).where(user: { id: 9 })
          scope.where(id: scoped)
        end
      end
    
    policy_scope(ProjectTime).where(date: @date).sum(:total)
    => 6.519722222222221
    
    policy_scope(ProjectTime).distinct.where(date: @date).sum(:total)
    => 6.519722222222221
    

    Request

    I believe this behavior could be wrapped into the gem and applied automatically. I believe it also make sense that our policies only take care of filtering stuff without leaving any side effect.

    opened by jamesst20 1
  • Split this into two methods?

    Split this into two methods?

    It would be nice if this method either returned the policy class checked, or if there was some way to determine this without re-implementing the policy lookup logic.

    https://github.com/varvet/pundit/blob/56af8f3ce25bf1eea47ec877f840b3de6fb32cdc/lib/pundit.rb#L76

    One way would be to split this into two methods:

    • Map incoming arguments to a policy.
    • Raise an exception if the policy isn't satisified.

    Would you accept a PR for this?

    opened by ioquatix 0
Owner
Varvet
Varvet
Implements authentication and authorization as FastAPI dependencies

FastAPI Security Implements authentication and authorization as dependencies in FastAPI. Features Authentication via JWT-based OAuth 2 access tokens a

Jacob Magnusson 111 Jan 7, 2023
Customizable User Authorization & User Management: Register, Confirm, Login, Change username/password, Forgot password and more.

Flask-User v1.0 Attention: Flask-User v1.0 is a Production/Stable version. The previous version is Flask-User v0.6. User Authentication and Management

Ling Thio 916 Feb 15, 2021
Simple yet powerful authorization / authentication client library for Python web applications.

Authomatic Authomatic is a framework agnostic library for Python web applications with a minimalistic but powerful interface which simplifies authenti

null 1k Dec 28, 2022
Awesome Django authorization, without the database

rules rules is a tiny but powerful app providing object-level permissions to Django, without requiring a database. At its core, it is a generic framew

null 1.6k Dec 30, 2022
Simple yet powerful authorization / authentication client library for Python web applications.

Authomatic Authomatic is a framework agnostic library for Python web applications with a minimalistic but powerful interface which simplifies authenti

null 962 Feb 4, 2021
Simple yet powerful authorization / authentication client library for Python web applications.

Authomatic Authomatic is a framework agnostic library for Python web applications with a minimalistic but powerful interface which simplifies authenti

null 962 Feb 19, 2021
Phishing Abusing Microsoft 365 OAuth Authorization Flow

Microsoft365_devicePhish Abusing Microsoft 365 OAuth Authorization Flow for Phishing Attack This is a simple proof-of-concept script that allows an at

bigb0ss 11 Dec 11, 2022
Abusing Microsoft 365 OAuth Authorization Flow for Phishing Attack

Microsoft365_devicePhish Abusing Microsoft 365 OAuth Authorization Flow for Phishing Attack This is a simple proof-of-concept script that allows an at

Optiv Security 76 Jan 2, 2023
A Python package, that allows you to acquire your RecNet authorization bearer token with your account credentials!

RecNet-Login This is a Python package, that allows you to acquire your RecNet bearer token with your account credentials! Installation Done via git: p

Jesse 6 Aug 18, 2022
Some scripts to utilise device code authorization for phishing.

OAuth Device Code Authorization Phishing Some scripts to utilise device code authorization for phishing. High level overview as per the instructions a

Daniel Underhay 6 Oct 3, 2022
A wagtail plugin to replace the login by an OAuth2.0 Authorization Server

Wagtail OAuth2.0 Login Plugin to replace Wagtail default login by an OAuth2.0 Authorization Server. What is wagtail-oauth2 OAuth2.0 is an authorizatio

Gandi 7 Oct 7, 2022
Skit-auth - Authorization for skit.ai's platform

skit-auth This is a simple authentication library for Skit's platform. Provides

Skit 3 Jan 8, 2022
Plotly Dash plugin to allow authentication through 3rd party OAuth providers.

dash-auth-external Integrate your dashboards with 3rd parties and external OAuth providers. Overview Do you want to build a Plotly Dash app which pull

James Holcombe 15 Dec 11, 2022
This app makes it extremely easy to build Django powered SPA's (Single Page App) or Mobile apps exposing all registration and authentication related functionality as CBV's (Class Base View) and REST (JSON)

Welcome to django-rest-auth Repository is unmaintained at the moment (on pause). More info can be found on this issue page: https://github.com/Tivix/d

Tivix 2.4k Jan 3, 2023
A host-guest based app in which host can CREATE the room. and guest can join room with room code and vote for song to skip. User is authenticated using Spotify API

A host-guest based app in which host can CREATE the room. and guest can join room with room code and vote for song to skip. User is authenticated using Spotify API

Aman Raj 5 May 10, 2022
Easy and secure implementation of Azure AD for your FastAPI APIs ๐Ÿ”’ Single- and multi-tenant support.

Easy and secure implementation of Azure AD for your FastAPI APIs ?? Single- and multi-tenant support.

Intility 220 Jan 5, 2023
A full Rest-API With Oauth2 and JWT for request & response a JSON file Using FastAPI and SQLAlchemy ๐Ÿ”‘

Pexon-Rest-API A full Rest-API for request & response a JSON file, Building a Simple WorkFlow that help you to Request a JSON File Format and Handling

Yasser Tahiri 15 Jul 22, 2022
Two factor authentication system using azure services and python language and its api's

FUTURE READY TALENT VIRTUAL INTERSHIP PROJECT PROJECT NAME - TWO FACTOR AUTHENTICATION SYSTEM Resources used: * Azure functions(python)

BHUSHAN SATISH DESHMUKH 1 Dec 10, 2021
Mock authentication API that acceccpts email and password and returns authentication result.

Mock authentication API that acceccpts email and password and returns authentication result.

Herman Shpryhau 1 Feb 11, 2022