chronos_authz 0.0.1
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 +7 -0
- data/.gitignore +1 -0
- data/.rspec +2 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +104 -0
- data/README.md +107 -0
- data/Rakefile +6 -0
- data/chronos_authz.gemspec +28 -0
- data/lib/chronos_authz.rb +16 -0
- data/lib/chronos_authz/acl.rb +81 -0
- data/lib/chronos_authz/authorizer.rb +39 -0
- data/lib/chronos_authz/configuration.rb +7 -0
- data/lib/chronos_authz/rule.rb +20 -0
- data/lib/chronos_authz/user.rb +13 -0
- data/lib/chronos_authz/validations/options_validator.rb +60 -0
- data/lib/chronos_authz/validations/validation_error.rb +11 -0
- data/lib/chronos_authz/version.rb +3 -0
- data/spec/acl_spec.rb +186 -0
- data/spec/authorizer_spec.rb +96 -0
- data/spec/config/authorizer_acl_test.yml +18 -0
- data/spec/config/error_page.html +1 -0
- data/spec/helpers/custom_rule.rb +9 -0
- data/spec/options_validator_spec.rb +85 -0
- data/spec/spec_helper.rb +115 -0
- metadata +179 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: dc17a6fb18f79cbc7f42761ead6902e96c09d3b3
|
4
|
+
data.tar.gz: 57e17def29d8341147bb81ef427055384914e1f2
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 53887e05e23adf136dff4856aa8bccec32815e36fde7add0012456db29b1a663a6e56c283f61d64298d634d7be42d4ab05a51530dcd795fda839a842ff91df1b
|
7
|
+
data.tar.gz: 92ccdfc2374509a8fd33132c7dbff4f188925b891f7143c8322e3d179f4660eba2d2336e06f6627b81fd718f198c3a6c66a3d3ce92d50089f4e07c47fdb1d4f9
|
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
.idea/
|
data/.rspec
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
chronos-authz (0.0.1)
|
5
|
+
activesupport
|
6
|
+
railties (>= 4.2)
|
7
|
+
request_store
|
8
|
+
|
9
|
+
GEM
|
10
|
+
remote: https://rubygems.org/
|
11
|
+
specs:
|
12
|
+
actionpack (5.2.0)
|
13
|
+
actionview (= 5.2.0)
|
14
|
+
activesupport (= 5.2.0)
|
15
|
+
rack (~> 2.0)
|
16
|
+
rack-test (>= 0.6.3)
|
17
|
+
rails-dom-testing (~> 2.0)
|
18
|
+
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
19
|
+
actionview (5.2.0)
|
20
|
+
activesupport (= 5.2.0)
|
21
|
+
builder (~> 3.1)
|
22
|
+
erubi (~> 1.4)
|
23
|
+
rails-dom-testing (~> 2.0)
|
24
|
+
rails-html-sanitizer (~> 1.0, >= 1.0.3)
|
25
|
+
activesupport (5.2.0)
|
26
|
+
concurrent-ruby (~> 1.0, >= 1.0.2)
|
27
|
+
i18n (>= 0.7, < 2)
|
28
|
+
minitest (~> 5.1)
|
29
|
+
tzinfo (~> 1.1)
|
30
|
+
ansi (1.5.0)
|
31
|
+
builder (3.2.3)
|
32
|
+
concurrent-ruby (1.0.5)
|
33
|
+
crass (1.0.4)
|
34
|
+
diff-lcs (1.3)
|
35
|
+
docile (1.3.1)
|
36
|
+
erubi (1.7.1)
|
37
|
+
hirb (0.7.3)
|
38
|
+
i18n (1.0.1)
|
39
|
+
concurrent-ruby (~> 1.0)
|
40
|
+
json (2.1.0)
|
41
|
+
loofah (2.2.2)
|
42
|
+
crass (~> 1.0.2)
|
43
|
+
nokogiri (>= 1.5.9)
|
44
|
+
method_source (0.9.0)
|
45
|
+
mini_portile2 (2.3.0)
|
46
|
+
minitest (5.11.3)
|
47
|
+
nokogiri (1.8.4)
|
48
|
+
mini_portile2 (~> 2.3.0)
|
49
|
+
rack (2.0.5)
|
50
|
+
rack-test (1.1.0)
|
51
|
+
rack (>= 1.0, < 3)
|
52
|
+
rails-dom-testing (2.0.3)
|
53
|
+
activesupport (>= 4.2.0)
|
54
|
+
nokogiri (>= 1.6)
|
55
|
+
rails-html-sanitizer (1.0.4)
|
56
|
+
loofah (~> 2.2, >= 2.2.2)
|
57
|
+
railties (5.2.0)
|
58
|
+
actionpack (= 5.2.0)
|
59
|
+
activesupport (= 5.2.0)
|
60
|
+
method_source
|
61
|
+
rake (>= 0.8.7)
|
62
|
+
thor (>= 0.18.1, < 2.0)
|
63
|
+
rake (12.0.0)
|
64
|
+
request_store (1.4.1)
|
65
|
+
rack (>= 1.4)
|
66
|
+
rspec (3.7.0)
|
67
|
+
rspec-core (~> 3.7.0)
|
68
|
+
rspec-expectations (~> 3.7.0)
|
69
|
+
rspec-mocks (~> 3.7.0)
|
70
|
+
rspec-core (3.7.1)
|
71
|
+
rspec-support (~> 3.7.0)
|
72
|
+
rspec-expectations (3.7.0)
|
73
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
74
|
+
rspec-support (~> 3.7.0)
|
75
|
+
rspec-mocks (3.7.0)
|
76
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
77
|
+
rspec-support (~> 3.7.0)
|
78
|
+
rspec-support (3.7.1)
|
79
|
+
simplecov (0.16.1)
|
80
|
+
docile (~> 1.1)
|
81
|
+
json (>= 1.8, < 3)
|
82
|
+
simplecov-html (~> 0.10.0)
|
83
|
+
simplecov-console (0.4.2)
|
84
|
+
ansi
|
85
|
+
hirb
|
86
|
+
simplecov
|
87
|
+
simplecov-html (0.10.2)
|
88
|
+
thor (0.20.0)
|
89
|
+
thread_safe (0.3.6)
|
90
|
+
tzinfo (1.2.5)
|
91
|
+
thread_safe (~> 0.1)
|
92
|
+
|
93
|
+
PLATFORMS
|
94
|
+
ruby
|
95
|
+
|
96
|
+
DEPENDENCIES
|
97
|
+
chronos-authz!
|
98
|
+
rake (>= 11.3.0)
|
99
|
+
rspec
|
100
|
+
simplecov
|
101
|
+
simplecov-console
|
102
|
+
|
103
|
+
BUNDLED WITH
|
104
|
+
1.16.2
|
data/README.md
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
# chronos_authz
|
2
|
+
A declarative authorization Rack middleware that supports custom authorization logic on a per-resource basis
|
3
|
+
|
4
|
+
## Usage sample for Rails
|
5
|
+
### 1. Install the gem
|
6
|
+
```ruby
|
7
|
+
gem 'chronos_authz'
|
8
|
+
```
|
9
|
+
|
10
|
+
### 2. Configure the ACL config/my_acl.yml
|
11
|
+
An incoming request's http method and path will be checked against the ACL file's records for a match. At minimum, you MUST configure the path of the resource in an ACL record and SHOULD configure the http_method. If the http_method isn't configured, http method checking will not be done. You can define any other configuration here per resource as needed (ex. :permissions is a custom configuration and is defined here as this will be used in the custom authorization rule.
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
manage_accounts:
|
15
|
+
path: "/accounts/.*" # regex pattern
|
16
|
+
http_method: # if array is used, this would work as an OR operation: checking if the incoming http request's method matches ANY of the configured http_method
|
17
|
+
- GET
|
18
|
+
- put
|
19
|
+
permissions:
|
20
|
+
- VIEW_ACCOUNTS
|
21
|
+
- UPDATE_ACCOUNTS
|
22
|
+
|
23
|
+
create_users:
|
24
|
+
path: "/users"
|
25
|
+
http_method: POST
|
26
|
+
permissions:
|
27
|
+
- CREATE_USERS
|
28
|
+
|
29
|
+
update_users:
|
30
|
+
path: "/users/.*"
|
31
|
+
http_method: PUT
|
32
|
+
permissions:
|
33
|
+
- UPDATE_USERS
|
34
|
+
# rule: AnotherCustomRule # Use a different rule for this ACL record
|
35
|
+
```
|
36
|
+
|
37
|
+
### 2. Create an authorization rule initializers/MyCustomRule.rb
|
38
|
+
An authorization rule MUST implement the __request_authorized?__ method. The rule has an access to the ff. instance variables:
|
39
|
+
|
40
|
+
1. @request - [__Rack::Request__](https://www.rubydoc.info/gems/rack/Rack/Request) for the incoming HTTP request
|
41
|
+
2. @acl_record - __ChronosAuthz::ACL::Record__ from the ACL yml that matches the incoming request's http method and path. Custom configuration in the ACL yaml will be accessible from this object. ex. @acl_record.permissions
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
class MyCustomRule < ChronosAuthz::Rule
|
45
|
+
|
46
|
+
# Must return a boolean to check
|
47
|
+
def request_authorized?
|
48
|
+
(@acl_record.permissions - user_claims).blank?
|
49
|
+
end
|
50
|
+
|
51
|
+
# Optional. Implement how claims are retrieved for a given user. Normally claims could be retrieved using cookies,
|
52
|
+
# JWT/id_token decoded from the request header, API calls, or a database query. Any value returned here will be available to the ChronosAuthz::User.claims helper module as well.
|
53
|
+
def user_claims
|
54
|
+
# SAMPLE ONLY! In this sample configuration, only the user with the access token '1d1234913em23' would only be able to successfully send a POST request to /users. Access token 'm123493429304' bearer could both create and update a User.
|
55
|
+
access_tokens = {
|
56
|
+
"1d1234913em23" => ["CREATE_USERS"],
|
57
|
+
"m123493429304" => ["CREATE_USERS", "UPDATE_USERS", "SomeOtherClaimInOtherFormat", "any-format-should-work-claim"]
|
58
|
+
}
|
59
|
+
|
60
|
+
access_token_from_request = @request.get_header("HTTP_AUTHORIZATION").gsub("Bearer ")
|
61
|
+
return (access_tokens[access_token_from_request] || [])
|
62
|
+
end
|
63
|
+
end
|
64
|
+
```
|
65
|
+
|
66
|
+
### 4. Use the middleware
|
67
|
+
```ruby
|
68
|
+
Rails.application.config.middleware.use ChronosAuthz::Authorizer do |config|
|
69
|
+
# Required. Default authorization rule to use
|
70
|
+
config.default_rule = MyCustomRule
|
71
|
+
|
72
|
+
# Optional. Default is false. If set to true, the ACL is treated as a whitelist of resource paths: authorization would return a 403 error if no ACL Record has been configured for a given resource path. If set to false, authorization check will only be done to the resources configured in the ACL.
|
73
|
+
config.strict_mode = true
|
74
|
+
|
75
|
+
# Optional. Configure the error page to render when authorization fails
|
76
|
+
config.error_page = "public/403.html"
|
77
|
+
|
78
|
+
# Optional. Default behavior will look for 'config/authorizer_acl.yml'. Configure which ACL yml to use
|
79
|
+
config.acl_yaml = "config/my_acl.yml"
|
80
|
+
end
|
81
|
+
```
|
82
|
+
|
83
|
+
## Helpers
|
84
|
+
Include the helper module __ChronosAuthz::User__ as needed to have access to the current user's claims via the __.claims__ method.
|
85
|
+
example:
|
86
|
+
```ruby
|
87
|
+
class User < ActiveRecord
|
88
|
+
include ChronosAuthz::User
|
89
|
+
end
|
90
|
+
```
|
91
|
+
|
92
|
+
With this helper included in your User model and assuming you are using Devise or any other AuthN solution, you may do the ff.:
|
93
|
+
```ruby
|
94
|
+
current_user.claims
|
95
|
+
=> ["CREATE_USERS"]
|
96
|
+
```
|
97
|
+
|
98
|
+
If the return value of user_claims method in your implementation is a hash:
|
99
|
+
```ruby
|
100
|
+
current_user.claims
|
101
|
+
=> {permissions: ["CREATE_USERS"], email: "someemail@yourdomain.com"}
|
102
|
+
|
103
|
+
current_user.claim[:email]
|
104
|
+
=> someemail@yourdomain.com
|
105
|
+
```
|
106
|
+
|
107
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
$LOAD_PATH.push File.expand_path("../lib", __FILE__)
|
2
|
+
|
3
|
+
require "chronos_authz/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "chronos_authz"
|
7
|
+
s.version = ChronosAuthz::VERSION
|
8
|
+
s.authors = ["Marianne Angelie del Mundo", "Rodette Pedro", "JR Respino", "Jayson Uy"]
|
9
|
+
s.email = %w(marianne@chronoscloud.com rodette@chronoscloud.com jr@chronoscloud.com jayson@chronoscloud.com)
|
10
|
+
s.homepage = "https://github.com/chronoscloud/chronoscloud-authz"
|
11
|
+
s.summary = "A minimal and declarative authorization layer"
|
12
|
+
s.description = "A declarative authorization Rack middleware that supports custom authorization logic on a per-resource basis"
|
13
|
+
s.license = 'N/A'
|
14
|
+
|
15
|
+
s.files = `git ls-files`.split("\n")
|
16
|
+
s.test_files = `git ls-files -- spec/*`.split("\n")
|
17
|
+
s.require_paths = ["lib"]
|
18
|
+
|
19
|
+
s.add_dependency "railties", ">= 4.2"
|
20
|
+
s.add_dependency "request_store"
|
21
|
+
s.add_dependency "activesupport"
|
22
|
+
s.required_ruby_version = ">= 2.4"
|
23
|
+
|
24
|
+
s.add_development_dependency "rake", ">= 11.3.0"
|
25
|
+
s.add_development_dependency "rspec-rails"
|
26
|
+
s.add_development_dependency "simplecov"
|
27
|
+
s.add_development_dependency "simplecov-console"
|
28
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'active_support/core_ext/string'
|
2
|
+
require 'active_support/core_ext/hash/indifferent_access'
|
3
|
+
require 'ostruct'
|
4
|
+
require 'request_store'
|
5
|
+
require 'yaml'
|
6
|
+
|
7
|
+
require 'chronos_authz/validations/validation_error'
|
8
|
+
require 'chronos_authz/validations/options_validator'
|
9
|
+
require 'chronos_authz/configuration'
|
10
|
+
require 'chronos_authz/acl'
|
11
|
+
require 'chronos_authz/rule'
|
12
|
+
require 'chronos_authz/user'
|
13
|
+
require 'chronos_authz/authorizer'
|
14
|
+
|
15
|
+
module ChronosAuthz
|
16
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module ChronosAuthz
|
2
|
+
class ACL
|
3
|
+
attr_accessor :acl_hash, :records
|
4
|
+
|
5
|
+
def self.build_from_yaml(acl_yaml = nil)
|
6
|
+
acl_yaml ||= 'config/authorizer_acl.yml'
|
7
|
+
acl_hash = YAML.load_file(acl_yaml)
|
8
|
+
records = ACL.records_from_acl_hash(acl_hash)
|
9
|
+
return ACL.new(records, acl_hash)
|
10
|
+
end
|
11
|
+
|
12
|
+
# Populate @records with instances of ACL::Record and validate each instances
|
13
|
+
def self.records_from_acl_hash(acl_hash)
|
14
|
+
return acl_hash.map do |record_name, record_options = {}|
|
15
|
+
ChronosAuthz::ACL::Record.new(record_options.merge!(name: record_name))
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize(records = [], acl_hash = nil)
|
20
|
+
@records = records
|
21
|
+
@acl_hash = acl_hash
|
22
|
+
end
|
23
|
+
|
24
|
+
# Find matching ACL Record
|
25
|
+
def find_match(http_method, request_path)
|
26
|
+
record = @records.select{ |record| record.matches?(http_method, request_path) }.first
|
27
|
+
# puts "[ChronosAuthz] Found ACL match: #{record.to_s}" if record
|
28
|
+
|
29
|
+
return record
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
class Record < OpenStruct
|
34
|
+
include ChronosAuthz::Validations::OptionsValidator
|
35
|
+
|
36
|
+
VALID_HTTP_METHODS = ["GET",
|
37
|
+
"POST",
|
38
|
+
"PUT",
|
39
|
+
"PATCH",
|
40
|
+
"DELETE",
|
41
|
+
"HEAD"].freeze
|
42
|
+
|
43
|
+
required :name, :path
|
44
|
+
check_constraint :http_method, VALID_HTTP_METHODS, allow_nil: true
|
45
|
+
|
46
|
+
def initialize(value = {})
|
47
|
+
value = value.with_indifferent_access
|
48
|
+
value[:http_method] = normalize_http_methods(value[:http_method])
|
49
|
+
value[:path] = normalize_path(value[:path])
|
50
|
+
|
51
|
+
super(value)
|
52
|
+
validate!
|
53
|
+
end
|
54
|
+
|
55
|
+
def matches?(http_method, request_path)
|
56
|
+
return false if request_path.nil?
|
57
|
+
|
58
|
+
request_path = normalize_path(request_path)
|
59
|
+
path_pattern = /\A#{self.path}\z/
|
60
|
+
|
61
|
+
method_matched = self.http_method.empty? || self.http_method.include?(http_method.to_s.upcase)
|
62
|
+
|
63
|
+
return false if !method_matched
|
64
|
+
return !request_path.match(path_pattern).nil?
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def normalize_path(resource_path)
|
70
|
+
return resource_path.squish if !resource_path.nil?
|
71
|
+
end
|
72
|
+
|
73
|
+
def normalize_http_methods(http_methods)
|
74
|
+
http_methods = [http_methods] if !http_methods.is_a? Array
|
75
|
+
http_methods.map!{ |http_method| http_method.to_s.upcase }
|
76
|
+
|
77
|
+
return http_methods.reject { |http_method| http_method.blank? }
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module ChronosAuthz
|
2
|
+
|
3
|
+
class Authorizer
|
4
|
+
|
5
|
+
attr_accessor :configuration, :acl
|
6
|
+
|
7
|
+
def initialize(app, options = {})
|
8
|
+
@app, @configuration = app, ::ChronosAuthz::Configuration.new(options)
|
9
|
+
|
10
|
+
yield @configuration if block_given?
|
11
|
+
@configuration.validate!
|
12
|
+
@acl = ChronosAuthz::ACL.build_from_yaml(@configuration.acl_yaml)
|
13
|
+
end
|
14
|
+
|
15
|
+
|
16
|
+
def call(env)
|
17
|
+
matched_acl_record = @acl.find_match(env["REQUEST_METHOD"], env["PATH_INFO"])
|
18
|
+
|
19
|
+
return render_unauthorized if @configuration.strict_mode && matched_acl_record.nil?
|
20
|
+
|
21
|
+
request = Rack::Request.new(env)
|
22
|
+
rule_class = matched_acl_record.try(:rule).try(:constantize) || @configuration.default_rule
|
23
|
+
rule_instance = rule_class.new(request, matched_acl_record)
|
24
|
+
|
25
|
+
return render_unauthorized if !rule_instance.request_authorized?
|
26
|
+
|
27
|
+
RequestStore.store[:chronos_authz_claims] = rule_instance.user_claims
|
28
|
+
status, headers, response = @app.call(env)
|
29
|
+
end
|
30
|
+
|
31
|
+
def render_unauthorized
|
32
|
+
if @configuration.error_page
|
33
|
+
# html = ActionView::Base.new.render(file: @configuration.error_page)
|
34
|
+
return [403, {'Content-Type' => 'text/html'}, [File.read(@configuration.error_page)]]
|
35
|
+
end
|
36
|
+
return [403, {'Content-Type' => 'text/plain'}, ["Unauthorized"]]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module ChronosAuthz
|
2
|
+
class Rule
|
3
|
+
|
4
|
+
attr_accessor :request, :acl_record
|
5
|
+
|
6
|
+
def initialize(request, acl_record)
|
7
|
+
@request = request
|
8
|
+
@acl_record = acl_record
|
9
|
+
end
|
10
|
+
|
11
|
+
def user_claims
|
12
|
+
nil
|
13
|
+
end
|
14
|
+
|
15
|
+
def request_authorized?
|
16
|
+
false
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module ChronosAuthz
|
2
|
+
module Validations
|
3
|
+
module OptionsValidator
|
4
|
+
|
5
|
+
def self.included base
|
6
|
+
base.extend OptionsValidatorClassMethods
|
7
|
+
end
|
8
|
+
|
9
|
+
module OptionsValidatorClassMethods
|
10
|
+
attr_accessor :required_options, :predefined_value_map
|
11
|
+
|
12
|
+
def required(*options)
|
13
|
+
self.required_options = options
|
14
|
+
end
|
15
|
+
|
16
|
+
def check_constraint(option, predefined_values = [], constraint_options = {})
|
17
|
+
self.predefined_value_map ||= {}
|
18
|
+
self.predefined_value_map[option] = { check_values: predefined_values,
|
19
|
+
constraint_options: constraint_options }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def validate!
|
24
|
+
|
25
|
+
# Required options
|
26
|
+
if !self.class.required_options.nil?
|
27
|
+
self.class.required_options.each do |required_option|
|
28
|
+
option_value = send(required_option) if respond_to?(required_option)
|
29
|
+
raise ChronosAuthz::Validations::ValidationError.new("Missing option #{required_option} in #{self.class}") if option_value.blank?
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Check constraint
|
34
|
+
if !self.class.predefined_value_map.nil?
|
35
|
+
self.class.predefined_value_map.each do |key, value|
|
36
|
+
option_values = send(key)
|
37
|
+
check_values = value[:check_values]
|
38
|
+
|
39
|
+
if !option_values.is_a? Array
|
40
|
+
option_values = [option_values]
|
41
|
+
end
|
42
|
+
|
43
|
+
if value[:constraint_options][:case_sensitive].nil? || !value[:constraint_options][:case_sensitive]
|
44
|
+
option_values = option_values.map{|option_value| option_value.to_s.upcase }
|
45
|
+
check_values = check_values.map{|check_value| check_value.to_s.upcase }
|
46
|
+
end
|
47
|
+
|
48
|
+
option_values.each do |option_value|
|
49
|
+
next if value[:constraint_options][:allow_nil] && (option_value.nil? || option_value.empty?)
|
50
|
+
raise ChronosAuthz::Validations::ValidationError.new("Invalid option value #{option_value} for #{key} in #{self.class}. Valid values are #{check_values}.") if !check_values.include?(option_value)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
self
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
data/spec/acl_spec.rb
ADDED
@@ -0,0 +1,186 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe ChronosAuthz::ACL do
|
4
|
+
let(:acl_yaml) { 'spec/config/authorizer_acl_test.yml' }
|
5
|
+
|
6
|
+
describe 'attribute accessors' do
|
7
|
+
it 'assigns acl_hash' do
|
8
|
+
acl_object = ChronosAuthz::ACL.build_from_yaml(acl_yaml)
|
9
|
+
|
10
|
+
expect(acl_object.acl_hash).to_not be_nil
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'assigns records' do
|
14
|
+
acl_object = ChronosAuthz::ACL.build_from_yaml(acl_yaml)
|
15
|
+
|
16
|
+
expect(acl_object.records).to_not be_empty
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe '.build_from_yaml' do
|
21
|
+
context 'when acl_yaml is specified' do
|
22
|
+
it 'loads a YAML file from the acl_yaml' do
|
23
|
+
allow(YAML).to receive(:load_file).and_return(YAML.load_file(acl_yaml))
|
24
|
+
expect(YAML).to receive(:load_file).with(acl_yaml)
|
25
|
+
|
26
|
+
ChronosAuthz::ACL.build_from_yaml(acl_yaml)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
context 'when no acl_yaml is specified' do
|
31
|
+
it 'loads a YAML file using a default path' do
|
32
|
+
allow(YAML).to receive(:load_file).and_return(YAML.load_file(acl_yaml))
|
33
|
+
expect(YAML).to receive(:load_file).with('config/authorizer_acl.yml')
|
34
|
+
|
35
|
+
ChronosAuthz::ACL.build_from_yaml
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'initializes a new ACL' do
|
40
|
+
expect(ChronosAuthz::ACL).to receive(:new)
|
41
|
+
|
42
|
+
ChronosAuthz::ACL.build_from_yaml(acl_yaml)
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'returns an ACL object' do
|
46
|
+
expect(ChronosAuthz::ACL.build_from_yaml(acl_yaml)).to be_an_instance_of(ChronosAuthz::ACL)
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
describe '.records_from_acl_hash' do
|
52
|
+
it 'returns an array of ACL::Record from a YAML hash' do
|
53
|
+
records = ChronosAuthz::ACL.records_from_acl_hash(YAML.load_file(acl_yaml))
|
54
|
+
expect(records).to_not be_empty
|
55
|
+
expect(records).to all(be_an(ChronosAuthz::ACL::Record))
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe '#find_match' do
|
60
|
+
let(:acl) { ChronosAuthz::ACL.build_from_yaml(acl_yaml) }
|
61
|
+
|
62
|
+
context 'when using valid parameters' do
|
63
|
+
context 'when http_method and request_path is in config' do
|
64
|
+
|
65
|
+
it 'returns an ACLRecord' do
|
66
|
+
expect(acl.find_match('POST', '/users')).to be_an_instance_of(ChronosAuthz::ACL::Record)
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'finds the record that matches the path pattern' do
|
70
|
+
expect(acl.find_match('GET', '/accounts/2')).to_not be_nil
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
context 'when http_method is not in config' do
|
75
|
+
it 'returns nil' do
|
76
|
+
expect(acl.find_match('DELETE', '/users')).to be_nil
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
context 'when request_path is not in config' do
|
81
|
+
it 'returns nil' do
|
82
|
+
expect(acl.find_match('GET', '/test')).to be_nil
|
83
|
+
expect(acl.find_match('POST', '/users////')).to be_nil
|
84
|
+
expect(acl.find_match('POST', '/user/1/1/1/')).to be_nil
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
context 'when using null parameters' do
|
90
|
+
context 'when http_method is null' do
|
91
|
+
it 'returns nil' do
|
92
|
+
expect(acl.find_match(nil, '/test')).to be_nil
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
context 'when resource_path is null' do
|
97
|
+
it 'returns nil' do
|
98
|
+
expect(acl.find_match('GET', 'nil')).to be_nil
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
|
106
|
+
describe ChronosAuthz::ACL::Record do
|
107
|
+
let(:acl_record_string_method) { ChronosAuthz::ACL::Record.new(name: 'create_users',
|
108
|
+
http_method: 'post',
|
109
|
+
path: ' /users ') }
|
110
|
+
let(:acl_record_array_method) { ChronosAuthz::ACL::Record.new(name: 'create_users',
|
111
|
+
http_method: ['POST', 'put'],
|
112
|
+
path: '/users') }
|
113
|
+
let(:acl_record_implicit_all_method) { ChronosAuthz::ACL::Record.new(name: 'create_users',
|
114
|
+
path: '/users') }
|
115
|
+
|
116
|
+
describe 'initialization' do
|
117
|
+
context 'when http_method is specified' do
|
118
|
+
context 'with a valid HTTP method' do
|
119
|
+
it 'assigns http_method as a normalized array' do
|
120
|
+
expect(acl_record_string_method.http_method).to_not be_empty
|
121
|
+
expect(acl_record_string_method.http_method).eql?(['POST'])
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
context 'with an invalid HTTP method' do
|
126
|
+
it 'raises a validation error' do
|
127
|
+
expect{ ChronosAuthz::ACL::Record.new(name: 'create_users',
|
128
|
+
http_method: 'GETS',
|
129
|
+
path: '/users') }.to raise_error(ChronosAuthz::Validations::ValidationError)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
context 'when path is specified' do
|
135
|
+
it 'assigns a squished http_method' do
|
136
|
+
expect(acl_record_string_method.path).eql?("/users")
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
it 'raises a validation error if name is not specified' do
|
141
|
+
expect{ ChronosAuthz::ACL::Record.new(path: '/users') }.to raise_error(ChronosAuthz::Validations::ValidationError)
|
142
|
+
end
|
143
|
+
|
144
|
+
it 'raises a validation error if path is not specified' do
|
145
|
+
expect{ ChronosAuthz::ACL::Record.new(name: 'create_users') }.to raise_error(ChronosAuthz::Validations::ValidationError)
|
146
|
+
end
|
147
|
+
|
148
|
+
end
|
149
|
+
|
150
|
+
describe '#matches?' do
|
151
|
+
context 'when request_path matches the request_path regex pattern config' do
|
152
|
+
context 'with an array http_method config' do
|
153
|
+
it 'returns true if http_method has a match from the http_method array config' do
|
154
|
+
expect(acl_record_array_method.matches?('POST', '/users')).to be true
|
155
|
+
end
|
156
|
+
|
157
|
+
it 'returns false if http_method doesn\'t have a match from the http_method array config' do
|
158
|
+
expect(acl_record_array_method.matches?('GET', '/accounts')).to be false
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
context 'with a string http_method config' do
|
163
|
+
it 'returns true if http_method matches the http_method string config' do
|
164
|
+
expect(acl_record_string_method.matches?('POST', '/users')).to be true
|
165
|
+
end
|
166
|
+
|
167
|
+
it 'returns false if http_method doesn\'t match the http_method string config' do
|
168
|
+
expect(acl_record_string_method.matches?('get', '/accounts')).to be false
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
context 'with a nil http_method config' do
|
173
|
+
it 'returns true' do
|
174
|
+
expect(acl_record_implicit_all_method.matches?('post', '/users')).to be true
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
context 'when request_path doesn\'t match the request_path regex pattern config' do
|
180
|
+
it 'returns false' do
|
181
|
+
expect(acl_record_implicit_all_method.matches?('post', '/account/')).to be false
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'rack'
|
3
|
+
describe ChronosAuthz::Authorizer do
|
4
|
+
|
5
|
+
class AuthorizeAllRequestRule < ChronosAuthz::Rule
|
6
|
+
def request_authorized?
|
7
|
+
true
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class BlockAllRequestsRule < ChronosAuthz::Rule
|
12
|
+
def request_authorized?
|
13
|
+
false
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe "Middleware configuration" do
|
18
|
+
|
19
|
+
it "should raise an error if default_rule isn't configured" do
|
20
|
+
env = mock_env("/")
|
21
|
+
app = lambda{}
|
22
|
+
expect{ rack_app(app).call(env) }.to raise_error(ChronosAuthz::Validations::ValidationError)
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should use default_rule for authorization check when no :rule option was specified in the matched ACL record" do
|
26
|
+
env = mock_env("/accounts/")
|
27
|
+
app = lambda{ |env| [200, {'Content-Type' => 'text/plain'}, ["OK"]] }
|
28
|
+
default_rule_class = AuthorizeAllRequestRule
|
29
|
+
expect_any_instance_of(default_rule_class).to receive(:request_authorized?)
|
30
|
+
rack_app(app, :default_rule => default_rule_class, :strict_mode => false).call(env)
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should use the matched ACL Record's :rule option if it is specified" do
|
34
|
+
env = mock_env("/users", method: "POST")
|
35
|
+
app = lambda{ |env| [200, {'Content-Type' => 'text/plain'}, ["OK"]] }
|
36
|
+
expect_any_instance_of(ChronosAuthz::Spec::Helpers::CustomRule).to receive(:request_authorized?)
|
37
|
+
expect_any_instance_of(AuthorizeAllRequestRule).to_not receive(:request_authorized?)
|
38
|
+
rack_app(app, :default_rule => AuthorizeAllRequestRule, :strict_mode => false).call(env)
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should use the configured error_page if the request is unauthorized" do
|
42
|
+
env = mock_env("/accounts/123", method: "PUT")
|
43
|
+
app = lambda{ |env| [200, {'Content-Type' => 'text/plain'}, ["OK"]] }
|
44
|
+
error_page_path = 'spec/config/error_page.html'
|
45
|
+
error_page_contents = File.read(error_page_path)
|
46
|
+
result = rack_app(app, :default_rule => BlockAllRequestsRule, :error_page => error_page_path).call(env)
|
47
|
+
expect(result.last.first).to eq(error_page_contents)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe "Authorization" do
|
52
|
+
|
53
|
+
it "should fail with a 403 response if strict_mode configuration is set to true and no ACL Record was configured for the incoming request" do
|
54
|
+
env = mock_env("/some_unknown_path")
|
55
|
+
app = lambda{}
|
56
|
+
result = rack_app(app, :default_rule => AuthorizeAllRequestRule, :strict_mode => true).call(env)
|
57
|
+
expect(result.first).to eq(403)
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should not fail with a 403 response if strict_mode configuration is not set to true and no ACL Record was configured for the incoming request" do
|
61
|
+
env = mock_env("/")
|
62
|
+
app = lambda{ |env| [200, {'Content-Type' => 'text/plain'}, ["OK"]] }
|
63
|
+
result = rack_app(app, :default_rule => AuthorizeAllRequestRule, :strict_mode => false).call(env)
|
64
|
+
expect(result.first).to eq(200)
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should fail with a 403 response if authorization check failed" do
|
68
|
+
env = mock_env("/")
|
69
|
+
app = lambda{}
|
70
|
+
result = rack_app(app, :default_rule => BlockAllRequestsRule).call(env)
|
71
|
+
expect(result.first).to eq(403)
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should not fail with a 403 response if authorization check succeeded" do
|
75
|
+
env = mock_env("/")
|
76
|
+
app = lambda{ |env| [200, {'Content-Type' => 'text/plain'}, ["OK"]] }
|
77
|
+
result = rack_app(app, :default_rule => AuthorizeAllRequestRule).call(env)
|
78
|
+
expect(result.first).to eq(200)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def rack_app(app, options= {})
|
83
|
+
options[:acl_yaml] ||= 'spec/config/authorizer_acl_test.yml'
|
84
|
+
Rack::Builder.new do
|
85
|
+
use ChronosAuthz::Authorizer, options
|
86
|
+
run app
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def mock_env(path = "/", params = {})
|
91
|
+
method = params.delete(:method) || "GET"
|
92
|
+
env = { 'HTTP_VERSION' => '1.1', 'REQUEST_METHOD' => "#{method}" }
|
93
|
+
Rack::MockRequest.env_for("#{path}?#{Rack::Utils.build_query(params)}", env)
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
manage_accounts:
|
2
|
+
path: "/accounts/.*"
|
3
|
+
http_method:
|
4
|
+
- GET
|
5
|
+
- put
|
6
|
+
permissions:
|
7
|
+
- AUTH::VIEW_ACCOUNT
|
8
|
+
- AUTH::MANAGE_ACCOUNTS
|
9
|
+
|
10
|
+
create_users:
|
11
|
+
path: "/users"
|
12
|
+
http_method: POST
|
13
|
+
permissions:
|
14
|
+
- AUTH::CREATE_USERS
|
15
|
+
rule: ChronosAuthz::Spec::Helpers::CustomRule
|
16
|
+
|
17
|
+
shipment:
|
18
|
+
path: "/*"
|
@@ -0,0 +1 @@
|
|
1
|
+
Custom error page
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe ChronosAuthz::Validations::OptionsValidator do
|
4
|
+
PRIMARY_COLORS = ["red", "yellow", "blue"].freeze
|
5
|
+
|
6
|
+
class WithRequiredAndCheckConstraint < OpenStruct
|
7
|
+
include ChronosAuthz::Validations::OptionsValidator
|
8
|
+
required :some_required_attribute
|
9
|
+
check_constraint :primary_color, PRIMARY_COLORS
|
10
|
+
end
|
11
|
+
|
12
|
+
class WithRequiredCheckConstraintOption < OpenStruct
|
13
|
+
include ChronosAuthz::Validations::OptionsValidator
|
14
|
+
check_constraint :primary_color, PRIMARY_COLORS, allow_nil: false
|
15
|
+
end
|
16
|
+
|
17
|
+
class WithCheckConstraint < OpenStruct
|
18
|
+
include ChronosAuthz::Validations::OptionsValidator
|
19
|
+
check_constraint :primary_color, ["red", "yellow", "blue"], allow_nil: true
|
20
|
+
end
|
21
|
+
|
22
|
+
class WithCaseSensitiveCheckConstraintOption < OpenStruct
|
23
|
+
include ChronosAuthz::Validations::OptionsValidator
|
24
|
+
check_constraint :primary_color, ["red", "yellow", "blue"], case_sensitive: true
|
25
|
+
end
|
26
|
+
|
27
|
+
describe 'required options validation' do
|
28
|
+
|
29
|
+
it 'raises a validation error if a required option is nil' do
|
30
|
+
expect{ WithRequiredAndCheckConstraint.new.validate! }.to raise_error(ChronosAuthz::Validations::ValidationError)
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
describe 'check constraint validation' do
|
36
|
+
|
37
|
+
context 'when option value is a string' do
|
38
|
+
it 'raises a validation error if the option value isn\'t found from the valid option values' do
|
39
|
+
expect{ WithRequiredAndCheckConstraint.new(some_required_attribute: 'some_value', primary_color: "brown").validate! }.to raise_error(ChronosAuthz::Validations::ValidationError)
|
40
|
+
end
|
41
|
+
|
42
|
+
context 'with allow_nil config set to false' do
|
43
|
+
it 'raises a validation error if option value is nil' do
|
44
|
+
expect{ WithRequiredCheckConstraintOption.new.validate! }.to raise_error(ChronosAuthz::Validations::ValidationError)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
context 'with allow_nil config set to true' do
|
49
|
+
it 'doesn\'t raise any validation errors if option value is nil' do
|
50
|
+
expect{ WithCheckConstraint.new.validate! }.to_not raise_error(ChronosAuthz::Validations::ValidationError)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
context 'with case_sensitive config set to true' do
|
55
|
+
it 'raises a validation error if option value is meaningfully equal to any of the valid option values but in different case' do
|
56
|
+
expect{ WithCaseSensitiveCheckConstraintOption.new(primary_color: "RED").validate! }.to raise_error(ChronosAuthz::Validations::ValidationError)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
context 'when option value is an array' do
|
62
|
+
it 'raises a validation error if an element from the option value isn\'t found from the valid option values' do
|
63
|
+
expect{ WithRequiredAndCheckConstraint.new(some_required_attribute: 'some_value', primary_color: ["RED", "brown"]).validate! }.to raise_error(ChronosAuthz::Validations::ValidationError)
|
64
|
+
end
|
65
|
+
|
66
|
+
context 'with allow_nil config set to false' do
|
67
|
+
it 'raises a validation error if an element from the option value is nil' do
|
68
|
+
expect{ WithRequiredCheckConstraintOption.new(primary_color: ["blue", nil]).validate! }.to raise_error(ChronosAuthz::Validations::ValidationError)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
context 'with allow_nil config set to true' do
|
73
|
+
it 'doesn\'t raise any validation errors if an element from the option value is nil' do
|
74
|
+
expect{ WithCheckConstraint.new(primary_color: ["blue", nil]).validate! }.to_not raise_error(ChronosAuthz::Validations::ValidationError)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
context 'with case_sensitive config set to true' do
|
79
|
+
it 'raises a validation error if an element from the option value is meaningfully equal to any of the valid option values but in different case' do
|
80
|
+
expect{ WithCaseSensitiveCheckConstraintOption.new(primary_color: ["blue", "ReD"]).validate! }.to raise_error(ChronosAuthz::Validations::ValidationError)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
require 'bundler/setup'
|
2
|
+
Bundler.setup
|
3
|
+
|
4
|
+
require 'chronos_authz'
|
5
|
+
|
6
|
+
require 'simplecov'
|
7
|
+
require 'simplecov-console'
|
8
|
+
|
9
|
+
Dir[File.join(File.dirname(__FILE__), "helpers", "**/*.rb")].each do |f|
|
10
|
+
require f
|
11
|
+
end
|
12
|
+
|
13
|
+
SimpleCov.formatter = SimpleCov.formatter = SimpleCov::Formatter::Console
|
14
|
+
SimpleCov.start
|
15
|
+
|
16
|
+
# This file was generated by the `rspec --init` command. Conventionally, all
|
17
|
+
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
18
|
+
# The generated `.rspec` file contains `--require spec_helper` which will cause
|
19
|
+
# this file to always be loaded, without a need to explicitly require it in any
|
20
|
+
# files.
|
21
|
+
#
|
22
|
+
# Given that it is always loaded, you are encouraged to keep this file as
|
23
|
+
# light-weight as possible. Requiring heavyweight dependencies from this file
|
24
|
+
# will add to the boot time of your test suite on EVERY test run, even for an
|
25
|
+
# individual file that may not need all of that loaded. Instead, consider making
|
26
|
+
# a separate helper file that requires the additional dependencies and performs
|
27
|
+
# the additional setup, and require it from the spec files that actually need
|
28
|
+
# it.
|
29
|
+
#
|
30
|
+
# The `.rspec` file also contains a few flags that are not defaults but that
|
31
|
+
# users commonly want.
|
32
|
+
#
|
33
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
34
|
+
RSpec::Expectations.configuration.on_potential_false_positives = :nothing
|
35
|
+
RSpec.configure do |config|
|
36
|
+
# rspec-expectations config goes here. You can use an alternate
|
37
|
+
# assertion/expectation library such as wrong or the stdlib/minitest
|
38
|
+
# assertions if you prefer.
|
39
|
+
# config.include(ChronosAuthz::Spec::Helpers)
|
40
|
+
|
41
|
+
config.expect_with :rspec do |expectations|
|
42
|
+
# This option will default to `true` in RSpec 4. It makes the `description`
|
43
|
+
# and `failure_message` of custom matchers include text for helper methods
|
44
|
+
# defined using `chain`, e.g.:
|
45
|
+
# be_bigger_than(2).and_smaller_than(4).description
|
46
|
+
# # => "be bigger than 2 and smaller than 4"
|
47
|
+
# ...rather than:
|
48
|
+
# # => "be bigger than 2"
|
49
|
+
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
# rspec-mocks config goes here. You can use an alternate test double
|
54
|
+
# library (such as bogus or mocha) by changing the `mock_with` option here.
|
55
|
+
config.mock_with :rspec do |mocks|
|
56
|
+
# Prevents you from mocking or stubbing a method that does not exist on
|
57
|
+
# a real object. This is generally recommended, and will default to
|
58
|
+
# `true` in RSpec 4.
|
59
|
+
mocks.verify_partial_doubles = true
|
60
|
+
end
|
61
|
+
|
62
|
+
# The settings below are suggested to provide a good initial experience
|
63
|
+
# with RSpec, but feel free to customize to your heart's content.
|
64
|
+
=begin
|
65
|
+
# These two settings work together to allow you to limit a spec run
|
66
|
+
# to individual examples or groups you care about by tagging them with
|
67
|
+
# `:focus` metadata. When nothing is tagged with `:focus`, all examples
|
68
|
+
# get run.
|
69
|
+
config.filter_run :focus
|
70
|
+
config.run_all_when_everything_filtered = true
|
71
|
+
|
72
|
+
# Allows RSpec to persist some state between runs in order to support
|
73
|
+
# the `--only-failures` and `--next-failure` CLI options. We recommend
|
74
|
+
# you configure your source control system to ignore this file.
|
75
|
+
config.example_status_persistence_file_path = "spec/examples.txt"
|
76
|
+
|
77
|
+
# Limits the available syntax to the non-monkey patched syntax that is
|
78
|
+
# recommended. For more details, see:
|
79
|
+
# - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
|
80
|
+
# - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
|
81
|
+
# - http://myronmars.to/n/dev-blog/2014/05/notable-changes-in-rspec-3#new__config_option_to_disable_rspeccore_monkey_patching
|
82
|
+
config.disable_monkey_patching!
|
83
|
+
|
84
|
+
# This setting enables warnings. It's recommended, but in some cases may
|
85
|
+
# be too noisy due to issues in dependencies.
|
86
|
+
config.warnings = true
|
87
|
+
|
88
|
+
# Many RSpec users commonly either run the entire suite or an individual
|
89
|
+
# file, and it's useful to allow more verbose output when running an
|
90
|
+
# individual spec file.
|
91
|
+
if config.files_to_run.one?
|
92
|
+
# Use the documentation formatter for detailed output,
|
93
|
+
# unless a formatter has already been configured
|
94
|
+
# (e.g. via a command-line flag).
|
95
|
+
config.default_formatter = 'doc'
|
96
|
+
end
|
97
|
+
|
98
|
+
# Print the 10 slowest examples and example groups at the
|
99
|
+
# end of the spec run, to help surface which specs are running
|
100
|
+
# particularly slow.
|
101
|
+
config.profile_examples = 10
|
102
|
+
|
103
|
+
# Run specs in random order to surface order dependencies. If you find an
|
104
|
+
# order dependency and want to debug it, you can fix the order by providing
|
105
|
+
# the seed, which is printed after each run.
|
106
|
+
# --seed 1234
|
107
|
+
config.order = :random
|
108
|
+
|
109
|
+
# Seed global randomization in this process using the `--seed` CLI option.
|
110
|
+
# Setting this allows you to use `--seed` to deterministically reproduce
|
111
|
+
# test failures related to randomization by passing the same `--seed` value
|
112
|
+
# as the one that triggered the failure.
|
113
|
+
Kernel.srand config.seed
|
114
|
+
=end
|
115
|
+
end
|
metadata
ADDED
@@ -0,0 +1,179 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: chronos_authz
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Marianne Angelie del Mundo
|
8
|
+
- Rodette Pedro
|
9
|
+
- JR Respino
|
10
|
+
- Jayson Uy
|
11
|
+
autorequire:
|
12
|
+
bindir: bin
|
13
|
+
cert_chain: []
|
14
|
+
date: 2018-08-10 00:00:00.000000000 Z
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: railties
|
18
|
+
requirement: !ruby/object:Gem::Requirement
|
19
|
+
requirements:
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '4.2'
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '4.2'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: request_store
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
requirements:
|
34
|
+
- - ">="
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: '0'
|
37
|
+
type: :runtime
|
38
|
+
prerelease: false
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
- !ruby/object:Gem::Dependency
|
45
|
+
name: activesupport
|
46
|
+
requirement: !ruby/object:Gem::Requirement
|
47
|
+
requirements:
|
48
|
+
- - ">="
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: '0'
|
51
|
+
type: :runtime
|
52
|
+
prerelease: false
|
53
|
+
version_requirements: !ruby/object:Gem::Requirement
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: '0'
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: rake
|
60
|
+
requirement: !ruby/object:Gem::Requirement
|
61
|
+
requirements:
|
62
|
+
- - ">="
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: 11.3.0
|
65
|
+
type: :development
|
66
|
+
prerelease: false
|
67
|
+
version_requirements: !ruby/object:Gem::Requirement
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: 11.3.0
|
72
|
+
- !ruby/object:Gem::Dependency
|
73
|
+
name: rspec-rails
|
74
|
+
requirement: !ruby/object:Gem::Requirement
|
75
|
+
requirements:
|
76
|
+
- - ">="
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: '0'
|
79
|
+
type: :development
|
80
|
+
prerelease: false
|
81
|
+
version_requirements: !ruby/object:Gem::Requirement
|
82
|
+
requirements:
|
83
|
+
- - ">="
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
- !ruby/object:Gem::Dependency
|
87
|
+
name: simplecov
|
88
|
+
requirement: !ruby/object:Gem::Requirement
|
89
|
+
requirements:
|
90
|
+
- - ">="
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: '0'
|
93
|
+
type: :development
|
94
|
+
prerelease: false
|
95
|
+
version_requirements: !ruby/object:Gem::Requirement
|
96
|
+
requirements:
|
97
|
+
- - ">="
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
version: '0'
|
100
|
+
- !ruby/object:Gem::Dependency
|
101
|
+
name: simplecov-console
|
102
|
+
requirement: !ruby/object:Gem::Requirement
|
103
|
+
requirements:
|
104
|
+
- - ">="
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
version: '0'
|
107
|
+
type: :development
|
108
|
+
prerelease: false
|
109
|
+
version_requirements: !ruby/object:Gem::Requirement
|
110
|
+
requirements:
|
111
|
+
- - ">="
|
112
|
+
- !ruby/object:Gem::Version
|
113
|
+
version: '0'
|
114
|
+
description: A declarative authorization Rack middleware that supports custom authorization
|
115
|
+
logic on a per-resource basis
|
116
|
+
email:
|
117
|
+
- marianne@chronoscloud.com
|
118
|
+
- rodette@chronoscloud.com
|
119
|
+
- jr@chronoscloud.com
|
120
|
+
- jayson@chronoscloud.com
|
121
|
+
executables: []
|
122
|
+
extensions: []
|
123
|
+
extra_rdoc_files: []
|
124
|
+
files:
|
125
|
+
- ".gitignore"
|
126
|
+
- ".rspec"
|
127
|
+
- Gemfile
|
128
|
+
- Gemfile.lock
|
129
|
+
- README.md
|
130
|
+
- Rakefile
|
131
|
+
- chronos_authz.gemspec
|
132
|
+
- lib/chronos_authz.rb
|
133
|
+
- lib/chronos_authz/acl.rb
|
134
|
+
- lib/chronos_authz/authorizer.rb
|
135
|
+
- lib/chronos_authz/configuration.rb
|
136
|
+
- lib/chronos_authz/rule.rb
|
137
|
+
- lib/chronos_authz/user.rb
|
138
|
+
- lib/chronos_authz/validations/options_validator.rb
|
139
|
+
- lib/chronos_authz/validations/validation_error.rb
|
140
|
+
- lib/chronos_authz/version.rb
|
141
|
+
- spec/acl_spec.rb
|
142
|
+
- spec/authorizer_spec.rb
|
143
|
+
- spec/config/authorizer_acl_test.yml
|
144
|
+
- spec/config/error_page.html
|
145
|
+
- spec/helpers/custom_rule.rb
|
146
|
+
- spec/options_validator_spec.rb
|
147
|
+
- spec/spec_helper.rb
|
148
|
+
homepage: https://github.com/chronoscloud/chronoscloud-authz
|
149
|
+
licenses:
|
150
|
+
- N/A
|
151
|
+
metadata: {}
|
152
|
+
post_install_message:
|
153
|
+
rdoc_options: []
|
154
|
+
require_paths:
|
155
|
+
- lib
|
156
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
157
|
+
requirements:
|
158
|
+
- - ">="
|
159
|
+
- !ruby/object:Gem::Version
|
160
|
+
version: '2.4'
|
161
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
162
|
+
requirements:
|
163
|
+
- - ">="
|
164
|
+
- !ruby/object:Gem::Version
|
165
|
+
version: '0'
|
166
|
+
requirements: []
|
167
|
+
rubyforge_project:
|
168
|
+
rubygems_version: 2.6.8
|
169
|
+
signing_key:
|
170
|
+
specification_version: 4
|
171
|
+
summary: A minimal and declarative authorization layer
|
172
|
+
test_files:
|
173
|
+
- spec/acl_spec.rb
|
174
|
+
- spec/authorizer_spec.rb
|
175
|
+
- spec/config/authorizer_acl_test.yml
|
176
|
+
- spec/config/error_page.html
|
177
|
+
- spec/helpers/custom_rule.rb
|
178
|
+
- spec/options_validator_spec.rb
|
179
|
+
- spec/spec_helper.rb
|