eaco 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|