active_entry 1.2.4 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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