authorizable 0.9.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 (94) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +33 -0
  3. data/.travis.yml +14 -0
  4. data/Gemfile +6 -0
  5. data/Gemfile.lock +178 -0
  6. data/LICENSE +22 -0
  7. data/README.md +80 -0
  8. data/Rakefile +1 -0
  9. data/authorizable.gemspec +40 -0
  10. data/config/locales/en.yml +6 -0
  11. data/lib/authorizable.rb +31 -0
  12. data/lib/authorizable/controller.rb +156 -0
  13. data/lib/authorizable/model.rb +162 -0
  14. data/lib/authorizable/permission_utilities.rb +89 -0
  15. data/lib/authorizable/permissions.rb +112 -0
  16. data/lib/authorizable/version.rb +5 -0
  17. data/spec/integration/controller_spec.rb +127 -0
  18. data/spec/integration/model_spec.rb +169 -0
  19. data/spec/rails_helper.rb +14 -0
  20. data/spec/spec_helper.rb +48 -0
  21. data/spec/support/definitions.rb +16 -0
  22. data/spec/support/factories.rb +17 -0
  23. data/spec/support/factory_girl.rb +7 -0
  24. data/spec/support/rails_app/Rakefile +6 -0
  25. data/spec/support/rails_app/app/assets/images/.keep +0 -0
  26. data/spec/support/rails_app/app/assets/javascripts/application.js +16 -0
  27. data/spec/support/rails_app/app/assets/javascripts/some_resources.js +2 -0
  28. data/spec/support/rails_app/app/assets/javascripts/users.js +2 -0
  29. data/spec/support/rails_app/app/assets/stylesheets/application.css +15 -0
  30. data/spec/support/rails_app/app/assets/stylesheets/scaffold.css +56 -0
  31. data/spec/support/rails_app/app/assets/stylesheets/some_resources.css +4 -0
  32. data/spec/support/rails_app/app/assets/stylesheets/users.css +4 -0
  33. data/spec/support/rails_app/app/controllers/application_controller.rb +14 -0
  34. data/spec/support/rails_app/app/controllers/events_controller.rb +58 -0
  35. data/spec/support/rails_app/app/controllers/users_controller.rb +58 -0
  36. data/spec/support/rails_app/app/helpers/application_helper.rb +2 -0
  37. data/spec/support/rails_app/app/helpers/events_helper.rb +2 -0
  38. data/spec/support/rails_app/app/helpers/users_helper.rb +2 -0
  39. data/spec/support/rails_app/app/mailers/.keep +0 -0
  40. data/spec/support/rails_app/app/models/collaboration.rb +16 -0
  41. data/spec/support/rails_app/app/models/concerns/.keep +0 -0
  42. data/spec/support/rails_app/app/models/discount.rb +3 -0
  43. data/spec/support/rails_app/app/models/event.rb +5 -0
  44. data/spec/support/rails_app/app/models/user.rb +9 -0
  45. data/spec/support/rails_app/app/views/events/_form.html.erb +17 -0
  46. data/spec/support/rails_app/app/views/events/edit.html.erb +6 -0
  47. data/spec/support/rails_app/app/views/events/index.html.erb +25 -0
  48. data/spec/support/rails_app/app/views/events/new.html.erb +5 -0
  49. data/spec/support/rails_app/app/views/events/show.html.erb +4 -0
  50. data/spec/support/rails_app/app/views/layouts/application.html.erb +14 -0
  51. data/spec/support/rails_app/app/views/users/_form.html.erb +17 -0
  52. data/spec/support/rails_app/app/views/users/edit.html.erb +6 -0
  53. data/spec/support/rails_app/app/views/users/index.html.erb +25 -0
  54. data/spec/support/rails_app/app/views/users/new.html.erb +5 -0
  55. data/spec/support/rails_app/app/views/users/show.html.erb +4 -0
  56. data/spec/support/rails_app/bin/bundle +3 -0
  57. data/spec/support/rails_app/bin/rails +8 -0
  58. data/spec/support/rails_app/bin/rake +4 -0
  59. data/spec/support/rails_app/config.ru +0 -0
  60. data/spec/support/rails_app/config/application.rb +29 -0
  61. data/spec/support/rails_app/config/boot.rb +3 -0
  62. data/spec/support/rails_app/config/database.yml +25 -0
  63. data/spec/support/rails_app/config/environment.rb +5 -0
  64. data/spec/support/rails_app/config/environments/development.rb +41 -0
  65. data/spec/support/rails_app/config/environments/production.rb +79 -0
  66. data/spec/support/rails_app/config/environments/test.rb +42 -0
  67. data/spec/support/rails_app/config/initializers/assets.rb +11 -0
  68. data/spec/support/rails_app/config/initializers/backtrace_silencers.rb +7 -0
  69. data/spec/support/rails_app/config/initializers/cookies_serializer.rb +3 -0
  70. data/spec/support/rails_app/config/initializers/filter_parameter_logging.rb +4 -0
  71. data/spec/support/rails_app/config/initializers/inflections.rb +16 -0
  72. data/spec/support/rails_app/config/initializers/mime_types.rb +4 -0
  73. data/spec/support/rails_app/config/initializers/session_store.rb +3 -0
  74. data/spec/support/rails_app/config/initializers/wrap_parameters.rb +14 -0
  75. data/spec/support/rails_app/config/locales/en.yml +23 -0
  76. data/spec/support/rails_app/config/routes.rb +60 -0
  77. data/spec/support/rails_app/config/secrets.yml +22 -0
  78. data/spec/support/rails_app/db/development.sqlite3 +0 -0
  79. data/spec/support/rails_app/db/migrate/20141231134904_create_users.rb +8 -0
  80. data/spec/support/rails_app/db/migrate/20150102221633_create_collaborations.rb +13 -0
  81. data/spec/support/rails_app/db/migrate/20150102225507_create_events.rb +9 -0
  82. data/spec/support/rails_app/db/migrate/20150104171110_create_discounts.rb +11 -0
  83. data/spec/support/rails_app/db/schema.rb +45 -0
  84. data/spec/support/rails_app/db/seeds.rb +7 -0
  85. data/spec/support/rails_app/db/test.sqlite3 +0 -0
  86. data/spec/support/rails_app/log/development.log +26296 -0
  87. data/spec/support/rails_app/public/404.html +67 -0
  88. data/spec/support/rails_app/public/422.html +67 -0
  89. data/spec/support/rails_app/public/500.html +66 -0
  90. data/spec/support/rails_app/public/favicon.ico +0 -0
  91. data/spec/support/rails_app/public/robots.txt +5 -0
  92. data/spec/unit/permission_utilities_spec.rb +157 -0
  93. data/spec/unit/permissions_spec.rb +65 -0
  94. metadata +352 -0
