diff --git a/declarative_authorization.gemspec b/declarative_authorization.gemspec index 7ca8db8..48347e8 100644 --- a/declarative_authorization.gemspec +++ b/declarative_authorization.gemspec @@ -21,4 +21,5 @@ Gem::Specification.new do |s| s.add_dependency(%q, ['~> 0.5.0']) s.add_dependency(%q, ['>= 4.2.5.2', '< 6']) + s.add_dependency(%q, ['~> 0.21']) end diff --git a/lib/declarative_authorization.rb b/lib/declarative_authorization.rb index 5ca6373..4a03f13 100644 --- a/lib/declarative_authorization.rb +++ b/lib/declarative_authorization.rb @@ -1,5 +1,6 @@ require File.join(%w{declarative_authorization helper}) require File.join(%w{declarative_authorization in_controller}) +require File.join(%w{declarative_authorization in_praxis_controller}) if defined?(ActiveRecord) require File.join(%w{declarative_authorization in_model}) require File.join(%w{declarative_authorization obligation_scope}) diff --git a/lib/declarative_authorization/in_controller.rb b/lib/declarative_authorization/in_controller.rb index d7c4939..6b3582e 100644 --- a/lib/declarative_authorization/in_controller.rb +++ b/lib/declarative_authorization/in_controller.rb @@ -1,8 +1,9 @@ -# Authorization::AuthorizationInController require File.dirname(__FILE__) + '/authorization.rb' +require File.dirname(__FILE__) + '/in_controller_common.rb' module Authorization module AuthorizationInController + include AuthorizationInControllerCommon def self.included(base) # :nodoc: base.extend(ClassMethods) @@ -11,96 +12,6 @@ def self.included(base) # :nodoc: end end - DEFAULT_DENY = false - - # If attribute_check is set for filter_access_to, decl_auth_context will try to - # load the appropriate object from the current controller's model with - # the id from params[:id]. If that fails, a 404 Not Found is often the - # right way to handle the error. If you have additional measures in place - # that restricts the find scope, handling this error as a permission denied - # might be a better way. Set failed_auto_loading_is_not_found to false - # for the latter behavior. - @@failed_auto_loading_is_not_found = true - def self.failed_auto_loading_is_not_found? - @@failed_auto_loading_is_not_found - end - def self.failed_auto_loading_is_not_found=(new_value) - @@failed_auto_loading_is_not_found = new_value - end - - # Returns the Authorization::Engine for the current controller. - def authorization_engine - @authorization_engine ||= Authorization::Engine.instance - end - - # If the current user meets the given privilege, permitted_to? returns true - # and yields to the optional block. The attribute checks that are defined - # in the authorization rules are only evaluated if an object is given - # for context. - # - # See examples for Authorization::AuthorizationHelper #permitted_to? - # - # If no object or context is specified, the controller_name is used as - # context. - # - def permitted_to?(privilege, object_or_sym = nil, options = {}) - if authorization_engine.permit!(privilege, options_for_permit(object_or_sym, options, false)) - yield if block_given? - true - else - false - end - end - - # Works similar to the permitted_to? method, but - # throws the authorization exceptions, just like Engine#permit! - def permitted_to!(privilege, object_or_sym = nil, options = {}) - authorization_engine.permit!(privilege, options_for_permit(object_or_sym, options, true)) - end - - # While permitted_to? is used for authorization, in some cases - # content should only be shown to some users without being concerned - # with authorization. E.g. to only show the most relevant menu options - # to a certain group of users. That is what has_role? should be used for. - def has_role?(*roles) - user_roles = authorization_engine.roles_for(current_user) - result = roles.all? do |role| - user_roles.include?(role) - end - yield if result and block_given? - result - end - - # Intended to be used where you want to allow users with any single listed role to view - # the content in question - def has_any_role?(*roles) - user_roles = authorization_engine.roles_for(current_user) - result = roles.any? do |role| - user_roles.include?(role) - end - yield if result and block_given? - result - end - - # As has_role? except checks all roles included in the role hierarchy - def has_role_with_hierarchy?(*roles) - user_roles = authorization_engine.roles_with_hierarchy_for(current_user) - result = roles.all? do |role| - user_roles.include?(role) - end - yield if result and block_given? - result - end - - # As has_any_role? except checks all roles included in the role hierarchy - def has_any_role_with_hierarchy?(*roles) - user_roles = authorization_engine.roles_with_hierarchy_for(current_user) - result = roles.any? do |role| - user_roles.include?(role) - end - yield if result and block_given? - result - end protected def filter_access_filter # :nodoc: diff --git a/lib/declarative_authorization/in_controller_common.rb b/lib/declarative_authorization/in_controller_common.rb new file mode 100644 index 0000000..2a6c3dc --- /dev/null +++ b/lib/declarative_authorization/in_controller_common.rb @@ -0,0 +1,103 @@ +# require File.dirname(__FILE__) + '/authorization.rb' + +module Authorization + module AuthorizationInControllerCommon + + DEFAULT_DENY = false + + def self.included(base) + base.module_eval do + # If attribute_check is set for filter_access_to, decl_auth_context will try to + # load the appropriate object from the current controller's model with + # the id from params[:id]. If that fails, a 404 Not Found is often the + # right way to handle the error. If you have additional measures in place + # that restricts the find scope, handling this error as a permission denied + # might be a better way. Set failed_auto_loading_is_not_found to false + # for the latter behavior. + @@failed_auto_loading_is_not_found = true + + def self.failed_auto_loading_is_not_found? + @@failed_auto_loading_is_not_found + end + + def self.failed_auto_loading_is_not_found=(new_value) + @@failed_auto_loading_is_not_found = new_value + end + end + end + + # Returns the Authorization::Engine for the current controller. + def authorization_engine + @authorization_engine ||= Authorization::Engine.instance + end + + # If the current user meets the given privilege, permitted_to? returns true + # and yields to the optional block. The attribute checks that are defined + # in the authorization rules are only evaluated if an object is given + # for context. + # + # See examples for Authorization::AuthorizationHelper #permitted_to? + # + # If no object or context is specified, the controller_name is used as + # context. + # + def permitted_to?(privilege, object_or_sym = nil, options = {}) + if authorization_engine.permit!(privilege, options_for_permit(object_or_sym, options, false)) + yield if block_given? + true + else + false + end + end + + # Works similar to the permitted_to? method, but + # throws the authorization exceptions, just like Engine#permit! + def permitted_to!(privilege, object_or_sym = nil, options = {}) + authorization_engine.permit!(privilege, options_for_permit(object_or_sym, options, true)) + end + + # While permitted_to? is used for authorization, in some cases + # content should only be shown to some users without being concerned + # with authorization. E.g. to only show the most relevant menu options + # to a certain group of users. That is what has_role? should be used for. + def has_role?(*roles) + user_roles = authorization_engine.roles_for(current_user) + result = roles.all? do |role| + user_roles.include?(role) + end + yield if result and block_given? + result + end + + # Intended to be used where you want to allow users with any single listed role to view + # the content in question + def has_any_role?(*roles) + user_roles = authorization_engine.roles_for(current_user) + result = roles.any? do |role| + user_roles.include?(role) + end + yield if result and block_given? + result + end + + # As has_role? except checks all roles included in the role hierarchy + def has_role_with_hierarchy?(*roles) + user_roles = authorization_engine.roles_with_hierarchy_for(current_user) + result = roles.all? do |role| + user_roles.include?(role) + end + yield if result and block_given? + result + end + + # As has_any_role? except checks all roles included in the role hierarchy + def has_any_role_with_hierarchy?(*roles) + user_roles = authorization_engine.roles_with_hierarchy_for(current_user) + result = roles.any? do |role| + user_roles.include?(role) + end + yield if result and block_given? + result + end + end +end diff --git a/lib/declarative_authorization/in_praxis_controller.rb b/lib/declarative_authorization/in_praxis_controller.rb new file mode 100644 index 0000000..cbf18b7 --- /dev/null +++ b/lib/declarative_authorization/in_praxis_controller.rb @@ -0,0 +1,358 @@ +require File.dirname(__FILE__) + '/authorization.rb' +require File.dirname(__FILE__) + '/in_controller_common.rb' + +module Authorization + module AuthorizationInPraxisController + include AuthorizationInControllerCommon + + def self.included(base) # :nodoc: + base.extend(ClassMethods) + base.module_eval do + before :action do |controller| + controller.send(:filter_access_filter) + end if method_defined?(:filter_access_filter) + end + end + + def controller_name + self.class.name.demodulize.underscore + end + + def action_name + self.request.action.name + end + + def params + self.request.params + end + + def logger + self.request.env['action_dispatch.logger'] + end + + protected + def filter_access_filter # :nodoc: + permissions = self.class.all_filter_access_permissions + all_permissions = permissions.select {|p| p.actions.include?(:all)} + matching_permissions = permissions.select {|p| p.matches?(action_name)} + allowed = false + auth_exception = nil + begin + allowed = if !matching_permissions.empty? + matching_permissions.all? {|perm| perm.permit!(self)} + elsif !all_permissions.empty? + all_permissions.all? {|perm| perm.permit!(self)} + else + !DEFAULT_DENY + end + rescue NotAuthorized => e + auth_exception = e + end + + unless allowed + if all_permissions.empty? and matching_permissions.empty? + logger.warn "Permission denied: No matching filter access " + + "rule found for #{self.controller_name}.#{action_name}" + elsif auth_exception + logger.info "Permission denied: #{auth_exception}" + end + if respond_to?(:permission_denied, true) + # permission_denied needs to render or redirect + send(:permission_denied) + else + Praxis::Responses::Forbidden.new(body: "You are not allowed to access this action.") + end + end + end + + def options_for_permit(object_or_sym = nil, options = {}, bang = true) + context = object = nil + if object_or_sym.nil? + context = self.class.decl_auth_context + elsif !Authorization.is_a_association_proxy?(object_or_sym) and object_or_sym.is_a?(Symbol) + context = object_or_sym + else + object = object_or_sym + end + + result = {:object => object, + :context => context, + :skip_attribute_test => object.nil?, + :bang => bang}.merge(options) + result[:user] = current_user unless result.key?(:user) + result + end + + module ClassMethods + # + # Defines a filter to be applied according to the authorization of the + # current user. Requires at least one symbol corresponding to an + # action as parameter. The special symbol :+all+ refers to all actions. + # The all :+all+ statement is only employed if no specific statement is + # present. + # class UserController < ApplicationController + # filter_access_to :index + # filter_access_to :new, :edit + # filter_access_to :all + # ... + # end + # + # The default is to allow access unconditionally if no rule matches. + # Thus, including the +filter_access_to+ :+all+ statement is a good + # idea, implementing a default-deny policy. + # + # When the access is denied, the method +permission_denied+ is called + # on the current controller, if defined. Else, a simple "you are not + # allowed" string is output. Log.info is given more information on the + # reasons of denial. + # + # def permission_denied + # flash[:error] = 'Sorry, you are not allowed to the requested page.' + # respond_to do |format| + # format.html { redirect_to(:back) rescue redirect_to('/') } + # format.xml { head :unauthorized } + # format.js { head :unauthorized } + # end + # end + # + # By default, required privileges are inferred from the action name and + # the controller name. Thus, in UserController :+edit+ requires + # :+edit+ +users+. To specify required privilege, use the option :+require+ + # filter_access_to :new, :create, :require => :create, :context => :users + # + # Without the :+attribute_check+ option, no constraints from the + # authorization rules are enforced because for some actions (collections, + # +new+, +create+), there is no object to evaluate conditions against. To + # allow attribute checks on all actions, it is a common pattern to provide + # custom objects through +before_actions+: + # class BranchesController < ApplicationController + # before do + # load_company + # end + # before actions: [:index, :new, :create] + # new_branch_from_company_and_params + # end + # + # protected + # def new_branch_from_company_and_params + # @branch = @company.branches.new(params[:branch]) + # end + # end + # NOTE: +before actions+ need to be defined before the first + # +filter_access_to+ call. + # + # For further customization, a custom filter expression may be formulated + # in a block, which is then evaluated in the context of the controller + # on a matching request. That is, for checking two objects, use the + # following: + # filter_access_to :merge do + # permitted_to!(:update, User.find(params[:original_id])) and + # permitted_to!(:delete, User.find(params[:id])) + # end + # The block should raise a Authorization::AuthorizationError or return + # false if the access is to be denied. + # + # Later calls to filter_access_to with overlapping actions overwrite + # previous ones for that action. + # + # All options: + # [:+require+] + # Privilege required; defaults to action_name + # [:+context+] + # The privilege's context, defaults to decl_auth_context, which consists + # of controller_name, prepended by any namespaces + # [:+attribute_check+] + # Enables the check of attributes defined in the authorization rules. + # Defaults to false. If enabled, filter_access_to will use a context + # object from one of the following sources (in that order): + # * the method from the :+load_method+ option, + # * an instance variable named after the singular of the context + # (by default from the controller name, e.g. @post for PostsController), + # * a find on the context model, using +params+[:id] as id value. + # Any of these methods will only be employed if :+attribute_check+ + # is enabled. + # [:+model+] + # The data model to load a context object from. Defaults to the + # context, singularized. + # [:+load_method+] + # Specify a method by symbol or a Proc object which should be used + # to load the object. Both should return the loaded object. + # If a Proc object is given, e.g. by way of + # +lambda+, it is called in the instance of the controller. + # Example demonstrating the default behavior: + # filter_access_to :show, :attribute_check => true, + # :load_method => lambda { User.find(params[:id]) } + # + + def controller_name + self.name.demodulize.underscore + end + + def filter_access_to(*args, &filter_block) + options = args.last.is_a?(Hash) ? args.pop : {} + options = { + :require => nil, + :context => nil, + :attribute_check => false, + :model => nil, + :load_method => nil + }.merge!(options) + privilege = options[:require] + context = options[:context] + actions = args.flatten + + filter_access_permissions.each do |perm| + perm.remove_actions(actions) + end + filter_access_permissions << + PraxisControllerPermission.new(actions, privilege, context, + options[:attribute_check], + options[:model], + options[:load_method], + filter_block) + end + + # Disables authorization entirely. Requires at least one symbol corresponding + # to an action as parameter. The special symbol :+all+ refers to all actions. + # The all :+all+ statement is only employed if no specific statement is + # present. + def no_filter_access_to(*args) + filter_access_to args do + true + end + end + + # Collecting all the ControllerPermission objects from the controller + # hierarchy. Permissions for actions are overwritten by calls to + # filter_access_to in child controllers with the same action. + def all_filter_access_permissions # :nodoc: + ancestors.inject([]) do |perms, mod| + if mod.respond_to?(:filter_access_permissions, true) + perms + + mod.filter_access_permissions.collect do |p1| + p1.clone.remove_actions(perms.inject(Set.new) {|actions, p2| actions + p2.actions}) + end + else + perms + end + end + end + + # Returns the context for authorization checks in the current controller. + # Uses the controller_name and prepends any namespaces underscored and + # joined with underscores. + # + # E.g. + # AllThosePeopleController => :all_those_people + # AnyName::Space::ThingsController => :any_name_space_things + # + def decl_auth_context + prefixes = name.split('::')[0..-2].map(&:underscore) + ((prefixes + [controller_name]) * '_').to_sym + end + + protected + + def filter_access_permissions # :nodoc: + unless filter_access_permissions? + ancestors[1..-1].reverse.each do |mod| + mod.filter_access_permissions if mod.respond_to?(:filter_access_permissions, true) + end + end + class_variable_set(:@@declarative_authorization_permissions, {}) unless filter_access_permissions? + class_variable_get(:@@declarative_authorization_permissions)[self.name] ||= [] + end + + def filter_access_permissions? # :nodoc: + class_variable_defined?(:@@declarative_authorization_permissions) + end + + def actions_from_option(option) # :nodoc: + case option + when nil + {} + when Symbol, String + {option.to_sym => option.to_sym} + when Hash + option + when Enumerable + option.each_with_object({}) do |action, hash| + if action.is_a?(Array) + raise "Unexpected option format: #{option.inspect}" if action.length != 2 + hash[action.first] = action.last + else + hash[action.to_sym] = action.to_sym + end + end + end + end + end + end + + class PraxisControllerPermission # :nodoc: + attr_reader :actions, :privilege, :context, :attribute_check + def initialize(actions, privilege, context, attribute_check = false, + load_object_model = nil, load_object_method = nil, + filter_block = nil) + @actions = actions.to_set + @privilege = privilege + @context = context + @load_object_model = load_object_model + @load_object_method = load_object_method + @filter_block = filter_block + @attribute_check = attribute_check + end + + def matches?(action_name) + @actions.include?(action_name.to_sym) + end + + def permit!(contr) + if @filter_block + return contr.instance_eval(&@filter_block) + end + object = @attribute_check ? load_object(contr) : nil + privilege = @privilege || :"#{contr.action_name}" + + contr.authorization_engine.permit!(privilege, + :user => contr.send(:current_user), + :object => object, + :skip_attribute_test => !@attribute_check, + :context => @context || contr.class.decl_auth_context) + end + + def remove_actions(actions) + @actions -= actions + self + end + + private + + def load_object(contr) + if @load_object_method and @load_object_method.is_a?(Symbol) + contr.send(@load_object_method) + elsif @load_object_method and @load_object_method.is_a?(Proc) + contr.instance_eval(&@load_object_method) + else + load_object_model = @load_object_model || + (@context ? @context.to_s.classify.constantize : contr.controller_name.classify.constantize) + load_object_model = load_object_model.classify.constantize if load_object_model.is_a?(String) + instance_var = "@#{load_object_model.name.demodulize.underscore}" + object = contr.instance_variable_get(instance_var) + unless object + begin + object = load_object_model.find(contr.params[:id]) + rescue => e + contr.logger.debug("filter_access_to tried to find " + + "#{load_object_model} from params[:id] " + + "(#{contr.params[:id].inspect}), because attribute_check is enabled " + + "and #{instance_var.to_s} isn't set, but failed: #{e.class.name}: #{e}") + raise if AuthorizationInController.failed_auto_loading_is_not_found? + end + contr.instance_variable_set(instance_var, object) + end + object + end + end + end +end diff --git a/test/model_test.rb b/test/model_test.rb index 0d6d227..dc3d7d5 100644 --- a/test/model_test.rb +++ b/test/model_test.rb @@ -1436,10 +1436,10 @@ def test_model_security_write_not_allowed_no_privilege } Authorization::Engine.instance(reader) - Authorization.current_user = MockUser.new(:test_role) + Authorization.stubs(:current_user).returns(MockUser.new(:test_role)) assert(object = TestModelSecurityModel.create) - Authorization.current_user = MockUser.new(:test_role_restricted) + Authorization.stubs(:current_user).returns(MockUser.new(:test_role_restricted)) assert_raise Authorization::NotAuthorized do object.update_attributes(:attr_2 => 2) end @@ -1503,11 +1503,11 @@ def test_model_security_with_and_without_find_restrictions } Authorization::Engine.instance(reader) - Authorization.current_user = MockUser.new(:test_role_unrestricted) + Authorization.stubs(:current_user).returns(MockUser.new(:test_role_unrestricted)) object = TestModelSecurityModel.create :attr => 2 object_with_find = TestModelSecurityModelWithFind.create :attr => 2 - Authorization.current_user = MockUser.new(:test_role) + Authorization.stubs(:current_user).returns(MockUser.new(:test_role)) assert_nothing_raised do object.class.find(object.id) end @@ -1559,9 +1559,9 @@ def test_model_security_delete_unallowed } Authorization::Engine.instance(reader) - Authorization.current_user = MockUser.new(:test_role_unrestricted) + Authorization.stubs(:current_user).returns(MockUser.new(:test_role_unrestricted)) object = TestModelSecurityModel.create :attr => 2 - Authorization.current_user = MockUser.new(:test_role) + Authorization.stubs(:current_user).returns(MockUser.new(:test_role)) assert_raise Authorization::AttributeAuthorizationError do object.destroy end diff --git a/test/praxis_controller_test.rb b/test/praxis_controller_test.rb new file mode 100644 index 0000000..c7bf4e7 --- /dev/null +++ b/test/praxis_controller_test.rb @@ -0,0 +1,381 @@ +require 'test_helper' + +class BasicPraxisControllerTest < PraxisTestCase + + def setup + header('X-API-Version', '1') + end + + def test_filter_access_to_receiving_an_explicit_array + reader = Authorization::Reader::DSLReader.new + + reader.parse %{ + authorization do + role :test_action_group_2 do + has_permission_on :praxis_dummy_controllers_basic, :to => :action_group_action_2 + end + end + } + + request!(PraxisDummy::Controllers::Basic, MockUser.new(:test_action_group_2), "/praxis_test_engine/basic/action_group_action_2", reader) + assert_equal 200, response.status + + request!(PraxisDummy::Controllers::Basic, MockUser.new(:test_action_group_2), "/praxis_test_engine/basic/action_group_action_1", reader) + assert_equal 403, response.status + + request!(PraxisDummy::Controllers::Basic, nil, "/praxis_test_engine/basic/action_group_action_2", reader) + assert_equal 403, response.status + end + + def test_filter_access + reader = Authorization::Reader::DSLReader.new + reader.parse %{ + authorization do + role :test_role do + has_permission_on :permissions, :to => :test + has_permission_on :praxis_dummy_controllers_basic, :to => :show_stuff + end + end + } + + request!(PraxisDummy::Controllers::Basic, MockUser.new(:test_role), "/praxis_test_engine/basic/test_action", reader) + assert_equal 200, response.status + + request!(PraxisDummy::Controllers::Basic, MockUser.new(:test_role), "/praxis_test_engine/basic/test_action_2", reader) + assert_equal 403, response.status + + request!(PraxisDummy::Controllers::Basic, MockUser.new(:test_role_2), "/praxis_test_engine/basic/test_action_2", reader) + assert_equal 403, response.status + + request!(PraxisDummy::Controllers::Basic, MockUser.new(:test_role), "/praxis_test_engine/basic/show_stuff/99", reader) + assert_equal 200, response.status + end + + def test_filter_access_multi_actions + reader = Authorization::Reader::DSLReader.new + reader.parse %{ + authorization do + role :test_role do + has_permission_on :permissions, :to => :test + end + end + } + + request!(PraxisDummy::Controllers::Basic, MockUser.new(:test_role), "/praxis_test_engine/basic/", reader, method: :post) + assert_equal 200, response.status + end + + def test_filter_access_unprotected_actions + reader = Authorization::Reader::DSLReader.new + reader.parse %{ + authorization do + role :test_role do + end + end + } + request!(PraxisDummy::Controllers::Basic, MockUser.new(:test_role), "/praxis_test_engine/basic/unprotected_action", reader) + assert_equal 200, response.status + end + + def test_filter_access_priv_hierarchy + reader = Authorization::Reader::DSLReader.new + reader.parse %{ + privileges do + privilege :read do + includes :list, :show_stuff + end + end + authorization do + role :test_role do + has_permission_on :praxis_dummy_controllers_basic, :to => :read + end + end + } + request!(PraxisDummy::Controllers::Basic, MockUser.new(:test_role), "/praxis_test_engine/basic/show_stuff/99", reader) + assert_equal 200, response.status + end + + def test_filter_access_skip_attribute_test + reader = Authorization::Reader::DSLReader.new + reader.parse %{ + authorization do + role :test_role do + has_permission_on :permissions, :to => :test do + if_attribute :id => is { user } + end + end + end + } + request!(PraxisDummy::Controllers::Basic, MockUser.new(:test_role), "/praxis_test_engine/basic/new", reader) + assert_equal 200, response.status + end + + + def test_existing_instance_var_remains_unchanged + reader = Authorization::Reader::DSLReader.new + reader.parse %{ + authorization do + role :test_role do + has_permission_on :permissions, :to => :test do + if_attribute :id => is { 5 } + end + end + end + } + PraxisDummy::Controllers::Basic.any_instance.expects(:instance_variable_get).once.with('@mock_model').returns(PraxisDummy::Models::MockModel.new(id: 5)) + PraxisDummy::Controllers::Basic.any_instance.expects(:instance_variable_set).with('@mock_model', anything).never + request!(PraxisDummy::Controllers::Basic, MockUser.new(:test_role), "/praxis_test_engine/basic/edit", reader) + assert_equal 200, response.status + end + + def test_permitted_to_without_context__no_current_user + reader = Authorization::Reader::DSLReader.new + reader.parse %{ + authorization do + role :test_role do + has_permission_on :praxis_dummy_controllers_basic, :to => :foo + end + end + } + PraxisDummy::Controllers::Basic.any_instance.stubs(:authorization_engine).returns(Authorization::Engine.new(reader)) + controller = PraxisDummy::Controllers::Basic.new({}) + assert_equal false, controller.permitted_to?(:foo) + assert_equal false, controller.permitted_to?(:bar) + end + + def test_permitted_to_without_context + reader = Authorization::Reader::DSLReader.new + reader.parse %{ + authorization do + role :test_role do + has_permission_on :praxis_dummy_controllers_basic, :to => :foo + end + end + } + PraxisDummy::Controllers::Basic.any_instance.stubs(:current_user).returns(MockUser.new(:test_role)) + PraxisDummy::Controllers::Basic.any_instance.stubs(:authorization_engine).returns(Authorization::Engine.new(reader)) + controller = PraxisDummy::Controllers::Basic.new({}) + assert controller.permitted_to?(:foo) + assert_equal false, controller.permitted_to?(:bar) + end +end + + +class FilterAccessToAllPraxisControllerTest < PraxisTestCase + + def setup + header('X-API-Version', '1') + end + + def test_filter_access_all + reader = Authorization::Reader::DSLReader.new + reader.parse %{ + authorization do + role :test_role do + has_permission_on :permissions, :to => :test + has_permission_on :praxis_dummy_controllers_all, :to => :show + end + end + } + + request!(PraxisDummy::Controllers::All, MockUser.new(:test_role), '/praxis_test_engine/all/show', reader) + assert_equal 200, response.status + + request!(PraxisDummy::Controllers::All, MockUser.new(:test_role), '/praxis_test_engine/all/view', reader) + assert_equal 200, response.status + + request!(PraxisDummy::Controllers::All, MockUser.new(:test_role_2), '/praxis_test_engine/all/show', reader) + assert_equal 403, response.status + end +end + + +class LoadMethodPraxisControllerTest < PraxisTestCase + + def setup + header('X-API-Version', '1') + end + + def teardown + Authorization::AuthorizationInController.failed_auto_loading_is_not_found = true + end + + def test_filter_access_with_object_load + reader = Authorization::Reader::DSLReader.new + reader.parse %{ + authorization do + role :test_role do + has_permission_on :praxis_dummy_controllers_load_method, :to => [:show, :edit] do + if_attribute :id => 1 + if_attribute :id => "1" + end + end + end + } + + request!(PraxisDummy::Controllers::LoadMethod, MockUser.new(:test_role), '/praxis_test_engine/load_method/show', reader, :id => 2) + assert_equal 403, response.status + + request!(PraxisDummy::Controllers::LoadMethod, MockUser.new(:test_role), '/praxis_test_engine/load_method/show', reader, :id => 1) + assert_equal 200, response.status + + PraxisDummy::Controllers::LoadMethod.any_instance.expects(:instance_variable_set).once.with('@load_method', ::LoadMethod.new(id: 1)) + request!(PraxisDummy::Controllers::LoadMethod, MockUser.new(:test_role), '/praxis_test_engine/load_method/edit', reader, :id => 1) + assert_equal 200, response.status + end + + def test_filter_access_object_load_without_param + reader = Authorization::Reader::DSLReader.new + reader.parse %{ + authorization do + role :test_role do + has_permission_on :praxis_dummy_controllers_load_method, :to => [:show_id_not_required, :edit] do + if_attribute :id => is {"1"} + end + end + end + } + + Authorization::AuthorizationInController.failed_auto_loading_is_not_found = true + request!(PraxisDummy::Controllers::LoadMethod, MockUser.new(:test_role), '/praxis_test_engine/load_method/show_id_not_required', reader) + assert_equal 500, response.status + + Authorization::AuthorizationInController.failed_auto_loading_is_not_found = false + request!(PraxisDummy::Controllers::LoadMethod, MockUser.new(:test_role), '/praxis_test_engine/load_method/show_id_not_required', reader) + assert_equal 403, response.status + end + + def test_filter_access_with_object_load_custom + reader = Authorization::Reader::DSLReader.new + reader.parse %{ + authorization do + role :test_role do + has_permission_on :praxis_dummy_controllers_load_method, :to => :view do + if_attribute :test => is {2} + end + has_permission_on :praxis_dummy_controllers_load_method, :to => :update do + if_attribute :test => is {1} + end + has_permission_on :praxis_dummy_controllers_load_method, :to => :delete do + if_attribute :test => is {2} + end + end + end + } + + request!(PraxisDummy::Controllers::LoadMethod, MockUser.new(:test_role), '/praxis_test_engine/load_method/delete', reader) + assert_equal 403, response.status + + request!(PraxisDummy::Controllers::LoadMethod, MockUser.new(:test_role), '/praxis_test_engine/load_method/update', reader) + assert_equal 200, response.status + + PraxisDummy::Controllers::LoadMethod.any_instance.expects(:load_method).twice.returns(PraxisDummy::Models::MockModel.new(test: 2)) + request!(PraxisDummy::Controllers::LoadMethod, MockUser.new(:test_role), '/praxis_test_engine/load_method/view', reader) + assert_equal 200, response.status + + request!(PraxisDummy::Controllers::LoadMethod, MockUser.new(:test_role_2), '/praxis_test_engine/load_method/view', reader) + assert_equal 403, response.status + end + + def test_filter_access_custom + reader = Authorization::Reader::DSLReader.new + reader.parse %{ + authorization do + role :test_role do + has_permission_on :praxis_dummy_controllers_load_method, :to => :edit + end + role :test_role_2 do + has_permission_on :praxis_dummy_controllers_load_method, :to => :create + end + end + } + + request!(PraxisDummy::Controllers::LoadMethod, MockUser.new(:test_role), '/praxis_test_engine/load_method/create', reader) + assert_equal 200, response.status + + request!(PraxisDummy::Controllers::LoadMethod, MockUser.new(:test_role_2), '/praxis_test_engine/load_method/create', reader) + assert_equal 403, response.status + end +end + + +class AccessOverwritePraxisControllerTest < PraxisTestCase + + def setup + header('X-API-Version', '1') + end + + def test_filter_access_overwrite + reader = Authorization::Reader::DSLReader.new + reader.parse %{ + authorization do + role :test_role do + has_permission_on :permissions, :to => :test + end + + role :test_role_2 do + has_permission_on :permissions_2, :to => :test + end + end + } + request!(PraxisDummy::Controllers::Overwrite, MockUser.new(:test_role), '/praxis_test_engine/overwrite/test_action_2', reader) + assert_equal 403, response.status + + request!(PraxisDummy::Controllers::Overwrite, MockUser.new(:test_role), '/praxis_test_engine/overwrite/test_action', reader) + assert_equal 200, response.status + end +end + + +class PluralizationPraxisControllerTest < PraxisTestCase + + def setup + header('X-API-Version', '1') + end + + def test_filter_access_people_controller + reader = Authorization::Reader::DSLReader.new + reader.parse %{ + authorization do + role :test_role do + has_permission_on :praxis_dummy_controllers_people, :to => :show + end + end + } + + request!(PraxisDummy::Controllers::People, MockUser.new(:test_role), '/praxis_test_engine/people/show', reader) + assert_equal 200, response.status + end +end + + +class NameSpacedPraxisControllerTest < PraxisTestCase + + def setup + header('X-API-Version', '1') + end + + def test_context + reader = Authorization::Reader::DSLReader.new + reader.parse %{ + authorization do + role :permitted_role do + has_permission_on :praxis_dummy_controllers_name_spaced, :to => :show + has_permission_on :name_spaced, :to => :update + end + role :prohibited_role do + has_permission_on :praxis_dummy_controllers_name_spaced, :to => :update + has_permission_on :name_spaced, :to => :show + end + end + } + request!(PraxisDummy::Controllers::NameSpaced, MockUser.new(:permitted_role), '/praxis_test_engine/name_spaced/show', reader) + assert_equal 200, response.status + request!(PraxisDummy::Controllers::NameSpaced, MockUser.new(:prohibited_role), '/praxis_test_engine/name_spaced/show', reader) + assert 403, response.status + request!(PraxisDummy::Controllers::NameSpaced, MockUser.new(:permitted_role), '/praxis_test_engine/name_spaced/update', reader) + assert_equal 200, response.status + request!(PraxisDummy::Controllers::NameSpaced, MockUser.new(:prohibited_role), '/praxis_test_engine/name_spaced/update', reader) + assert 403, response.status + end +end diff --git a/test/praxis_dummy/app/controllers/all.rb b/test/praxis_dummy/app/controllers/all.rb new file mode 100644 index 0000000..d4014ca --- /dev/null +++ b/test/praxis_dummy/app/controllers/all.rb @@ -0,0 +1,25 @@ + + +module PraxisDummy + module Controllers + class All + include Praxis::Controller + include Authorization::AuthorizationInPraxisController + + implements PraxisDummy::Endpoints::All + attr_accessor :current_user + attr_writer :authorization_engine + + filter_access_to :all + filter_access_to :view, :require => :test, :context => :permissions + + def show + Praxis::Responses::Ok.new(headers: {'Content-Type' => 'application/json' }) + end + + def view + Praxis::Responses::Ok.new(headers: {'Content-Type' => 'application/json'}) + end + end + end +end diff --git a/test/praxis_dummy/app/controllers/basic.rb b/test/praxis_dummy/app/controllers/basic.rb new file mode 100644 index 0000000..aa2a27b --- /dev/null +++ b/test/praxis_dummy/app/controllers/basic.rb @@ -0,0 +1,52 @@ + + +module PraxisDummy + module Controllers + class Basic + include Praxis::Controller + include Authorization::AuthorizationInPraxisController + + implements PraxisDummy::Endpoints::Basic + attr_accessor :current_user + attr_writer :authorization_engine + + filter_access_to :test_action, :require => :test, :context => :permissions + filter_access_to :test_action_2, :require => :test, :context => :permissions_2 + filter_access_to :show_stuff + filter_access_to :edit_stuff, :create, :require => :test, :context => :permissions + filter_access_to :edit, :require => :test, :context => :permissions, + :attribute_check => true, :model => PraxisDummy::Models::MockModel + filter_access_to :new, :require => :test, :context => :permissions + + filter_access_to [:action_group_action_1, :action_group_action_2] + + def action_group_action_2 + Praxis::Responses::Ok.new(headers: {'Content-Type' => 'application/json' }) + end + + def test_action + Praxis::Responses::Ok.new(headers: {'Content-Type' => 'application/json'}) + end + + def show_stuff + Praxis::Responses::Ok.new(headers: {'Content-Type' => 'application/json'}) + end + + def create + Praxis::Responses::Ok.new(headers: {'Content-Type' => 'application/json'}) + end + + def unprotected_action + Praxis::Responses::Ok.new(headers: {'Content-Type' => 'application/json'}) + end + + def new + Praxis::Responses::Ok.new(headers: {'Content-Type' => 'application/json'}) + end + + def edit + Praxis::Responses::Ok.new(headers: {'Content-Type' => 'application/json'}) + end + end + end +end diff --git a/test/praxis_dummy/app/controllers/foo.rb b/test/praxis_dummy/app/controllers/foo.rb new file mode 100644 index 0000000..7a21e02 --- /dev/null +++ b/test/praxis_dummy/app/controllers/foo.rb @@ -0,0 +1,5 @@ + +class Foo + include Praxis::Controller + implements Endpoints::Foo +end diff --git a/test/praxis_dummy/app/controllers/load_method.rb b/test/praxis_dummy/app/controllers/load_method.rb new file mode 100644 index 0000000..4851e28 --- /dev/null +++ b/test/praxis_dummy/app/controllers/load_method.rb @@ -0,0 +1,51 @@ + + +module PraxisDummy + module Controllers + class LoadMethod + include Praxis::Controller + include Authorization::AuthorizationInPraxisController + + implements PraxisDummy::Endpoints::LoadMethod + attr_accessor :current_user + attr_writer :authorization_engine + + filter_access_to :show, :show_id_not_required, attribute_check: true, model: PraxisDummy::Models::MockModel + filter_access_to :edit, attribute_check: true + filter_access_to :update, :delete, attribute_check: true, + load_method: proc {PraxisDummy::Models::MockModel.new(test: 1)} + filter_access_to :create do + permitted_to! :edit, :praxis_dummy_controllers_load_method + end + filter_access_to :view, :attribute_check => true, load_method: :load_method + + def show(id:) + Praxis::Responses::Ok.new(headers: {'Content-Type' => 'application/json'}) + end + + def show_id_not_required(id: nil) + Praxis::Responses::Ok.new(headers: {'Content-Type' => 'application/json'}) + end + + def edit(id:) + Praxis::Responses::Ok.new(headers: {'Content-Type' => 'application/json'}) + end + + def update + Praxis::Responses::Ok.new(headers: {'Content-Type' => 'application/json'}) + end + + def delete(id:) + Praxis::Responses::Ok.new(headers: {'Content-Type' => 'application/json'}) + end + + def create + Praxis::Responses::Ok.new(headers: {'Content-Type' => 'application/json'}) + end + + def view(id: nil) + Praxis::Responses::Ok.new(headers: {'Content-Type' => 'application/json'}) + end + end + end +end diff --git a/test/praxis_dummy/app/controllers/name_spaced.rb b/test/praxis_dummy/app/controllers/name_spaced.rb new file mode 100644 index 0000000..d9be332 --- /dev/null +++ b/test/praxis_dummy/app/controllers/name_spaced.rb @@ -0,0 +1,25 @@ + + +module PraxisDummy + module Controllers + class NameSpaced + include Praxis::Controller + include Authorization::AuthorizationInPraxisController + + implements PraxisDummy::Endpoints::NameSpaced + attr_accessor :current_user + attr_writer :authorization_engine + + filter_access_to :show + filter_access_to :update, :context => :name_spaced + + def show + Praxis::Responses::Ok.new(headers: {'Content-Type' => 'application/json' }) + end + + def update + Praxis::Responses::Ok.new(headers: {'Content-Type' => 'application/json'}) + end + end + end +end diff --git a/test/praxis_dummy/app/controllers/overwrite.rb b/test/praxis_dummy/app/controllers/overwrite.rb new file mode 100644 index 0000000..6e46953 --- /dev/null +++ b/test/praxis_dummy/app/controllers/overwrite.rb @@ -0,0 +1,25 @@ + + +module PraxisDummy + module Controllers + class Overwrite + include Praxis::Controller + include Authorization::AuthorizationInPraxisController + + implements PraxisDummy::Endpoints::Overwrite + attr_accessor :current_user + attr_writer :authorization_engine + + filter_access_to :test_action, :test_action_2, :require => :test, :context => :permissions_2 + filter_access_to :test_action, :require => :test, :context => :permissions + + def test_action + Praxis::Responses::Ok.new(headers: {'Content-Type' => 'application/json' }) + end + + def test_action_2 + Praxis::Responses::Ok.new(headers: {'Content-Type' => 'application/json'}) + end + end + end +end diff --git a/test/praxis_dummy/app/controllers/people.rb b/test/praxis_dummy/app/controllers/people.rb new file mode 100644 index 0000000..e1295dc --- /dev/null +++ b/test/praxis_dummy/app/controllers/people.rb @@ -0,0 +1,24 @@ + + +module PraxisDummy + module Controllers + class People + include Praxis::Controller + include Authorization::AuthorizationInPraxisController + + implements PraxisDummy::Endpoints::People + attr_accessor :current_user + attr_writer :authorization_engine + + filter_access_to :all + + def show + Praxis::Responses::Ok.new(headers: {'Content-Type' => 'application/json' }) + end + + def view + Praxis::Responses::Ok.new(headers: {'Content-Type' => 'application/json'}) + end + end + end +end diff --git a/test/praxis_dummy/app/models/load_method.rb b/test/praxis_dummy/app/models/load_method.rb new file mode 100644 index 0000000..a15ff6a --- /dev/null +++ b/test/praxis_dummy/app/models/load_method.rb @@ -0,0 +1,7 @@ +require File.expand_path(File.dirname(__FILE__) + '/mock_model') + +class LoadMethod < PraxisDummy::Models::MockModel + def self.name + "LoadMethod" + end +end diff --git a/test/praxis_dummy/app/models/mock_model.rb b/test/praxis_dummy/app/models/mock_model.rb new file mode 100644 index 0000000..4f0d88e --- /dev/null +++ b/test/praxis_dummy/app/models/mock_model.rb @@ -0,0 +1,40 @@ +module PraxisDummy + module Models + class MockModel + def initialize(attrs = {}) + attrs.each do |key, value| + instance_variable_set(:"@#{key}", value) + self.class.class_eval do + attr_reader key + end + end + end + + def self.descends_from_active_record? + true + end + + def self.table_name + name.tableize + end + + def self.name + "MockModel" + end + + def self.find(*args) + raise StandardError, "Couldn't find #{self.name} with id #{args[0].inspect}" unless args[0] + new :id => args[0] + end + + def self.find_or_initialize_by(args) + raise StandardError, "Syntax error: find_or_initialize by expects a hash: User.find_or_initialize_by(:id => @user.id)" unless args.is_a?(Hash) + new args + end + + def ==(other) + self.id == other.id + end + end + end +end diff --git a/test/praxis_dummy/config/environment.rb b/test/praxis_dummy/config/environment.rb new file mode 100644 index 0000000..207b01e --- /dev/null +++ b/test/praxis_dummy/config/environment.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require 'praxis/plugins/praxis_mapper_plugin' + +Praxis::Application.configure do |application| + # Use Rack::ContentLength middleware + application.middleware Rack::ContentLength + + application.bootloader.use Praxis::Plugins::PraxisMapperPlugin, + config_data: { + repositories: {}, + log_stats: 'skip' + } + + # Ensure we validate responses + application.config.praxis.validate_responses = true #if %w[development test].include?(ENV['RAILS_ENV']) + + # Configure application layout + application.layout do + map :design, 'design/' do + map :api, 'api.rb' + map :endpoints, '**/endpoints/**/*' + end + map :app, 'app/' do + map :models, '**/models/**/*' + map :controllers, '**/controllers/**/*' + end + end +end + +Praxis::Blueprint.caching_enabled = false diff --git a/test/praxis_dummy/design/api.rb b/test/praxis_dummy/design/api.rb new file mode 100644 index 0000000..5935a0e --- /dev/null +++ b/test/praxis_dummy/design/api.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +Praxis::ApiDefinition.define do |api| + api.info do + name 'praxis_dummy' + title 'praxis_dummy' + base_path '/praxis_test_engine' + consumes 'json' + produces 'json' + end +end diff --git a/test/praxis_dummy/design/endpoints/all.rb b/test/praxis_dummy/design/endpoints/all.rb new file mode 100644 index 0000000..e731afa --- /dev/null +++ b/test/praxis_dummy/design/endpoints/all.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module PraxisDummy + module Endpoints + class All + include Praxis::ResourceDefinition + version '1' + + media_type 'application/json' + + action :show do + description 'All Show Action' + routing {get '/show'} + response :ok + response :unauthorized + response :forbidden + end + + action :view do + description 'All View Action' + routing {get '/view'} + response :ok + response :unauthorized + response :forbidden + end + end + end +end diff --git a/test/praxis_dummy/design/endpoints/basic.rb b/test/praxis_dummy/design/endpoints/basic.rb new file mode 100644 index 0000000..e739861 --- /dev/null +++ b/test/praxis_dummy/design/endpoints/basic.rb @@ -0,0 +1,117 @@ +# frozen_string_literal: true + +module PraxisDummy + module Endpoints + class Basic + include Praxis::ResourceDefinition + version '1' + + media_type 'application/json' + + action :index do + description 'Index Test' + routing {get '/'} + response :ok + response :unauthorized + response :forbidden + end + + action :new do + description 'New Test' + routing {get '/new'} + response :ok + response :unauthorized + response :forbidden + end + + action :edit do + description 'Edit Test' + routing {get '/edit'} + response :ok + response :unauthorized + response :forbidden + end + + action :show_stuff do + description 'Show Test' + routing {get '/show_stuff/:id'} + params do + attribute :id, Integer, required: true + end + response :ok + response :unauthorized + response :forbidden + end + + action :update do + description 'Update Test' + routing {put '/:id'} + params do + attribute :id, required: true + end + response :ok + response :unauthorized + response :forbidden + end + + action :create do + description 'Create Test' + routing {post '/'} + response :ok + response :unauthorized + response :forbidden + end + + action :destroy do + description 'Destroy Test' + routing {delete '/:id'} + params do + attribute :id, required: true + end + response :ok + response :unauthorized + response :forbidden + end + + action :test_action do + description 'test_action' + routing {get '/test_action'} + response :ok + response :unauthorized + response :forbidden + end + + action :test_action_2 do + description 'test_action_2' + routing {get '/test_action_2'} + response :ok + response :unauthorized + response :forbidden + end + + action :unprotected_action do + description 'unprotected_action' + routing {get '/unprotected_action'} + response :ok + response :unauthorized + response :forbidden + end + + action :action_group_action_1 do + description 'action_group_action_1' + routing {get '/action_group_action_1'} + response :ok + response :unauthorized + response :forbidden + end + + action :action_group_action_2 do + description 'action_group_action_2' + routing {get '/action_group_action_2'} + response :ok + response :unauthorized + response :forbidden + end + end + end +end diff --git a/test/praxis_dummy/design/endpoints/foo.rb b/test/praxis_dummy/design/endpoints/foo.rb new file mode 100644 index 0000000..6f49957 --- /dev/null +++ b/test/praxis_dummy/design/endpoints/foo.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module Endpoints + class Foo + include Praxis::ResourceDefinition + version '1' + + media_type 'application/json' + + action :show do + description 'Show me the Foo' + routing {get '/:id'} + params do + attribute :id, required: true + end + response :ok + response :unauthorized + end + + action :action_group_action_2 do + description 'action_group_action_2' + routing {get '/action_group_action_2'} + response :ok + response :unauthorized + end + end +end diff --git a/test/praxis_dummy/design/endpoints/load_method.rb b/test/praxis_dummy/design/endpoints/load_method.rb new file mode 100644 index 0000000..f305339 --- /dev/null +++ b/test/praxis_dummy/design/endpoints/load_method.rb @@ -0,0 +1,86 @@ +# frozen_string_literal: true + +module PraxisDummy + module Endpoints + class LoadMethod + include Praxis::ResourceDefinition + version '1' + + media_type 'application/json' + + action :show do + description 'Load Method Show Action' + routing {get '/show'} + params do + attribute :id, Integer#, required: true + end + response :ok + response :unauthorized + response :forbidden + end + + action :show_id_not_required do + description 'Load Method Show Action' + routing {get '/show_id_not_required'} + params do + attribute :id, Integer + end + response :ok + response :unauthorized + response :forbidden + end + + action :edit do + description 'Load Method Edit Action' + routing {get '/edit'} + params do + attribute :id, Integer, required: true + end + response :ok + response :unauthorized + response :forbidden + end + + action :view do + description 'Load Method View Action' + routing {get '/view'} + params do + attribute :id, Integer#, required: true + end + response :ok + response :unauthorized + response :forbidden + end + + action :update do + description 'Load Method Update Action' + routing {get '/update'} + # params do + # attribute :id, Integer#, required: true + # end + response :ok + response :unauthorized + response :forbidden + end + + action :delete do + description 'Load Method Delete Action' + routing {get '/delete'} + params do + attribute :id, Integer#, required: true + end + response :ok + response :unauthorized + response :forbidden + end + + action :create do + description 'Load Method Create Action' + routing {get '/create'} + response :ok + response :unauthorized + response :forbidden + end + end + end +end diff --git a/test/praxis_dummy/design/endpoints/name_spaced.rb b/test/praxis_dummy/design/endpoints/name_spaced.rb new file mode 100644 index 0000000..1562f8b --- /dev/null +++ b/test/praxis_dummy/design/endpoints/name_spaced.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module PraxisDummy + module Endpoints + class NameSpaced + include Praxis::ResourceDefinition + version '1' + + media_type 'application/json' + + action :show do + description 'NameSpaced Show Action' + routing {get '/show'} + response :ok + response :unauthorized + response :forbidden + end + + action :update do + description 'NameSpaced Update Action' + routing {get '/update'} + response :ok + response :unauthorized + response :forbidden + end + end + end +end diff --git a/test/praxis_dummy/design/endpoints/overwrite.rb b/test/praxis_dummy/design/endpoints/overwrite.rb new file mode 100644 index 0000000..db5c5fe --- /dev/null +++ b/test/praxis_dummy/design/endpoints/overwrite.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module PraxisDummy + module Endpoints + class Overwrite + include Praxis::ResourceDefinition + version '1' + + media_type 'application/json' + + action :test_action do + description 'Overwrite test_action Action' + routing {get '/test_action'} + response :ok + response :unauthorized + response :forbidden + end + + action :test_action_2 do + description 'Overwrite test_action_2 Action' + routing {get '/test_action_2'} + response :ok + response :unauthorized + response :forbidden + end + end + end +end diff --git a/test/praxis_dummy/design/endpoints/people.rb b/test/praxis_dummy/design/endpoints/people.rb new file mode 100644 index 0000000..8ce3ee7 --- /dev/null +++ b/test/praxis_dummy/design/endpoints/people.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +module PraxisDummy + module Endpoints + class People + include Praxis::ResourceDefinition + version '1' + + media_type 'application/json' + + action :show do + description 'People Show Action' + routing {get '/show'} + response :ok + response :unauthorized + response :forbidden + end + end + end +end diff --git a/test/praxis_test_engine.rb b/test/praxis_test_engine.rb new file mode 100644 index 0000000..4a47766 --- /dev/null +++ b/test/praxis_test_engine.rb @@ -0,0 +1,9 @@ +require 'praxis' + +class PraxisTestEngine < Rails::Engine + initializer 'praxis_test_engine.add_middleware' do |app| + root_path = PraxisTestEngine.root + 'test/praxis_dummy' + mware = ::Praxis::MiddlewareApp.for(root: root_path) + app.middleware.use mware + end +end diff --git a/test/praxis_test_helper.rb b/test/praxis_test_helper.rb new file mode 100644 index 0000000..b7c525a --- /dev/null +++ b/test/praxis_test_helper.rb @@ -0,0 +1,24 @@ +require 'mocha/minitest' + +class PraxisTestCase < Minitest::Test + include Rack::Test::Methods + + APP = Rack::Builder.app do + run Rails.application + end + + def app + APP + end + + def request!(controller_class, user, url, reader, method: :get, **params) + controller_class.any_instance.stubs(:current_user).returns(user) + controller_class.any_instance.stubs(:authorization_engine).returns(Authorization::Engine.new(reader)) + send(method, url, params) + end + + def response + last_response + end +end + diff --git a/test/test_helper.rb b/test/test_helper.rb index fc6b213..1e1927b 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -30,6 +30,7 @@ require DA_ROOT + File.join(%w{lib declarative_authorization authorization}) require DA_ROOT + File.join(%w{lib declarative_authorization in_controller}) +require DA_ROOT + File.join(%w{lib declarative_authorization in_praxis_controller}) require DA_ROOT + File.join(%w{lib declarative_authorization maintenance}) require DA_ROOT + File.join(%w{lib declarative_authorization test helpers}) @@ -118,6 +119,8 @@ class User < ActiveRecord::Base scope :visible_by, ->(user) { where(id: user.id) } end +require DA_ROOT + 'test/praxis_test_engine' + class TestApp class Application < ::Rails::Application config.eager_load = false @@ -129,6 +132,8 @@ class Application < ::Rails::Application end end +require DA_ROOT + 'test/praxis_test_helper' + class ApplicationController < ActionController::Base end @@ -136,6 +141,8 @@ class ApplicationController < ActionController::Base match '/name/spaced_things(/:action)' => 'name/spaced_things', via: [:get, :post, :put, :patch, :delete] match '/deep/name_spaced/things(/:action)' => 'deep/name_spaced/things', via: [:get, :post, :put, :patch, :delete] match '/:controller(/:action(/:id))', via: [:get, :post, :put, :patch, :delete] + + mount PraxisTestEngine, at: "/praxis_test_engine", as: "praxis_test_engine" end ActionController::Base.send :include, Authorization::AuthorizationInController