eaco 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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