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.
@@ -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/controller_methods"
4
- require "active_entry/railtie" if defined? Rails::Railtie
4
+ require "active_entry/base"
5
+ require "active_entry/policy_finder"
5
6
 
6
- module ActiveEntry
7
- # Verifies that #authenticate! has been called in the controller.
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
- # Verifies that #authorize! has been called in the controller.
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
- general_decision_maker_defined = respond_to? general_decision_maker_method_name, true
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
@@ -1,82 +1,65 @@
1
- # @author Tobias Feistmantl
2
1
  module ActiveEntry
3
- # Generic authorization error.
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
- # Error for controllers in which authorization isn't handled.
12
- #
13
- # @raise [AuthorizationNotPerformedError]
14
- # if authorize! is not called
15
- # in the controller class.
16
- class AuthorizationNotPerformedError < AuthorizationError
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
- # Error for controllers in which authorization decision maker is missing.
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
- # Error if user unauthorized.
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
- def initialize(error={})
39
- @error = error
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
- # Error for controllers in which authentication isn't handled.
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
- # Error for controllers in which authentication decision maker is missing.
60
- #
61
- # @raise [AuthenticationDecisionMakerMissingError]
62
- # if the #authenticated? method isn't defined
63
- # in the controller class.
64
- class AuthenticationDecisionMakerMissingError < AuthenticationError
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
- # Error if user not authenticated
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(error={})
79
- @error = error
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,4 @@
1
+ module ActiveEntry
2
+ module Generators
3
+ end
4
+ 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
@@ -1,9 +1,9 @@
1
+ require_relative '../../app/helpers/active_entry/view_helper'
2
+
1
3
  module ActiveEntry
2
- class Railtie < ::Rails::Railtie
3
- initializer 'active_entry.include_in_action_controller' do
4
- ActiveSupport.on_load :action_controller do
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