rails-action-authorization 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: a8d8a18e02c7cba715ff6d0369b2bc1463545d45062e58408ec8384d1dcbd316
4
+ data.tar.gz: 04ecdf2bae106d9cee8d4601f8693a7634a311a5cd12b905631954ed7e4d2be4
5
+ SHA512:
6
+ metadata.gz: 81053e97cbf237ac0cf489a370d2f3c13b057838c99fecbed411e814e7d884d2f18faf73a8cd2705e2e39fa1d5bcb8555f7626dbabe5933b3fc0a8e878912709
7
+ data.tar.gz: 6d02f17442c5276e29de19ce8d6ba4599841847ee52b39660cdadafdbc96400801878daa4b02ca842021d3c6c6be8e0fc6cd9d351d5025b3fd5f27653e8ba41a
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2020 Andrew Luchuk
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,145 @@
1
+ [![Build Status](https://travis-ci.org/speratus/rails-action-authorization.svg?branch=master)](https://travis-ci.org/speratus/rails-action-authorization)
2
+ [![codecov](https://codecov.io/gh/speratus/rails-action-authorization/branch/master/graph/badge.svg)](https://codecov.io/gh/speratus/rails-action-authorization)
3
+ [![Maintainability](https://api.codeclimate.com/v1/badges/588fbef9d8a7c39bb071/maintainability)](https://codeclimate.com/github/speratus/authorizer/maintainability)
4
+
5
+ # Rails Action Authorization
6
+ Rails Action Authorization is a rails plugin that gives developers a lightweight authorization framework.
7
+
8
+ While there are lots of rails plugins designed to do authorization, Rails Action Authorization strives to
9
+ be the most intuitive while simultaneously allowing developers to write the smallest possible amount of code to have a functioning authorization system.
10
+
11
+ ## Usage
12
+ There are two parts to using `rails-action-authorization`. The first part is defining rules.
13
+ Rules are defined on models, and they specify how actions determine how to authorize users (or other models).
14
+
15
+ The second parrt of using `rails-action-authorization` is on the controller side. In any action in which authorization is required, run the `check_authorization` method.
16
+
17
+ ### Step One, defining rules
18
+ Suppose we are writing the proverbial blogging application, and we need some way to determine whether a user is permitted to edit a post.
19
+
20
+ The first step is to define a rule for the action that will need authorization. In our case, we'll need authorization for the `edit` and `update` actions. To define a rule, we'll have to edit our `Post` model.
21
+ ```ruby
22
+ # models/Post.rb
23
+
24
+ class Post < ApplicationRecord
25
+ ...
26
+ end
27
+ ```
28
+
29
+ Rules are defined using the `define_rule` method. `define_rule` takes at least one argument that is a string or a symbol that represents the action that needs authorization in the format `controller#action`. In our case, we need to authorize the `edit` and `update` actions like so:
30
+ ```ruby
31
+ # models/Post.rb
32
+ class Post < ApplicationRecord
33
+ ...
34
+ define_rule 'posts#edit', 'posts#update' do |post, user|
35
+ # Authorization code here
36
+ end
37
+ end
38
+ ```
39
+
40
+ `define_rule` can take as many arguments as desired, enabling developers to define authorization rules for multiple actions simultaneously.
41
+
42
+ The block must return `true` or `false`, `true` if the user is permitted to edit the post
43
+ and `false` if the user is not permitted. The block itself will always yield the resource
44
+ that is being authorized as the first argument and the actor requesting authorization as the second argument. The first argument should always be the type as the class in which `define_rule` is called (if you ever encounter a case where it is not, then please report it as a bug). The second argument could technically be any model, but will probably most often be a `User` instance.
45
+
46
+ Since we only want a post's author to be able to edit a post, we'll define our rule as follows:
47
+ ```ruby
48
+ # models/Post.rb
49
+ class Post < ApplicationRecord
50
+ ...
51
+ define_rule 'posts#edit', 'posts#update' do |post, user|
52
+ post.author == user
53
+ end
54
+ end
55
+ ```
56
+
57
+ Next, we have to check the authorization of the user in each controller. The only thing required
58
+ to do this is to invoke `check_authorization` somewhere in each action that requries authorization.
59
+ ```ruby
60
+ # controllers/posts_controller.rb
61
+ class PostsController < ApplicationController
62
+ before_action :authorize_user, only: [:edit, :update]
63
+
64
+ ...
65
+
66
+ def edit
67
+ ...
68
+ #You will be able to reference @post in your views as usuals.
69
+ end
70
+
71
+ def update
72
+ ...
73
+ end
74
+
75
+ private
76
+
77
+ def authorize_user
78
+ @post = check_authorization(Post.find_by(id: params[:id]), current_user) # Here, use your own method for getting the current user.
79
+
80
+ end
81
+ end
82
+ ```
83
+ Notice that we are able to call check_authorization in a `before_action`. This is because `check_authorization`
84
+ knows how to identify the action it is called in without requiring developers to specify it themselves.
85
+
86
+ This will work great when the owner tries to edit his own posts, but if another user attempts to edit
87
+ a post, the server will raise an error. `rails-action-authorization` raises a `ForbiddenError` if a
88
+ user fails to be authorized in order to eliminate potential ambiguity of returning nil or some other
89
+ value.
90
+
91
+ In order to handle authorization failures, we'll have to adapt our controller slightly:
92
+ ```ruby
93
+ # controllers/posts_controller.rb
94
+ class PostsController < ApplicationController
95
+ before_action :authorize_user, only: [:edit, :update]
96
+ rescue_from ActionAuthorization::ForbiddenError, with:
97
+ :handle_forbidden
98
+
99
+ ...
100
+
101
+ def edit
102
+ ...
103
+ #You will be able to reference @post in your views as usuals.
104
+ end
105
+
106
+ def update
107
+ ...
108
+ end
109
+
110
+ private
111
+
112
+ def authorize_user
113
+ @post = check_authorization(Post.find_by(id: params[:id]), current_user) # Here, use your own method for getting the current user.
114
+ end
115
+
116
+ def handle_forbidden
117
+ render '403', status: 403
118
+ end
119
+ end
120
+ ```
121
+ This will cause the execution of any action to cease immediately when a user fails to authorize, and
122
+ instead run the code in `handle_forbidden`.
123
+
124
+ Of course, the above example assumes that you have a template for a `403` error, but you can run
125
+ whatever code is necessary in your case.
126
+
127
+ ## Installation
128
+ Add this line to your application's Gemfile:
129
+
130
+ ```ruby
131
+ gem 'rails-action-authorization'
132
+ ```
133
+
134
+ And then execute:
135
+ ```bash
136
+ $ bundle
137
+ ```
138
+
139
+ Or install it yourself as:
140
+ ```bash
141
+ $ gem install rails-action-authorization
142
+ ```
143
+
144
+ ## License
145
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,27 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'Authorizer'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.md')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+ require 'bundler/gem_tasks'
18
+
19
+ require 'rake/testtask'
20
+
21
+ Rake::TestTask.new(:test) do |t|
22
+ t.libs << 'test'
23
+ t.pattern = 'test/**/*_test.rb'
24
+ t.verbose = false
25
+ end
26
+
27
+ task default: :test
data/lib/authorizer.rb ADDED
@@ -0,0 +1,5 @@
1
+ require "authorizer/railtie"
2
+
3
+ module ActionAuthorization
4
+ # Your code goes here...
5
+ end
@@ -0,0 +1,21 @@
1
+ module ActionAuthorization
2
+ OPTIONS = [:authorize_associated, :behavior]
3
+
4
+ POSSIBILITIES = [:allow_all, :deny_all, :filter]
5
+
6
+ class ActionController::Metal
7
+ def check_authorization(resource, authorizee, **options)
8
+ action = "#{params[:controller]}##{action_name}"
9
+
10
+ if resource.respond_to?(:length)
11
+ return resource if resource.length == 0
12
+ r = Resource.new(action, authorizee, *resource, **options)
13
+ result = r.get
14
+ else
15
+ result = resource.is_authorized(action, authorizee)
16
+ end
17
+
18
+ result
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,39 @@
1
+ module ActionAuthorization
2
+ class ActiveRecord::Base
3
+ def self.get_perms
4
+ unless (self.class_variables.include?(:'@@perms'))
5
+ @@perms = {}
6
+ end
7
+ init_fallback_rule
8
+ return @@perms
9
+ end
10
+
11
+ def self.init_fallback_rule
12
+ @@fallback_rule = nil unless (self.class_variable_defined?(:@@fallback_rule))
13
+ end
14
+
15
+ def self.define_rule(*names, &block)
16
+ perms = self.get_perms
17
+ names.each {|name| perms[name.to_sym] = block}
18
+ end
19
+
20
+ def self.set_fallback_rule(&rule)
21
+ @@fallback_rule = rule
22
+ end
23
+
24
+ def is_authorized(action, authorizee)
25
+ symbol = action.to_sym
26
+ perms = self.class.get_perms
27
+
28
+ authorized = false
29
+ authorized = perms[symbol].(self, authorizee) if perms[symbol]
30
+ authorized = @@fallback_rule.(self, authorizee) if @@fallback_rule && !perms[symbol]
31
+
32
+ raise ForbiddenError.new(
33
+ "Actor #{authorizee} is not authorized to perform action #{action} on resource #{self}."
34
+ ) unless authorized
35
+
36
+ self
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,4 @@
1
+ require_relative './exceptions'
2
+ require_relative './active_record_patch'
3
+ require_relative './action_controller_patch'
4
+ require_relative './resource'
@@ -0,0 +1,9 @@
1
+ module ActionAuthorization
2
+ class AuthorizationError < ::StandardError
3
+
4
+ end
5
+
6
+ class ForbiddenError < AuthorizationError
7
+
8
+ end
9
+ end
@@ -0,0 +1,8 @@
1
+ require_relative './authorizer.rb'
2
+
3
+ module Authorizer
4
+ class Railtie < ::Rails::Railtie
5
+ # ActiveRecord::Base.include Authorizer::ModelClassMethods
6
+ # ActionController::API.include Authorizer::ControllerMethods
7
+ end
8
+ end
@@ -0,0 +1,49 @@
1
+ module ActionAuthorization
2
+ class Resource
3
+ attr_reader :action, :actor, :resources, :options
4
+
5
+ def initialize(action, actor, *resources, **options)
6
+ @action = action
7
+ @actor = actor
8
+ @resources = resources
9
+ @options = options
10
+ end
11
+
12
+ def get
13
+ return @resources if @resources.nil?
14
+ return @resources if @resources.length == 0
15
+
16
+ behavior = @options[:behavior]
17
+ if !behavior
18
+ behavior = :filter
19
+ end
20
+
21
+ case behavior
22
+ when :allow_all
23
+ collect_permitted(return_res: true) {|results| results.length > 0}
24
+ when :deny_all
25
+ collect_permitted {|results| results.length == @resources.length}
26
+ when :filter
27
+ collect_permitted {|results| results.length > 0}
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def collect_permitted(return_res: false)
34
+ results = @resources.filter do |r|
35
+ begin
36
+ r.is_authorized(@action, @actor) != nil
37
+ rescue
38
+ false
39
+ end
40
+ end
41
+
42
+ unless yield(results)
43
+ raise ForbiddenError
44
+ end
45
+ return @resources if return_res
46
+ results
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,3 @@
1
+ module ActionAuthorization
2
+ VERSION = '1.0.0'
3
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :authorizer do
3
+ # # Task goes here
4
+ # end
metadata ADDED
@@ -0,0 +1,108 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rails-action-authorization
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Andrew Luchuk
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-04-01 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 6.0.2
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 6.0.2.1
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: 6.0.2
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 6.0.2.1
33
+ - !ruby/object:Gem::Dependency
34
+ name: sqlite3
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ type: :development
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ - !ruby/object:Gem::Dependency
48
+ name: codecov
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ description: Rails Action Authorization adds an authorization framework for controller
62
+ actions. Rails Action Authorization is designed to be extremely lightweight and
63
+ flexible, enabling developers to spend less time trying to build complex authorization
64
+ systems. It's unopinionated design makes it easy to define any kind of authorization
65
+ rules with minimal effort.
66
+ email:
67
+ - andrew.luchuk@outlook.com
68
+ executables: []
69
+ extensions: []
70
+ extra_rdoc_files: []
71
+ files:
72
+ - MIT-LICENSE
73
+ - README.md
74
+ - Rakefile
75
+ - lib/authorizer.rb
76
+ - lib/authorizer/action_controller_patch.rb
77
+ - lib/authorizer/active_record_patch.rb
78
+ - lib/authorizer/authorizer.rb
79
+ - lib/authorizer/exceptions.rb
80
+ - lib/authorizer/railtie.rb
81
+ - lib/authorizer/resource.rb
82
+ - lib/authorizer/version.rb
83
+ - lib/tasks/authorizer_tasks.rake
84
+ homepage: https://github.com/speratus/rails-action-authorization
85
+ licenses:
86
+ - MIT
87
+ metadata: {}
88
+ post_install_message:
89
+ rdoc_options: []
90
+ require_paths:
91
+ - lib
92
+ required_ruby_version: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ required_rubygems_version: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - ">="
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ requirements: []
103
+ rubygems_version: 3.0.6
104
+ signing_key:
105
+ specification_version: 4
106
+ summary: Rails Action Authorization adds an authorization framework for controller
107
+ actions.
108
+ test_files: []