pragmarb/pragma-policy

View on GitHub
README.md

Summary

Maintainability
Test Coverage
# Pragma::Policy

[![Build Status](https://travis-ci.org/pragmarb/pragma-policy.svg?branch=master)](https://travis-ci.org/pragmarb/pragma-policy)
[![Coverage Status](https://coveralls.io/repos/github/pragmarb/pragma-policy/badge.svg?branch=master)](https://coveralls.io/github/pragmarb/pragma-policy?branch=master)
[![Maintainability](https://api.codeclimate.com/v1/badges/e51e8d7489eb72ab97ba/maintainability)](https://codeclimate.com/github/pragmarb/pragma-policy/maintainability)

Policies provide fine-grained access control for your API resources.

## Installation

Add this line to your application's Gemfile:

```ruby
gem 'pragma-policy'
```

And then execute:

```console
$ bundle
```

Or install it yourself as:

```console
$ gem install pragma-policy
```

## Usage

To create a policy, simply inherit from `Pragma::Policy::Base`:

```ruby
module API
  module V1
    module Article
      class Policy < Pragma::Policy::Base
      end
    end
  end
end
```

By default, the policy does not return any objects when scoping and forbids all operations.

You can start customizing your policy by defining a scope and operation predicates:

```ruby
module API
  module V1
    module Article
      class Policy < Pragma::Policy::Base
        class Scope < Pragma::Policy::Base::Scope
          def resolve
            scope.where('published = ? OR author_id = ?', true, user.id)
          end
        end

        def show?
          record.published? || record.author_id == user.id
        end

        def update?
          record.author_id == user.id
        end

        def destroy?
          record.author_id == user.id
        end
      end
    end
  end
end
```

You are ready to use your policy!

### Retrieving records

To retrieve all the records accessible by a user, use the `.accessible_by` class method:

```ruby
posts = API::V1::Article::Policy::Scope.new(user, Article.all).resolve
```

### Authorizing operations

To authorize an operation, first instantiate the policy, then use the predicate methods:

```ruby
policy = API::V1::Article::Policy.new(user, post)
fail 'You cannot update this post!' unless policy.update?
```

Since raising when the operation is forbidden is so common, we provide bang methods a shorthand
syntax. `Pragma::Policy::NotAuthorizedError` is raised if the predicate method returns `false`:

```ruby
policy = API::V1::Article::Policy.new(user, post)
policy.update! # raises if the user cannot update the post
```

### Reusing Pundit policies

If you already use [Pundit](https://github.com/varvet/pundit), there's no need to copy-paste
policies for your API. You can use `Pragma::Policy::Pundit` to delegate to your existing policies
and scopes:

```ruby
module API
  module V1
    module Article
      class Policy < Pragma::Pundit::Policy
        # This is optional: the inferred default would be ArticlePolicy.
        self.pundit_klass = CustomArticlePolicy
      end
    end
  end
end
```

Note that you can still override specific methods if you want, and we'll keep delegating the rest
to Pundit:

```ruby
module API
  module V1
    module Article
      class Policy < Pragma::Pundit::Policy
        def create?
          # Your custom create policy here
        end
      end
    end
  end
end
```

### Passing additional context

If you want to pass additional context to the policy, just pass it instead of the user object.
Pragma::Policy never uses your context in any way, so you can pass whatever you want:

```ruby
policy = API::V1::Article::Policy.new(OpenStruct.new(ip: request.remote_ip, user: user), post)
policy.update!
```

In your policy, you can use `#context` as an alias for `#user` for convenience:

```ruby
module API
  module V1
    module Article
      class Policy < Pragma::Pundit::Policy
        def update?
          record.author_id == context.user.id || context.ip == '127.0.0.1'
        end
      end
    end
  end
end
```

If you are using [pragma-rails](https://github.com/pragmarb/pragma-rails), you may change the
context passed to the policy by defining a `#policy_context` method on your controller. This way you
are not forced to override `#current_user` or `#pragma_user`:

```ruby
module API
  module V1
    class PostsController < ApplicationController
      # ...

      private

      def policy_context
        OpenStruct.new(ip: request.remote_ip, user: current_user)
      end
    end
  end
end
```

## Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/pragmarb/pragma-policy.

## License

The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).