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.
Files changed (76) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +21 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +18 -0
  5. data/.yardopts +1 -0
  6. data/Appraisals +22 -0
  7. data/Gemfile +4 -0
  8. data/Guardfile +34 -0
  9. data/LICENSE.txt +23 -0
  10. data/README.md +225 -0
  11. data/Rakefile +26 -0
  12. data/eaco.gemspec +27 -0
  13. data/features/active_record.example.yml +8 -0
  14. data/features/active_record.travis.yml +7 -0
  15. data/features/rails_integration.feature +10 -0
  16. data/features/step_definitions/database.rb +7 -0
  17. data/features/step_definitions/resource_authorization.rb +15 -0
  18. data/features/support/env.rb +9 -0
  19. data/gemfiles/rails_3.2.gemfile +9 -0
  20. data/gemfiles/rails_4.0.gemfile +8 -0
  21. data/gemfiles/rails_4.1.gemfile +8 -0
  22. data/gemfiles/rails_4.2.gemfile +8 -0
  23. data/lib/eaco.rb +93 -0
  24. data/lib/eaco/acl.rb +206 -0
  25. data/lib/eaco/actor.rb +86 -0
  26. data/lib/eaco/adapters.rb +14 -0
  27. data/lib/eaco/adapters/active_record.rb +70 -0
  28. data/lib/eaco/adapters/active_record/compatibility.rb +83 -0
  29. data/lib/eaco/adapters/active_record/compatibility/v32.rb +27 -0
  30. data/lib/eaco/adapters/active_record/compatibility/v40.rb +59 -0
  31. data/lib/eaco/adapters/active_record/compatibility/v41.rb +16 -0
  32. data/lib/eaco/adapters/active_record/compatibility/v42.rb +17 -0
  33. data/lib/eaco/adapters/active_record/postgres_jsonb.rb +36 -0
  34. data/lib/eaco/adapters/couchrest_model.rb +37 -0
  35. data/lib/eaco/adapters/couchrest_model/couchdb_lucene.rb +71 -0
  36. data/lib/eaco/controller.rb +158 -0
  37. data/lib/eaco/cucumber.rb +11 -0
  38. data/lib/eaco/cucumber/active_record.rb +163 -0
  39. data/lib/eaco/cucumber/active_record/department.rb +19 -0
  40. data/lib/eaco/cucumber/active_record/document.rb +18 -0
  41. data/lib/eaco/cucumber/active_record/position.rb +21 -0
  42. data/lib/eaco/cucumber/active_record/schema.rb +36 -0
  43. data/lib/eaco/cucumber/active_record/user.rb +24 -0
  44. data/lib/eaco/cucumber/world.rb +136 -0
  45. data/lib/eaco/designator.rb +264 -0
  46. data/lib/eaco/dsl.rb +40 -0
  47. data/lib/eaco/dsl/acl.rb +163 -0
  48. data/lib/eaco/dsl/actor.rb +139 -0
  49. data/lib/eaco/dsl/actor/designators.rb +110 -0
  50. data/lib/eaco/dsl/base.rb +52 -0
  51. data/lib/eaco/dsl/resource.rb +129 -0
  52. data/lib/eaco/dsl/resource/permissions.rb +131 -0
  53. data/lib/eaco/error.rb +36 -0
  54. data/lib/eaco/railtie.rb +46 -0
  55. data/lib/eaco/rake.rb +10 -0
  56. data/lib/eaco/rake/default_task.rb +164 -0
  57. data/lib/eaco/resource.rb +234 -0
  58. data/lib/eaco/version.rb +7 -0
  59. data/spec/eaco/acl_spec.rb +147 -0
  60. data/spec/eaco/actor_spec.rb +13 -0
  61. data/spec/eaco/adapters/active_record/postgres_jsonb_spec.rb +9 -0
  62. data/spec/eaco/adapters/active_record_spec.rb +13 -0
  63. data/spec/eaco/adapters/couchrest_model/couchdb_lucene_spec.rb +9 -0
  64. data/spec/eaco/adapters/couchrest_model_spec.rb +9 -0
  65. data/spec/eaco/controller_spec.rb +12 -0
  66. data/spec/eaco/designator_spec.rb +25 -0
  67. data/spec/eaco/dsl/acl_spec.rb +9 -0
  68. data/spec/eaco/dsl/actor/designators_spec.rb +7 -0
  69. data/spec/eaco/dsl/actor_spec.rb +15 -0
  70. data/spec/eaco/dsl/resource/permissions_spec.rb +7 -0
  71. data/spec/eaco/dsl/resource_spec.rb +17 -0
  72. data/spec/eaco/error_spec.rb +9 -0
  73. data/spec/eaco/resource_spec.rb +31 -0
  74. data/spec/eaco_spec.rb +49 -0
  75. data/spec/spec_helper.rb +71 -0
  76. 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,16 @@
1
+ module Eaco
2
+ module Adapters
3
+ module ActiveRecord
4
+ class Compatibility
5
+
6
+ ##
7
+ # Rails 4.1 JSONB support module.
8
+ #
9
+ # Magically, the 4.0 hacks work on 4.1. YAY!
10
+ #
11
+ V41 = V40
12
+
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,17 @@
1
+ module Eaco
2
+ module Adapters
3
+ module ActiveRecord
4
+ class Compatibility
5
+
6
+ ##
7
+ # Rails 4.2 JSONB support module.
8
+ #
9
+ # Magically, this module is empty. YAY!
10
+ #
11
+ module V42
12
+ end
13
+
14
+ end
15
+ end
16
+ end
17
+ 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