aaf-gumboot 1.0.0.pre.alpha.2

Sign up to get free protection for your applications and to get access to all the features.
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
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: e952df6d99ca0bf6a4318eaa499ed7c77a89c141
4
+ data.tar.gz: c87a7d4266d7e3cfea893b31864c095c6cdb940b
5
+ SHA512:
6
+ metadata.gz: 6939763db4326b84e5df6b9ff948db76520132cafbee27578ed5c0dd3e358b24138589052092cce16b2eba42a21a95932d054c22bfc27fd1d31f22a94efc71b7
7
+ data.tar.gz: 74e51eee1bfacd930826ade7230aecc10fbf5e463dd0b9ef61907f95c4d5e89a2b63eb1ca7125c52e13a44ce371e43f1b36a037f481a8c77bb7c79963150f2ac
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
15
+ Gemfile.lock
16
+
17
+ /spec/dummy/log/*
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,15 @@
1
+ Style/Documentation:
2
+ Enabled: false
3
+
4
+ Style/FileName:
5
+ Exclude:
6
+ - lib/aaf-gumboot.rb
7
+
8
+ AllCops:
9
+ TargetRubyVersion: 2.1
10
+
11
+ Metrics/BlockLength:
12
+ Exclude:
13
+ - aaf-gumboot.gemspec
14
+ - spec/**/*.rb
15
+ - lib/**/*.rb
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in aaf-gumboot.gemspec
4
+ gemspec
data/Guardfile ADDED
@@ -0,0 +1,18 @@
1
+ guard :bundler do
2
+ watch('Gemfile')
3
+ watch(/^.+\.gemspec/)
4
+ end
5
+
6
+ guard :rspec, cmd: 'bundle exec rspec' do
7
+ watch(%r{/^spec\/.+_spec\.rb$/})
8
+ watch(%r{/^lib\/gumboot\/shared_examples\/(.+)\.rb$/}) do |m|
9
+ "spec/gumboot/#{m[1]}_spec.rb"
10
+ end
11
+ watch(%r{^spec/(dummy|support)/.+\.rb}) { 'spec' }
12
+ watch('spec/spec_helper.rb') { 'spec' }
13
+ end
14
+
15
+ guard :rubocop do
16
+ watch(/.+\.rb$/)
17
+ watch(%r{/(?:.+\/)?\.rubocop\.yml$/}) { |m| File.dirname(m[0]) }
18
+ end
data/LICENSE ADDED
@@ -0,0 +1,202 @@
1
+
2
+ Apache License
3
+ Version 2.0, January 2004
4
+ http://www.apache.org/licenses/
5
+
6
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
+
8
+ 1. Definitions.
9
+
10
+ "License" shall mean the terms and conditions for use, reproduction,
11
+ and distribution as defined by Sections 1 through 9 of this document.
12
+
13
+ "Licensor" shall mean the copyright owner or entity authorized by
14
+ the copyright owner that is granting the License.
15
+
16
+ "Legal Entity" shall mean the union of the acting entity and all
17
+ other entities that control, are controlled by, or are under common
18
+ control with that entity. For the purposes of this definition,
19
+ "control" means (i) the power, direct or indirect, to cause the
20
+ direction or management of such entity, whether by contract or
21
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
22
+ outstanding shares, or (iii) beneficial ownership of such entity.
23
+
24
+ "You" (or "Your") shall mean an individual or Legal Entity
25
+ exercising permissions granted by this License.
26
+
27
+ "Source" form shall mean the preferred form for making modifications,
28
+ including but not limited to software source code, documentation
29
+ source, and configuration files.
30
+
31
+ "Object" form shall mean any form resulting from mechanical
32
+ transformation or translation of a Source form, including but
33
+ not limited to compiled object code, generated documentation,
34
+ and conversions to other media types.
35
+
36
+ "Work" shall mean the work of authorship, whether in Source or
37
+ Object form, made available under the License, as indicated by a
38
+ copyright notice that is included in or attached to the work
39
+ (an example is provided in the Appendix below).
40
+
41
+ "Derivative Works" shall mean any work, whether in Source or Object
42
+ form, that is based on (or derived from) the Work and for which the
43
+ editorial revisions, annotations, elaborations, or other modifications
44
+ represent, as a whole, an original work of authorship. For the purposes
45
+ of this License, Derivative Works shall not include works that remain
46
+ separable from, or merely link (or bind by name) to the interfaces of,
47
+ the Work and Derivative Works thereof.
48
+
49
+ "Contribution" shall mean any work of authorship, including
50
+ the original version of the Work and any modifications or additions
51
+ to that Work or Derivative Works thereof, that is intentionally
52
+ submitted to Licensor for inclusion in the Work by the copyright owner
53
+ or by an individual or Legal Entity authorized to submit on behalf of
54
+ the copyright owner. For the purposes of this definition, "submitted"
55
+ means any form of electronic, verbal, or written communication sent
56
+ to the Licensor or its representatives, including but not limited to
57
+ communication on electronic mailing lists, source code control systems,
58
+ and issue tracking systems that are managed by, or on behalf of, the
59
+ Licensor for the purpose of discussing and improving the Work, but
60
+ excluding communication that is conspicuously marked or otherwise
61
+ designated in writing by the copyright owner as "Not a Contribution."
62
+
63
+ "Contributor" shall mean Licensor and any individual or Legal Entity
64
+ on behalf of whom a Contribution has been received by Licensor and
65
+ subsequently incorporated within the Work.
66
+
67
+ 2. Grant of Copyright License. Subject to the terms and conditions of
68
+ this License, each Contributor hereby grants to You a perpetual,
69
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70
+ copyright license to reproduce, prepare Derivative Works of,
71
+ publicly display, publicly perform, sublicense, and distribute the
72
+ Work and such Derivative Works in Source or Object form.
73
+
74
+ 3. Grant of Patent License. Subject to the terms and conditions of
75
+ this License, each Contributor hereby grants to You a perpetual,
76
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
+ (except as stated in this section) patent license to make, have made,
78
+ use, offer to sell, sell, import, and otherwise transfer the Work,
79
+ where such license applies only to those patent claims licensable
80
+ by such Contributor that are necessarily infringed by their
81
+ Contribution(s) alone or by combination of their Contribution(s)
82
+ with the Work to which such Contribution(s) was submitted. If You
83
+ institute patent litigation against any entity (including a
84
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
85
+ or a Contribution incorporated within the Work constitutes direct
86
+ or contributory patent infringement, then any patent licenses
87
+ granted to You under this License for that Work shall terminate
88
+ as of the date such litigation is filed.
89
+
90
+ 4. Redistribution. You may reproduce and distribute copies of the
91
+ Work or Derivative Works thereof in any medium, with or without
92
+ modifications, and in Source or Object form, provided that You
93
+ meet the following conditions:
94
+
95
+ (a) You must give any other recipients of the Work or
96
+ Derivative Works a copy of this License; and
97
+
98
+ (b) You must cause any modified files to carry prominent notices
99
+ stating that You changed the files; and
100
+
101
+ (c) You must retain, in the Source form of any Derivative Works
102
+ that You distribute, all copyright, patent, trademark, and
103
+ attribution notices from the Source form of the Work,
104
+ excluding those notices that do not pertain to any part of
105
+ the Derivative Works; and
106
+
107
+ (d) If the Work includes a "NOTICE" text file as part of its
108
+ distribution, then any Derivative Works that You distribute must
109
+ include a readable copy of the attribution notices contained
110
+ within such NOTICE file, excluding those notices that do not
111
+ pertain to any part of the Derivative Works, in at least one
112
+ of the following places: within a NOTICE text file distributed
113
+ as part of the Derivative Works; within the Source form or
114
+ documentation, if provided along with the Derivative Works; or,
115
+ within a display generated by the Derivative Works, if and
116
+ wherever such third-party notices normally appear. The contents
117
+ of the NOTICE file are for informational purposes only and
118
+ do not modify the License. You may add Your own attribution
119
+ notices within Derivative Works that You distribute, alongside
120
+ or as an addendum to the NOTICE text from the Work, provided
121
+ that such additional attribution notices cannot be construed
122
+ as modifying the License.
123
+
124
+ You may add Your own copyright statement to Your modifications and
125
+ may provide additional or different license terms and conditions
126
+ for use, reproduction, or distribution of Your modifications, or
127
+ for any such Derivative Works as a whole, provided Your use,
128
+ reproduction, and distribution of the Work otherwise complies with
129
+ the conditions stated in this License.
130
+
131
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
132
+ any Contribution intentionally submitted for inclusion in the Work
133
+ by You to the Licensor shall be under the terms and conditions of
134
+ this License, without any additional terms or conditions.
135
+ Notwithstanding the above, nothing herein shall supersede or modify
136
+ the terms of any separate license agreement you may have executed
137
+ with Licensor regarding such Contributions.
138
+
139
+ 6. Trademarks. This License does not grant permission to use the trade
140
+ names, trademarks, service marks, or product names of the Licensor,
141
+ except as required for reasonable and customary use in describing the
142
+ origin of the Work and reproducing the content of the NOTICE file.
143
+
144
+ 7. Disclaimer of Warranty. Unless required by applicable law or
145
+ agreed to in writing, Licensor provides the Work (and each
146
+ Contributor provides its Contributions) on an "AS IS" BASIS,
147
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148
+ implied, including, without limitation, any warranties or conditions
149
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150
+ PARTICULAR PURPOSE. You are solely responsible for determining the
151
+ appropriateness of using or redistributing the Work and assume any
152
+ risks associated with Your exercise of permissions under this License.
153
+
154
+ 8. Limitation of Liability. In no event and under no legal theory,
155
+ whether in tort (including negligence), contract, or otherwise,
156
+ unless required by applicable law (such as deliberate and grossly
157
+ negligent acts) or agreed to in writing, shall any Contributor be
158
+ liable to You for damages, including any direct, indirect, special,
159
+ incidental, or consequential damages of any character arising as a
160
+ result of this License or out of the use or inability to use the
161
+ Work (including but not limited to damages for loss of goodwill,
162
+ work stoppage, computer failure or malfunction, or any and all
163
+ other commercial damages or losses), even if such Contributor
164
+ has been advised of the possibility of such damages.
165
+
166
+ 9. Accepting Warranty or Additional Liability. While redistributing
167
+ the Work or Derivative Works thereof, You may choose to offer,
168
+ and charge a fee for, acceptance of support, warranty, indemnity,
169
+ or other liability obligations and/or rights consistent with this
170
+ License. However, in accepting such obligations, You may act only
171
+ on Your own behalf and on Your sole responsibility, not on behalf
172
+ of any other Contributor, and only if You agree to indemnify,
173
+ defend, and hold each Contributor harmless for any liability
174
+ incurred by, or claims asserted against, such Contributor by reason
175
+ of your accepting any such warranty or additional liability.
176
+
177
+ END OF TERMS AND CONDITIONS
178
+
179
+ APPENDIX: How to apply the Apache License to your work.
180
+
181
+ To apply the Apache License to your work, attach the following
182
+ boilerplate notice, with the fields enclosed by brackets "[]"
183
+ replaced with your own identifying information. (Don't include
184
+ the brackets!) The text should be enclosed in the appropriate
185
+ comment syntax for the file format. We also recommend that a
186
+ file or class name and description of purpose be included on the
187
+ same "printed page" as the copyright notice for easier
188
+ identification within third-party archives.
189
+
190
+ Copyright [yyyy] [name of copyright owner]
191
+
192
+ Licensed under the Apache License, Version 2.0 (the "License");
193
+ you may not use this file except in compliance with the License.
194
+ You may obtain a copy of the License at
195
+
196
+ http://www.apache.org/licenses/LICENSE-2.0
197
+
198
+ Unless required by applicable law or agreed to in writing, software
199
+ distributed under the License is distributed on an "AS IS" BASIS,
200
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201
+ See the License for the specific language governing permissions and
202
+ limitations under the License.
data/README.md ADDED
@@ -0,0 +1,1069 @@
1
+ # AAF Gumboot
2
+
3
+ [![Gem Version][GV img]][Gem Version]
4
+ [![Build Status][BS img]][Build Status]
5
+ [![Dependency Status][DS img]][Dependency Status]
6
+ [![Code Climate][CC img]][Code Climate]
7
+ [![Coverage Status][CS img]][Code Climate]
8
+
9
+ [Gem Version]: https://rubygems.org/gems/aaf-gumboot
10
+ [Build Status]: https://codeship.com/projects/91207
11
+ [Dependency Status]: https://gemnasium.com/ausaccessfed/aaf-gumboot
12
+ [Code Climate]: https://codeclimate.com/github/ausaccessfed/aaf-gumboot
13
+
14
+ [GV img]: https://img.shields.io/gem/v/aaf-gumboot.svg
15
+ [BS img]: https://img.shields.io/codeship/9f557e20-0ccb-0133-b925-7aae0ba3591b/develop.svg
16
+ [DS img]: https://img.shields.io/gemnasium/ausaccessfed/aaf-gumboot.svg
17
+ [CC img]: https://img.shields.io/codeclimate/github/ausaccessfed/aaf-gumboot.svg
18
+ [CS img]: https://img.shields.io/codeclimate/coverage/github/ausaccessfed/aaf-gumboot.svg
19
+
20
+ Subjects, APISubjects, Roles, Permissions, Access Control, RESTful APIs, Events and the endless stream of possible Gems.
21
+
22
+ Gumboot sloshes through these **muddy** topics for AAF applications, bringing down swift justice where it finds problems.
23
+
24
+ ![](http://i.imgur.com/XP4Yw6e.jpg)
25
+
26
+ ```
27
+ Copyright 2014-2015, Australian Access Federation
28
+
29
+ Licensed under the Apache License, Version 2.0 (the "License");
30
+ you may not use this file except in compliance with the License.
31
+ You may obtain a copy of the License at
32
+
33
+ http://www.apache.org/licenses/LICENSE-2.0
34
+
35
+ Unless required by applicable law or agreed to in writing, software
36
+ distributed under the License is distributed on an "AS IS" BASIS,
37
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
38
+ See the License for the specific language governing permissions and
39
+ limitations under the License.
40
+ ```
41
+
42
+ ## Your local development environment
43
+ Before you get started you should ensure your development machine has the following files located under `~/.aaf`.
44
+
45
+ 1. `rapidconnect.yml`
46
+
47
+ Generate a secret to use with Rapid Connect with the following:
48
+
49
+ $> tr -dc '[[:alnum:][:punct:]]' < /dev/urandom | head -c32 ;echo
50
+
51
+ Access [https://rapid.test.aaf.edu.au](https://rapid.test.aaf.edu.au) and register using your secret created above and the callback URL of `http://localhost:8080/auth/jwt`
52
+
53
+ Upon successful registration you cannot simply use the URL Rapid Connect provides by team. Request one of the team change your registration to be of the type `AU Research`. They can then provide you the appropriate URL to enter below.
54
+
55
+ Edit your `rapidconnect.yml` to contains:
56
+
57
+ ``` ruby
58
+ ---
59
+ url: 'https://rapid.test.aaf.edu.au/jwt/authnrequest/.....'
60
+ secret: '<you generated this>'
61
+ issuer: 'https://rapid.test.aaf.edu.au'
62
+ audience: 'http://localhost:8080'
63
+ ```
64
+
65
+ 2. Create a key for use with event code called `event_encryption_key.pem` via the command `openssl genrsa -out ~/.aaf/event_encryption_key.pem 2048`
66
+
67
+ 3. Create a key and CSR for use with access to other AAF application API endpoints.
68
+
69
+ 1. `openssl genrsa -out api-client.key 2048`
70
+ 2. `openssl req -new -key api-client.key -out api-client.csr -subj '/CN=Your Name Here/'`
71
+ 2. Access [https://certs.aaf.edu.au/](https://certs.aaf.edu.au/)
72
+ 3. Request a certificate under `Australian Access Federation` provide your CSR and select the CA 'AAF API Client CA'
73
+ 4. Wait for approval
74
+ 5. One approved your certificate link will be sent to you in email, download this file
75
+ 6. Record the certificate CN, you will need this in the future
76
+ 7. Rename the downloaded file as api-client.crt
77
+
78
+ Here is what your `~/.aaf` should end up looking like:
79
+
80
+ ```
81
+ $ ls -l
82
+
83
+ total 32
84
+
85
+ api-client.crt
86
+ api-client.key
87
+ event_encryption_key.pem
88
+ rapidconnect.yml
89
+ ```
90
+
91
+ ## General advice for AAF Ruby applications
92
+ For AAF staff this document assumes you've already read and are following the more general AAF [development workflow](https://github.com/ausaccessfed/developmentworkflow).
93
+
94
+ ### Code Comments
95
+ In general AAF ruby based applications don't need 'default' or 'generated' comments committed to our code. We're all experienced developers so this kind of extraneous comment doesn't make sense in our space. You should remove these prior to submitting PR.
96
+
97
+ Of course ACTUAL comments describing something you've written that is a little bit odd or unusual are very much welcome.
98
+
99
+ ## Gems
100
+ The way we build ruby applications has tried to be standardised as much as possible at a base layer. You're likely going to want all these Gems in your Gemfile for a Rails app or a considerable subset of them for a non Rails app.
101
+
102
+ ```ruby
103
+ gem 'rails', '4.2.3' # Ensure latest release
104
+ gem 'mysql2'
105
+
106
+ gem 'valhammer'
107
+ gem 'accession'
108
+
109
+ gem 'puma', require: false
110
+ gem 'god', require: false
111
+
112
+ group :development, :test do
113
+ gem 'rspec-rails', '~> 3.3.0'
114
+ gem 'shoulda-matchers'
115
+
116
+ gem 'factory_girl_rails'
117
+ gem 'faker'
118
+ gem 'timecop'
119
+ gem 'database_cleaner'
120
+
121
+ gem 'rubocop', require: false
122
+ gem 'simplecov', require: false
123
+
124
+ gem 'capybara', require: false
125
+ gem 'poltergeist', require: false
126
+ gem 'phantomjs', require: 'phantomjs/poltergeist'
127
+
128
+ gem 'guard', require: false
129
+ gem 'guard-bundler', require: false
130
+ gem 'guard-rubocop', require: false
131
+ gem 'guard-rspec', require: false
132
+ gem 'guard-brakeman', require: false
133
+ gem 'terminal-notifier-guard', require: false
134
+
135
+ gem 'aaf-gumboot'
136
+ end
137
+ ```
138
+
139
+ Execute:
140
+
141
+ $ bundle
142
+
143
+ ## Guard
144
+ Gumboot projects make use of Guard, Rubocop, RSpec and Brakeman.
145
+
146
+ To get this all up and running you should execute:
147
+
148
+ $ bundle exec guard init
149
+
150
+ The result will be a reasonably complete `Guardfile`. As described earlier you can remove the default comments that are generated and also references to the Turnip project which we don't utilise.
151
+
152
+ ### RSpec
153
+ Execute:
154
+
155
+ $ bundle exec rails generate rspec:install
156
+
157
+ Modify the generated RSpec config file as follows `.rspec`:
158
+
159
+ ```
160
+ --color
161
+ --require spec_helper
162
+ --format documentation
163
+ ```
164
+
165
+ ### Rubocop
166
+ Add a Rubocop config file `.rubocop.yml`:
167
+
168
+ ```
169
+ AllCops:
170
+ TargetRubyVersion: 2.3
171
+ Exclude:
172
+ - db/schema.rb
173
+
174
+ Rails:
175
+ Enabled: true
176
+
177
+ Rails/Output:
178
+ Exclude:
179
+ - db/seeds.rb
180
+
181
+ Style/Documentation:
182
+ Enabled: false
183
+
184
+ Metrics/MethodLength:
185
+ Exclude:
186
+ - db/migrate/*.rb
187
+
188
+ Metrics/AbcSize:
189
+ Exclude:
190
+ - db/migrate/*.rb
191
+ ```
192
+
193
+ ### Simplecov
194
+ Add a simplecov config file `.simplecov`:
195
+
196
+ ```
197
+ SimpleCov.start('rails') do
198
+ minimum_coverage 100
199
+ end
200
+ ```
201
+
202
+ Edit `spec/spec_helper.rb` and add
203
+
204
+ ``` ruby
205
+ require 'simplecov'
206
+ ```
207
+
208
+ ## Git Ignore
209
+ This needs to be customised per application but be sure it excludes **all** local config files, keys, certificates and anything else containing secrets. For example:
210
+
211
+ ```
212
+ # TODO this is application specific
213
+ config/xyz_service.yml
214
+
215
+ config/rapidconnect.yml
216
+ config/api-client.*
217
+ config/event_encryption_key.pem
218
+ spec/examples.txt
219
+
220
+ coverage
221
+ tmp
222
+ log
223
+ vendor
224
+ .DS_Store
225
+ ```
226
+
227
+ ## Acronyms
228
+ Ensure 'API' is an acronym within your application:
229
+
230
+ e.g for Rails applications in config/initializers/inflections.rb
231
+
232
+ ```ruby
233
+ ActiveSupport::Inflector.inflections(:en) do |inflect|
234
+ inflect.acronym 'API'
235
+ end
236
+ ```
237
+ ## Authentication and Identity (1 of 2)
238
+ There are two options for AAF applications to authenticate and obtain identity
239
+ information for subjects, Rapid Connect and SAML/Shibboleth.
240
+ Unless otherwise specified for your project default to using Rapid Connect.
241
+
242
+ ### Rapid Connect using rapid-rack gem
243
+
244
+ #### Gemfile
245
+ Add the following gem to your Gemfile in the default group:
246
+
247
+ ``` ruby
248
+ gem 'rapid-rack'
249
+ ```
250
+
251
+ Execute:
252
+
253
+ $ bundle
254
+
255
+ #### Authentication Receiver
256
+ To utilise Rapid Connect your application will require a receiver class
257
+ which will receive the validated claim from Rapid Connect and establish a
258
+ session for the authenticated subject.
259
+
260
+ As an initial step our receiver class will be a no-op. You'll implement the receiver, in full, later in this document.
261
+
262
+ Create `lib/authentication.rb`:
263
+
264
+ ``` ruby
265
+ # frozen_string_literal: true
266
+ module Authentication
267
+ end
268
+
269
+ require 'authentication/subject_receiver'
270
+ ```
271
+
272
+ Create `lib/authentication/subject_receiver.rb`:
273
+
274
+ ``` ruby
275
+ # frozen_string_literal: true
276
+ module Authentication
277
+ class SubjectReceiver
278
+ include RapidRack::DefaultReceiver
279
+ include RapidRack::RedisRegistry
280
+
281
+ def map_attributes(_env, attrs)
282
+ {}
283
+ end
284
+
285
+ def subject(_env, attrs)
286
+ end
287
+ end
288
+ end
289
+ ```
290
+
291
+ #### Configure receiver
292
+ Add the following to `config/application.rb`:
293
+
294
+ ``` ruby
295
+ config.autoload_paths += [
296
+ File.join(config.root, 'lib')
297
+ ]
298
+ config.rapid_rack.receiver = 'Authentication::SubjectReceiver'
299
+ ```
300
+
301
+ #### Configure routes
302
+ Add the following to `config/routes.yml`:
303
+
304
+ ``` ruby
305
+ mount RapidRack::Engine => '/auth'
306
+ ```
307
+
308
+ ### SAML/Shibboleth using saml-rack gem
309
+ **TODO**: This needs to be written along with finalisation of the shib-rack readme,
310
+ currently being authored at https://github.com/ausaccessfed/shib-rack under the
311
+ `feature/readme` branch. This section should essentially mirror the above for
312
+ Rapid Connect given the concepts in each gem are reasonably similar.
313
+
314
+ ## Setup
315
+ All AAF applications utilise a standard setup process. This makes it easier for
316
+ developers who are new to a project as all you should need to know to get
317
+ started is `./bin/setup`. In addition this helps with merging in additional
318
+ config options as projects grow.
319
+
320
+ ### Service specific config
321
+
322
+ Most of our applications require unique configuration at deployment time and we structure this within a standard location of `config/xyz_service.yml.dist`. Replace **xyz** with the name of your application. e.g. For bigboot-service, you'd use `config/bigboot_service.yml.dist`
323
+
324
+ ### Create a custom setup script for your application
325
+
326
+ The general structure of your `./bin/setup` file should be as follows:
327
+
328
+ ```ruby
329
+ #!/usr/bin/env ruby
330
+ # frozen_string_literal: true
331
+
332
+ Dir.chdir File.expand_path('..', File.dirname(__FILE__))
333
+
334
+ puts '== Installing dependencies =='
335
+ system 'gem install bundler --conservative'
336
+ system 'bundle check || bundle install'
337
+
338
+ require 'bundler/setup'
339
+ require 'gumboot/strap'
340
+
341
+ include Gumboot::Strap
342
+
343
+ puts "\n== Installing configuration files =="
344
+
345
+ link_global_configuration %w(api-client.crt api-client.key event_encryption_key.pem)
346
+
347
+ # Rapid Connect Applications must enable this
348
+ # link_global_configuration %w(rapidconnect.yml)
349
+
350
+ # TODO: Name this per your local app
351
+ update_local_configuration %w(xyz_service.yml)
352
+
353
+ puts "\n== Loading Rails environment =="
354
+ require_relative '../config/environment'
355
+
356
+ ensure_activerecord_databases(%w(test development))
357
+ maintain_activerecord_schema
358
+ clean_logs
359
+ clean_tempfiles
360
+ ```
361
+
362
+ You can find task details (or add new ones) at [https://github.com/ausaccessfed/aaf-gumboot/blob/develop/lib/gumboot/strap.rb](https://github.com/ausaccessfed/aaf-gumboot/blob/develop/lib/gumboot/strap.rb).
363
+
364
+ ## Database
365
+
366
+ ### Configuration
367
+ **Note:** This is only applicable to applications using MySQL / MariaDB.
368
+
369
+ We use the following conventions for naming around databases:
370
+
371
+ Username: `xyz_app` - where xyz represents the name of your application. In most
372
+ cases this will be xyz-service, you should drop the `-service`.
373
+
374
+ e.g. For bigboot-service, you'd use `bigboot_app`
375
+
376
+ Database name: `xyz_#{env}` - where xyz represents the name of your application.
377
+ In most cases this will be xyz-service, you should drop the `-service`.
378
+
379
+ e.g. For bigboot-service, you'd use `bigboot_development` for development.
380
+
381
+ ### Example Configuration
382
+
383
+ The following example should form the basis of project database configuration.
384
+ It is ready to be used for local development/test, for codeship CI and with AAF
385
+ production defaults.
386
+
387
+ ```yaml
388
+ default: &default
389
+ adapter: mysql2
390
+ username: xyz_app
391
+ password: password
392
+ host: 127.0.0.1
393
+ pool: 5
394
+ encoding: utf8
395
+ collation: utf8_bin
396
+
397
+ development:
398
+ <<: *default
399
+ database: xyz_development
400
+
401
+ test:
402
+ <<: *default
403
+ database: xyz_test
404
+
405
+ production:
406
+ <<: *default
407
+ username: <%= ENV['XYZ_DB_USERNAME'] %>
408
+ password: <%= ENV['XYZ_DB_PASSWORD'] %>
409
+ database: <%= ENV['XYZ_DB_NAME'] %>
410
+ ```
411
+
412
+ ### UTF8 and binary collation
413
+ The example config above will ensure your database connection is using
414
+ the `utf8` character set, and `utf8_bin` collation which is required for all
415
+ AAF applications.
416
+
417
+ However you *MUST* also create a migration which ensures the correct setting
418
+ is applied at the database level:
419
+
420
+ ```ruby
421
+ class ChangeDatabaseCollationToBinary < ActiveRecord::Migration
422
+ def change
423
+ execute('ALTER DATABASE COLLATE = utf8_bin')
424
+ end
425
+ end
426
+ ```
427
+
428
+ Before creating any further migrations, add the RSpec shared examples which
429
+ validate the encoding and collation of your schema:
430
+
431
+ ```ruby
432
+ # spec/models/schema_spec.rb
433
+
434
+ require 'rails_helper'
435
+ require 'gumboot/shared_examples/database_schema'
436
+
437
+ RSpec.describe 'Database Schema' do
438
+ let(:connection) { ActiveRecord::Base.connection.raw_connection }
439
+
440
+ include_context 'Database Schema'
441
+ end
442
+ ```
443
+
444
+ #### Existing Applications
445
+ For any existing app which adopts this set of tests, it is not sufficient to
446
+ change the configuration and run all migrations again on a clean database. All
447
+ tables which predate the configuration change MUST have a migration created
448
+ which alters their collation (to ensure test / production environments have the
449
+ correct database schema). This can be done per table as follows:
450
+
451
+ ```ruby
452
+ class ChangeTableCollationToBinary < ActiveRecord::Migration
453
+ def change
454
+ execute('ALTER TABLE my_objects COLLATE = utf8_bin')
455
+ execute('ALTER TABLE my_objects CONVERT TO CHARACTER SET utf8 ' \
456
+ 'COLLATE utf8_bin')
457
+ end
458
+ end
459
+ ```
460
+
461
+ The change in collation will not be reflected in `db/schema.rb`, but will still
462
+ be applied correctly during `rake db:schema:load` due to the new database
463
+ collation setting.
464
+
465
+ ### Continuous Integration environments
466
+ An often-seen pattern on CI servers is to use a database which was created
467
+ out-of-band before permissions were granted to access the database. This very
468
+ rarely results in the correct collation setting for the database.
469
+
470
+ There are two ways we can address this easily:
471
+
472
+ 1. Drop and create the database again before loading the schema or running
473
+ migrations. For example:
474
+
475
+ ```
476
+ bundle exec rake db:drop db:create db:schema:load
477
+ ```
478
+
479
+ 2. Alter the collation of the database using the `mysql` command line client
480
+ (ensure to alter both the `test` and `development` databases when using this
481
+ method). For example:
482
+
483
+ ```
484
+ mysql -e 'ALTER DATABASE COLLATE = utf8_bin' xyz_development
485
+ mysql -e 'ALTER DATABASE COLLATE = utf8_bin' xyz_test
486
+ ```
487
+
488
+ Also note that some CI platforms will automatically set your
489
+ `config/database.yml` (thus **overwriting your collation settings**).
490
+ For Codeship refer to [https://codeship.com/documentation/databases/](https://codeship.com/documentation/databases/) to configure your database
491
+ correctly.
492
+
493
+ ## Models
494
+ All AAF applications **must** provide the following models.
495
+
496
+ Example implementations are provided for ActiveModel and Sequel below. Developers may extend models or implement them in any way they wish.
497
+
498
+ For each model a FactoryGirl factory *must also be provided*.
499
+
500
+ For each model the provided RSpec shared examples **must** be used within your application and **must** pass.
501
+
502
+ ### Subject
503
+ A Subject represents state and security operations for a single application user.
504
+
505
+ #### Active Model
506
+ ```ruby
507
+ class Subject < ActiveRecord::Base
508
+ include Accession::Principal
509
+
510
+ has_many :subject_roles
511
+ has_many :roles, through: :subject_roles
512
+
513
+ valhammer
514
+
515
+ def permissions
516
+ # This could be extended to gather permissions from
517
+ # other data sources providing input to subject identity
518
+ roles.joins(:permissions).pluck('permissions.value')
519
+ end
520
+
521
+ def functioning?
522
+ # more than enabled? could inform functioning?
523
+ # such as an administrative or AAF lock
524
+ enabled?
525
+ end
526
+ end
527
+ ```
528
+ #### Sequel
529
+ ``` ruby
530
+ class Subject < Sequel::Model
531
+ include Accession::Principal
532
+
533
+ many_to_many :roles, class: 'Role'
534
+
535
+ def permissions
536
+ # This could be extended to gather permissions from
537
+ # other data sources providing input to api_subject identity
538
+ roles.flat_map { |role| role.permissions.map(&:value) }
539
+ end
540
+
541
+ def functioning?
542
+ # more than enabled? could inform functioning?
543
+ # such as an administrative or AAF lock
544
+ enabled?
545
+ end
546
+
547
+ def validate
548
+ validates_presence [:name, :mail, :enabled, :complete]
549
+ validates_presence [:targeted_id, :shared_token] if complete?
550
+ end
551
+ end
552
+ ```
553
+
554
+ #### RSpec shared examples
555
+ ```ruby
556
+ require 'rails_helper'
557
+
558
+ require 'gumboot/shared_examples/subjects'
559
+
560
+ RSpec.describe Subject, type: :model do
561
+ include_examples 'Subjects'
562
+
563
+ # TODO: examples for your model extensions here
564
+ end
565
+ ```
566
+
567
+ ### API Subject
568
+ An API Subject is an extension of the Subject concept reserved specifically for Subjects that utilise x509 client certificate verification to make requests to the applications RESTful API endpoints. x509_cn client certificates **MUST** be unique.
569
+
570
+ #### Active Model
571
+ ``` ruby
572
+ class APISubject < ActiveRecord::Base
573
+ include Accession::Principal
574
+
575
+ has_many :api_subject_roles
576
+ has_many :roles, through: :api_subject_roles
577
+
578
+ valhammer
579
+ validates :x509_cn, format: { with: /\A[\w-]+\z/ }
580
+
581
+ def permissions
582
+ # This could be extended to gather permissions from
583
+ # other data sources providing input to api_subject identity
584
+ roles.joins(:permissions).pluck('permissions.value')
585
+ end
586
+
587
+ def functioning?
588
+ # more than enabled? could inform functioning?
589
+ # such as an administrative or AAF lock
590
+ enabled?
591
+ end
592
+ end
593
+ ```
594
+
595
+ #### Sequel
596
+ ``` ruby
597
+ class APISubject < Sequel::Model
598
+ include Accession::Principal
599
+
600
+ many_to_many :roles, class: 'Role'
601
+
602
+ def permissions
603
+ # This could be extended to gather permissions from
604
+ # other data sources providing input to api_subject identity
605
+ roles.flat_map { |role| role.permissions.map(&:value) }
606
+ end
607
+
608
+ def functioning?
609
+ # more than enabled? could inform functioning?
610
+ # such as an administrative or AAF lock
611
+ enabled?
612
+ end
613
+
614
+ def validate
615
+ validates_presence [:x509_cn, :description,
616
+ :contact_name, :contact_mail, :enabled]
617
+ validates_format /\A[\w-]+\z/, :x509_cn
618
+ end
619
+ end
620
+ ```
621
+
622
+ #### RSpec shared examples
623
+ ```ruby
624
+ require 'rails_helper'
625
+
626
+ require 'gumboot/shared_examples/api_subjects'
627
+
628
+ RSpec.describe APISubject, type: :model do
629
+ include_examples 'API Subjects'
630
+
631
+ # TODO: examples for your model extensions here
632
+ end
633
+ ```
634
+
635
+ ### Role
636
+ The term *Role* is thrown around a lot and it's meaning is very diluted. For our purposes a Role is really a collection of permissions and a collection of Subjects for whom each associated permission is applied.
637
+
638
+ #### Active Record
639
+ ``` ruby
640
+ class Role < ActiveRecord::Base
641
+ has_many :api_subject_roles
642
+ has_many :api_subjects, through: :api_subject_roles
643
+
644
+ has_many :subject_roles
645
+ has_many :subjects, through: :subject_roles
646
+
647
+ has_many :permissions
648
+
649
+ valhammer
650
+ end
651
+ ```
652
+
653
+ #### Sequel
654
+ ``` ruby
655
+ class Role < Sequel::Model
656
+ one_to_many :permissions
657
+
658
+ many_to_many :api_subjects
659
+ many_to_many :subjects
660
+
661
+ def validate
662
+ super
663
+ validates_presence [:name]
664
+ end
665
+ end
666
+ ```
667
+
668
+ #### RSpec shared examples
669
+ ```ruby
670
+ require 'rails_helper'
671
+
672
+ require 'gumboot/shared_examples/roles'
673
+
674
+ RSpec.describe Role, type: :model do
675
+ include_examples 'Roles'
676
+
677
+ # TODO: examples for your model extensions here
678
+ end
679
+ ```
680
+
681
+ ### Permission
682
+ Permissions are the lowest level constructs in security policies. They describe which actions a Subject is able to perform or data the Subject is able to access.
683
+
684
+ #### Active Record
685
+ ``` ruby
686
+ class Permission < ActiveRecord::Base
687
+ belongs_to :role
688
+
689
+ valhammer
690
+
691
+ validates :value, format: Accession::Permission.regexp
692
+ end
693
+ ```
694
+
695
+ #### Sequel
696
+ ``` ruby
697
+ class Permission < Sequel::Model
698
+ many_to_one :role
699
+
700
+ def validate
701
+ super
702
+ validates_presence [:role, :value]
703
+ end
704
+ end
705
+ ```
706
+
707
+ #### RSpec shared examples
708
+ ```ruby
709
+ require 'rails_helper'
710
+
711
+ require 'gumboot/shared_examples/permissions'
712
+
713
+ RSpec.describe Permission, type: :model do
714
+ include_examples 'Permissions'
715
+
716
+ # TODO: examples for your model extensions here
717
+ end
718
+ ```
719
+
720
+ ### Foreign Keys
721
+
722
+ Rails 4.2 and above support a new foreign keys DSL. This is feature is not presently enabled in all databases [see supported database list](http://edgeguides.rubyonrails.org/4_2_release_notes.html#foreign-key-support) but you can safely integrate this supplied tests regardless of database.
723
+
724
+ #### Adding Foreign Keys
725
+
726
+ Include these foreign keys in a relevant migration.
727
+
728
+ ```ruby
729
+ add_foreign_key 'api_subject_roles', 'api_subjects'
730
+ add_foreign_key 'api_subject_roles', 'roles'
731
+ add_foreign_key 'permissions', 'roles'
732
+ add_foreign_key 'subject_roles', 'roles'
733
+ add_foreign_key 'subject_roles', 'subjects'
734
+ ```
735
+
736
+ #### RSpec shared examples
737
+
738
+ The shared examples will **only** be run if your current database configuration supports foreign keys. Otherwise they will be safely ignored by rspec at runtime.
739
+
740
+ **Important Note:** These specs are **ONLY** valid for ActiveRecord. Sequel users should implement their own specs to test foreign keys meet the required specification.
741
+
742
+ ```ruby
743
+ require 'rails_helper'
744
+
745
+ require 'gumboot/shared_examples/foreign_keys'
746
+
747
+ RSpec.describe 'Foreign Keys' do
748
+ include_examples 'Gumboot Foreign Keys'
749
+
750
+ # TODO: examples for your foreign key extensions here
751
+
752
+ end
753
+ ```
754
+
755
+ ## Authentication and Identity (2 of 2)
756
+ You should now follow the documention for [https://github.com/ausaccessfed/rapid-rack](https://github.com/ausaccessfed/rapid-rack) or [https://github.com/ausaccessfed/shib-rack](https://github.com/ausaccessfed/shib-rack) depending on how your application is handling authentication and identity to complete your receiver implementation.
757
+
758
+ ## Access Control
759
+
760
+ **TODO**
761
+
762
+ ## Controllers
763
+
764
+ AAF applications must utilise controllers which default to verifying authentication and access control on every request. This can be changed as implementations require to be publicly accessible for example but must be explicitly configured in code to make it clear to all.
765
+
766
+ ##### Rails 4.x
767
+ See `spec/dummy/app/controllers/application_controller.rb` for the implementation this example is based on
768
+
769
+ ``` ruby
770
+ class ApplicationController < ActionController::Base
771
+ Forbidden = Class.new(StandardError)
772
+ private_constant :Forbidden
773
+ rescue_from Forbidden, with: :forbidden
774
+
775
+ Unauthorized = Class.new(StandardError)
776
+ private_constant :Unauthorized
777
+ rescue_from Unauthorized, with: :unauthorized
778
+
779
+ protect_from_forgery with: :exception
780
+ before_action :ensure_authenticated
781
+ after_action :ensure_access_checked
782
+
783
+ def subject
784
+ subject = session[:subject_id] && Subject.find_by(id: session[:subject_id])
785
+ return nil unless subject.try(:functioning?)
786
+ @subject = subject
787
+ end
788
+
789
+ protected
790
+
791
+ def ensure_authenticated
792
+ return force_authentication unless session[:subject_id]
793
+
794
+ @subject = Subject.find_by(id: session[:subject_id])
795
+ raise(Unauthorized, 'Subject invalid') unless @subject
796
+ raise(Unauthorized, 'Subject not functional') unless @subject.functioning?
797
+ end
798
+
799
+ def ensure_access_checked
800
+ return if @access_checked
801
+
802
+ method = "#{self.class.name}##{params[:action]}"
803
+ raise("No access control performed by #{method}")
804
+ end
805
+
806
+ def check_access!(action)
807
+ raise(Forbidden) unless subject.permits?(action)
808
+ @access_checked = true
809
+ end
810
+
811
+ def public_action
812
+ @access_checked = true
813
+ end
814
+
815
+ def unauthorized
816
+ reset_session
817
+ render 'errors/unauthorized', status: :unauthorized
818
+ end
819
+
820
+ def forbidden
821
+ render 'errors/forbidden', status: :forbidden
822
+ end
823
+
824
+ def force_authentication
825
+ session[:return_url] = request.url if request.get?
826
+
827
+ redirect_to('/auth/login')
828
+ end
829
+ end
830
+ ```
831
+
832
+ #### RSpec shared examples
833
+ ``` ruby
834
+ require 'rails_helper'
835
+
836
+ require 'gumboot/shared_examples/application_controller'
837
+
838
+ RSpec.describe ApplicationController, type: :controller do
839
+ include_examples 'Application controller'
840
+ end
841
+ ```
842
+
843
+ ## RESTful API
844
+
845
+ ### Versioning
846
+
847
+ All AAF API **must** be versioned by default.
848
+
849
+ Clients **must** supply an Accept header with all API requests. It must specify the version of API which the client is expecting to communicate with:
850
+
851
+ ```
852
+ Accept: application/vnd.aaf.<your application name>.vX+json
853
+ ```
854
+
855
+ For example a client communicating with the *example* application and using v1 of the API would be required to send:
856
+
857
+ ```
858
+ Accept: application/vnd.aaf.example.v1+json
859
+ ```
860
+
861
+ Change within an API version number will only be by extension, either with additional endpoints being made available or additional JSON being added to currently documented responses. Either of these changes should not impact well behaved clients that correctly parse and use JSON as intended. Clients should be advised of this expectation before receiving access.
862
+
863
+ ### Client Errors
864
+
865
+ There are three possible types of client errors on API calls that receive request bodies:
866
+
867
+ * Sending invalid JSON will result in a 400 Bad Request response.
868
+ * Sending the wrong type of JSON values will result in a 400 Bad Request response.
869
+ * Sending invalid fields will result in a 422 Unprocessable Entity response.
870
+ * Sending invalid credentials will result in a 401 unauthorised response.
871
+ * Sending requests to resources for which the Subject has no permission will result in a 403 forbidden response.
872
+
873
+ Response errors will contain JSON with the 'message' or 'errors' values specified to give more visibility into what went wrong.
874
+
875
+ ### Documenting resources
876
+
877
+ **TODO**
878
+
879
+ ### Responding to requests
880
+ To ensure all AAF API work the same a base controller for all API related controllers to extend from is recommended.
881
+
882
+ Having this controller live within an API module is recommended.
883
+
884
+ #### Controllers
885
+
886
+ ##### Rails 4.x
887
+ See `spec/dummy/app/controllers/api/api_controller.rb` for the implementation this example is based on
888
+
889
+ ```ruby
890
+ require 'openssl'
891
+
892
+ module API
893
+ class APIController < ActionController::Base
894
+ Forbidden = Class.new(StandardError)
895
+ private_constant :Forbidden
896
+ rescue_from Forbidden, with: :forbidden
897
+
898
+ Unauthorized = Class.new(StandardError)
899
+ private_constant :Unauthorized
900
+ rescue_from Unauthorized, with: :unauthorized
901
+
902
+ protect_from_forgery with: :null_session
903
+ before_action :ensure_authenticated
904
+ after_action :ensure_access_checked
905
+
906
+ attr_reader :subject
907
+
908
+ protected
909
+
910
+ def ensure_authenticated
911
+ # Ensure API subject exists and is functioning
912
+ @subject = APISubject.find_by(x509_cn: x509_cn)
913
+ raise(Unauthorized, 'Subject invalid') unless @subject
914
+ raise(Unauthorized, 'Subject not functional') unless @subject.functioning?
915
+ end
916
+
917
+ def ensure_access_checked
918
+ return if @access_checked
919
+
920
+ method = "#{self.class.name}##{params[:action]}"
921
+ raise("No access control performed by #{method}")
922
+ end
923
+
924
+ def x509_cn
925
+ # Verified DN pushed by nginx following successful client SSL verification
926
+ # nginx is always going to do a better job of terminating SSL then we can
927
+ raise(Unauthorized, 'Subject DN') if x509_dn.nil?
928
+
929
+ x509_dn_parsed = OpenSSL::X509::Name.parse(x509_dn)
930
+ x509_dn_hash = Hash[x509_dn_parsed.to_a
931
+ .map { |components| components[0..1] }]
932
+
933
+ x509_dn_hash['CN'] || raise(Unauthorized, 'Subject CN invalid')
934
+
935
+ rescue OpenSSL::X509::NameError
936
+ raise(Unauthorized, 'Subject DN invalid')
937
+ end
938
+
939
+ def x509_dn
940
+ x509_dn = request.headers['HTTP_X509_DN'].try(:force_encoding, 'UTF-8')
941
+ x509_dn == '(null)' ? nil : x509_dn
942
+ end
943
+
944
+ def check_access!(action)
945
+ raise(Forbidden) unless @subject.permits?(action)
946
+ @access_checked = true
947
+ end
948
+
949
+ def public_action
950
+ @access_checked = true
951
+ end
952
+
953
+ def unauthorized(exception)
954
+ message = 'SSL client failure.'
955
+ error = exception.message
956
+ render json: { message: message, error: error }, status: :unauthorized
957
+ end
958
+
959
+ def forbidden(_exception)
960
+ message = 'The request was understood but explicitly denied.'
961
+ render json: { message: message }, status: :forbidden
962
+ end
963
+ end
964
+ end
965
+ ```
966
+
967
+ #### RSpec shared examples
968
+ ``` ruby
969
+ require 'rails_helper'
970
+
971
+ require 'gumboot/shared_examples/api_controller'
972
+
973
+ RSpec.describe API::APIController, type: :controller do
974
+ include_examples 'API base controller'
975
+ end
976
+ ```
977
+
978
+ ### Routing requests
979
+ Routing to the appropriate controller for handling API requests **must** be undertaken using content within the Accept header.
980
+
981
+ #### Rails 4.x
982
+ Appropriate routing in a Rails 4.x application can be achieved as follows. Ensure you replace instances of *<your application name>* with something unique to the application i.e for the application named 'SAML service' we might use **`application/vnd.aaf.saml-service.v1+json`**
983
+
984
+ `lib/api_constraints.rb`
985
+
986
+ ```ruby
987
+ class APIConstraints
988
+ def initialize(version:, default: false)
989
+ @version = version
990
+ @default = default
991
+ end
992
+
993
+ def matches?(req)
994
+ @default || req.headers['Accept'].include?(version_string)
995
+ end
996
+
997
+ private
998
+
999
+ def version_string
1000
+ "application/vnd.aaf.<your application name>.v#{@version}+json"
1001
+ end
1002
+ end
1003
+ ```
1004
+
1005
+ `config/routes.rb`
1006
+
1007
+ ```ruby
1008
+ require 'api_constraints'
1009
+
1010
+ <Your Application>::Application.routes.draw do
1011
+
1012
+ namespace :api, defaults: { format: 'json' } do
1013
+ scope constraints: APIConstraints.new(version: 1, default: true) do
1014
+ resources :xyz, param: :uid, only: [:show, :create, :update, :destroy]
1015
+ end
1016
+ end
1017
+
1018
+ end
1019
+ ```
1020
+ This method has controllers living within the API::VX module and naturally extending the APIController documented above.
1021
+
1022
+ #### RSpec shared examples
1023
+ ``` ruby
1024
+ require 'rails_helper'
1025
+ require 'gumboot/shared_examples/api_constraints'
1026
+
1027
+ RSpec.describe APIConstraints do
1028
+ let(:matching_request) do
1029
+ headers = { 'Accept' => 'application/vnd.aaf.<your application name>.v1+json' }
1030
+ instance_double(ActionDispatch::Request, headers: headers)
1031
+ end
1032
+ let(:non_matching_request) do
1033
+ headers = { 'Accept' => 'application/vnd.aaf.<your application name>.v2+json' }
1034
+ instance_double(ActionDispatch::Request, headers: headers)
1035
+ end
1036
+
1037
+ include_examples 'API constraints'
1038
+ end
1039
+ ```
1040
+
1041
+ ## Event Handling
1042
+
1043
+ **TODO** - Publishing and consuming events from AAF SQS.
1044
+
1045
+ ## Continuous Integration
1046
+
1047
+ **TODO**
1048
+
1049
+ ``` ruby
1050
+ # frozen_string_literal: true
1051
+ begin
1052
+ require 'rubocop/rake_task'
1053
+ require 'brakeman'
1054
+ rescue LoadError
1055
+ :production
1056
+ end
1057
+
1058
+ require File.expand_path('../config/application', __FILE__)
1059
+
1060
+ Rails.application.load_tasks
1061
+
1062
+ RuboCop::RakeTask.new if defined? RuboCop
1063
+
1064
+ task :brakeman do
1065
+ Brakeman.run app_path: '.', print_report: true, exit_on_warn: true
1066
+ end
1067
+
1068
+ task default: [:rubocop, :spec, :brakeman]
1069
+ ```