@@ -0,0 +1,169 @@
1
+ require 'rails_helper'
2
+
3
+ describe Authorizable::Model, type: :model do
4
+
5
+ before(:each) do
6
+ @user = create(:user)
7
+ @user.class.send(:include, Authorizable::Model)
8
+ end
9
+
10
+ describe "process_permission" do
11
+ before(:each) do
12
+ @event = create(:event, user: @user)
13
+ @collaborator = create(:user)
14
+
15
+ @event.collaborators << @collaborator
16
+ @event.save
17
+ end
18
+
19
+ it 'allows a permission' do
20
+ result = @user.can_view_collaborators?(@event)
21
+ expect(result).to eq true
22
+ end
23
+
24
+ it 'allows permission short hand' do
25
+ result = @user.can_edit?(@event)
26
+ expect(result).to eq true
27
+ end
28
+
29
+ it 'calls super if method does not match permission pattern' do
30
+ expect{
31
+ @user.do_something
32
+ }.to raise_error(NoMethodError)
33
+ end
34
+
35
+ it 'disallows a permission' do
36
+ result = @collaborator.can_view_collaborators?(@event)
37
+ expect(result).to eq false
38
+ end
39
+
40
+ it 'sets cache' do
41
+ expect(@user).to receive(:set_permission_cache).and_call_original
42
+ @user.can_view_collaborators?(@event)
43
+ cache = @user.instance_variable_get("@permission_cache")
44
+ expect(cache[Authorizable::Model::IS_OWNER]).to have_key(:can_view_collaborators?)
45
+ end
46
+
47
+ it 'returns from cache' do
48
+ # call once - store cache
49
+ @user.can_view_collaborators?(@event)
50
+ cache = @user.instance_variable_get("@permission_cache")
51
+ expect(cache[Authorizable::Model::IS_OWNER]).to have_key(:can_view_collaborators?)
52
+
53
+ # call again, return from cache
54
+ expect(@user).to_not receive(:set_permission_cache)
55
+ @user.can_view_collaborators?(@event)
56
+
57
+ end
58
+ end
59
+
60
+ describe "can?" do
61
+
62
+ it 'gets the default value' do
63
+ result = @user.send(:can?, Authorizable.definitions.keys.first)
64
+ expect(result).to eq true
65
+ end
66
+
67
+ it 'gets the value from a pre-defined set' do
68
+ key = Authorizable.definitions.keys.first
69
+ result = @user.send(:can?, key, Authorizable::Model::IS_OWNER, { key => false } )
70
+ expect(result).to eq false
71
+ end
72
+ end
73
+
74
+ describe "get_role_of" do
75
+ before(:each) do
76
+ @collaborator = create(:user)
77
+ @unrelated = create(:user)
78
+ end
79
+
80
+ context 'event' do
81
+ before(:each) do
82
+ @event = create(:event, user: @user)
83
+ end
84
+
85
+ it 'is owner' do
86
+ result = @user.send(:get_role_of, @event)
87
+ expect(result).to eq Authorizable::Model::IS_OWNER
88
+ end
89
+
90
+ it 'is collaborator' do
91
+ @event.collaborators << @collaborator
92
+ @event.save
93
+ result = @collaborator.send(:get_role_of, @event)
94
+ expect(result).to eq Authorizable::Model::IS_UNRELATED
95
+ end
96
+
97
+ it 'is unrelated' do
98
+ result = @unrelated.send(:get_role_of, @event)
99
+ expect(result).to eq Authorizable::Model::IS_UNRELATED
100
+ end
101
+ end
102
+ end
103
+
104
+ describe "has_role_with" do
105
+
106
+ it 'is the owner' do
107
+ @event = create(:event, user: @user)
108
+ result = @user.send(:has_role_with, @event)
109
+ expect(result).to eq Authorizable::Model::IS_OWNER
110
+ end
111
+
112
+ it 'is not the owner' do
113
+ user = create(:user)
114
+ @event = create(:event, user: @user)
115
+ result = user.send(:has_role_with, @event)
116
+ expect(result).to eq Authorizable::Model::IS_UNRELATED
117
+ end
118
+
119
+ it 'object does not respond to user_id' do
120
+ result = @user.send(:has_role_with, 3)
121
+ expect(result).to eq Authorizable::Model::IS_UNRELATED
122
+ end
123
+
124
+ end
125
+
126
+ context 'cache' do
127
+
128
+ before(:each) do
129
+ @user = create(:user)
130
+ end
131
+
132
+ describe "value_from_permission_cache" do
133
+ before(:each) do
134
+ @user.instance_variable_set(
135
+ '@permission_cache',
136
+ {
137
+ 1 => { "role" => true },
138
+ "roleless" => true
139
+ }
140
+ )
141
+ end
142
+
143
+ it 'gets a value for a non-role-based permission' do
144
+ expect(@user.send(:value_from_permission_cache, "roleless")).to eq true
145
+ end
146
+
147
+ it 'gets a value for a role-based permission' do
148
+ expect(@user.send(:value_from_permission_cache, "role", 1)).to eq true
149
+ end
150
+ end
151
+
152
+ describe "set_permission_cache" do
153
+ it 'sets a role-based permission' do
154
+ @user.send(:set_permission_cache, name: "permission_name", role: 1, value: true)
155
+ cache = @user.instance_variable_get('@permission_cache')
156
+
157
+ expect(cache[1]).to be_present
158
+ expect(cache[1]["permission_name"]).to eq true
159
+ end
160
+
161
+ it 'sets a non-role-based permission' do
162
+ @user.send(:set_permission_cache, name: "permission_name", value: true)
163
+ cache = @user.instance_variable_get('@permission_cache')
164
+
165
+ expect(cache["permission_name"]).to eq true
166
+ end
167
+ end
168
+ end
169
+ end
@@ -0,0 +1,14 @@
1
+ require 'spec_helper'
2
+ require 'factory_girl_rails'
3
+ require 'rspec/rails'
4
+
5
+ ActiveRecord::Migration.maintain_test_schema!
6
+
7
+ require 'support/rails_app/config/environment'
8
+
9
+ # set up db
10
+ # be sure to update the schema if required by doing
11
+ # - cd spec/rails_app
12
+ # - rake db:migrate
13
+ ActiveRecord::Schema.verbose = false
14
+ load "support/rails_app/db/schema.rb" # use db agnostic schema by default
@@ -0,0 +1,48 @@
1
+ require "rubygems"
2
+ require "bundler/setup"
3
+
4
+ require "pry-byebug" # binding.pry to debug!
5
+
6
+ # Coverage
7
+ require "codeclimate-test-reporter"
8
+ ENV['CODECLIMATE_REPO_TOKEN'] = "68f28e1d037ca7b58d24f943b59d82cd649e1f9b9f79437d4a5d864140dd4eb0"
9
+ CodeClimate::TestReporter.start
10
+
11
+ require 'rspec/autorun'
12
+ require 'factory_girl'
13
+
14
+ # This Gem
15
+ require "authorizable"
16
+
17
+ Dir[File.dirname(__FILE__) + '/support/**/*.rb'].each {|file|
18
+ # skip the dummy app
19
+ next if file.include?('support/rails_app')
20
+ require file
21
+ }
22
+
23
+ # This file was generated by the `rspec --init` command. Conventionally, all
24
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
25
+ # Require this file using `require "spec_helper"` to ensure that it is only
26
+ # loaded once.
27
+ #
28
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
29
+ RSpec.configure do |config|
30
+ config.treat_symbols_as_metadata_keys_with_true_values = true
31
+ config.run_all_when_everything_filtered = true
32
+ config.filter_run :focus
33
+
34
+ # Run specs in random order to surface order dependencies. If you find an
35
+ # order dependency and want to debug it, you can fix the order by providing
36
+ # the seed, which is printed after each run.
37
+ # --seed 1234
38
+ config.order = 'random'
39
+
40
+ config.before(:each) do |x|
41
+ $example_definitions.call
42
+ end
43
+
44
+ config.after(:each) do |x|
45
+ # reset class variable
46
+ Authorizable::Permissions.definitions = {}
47
+ end
48
+ end
@@ -0,0 +1,16 @@
1
+ $example_definitions = ->{
2
+ Authorizable::Permissions.set(
3
+ edit_organization: [Authorizable::OBJECT, [true, false]],
4
+ delete_organization: [Authorizable::OBJECT, [true, false], nil, ->(e, user){ e.user == user }, ->(e, user){ e.owner == user }],
5
+ create_organization: [Authorizable::ACCESS, true, nil, nil],
6
+
7
+ view_collaborators: [Authorizable::ACCESS, [true, false]]
8
+ )
9
+
10
+
11
+ Authorizable::Permissions.class_eval do
12
+ can :edit_event
13
+ can :delete_event
14
+ can :not_do_this, false
15
+ end
16
+ }
@@ -0,0 +1,17 @@
1
+ FactoryGirl.define do
2
+
3
+ factory :user do
4
+
5
+ end
6
+
7
+ factory :event do
8
+ name "Test Event"
9
+ association :user, factory: :user
10
+ end
11
+
12
+ factory :discount do
13
+ name "Some Discount"
14
+ event
15
+ end
16
+
17
+ end
@@ -0,0 +1,7 @@
1
+ RSpec.configure do |config|
2
+ config.include FactoryGirl::Syntax::Methods
3
+
4
+ config.before(:suite) do
5
+ FactoryGirl.lint
6
+ end
7
+ end
@@ -0,0 +1,6 @@
1
+ # Add your own tasks in files placed in lib/tasks ending in .rake,
2
+ # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
3
+
4
+ require File.expand_path('../config/application', __FILE__)
5
+
6
+ Rails.application.load_tasks
@@ -0,0 +1,16 @@
1
+ // This is a manifest file that'll be compiled into application.js, which will include all the files
2
+ // listed below.
3
+ //
4
+ // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
5
+ // or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
6
+ //
7
+ // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8
+ // compiled file.
9
+ //
10
+ // Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details
11
+ // about supported directives.
12
+ //
13
+ //= require jquery
14
+ //= require jquery_ujs
15
+ //= require turbolinks
16
+ //= require_tree .
@@ -0,0 +1,2 @@
1
+ // Place all the behaviors and hooks related to the matching controller here.
2
+ // All this logic will automatically be available in application.js.
@@ -0,0 +1,2 @@
1
+ // Place all the behaviors and hooks related to the matching controller here.
2
+ // All this logic will automatically be available in application.js.
@@ -0,0 +1,15 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9
+ * compiled file so the styles you add here take precedence over styles defined in any styles
10
+ * defined in the other CSS/SCSS files in this directory. It is generally better to create a new
11
+ * file per style scope.
12
+ *
13
+ *= require_tree .
14
+ *= require_self
15
+ */
@@ -0,0 +1,56 @@
1
+ body { background-color: #fff; color: #333; }
2
+
3
+ body, p, ol, ul, td {
4
+ font-family: verdana, arial, helvetica, sans-serif;
5
+ font-size: 13px;
6
+ line-height: 18px;
7
+ }
8
+
9
+ pre {
10
+ background-color: #eee;
11
+ padding: 10px;
12
+ font-size: 11px;
13
+ }
14
+
15
+ a { color: #000; }
16
+ a:visited { color: #666; }
17
+ a:hover { color: #fff; background-color:#000; }
18
+
19
+ div.field, div.actions {
20
+ margin-bottom: 10px;
21
+ }
22
+
23
+ #notice {
24
+ color: green;
25
+ }
26
+
27
+ .field_with_errors {
28
+ padding: 2px;
29
+ background-color: red;
30
+ display: table;
31
+ }
32
+
33
+ #error_explanation {
34
+ width: 450px;
35
+ border: 2px solid red;
36
+ padding: 7px;
37
+ padding-bottom: 0;
38
+ margin-bottom: 20px;
39
+ background-color: #f0f0f0;
40
+ }
41
+
42
+ #error_explanation h2 {
43
+ text-align: left;
44
+ font-weight: bold;
45
+ padding: 5px 5px 5px 15px;
46
+ font-size: 12px;
47
+ margin: -7px;
48
+ margin-bottom: 0px;
49
+ background-color: #c00;
50
+ color: #fff;
51
+ }
52
+
53
+ #error_explanation ul li {
54
+ font-size: 12px;
55
+ list-style: square;
56
+ }
@@ -0,0 +1,4 @@
1
+ /*
2
+ Place all the styles related to the matching controller here.
3
+ They will automatically be included in application.css.
4
+ */
@@ -0,0 +1,4 @@
1
+ /*
2
+ Place all the styles related to the matching controller here.
3
+ They will automatically be included in application.css.
4
+ */
@@ -0,0 +1,14 @@
1
+ class ApplicationController < ActionController::Base
2
+ # Prevent CSRF attacks by raising an exception.
3
+ # For APIs, you may want to use :null_session instead.
4
+ protect_from_forgery with: :exception
5
+
6
+ # some fake helpers here
7
+ def current_user=(user = User.last)
8
+ @current_user = user
9
+ end
10
+
11
+ def current_user
12
+ @current_user
13
+ end
14
+ end
@@ -0,0 +1,58 @@
1
+ class EventsController < ApplicationController
2
+ before_action :set_event, only: [:show, :edit, :update, :destroy]
3
+
4
+ # GET /events
5
+ def index
6
+ @events = Event.all
7
+ end
8
+
9
+ # GET /events/1
10
+ def show
11
+ end
12
+
13
+ # GET /events/new
14
+ def new
15
+ @event = Event.new
16
+ end
17
+
18
+ # GET /events/1/edit
19
+ def edit
20
+ end
21
+
22
+ # POST /events
23
+ def create
24
+ @event = Event.new(event_params)
25
+
26
+ if @event.save
27
+ redirect_to @event, notice: 'Event was successfully created.'
28
+ else
29
+ render :new
30
+ end
31
+ end
32
+
33
+ # PATCH/PUT /events/1
34
+ def update
35
+ if @event.update(event_params)
36
+ redirect_to @event, notice: 'Event was successfully updated.'
37
+ else
38
+ render :edit
39
+ end
40
+ end
41
+
42
+ # DELETE /events/1
43
+ def destroy
44
+ @event.destroy
45
+ redirect_to events_url, notice: 'Event was successfully destroyed.'
46
+ end
47
+
48
+ private
49
+ # Use callbacks to share common setup or constraints between actions.
50
+ def set_event
51
+ @event = Event.find(params[:id])
52
+ end
53
+
54
+ # Only allow a trusted parameter "white list" through.
55
+ def event_params
56
+ params[:event]
57
+ end
58
+ end