Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/tomasc/mongoid_ability

Custom Ability class that allows CanCanCan authorization library store permissions in MongoDB via the Mongoid gem.
https://github.com/tomasc/mongoid_ability

authorization cancancan mongoid permissions

Last synced: 2 months ago
JSON representation

Custom Ability class that allows CanCanCan authorization library store permissions in MongoDB via the Mongoid gem.

Awesome Lists containing this project

README

        

# Mongoid Ability

[![Build Status](https://travis-ci.org/tomasc/mongoid_ability.svg)](https://travis-ci.org/tomasc/mongoid_ability) [![Gem Version](https://badge.fury.io/rb/mongoid_ability.svg)](http://badge.fury.io/rb/mongoid_ability) [![Coverage Status](https://img.shields.io/coveralls/tomasc/mongoid_ability.svg)](https://coveralls.io/r/tomasc/mongoid_ability)

Custom `Ability` class that allows [CanCanCan](https://github.com/CanCanCommunity/cancancan) authorization library store permissions in [MongoDB](http://www.mongodb.org) via the [Mongoid](https://github.com/mongoid/mongoid) gem.

## Installation

Add this line to your application's Gemfile:

```ruby
gem 'mongoid_ability'
```

And then execute:

```
$ bundle
```

Or install it yourself as:

```
$ gem install mongoid_ability
```

## Setup

The permissions are defined by a `Lock` that applies to a `Subject` and defines access for its owner – `User` and/or its `Role`.

### Lock

A `Lock` class can be any class that include `MongoidAbility::Lock`. There should be only one such class in an application.

```ruby
class MyLock
include Mongoid::Document
include MongoidAbility::Lock

embedded_in :owner, polymorphic: true
end
```

This class defines a permission itself using the following fields:

`:subject_type, type: String`
`:subject_id, type: Moped::BSON::ObjectId`
`:action, type: Symbol, default: :read`
`:outcome, type: Boolean, default: false`

These fields define what subject (respectively subject type, when referring to a class) the lock applies to, which action it is defined for (for example `:read`), and whether the outcome is positive or negative.

### Subject

All subjects (classes which permissions you want to control) will include the `MongoidAbility::Subject` module.

Each action and its default outcome needs to be defined using the `.default_lock` macro.

```ruby
class MySubject
include Mongoid::Document
include MongoidAbility::Subject

default_lock MyLock, :read, true
default_lock MyLock, :update, false
end
```

The subject classes can be subclassed. Subclasses inherit the default locks (unless they override them), the resulting outcome being correctly calculated bottom-up the superclass chain.

Additionally the locks can be converted to Mongoid criteria:

```ruby
MySubject.accessible_by(ability, :read)
```

### Owner

This `Ability` class supports two levels of inheritance (for example User and its Roles). The locks can be either embedded (via `.embeds_many`) or associated (via `.has_many`). Make sure to include the `as: :owner` option.

```ruby
class MyUser
include Mongoid::Document
include MongoidAbility::Owner

embeds_many :locks, class_name: 'MyLock', as: :owner
has_and_belongs_to_many :roles, class_name: 'MyRole'

# override if your relation is named differently
def self.locks_relation_name
:locks
end

# override if your relation is named differently
def self.inherit_from_relation_name
:roles
end
end
```

```ruby
class MyRole
include Mongoid::Document
include MongoidAbility::Owner

embeds_many :locks, class_name: 'MyLock', as: :owner
has_and_belongs_to_many :users, class_name: 'MyUser'
end
```

Both users and roles can be further subclassed.

The owner also gains the `#can?` and `#cannot?` methods, that are delegate to the user's ability. It is then easy to perform permission checks per user:

```ruby
current_user.can?(:read, resource, options)
other_user.can?(:read, ResourceClass, options)
```

Ability can be easily obtained as:

```ruby
current_user.ability
```

### Caching

The ability object is fully cache-able, which means it is possible to save some precious time on every request (instead of always converting the Lock documents to CanCan rules):

```ruby
class ActionController::Base
def current_ability
@current_ability ||= Rails.cache.fetch([current_user.cache_key, 'ability'].join('/')) do
MongoidAbility::Ability.new(current_user)
end.tap do |ability|
ability.owner ||= current_user
end
end
end
```

And on the owner:

```ruby
def ability
@ability ||= Rails.cache.fetch([cache_key, 'ability'].join('/')) do
MongoidAbility::Ability.new(self)
end.tap do |ability|
ability.owner ||= self
end
end
```

Of course this assumes the user's `cache_key` updates when any of its locks (or locks stored on its roles) change.

Note the owner has to be assigned after fetching the ability from cache.

### Decoration

To be able to check permissions on decorated objects (for example via the Draper gem) subclass the Ability class as follows:

```ruby
class MyAbility < MongoidAbility::Ability
def can?(action, subject, *extra_args)
while subject.is_a?(Draper::Decorator)
subject = subject.model
end

super(action, subject, *extra_args)
end
end
```

### CanCanCan

The default `:current_ability` defined by [CanCanCan](https://github.com/CanCanCommunity/cancancan) will be automatically overriden by the `Ability` class provided by this gem.

## Usage

1. Setup subject classes and their default locks.
2. Define permissions using lock objects embedded (or associated to) either in user or role.
3. Use standard [CanCanCan](https://github.com/CanCanCommunity/cancancan) helpers (`.authorize!`, `#can?`, `#cannot?`) to authorize the current user.

## Contributing

1. Fork it ( https://github.com/tomasc/mongoid_ability/fork )
2. Create your feature branch (`git checkout -b my-new-feature`)
3. Commit your changes (`git commit -am 'Add some feature'`)
4. Push to the branch (`git push origin my-new-feature`)
5. Create a new Pull Request