aaf-gumboot 1.0.0.pre.alpha.2

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 (90) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.rspec +3 -0
  4. data/.rubocop.yml +15 -0
  5. data/Gemfile +4 -0
  6. data/Guardfile +18 -0
  7. data/LICENSE +202 -0
  8. data/README.md +1069 -0
  9. data/Rakefile +8 -0
  10. data/aaf-gumboot.gemspec +42 -0
  11. data/lib/aaf-gumboot.rb +1 -0
  12. data/lib/gumboot.rb +5 -0
  13. data/lib/gumboot/shared_examples/anonymous_controller.rb +17 -0
  14. data/lib/gumboot/shared_examples/api_constraints.rb +29 -0
  15. data/lib/gumboot/shared_examples/api_controller.rb +206 -0
  16. data/lib/gumboot/shared_examples/api_subjects.rb +44 -0
  17. data/lib/gumboot/shared_examples/application_controller.rb +223 -0
  18. data/lib/gumboot/shared_examples/database_schema.rb +45 -0
  19. data/lib/gumboot/shared_examples/foreign_keys.rb +65 -0
  20. data/lib/gumboot/shared_examples/permissions.rb +45 -0
  21. data/lib/gumboot/shared_examples/roles.rb +15 -0
  22. data/lib/gumboot/shared_examples/subjects.rb +29 -0
  23. data/lib/gumboot/strap.rb +121 -0
  24. data/lib/gumboot/version.rb +3 -0
  25. data/spec/dummy/README.rdoc +28 -0
  26. data/spec/dummy/Rakefile +3 -0
  27. data/spec/dummy/app/assets/images/.keep +0 -0
  28. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  29. data/spec/dummy/app/assets/stylesheets/application.css +15 -0
  30. data/spec/dummy/app/controllers/api/api_controller.rb +78 -0
  31. data/spec/dummy/app/controllers/application_controller.rb +64 -0
  32. data/spec/dummy/app/controllers/concerns/.keep +0 -0
  33. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  34. data/spec/dummy/app/mailers/.keep +0 -0
  35. data/spec/dummy/app/models/.keep +0 -0
  36. data/spec/dummy/app/models/api_subject.rb +23 -0
  37. data/spec/dummy/app/models/api_subject_role.rb +6 -0
  38. data/spec/dummy/app/models/concerns/.keep +0 -0
  39. data/spec/dummy/app/models/permission.rb +7 -0
  40. data/spec/dummy/app/models/role.rb +11 -0
  41. data/spec/dummy/app/models/subject.rb +20 -0
  42. data/spec/dummy/app/models/subject_role.rb +6 -0
  43. data/spec/dummy/app/views/dynamic_errors/forbidden.html.erb +0 -0
  44. data/spec/dummy/app/views/dynamic_errors/unauthorized.html.erb +0 -0
  45. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  46. data/spec/dummy/bin/bundle +3 -0
  47. data/spec/dummy/bin/rails +4 -0
  48. data/spec/dummy/bin/rake +4 -0
  49. data/spec/dummy/config.ru +4 -0
  50. data/spec/dummy/config/application.rb +18 -0
  51. data/spec/dummy/config/boot.rb +5 -0
  52. data/spec/dummy/config/database.yml +5 -0
  53. data/spec/dummy/config/environment.rb +5 -0
  54. data/spec/dummy/config/environments/development.rb +32 -0
  55. data/spec/dummy/config/environments/production.rb +37 -0
  56. data/spec/dummy/config/environments/test.rb +33 -0
  57. data/spec/dummy/config/initializers/assets.rb +4 -0
  58. data/spec/dummy/config/initializers/backtrace_silencers.rb +0 -0
  59. data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
  60. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  61. data/spec/dummy/config/initializers/inflections.rb +15 -0
  62. data/spec/dummy/config/initializers/mime_types.rb +4 -0
  63. data/spec/dummy/config/initializers/session_store.rb +3 -0
  64. data/spec/dummy/config/initializers/wrap_parameters.rb +9 -0
  65. data/spec/dummy/config/locales/en.yml +23 -0
  66. data/spec/dummy/config/routes.rb +2 -0
  67. data/spec/dummy/config/secrets.yml +22 -0
  68. data/spec/dummy/db/schema.rb +51 -0
  69. data/spec/dummy/db/test.sqlite3 +0 -0
  70. data/spec/dummy/lib/api_constraints.rb +16 -0
  71. data/spec/dummy/lib/assets/.keep +0 -0
  72. data/spec/dummy/public/404.html +67 -0
  73. data/spec/dummy/public/422.html +67 -0
  74. data/spec/dummy/public/500.html +66 -0
  75. data/spec/dummy/public/favicon.ico +0 -0
  76. data/spec/factories/api_subjects.rb +20 -0
  77. data/spec/factories/permissions.rb +6 -0
  78. data/spec/factories/roles.rb +5 -0
  79. data/spec/factories/subjects.rb +24 -0
  80. data/spec/gumboot/api_constraints_spec.rb +18 -0
  81. data/spec/gumboot/api_controller_spec.rb +7 -0
  82. data/spec/gumboot/api_subjects_spec.rb +7 -0
  83. data/spec/gumboot/application_controller_spec.rb +7 -0
  84. data/spec/gumboot/foreign_keys_spec.rb +7 -0
  85. data/spec/gumboot/permissions_spec.rb +7 -0
  86. data/spec/gumboot/roles_spec.rb +7 -0
  87. data/spec/gumboot/subjects_spec.rb +7 -0
  88. data/spec/lib/gumboot/strap_spec.rb +330 -0
  89. data/spec/spec_helper.rb +45 -0
  90. metadata +387 -0
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+ require 'rubocop/rake_task'
4
+
5
+ RSpec::Core::RakeTask.new(:spec)
6
+ RuboCop::RakeTask.new
7
+
8
+ task default: [:spec, :rubocop]
@@ -0,0 +1,42 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'gumboot/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'aaf-gumboot'
8
+ spec.version = Gumboot::VERSION
9
+ spec.authors = ['Bradley Beddoes']
10
+ spec.email = ['bradleybeddoes@aaf.edu.au']
11
+ spec.summary = 'Kick off subject and API structure for AAF applications'
12
+ spec.description = 'Provides a set of shared specs and base generators to' \
13
+ ' ensure that all AAF ruby applications follow the' \
14
+ ' same basic structure and implementation for' \
15
+ ' subjects, RESTful APIs and access control.'
16
+ spec.homepage = 'http://www.aaf.edu.au'
17
+ spec.license = 'Apache-2.0'
18
+
19
+ spec.files = `git ls-files -z`.split("\x0")
20
+
21
+ spec.add_dependency 'accession', '~> 1.0'
22
+
23
+ spec.add_development_dependency 'bundler', '~> 1.7'
24
+ spec.add_development_dependency 'rake', '~> 10.0'
25
+ spec.add_development_dependency 'mysql2', '~> 0.3.20'
26
+ spec.add_development_dependency 'sqlite3'
27
+ spec.add_development_dependency 'valhammer'
28
+ spec.add_development_dependency 'factory_girl'
29
+ spec.add_development_dependency 'faker'
30
+ spec.add_development_dependency 'rspec-rails'
31
+ spec.add_development_dependency 'rubocop'
32
+ spec.add_development_dependency 'simplecov'
33
+ spec.add_development_dependency 'codeclimate-test-reporter'
34
+ spec.add_development_dependency 'rails-controller-testing'
35
+
36
+ spec.add_development_dependency 'guard'
37
+ spec.add_development_dependency 'guard-rspec'
38
+ spec.add_development_dependency 'guard-rubocop'
39
+ spec.add_development_dependency 'guard-bundler'
40
+
41
+ spec.add_development_dependency 'rails', '~> 4.2.6'
42
+ end
@@ -0,0 +1 @@
1
+ require 'gumboot'
data/lib/gumboot.rb ADDED
@@ -0,0 +1,5 @@
1
+ module Gumboot
2
+ # Your code goes here...
3
+ end
4
+
5
+ require 'gumboot/version'
@@ -0,0 +1,17 @@
1
+ RSpec.shared_examples 'Anon controller' do
2
+ controller(described_class) do
3
+ def an_action
4
+ check_access!('required:permission')
5
+ head :ok
6
+ end
7
+
8
+ def bad_action
9
+ head :ok
10
+ end
11
+
12
+ def public
13
+ public_action
14
+ head :ok
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,29 @@
1
+ RSpec.shared_examples 'API constraints' do
2
+ context 'AAF shared implementation' do
3
+ context '#matches?' do
4
+ context 'with default: false' do
5
+ subject { described_class.new(version: '1', default: false) }
6
+
7
+ it 'is true for a valid request' do
8
+ expect(subject.matches?(matching_request)).to be_truthy
9
+ end
10
+
11
+ it 'is false for a non-matching request' do
12
+ expect(subject.matches?(non_matching_request)).to be_falsey
13
+ end
14
+ end
15
+
16
+ context 'with default: true' do
17
+ subject { described_class.new(version: '1', default: true) }
18
+
19
+ it 'is true for a valid request' do
20
+ expect(subject.matches?(matching_request)).to be_truthy
21
+ end
22
+
23
+ it 'is true for a non-matching request' do
24
+ expect(subject.matches?(non_matching_request)).to be_truthy
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,206 @@
1
+ require 'gumboot/shared_examples/anonymous_controller'
2
+
3
+ RSpec.shared_examples 'API base controller' do
4
+ context 'AAF shared implementation' do
5
+ include_examples 'Anon controller'
6
+
7
+ before do
8
+ @routes.draw do
9
+ get '/anonymous/an_action' => 'api/api#an_action'
10
+ get '/anonymous/bad_action' => 'api/api#bad_action'
11
+ get '/anonymous/public' => 'api/api#public'
12
+ end
13
+ end
14
+
15
+ it { is_expected.to respond_to(:subject) }
16
+
17
+ context '#ensure_authenticated as before_action' do
18
+ subject { response }
19
+ let(:json) { JSON.parse(subject.body) }
20
+
21
+ context 'no x509 header set by nginx' do
22
+ before { get :an_action }
23
+
24
+ it { is_expected.to have_http_status(:unauthorized) }
25
+
26
+ context 'json within response' do
27
+ it 'has a message' do
28
+ expect(json['message']).to eq('SSL client failure.')
29
+ end
30
+ it 'has an error' do
31
+ expect(json['error']).to eq('Subject DN')
32
+ end
33
+ end
34
+ end
35
+
36
+ context 'x509 header set to "(null)"' do
37
+ before do
38
+ request.env['HTTP_X509_DN'] = '(null)'
39
+ get :an_action
40
+ end
41
+
42
+ it { is_expected.to have_http_status(:unauthorized) }
43
+ context 'json within response' do
44
+ it 'has a message' do
45
+ expect(json['message']).to eq('SSL client failure.')
46
+ end
47
+ it 'has an error' do
48
+ expect(json['error']).to eq('Subject DN')
49
+ end
50
+ end
51
+ end
52
+
53
+ context 'invalid x509 header set by nginx' do
54
+ before do
55
+ request.env['HTTP_X509_DN'] = "Z=#{Faker::Lorem.word}"
56
+ get :an_action
57
+ end
58
+
59
+ it { is_expected.to have_http_status(:unauthorized) }
60
+ context 'json within response' do
61
+ it 'has a message' do
62
+ expect(json['message']).to eq('SSL client failure.')
63
+ end
64
+ it 'has an error' do
65
+ expect(json['error']).to eq('Subject DN invalid')
66
+ end
67
+ end
68
+ end
69
+
70
+ context 'without a CN component to DN' do
71
+ before do
72
+ request.env['HTTP_X509_DN'] = "O=#{Faker::Lorem.word}"
73
+ get :an_action
74
+ end
75
+
76
+ it { is_expected.to have_http_status(:unauthorized) }
77
+ context 'json within response' do
78
+ it 'has a message' do
79
+ expect(json['message']).to eq('SSL client failure.')
80
+ end
81
+ it 'has an error' do
82
+ expect(json['error']).to eq('Subject CN invalid')
83
+ end
84
+ end
85
+ end
86
+
87
+ context 'with a CN that does not represent an APISubject' do
88
+ before do
89
+ request.env['HTTP_X509_DN'] = "CN=#{Faker::Lorem.word}/" \
90
+ "O=#{Faker::Lorem.word}"
91
+ get :an_action
92
+ end
93
+
94
+ it { is_expected.to have_http_status(:unauthorized) }
95
+ context 'json within response' do
96
+ it 'has a message' do
97
+ expect(json['message']).to eq('SSL client failure.')
98
+ end
99
+ it 'has an error' do
100
+ expect(json['error']).to eq('Subject invalid')
101
+ end
102
+ end
103
+ end
104
+
105
+ context 'with an APISubject that is not functioning' do
106
+ let(:api_subject) { create :api_subject, enabled: false }
107
+
108
+ before do
109
+ request.env['HTTP_X509_DN'] = "CN=#{api_subject.x509_cn}/" \
110
+ "O=#{Faker::Lorem.word}"
111
+ get :an_action
112
+ end
113
+
114
+ it { is_expected.to have_http_status(:unauthorized) }
115
+ context 'json within response' do
116
+ it 'has a message' do
117
+ expect(json['message']).to eq('SSL client failure.')
118
+ end
119
+ it 'has an error' do
120
+ expect(json['error']).to eq('Subject not functional')
121
+ end
122
+ end
123
+ end
124
+ end
125
+
126
+ context '#ensure_access_checked as after_action' do
127
+ subject(:api_subject) { create :api_subject }
128
+ let(:json) { JSON.parse(response.body) }
129
+
130
+ before do
131
+ request.env['HTTP_X509_DN'] = "CN=#{api_subject.x509_cn}/DC=example"
132
+ end
133
+
134
+ RSpec.shared_examples 'APIController base state' do
135
+ it 'fails request to incorrectly implemented action' do
136
+ msg = 'No access control performed by API::APIController#bad_action'
137
+ expect { get :bad_action }.to raise_error(msg)
138
+ end
139
+
140
+ it 'completes request to a public action' do
141
+ get :public
142
+ expect(response).to have_http_status(:ok)
143
+ end
144
+ end
145
+
146
+ context 'subject without permissions' do
147
+ include_examples 'APIController base state'
148
+
149
+ it 'has no permissions' do
150
+ expect(api_subject.permissions).to eq([])
151
+ end
152
+
153
+ context 'the request does not complete' do
154
+ before { get :an_action }
155
+ it 'should respond with status code :forbidden (403)' do
156
+ expect(response).to have_http_status(:forbidden)
157
+ end
158
+ it 'recieves a json message' do
159
+ expect(json['message'])
160
+ .to eq('The request was understood but explicitly denied.')
161
+ end
162
+ end
163
+ end
164
+
165
+ context 'subject with invalid permissions' do
166
+ subject(:api_subject) do
167
+ create :api_subject, :authorized, permission: 'invalid:permission'
168
+ end
169
+
170
+ include_examples 'APIController base state'
171
+
172
+ it 'has an invalid permission' do
173
+ expect(api_subject.permissions).to eq(['invalid:permission'])
174
+ end
175
+
176
+ context 'the request does not complete' do
177
+ before { get :an_action }
178
+ it 'should respond with status code :forbidden (403)' do
179
+ expect(response).to have_http_status(:forbidden)
180
+ end
181
+ it 'recieves a json message' do
182
+ expect(json['message'])
183
+ .to eq('The request was understood but explicitly denied.')
184
+ end
185
+ end
186
+ end
187
+
188
+ context 'subject with valid permission' do
189
+ subject(:api_subject) do
190
+ create :api_subject, :authorized, permission: 'required:permission'
191
+ end
192
+
193
+ include_examples 'APIController base state'
194
+
195
+ it 'has a valid permission' do
196
+ expect(api_subject.permissions).to eq(['required:permission'])
197
+ end
198
+
199
+ it 'completes request after permissions checked' do
200
+ get :an_action
201
+ expect(response).to have_http_status(:ok)
202
+ end
203
+ end
204
+ end
205
+ end
206
+ end
@@ -0,0 +1,44 @@
1
+ RSpec.shared_examples 'API Subjects' do
2
+ context 'AAF shared implementation' do
3
+ subject { build :api_subject }
4
+
5
+ it { is_expected.to be_valid }
6
+ it { is_expected.to be_an(Accession::Principal) }
7
+ it { is_expected.to respond_to(:roles) }
8
+ it { is_expected.to respond_to(:permissions) }
9
+ it { is_expected.to respond_to(:permits?) }
10
+ it { is_expected.to respond_to(:functioning?) }
11
+
12
+ it 'is invalid without an x509_cn' do
13
+ subject.x509_cn = nil
14
+ expect(subject).not_to be_valid
15
+ end
16
+ it 'is invalid if an x509 value is not in the correct format' do
17
+ subject.x509_cn += '%^%&*'
18
+ expect(subject).not_to be_valid
19
+ end
20
+ it 'is valid if an x509 value is in the correct format' do
21
+ expect(subject).to be_valid
22
+ end
23
+ it 'is invalid if an x509 value is not unique' do
24
+ create(:api_subject, x509_cn: subject.x509_cn)
25
+ expect(subject).not_to be_valid
26
+ end
27
+ it 'is invalid without a description' do
28
+ subject.description = nil
29
+ expect(subject).not_to be_valid
30
+ end
31
+ it 'is invalid without a contact name' do
32
+ subject.contact_name = nil
33
+ expect(subject).not_to be_valid
34
+ end
35
+ it 'is invalid without a contact mail address' do
36
+ subject.contact_mail = nil
37
+ expect(subject).not_to be_valid
38
+ end
39
+ it 'is invalid without an enabled state' do
40
+ subject.enabled = nil
41
+ expect(subject).not_to be_valid
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,223 @@
1
+ require 'gumboot/shared_examples/anonymous_controller'
2
+
3
+ RSpec.shared_examples 'Application controller' do
4
+ context 'AAF shared implementation' do
5
+ include_examples 'Anon controller'
6
+
7
+ before do
8
+ @routes.draw do
9
+ get '/anonymous/an_action' => 'anonymous#an_action'
10
+ get '/anonymous/bad_action' => 'anonymous#bad_action'
11
+ get '/anonymous/public' => 'anonymous#public'
12
+ end
13
+ end
14
+
15
+ context '#subject' do
16
+ context 'No subject ID is set in session' do
17
+ before do
18
+ session[:subject_id] = nil
19
+ end
20
+
21
+ it 'returns nil' do
22
+ expect(subject.subject).to be_nil
23
+ end
24
+ end
25
+
26
+ context 'session has subject_id that does not represent a Subject' do
27
+ before do
28
+ session[:subject_id] = -1
29
+ end
30
+
31
+ it 'returns nil' do
32
+ expect(subject.subject).to be_nil
33
+ end
34
+ end
35
+
36
+ context 'Subject that is not functioning' do
37
+ let(:current_subject) { create :subject, enabled: false }
38
+
39
+ before do
40
+ session[:subject_id] = current_subject.id
41
+ end
42
+
43
+ it 'returns nil' do
44
+ expect(subject.subject).to be_nil
45
+ end
46
+ end
47
+
48
+ context 'Subject is valid' do
49
+ let(:current_subject) { create :subject }
50
+
51
+ before do
52
+ session[:subject_id] = current_subject.id
53
+ end
54
+
55
+ it 'returns subject' do
56
+ expect(subject.subject).to eq(current_subject)
57
+ end
58
+ end
59
+ end
60
+
61
+ context '#ensure_authenticated as before_action' do
62
+ subject { response }
63
+
64
+ context 'No subject ID is set in session' do
65
+ before do
66
+ session[:subject_id] = nil
67
+ get :an_action
68
+ end
69
+
70
+ it { is_expected.to have_http_status(:redirect) }
71
+ it { is_expected.to redirect_to('/auth/login') }
72
+ end
73
+
74
+ context 'session has subject_id that does not represent a Subject' do
75
+ before do
76
+ session[:subject_id] = -1
77
+ get :an_action
78
+ end
79
+
80
+ it { is_expected.to have_http_status(:unauthorized) }
81
+
82
+ it do
83
+ is_expected.to render_template(
84
+ 'dynamic_errors/unauthorized',
85
+ 'layouts/application'
86
+ )
87
+ end
88
+
89
+ it 'resets the session' do
90
+ expect(session[:subject_id]).to be_nil
91
+ end
92
+ end
93
+
94
+ context 'Subject that is not functioning' do
95
+ let(:current_subject) { create :subject, enabled: false }
96
+
97
+ before do
98
+ session[:subject_id] = current_subject.id
99
+ get :an_action
100
+ end
101
+
102
+ it { is_expected.to have_http_status(:unauthorized) }
103
+
104
+ it do
105
+ is_expected.to render_template(
106
+ 'dynamic_errors/unauthorized',
107
+ 'layouts/application'
108
+ )
109
+ end
110
+
111
+ it 'resets the session' do
112
+ expect(session[:subject_id]).to be_nil
113
+ end
114
+ end
115
+
116
+ context 'when request is session' do
117
+ it 'POST request should not create a uri session' do
118
+ post :an_action
119
+ expect(session).not_to include(:return_url)
120
+ end
121
+
122
+ it 'GET request should not create a uri session' do
123
+ get :an_action
124
+ uri = URI.parse(session[:return_url])
125
+ expect(uri.path).to eq('/anonymous/an_action')
126
+ expect(uri.query).to be_blank
127
+ expect(uri.fragment).to be_blank
128
+ end
129
+
130
+ it 'GET request should create a uri session including fragments' do
131
+ get :an_action, params: { time: 1000 }
132
+ uri = URI.parse(session[:return_url])
133
+
134
+ expect(uri.path).to eq('/anonymous/an_action')
135
+ expect(uri.query).to eq('time=1000')
136
+ expect(uri.fragment).to be_blank
137
+ end
138
+ end
139
+ end
140
+
141
+ context '#ensure_access_checked as after_action' do
142
+ before { session[:subject_id] = subject.id }
143
+
144
+ RSpec.shared_examples 'ApplicationController base state' do
145
+ it 'fails request to incorrectly implemented action' do
146
+ msg = 'No access control performed by AnonymousController#bad_action'
147
+ expect { get :bad_action }.to raise_error(msg)
148
+ end
149
+
150
+ it 'completes request to a public action' do
151
+ get :public
152
+ expect(response).to have_http_status(:ok)
153
+ end
154
+ end
155
+
156
+ context 'subject without permissions' do
157
+ subject(:subject) { create :subject }
158
+
159
+ include_examples 'ApplicationController base state'
160
+
161
+ it 'has no permissions' do
162
+ expect(subject.permissions).to eq([])
163
+ end
164
+
165
+ context 'the request does not complete' do
166
+ before { get :an_action }
167
+ it 'should respond with status code :forbidden (403)' do
168
+ expect(response).to have_http_status(:forbidden)
169
+ end
170
+
171
+ it 'renders forbidden template' do
172
+ expect(response).to render_template(
173
+ 'dynamic_errors/forbidden',
174
+ 'layouts/application'
175
+ )
176
+ end
177
+ end
178
+ end
179
+
180
+ context 'subject with invalid permissions' do
181
+ subject(:subject) do
182
+ create :subject, :authorized, permission: 'invalid:permission'
183
+ end
184
+
185
+ include_examples 'ApplicationController base state'
186
+
187
+ it 'has an invalid permission' do
188
+ expect(subject.permissions).to eq(['invalid:permission'])
189
+ end
190
+
191
+ context 'the request does not complete' do
192
+ before { get :an_action }
193
+ it 'should respond with status code :forbidden (403)' do
194
+ expect(response).to have_http_status(:forbidden)
195
+ end
196
+ it 'renders forbidden template' do
197
+ expect(response).to render_template(
198
+ 'dynamic_errors/forbidden',
199
+ 'layouts/application'
200
+ )
201
+ end
202
+ end
203
+ end
204
+
205
+ context 'subject with valid permission' do
206
+ subject(:subject) do
207
+ create :subject, :authorized, permission: 'required:permission'
208
+ end
209
+
210
+ include_examples 'ApplicationController base state'
211
+
212
+ it 'has a valid permission' do
213
+ expect(subject.permissions).to eq(['required:permission'])
214
+ end
215
+
216
+ it 'completes request after permissions checked' do
217
+ get :an_action
218
+ expect(response).to have_http_status(:ok)
219
+ end
220
+ end
221
+ end
222
+ end
223
+ end