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,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
|