eaco 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +21 -0
- data/.rspec +2 -0
- data/.travis.yml +18 -0
- data/.yardopts +1 -0
- data/Appraisals +22 -0
- data/Gemfile +4 -0
- data/Guardfile +34 -0
- data/LICENSE.txt +23 -0
- data/README.md +225 -0
- data/Rakefile +26 -0
- data/eaco.gemspec +27 -0
- data/features/active_record.example.yml +8 -0
- data/features/active_record.travis.yml +7 -0
- data/features/rails_integration.feature +10 -0
- data/features/step_definitions/database.rb +7 -0
- data/features/step_definitions/resource_authorization.rb +15 -0
- data/features/support/env.rb +9 -0
- data/gemfiles/rails_3.2.gemfile +9 -0
- data/gemfiles/rails_4.0.gemfile +8 -0
- data/gemfiles/rails_4.1.gemfile +8 -0
- data/gemfiles/rails_4.2.gemfile +8 -0
- data/lib/eaco.rb +93 -0
- data/lib/eaco/acl.rb +206 -0
- data/lib/eaco/actor.rb +86 -0
- data/lib/eaco/adapters.rb +14 -0
- data/lib/eaco/adapters/active_record.rb +70 -0
- data/lib/eaco/adapters/active_record/compatibility.rb +83 -0
- data/lib/eaco/adapters/active_record/compatibility/v32.rb +27 -0
- data/lib/eaco/adapters/active_record/compatibility/v40.rb +59 -0
- data/lib/eaco/adapters/active_record/compatibility/v41.rb +16 -0
- data/lib/eaco/adapters/active_record/compatibility/v42.rb +17 -0
- data/lib/eaco/adapters/active_record/postgres_jsonb.rb +36 -0
- data/lib/eaco/adapters/couchrest_model.rb +37 -0
- data/lib/eaco/adapters/couchrest_model/couchdb_lucene.rb +71 -0
- data/lib/eaco/controller.rb +158 -0
- data/lib/eaco/cucumber.rb +11 -0
- data/lib/eaco/cucumber/active_record.rb +163 -0
- data/lib/eaco/cucumber/active_record/department.rb +19 -0
- data/lib/eaco/cucumber/active_record/document.rb +18 -0
- data/lib/eaco/cucumber/active_record/position.rb +21 -0
- data/lib/eaco/cucumber/active_record/schema.rb +36 -0
- data/lib/eaco/cucumber/active_record/user.rb +24 -0
- data/lib/eaco/cucumber/world.rb +136 -0
- data/lib/eaco/designator.rb +264 -0
- data/lib/eaco/dsl.rb +40 -0
- data/lib/eaco/dsl/acl.rb +163 -0
- data/lib/eaco/dsl/actor.rb +139 -0
- data/lib/eaco/dsl/actor/designators.rb +110 -0
- data/lib/eaco/dsl/base.rb +52 -0
- data/lib/eaco/dsl/resource.rb +129 -0
- data/lib/eaco/dsl/resource/permissions.rb +131 -0
- data/lib/eaco/error.rb +36 -0
- data/lib/eaco/railtie.rb +46 -0
- data/lib/eaco/rake.rb +10 -0
- data/lib/eaco/rake/default_task.rb +164 -0
- data/lib/eaco/resource.rb +234 -0
- data/lib/eaco/version.rb +7 -0
- data/spec/eaco/acl_spec.rb +147 -0
- data/spec/eaco/actor_spec.rb +13 -0
- data/spec/eaco/adapters/active_record/postgres_jsonb_spec.rb +9 -0
- data/spec/eaco/adapters/active_record_spec.rb +13 -0
- data/spec/eaco/adapters/couchrest_model/couchdb_lucene_spec.rb +9 -0
- data/spec/eaco/adapters/couchrest_model_spec.rb +9 -0
- data/spec/eaco/controller_spec.rb +12 -0
- data/spec/eaco/designator_spec.rb +25 -0
- data/spec/eaco/dsl/acl_spec.rb +9 -0
- data/spec/eaco/dsl/actor/designators_spec.rb +7 -0
- data/spec/eaco/dsl/actor_spec.rb +15 -0
- data/spec/eaco/dsl/resource/permissions_spec.rb +7 -0
- data/spec/eaco/dsl/resource_spec.rb +17 -0
- data/spec/eaco/error_spec.rb +9 -0
- data/spec/eaco/resource_spec.rb +31 -0
- data/spec/eaco_spec.rb +49 -0
- data/spec/spec_helper.rb +71 -0
- metadata +296 -0
@@ -0,0 +1,158 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
|
3
|
+
module Eaco
|
4
|
+
|
5
|
+
##
|
6
|
+
# An ActionController extension to verify authorization in Rails applications.
|
7
|
+
#
|
8
|
+
# Tested on Rails 3.2 and up on Ruby 2.0 and up.
|
9
|
+
#
|
10
|
+
module Controller
|
11
|
+
extend ActiveSupport::Concern
|
12
|
+
|
13
|
+
##
|
14
|
+
# Controller authorization DSL.
|
15
|
+
#
|
16
|
+
module ClassMethods
|
17
|
+
|
18
|
+
##
|
19
|
+
# Defines the ability required to access a given controller action.
|
20
|
+
#
|
21
|
+
# Example:
|
22
|
+
#
|
23
|
+
# class DocumentsController < ApplicationController
|
24
|
+
# authorize :index, [:folder, :index]
|
25
|
+
# authorize :show, [:folder, :read]
|
26
|
+
# authorize :create, :update, [:folder, :write]
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# Here +@folder+ is expected to be an authorized +Resource+, and for the
|
30
|
+
# +index+ action the +current_user+ is checked to +can?(:index, @folder)+
|
31
|
+
# while for +show+, +can?(:read, @folder)+ and for +create+ and +update+
|
32
|
+
# checks that it +can?(:write, @folder)+.
|
33
|
+
#
|
34
|
+
# The special +:all+ action name requires the given ability on the given
|
35
|
+
# Resource for all actions.
|
36
|
+
#
|
37
|
+
# If an action has no authorization defined, access is granted.
|
38
|
+
#
|
39
|
+
# Adds {Controller#confront_eaco} as a +before_filter+.
|
40
|
+
#
|
41
|
+
# @param actions [Variadic] see above.
|
42
|
+
#
|
43
|
+
# @return void
|
44
|
+
#
|
45
|
+
def authorize(*actions)
|
46
|
+
target = actions.pop
|
47
|
+
|
48
|
+
actions.each {|action| authorization_permissions.update(action => target)}
|
49
|
+
|
50
|
+
@_eaco_filter_installed ||= begin
|
51
|
+
before_filter :confront_eaco
|
52
|
+
true
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
##
|
57
|
+
# @return [Symbol] the permission required to access the given action.
|
58
|
+
#
|
59
|
+
def permission_for(action)
|
60
|
+
authorization_permissions[action] || authorization_permissions[:all]
|
61
|
+
end
|
62
|
+
|
63
|
+
protected
|
64
|
+
##
|
65
|
+
# Permission requirements configured on this controller.
|
66
|
+
#
|
67
|
+
def authorization_permissions
|
68
|
+
@_authorization_permissions ||= {}
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
##
|
73
|
+
# Asks Eaco whether thou shalt pass or not.
|
74
|
+
#
|
75
|
+
# The implementation is left in this method's body, despite a bit long for
|
76
|
+
# many's taste, as it is pretty imperative and simple code. Moreover, the
|
77
|
+
# less we pollute ActionController's namespace, the better.
|
78
|
+
#
|
79
|
+
# @return [void]
|
80
|
+
#
|
81
|
+
# @raise [Error] if the instance variable configured in {.authorize} is not found
|
82
|
+
# @raise [Forbidden] if the +current_user+ is not granted access.
|
83
|
+
#
|
84
|
+
#
|
85
|
+
# == La Guardiana
|
86
|
+
# /\
|
87
|
+
# .-_-. / \
|
88
|
+
# || .-.( .' .-. // \ /
|
89
|
+
# \\\/ (((\ /))) \ / // )(
|
90
|
+
# ) '._ ,-. ___. )/ //(__)
|
91
|
+
# \_((( ( :) \)))/ , / ||
|
92
|
+
# \_ \ '-' /_ /| ),// ||
|
93
|
+
# \ (_._.'_ \ (o__// _||_
|
94
|
+
# \ )\ .(/ / __) \ \
|
95
|
+
# ( \ '_ .' /( |-. \
|
96
|
+
# \_'._'.\__/)))) (__)'.'.
|
97
|
+
# _._ | | _.-._ || \ '.
|
98
|
+
# / //--' / '--//'-'/\||____\ '.
|
99
|
+
# \---.\ .----.// // ||// '\ \
|
100
|
+
# / ' \/ ' \\__\\ ,||\\_______.'
|
101
|
+
# \\___//\\____//\____\ ||
|
102
|
+
# _.-'''---. /\___/ \____/ \\/ ||
|
103
|
+
# ..'_.''''---.| /. \ / ||
|
104
|
+
# .'.-'O __ / _/ )_.--.____( ||
|
105
|
+
# / / / \__/ /' /\ \(__.--._____) ||
|
106
|
+
# | | /\ \ \_.' | | \ | ||
|
107
|
+
# \ '.__\,_.'.__/./ / ) . |\ ||
|
108
|
+
# '..__ O --' ___..' /\ /|'. ||
|
109
|
+
# ''----' | \/\.' / /'. ||
|
110
|
+
# |\(()).' / \ ||
|
111
|
+
# _/ \ \/ / \||
|
112
|
+
# __..--'' '. | |||
|
113
|
+
# .-'' / '._|/ |||
|
114
|
+
# / __.- / /||
|
115
|
+
# \ ____..-----'' / | ||
|
116
|
+
# '. )). | / ||
|
117
|
+
# ''._// \ .-----./ ||
|
118
|
+
# '. \ (.-----.) ||
|
119
|
+
# '. \ | / ||
|
120
|
+
# )_ \ | | ||
|
121
|
+
# /__'O\ ( ) ( ||
|
122
|
+
# _______mrf,-'____/|/__ |\ \ ||
|
123
|
+
# | | ||
|
124
|
+
# |____) (__)
|
125
|
+
# '-----' ||
|
126
|
+
# \ | ||
|
127
|
+
# \ | ||
|
128
|
+
# \ | ||
|
129
|
+
# | \ ||
|
130
|
+
# |_ \ ||
|
131
|
+
# /_'O\||
|
132
|
+
# .-'___/(__)
|
133
|
+
#
|
134
|
+
# http://ascii.co.uk/art/guardiana
|
135
|
+
#
|
136
|
+
def confront_eaco
|
137
|
+
action = params[:action].intern
|
138
|
+
resource_ivar, permission = self.class.permission_for(action)
|
139
|
+
|
140
|
+
if resource_ivar && permission
|
141
|
+
resource = instance_variable_get(['@', resource_ivar].join.intern)
|
142
|
+
|
143
|
+
if resource.nil?
|
144
|
+
raise Error, <<-EOF
|
145
|
+
@#{resource_ivar} is not set, can't authorize #{self}##{action}
|
146
|
+
EOF
|
147
|
+
end
|
148
|
+
|
149
|
+
unless current_user.can? permission, resource
|
150
|
+
raise Forbidden, <<-EOF
|
151
|
+
`#{current_user}' not authorized to `#{action}' on `#{resource}'
|
152
|
+
EOF
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
end
|
@@ -0,0 +1,163 @@
|
|
1
|
+
begin
|
2
|
+
require 'active_record'
|
3
|
+
rescue LoadError
|
4
|
+
abort "ActiveRecord requires the rails appraisal. Try `appraisal cucumber`"
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'yaml'
|
8
|
+
|
9
|
+
module Eaco
|
10
|
+
module Cucumber
|
11
|
+
|
12
|
+
##
|
13
|
+
# +ActiveRecord+ configuration and connection.
|
14
|
+
#
|
15
|
+
# Database configuration is looked up in +features/active_record.yml+ by
|
16
|
+
# default. Logs are sent to +features/active_record.log+, truncating the
|
17
|
+
# file at each run.
|
18
|
+
#
|
19
|
+
# Environment variables:
|
20
|
+
#
|
21
|
+
# * +EACO_AR_CONFIG+ specify a different +ActiveRecord+ configuration file
|
22
|
+
# * +VERBOSE+ log to +stderr+
|
23
|
+
#
|
24
|
+
module ActiveRecord
|
25
|
+
autoload :Document, 'eaco/cucumber/active_record/document' # Resource
|
26
|
+
autoload :User, 'eaco/cucumber/active_record/user' # Actor
|
27
|
+
autoload :Department, 'eaco/cucumber/active_record/department' # Designator source
|
28
|
+
autoload :Position, 'eaco/cucumber/active_record/position' # Designator source
|
29
|
+
|
30
|
+
extend self
|
31
|
+
|
32
|
+
##
|
33
|
+
# Looks up ActiveRecord and sets the +logger+.
|
34
|
+
#
|
35
|
+
# @return [Class] +ActiveRecord::Base+
|
36
|
+
#
|
37
|
+
def active_record
|
38
|
+
@_active_record ||= ::ActiveRecord::Base.tap do |active_record|
|
39
|
+
active_record.logger = ::Logger.new(active_record_log).tap {|l| l.level = 0}
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
##
|
44
|
+
# Log to stderr if +VERBOSE+ is given, else log to
|
45
|
+
# +features/active_record.log+
|
46
|
+
#
|
47
|
+
# @return [IO] the log destination
|
48
|
+
#
|
49
|
+
def active_record_log
|
50
|
+
@_active_record_log ||= ENV['VERBOSE'] ? $stderr :
|
51
|
+
'features/active_record.log'.tap {|f| File.open(f, "w+")}
|
52
|
+
end
|
53
|
+
|
54
|
+
##
|
55
|
+
# @return [Logger] the logger configured, logging to {.active_record_log}.
|
56
|
+
#
|
57
|
+
def logger
|
58
|
+
active_record.logger
|
59
|
+
end
|
60
|
+
|
61
|
+
##
|
62
|
+
# @return [ActiveRecord::Connection] the current +ActiveRecord+ connection
|
63
|
+
# object.
|
64
|
+
#
|
65
|
+
def connection
|
66
|
+
active_record.connection
|
67
|
+
end
|
68
|
+
alias adapter connection
|
69
|
+
|
70
|
+
##
|
71
|
+
# Returns an Hash wit the database configuration.
|
72
|
+
#
|
73
|
+
# Caveat:the returned +Hash+ has a custom +.to_s+ method that formats
|
74
|
+
# the configuration as a +pgsql://+ URL.
|
75
|
+
#
|
76
|
+
# @return [Hash] the current database configuration
|
77
|
+
#
|
78
|
+
# @see {#config_file}
|
79
|
+
#
|
80
|
+
def configuration
|
81
|
+
@_config ||= YAML.load(config_file.read).tap do |conf|
|
82
|
+
def conf.to_s
|
83
|
+
'pgsql://%s:%s@%s/%s' % values_at(
|
84
|
+
:username, :password, :hostname, :database
|
85
|
+
)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
##
|
91
|
+
# @return [Pathname] the currently configured configuration file. Override
|
92
|
+
# using the +EACO_AR_CONFIG' envinronment variable.
|
93
|
+
#
|
94
|
+
def config_file
|
95
|
+
Pathname.new(ENV['EACO_AR_CONFIG'] || default_config_file)
|
96
|
+
end
|
97
|
+
|
98
|
+
##
|
99
|
+
# @return [String] +active_record.yml+ relative to this source file.
|
100
|
+
#
|
101
|
+
# @raise [Errno::ENOENT] if the configuration file is not found.
|
102
|
+
#
|
103
|
+
def default_config_file
|
104
|
+
Pathname.new('features/active_record.yml').realpath
|
105
|
+
|
106
|
+
rescue Errno::ENOENT => error
|
107
|
+
raise error.class.new, <<-EOF.squeeze(' ')
|
108
|
+
|
109
|
+
#{error.message}.
|
110
|
+
|
111
|
+
Please define your Active Record database configuration in the
|
112
|
+
default location, or specify your configuration file location by
|
113
|
+
passing the `EACO_AR_CONFIG' environment variable.
|
114
|
+
EOF
|
115
|
+
end
|
116
|
+
|
117
|
+
##
|
118
|
+
# Establish ActiveRecord connection using the given configuration hash
|
119
|
+
#
|
120
|
+
# @param config [Hash] the configuration to use, {#configuration} by default.
|
121
|
+
#
|
122
|
+
# @return [ActiveRecord::ConnectionAdapters::ConnectionPool]
|
123
|
+
#
|
124
|
+
# @raise [ActiveRecord::ActiveRecordError] if cannot connect
|
125
|
+
#
|
126
|
+
def connect!(config = self.configuration)
|
127
|
+
unless ENV['VERBOSE']
|
128
|
+
config = config.merge(min_messages: 'WARNING')
|
129
|
+
end
|
130
|
+
|
131
|
+
active_record.establish_connection(config)
|
132
|
+
end
|
133
|
+
|
134
|
+
##
|
135
|
+
# Loads the defined {ActiveRecord#schema}
|
136
|
+
#
|
137
|
+
# @return [nil]
|
138
|
+
#
|
139
|
+
def define_schema!
|
140
|
+
load 'eaco/cucumber/active_record/schema.rb'
|
141
|
+
end
|
142
|
+
|
143
|
+
##
|
144
|
+
# Drops and recreates the database specified in the {#configuration}.
|
145
|
+
#
|
146
|
+
# TODO untangle from postgres
|
147
|
+
#
|
148
|
+
# @return [void]
|
149
|
+
#
|
150
|
+
def recreate_database!
|
151
|
+
database = config.fetch(:database)
|
152
|
+
connect! config.merge(database: :postgres) # FIXME
|
153
|
+
|
154
|
+
connection.drop_database database
|
155
|
+
connection.create_database database
|
156
|
+
connect! config
|
157
|
+
|
158
|
+
logger.info "Connected to #{config}"
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
end
|
163
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Eaco
|
2
|
+
module Cucumber
|
3
|
+
module ActiveRecord
|
4
|
+
|
5
|
+
##
|
6
|
+
# A department holds many {Position}s.
|
7
|
+
#
|
8
|
+
# For the background story, see {Eaco::Cucumber::World}.
|
9
|
+
#
|
10
|
+
# @see Position
|
11
|
+
# @see Eaco::Actor
|
12
|
+
# @see Eaco::Cucumber::World
|
13
|
+
#
|
14
|
+
class Department < ::ActiveRecord::Base
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Eaco
|
2
|
+
module Cucumber
|
3
|
+
module ActiveRecord
|
4
|
+
|
5
|
+
##
|
6
|
+
# This is an example of a {Eaco::Resource} that can be protected by an
|
7
|
+
# {Eaco::ACL}. For the background story, see {Eaco::Cucumber::World}.
|
8
|
+
#
|
9
|
+
# @see User
|
10
|
+
# @see Eaco::Resource
|
11
|
+
# @see Eaco::Cucumber::World
|
12
|
+
#
|
13
|
+
class Document < ::ActiveRecord::Base
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Eaco
|
2
|
+
module Cucumber
|
3
|
+
module ActiveRecord
|
4
|
+
|
5
|
+
##
|
6
|
+
# A Position is occupied by an {User} in a {Department}.
|
7
|
+
#
|
8
|
+
# For the background story, see {Eaco::Cucumber::World}.
|
9
|
+
#
|
10
|
+
# @see User
|
11
|
+
# @see Department
|
12
|
+
# @see Eaco::Cucumber::World
|
13
|
+
#
|
14
|
+
class Position < ::ActiveRecord::Base
|
15
|
+
belongs_to :user
|
16
|
+
belongs_to :department
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Eaco
|
2
|
+
module Cucumber
|
3
|
+
module ActiveRecord
|
4
|
+
|
5
|
+
# @!method schema
|
6
|
+
#
|
7
|
+
# Defines the database schema for the {Eaco::Cucumber::World} scenario.
|
8
|
+
#
|
9
|
+
# @see Eaco::Cucumber::World
|
10
|
+
#
|
11
|
+
::ActiveRecord::Schema.define(version: '2015022301') do
|
12
|
+
create_table 'documents', force: true do |t|
|
13
|
+
t.string :name
|
14
|
+
t.text :contents
|
15
|
+
t.column :acl, :jsonb
|
16
|
+
end
|
17
|
+
|
18
|
+
create_table 'users', force: true do |t|
|
19
|
+
t.string :name
|
20
|
+
end
|
21
|
+
|
22
|
+
create_table 'departments', force: true do |t|
|
23
|
+
t.string :abbr
|
24
|
+
end
|
25
|
+
|
26
|
+
create_table 'positions', force: true do |t|
|
27
|
+
t.string :job_title
|
28
|
+
|
29
|
+
t.references :user
|
30
|
+
t.references :department
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Eaco
|
2
|
+
module Cucumber
|
3
|
+
module ActiveRecord
|
4
|
+
|
5
|
+
##
|
6
|
+
# This is an example of a {Eaco::Actor} that can be authorized against
|
7
|
+
# the ACLs in a resource, such as the example {Document}.
|
8
|
+
#
|
9
|
+
# For the background story, see {Eaco::Cucumber::World}.
|
10
|
+
#
|
11
|
+
# @see Document
|
12
|
+
# @see Eaco::Actor
|
13
|
+
# @see Eaco::Cucumber::World
|
14
|
+
#
|
15
|
+
class User < ::ActiveRecord::Base
|
16
|
+
autoload :Designators, 'lib/eaco/cucumber/designators.rb'
|
17
|
+
|
18
|
+
has_many :positions
|
19
|
+
has_many :departments, through: :positions
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|