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,14 @@
|
|
1
|
+
module Eaco
|
2
|
+
|
3
|
+
# Persistance adapters for ACL objects and authorized collections extractor
|
4
|
+
# strategies.
|
5
|
+
#
|
6
|
+
# @see ActiveRecord
|
7
|
+
# @see CouchrestModel
|
8
|
+
#
|
9
|
+
module Adapters
|
10
|
+
autoload :ActiveRecord, 'eaco/adapters/active_record'
|
11
|
+
autoload :CouchrestModel, 'eaco/adapters/couchrest_model'
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module Eaco
|
2
|
+
module Adapters
|
3
|
+
|
4
|
+
##
|
5
|
+
# PostgreSQL 9.4 and up backing store for ACLs.
|
6
|
+
#
|
7
|
+
# @see ACL
|
8
|
+
# @see PostgresJSONb
|
9
|
+
#
|
10
|
+
module ActiveRecord
|
11
|
+
autoload :PostgresJSONb, 'eaco/adapters/active_record/postgres_jsonb'
|
12
|
+
autoload :Compatibility, 'eaco/adapters/active_record/compatibility'
|
13
|
+
|
14
|
+
##
|
15
|
+
# Currently defined collection extraction strategies.
|
16
|
+
#
|
17
|
+
# @return Hash
|
18
|
+
#
|
19
|
+
def self.strategies
|
20
|
+
{:pg_jsonb => PostgresJSONb}
|
21
|
+
end
|
22
|
+
|
23
|
+
##
|
24
|
+
# Checks whether the model's data structure fits the ACL persistance
|
25
|
+
# requirements.
|
26
|
+
#
|
27
|
+
# @param base [Class] your application's model
|
28
|
+
#
|
29
|
+
# @return void
|
30
|
+
#
|
31
|
+
def self.included(base)
|
32
|
+
Compatibility.new(base).check!
|
33
|
+
|
34
|
+
column = base.columns_hash.fetch('acl', nil)
|
35
|
+
|
36
|
+
unless column
|
37
|
+
raise Malformed, "Please define a jsonb column named `acl` on #{base}."
|
38
|
+
end
|
39
|
+
|
40
|
+
unless column.type == :json || column.type == :jsonb
|
41
|
+
raise Malformed, "The `acl` column on #{base} must be of the json type."
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
##
|
46
|
+
# @return [ACL] this Resource's ACL.
|
47
|
+
#
|
48
|
+
# @see ACL
|
49
|
+
#
|
50
|
+
def acl
|
51
|
+
acl = read_attribute(:acl)
|
52
|
+
self.class.acl.new(acl)
|
53
|
+
end
|
54
|
+
|
55
|
+
##
|
56
|
+
# Sets the Resource's ACL.
|
57
|
+
#
|
58
|
+
# @param acl [ACL] the new ACL to set.
|
59
|
+
#
|
60
|
+
# @return [ACL]
|
61
|
+
#
|
62
|
+
# @see ACL
|
63
|
+
#
|
64
|
+
def acl=(acl)
|
65
|
+
write_attribute :acl, acl.to_hash
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
module Eaco
|
2
|
+
module Adapters
|
3
|
+
module ActiveRecord
|
4
|
+
|
5
|
+
##
|
6
|
+
# Sets up JSONB support for the different AR versions
|
7
|
+
#
|
8
|
+
class Compatibility
|
9
|
+
autoload :V32, 'eaco/adapters/active_record/compatibility/v32.rb'
|
10
|
+
autoload :V40, 'eaco/adapters/active_record/compatibility/v40.rb'
|
11
|
+
autoload :V41, 'eaco/adapters/active_record/compatibility/v41.rb'
|
12
|
+
autoload :V42, 'eaco/adapters/active_record/compatibility/v42.rb'
|
13
|
+
|
14
|
+
##
|
15
|
+
# @param model [ActiveRecord::Base] the model to check
|
16
|
+
#
|
17
|
+
def initialize(model)
|
18
|
+
@model = model
|
19
|
+
end
|
20
|
+
|
21
|
+
##
|
22
|
+
# Checks whether the model is compatible. Looks up the
|
23
|
+
# {#support_module} and includes it.
|
24
|
+
#
|
25
|
+
# @see #support_module
|
26
|
+
def check!
|
27
|
+
layer = support_module
|
28
|
+
base.instance_eval { include layer }
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
##
|
34
|
+
# @return [ActiveRecord::Base] associated with the model
|
35
|
+
#
|
36
|
+
def base
|
37
|
+
@model.base_class.superclass
|
38
|
+
end
|
39
|
+
|
40
|
+
##
|
41
|
+
# @return [String] the +ActiveRecord+ major and minor version numbers
|
42
|
+
#
|
43
|
+
# Example: "42" for 4.2
|
44
|
+
#
|
45
|
+
def active_record_version
|
46
|
+
ver = base.parent::VERSION
|
47
|
+
[ver.const_get(:MAJOR), ver.const_get(:MINOR)].join
|
48
|
+
end
|
49
|
+
|
50
|
+
##
|
51
|
+
# Tries to look up the support module for the {#active_record_version}
|
52
|
+
# in the {Compatibility} namespace.
|
53
|
+
#
|
54
|
+
# @return [Module] the support module
|
55
|
+
#
|
56
|
+
# @raise [Eaco::Error] if not found.
|
57
|
+
#
|
58
|
+
# @see check!
|
59
|
+
#
|
60
|
+
def support_module
|
61
|
+
unless self.class.const_defined?(support_module_name)
|
62
|
+
raise Eaco::Error, <<-EOF
|
63
|
+
Unsupported Active Record version: #{active_record_version}
|
64
|
+
EOF
|
65
|
+
end
|
66
|
+
|
67
|
+
self.class.const_get support_module_name
|
68
|
+
end
|
69
|
+
|
70
|
+
##
|
71
|
+
# @return [String] "V" with {.active_record_version} appended.
|
72
|
+
#
|
73
|
+
# Example: "V32" for Rails 3.2.
|
74
|
+
#
|
75
|
+
def support_module_name
|
76
|
+
['V', active_record_version].join
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Eaco
|
2
|
+
module Adapters
|
3
|
+
module ActiveRecord
|
4
|
+
class Compatibility
|
5
|
+
|
6
|
+
##
|
7
|
+
# Rails 3.2 JSONB support module.
|
8
|
+
#
|
9
|
+
# Uses https://github.com/romanbsd/activerecord-postgres-json to do
|
10
|
+
# the dirty compatibility stuff. This module only uses +.serialize+
|
11
|
+
# to set the +JSON+ coder.
|
12
|
+
#
|
13
|
+
module V32
|
14
|
+
require 'activerecord-postgres-json'
|
15
|
+
|
16
|
+
##
|
17
|
+
# Sets the JSON coder on the acl column
|
18
|
+
#
|
19
|
+
def self.included(base)
|
20
|
+
base.serialize :acl, ::ActiveRecord::Coders::JSON
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module Eaco
|
2
|
+
module Adapters
|
3
|
+
module ActiveRecord
|
4
|
+
class Compatibility
|
5
|
+
|
6
|
+
##
|
7
|
+
# Rails v4.0.X compatibility layer for jsonb
|
8
|
+
#
|
9
|
+
module V40
|
10
|
+
##
|
11
|
+
#
|
12
|
+
# Sets up the OID Type Map, reloads it, hacks native database types,
|
13
|
+
# and makes jsonb mimick itself as a json - for the rest of the AR
|
14
|
+
# machinery to work intact.
|
15
|
+
#
|
16
|
+
def self.included(base)
|
17
|
+
adapter = base.connection
|
18
|
+
|
19
|
+
adapter.class::OID.register_type 'jsonb', adapter.class::OID::Json.new
|
20
|
+
adapter.send :reload_type_map
|
21
|
+
|
22
|
+
adapter.native_database_types.update(jsonb: {name: 'json'})
|
23
|
+
|
24
|
+
adapter.class.parent::PostgreSQLColumn.instance_eval do
|
25
|
+
include Column
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
##
|
30
|
+
# Patches to ActiveRecord::ConnectionAdapters::PostgreSQLColumn
|
31
|
+
#
|
32
|
+
module Column
|
33
|
+
##
|
34
|
+
# Makes +sql_type+ return +json+ for +jsonb+ columns
|
35
|
+
#
|
36
|
+
def sql_type
|
37
|
+
orig_type = super
|
38
|
+
orig_type == 'jsonb' ? 'json' : type
|
39
|
+
end
|
40
|
+
|
41
|
+
##
|
42
|
+
# Makes +simplified_type+ return +json+ for +jsonb+ columns
|
43
|
+
#
|
44
|
+
# @return [Symbol]
|
45
|
+
#
|
46
|
+
def simplified_type(field_type)
|
47
|
+
if field_type == 'jsonb'
|
48
|
+
:json
|
49
|
+
else
|
50
|
+
super
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Eaco
|
2
|
+
module Adapters
|
3
|
+
module ActiveRecord
|
4
|
+
|
5
|
+
##
|
6
|
+
# Authorized collection extractor on PostgreSQL >= 9.4 and a +jsonb+
|
7
|
+
# column named +acl+.
|
8
|
+
#
|
9
|
+
# TODO negative authorizations (using a separate column?)
|
10
|
+
#
|
11
|
+
# @see ACL
|
12
|
+
# @see Actor
|
13
|
+
# @see Resource
|
14
|
+
#
|
15
|
+
module PostgresJSONb
|
16
|
+
|
17
|
+
##
|
18
|
+
# Uses the json key existance operator +?|+ to check whether one of the
|
19
|
+
# +Actor+'s +Designator+ instances exist as keys in the +ACL+ objects.
|
20
|
+
#
|
21
|
+
# @param actor [Actor]
|
22
|
+
#
|
23
|
+
# @return [ActiveRecord::Relation] the authorized collection scope.
|
24
|
+
#
|
25
|
+
def accessible_by(actor)
|
26
|
+
return scoped if actor.is_admin?
|
27
|
+
|
28
|
+
designators = actor.designators.map {|d| quote_value(d) }
|
29
|
+
|
30
|
+
where("acl ?| array[#{designators.join(',')}]")
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Eaco
|
2
|
+
module Adapters
|
3
|
+
|
4
|
+
##
|
5
|
+
# CouchRest::Model backing store for ACLs, that naively uses +property+.
|
6
|
+
# As the ACL class is an +Hash+, it gets unserialized automagically by
|
7
|
+
# CouchRest guts.
|
8
|
+
#
|
9
|
+
# @see ACL
|
10
|
+
# @see CouchDBLucene
|
11
|
+
#
|
12
|
+
module CouchrestModel
|
13
|
+
autoload :CouchDBLucene, 'eaco/adapters/couchrest_model/couchdb_lucene'
|
14
|
+
|
15
|
+
##
|
16
|
+
# Returns currently available collection extraction strategies.
|
17
|
+
#
|
18
|
+
def self.strategies
|
19
|
+
{lucene: CouchDBLucene}
|
20
|
+
end
|
21
|
+
|
22
|
+
##
|
23
|
+
# Defines the +acl+ property on the given model
|
24
|
+
#
|
25
|
+
# @param base [CouchRest::Model] your model class.
|
26
|
+
#
|
27
|
+
# @return [void]
|
28
|
+
#
|
29
|
+
def self.included(base)
|
30
|
+
base.instance_eval do
|
31
|
+
property :acl, acl
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module Eaco
|
2
|
+
module Adapters
|
3
|
+
module CouchrestModel
|
4
|
+
|
5
|
+
##
|
6
|
+
# Authorized collection extractor on CouchDB using the CouchDB Lucene
|
7
|
+
# full-text indexer <https://github.com/ifad/couchdb-lucene>, a patched
|
8
|
+
# CouchRest <https://github.com/ifad/couchrest> to interact with the
|
9
|
+
# "_fti" couchdb lucene API endpoint, and a patched CouchRest::Model
|
10
|
+
# <https://github.com/ifad/couchrest_model> that provides a search()
|
11
|
+
# API to run lucene queries.
|
12
|
+
#
|
13
|
+
# It requires an indexing strategy similar to the following:
|
14
|
+
#
|
15
|
+
# {
|
16
|
+
# _id: "_design/lucene",
|
17
|
+
# language: "javascript",
|
18
|
+
# fulltext: {
|
19
|
+
# search: {
|
20
|
+
# defaults: { store: "no" },
|
21
|
+
# analyzer: "perfield:{acl:\"keyword\"}",
|
22
|
+
# index: function(doc) {
|
23
|
+
#
|
24
|
+
# var acl = doc.acl;
|
25
|
+
# if (!acl) {
|
26
|
+
# return null;
|
27
|
+
# }
|
28
|
+
#
|
29
|
+
# var ret = new Document();
|
30
|
+
#
|
31
|
+
# for (key in acl) {
|
32
|
+
# ret.add(key, {
|
33
|
+
# type: 'string',
|
34
|
+
# field: 'acl',
|
35
|
+
# index: 'not_analyzed'
|
36
|
+
# });
|
37
|
+
# }
|
38
|
+
#
|
39
|
+
# return ret;
|
40
|
+
# }
|
41
|
+
# }
|
42
|
+
# }
|
43
|
+
# }
|
44
|
+
#
|
45
|
+
# Made in Italy.
|
46
|
+
#
|
47
|
+
# @see ACL
|
48
|
+
# @see Actor
|
49
|
+
# @see Resource
|
50
|
+
#
|
51
|
+
module CouchDBLucene
|
52
|
+
|
53
|
+
##
|
54
|
+
# Uses a Lucene query to extract Resources accessible by the given Actor.
|
55
|
+
#
|
56
|
+
# @param actor [Actor]
|
57
|
+
#
|
58
|
+
# @return [CouchRest::Model::Search::View] the authorized collection scope.
|
59
|
+
#
|
60
|
+
def accessible_by(actor)
|
61
|
+
return search(nil) if actor.is_admin?
|
62
|
+
|
63
|
+
designators = actor.designators.map {|item| '"%s"' % item }
|
64
|
+
|
65
|
+
search "acl:(#{designators.join(' OR ')})"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|