├── .gitignore ├── Gemfile ├── MIT-LICENSE ├── README.rdoc ├── Rakefile ├── VERSION ├── aegis.gemspec ├── lib ├── aegis.rb └── aegis │ ├── action.rb │ ├── active_record_ext.rb │ ├── compiler.rb │ ├── controller.rb │ ├── errors.rb │ ├── has_role.rb │ ├── loader.rb │ ├── parser.rb │ ├── permissions.rb │ ├── resource.rb │ ├── role.rb │ ├── sieve.rb │ ├── spec.rb │ ├── spec │ └── matchers.rb │ └── util.rb └── spec ├── aegis ├── controller_spec.rb ├── has_role_spec.rb ├── loader_spec.rb ├── permissions_spec.rb ├── sieve_spec.rb └── spec │ └── matchers_spec.rb ├── app_root ├── app │ ├── controllers │ │ ├── application_controller.rb │ │ └── reviews_controller.rb │ └── models │ │ ├── permissions.rb │ │ ├── property.rb │ │ ├── review.rb │ │ └── user.rb ├── config │ ├── boot.rb │ ├── database.yml │ ├── environment.rb │ ├── environments │ │ ├── in_memory.rb │ │ ├── mysql.rb │ │ ├── postgresql.rb │ │ ├── sqlite.rb │ │ └── sqlite3.rb │ └── routes.rb ├── db │ └── migrate │ │ ├── 001_create_users.rb │ │ ├── 002_create_properties.rb │ │ └── 003_create_reviews.rb ├── lib │ └── console_with_fixtures.rb ├── log │ └── .gitignore └── script │ └── console ├── controllers └── reviews_controller_spec.rb ├── rcov.opts ├── spec.opts ├── spec_helper.rb └── support └── spec_candy.rb /.gitignore: -------------------------------------------------------------------------------- 1 | doc 2 | pkg 3 | *.gem 4 | .idea 5 | Gemfile.lock 6 | 7 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'http://rubygems.org' 2 | 3 | gem 'rails', '=2.3.8' 4 | gem "aegis", :path => '.' 5 | gem 'rspec', '=1.3.1' 6 | gem 'rspec-rails', '=1.3.3' 7 | gem 'ruby-debug' 8 | -------------------------------------------------------------------------------- /MIT-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009 Henning Koch 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.rdoc: -------------------------------------------------------------------------------- 1 | = Aegis - A complete authorization solution for Rails 2 | 3 | Aegis is an authorization solution for Ruby on Rails that supports roles and a RESTish, resource-style declaration of 4 | permission rules. Getting started with Aegis is easy and requires very little integration. As your authorization 5 | requirements become more complex, Aegis will grow with you. 6 | 7 | 8 | == End of life notice! 9 | 10 | The authors of this gem have moved on to create {Consul}[https://github.com/makandra/consul], our next-gen 11 | authorization solution. While Aegis remains a stable solution for Rails 2, this gem is not being developed further. 12 | 13 | If you are looking for Rails 3+ support you might want to browse through {forks of Aegis}[https://github.com/bitcababy/aegis/network]. 14 | 15 | If you are interested in taking over future maintenance of Aegis, write to henning.koch@makandra.de regarding transfer 16 | of gem ownership. Please attach links to work you did on Aegis in a fork, so we can know you are serious about this. 17 | 18 | 19 | == Getting started 20 | 21 | All your permissions live in a single class Permissions. 22 | Permissions are described using resources, similiar to your routes. 23 | Your permission resources can match those in your routes, but don't have to. 24 | 25 | Access to resources or individual actions can be granted or denied to specific roles. 26 | 27 | class Permissions < Aegis::Permissions 28 | 29 | role :user 30 | role :admin 31 | 32 | resources :projects do 33 | allow :everyone 34 | end 35 | 36 | resources :users do 37 | allow :admin 38 | end 39 | 40 | end 41 | 42 | To give your user model a role, it needs to have an attribute +role_name+. The has_role macro wires everything together: 43 | 44 | class User < ActiveRecord::Base 45 | has_role 46 | end 47 | 48 | You can now check if a user has permission to access a given action in your controllers and views: 49 | 50 | <% if current_user.may_update_project? @project %> 51 | <%= link_to 'Edit', edit_project_path(@project) %> 52 | <% end %> 53 | 54 | You can protect all actions in a controller through an Aegis resource with a single line: 55 | 56 | class ProjectsController < ApplicationController 57 | permissions :projects 58 | end 59 | 60 | 61 | == Further reading 62 | 63 | You are now familiar with the basic use case. Aegis can do a *lot* more than that. 64 | There is an awesome {documentation wiki}[http://wiki.github.com/makandra/aegis/] with detailed information on many basic and advanced topics, including: 65 | 66 | * {Defining roles and basic permissions}[http://wiki.github.com/makandra/aegis/defining-roles-and-basic-permissions] 67 | * {Checking permissions}[http://wiki.github.com/makandra/aegis/checking-permissions] 68 | * {Giving your user model a role}[http://wiki.github.com/makandra/aegis/giving-your-user-model-a-role] 69 | * {Defining permissions with resources}[http://wiki.github.com/makandra/aegis/defining-permissions-with-resources] 70 | * {Controller integration}[http://wiki.github.com/makandra/aegis/controller-integration] 71 | * {Giving default access to superusers}[http://wiki.github.com/makandra/aegis/giving-default-access-to-superusers] 72 | * {Distinguishing between reading and writing actions}[http://wiki.github.com/makandra/aegis/distinguishing-between-reading-and-writing-actions] 73 | * {Aliasing actions}[http://wiki.github.com/makandra/aegis/aliasing-actions] 74 | * {Checking permissions when no user is signed in}[http://wiki.github.com/makandra/aegis/checking-permissions-when-no-user-is-signed-in] 75 | * {Handling denied permissions in your controllers}[http://wiki.github.com/makandra/aegis/handling-denied-permissions-in-your-controllers] 76 | * {Changing behavior when a permission is undefined}[http://wiki.github.com/makandra/aegis/changing-behavior-when-a-permission-is-undefined] 77 | * {Multiple roles per user}[http://wiki.github.com/makandra/aegis/multiple-roles-per-user] 78 | * {Testing permissions}[http://wiki.github.com/makandra/aegis/testing-permissions] 79 | * {Upgrading to Aegis 2}[http://wiki.github.com/makandra/aegis/upgrading-to-aegis-2] 80 | 81 | 82 | == Installation 83 | 84 | Aegis is a gem, which you can install with 85 | sudo gem install aegis 86 | 87 | In Rails 2, add the following to your environment.rb: 88 | config.gem 'aegis' 89 | 90 | In Rails 3, add the following to your Gemfile: 91 | gem 'aegis' 92 | 93 | 94 | == Compatibility 95 | 96 | Aegis was tested in Rails 2 only. If you are looking for Rails 3+ support you might want to browse through {forks of Aegis}[https://github.com/bitcababy/aegis/network]. 97 | 98 | 99 | == Credits 100 | 101 | Henning Koch, Tobias Kraze 102 | 103 | {makandra.com}[http://makandra.com/] 104 | 105 | {gem-session.com}[http://gem-session.com/] 106 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rake' 2 | require 'rake/rdoctask' 3 | require 'spec/rake/spectask' 4 | 5 | desc 'Default: Run Aegis specs' 6 | task :default => :spec 7 | 8 | desc "Run Aegis specs" 9 | Spec::Rake::SpecTask.new() do |t| 10 | t.spec_opts = ['--options', "\"spec/spec.opts\""] 11 | t.spec_files = FileList['spec/**/*_spec.rb'] 12 | end 13 | 14 | desc 'Generate documentation for the Aegis gem' 15 | Rake::RDocTask.new(:rdoc) do |rdoc| 16 | rdoc.rdoc_dir = 'rdoc' 17 | rdoc.title = 'Aegis' 18 | rdoc.options << '--line-numbers' << '--inline-source' 19 | rdoc.rdoc_files.include('README') 20 | rdoc.rdoc_files.include('lib/**/*.rb') 21 | end 22 | 23 | begin 24 | require 'jeweler' 25 | Jeweler::Tasks.new do |gemspec| 26 | gemspec.name = "aegis" 27 | gemspec.summary = "Complete authorization solution for Rails" 28 | gemspec.email = "henning.koch@makandra.de" 29 | gemspec.homepage = "http://github.com/makandra/aegis" 30 | gemspec.description = "Aegis is an authorization solution for Ruby on Rails that supports roles and a RESTish, resource-style declaration of permission rules." 31 | gemspec.authors = ["Henning Koch", "Tobias Kraze"] 32 | gemspec.post_install_message = "Upgrade notice:\nIf you are using Aegis' automatic controller integration, include Aegis::Controller in your ApplicationController\nAlso see http://wiki.github.com/makandra/aegis/controller-integration\n" 33 | end 34 | rescue LoadError 35 | puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com" 36 | end 37 | 38 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 2.5.3 2 | -------------------------------------------------------------------------------- /aegis.gemspec: -------------------------------------------------------------------------------- 1 | # Generated by jeweler 2 | # DO NOT EDIT THIS FILE DIRECTLY 3 | # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command 4 | # -*- encoding: utf-8 -*- 5 | 6 | Gem::Specification.new do |s| 7 | s.name = %q{aegis} 8 | s.version = "2.5.3" 9 | 10 | s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= 11 | s.authors = ["Henning Koch", "Tobias Kraze"] 12 | s.date = %q{2010-11-10} 13 | s.description = %q{Aegis is an authorization solution for Ruby on Rails that supports roles and a RESTish, resource-style declaration of permission rules.} 14 | s.email = %q{henning.koch@makandra.de} 15 | s.extra_rdoc_files = [ 16 | "README.rdoc" 17 | ] 18 | s.files = [ 19 | ".gitignore", 20 | "Gemfile", 21 | "MIT-LICENSE", 22 | "README.rdoc", 23 | "Rakefile", 24 | "VERSION", 25 | "aegis.gemspec", 26 | "lib/aegis.rb", 27 | "lib/aegis/action.rb", 28 | "lib/aegis/active_record_ext.rb", 29 | "lib/aegis/compiler.rb", 30 | "lib/aegis/controller.rb", 31 | "lib/aegis/errors.rb", 32 | "lib/aegis/has_role.rb", 33 | "lib/aegis/loader.rb", 34 | "lib/aegis/parser.rb", 35 | "lib/aegis/permissions.rb", 36 | "lib/aegis/resource.rb", 37 | "lib/aegis/role.rb", 38 | "lib/aegis/sieve.rb", 39 | "lib/aegis/spec.rb", 40 | "lib/aegis/spec/matchers.rb", 41 | "lib/aegis/util.rb", 42 | "spec/aegis/controller_spec.rb", 43 | "spec/aegis/has_role_spec.rb", 44 | "spec/aegis/loader_spec.rb", 45 | "spec/aegis/permissions_spec.rb", 46 | "spec/aegis/sieve_spec.rb", 47 | "spec/aegis/spec/matchers_spec.rb", 48 | "spec/app_root/app/controllers/application_controller.rb", 49 | "spec/app_root/app/controllers/reviews_controller.rb", 50 | "spec/app_root/app/models/permissions.rb", 51 | "spec/app_root/app/models/property.rb", 52 | "spec/app_root/app/models/review.rb", 53 | "spec/app_root/app/models/user.rb", 54 | "spec/app_root/config/boot.rb", 55 | "spec/app_root/config/database.yml", 56 | "spec/app_root/config/environment.rb", 57 | "spec/app_root/config/environments/in_memory.rb", 58 | "spec/app_root/config/environments/mysql.rb", 59 | "spec/app_root/config/environments/postgresql.rb", 60 | "spec/app_root/config/environments/sqlite.rb", 61 | "spec/app_root/config/environments/sqlite3.rb", 62 | "spec/app_root/config/routes.rb", 63 | "spec/app_root/db/migrate/001_create_users.rb", 64 | "spec/app_root/db/migrate/002_create_properties.rb", 65 | "spec/app_root/db/migrate/003_create_reviews.rb", 66 | "spec/app_root/lib/console_with_fixtures.rb", 67 | "spec/app_root/log/.gitignore", 68 | "spec/app_root/script/console", 69 | "spec/controllers/reviews_controller_spec.rb", 70 | "spec/rcov.opts", 71 | "spec/spec.opts", 72 | "spec/spec_helper.rb", 73 | "spec/support/spec_candy.rb" 74 | ] 75 | s.homepage = %q{http://github.com/makandra/aegis} 76 | s.post_install_message = %q{Upgrade notice: 77 | If you are using Aegis' automatic controller integration, include Aegis::Controller in your ApplicationController 78 | Also see http://wiki.github.com/makandra/aegis/controller-integration 79 | } 80 | s.rdoc_options = ["--charset=UTF-8"] 81 | s.require_paths = ["lib"] 82 | s.rubygems_version = %q{1.3.7} 83 | s.summary = %q{Complete authorization solution for Rails} 84 | s.test_files = [ 85 | "spec/app_root/app/controllers/application_controller.rb", 86 | "spec/app_root/app/controllers/reviews_controller.rb", 87 | "spec/app_root/app/models/permissions.rb", 88 | "spec/app_root/app/models/property.rb", 89 | "spec/app_root/app/models/review.rb", 90 | "spec/app_root/app/models/user.rb", 91 | "spec/app_root/config/boot.rb", 92 | "spec/app_root/config/environment.rb", 93 | "spec/app_root/config/environments/in_memory.rb", 94 | "spec/app_root/config/environments/mysql.rb", 95 | "spec/app_root/config/environments/postgresql.rb", 96 | "spec/app_root/config/environments/sqlite.rb", 97 | "spec/app_root/config/environments/sqlite3.rb", 98 | "spec/app_root/config/routes.rb", 99 | "spec/app_root/db/migrate/001_create_users.rb", 100 | "spec/app_root/db/migrate/002_create_properties.rb", 101 | "spec/app_root/db/migrate/003_create_reviews.rb", 102 | "spec/app_root/lib/console_with_fixtures.rb", 103 | "spec/controllers/reviews_controller_spec.rb", 104 | "spec/spec_helper.rb", 105 | "spec/aegis/has_role_spec.rb", 106 | "spec/aegis/loader_spec.rb", 107 | "spec/aegis/permissions_spec.rb", 108 | "spec/aegis/sieve_spec.rb", 109 | "spec/aegis/spec/matchers_spec.rb", 110 | "spec/aegis/controller_spec.rb", 111 | "spec/support/spec_candy.rb" 112 | ] 113 | 114 | if s.respond_to? :specification_version then 115 | current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION 116 | s.specification_version = 3 117 | 118 | if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then 119 | else 120 | end 121 | else 122 | end 123 | end 124 | 125 | -------------------------------------------------------------------------------- /lib/aegis.rb: -------------------------------------------------------------------------------- 1 | require 'aegis/loader' 2 | Aegis::Loader.load_paths 3 | -------------------------------------------------------------------------------- /lib/aegis/action.rb: -------------------------------------------------------------------------------- 1 | module Aegis 2 | class Action 3 | 4 | attr_reader :name, :takes_object, :takes_parent_object, :writing, :sieves, :pluralize_resource 5 | 6 | def initialize(name, options) 7 | @name = name.to_s 8 | @sieves = [] 9 | update(options, true) 10 | end 11 | 12 | def update(options, use_defaults = false) 13 | update_attribute(options, :takes_object, use_defaults, true) 14 | update_attribute(options, :takes_parent_object, use_defaults, false) 15 | update_attribute(options, :writing, use_defaults, true) 16 | update_attribute(options, :pluralize_resource, use_defaults, false) 17 | end 18 | 19 | def update_attribute(options, key, use_defaults, default) 20 | value = options[key] 21 | value = default if value.nil? && use_defaults 22 | instance_variable_set("@#{key}", value) unless value.nil? 23 | end 24 | 25 | def may?(user, *args) 26 | context = extract_context(user, args) 27 | user.roles.any? do |role| 28 | may_as_role?(role, context, *args) 29 | end 30 | end 31 | 32 | def may!(user, *args) 33 | may?(user, *args) or raise Aegis::AccessDenied, "Access denied: #{args.inspect}" 34 | end 35 | 36 | def self.index(options = {}) 37 | new('index', options.reverse_merge(:takes_object => false, :pluralize_resource => true, :writing => false)) 38 | end 39 | 40 | def self.show(options = {}) 41 | new('show', options.reverse_merge(:takes_object => true, :writing => false)) 42 | end 43 | 44 | def self.update(options = {}) 45 | new('update', options.reverse_merge(:takes_object => true, :writing => true)) 46 | end 47 | 48 | def self.create(options = {}) 49 | new('create', options.reverse_merge(:takes_object => false, :writing => true)) 50 | end 51 | 52 | def self.destroy(options = {}) 53 | new('destroy', options.reverse_merge(:takes_object => true, :writing => true)) 54 | end 55 | 56 | def self.undefined 57 | new(nil, :takes_object => false, :writing => true) 58 | end 59 | 60 | def self.allow_to_all 61 | action = undefined 62 | action.sieves << Aegis::Sieve.allow_to_all 63 | action 64 | end 65 | 66 | def self.deny_to_all 67 | action = undefined 68 | action.sieves << Aegis::Sieve.deny_to_all 69 | action 70 | end 71 | 72 | def abstract? 73 | name.blank? 74 | end 75 | 76 | def inspect 77 | "Action(#{{ :name => name, :takes_object => takes_object, :takes_parent_object => takes_parent_object, :sieves => sieves }.inspect})" 78 | end 79 | 80 | private 81 | 82 | def may_as_role?(role, context, *args) 83 | context.role = role 84 | may = role.may_by_default? 85 | for sieve in sieves 86 | opinion = sieve.may?(context, *args) 87 | may = opinion unless opinion.nil? 88 | end 89 | may 90 | end 91 | 92 | # not *args so we can change the array reference 93 | def extract_context(user, args) 94 | context = {} 95 | context[:user] = user 96 | if takes_parent_object 97 | context[:parent_object] = args.shift or raise ArgumentError, "No parent object given" 98 | end 99 | if takes_object 100 | context[:object] = args.shift or raise ArgumentError, "No object given" 101 | end 102 | OpenStruct.new(context) 103 | end 104 | 105 | end 106 | end 107 | -------------------------------------------------------------------------------- /lib/aegis/active_record_ext.rb: -------------------------------------------------------------------------------- 1 | ActiveRecord::Base.extend(Aegis::HasRole) 2 | -------------------------------------------------------------------------------- /lib/aegis/compiler.rb: -------------------------------------------------------------------------------- 1 | module Aegis 2 | class Compiler 3 | 4 | ATOM_GROUPS = { 5 | :namespace => :structure, 6 | :resource => :structure, 7 | :resources => :structure, 8 | :action => :structure, 9 | :allow => :sieve, 10 | :deny => :sieve, 11 | :reading => :sieve, 12 | :writing => :sieve 13 | } 14 | 15 | def initialize(resource) 16 | @resource = resource 17 | end 18 | 19 | def compile(atoms) 20 | for atom in atoms 21 | case atom_group(atom) 22 | when :structure 23 | compile_structure(atom) 24 | when :sieve 25 | compile_sieve(atom) 26 | else 27 | unexpected_atom_type!(atom) 28 | end 29 | end 30 | end 31 | 32 | def self.compile(resource, atoms) 33 | new(resource).compile(atoms) 34 | end 35 | 36 | private 37 | 38 | def compile_structure(atom) 39 | case atom[:type] 40 | when :action 41 | compile_action(atom) 42 | when :namespace 43 | compile_namespace(atom) 44 | when :resource 45 | compile_child_resource(atom, :singleton) 46 | when :resources 47 | compile_child_resource(atom, :collection) 48 | else 49 | unexpected_atom_type!(atom) 50 | end 51 | end 52 | 53 | def compile_namespace(atom) 54 | atom[:options].merge!(:only => []) 55 | compile_child_resource(atom, :singleton) 56 | end 57 | 58 | def compile_action(atom) 59 | action = @resource.create_or_update_action( 60 | atom[:name], 61 | create_action_options(atom[:options]), 62 | update_action_options(atom[:options]) 63 | ) 64 | for sieve_atom in atom[:children] 65 | compile_sieve(sieve_atom, [action]) 66 | end 67 | end 68 | 69 | def compile_sieve(atom, affected_actions = @resource.actions) 70 | case atom[:type] 71 | when :allow 72 | for action in affected_actions 73 | action.sieves << Aegis::Sieve.new(atom[:role_name], true, atom[:block]) 74 | end 75 | when :deny 76 | for action in affected_actions 77 | action.sieves << Aegis::Sieve.new(atom[:role_name], false, atom[:block]) 78 | end 79 | when :reading 80 | for child in atom[:children] 81 | compile_sieve(child, @resource.reading_actions) 82 | end 83 | when :writing 84 | for child in atom[:children] 85 | compile_sieve(child, @resource.writing_actions) 86 | end 87 | else 88 | unexpected_atom_type!(atom) 89 | end 90 | end 91 | 92 | def compile_child_resource(atom, type) 93 | child = Aegis::Resource.new(@resource, atom[:name], type, atom[:options]) 94 | @resource.children << child 95 | Aegis::Compiler.compile(child, atom[:children]) 96 | end 97 | 98 | def create_action_options(options) 99 | { :takes_object => @resource.new_action_takes_object?(options), 100 | :takes_parent_object => @resource.new_action_takes_parent_object?(options) 101 | }.merge(update_action_options(options)) 102 | end 103 | 104 | def update_action_options(options) 105 | { :writing => options[:writing], 106 | :pluralize_resource => options[:collection] } 107 | end 108 | 109 | def atom_group(atom) 110 | ATOM_GROUPS[atom[:type]] 111 | end 112 | 113 | def unexpected_atom_type!(atom) 114 | raise Aegis::InvalidSyntax, "Unexpected atom type: #{atom[:type]}" 115 | end 116 | 117 | end 118 | end 119 | -------------------------------------------------------------------------------- /lib/aegis/controller.rb: -------------------------------------------------------------------------------- 1 | module Aegis 2 | module Controller 3 | 4 | def self.included(base) 5 | base.send :include, InstanceMethods 6 | base.send :extend, ClassMethods 7 | end 8 | 9 | module ClassMethods 10 | 11 | private 12 | 13 | def require_permissions(options = {}) 14 | before_filter :unchecked_permissions, options 15 | end 16 | 17 | def skip_permissions(options = {}) 18 | skip_before_filter :unchecked_permissions, options 19 | end 20 | 21 | def permissions(resource, options = {}) 22 | 23 | filter_options = options.slice(:except, :only) 24 | 25 | skip_before_filter :unchecked_permissions, filter_options 26 | 27 | # Store arguments for testing 28 | @aegis_permissions_resource = resource 29 | @aegis_permissions_options = options 30 | 31 | before_filter :check_permissions, filter_options 32 | 33 | instance_eval do 34 | 35 | private 36 | 37 | actions_map = (options[:map] || {}).stringify_keys 38 | object_method = options[:object] || :object 39 | parent_object_method = options[:parent_object] || :parent_object 40 | user_method = options[:user] || :current_user 41 | permissions = lambda { Aegis::Permissions.app_permissions(options[:permissions]) } 42 | 43 | define_method :check_permissions do 44 | action = permissions.call.guess_action( 45 | resource, 46 | action_name.to_s, 47 | actions_map 48 | ) 49 | args = [] 50 | args << permissions.call.send(:handle_missing_user, send(user_method)) 51 | args << send(parent_object_method) if action.takes_parent_object 52 | args << send(object_method) if action.takes_object 53 | action.may!(*args) 54 | end 55 | 56 | end 57 | 58 | end 59 | 60 | end 61 | 62 | module InstanceMethods 63 | 64 | private 65 | 66 | def unchecked_permissions 67 | raise Aegis::UncheckedPermissions, "This controller does not check permissions" 68 | end 69 | 70 | end 71 | 72 | end 73 | end 74 | 75 | -------------------------------------------------------------------------------- /lib/aegis/errors.rb: -------------------------------------------------------------------------------- 1 | module Aegis 2 | 3 | class AccessDenied < StandardError 4 | end 5 | 6 | class UncheckedPermissions < StandardError 7 | end 8 | 9 | class InvalidSyntax < StandardError 10 | end 11 | 12 | class MissingUser < StandardError 13 | end 14 | 15 | class MissingAction < StandardError 16 | end 17 | 18 | end 19 | -------------------------------------------------------------------------------- /lib/aegis/has_role.rb: -------------------------------------------------------------------------------- 1 | module Aegis 2 | module HasRole 3 | 4 | def has_role(options = {}) 5 | 6 | permissions = lambda { Aegis::Permissions.app_permissions(options[:permissions]) } 7 | 8 | may_pattern = /^may_(.+?)([\!\?])$/ 9 | 10 | send :define_method, :role_names do 11 | (role_name || '').split(/\s*,\s*/) 12 | end 13 | 14 | send :define_method, :role_names= do |role_names| 15 | self.role_name = role_names.reject(&:blank?).join(',') 16 | end 17 | 18 | send :define_method, :role do 19 | roles.first 20 | end 21 | 22 | send :define_method, :roles do 23 | role_names.collect do |role_name| 24 | permissions.call.find_role_by_name(role_name) 25 | end.compact 26 | end 27 | 28 | send :define_method, :has_role? do |role_name| 29 | role_names.include?(role_name.to_s) 30 | end 31 | 32 | Aegis::Util.define_class_method(self, :validates_role) do |*validate_options| 33 | validate_options = validate_options[0] || {} 34 | 35 | send :define_method, :validate_role do 36 | unless role_names.size > 0 && role_names.size == roles.size 37 | message = validate_options[:message] || I18n.translate('activerecord.errors.messages.inclusion') 38 | errors.add :role_name, message 39 | end 40 | end 41 | 42 | validate :validate_role 43 | end 44 | 45 | if options[:default] 46 | 47 | unless method_defined?(:after_initialize) 48 | send :define_method, :after_initialize do 49 | end 50 | end 51 | 52 | send :define_method, :set_default_role_name do 53 | if new_record? && role_name.blank? 54 | self.role_name = options[:default] 55 | end 56 | end 57 | 58 | after_initialize :set_default_role_name 59 | 60 | end 61 | 62 | unless method_defined?(:method_missing_with_aegis_permissions) 63 | 64 | # Delegate may_...? and may_...! methods to the permissions class. 65 | send :define_method, :method_missing_with_aegis_permissions do |symb, *args| 66 | method_name = symb.to_s 67 | if method_name =~ may_pattern 68 | action_path, severity = $1, $2 69 | Aegis::Util.define_class_method(self, method_name) do |*method_args| 70 | permissions.call.send("may#{severity}", self, action_path, *method_args) 71 | end 72 | send(method_name, *args) 73 | else 74 | method_missing_without_aegis_permissions(symb, *args) 75 | end 76 | end 77 | 78 | alias_method_chain :method_missing, :aegis_permissions 79 | 80 | send :define_method, :respond_to_with_aegis_permissions? do |symb, *args| 81 | include_private = args.first.nil? ? false : args.first 82 | respond_to_without_aegis_permissions?(symb, include_private) || (symb.to_s =~ may_pattern) 83 | end 84 | 85 | alias_method_chain :respond_to?, :aegis_permissions 86 | 87 | end 88 | end 89 | 90 | end 91 | end 92 | -------------------------------------------------------------------------------- /lib/aegis/loader.rb: -------------------------------------------------------------------------------- 1 | module Aegis 2 | class Loader 3 | class << self 4 | 5 | def paths 6 | [ 'ostruct', 7 | 'aegis/util', 8 | 'aegis/errors', 9 | 'aegis/action', 10 | 'aegis/compiler', 11 | 'aegis/has_role', 12 | 'aegis/parser', 13 | 'aegis/permissions', 14 | 'aegis/resource', 15 | 'aegis/role', 16 | 'aegis/sieve', 17 | 'aegis/controller', 18 | 'aegis/active_record_ext' ] 19 | end 20 | 21 | def load_paths 22 | for path in paths 23 | require path 24 | end 25 | @loaded = true 26 | end 27 | 28 | def loaded? 29 | @loaded 30 | end 31 | 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /lib/aegis/parser.rb: -------------------------------------------------------------------------------- 1 | module Aegis 2 | class Parser 3 | 4 | attr_reader :atoms 5 | 6 | def self.parse(&block) 7 | Aegis::Parser.new.parse(&block) 8 | end 9 | 10 | def initialize 11 | @atoms = [] 12 | end 13 | 14 | def parse(&block) 15 | instance_eval(&block) if block 16 | atoms 17 | end 18 | 19 | def action(*args, &block) 20 | if block && block.arity > 0 21 | # useful warning for people upgrading from Aegis 2 22 | raise Aegis::InvalidSyntax, "Action blocks do not take block arguments in Aegis 2. allow/deny blocks do." 23 | end 24 | split_definitions(*args) do |name, options| 25 | @atoms.push({ 26 | :type => :action, 27 | :name => name.to_s, 28 | :options => options, 29 | :children => Aegis::Parser.parse(&block) 30 | }) 31 | end 32 | end 33 | 34 | def namespace(*args, &block) 35 | split_definitions(*args) do |name, options| 36 | @atoms.push({ 37 | :type => :namespace, 38 | :name => name.to_s, 39 | :options => options, 40 | :children => Aegis::Parser.parse(&block) 41 | }) 42 | end 43 | end 44 | 45 | def resource(*args, &block) 46 | split_definitions(*args) do |name, options| 47 | @atoms.push({ 48 | :type => :resource, 49 | :name => name.to_s, 50 | :options => options, 51 | :children => Aegis::Parser.parse(&block) 52 | }) 53 | end 54 | end 55 | 56 | def resources(*args, &block) 57 | split_definitions(*args) do |name, options| 58 | @atoms.push({ 59 | :type => :resources, 60 | :name => name.to_s, 61 | :options => options, 62 | :children => Aegis::Parser.parse(&block) 63 | }) 64 | end 65 | end 66 | 67 | def allow(*args, &block) 68 | split_definitions(*args) do |role_name, options| 69 | @atoms.push({ 70 | :type => :allow, 71 | :role_name => role_name.to_s, 72 | :block => block 73 | }) 74 | end 75 | end 76 | 77 | def deny(*args, &block) 78 | split_definitions(*args) do |role_name, options| 79 | @atoms.push({ 80 | :type => :deny, 81 | :role_name => role_name.to_s, 82 | :block => block 83 | }) 84 | end 85 | end 86 | 87 | def reading(&block) 88 | block or raise Aegis::InvalidSyntax, "missing block" 89 | @atoms.push({ 90 | :type => :reading, 91 | :children => Aegis::Parser.parse(&block) 92 | }) 93 | end 94 | 95 | def writing(&block) 96 | block or raise Aegis::InvalidSyntax, "missing block" 97 | @atoms.push({ 98 | :type => :writing, 99 | :children => Aegis::Parser.parse(&block) 100 | }) 101 | end 102 | 103 | private 104 | 105 | def split_definitions(*args, &definition) 106 | options = args.extract_options! 107 | args = [nil] if args.empty? 108 | for name in args 109 | definition.call(name, options) 110 | end 111 | end 112 | 113 | end 114 | end 115 | -------------------------------------------------------------------------------- /lib/aegis/permissions.rb: -------------------------------------------------------------------------------- 1 | module Aegis 2 | class Permissions 3 | class << self 4 | 5 | MISSING_ACTION_STRATEGIES = [ 6 | :allow, :deny, :default_permission, :error 7 | ] 8 | 9 | def missing_action_means(strategy) 10 | prepare 11 | MISSING_ACTION_STRATEGIES.include?(strategy) or raise Aegis::InvalidSyntax, "missing_action_means must be one of #{MISSING_ACTION_STRATEGIES.inspect}" 12 | @missing_action_strategy = strategy 13 | end 14 | 15 | def missing_user_means(strategy_symbol = nil, &strategy_block) 16 | prepare 17 | @missing_user_strategy = strategy_symbol || strategy_block 18 | end 19 | 20 | def alias_action(aliases) 21 | prepare 22 | aliases.each do |key, value| 23 | @action_aliases[key.to_s] = value.to_s 24 | end 25 | end 26 | 27 | def permission(*args) 28 | raise Aegis::InvalidSyntax, "The Aegis API has changed. See http://wiki.github.com/makandra/aegis/upgrading-to-aegis-2 for migration instructions." 29 | end 30 | 31 | def action(*args, &block) 32 | prepare 33 | @parser.action(*args, &block) 34 | end 35 | 36 | def resource(*args, &block) 37 | prepare 38 | @parser.resource(*args, &block) 39 | end 40 | 41 | def namespace(*args, &block) 42 | prepare 43 | @parser.namespace(*args, &block) 44 | end 45 | 46 | def resources(*args, &block) 47 | prepare 48 | @parser.resources(*args, &block) 49 | end 50 | 51 | def may?(user, path, *args) 52 | query_action(:may?, user, path, *args) 53 | end 54 | 55 | def may!(user, path, *args) 56 | query_action(:may!, user, path, *args) 57 | end 58 | 59 | def role(role_name, options = {}) 60 | role_name = role_name.to_s 61 | role_name != 'everyone' or raise Aegis::InvalidSyntax, "Cannot define a role named: #{role_name}" 62 | @roles_by_name ||= {} 63 | @roles_by_name[role_name] = Aegis::Role.new(role_name, options) 64 | end 65 | 66 | def roles 67 | @roles_by_name.values.sort 68 | end 69 | 70 | def find_role_by_name(name) 71 | @roles_by_name[name.to_s] 72 | end 73 | 74 | def guess_action(resource_name, action_name, map = {}) 75 | compile 76 | action = nil 77 | action_name = action_name.to_s 78 | possible_paths = guess_action_paths(resource_name, action_name, map) 79 | possible_paths.detect do |path| 80 | action = find_action_by_path(path, false) 81 | end 82 | handle_missing_action(action, possible_paths.first) 83 | end 84 | 85 | def find_action_by_path(path, handle_missing = true) 86 | compile 87 | action = @actions_by_path[path.to_s] 88 | action = handle_missing_action(action, path) if handle_missing 89 | action 90 | end 91 | 92 | def app_permissions(option) 93 | if option.is_a?(Class) 94 | option 95 | else 96 | (option || '::Permissions').constantize 97 | end 98 | end 99 | 100 | def inspect 101 | compile 102 | "Permissions(#{@root_resource.inspect})" 103 | end 104 | 105 | private 106 | 107 | def query_action(verb, user, path, *args) 108 | prepare 109 | user = handle_missing_user(user) 110 | action = find_action_by_path(path) 111 | action.send(verb, user, *args) 112 | end 113 | 114 | def handle_missing_user(possibly_missing_user) 115 | possibly_missing_user ||= case @missing_user_strategy 116 | when :error then raise Aegis::MissingUser, "Cannot check permission without a user" 117 | when Proc then @missing_user_strategy.call 118 | end 119 | end 120 | 121 | def handle_missing_action(possibly_missing_action, path) 122 | possibly_missing_action ||= case @missing_action_strategy 123 | when :default_permission then Aegis::Action.undefined 124 | when :allow then Aegis::Action.allow_to_all 125 | when :deny then Aegis::Action.deny_to_all 126 | when :error then raise Aegis::MissingAction, "Missing action: #{path}" 127 | end 128 | end 129 | 130 | def guess_action_paths(resource_name, action_name, map) 131 | if mapped = map[action_name] 132 | [ mapped.to_s ] 133 | else 134 | [ "#{action_name}_#{resource_name.to_s.singularize}", 135 | "#{action_name}_#{resource_name.to_s.pluralize}", 136 | resource_name ] 137 | end 138 | end 139 | 140 | def prepare 141 | unless @parser 142 | @parser = Aegis::Parser.new 143 | @missing_user_strategy = :error 144 | @missing_action_strategy = :default_permission 145 | @action_aliases = { 146 | 'new' => 'create', 147 | 'edit' => 'update' 148 | } 149 | end 150 | end 151 | 152 | def compile 153 | unless @root_resource 154 | prepare 155 | @root_resource = Aegis::Resource.new(nil, nil, :root, {}) 156 | Aegis::Compiler.compile(@root_resource, @parser.atoms) 157 | index_actions 158 | end 159 | end 160 | 161 | def index_actions 162 | @actions_by_path = @root_resource.index_actions_by_path(@action_aliases) 163 | end 164 | 165 | end 166 | end 167 | end 168 | -------------------------------------------------------------------------------- /lib/aegis/resource.rb: -------------------------------------------------------------------------------- 1 | module Aegis 2 | class Resource 3 | 4 | attr_reader :parent, :children, :name, :type, :never_takes_object, :actions 5 | 6 | def initialize(parent, name, type, options) 7 | @parent = parent 8 | @children = [] 9 | @name = name 10 | @type = type 11 | @actions = initial_actions(options) 12 | # @never_takes_object = options[:object] == false 13 | end 14 | 15 | def inspect 16 | "Resource(#{{:name => name || 'root', :actions => actions, :children => children, :parent => parent}.inspect})" 17 | end 18 | 19 | def find_action_by_name(name) 20 | name = name.to_s 21 | @actions.detect { |action| action.name == name } 22 | end 23 | 24 | def create_or_update_action(name, create_options, update_options) 25 | action = nil 26 | if action = find_action_by_name(name) 27 | action.update(update_options) 28 | else 29 | action = Action.new(name, create_options) 30 | @actions << action 31 | end 32 | action 33 | end 34 | 35 | def root? 36 | type == :root 37 | end 38 | 39 | def singleton? 40 | type == :singleton 41 | end 42 | 43 | def collection? 44 | type == :collection 45 | end 46 | 47 | def reading_actions 48 | actions.reject(&:writing) 49 | end 50 | 51 | def writing_actions 52 | actions.select(&:writing) 53 | end 54 | 55 | def action_paths(action, aliases) 56 | if root? 57 | action_names_with_aliases(action.name, aliases) 58 | else 59 | action_names_with_aliases(action.name, aliases).collect do |action_name| 60 | build_path( 61 | action_name, 62 | parent && parent.path(false), 63 | action.pluralize_resource ? name.pluralize : name.singularize 64 | ) 65 | end 66 | end 67 | end 68 | 69 | def action_names_with_aliases(native_name, aliases) 70 | names = [native_name] 71 | aliases.each do |key, value| 72 | names << key if value == native_name 73 | end 74 | names 75 | end 76 | 77 | def index_actions_by_path(aliases) 78 | index = {} 79 | actions.each do |action| 80 | action_paths(action, aliases).each do |path| 81 | index[path] = action 82 | end 83 | end 84 | children.each do |child| 85 | index.merge! child.index_actions_by_path(aliases) 86 | end 87 | index 88 | end 89 | 90 | def new_action_takes_object?(action_options = {}) 91 | collection? && action_options[:collection] != true # && !never_takes_object 92 | end 93 | 94 | def new_action_takes_parent_object?(action_options = {}) 95 | parent && parent.collection? # && !parent.never_takes_object 96 | end 97 | 98 | protected 99 | 100 | def path(pluralize = true) 101 | parent_path = parent && parent.path(false) 102 | pluralized_name = name ? (pluralize ? name.pluralize : name.singularize) : nil 103 | build_path(parent_path, pluralized_name) 104 | end 105 | 106 | private 107 | 108 | def build_path(*args) 109 | args.select(&:present?).join("_") 110 | end 111 | 112 | def filter_actions(actions, options) 113 | if options[:only] 114 | actions = actions.select(&action_name_filter(options[:only])) 115 | elsif options[:except] 116 | actions = actions.reject(&action_name_filter(options[:except])) 117 | end 118 | actions 119 | end 120 | 121 | def action_name_filter(whitelist) 122 | whitelist = whitelist.collect(&:to_s) 123 | lambda { |action| whitelist.include?(action.name) } 124 | end 125 | 126 | def initial_actions(options) 127 | send("initial_actions_for_#{type}", options) 128 | end 129 | 130 | def initial_actions_for_collection(options = {}) 131 | filter_actions([ 132 | Aegis::Action.index(initial_action_options), 133 | Aegis::Action.show(initial_action_options), 134 | Aegis::Action.update(initial_action_options), 135 | Aegis::Action.create(initial_action_options), 136 | Aegis::Action.destroy(initial_action_options), 137 | ], options) 138 | end 139 | 140 | def initial_actions_for_singleton(options = {}) 141 | filter_actions([ 142 | Aegis::Action.show(initial_action_options(:takes_object => false)), 143 | Aegis::Action.update(initial_action_options(:takes_object => false)), 144 | Aegis::Action.create(initial_action_options(:takes_object => false)), 145 | Aegis::Action.destroy(initial_action_options(:takes_object => false)) 146 | ], options) 147 | end 148 | 149 | def initial_action_options(options = {}) 150 | { :takes_parent_object => new_action_takes_parent_object? }.merge(options) 151 | end 152 | 153 | def initial_actions_for_root(options = {}) 154 | [] 155 | end 156 | 157 | end 158 | end 159 | -------------------------------------------------------------------------------- /lib/aegis/role.rb: -------------------------------------------------------------------------------- 1 | module Aegis 2 | class Role 3 | 4 | attr_reader :name, :default_permission 5 | 6 | def initialize(name, options) 7 | @name = name 8 | @default_permission = options[:default_permission] == :allow ? :allow : :deny 9 | freeze 10 | end 11 | 12 | def may_by_default? 13 | @default_permission == :allow 14 | end 15 | 16 | def <=>(other) 17 | name <=> other.name 18 | end 19 | 20 | def to_s 21 | name.to_s.humanize 22 | end 23 | 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/aegis/sieve.rb: -------------------------------------------------------------------------------- 1 | module Aegis 2 | class Sieve 3 | 4 | def initialize(role_name, effect, block = nil) 5 | role_name = 'everyone' if role_name.blank? 6 | @role_name = role_name.to_s 7 | @effect = effect 8 | @block = block 9 | end 10 | 11 | def may?(context, *args) 12 | matches_role = @role_name == 'everyone' || @role_name == context.role.name 13 | if matches_role 14 | if @block 15 | block_result = context.instance_exec(*args, &@block) 16 | block_result ? @effect : !@effect 17 | else 18 | @effect 19 | end 20 | else 21 | nil 22 | end 23 | end 24 | 25 | def inspect 26 | "Sieve(#{{:role_name => @role_name, :effect => @effect ? :allow : :deny, :block => @block.present?}.inspect})" 27 | end 28 | 29 | def self.allow_to_all 30 | new('everyone', true) 31 | end 32 | 33 | def self.deny_to_all 34 | new('everyone', false) 35 | end 36 | 37 | end 38 | 39 | end 40 | -------------------------------------------------------------------------------- /lib/aegis/spec.rb: -------------------------------------------------------------------------------- 1 | # Support aegis/spec for old code. 2 | # Maybe remove this some day. 3 | require 'aegis/spec/matchers' 4 | 5 | -------------------------------------------------------------------------------- /lib/aegis/spec/matchers.rb: -------------------------------------------------------------------------------- 1 | module Aegis 2 | module Spec 3 | module Matchers 4 | 5 | class CheckPermissions 6 | 7 | def initialize(expected_resource, expected_options = {}) 8 | @expected_resource = expected_resource 9 | @expected_options = expected_options 10 | end 11 | 12 | def matches?(controller) 13 | @controller_class = controller.class 14 | @actual_resource = @controller_class.instance_variable_get('@aegis_permissions_resource') 15 | @actual_options = @controller_class.instance_variable_get('@aegis_permissions_options') 16 | @actual_resource == @expected_resource && @actual_options == @expected_options 17 | end 18 | 19 | def failure_message 20 | if @actual_resource != @expected_resource 21 | "expected #{@controller_class} to check permissions against resource #{@expected_resource.inspect}, but it checked against #{@actual_resource.inspect}" 22 | else 23 | "expected #{@controller_class} to check permissions with options #{@expected_options.inspect}, but options were #{@actual_options.inspect}" 24 | end 25 | end 26 | 27 | def negative_failure_message 28 | if @actual_resource == @expected_resource 29 | "expected #{@controller_class} to not check permissions against resource #{@expected_resource.inspect}" 30 | else 31 | "expected #{@controller_class} to not check permissions with options #{@expected_options.inspect}" 32 | end 33 | end 34 | 35 | def description 36 | description = "check permissions against resource #{@expected_resource.inspect}" 37 | description << " with options #{@expected_options.inspect}" if @expected_options.any? 38 | description 39 | end 40 | 41 | end 42 | 43 | def check_permissions(*args) 44 | CheckPermissions.new(*args) 45 | end 46 | 47 | class BeAllowedTo 48 | 49 | def initialize(expected_action, *expected_args) 50 | @expected_action = expected_action 51 | @expected_args = expected_args 52 | end 53 | 54 | def matches?(user) 55 | @actual_user = user 56 | @actual_user.send("may_#{@expected_action}?", *@expected_args) 57 | end 58 | 59 | def description 60 | "be allowed to #{action_as_prose}" 61 | end 62 | 63 | def failure_message 64 | "expected #{@actual_user.inspect} to be allowed to #{action_as_prose}" 65 | end 66 | 67 | def negative_failure_message 68 | "expected #{@actual_user.inspect} to be denied to #{action_as_prose}" 69 | end 70 | 71 | private 72 | 73 | def action_as_prose 74 | @expected_action.to_s + (@expected_args.present? ? " given #{@expected_args.inspect}" : "") 75 | end 76 | 77 | end 78 | 79 | def be_allowed_to(*args) 80 | BeAllowedTo.new(*args) 81 | end 82 | 83 | end 84 | end 85 | end 86 | 87 | ActiveSupport::TestCase.send :include, Aegis::Spec::Matchers 88 | 89 | -------------------------------------------------------------------------------- /lib/aegis/util.rb: -------------------------------------------------------------------------------- 1 | module Aegis 2 | class Util 3 | class << self 4 | 5 | def define_class_method(object, method, &body) 6 | prototype = object.respond_to?(:singleton_class) ? object.singleton_class : object.metaclass 7 | prototype.send(:define_method, method, &body) 8 | end 9 | 10 | end 11 | end 12 | end -------------------------------------------------------------------------------- /spec/aegis/controller_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Aegis::Controller do 4 | 5 | before(:each) do 6 | 7 | permissions_class = @permissions_class = Class.new(Aegis::Permissions) do 8 | role :user 9 | resources :posts do 10 | reading do 11 | allow :user 12 | end 13 | end 14 | end 15 | 16 | @user_class = Class.new(ActiveRecord::Base) do 17 | set_table_name 'users' 18 | has_role :permissions => permissions_class 19 | end 20 | 21 | user = @user = @user_class.new(:role_name => 'user') 22 | 23 | @controller_class = Class.new(ActionController::Base) do 24 | include Aegis::Controller 25 | define_method :current_user do 26 | user 27 | end 28 | end 29 | 30 | end 31 | 32 | describe 'require_permissions' do 33 | 34 | it "should set a before_filter :unchecked_permissions" do 35 | @controller_class.should_receive(:before_filter).with(:unchecked_permissions, :only => :show) 36 | @controller_class.class_eval do 37 | require_permissions :only => :show 38 | end 39 | end 40 | 41 | end 42 | 43 | describe 'skip_permissions' do 44 | 45 | it "should skip a before_filter :unchecked_permissions" do 46 | @controller_class.should_receive(:skip_before_filter).with(:unchecked_permissions, :only => :show) 47 | @controller_class.class_eval do 48 | skip_permissions :only => :show 49 | end 50 | end 51 | 52 | end 53 | 54 | describe 'unchecked_permissions' do 55 | 56 | it "should raise Aegis::UncheckedPermissions" do 57 | controller = @controller_class.new 58 | expect { controller.send(:unchecked_permissions) }.to raise_error(Aegis::UncheckedPermissions) 59 | end 60 | 61 | end 62 | 63 | describe 'permissions' do 64 | 65 | it "should fetch the context through #object, #parent_object and #current_user by default" do 66 | permissions_class = @permissions_class 67 | @controller_class.class_eval do 68 | permissions :posts, :permissions => permissions_class 69 | end 70 | controller = @controller_class.new 71 | permissions_class.stub(:guess_action => stub('action', :takes_object => true, :takes_parent_object => true).as_null_object) 72 | controller.should_receive(:object).and_return('the object') 73 | controller.should_receive(:parent_object).and_return('the parent object') 74 | controller.should_receive(:current_user).and_return('the user') 75 | controller.send(:check_permissions) 76 | end 77 | 78 | it "should allow custom readers for object, parent object and the current user" do 79 | permissions_class = @permissions_class 80 | @controller_class.class_eval do 81 | permissions :posts, :permissions => permissions_class, :user => :my_user, :object => :my_object, :parent_object => :my_parent 82 | end 83 | controller = @controller_class.new 84 | permissions_class.stub(:guess_action => stub('action', :takes_object => true, :takes_parent_object => true).as_null_object) 85 | controller.should_receive(:my_object).and_return('the object') 86 | controller.should_receive(:my_parent).and_return('the parent object') 87 | controller.should_receive(:my_user).and_return('the user') 88 | controller.send(:check_permissions) 89 | end 90 | 91 | it 'should install a before_filter that checks permissions' do 92 | @controller_class.should_receive(:before_filter).with(:check_permissions, :only => [:update]) 93 | permissions_class = @permissions_class 94 | @controller_class.class_eval do 95 | permissions :posts, :permissions => permissions_class, :only => [:update] 96 | end 97 | end 98 | 99 | end 100 | 101 | describe 'check_permissions' do 102 | 103 | before(:each) do 104 | permissions_class = @permissions_class 105 | map = @map = { 'controller_action' => 'permission_path' } 106 | @controller_class.class_eval do 107 | permissions :posts, :permissions => permissions_class, :map => map 108 | end 109 | @controller = @controller_class.new 110 | @controller.stub(:object => "object", :parent_object => "parent object") 111 | end 112 | 113 | it "should guess the aegis-action using the current resource, action name and actions-map" do 114 | @controller.stub(:action_name => 'update') 115 | action = stub("action").as_null_object 116 | @permissions_class.should_receive(:guess_action).with(:posts, 'update', @map).and_return(action) 117 | @controller.send(:check_permissions) 118 | end 119 | 120 | it "should raise an error if permission is denied" do 121 | @controller.stub(:action_name => 'update') 122 | lambda { @controller.send(:check_permissions) }.should raise_error(Aegis::AccessDenied) 123 | end 124 | 125 | it "should pass silently if permission is granted" do 126 | @controller.stub(:action_name => 'show') 127 | lambda { @controller.send(:check_permissions) }.should_not raise_error 128 | end 129 | 130 | end 131 | 132 | end 133 | -------------------------------------------------------------------------------- /spec/aegis/has_role_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Aegis::HasRole do 4 | 5 | before(:each) do 6 | 7 | @permissions_class = permissions_class = Class.new(Aegis::Permissions) do 8 | role :user 9 | role :moderator 10 | role :admin 11 | end 12 | 13 | @user_class = Class.new(ActiveRecord::Base) do 14 | set_table_name 'users' 15 | has_role :permissions => permissions_class 16 | end 17 | 18 | end 19 | 20 | describe 'has_role' do 21 | 22 | it "should define accessors for the role association" do 23 | user = @user_class.new 24 | user.should respond_to(:role) 25 | user.should respond_to(:roles) 26 | user.should respond_to(:has_role?) 27 | end 28 | 29 | it "should allow a default for new records" do 30 | permissions_class = @permissions_class 31 | @user_class.class_eval { has_role :permissions => permissions_class, :default => "admin" } 32 | user = @user_class.new 33 | user.role_name.should == 'admin' 34 | end 35 | 36 | end 37 | 38 | describe 'role' do 39 | 40 | it "should return the first role" do 41 | user = @user_class.new 42 | user.should_receive(:roles).and_return(['first role', 'second role']) 43 | user.role.should == 'first role' 44 | end 45 | 46 | it "should be nil if no roles are associated" do 47 | user = @user_class.new 48 | user.should_receive(:roles).and_return([]) 49 | user.role.should be_nil 50 | end 51 | 52 | end 53 | 54 | describe 'roles' do 55 | 56 | it "should return the corresponding role for each role name" do 57 | user = @user_class.new 58 | user.should_receive(:role_names).and_return(['admin', 'user']) 59 | user.roles.collect(&:name).should == ['admin', 'user'] 60 | end 61 | 62 | it "should ignore unknown role names that doesn't match a known role" do 63 | user = @user_class.new 64 | user.should_receive(:role_names).and_return(['unknown role', 'user']) 65 | user.roles.collect(&:name).should == ['user'] 66 | end 67 | 68 | end 69 | 70 | describe 'role_names' do 71 | 72 | it "should be empty if the role name is blank" do 73 | user = @user_class.new(:role_name => '') 74 | user.role_names.should be_empty 75 | end 76 | 77 | it "should be empty if the role_name is nil" do 78 | user = @user_class.new(:role_name => nil) 79 | user.role_names.should be_empty 80 | end 81 | 82 | it "should deserialize a single role name into an array with a single element" do 83 | user = @user_class.new(:role_name => 'admin') 84 | user.role_names.should == ['admin'] 85 | end 86 | 87 | it "should deserialize multiple, comma-separated role names into an array" do 88 | user = @user_class.new(:role_name => 'admin,user') 89 | user.role_names.should == ['admin', 'user'] 90 | end 91 | 92 | it "should ignore whitespace around the comma-separator" do 93 | user = @user_class.new(:role_name => 'admin , user') 94 | user.role_names.should == ['admin', 'user'] 95 | end 96 | 97 | end 98 | 99 | describe 'role_names=' do 100 | 101 | it "should serialize the given array into a comma-separated string and store it into #role_name" do 102 | user = @user_class.new 103 | user.should_receive(:role_name=).with("first,second") 104 | user.role_names = ['first', 'second'] 105 | end 106 | 107 | it "should ignore blank role names" do 108 | user = @user_class.new 109 | user.should_receive(:role_name=).with("first,second") 110 | user.role_names = ['', nil, 'first', '', nil, 'second', '', nil] 111 | end 112 | 113 | end 114 | 115 | describe 'has_role?' do 116 | 117 | it "should return true if the user has the given role name" do 118 | user = @user_class.new(:role_name => 'admin, user') 119 | user.should have_role('admin') 120 | user.should have_role('user') 121 | end 122 | 123 | it "should return false if the user does not have the given role name" do 124 | user = @user_class.new(:role_name => 'admin, user') 125 | user.should_not have_role('moderator') 126 | end 127 | 128 | it "should allow to query the role name as a symbol instead of a string" do 129 | user = @user_class.new(:role_name => 'admin, user') 130 | user.should have_role(:admin) 131 | end 132 | 133 | end 134 | 135 | describe 'method_missing' do 136 | 137 | it "should delegate may...? messages to the permissions class" do 138 | user = @user_class.new 139 | @permissions_class.should_receive(:may?).with(user, 'do_action', 'argument') 140 | user.may_do_action?('argument') 141 | end 142 | 143 | it "should delegate may...! messages to the permissions class" do 144 | user = @user_class.new 145 | @permissions_class.should_receive(:may!).with(user, 'do_action', 'argument') 146 | user.may_do_action!('argument') 147 | end 148 | 149 | it "should retain its usual behaviour for non-permission methods" do 150 | user = @user_class.new 151 | lambda { user.nonexisting_method }.should raise_error(NoMethodError) 152 | end 153 | 154 | it 'should define a missing Aegis method so that method is used directly from there on' do 155 | user = @user_class.new 156 | user.should_receive_and_execute(:method_missing).once 157 | 2.times { user.may_do_action? } 158 | end 159 | 160 | end 161 | 162 | describe 'respond_to?' do 163 | 164 | it "should be true for all permission methods, even if they are not explicitely defined" do 165 | user = @user_class.new 166 | user.should respond_to(:may_foo?) 167 | user.should respond_to(:may_foo!) 168 | end 169 | 170 | it "should retain its usual behaviour for non-permission methods" do 171 | user = @user_class.new 172 | user.should respond_to(:to_s) 173 | user.should_not respond_to(:nonexisting_method) 174 | end 175 | 176 | end 177 | 178 | describe 'validates_role' do 179 | 180 | before(:each) do 181 | @user_class.class_eval { validates_role } 182 | end 183 | 184 | it "should run the validation callback :validate_role" do 185 | user = @user_class.new 186 | user.should_receive(:validate_role) 187 | user.run_callbacks(:validate) 188 | end 189 | 190 | it "should add an inclusion error to the role name if the role name is nil" do 191 | user = @user_class.new(:role_name => nil) 192 | user.errors.should_receive(:add).with(:role_name, I18n.translate('activerecord.errors.messages.inclusion')) 193 | user.send(:validate_role) 194 | end 195 | 196 | it "should add an inclusion error to the role name if the role name is an empty string" do 197 | user = @user_class.new(:role_name => '') 198 | user.errors.should_receive(:add).with(:role_name, I18n.translate('activerecord.errors.messages.inclusion')) 199 | user.send(:validate_role) 200 | end 201 | 202 | it "should add an inclusion error to the role name if a role name doesn't match a role" do 203 | user = @user_class.new(:role_name => 'user,nonexisting_role') 204 | user.errors.should_receive(:add).with(:role_name, I18n.translate('activerecord.errors.messages.inclusion')) 205 | user.send(:validate_role) 206 | end 207 | 208 | it "should add no error if all role names matches a role" do 209 | user = @user_class.new(:role_name => 'admin,user') 210 | user.errors.should_not_receive(:add) 211 | user.send(:validate_role) 212 | end 213 | 214 | it "should allow a custom error message with the :message options" do 215 | @user_class.class_eval { validates_role :message => "custom message" } 216 | user = @user_class.new(:role_name => '') 217 | user.errors.should_receive(:add).with(:role_name, 'custom message') 218 | user.send(:validate_role) 219 | end 220 | 221 | end 222 | 223 | end 224 | -------------------------------------------------------------------------------- /spec/aegis/loader_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Aegis::Loader do 4 | 5 | describe 'paths' do 6 | 7 | it "should return all paths in the lib folder, except files that are optionally loaded" do 8 | 9 | root = "#{File.dirname(__FILE__)}/../lib/" 10 | Dir["#{root}*/*.rb"].each do |file| 11 | path = file.sub(root, "").sub(/\.rb$/, "") 12 | Aegis::Loader.paths.should include(path) unless path == 'aegis/loader' || path == 'aegis/spec' 13 | end 14 | 15 | end 16 | 17 | end 18 | 19 | describe 'load_paths' do 20 | 21 | it "should require all paths" do 22 | 23 | Aegis::Loader.stub(:paths => ['one', 'two']) 24 | Aegis::Loader.should_receive(:require).with('one') 25 | Aegis::Loader.should_receive(:require).with('two') 26 | Aegis::Loader.load_paths 27 | 28 | end 29 | 30 | end 31 | 32 | describe 'loaded?' do 33 | 34 | it "should be loaded by the time this test runs" do 35 | Aegis::Loader.should be_loaded 36 | end 37 | 38 | end 39 | 40 | end -------------------------------------------------------------------------------- /spec/aegis/permissions_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Aegis::Permissions do 4 | 5 | before(:each) do 6 | 7 | permissions = @permissions = Class.new(Aegis::Permissions) do 8 | role :guest 9 | role :user 10 | role :moderator 11 | role :admin, :default_permission => :allow 12 | end 13 | 14 | @user_class = Class.new(ActiveRecord::Base) do 15 | set_table_name 'users' 16 | has_role :permissions => permissions 17 | end 18 | 19 | @user = @user_class.new(:role_name => 'user') 20 | @moderator = @user_class.new(:role_name => 'moderator') 21 | @admin = @user_class.new(:role_name => 'admin') 22 | 23 | end 24 | 25 | describe 'find_role_by_name' do 26 | 27 | it "should look up previously defined roles" do 28 | 29 | user_role = @permissions.find_role_by_name('user') 30 | admin_role = @permissions.find_role_by_name('admin') 31 | 32 | user_role.name.should == 'user' 33 | user_role.may_by_default?.should be_false 34 | 35 | admin_role.name.should == 'admin' 36 | admin_role.may_by_default?.should be_true 37 | 38 | end 39 | 40 | it "should be nil if no role with that name was defined" do 41 | @permissions.find_role_by_name('nonexisting_role').should be_nil 42 | end 43 | 44 | end 45 | 46 | describe 'permission definition' do 47 | 48 | it "should allow the definition of simple actions" do 49 | 50 | @permissions.class_eval do 51 | action :action_name do 52 | allow :user 53 | end 54 | end 55 | 56 | @permissions.may?(@user, 'action_name').should be_true 57 | 58 | end 59 | 60 | it "should allow the definition of multiple actions at once" do 61 | 62 | @permissions.class_eval do 63 | action :action1, :action2 do 64 | allow 65 | end 66 | end 67 | 68 | @permissions.may?(@user, 'action1').should be_true 69 | @permissions.may?(@user, 'action2').should be_true 70 | @permissions.may?(@user, 'action3').should be_false 71 | 72 | end 73 | 74 | it "should match an allow/deny directive to everyone is no role is named" do 75 | @permissions.class_eval do 76 | action :allowed_to_all do 77 | allow 78 | end 79 | action :denied_to_all do 80 | deny 81 | end 82 | end 83 | 84 | @permissions.may?(@user, 'allowed_to_all').should be_true 85 | @permissions.may?(@admin, 'denied_to_all').should be_false 86 | end 87 | 88 | it "should allow to grant permissions to multiple roles at once" do 89 | 90 | @permissions.class_eval do 91 | action :action_name do 92 | allow :user, :moderator 93 | end 94 | end 95 | 96 | @permissions.may?(@user, 'action_name').should be_true 97 | @permissions.may?(@moderator, 'action_name').should be_true 98 | 99 | end 100 | 101 | it "should return the default permission when queried for undefined actions" do 102 | 103 | @permissions.may?(@user, 'undefined_action').should be_false 104 | @permissions.may?(@admin, 'undefined_action').should be_true 105 | 106 | end 107 | 108 | it "should distinguish between roles" do 109 | 110 | @permissions.class_eval do 111 | action :update_news do 112 | allow :moderator 113 | end 114 | end 115 | 116 | @permissions.may?(@user, 'update_news').should be_false 117 | @permissions.may?(@moderator, 'update_news').should be_true 118 | 119 | end 120 | 121 | describe 'multiple roles' do 122 | 123 | before(:each) do 124 | 125 | @permissions.class_eval do 126 | action :update_news do 127 | allow :moderator 128 | end 129 | end 130 | 131 | end 132 | 133 | it "should allow a user with multiple roles access if at least one role passes, even if other roles don't" do 134 | person = @user_class.new(:role_names => ['user', 'moderator']) 135 | @permissions.may?(person, 'update_news').should be_true 136 | end 137 | 138 | it "should deny a user with multiple roles access if no role passes" do 139 | person = @user_class.new(:role_names => ['user', 'guest']) 140 | @permissions.may?(person, 'update_news').should be_false 141 | end 142 | 143 | it "should deny a user with no roles access" do 144 | person = @user_class.new(:role_names => []) 145 | @permissions.may?(person, 'update_news').should be_false 146 | end 147 | 148 | it "should honor default permissions" do 149 | person = @user_class.new(:role_names => ['admin']) 150 | @permissions.may?(person, 'update_news').should be_true 151 | end 152 | 153 | end 154 | 155 | it "should run sieves in a sequence, the result being the last matching sieve" do 156 | 157 | @permissions.class_eval do 158 | action :update_news do 159 | allow :everyone 160 | deny :user 161 | end 162 | end 163 | 164 | @permissions.may?(@user, 'update_news').should be_false 165 | @permissions.may?(@moderator, 'update_news').should be_true 166 | 167 | end 168 | 169 | it "should evaluate collection resources" do 170 | 171 | @permissions.class_eval do 172 | resources :posts do 173 | allow :moderator 174 | end 175 | end 176 | 177 | @permissions.may?(@moderator, 'update_post', "the post").should be_true 178 | @permissions.may?(@moderator, 'show_post', "the post").should be_true 179 | @permissions.may?(@moderator, 'create_post', "the post").should be_true 180 | @permissions.may?(@moderator, 'destroy_post', "the post").should be_true 181 | @permissions.may?(@moderator, 'index_posts').should be_true 182 | 183 | end 184 | 185 | it "should allow to configure generated resource actions" do 186 | 187 | @permissions.class_eval do 188 | resources :posts do 189 | action :index do 190 | allow :user 191 | end 192 | action :show do 193 | allow :user 194 | end 195 | end 196 | end 197 | 198 | @permissions.may?(@user, 'update_post', "the post").should be_false 199 | @permissions.may?(@user, 'show_post', "the post").should be_true 200 | @permissions.may?(@user, 'create_post', "the post").should be_false 201 | @permissions.may?(@user, 'destroy_post', "the post").should be_false 202 | @permissions.may?(@user, 'index_posts').should be_true 203 | 204 | @permissions.find_action_by_path('index_posts').takes_object.should be_false 205 | @permissions.find_action_by_path('show_post').takes_object.should be_true 206 | 207 | end 208 | 209 | it "should raise an error if an action takes an object but does not get it in the arguments" do 210 | 211 | @permissions.class_eval do 212 | resources :posts do 213 | allow :moderator 214 | end 215 | end 216 | 217 | expect do 218 | @permissions.may?(@moderator, 'update_post') 219 | end.to raise_error(ArgumentError) 220 | 221 | end 222 | 223 | it "should evaluate sieves with blocks" do 224 | 225 | @permissions.class_eval do 226 | resources :posts do 227 | allow :user do 228 | user.name == 'Waldo' 229 | end 230 | end 231 | end 232 | 233 | frank = @user_class.new(:name => 'Frank', :role_name => 'user') 234 | waldo = @user_class.new(:name => 'Waldo', :role_name => 'user') 235 | 236 | @permissions.may?(frank, 'update_post', 'the post').should be_false 237 | @permissions.may?(waldo, 'update_post', 'the post').should be_true 238 | 239 | end 240 | 241 | it "should evaluate singleton resources, which take no object" do 242 | 243 | @permissions.class_eval do 244 | resource :session do 245 | allow :moderator 246 | end 247 | end 248 | 249 | @permissions.may?(@moderator, 'update_session').should be_true 250 | @permissions.may?(@moderator, 'show_session').should be_true 251 | @permissions.may?(@moderator, 'create_session').should be_true 252 | @permissions.may?(@moderator, 'destroy_session').should be_true 253 | 254 | end 255 | 256 | it "should allow to nest resources into collection resources" do 257 | 258 | @permissions.class_eval do 259 | resources :properties do 260 | resources :comments do 261 | allow :moderator 262 | end 263 | end 264 | end 265 | 266 | @permissions.may?(@moderator, 'update_property_comment', "the property", "the comment").should be_true 267 | @permissions.may?(@moderator, 'show_property_comment', "the property", "the comment").should be_true 268 | @permissions.may?(@moderator, 'create_property_comment', "the property").should be_true 269 | @permissions.may?(@moderator, 'destroy_property_comment', "the property", "the comment").should be_true 270 | @permissions.may?(@moderator, 'index_property_comments', "the property").should be_true 271 | 272 | end 273 | 274 | it "should allow to nest resources into singleton resources" do 275 | 276 | @permissions.class_eval do 277 | resource :account do 278 | resources :bookings do 279 | allow :moderator 280 | end 281 | end 282 | end 283 | 284 | @permissions.may?(@moderator, 'update_account_booking', "the booking").should be_true 285 | @permissions.may?(@moderator, 'show_account_booking', "the booking").should be_true 286 | @permissions.may?(@moderator, 'create_account_booking').should be_true 287 | @permissions.may?(@moderator, 'destroy_account_booking', "the booking").should be_true 288 | @permissions.may?(@moderator, 'index_account_bookings').should be_true 289 | 290 | @permissions.find_action_by_path('update_account').should_not be_abstract 291 | 292 | end 293 | 294 | it "should support namespaces, which act like singleton resources but don't generate actions by default" do 295 | 296 | @permissions.class_eval do 297 | namespace :admin do 298 | resources :bookings do 299 | allow :moderator 300 | end 301 | end 302 | end 303 | 304 | @permissions.may?(@moderator, 'update_admin_booking', "the booking").should be_true 305 | @permissions.may?(@moderator, 'show_admin_booking', "the booking").should be_true 306 | @permissions.may?(@moderator, 'create_admin_booking').should be_true 307 | @permissions.may?(@moderator, 'destroy_admin_booking', "the booking").should be_true 308 | @permissions.may?(@moderator, 'index_admin_bookings').should be_true 309 | 310 | @permissions.find_action_by_path('update_admin').should be_abstract 311 | 312 | end 313 | 314 | it "should allow multiple levels of resource-nesting" do 315 | 316 | @permissions.class_eval do 317 | resources :properties do 318 | resources :reviews do 319 | resources :comments do 320 | allow :moderator 321 | end 322 | end 323 | end 324 | end 325 | 326 | @permissions.may?(@moderator, 'update_property_review_comment', "the review", "the comment").should be_true 327 | @permissions.may?(@moderator, 'show_property_review_comment', "the review", "the comment").should be_true 328 | @permissions.may?(@moderator, 'create_property_review_comment', "the review").should be_true 329 | @permissions.may?(@moderator, 'destroy_property_review_comment', "the review", "the comment").should be_true 330 | @permissions.may?(@moderator, 'index_property_review_comments', "the review").should be_true 331 | 332 | end 333 | 334 | it "should raise an error if an action takes a parent object but does not get it in the arguments" do 335 | 336 | @permissions.class_eval do 337 | resources :properties do 338 | resources :comments 339 | end 340 | end 341 | 342 | lambda { @permissions.may?(@moderator, 'update_property_comment', "the comment") }.should raise_error(ArgumentError) 343 | 344 | end 345 | 346 | it "should allow sieves with blocks and arguments" do 347 | 348 | @permissions.class_eval do 349 | action :sign_in do 350 | allow do |password| 351 | user.password == password 352 | end 353 | end 354 | end 355 | 356 | @user.stub(:password => "secret") 357 | @permissions.may?(@user, "sign_in", "wrong_password").should be_false 358 | @permissions.may?(@user, "sign_in", "secret").should be_true 359 | 360 | end 361 | 362 | it 'should raise an error when trying to define a role named "everyone"' do 363 | 364 | expect do 365 | @permissions.class_eval do 366 | role :everyone 367 | end 368 | end.to raise_error(Aegis::InvalidSyntax) 369 | 370 | end 371 | 372 | it "should raise an error if the argument is given to the action (Aegis 1) instead of the allow block (Aegis 2)" do 373 | 374 | expect do 375 | @permissions.class_eval do 376 | action :sign_in do |password| 377 | allow :everyone 378 | end 379 | end 380 | end.to raise_error(Aegis::InvalidSyntax) 381 | 382 | end 383 | 384 | it 'should raise an error if a #permission (singular) method is called (which no longer exists in Aegis 2)' do 385 | 386 | expect do 387 | @permissions.class_eval do 388 | permission :foo do 389 | allow :everyone 390 | end 391 | end 392 | end.to raise_error(Aegis::InvalidSyntax) 393 | 394 | end 395 | 396 | it "should provide the object and parent_object for a sieve block" do 397 | spy = stub("spy") 398 | @permissions.class_eval do 399 | resources :properties do 400 | resources :comments do 401 | allow :moderator do |additional_argument| 402 | spy.observe(parent_object, object, additional_argument) 403 | end 404 | end 405 | end 406 | end 407 | 408 | spy.should_receive(:observe).with("the property", "the comment", "additional argument") 409 | @permissions.may?(@moderator, "update_property_comment", "the property", "the comment", "additional argument") 410 | end 411 | 412 | it "should evaluate additional resource actions" do 413 | 414 | @permissions.class_eval do 415 | resources :properties do 416 | action :zoom_into do 417 | allow :user 418 | end 419 | action :view_all, :collection => true do 420 | allow :user 421 | end 422 | end 423 | end 424 | 425 | @permissions.may?(@user, "zoom_into_property", "the property").should be_true 426 | @permissions.may?(@user, "view_all_properties").should be_true 427 | 428 | end 429 | 430 | it "should allow rules that only affect reading actions" do 431 | 432 | @permissions.class_eval do 433 | resources :posts do 434 | action :syndicate, :writing => false 435 | action :close 436 | reading do 437 | allow :user 438 | end 439 | end 440 | end 441 | 442 | @permissions.may?(@user, 'update_post', "the post").should be_false 443 | @permissions.may?(@user, 'show_post', "the post").should be_true 444 | @permissions.may?(@user, 'create_post', "the post").should be_false 445 | @permissions.may?(@user, 'destroy_post', "the post").should be_false 446 | @permissions.may?(@user, 'index_posts').should be_true 447 | @permissions.may?(@user, 'syndicate_post', "the post").should be_true 448 | @permissions.may?(@user, 'close_post", "the post').should be_false 449 | 450 | end 451 | 452 | it "should allow rules that only affect writing actions" do 453 | 454 | @permissions.class_eval do 455 | resources :posts do 456 | action :syndicate, :writing => false 457 | action :close 458 | writing do 459 | allow :moderator 460 | end 461 | end 462 | end 463 | 464 | # debugger 465 | 466 | @permissions.may?(@moderator, 'update_post', "the post").should be_true 467 | @permissions.may?(@moderator, 'show_post', "the post").should be_false 468 | @permissions.may?(@moderator, 'create_post', "the post").should be_true 469 | @permissions.may?(@moderator, 'destroy_post', "the post").should be_true 470 | @permissions.may?(@moderator, 'index_posts').should be_false 471 | @permissions.may?(@moderator, 'syndicate_post', "the post").should be_false 472 | @permissions.may?(@moderator, "close_post", "the post").should be_true 473 | 474 | end 475 | 476 | it 'should raise an error if a #reading directive is stated without a block' do 477 | expect do 478 | @permissions.class_eval do 479 | resources :posts do 480 | reading 481 | end 482 | end 483 | end.to raise_error(Aegis::InvalidSyntax) 484 | end 485 | 486 | it 'should raise an error if a #writing directive is stated without a block' do 487 | expect do 488 | @permissions.class_eval do 489 | resources :posts do 490 | writing 491 | end 492 | end 493 | end.to raise_error(Aegis::InvalidSyntax) 494 | end 495 | 496 | it "should allow resources with only selected actions" do 497 | @permissions.class_eval do 498 | resources :posts, :only => [:show, :update] 499 | end 500 | @permissions.find_action_by_path('update_post').should_not be_abstract 501 | @permissions.find_action_by_path('show_post').should_not be_abstract 502 | @permissions.find_action_by_path('create_post').should be_abstract 503 | @permissions.find_action_by_path('destroy_post').should be_abstract 504 | @permissions.find_action_by_path('index_posts').should be_abstract 505 | end 506 | 507 | it "should allow resources with all actions except a selected few" do 508 | @permissions.class_eval do 509 | resources :posts, :except => [:show, :update] 510 | end 511 | @permissions.find_action_by_path('update_post').should be_abstract 512 | @permissions.find_action_by_path('show_post').should be_abstract 513 | @permissions.find_action_by_path('create_post').should_not be_abstract 514 | @permissions.find_action_by_path('destroy_post').should_not be_abstract 515 | @permissions.find_action_by_path('index_posts').should_not be_abstract 516 | end 517 | 518 | it 'should allow to override individual actions' do 519 | @permissions.class_eval do 520 | resources :posts do 521 | allow :everyone 522 | action :create do 523 | deny :everyone 524 | end 525 | end 526 | end 527 | @permissions.may?(@user, 'index_posts').should be_true 528 | @permissions.may?(@user, 'create_post').should be_false 529 | end 530 | 531 | it 'should allow to repeatedly define permissions for the same action, deciding for the last directive that matched' do 532 | @permissions.class_eval do 533 | resources :posts do 534 | action :create do 535 | allow :everyone 536 | end 537 | action :create do 538 | deny :user 539 | end 540 | end 541 | end 542 | @permissions.may?(@admin, 'create_posts').should be_true 543 | @permissions.may?(@user, 'create_post').should be_false 544 | end 545 | 546 | it "should alias action names for all actions and resources, aliasing #new and #edit by default" do 547 | 548 | @permissions.class_eval do 549 | 550 | alias_action :delete => :destroy 551 | 552 | resources :properties do 553 | resources :comments 554 | end 555 | end 556 | 557 | @permissions.find_action_by_path('delete_property').should_not be_abstract 558 | @permissions.find_action_by_path('new_property').should_not be_abstract 559 | @permissions.find_action_by_path('edit_property').should_not be_abstract 560 | 561 | @permissions.find_action_by_path('delete_property_comment').should_not be_abstract 562 | @permissions.find_action_by_path('new_property_comment').should_not be_abstract 563 | @permissions.find_action_by_path('edit_property_comment').should_not be_abstract 564 | 565 | end 566 | end 567 | 568 | describe 'may!' do 569 | 570 | it "should return if permission is granted" do 571 | lambda { @permissions.may!(@admin, :delete_everything) }.should_not raise_error 572 | end 573 | 574 | it "should raise an error if permission is denied" do 575 | lambda { @permissions.may!(@user, :delete_everything) }.should raise_error(Aegis::AccessDenied) 576 | end 577 | 578 | end 579 | 580 | describe 'behavior when checking permissions without a user' do 581 | 582 | it "should raise an error if no missing user strategy is defined" do 583 | expect { @permissions.may?(nil, :some_action) }.to raise_error(Aegis::MissingUser) 584 | end 585 | 586 | it 'should raise an error if the missing user strategy is :error' do 587 | @permissions.class_eval do 588 | missing_user_means :error 589 | end 590 | expect { @permissions.may?(nil, :some_action) }.to raise_error(Aegis::MissingUser) 591 | end 592 | 593 | it "should substitute the results from the missing user strategy" do 594 | @permissions.class_eval do 595 | missing_user_means { User.new(:role_name => 'user') } 596 | action :create_post do 597 | allow :moderator 598 | end 599 | action :show_post do 600 | allow :user 601 | end 602 | end 603 | @permissions.may?(nil, :create_post).should be_false 604 | @permissions.may?(nil, :show_post).should be_true 605 | end 606 | 607 | end 608 | 609 | describe 'behavior when a permission is not defined' do 610 | 611 | it "should use the default permission if the strategy is :default_permission" do 612 | @permissions.class_eval do 613 | missing_action_means :default_permission 614 | end 615 | @permissions.may?(@user, 'missing_action').should be_false 616 | @permissions.may?(@admin, 'missing_action').should be_true 617 | end 618 | 619 | it "should grant everyone access if the strategy is :allow" do 620 | @permissions.class_eval do 621 | missing_action_means :allow 622 | end 623 | @permissions.may?(@user, 'missing_action').should be_true 624 | @permissions.may?(@admin, 'missing_action').should be_true 625 | end 626 | 627 | it "should deny everyone access if the strategy is :deny" do 628 | @permissions.class_eval do 629 | missing_action_means :deny 630 | end 631 | @permissions.may?(@user, 'missing_action').should be_false 632 | @permissions.may?(@admin, 'missing_action').should be_false 633 | end 634 | 635 | it "should raise an error naming the missing action if the strategy is :error" do 636 | @permissions.class_eval do 637 | missing_action_means :error 638 | end 639 | lambda { @permissions.may?(@user, 'missing_action') }.should raise_error(Aegis::MissingAction, 'Missing action: missing_action') 640 | lambda { @permissions.may?(@admin, 'missing_action') }.should raise_error(Aegis::MissingAction, 'Missing action: missing_action') 641 | end 642 | 643 | end 644 | 645 | describe 'guess_action' do 646 | 647 | it "should guess an action based on the given resource and action name, trying both singular and plural" do 648 | 649 | @permissions.class_eval do 650 | resources :posts 651 | end 652 | 653 | @permissions.guess_action(:posts, :index).should_not be_abstract 654 | @permissions.guess_action(:posts, :update).should_not be_abstract 655 | @permissions.guess_action(:posts, :unknown_action).should be_abstract 656 | 657 | end 658 | 659 | it "should consult the actions map first and use the the default behaviour for unmapped actions" do 660 | 661 | @permissions.class_eval do 662 | resources :posts, :only => [:update] do 663 | action :view_all, :collection => true 664 | end 665 | end 666 | 667 | @permissions.guess_action(:posts, 'index', 'index' => 'view_all_posts').should_not be_abstract 668 | @permissions.guess_action(:posts, 'update', 'index' => 'view_all_posts').should_not be_abstract 669 | 670 | end 671 | 672 | it "should find a root action that has the same name as the given resource" do 673 | 674 | @permissions.class_eval do 675 | action :author_section 676 | end 677 | 678 | @permissions.guess_action(:author_section, 'irrelevant_action_name').should_not be_abstract 679 | @permissions.guess_action(:undefined_action, 'irrelevant_action_name').should be_abstract 680 | 681 | end 682 | 683 | end 684 | 685 | describe 'find_action_by_path' do 686 | 687 | before(:each) do 688 | @permissions.class_eval do 689 | action :action_name do 690 | allow :user 691 | end 692 | end 693 | end 694 | 695 | it "should find an action by a string" do 696 | @permissions.find_action_by_path('action_name').should_not be_abstract 697 | end 698 | 699 | it "should find an action by a symbol" do 700 | @permissions.find_action_by_path(:action_name).should_not be_abstract 701 | end 702 | 703 | end 704 | 705 | end 706 | -------------------------------------------------------------------------------- /spec/aegis/sieve_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Aegis::Sieve do 4 | 5 | before(:each) do 6 | @role = stub('role', :name => 'user') 7 | # user = stub('user', :role => role) 8 | @context = OpenStruct.new(:role => @role) 9 | end 10 | 11 | describe 'may? 'do 12 | 13 | it "should use the role's name to find out if the sieve matches" do 14 | @role.should_receive(:name) 15 | Aegis::Sieve.new('moderator', true).may?(@context) 16 | end 17 | 18 | it "should return nil if the sieve doesn't match the role" do 19 | Aegis::Sieve.new('moderator', true).may?(@context).should be_nil 20 | end 21 | 22 | it "should return the effect if the sieve matches the role" do 23 | Aegis::Sieve.new('user', true).may?(@context).should be_true 24 | Aegis::Sieve.new('user', false).may?(@context).should be_false 25 | end 26 | 27 | it "should match any role if its role name is set to 'everyone'" do 28 | Aegis::Sieve.new('everyone', true).may?(@context).should be_true 29 | Aegis::Sieve.new('everyone', false).may?(@context).should be_false 30 | end 31 | 32 | context "with a block" do 33 | 34 | it "should return the effect if the block evaluates to true" do 35 | Aegis::Sieve.new('user', true, lambda { true }).may?(@context).should be_true 36 | Aegis::Sieve.new('user', false, lambda { true }).may?(@context).should be_false 37 | end 38 | 39 | it "should invert the effect if the block evaluates to false" do 40 | Aegis::Sieve.new('user', true, lambda { false }).may?(@context).should be_false 41 | Aegis::Sieve.new('user', false, lambda { false }).may?(@context).should be_true 42 | end 43 | 44 | end 45 | 46 | end 47 | 48 | end 49 | -------------------------------------------------------------------------------- /spec/aegis/spec/matchers_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Aegis::Spec::Matchers do 4 | 5 | describe 'be_allowed_to' do 6 | 7 | before(:each) do 8 | 9 | permissions = @permissions = Class.new(Aegis::Permissions) do 10 | role :user 11 | resources :files do 12 | allow :user do 13 | object == 'allowed-file' 14 | end 15 | end 16 | end 17 | 18 | @user_class = Class.new(ActiveRecord::Base) do 19 | set_table_name 'users' 20 | has_role :permissions => permissions 21 | end 22 | 23 | @user = @user_class.new(:role_name => 'user') 24 | 25 | end 26 | 27 | it 'should match the positive case' do 28 | @user.should be_allowed_to(:update_file, 'allowed-file') 29 | end 30 | 31 | it 'should match the negative case' do 32 | @user.should_not be_allowed_to(:update_file, 'denied-file') 33 | end 34 | 35 | end 36 | 37 | describe 'check_permissions' do 38 | 39 | before(:each) do 40 | @controller = Class.new(ActionController::Base) do 41 | include Aegis::Controller 42 | permissions :post 43 | end.new 44 | end 45 | 46 | it 'should match the positive case' do 47 | @controller.should check_permissions(:post) 48 | end 49 | 50 | it 'should match the negative case' do 51 | @controller.should_not check_permissions(:reviews) 52 | end 53 | 54 | end 55 | 56 | end 57 | -------------------------------------------------------------------------------- /spec/app_root/app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | include Aegis::Controller 3 | 4 | require_permissions 5 | 6 | def current_user 7 | nil # test missing_user_means 8 | end 9 | 10 | end 11 | -------------------------------------------------------------------------------- /spec/app_root/app/controllers/reviews_controller.rb: -------------------------------------------------------------------------------- 1 | class ReviewsController < ApplicationController 2 | 3 | permissions :property_reviews 4 | 5 | def show 6 | end 7 | 8 | def edit 9 | end 10 | 11 | def update 12 | end 13 | 14 | def new 15 | end 16 | 17 | def create 18 | end 19 | 20 | def destroy 21 | end 22 | 23 | def index 24 | end 25 | 26 | private 27 | 28 | def object 29 | @oject ||= parent_object.reviews.find(params[:id]) 30 | end 31 | 32 | def parent_object 33 | @parent_object ||= Property.find(params[:id]) 34 | end 35 | 36 | end 37 | -------------------------------------------------------------------------------- /spec/app_root/app/models/permissions.rb: -------------------------------------------------------------------------------- 1 | class Permissions < Aegis::Permissions 2 | 3 | role :user 4 | 5 | missing_user_means { User.new(:role_name => 'user') } 6 | 7 | resources :properties do 8 | resources :reviews do 9 | reading do 10 | allow :user 11 | end 12 | end 13 | end 14 | 15 | end 16 | 17 | -------------------------------------------------------------------------------- /spec/app_root/app/models/property.rb: -------------------------------------------------------------------------------- 1 | class Property < ActiveRecord::Base 2 | 3 | has_many :reviews 4 | 5 | end 6 | -------------------------------------------------------------------------------- /spec/app_root/app/models/review.rb: -------------------------------------------------------------------------------- 1 | class Review < ActiveRecord::Base 2 | 3 | belongs_to :property 4 | 5 | end -------------------------------------------------------------------------------- /spec/app_root/app/models/user.rb: -------------------------------------------------------------------------------- 1 | class User < ActiveRecord::Base 2 | 3 | has_role 4 | 5 | end -------------------------------------------------------------------------------- /spec/app_root/config/boot.rb: -------------------------------------------------------------------------------- 1 | # Allow customization of the rails framework path 2 | RAILS_FRAMEWORK_ROOT = (ENV['RAILS_FRAMEWORK_ROOT'] || "#{File.dirname(__FILE__)}/../../../../../../vendor/rails") unless defined?(RAILS_FRAMEWORK_ROOT) 3 | 4 | # Don't change this file! 5 | # Configure your app in config/environment.rb and config/environments/*.rb 6 | 7 | RAILS_ROOT = "#{File.dirname(__FILE__)}/.." unless defined?(RAILS_ROOT) 8 | 9 | module Rails 10 | class << self 11 | def boot! 12 | unless booted? 13 | preinitialize 14 | pick_boot.run 15 | end 16 | end 17 | 18 | def booted? 19 | defined? Rails::Initializer 20 | end 21 | 22 | def pick_boot 23 | (vendor_rails? ? VendorBoot : GemBoot).new 24 | end 25 | 26 | def vendor_rails? 27 | File.exist?(RAILS_FRAMEWORK_ROOT) 28 | end 29 | 30 | def preinitialize 31 | load(preinitializer_path) if File.exist?(preinitializer_path) 32 | end 33 | 34 | def preinitializer_path 35 | "#{RAILS_ROOT}/config/preinitializer.rb" 36 | end 37 | end 38 | 39 | class Boot 40 | def run 41 | load_initializer 42 | Rails::Initializer.run(:set_load_path) 43 | end 44 | end 45 | 46 | class VendorBoot < Boot 47 | def load_initializer 48 | require "#{RAILS_FRAMEWORK_ROOT}/railties/lib/initializer" 49 | Rails::Initializer.run(:install_gem_spec_stubs) 50 | end 51 | end 52 | 53 | class GemBoot < Boot 54 | def load_initializer 55 | self.class.load_rubygems 56 | load_rails_gem 57 | require 'initializer' 58 | end 59 | 60 | def load_rails_gem 61 | if version = self.class.gem_version 62 | gem 'rails', version 63 | else 64 | gem 'rails' 65 | end 66 | rescue Gem::LoadError => load_error 67 | $stderr.puts %(Missing the Rails #{version} gem. Please `gem install -v=#{version} rails`, update your RAILS_GEM_VERSION setting in config/environment.rb for the Rails version you do have installed, or comment out RAILS_GEM_VERSION to use the latest version installed.) 68 | exit 1 69 | end 70 | 71 | class << self 72 | def rubygems_version 73 | Gem::RubyGemsVersion rescue nil 74 | end 75 | 76 | def gem_version 77 | if defined? RAILS_GEM_VERSION 78 | RAILS_GEM_VERSION 79 | elsif ENV.include?('RAILS_GEM_VERSION') 80 | ENV['RAILS_GEM_VERSION'] 81 | else 82 | parse_gem_version(read_environment_rb) 83 | end 84 | end 85 | 86 | def load_rubygems 87 | require 'rubygems' 88 | min_version = '1.1.1' 89 | unless rubygems_version >= min_version 90 | $stderr.puts %Q(Rails requires RubyGems >= #{min_version} (you have #{rubygems_version}). Please `gem update --system` and try again.) 91 | exit 1 92 | end 93 | 94 | rescue LoadError 95 | $stderr.puts %Q(Rails requires RubyGems >= #{min_version}. Please install RubyGems and try again: http://rubygems.rubyforge.org) 96 | exit 1 97 | end 98 | 99 | def parse_gem_version(text) 100 | $1 if text =~ /^[^#]*RAILS_GEM_VERSION\s*=\s*["']([!~<>=]*\s*[\d.]+)["']/ 101 | end 102 | 103 | private 104 | def read_environment_rb 105 | environment_rb = "#{RAILS_ROOT}/config/environment.rb" 106 | environment_rb = "#{HELPER_RAILS_ROOT}/config/environment.rb" unless File.exists?(environment_rb) 107 | File.read(environment_rb) 108 | end 109 | end 110 | end 111 | end 112 | 113 | # All that for this: 114 | Rails.boot! 115 | -------------------------------------------------------------------------------- /spec/app_root/config/database.yml: -------------------------------------------------------------------------------- 1 | in_memory: 2 | adapter: sqlite3 3 | database: ":memory:" 4 | verbosity: quiet 5 | sqlite: 6 | adapter: sqlite 7 | dbfile: plugin_test.sqlite.db 8 | sqlite3: 9 | adapter: sqlite3 10 | dbfile: plugin_test.sqlite3.db 11 | postgresql: 12 | adapter: postgresql 13 | username: postgres 14 | password: postgres 15 | database: plugin_test 16 | mysql: 17 | adapter: mysql 18 | host: localhost 19 | username: root 20 | password: 21 | database: plugin_test 22 | -------------------------------------------------------------------------------- /spec/app_root/config/environment.rb: -------------------------------------------------------------------------------- 1 | require File.join(File.dirname(__FILE__), 'boot') 2 | 3 | Rails::Initializer.run do |config| 4 | config.cache_classes = false 5 | config.whiny_nils = true 6 | config.action_controller.session = { :key => "_myapp_session", :secret => "gwirofjweroijger8924rt2zfwehfuiwehb1378rifowenfoqwphf23" } 7 | config.plugin_locators.unshift( 8 | Class.new(Rails::Plugin::Locator) do 9 | def plugins 10 | [Rails::Plugin.new(File.expand_path('.'))] 11 | end 12 | end 13 | ) unless defined?(PluginTestHelper::PluginLocator) 14 | end 15 | -------------------------------------------------------------------------------- /spec/app_root/config/environments/in_memory.rb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/makandra/aegis/8feac581285d38d0a9876d73f2da755ec3359678/spec/app_root/config/environments/in_memory.rb -------------------------------------------------------------------------------- /spec/app_root/config/environments/mysql.rb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/makandra/aegis/8feac581285d38d0a9876d73f2da755ec3359678/spec/app_root/config/environments/mysql.rb -------------------------------------------------------------------------------- /spec/app_root/config/environments/postgresql.rb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/makandra/aegis/8feac581285d38d0a9876d73f2da755ec3359678/spec/app_root/config/environments/postgresql.rb -------------------------------------------------------------------------------- /spec/app_root/config/environments/sqlite.rb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/makandra/aegis/8feac581285d38d0a9876d73f2da755ec3359678/spec/app_root/config/environments/sqlite.rb -------------------------------------------------------------------------------- /spec/app_root/config/environments/sqlite3.rb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/makandra/aegis/8feac581285d38d0a9876d73f2da755ec3359678/spec/app_root/config/environments/sqlite3.rb -------------------------------------------------------------------------------- /spec/app_root/config/routes.rb: -------------------------------------------------------------------------------- 1 | ActionController::Routing::Routes.draw do |map| 2 | 3 | map.resources :properties do |properties| 4 | properties.resources :reviews 5 | end 6 | 7 | end 8 | -------------------------------------------------------------------------------- /spec/app_root/db/migrate/001_create_users.rb: -------------------------------------------------------------------------------- 1 | class CreateUsers < ActiveRecord::Migration 2 | 3 | def self.up 4 | create_table :users do |t| 5 | t.string :role_name 6 | t.string :name 7 | t.timestamps 8 | end 9 | end 10 | 11 | def self.down 12 | drop_table :users 13 | end 14 | 15 | end 16 | -------------------------------------------------------------------------------- /spec/app_root/db/migrate/002_create_properties.rb: -------------------------------------------------------------------------------- 1 | class CreateProperties < ActiveRecord::Migration 2 | 3 | def self.up 4 | create_table :properties do |t| 5 | t.timestamps 6 | end 7 | end 8 | 9 | def self.down 10 | drop_table :properties 11 | end 12 | 13 | end 14 | -------------------------------------------------------------------------------- /spec/app_root/db/migrate/003_create_reviews.rb: -------------------------------------------------------------------------------- 1 | class CreateReviews < ActiveRecord::Migration 2 | 3 | def self.up 4 | create_table :reviews do |t| 5 | t.integer :property_id 6 | t.timestamps 7 | end 8 | end 9 | 10 | def self.down 11 | drop_table :reviews 12 | end 13 | 14 | end 15 | -------------------------------------------------------------------------------- /spec/app_root/lib/console_with_fixtures.rb: -------------------------------------------------------------------------------- 1 | # Loads fixtures into the database when running the test app via the console 2 | (ENV['FIXTURES'] ? ENV['FIXTURES'].split(/,/) : Dir.glob(File.join(Rails.root, '../fixtures/*.{yml,csv}'))).each do |fixture_file| 3 | Fixtures.create_fixtures(File.join(Rails.root, '../fixtures'), File.basename(fixture_file, '.*')) 4 | end 5 | -------------------------------------------------------------------------------- /spec/app_root/log/.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | -------------------------------------------------------------------------------- /spec/app_root/script/console: -------------------------------------------------------------------------------- 1 | irb = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb' 2 | libs = " -r irb/completion" 3 | libs << " -r test/test_helper" 4 | libs << " -r console_app" 5 | libs << " -r console_with_helpers" 6 | libs << " -r console_with_fixtures" 7 | exec "#{irb} #{libs} --simple-prompt" 8 | -------------------------------------------------------------------------------- /spec/controllers/reviews_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/../spec_helper' 2 | 3 | describe ReviewsController do 4 | 5 | before(:each) do 6 | Property.stub(:find => stub("a property", :reviews => stub("reviews", :find => "a review"))) 7 | end 8 | 9 | it "should grant access in a fully integrated scenario" do 10 | lambda { get :show, :property_id => '10', :id => '20' }.should_not raise_error 11 | lambda { get :index, :property_id => '10' }.should_not raise_error 12 | end 13 | 14 | it "should deny access in a fully integrated scenario" do 15 | lambda { put :update, :property_id => '10', :id => '20' }.should raise_error(Aegis::AccessDenied) 16 | lambda { get :new, :property_id => '10' }.should raise_error(Aegis::AccessDenied) 17 | end 18 | 19 | end 20 | -------------------------------------------------------------------------------- /spec/rcov.opts: -------------------------------------------------------------------------------- 1 | --exclude "spec/*,gems/*" 2 | --rails -------------------------------------------------------------------------------- /spec/spec.opts: -------------------------------------------------------------------------------- 1 | --colour 2 | --format progress 3 | --loadby mtime 4 | --reverse 5 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | $: << File.join(File.dirname(__FILE__), "/../lib" ) 2 | 3 | # Set the default environment to sqlite3's in_memory database 4 | ENV['RAILS_ENV'] ||= 'in_memory' 5 | 6 | # Load the Rails environment and testing framework 7 | require "#{File.dirname(__FILE__)}/app_root/config/environment" 8 | # require "#{File.dirname(__FILE__)}/../lib/aegis" 9 | require 'spec/rails' 10 | 11 | # Undo changes to RAILS_ENV 12 | silence_warnings {RAILS_ENV = ENV['RAILS_ENV']} 13 | 14 | # Run the migrations 15 | ActiveRecord::Migrator.migrate("#{Rails.root}/db/migrate") 16 | 17 | Spec::Runner.configure do |config| 18 | config.use_transactional_fixtures = true 19 | config.use_instantiated_fixtures = false 20 | end 21 | 22 | # Requires supporting files with custom matchers and macros, etc, 23 | # in ./support/ and its subdirectories. 24 | Dir[File.expand_path(File.join(File.dirname(__FILE__),'support','**','*.rb'))].each {|f| require f} 25 | -------------------------------------------------------------------------------- /spec/support/spec_candy.rb: -------------------------------------------------------------------------------- 1 | # Abbreviated from http://makandra.com/notes/627-the-definitive-spec_candy-rb-rspec-helper 2 | 3 | Object.class_eval do 4 | 5 | # a should receive that executes the expected method as usual. does not work with and_return 6 | def should_receive_and_execute(method) 7 | method_called = "_#{method}_called" 8 | 9 | prototype = respond_to?(:singleton_class) ? singleton_class : metaclass 10 | prototype.class_eval do 11 | define_method method do |*args| 12 | send(method_called, *args) 13 | super 14 | end 15 | end 16 | 17 | should_receive(method_called) 18 | end 19 | 20 | end 21 | --------------------------------------------------------------------------------