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,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,11 @@
1
+ module Eaco
2
+
3
+ ##
4
+ # Namespace that holds all cucumber-related helpers.
5
+ #
6
+ module Cucumber
7
+ autoload :ActiveRecord, 'eaco/cucumber/active_record.rb'
8
+ autoload :World, 'eaco/cucumber/world.rb'
9
+ end
10
+
11
+ 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