active_entry 1.2.4 → 2.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 +4 -4
- data/README.md +275 -141
- data/app/controllers/concerns/active_entry/concern.rb +57 -0
- data/app/helpers/active_entry/view_helper.rb +20 -0
- data/lib/active_entry.rb +7 -88
- data/lib/active_entry/base.rb +66 -0
- data/lib/active_entry/errors.rb +44 -61
- data/lib/active_entry/generators.rb +4 -0
- data/lib/active_entry/policy_finder.rb +25 -0
- data/lib/active_entry/railtie.rb +6 -6
- data/lib/active_entry/rspec.rb +56 -0
- data/lib/active_entry/version.rb +1 -1
- data/lib/generators/active_entry/install/USAGE +8 -0
- data/lib/generators/active_entry/install/install_generator.rb +9 -0
- data/lib/generators/active_entry/install/templates/application_policy.rb +7 -0
- data/lib/generators/policy/USAGE +8 -0
- data/lib/generators/policy/policy_generator.rb +7 -0
- data/lib/generators/policy/templates/policy.rb +53 -0
- data/lib/generators/rspec/USAGE +8 -0
- data/lib/generators/rspec/policy_generator.rb +11 -0
- data/lib/generators/rspec/templates/policy_spec.rb +5 -0
- metadata +19 -5
- data/lib/active_entry/controller_methods.rb +0 -99
@@ -0,0 +1,57 @@
|
|
1
|
+
require "active_support/concern"
|
2
|
+
|
3
|
+
module ActiveEntry
|
4
|
+
module ControllerConcern
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
class_methods do
|
8
|
+
# Methods .authenticate_now!, .authorize_now!, and .pass_now!
|
9
|
+
[:authenticate, :authorize, :pass].each do |method_name|
|
10
|
+
define_method "#{method_name}_now!" do
|
11
|
+
before_action do
|
12
|
+
args = {}
|
13
|
+
instance_variables.collect{ |v| v.to_s.remove("@").to_sym }.each do |name|
|
14
|
+
value = instance_variable_get ["@", name].join
|
15
|
+
next if value.nil?
|
16
|
+
args[name] = value
|
17
|
+
end
|
18
|
+
send "#{method_name}!", **args
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def verify_authentication!
|
24
|
+
after_action do
|
25
|
+
raise AuthenticationNotPerformedError.new(self.class, action_name) unless @__authentication_done
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def verify_authorization!
|
30
|
+
after_action do
|
31
|
+
raise AuthorizationNotPerformedError.new(self.class, action_name) unless @__authorization_done
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def pass! **args
|
37
|
+
authenticate! **args
|
38
|
+
authorize! **args
|
39
|
+
end
|
40
|
+
|
41
|
+
def authenticate! **args
|
42
|
+
policy_class::Authentication.pass! action_name, **args unless @__authentication_done
|
43
|
+
@__authentication_done = true
|
44
|
+
end
|
45
|
+
|
46
|
+
def authorize! **args
|
47
|
+
policy_class::Authorization.pass! action_name, **args unless @__authorization_done
|
48
|
+
@__authorization_done = true
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def policy_class
|
54
|
+
PolicyFinder.policy_for self.class.name.remove("Controller")
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module ActiveEntry
|
2
|
+
module ViewHelper
|
3
|
+
def authorized_for? controller_name, action, **args
|
4
|
+
controller_name = controller_name.to_s.camelize.remove "Controller"
|
5
|
+
policy = ActiveEntry::PolicyFinder.policy_for controller_name
|
6
|
+
policy::Authorization.pass? action, **args
|
7
|
+
end
|
8
|
+
|
9
|
+
def link_to_if_authorized name = nil, options = nil, html_options = nil, &block
|
10
|
+
url = url_for options
|
11
|
+
method = options&.is_a?(Hash) && options[:method] ? options[:method].to_s.upcase : "GET"
|
12
|
+
|
13
|
+
recognized_path = Rails.application.routes.recognize_path(url, method: method)
|
14
|
+
|
15
|
+
authorized = authorized_for? recognized_path[:controller], recognized_path[:action]
|
16
|
+
|
17
|
+
link_to name, options, html_options, &block if authorized
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/active_entry.rb
CHANGED
@@ -1,94 +1,13 @@
|
|
1
1
|
require "active_entry/version"
|
2
|
+
require "active_entry/generators"
|
2
3
|
require "active_entry/errors"
|
3
|
-
require "active_entry/
|
4
|
-
require "active_entry/
|
4
|
+
require "active_entry/base"
|
5
|
+
require "active_entry/policy_finder"
|
5
6
|
|
6
|
-
|
7
|
-
|
8
|
-
def verify_authentication!
|
9
|
-
raise ActiveEntry::AuthenticationNotPerformedError unless @_authentication_done == true
|
10
|
-
end
|
11
|
-
|
12
|
-
# Authenticates the user
|
13
|
-
def authenticate!
|
14
|
-
general_decision_maker_method_name = :authenticated?
|
15
|
-
scoped_decision_maker_method_name = [action_name, :authenticated?].join("_").to_sym
|
16
|
-
|
17
|
-
general_decision_maker_defined = respond_to? general_decision_maker_method_name, true
|
18
|
-
scoped_decision_maker_defined = respond_to? scoped_decision_maker_method_name, true
|
19
|
-
|
20
|
-
# Check if a scoped decision maker method is defined and use it over
|
21
|
-
# general decision maker method.
|
22
|
-
decision_maker_to_use = scoped_decision_maker_defined ? scoped_decision_maker_method_name : general_decision_maker_method_name
|
23
|
-
|
24
|
-
# Raise an error if the #authenticate? action isn't defined.
|
25
|
-
#
|
26
|
-
# This ensures that you actually do authentication in your controller.
|
27
|
-
if !scoped_decision_maker_defined && !general_decision_maker_defined
|
28
|
-
raise ActiveEntry::AuthenticationDecisionMakerMissingError
|
29
|
-
end
|
30
|
-
|
31
|
-
error = {}
|
32
|
-
|
33
|
-
if method(decision_maker_to_use).arity > 0
|
34
|
-
is_authenticated = send decision_maker_to_use, error
|
35
|
-
else
|
36
|
-
is_authenticated = send decision_maker_to_use
|
37
|
-
end
|
38
|
-
|
39
|
-
# Tell #verify_authentication! that authentication
|
40
|
-
# has been performed.
|
41
|
-
@_authentication_done = true
|
42
|
-
|
43
|
-
# If the authenticated? method returns not true
|
44
|
-
# it raises the ActiveEntry::NotAuthenticatedError.
|
45
|
-
#
|
46
|
-
# Use the .rescue_from method from ActionController::Base
|
47
|
-
# to catch the exception and show the user a proper error message.
|
48
|
-
raise ActiveEntry::NotAuthenticatedError.new(error) unless is_authenticated == true
|
49
|
-
end
|
7
|
+
require_relative "../app/controllers/concerns/active_entry/concern" if defined?(ActionController::Base)
|
8
|
+
require 'active_entry/railtie' if defined?(Rails)
|
50
9
|
|
51
|
-
|
52
|
-
def verify_authorization!
|
53
|
-
raise ActiveEntry::AuthorizationNotPerformedError unless @_authorization_done == true
|
54
|
-
end
|
55
|
-
|
56
|
-
# Authorizes the user.
|
57
|
-
def authorize!
|
58
|
-
general_decision_maker_method_name = :authorized?
|
59
|
-
scoped_decision_maker_method_name = [action_name, :authorized?].join("_").to_sym
|
10
|
+
require "active_support/inflector"
|
60
11
|
|
61
|
-
|
62
|
-
scoped_decision_maker_defined = respond_to? scoped_decision_maker_method_name, true
|
63
|
-
|
64
|
-
# Check if a scoped decision maker method is defined and use it over
|
65
|
-
# general decision maker method.
|
66
|
-
decision_maker_to_use = scoped_decision_maker_defined ? scoped_decision_maker_method_name : general_decision_maker_method_name
|
67
|
-
|
68
|
-
# Raise an error if the #authorize? action isn't defined.
|
69
|
-
#
|
70
|
-
# This ensures that you actually do authorization in your controller.
|
71
|
-
if !scoped_decision_maker_defined && !general_decision_maker_defined
|
72
|
-
raise ActiveEntry::AuthorizationDecisionMakerMissingError
|
73
|
-
end
|
74
|
-
|
75
|
-
error = {}
|
76
|
-
|
77
|
-
if method(decision_maker_to_use).arity > 0
|
78
|
-
is_authorized = send(decision_maker_to_use, error)
|
79
|
-
else
|
80
|
-
is_authorized = send(decision_maker_to_use)
|
81
|
-
end
|
82
|
-
|
83
|
-
# Tell #verify_authorization! that authorization
|
84
|
-
# has been performed.
|
85
|
-
@_authorization_done = true
|
86
|
-
|
87
|
-
# If the authorized? method does not return true
|
88
|
-
# it raises the ActiveEntry::NotAuthorizedError
|
89
|
-
#
|
90
|
-
# Use the .rescue_from method from ActionController::Base
|
91
|
-
# to catch the exception and show the user a proper error message.
|
92
|
-
raise ActiveEntry::NotAuthorizedError.new(error) unless is_authorized == true
|
93
|
-
end
|
12
|
+
module ActiveEntry
|
94
13
|
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module ActiveEntry
|
2
|
+
class Base
|
3
|
+
class Authentication < Base
|
4
|
+
AUTH_ERROR = NotAuthenticatedError
|
5
|
+
|
6
|
+
def self.pass! method_name, **args
|
7
|
+
new(method_name, **args).pass!
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.pass? method_name, **args
|
11
|
+
new(method_name, **args).pass?
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class Authorization < Base
|
16
|
+
AUTH_ERROR = NotAuthorizedError
|
17
|
+
|
18
|
+
def self.pass! method_name, **args
|
19
|
+
new(method_name, **args).pass!
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.pass? method_name, **args
|
23
|
+
new(method_name, **args).pass?
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def initialize method_name, **args
|
28
|
+
@_method_name_to_entrify = method_name
|
29
|
+
@_args = args
|
30
|
+
@_args.each { |name, value| instance_variable_set ["@", name].join, value }
|
31
|
+
end
|
32
|
+
|
33
|
+
class << self
|
34
|
+
def pass! method_name, **args
|
35
|
+
Authentication.pass! method_name, **args
|
36
|
+
Authorization.pass! method_name, **args
|
37
|
+
end
|
38
|
+
|
39
|
+
def pass? method_name, **args
|
40
|
+
Authentication.pass? method_name, **args
|
41
|
+
Authorization.pass? method_name, **args
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
def pass!
|
47
|
+
pass? or raise self.class::AUTH_ERROR.new(@error, @_method_name_to_entrify, @_args)
|
48
|
+
end
|
49
|
+
|
50
|
+
def pass?
|
51
|
+
decision_maker_method.call == true
|
52
|
+
end
|
53
|
+
|
54
|
+
def success
|
55
|
+
true
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def decision_maker_method
|
61
|
+
decision_maker_method_name = [@_method_name_to_entrify, "?"].join
|
62
|
+
raise DecisionMakerMethodNotDefinedError.new(self.class, decision_maker_method_name) unless respond_to?(decision_maker_method_name)
|
63
|
+
method decision_maker_method_name
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
data/lib/active_entry/errors.rb
CHANGED
@@ -1,82 +1,65 @@
|
|
1
|
-
# @author Tobias Feistmantl
|
2
1
|
module ActiveEntry
|
3
|
-
|
4
|
-
# Other, more specific, errors inherit from this one.
|
5
|
-
#
|
6
|
-
# @raise [AuthorizationError]
|
7
|
-
# if something generic is happening.
|
8
|
-
class AuthorizationError < StandardError
|
2
|
+
class Error < StandardError
|
9
3
|
end
|
10
4
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
5
|
+
class AuthError < Error
|
6
|
+
attr_reader :error, :method, :arguments
|
7
|
+
|
8
|
+
def initialize error, method, arguments
|
9
|
+
@error = error
|
10
|
+
@method = method
|
11
|
+
@arguments = arguments
|
12
|
+
@message = "Not authenticated/authorized for method ##{@method}"
|
13
|
+
|
14
|
+
super @message
|
15
|
+
end
|
17
16
|
end
|
18
17
|
|
19
|
-
|
20
|
-
#
|
21
|
-
# @raise [AuthorizationDecisionMakerMissingError]
|
22
|
-
# if the #authorized? method isn't defined
|
23
|
-
# in the controller class.
|
24
|
-
class AuthorizationDecisionMakerMissingError < AuthorizationError
|
18
|
+
class NotAuthenticatedError < AuthError
|
25
19
|
end
|
26
20
|
|
27
|
-
|
28
|
-
|
29
|
-
# @raise [NotAuthorizedError]
|
30
|
-
# if authorized? isn't returning true.
|
31
|
-
#
|
32
|
-
# @note
|
33
|
-
# Should always be called at the end
|
34
|
-
# of the #authorize! method.
|
35
|
-
class NotAuthorizedError < AuthorizationError
|
36
|
-
attr_reader :error
|
21
|
+
class NotAuthorizedError < AuthError
|
22
|
+
end
|
37
23
|
|
38
|
-
|
39
|
-
|
24
|
+
class NotPerformedError < Error
|
25
|
+
attr_reader :class_name, :method
|
26
|
+
|
27
|
+
def initialize class_name, method
|
28
|
+
@class_name = class_name
|
29
|
+
@method = method
|
30
|
+
@message = "Auth not performed for #{@class_name}##{@method}."
|
31
|
+
|
32
|
+
super @message
|
40
33
|
end
|
41
34
|
end
|
42
35
|
|
43
|
-
|
44
|
-
# Base class for authentication errors.
|
45
|
-
#
|
46
|
-
# @raise [AuthenticationError]
|
47
|
-
# if something generic happens.
|
48
|
-
class AuthenticationError < StandardError
|
36
|
+
class AuthenticationNotPerformedError < NotPerformedError
|
49
37
|
end
|
50
38
|
|
51
|
-
|
52
|
-
#
|
53
|
-
# @raise [AuthenticationNotPerformedError]
|
54
|
-
# if authenticate! is not called
|
55
|
-
# in the controller class.
|
56
|
-
class AuthenticationNotPerformedError < AuthenticationError
|
39
|
+
class AuthorizationNotPerformedError < NotPerformedError
|
57
40
|
end
|
58
41
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
42
|
+
class NotDefinedError < Error
|
43
|
+
attr_reader :policy_name, :class_name
|
44
|
+
|
45
|
+
def initialize policy_name, class_name
|
46
|
+
@policy_name = policy_name
|
47
|
+
@class_name = class_name
|
48
|
+
@message = "Policy #{policy_name} for class #{@class_name} not defined."
|
49
|
+
|
50
|
+
super @message
|
51
|
+
end
|
65
52
|
end
|
66
53
|
|
67
|
-
|
68
|
-
|
69
|
-
# @raise [NotAuthenticatedError]
|
70
|
-
# if authenticated? isn't returning true.
|
71
|
-
#
|
72
|
-
# @note
|
73
|
-
# Should always be called at the end
|
74
|
-
# of the #authenticate! method.
|
75
|
-
class NotAuthenticatedError < AuthenticationError
|
76
|
-
attr_reader :error
|
54
|
+
class DecisionMakerMethodNotDefinedError < Error
|
55
|
+
attr_reader :policy_name, :decision_maker_method_name
|
77
56
|
|
78
|
-
def initialize
|
79
|
-
@
|
57
|
+
def initialize policy_name, decision_maker_method_name
|
58
|
+
@policy_name = policy_name
|
59
|
+
@decision_maker_method_name = decision_maker_method_name
|
60
|
+
@message = "Decision maker #{policy_name}##{decision_maker_method_name} is not defined."
|
61
|
+
|
62
|
+
super @message
|
80
63
|
end
|
81
64
|
end
|
82
65
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module ActiveEntry
|
2
|
+
class PolicyFinder
|
3
|
+
attr_reader :class_name
|
4
|
+
|
5
|
+
def initialize class_name
|
6
|
+
@class_name = class_name
|
7
|
+
end
|
8
|
+
|
9
|
+
class << self
|
10
|
+
def policy_for class_name
|
11
|
+
new(class_name).policy
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def policy
|
16
|
+
policy_class_name.safe_constantize or raise NotDefinedError.new(policy_class_name, @class_name)
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def policy_class_name
|
22
|
+
[@class_name, "Policy"].join
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/active_entry/railtie.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
|
+
require_relative '../../app/helpers/active_entry/view_helper'
|
2
|
+
|
1
3
|
module ActiveEntry
|
2
|
-
class Railtie <
|
3
|
-
initializer
|
4
|
-
ActiveSupport.on_load
|
5
|
-
::ActionController::Base.include(ActiveEntry)
|
6
|
-
end
|
4
|
+
class Railtie < Rails::Railtie
|
5
|
+
initializer "active_entry.view_helper" do
|
6
|
+
ActiveSupport.on_load(:action_view) { include ActiveEntry::ViewHelper }
|
7
7
|
end
|
8
8
|
end
|
9
|
-
end
|
9
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module ActiveEntry
|
2
|
+
module Rspec
|
3
|
+
module Matchers
|
4
|
+
extend ::RSpec::Matchers::DSL
|
5
|
+
|
6
|
+
matcher :be_authenticated_for do |action, **args|
|
7
|
+
match do |policy|
|
8
|
+
policy.pass? action, **args
|
9
|
+
end
|
10
|
+
|
11
|
+
description do
|
12
|
+
"be authenticated for #{action}"
|
13
|
+
end
|
14
|
+
|
15
|
+
failure_message do |policy|
|
16
|
+
"expected that #{policy} passes authentication for #{action}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
matcher :be_authorized_for do |action, **args|
|
21
|
+
match do |policy|
|
22
|
+
policy.pass? action, **args
|
23
|
+
end
|
24
|
+
|
25
|
+
description do
|
26
|
+
"be authorized for #{action}"
|
27
|
+
end
|
28
|
+
|
29
|
+
failure_message do |policy|
|
30
|
+
"expected that #{policy} passes authorization for #{action}"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
module DSL
|
36
|
+
end
|
37
|
+
|
38
|
+
module PolicyExampleGroup
|
39
|
+
include ActiveEntry::Rspec::Matchers
|
40
|
+
|
41
|
+
def self.included(base)
|
42
|
+
base.metadata[:type] = :policy
|
43
|
+
base.extend ActiveEntry::Rspec::DSL
|
44
|
+
super
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
RSpec.configure do |config|
|
51
|
+
config.include(
|
52
|
+
ActiveEntry::Rspec::PolicyExampleGroup,
|
53
|
+
type: :policy,
|
54
|
+
file_path: %r{spec/policies}
|
55
|
+
)
|
56
|
+
end
|