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.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.rspec +3 -0
- data/.rubocop.yml +15 -0
- data/Gemfile +4 -0
- data/Guardfile +18 -0
- data/LICENSE +202 -0
- data/README.md +1069 -0
- data/Rakefile +8 -0
- data/aaf-gumboot.gemspec +42 -0
- data/lib/aaf-gumboot.rb +1 -0
- data/lib/gumboot.rb +5 -0
- data/lib/gumboot/shared_examples/anonymous_controller.rb +17 -0
- data/lib/gumboot/shared_examples/api_constraints.rb +29 -0
- data/lib/gumboot/shared_examples/api_controller.rb +206 -0
- data/lib/gumboot/shared_examples/api_subjects.rb +44 -0
- data/lib/gumboot/shared_examples/application_controller.rb +223 -0
- data/lib/gumboot/shared_examples/database_schema.rb +45 -0
- data/lib/gumboot/shared_examples/foreign_keys.rb +65 -0
- data/lib/gumboot/shared_examples/permissions.rb +45 -0
- data/lib/gumboot/shared_examples/roles.rb +15 -0
- data/lib/gumboot/shared_examples/subjects.rb +29 -0
- data/lib/gumboot/strap.rb +121 -0
- data/lib/gumboot/version.rb +3 -0
- data/spec/dummy/README.rdoc +28 -0
- data/spec/dummy/Rakefile +3 -0
- data/spec/dummy/app/assets/images/.keep +0 -0
- data/spec/dummy/app/assets/javascripts/application.js +13 -0
- data/spec/dummy/app/assets/stylesheets/application.css +15 -0
- data/spec/dummy/app/controllers/api/api_controller.rb +78 -0
- data/spec/dummy/app/controllers/application_controller.rb +64 -0
- data/spec/dummy/app/controllers/concerns/.keep +0 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/mailers/.keep +0 -0
- data/spec/dummy/app/models/.keep +0 -0
- data/spec/dummy/app/models/api_subject.rb +23 -0
- data/spec/dummy/app/models/api_subject_role.rb +6 -0
- data/spec/dummy/app/models/concerns/.keep +0 -0
- data/spec/dummy/app/models/permission.rb +7 -0
- data/spec/dummy/app/models/role.rb +11 -0
- data/spec/dummy/app/models/subject.rb +20 -0
- data/spec/dummy/app/models/subject_role.rb +6 -0
- data/spec/dummy/app/views/dynamic_errors/forbidden.html.erb +0 -0
- data/spec/dummy/app/views/dynamic_errors/unauthorized.html.erb +0 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/bin/bundle +3 -0
- data/spec/dummy/bin/rails +4 -0
- data/spec/dummy/bin/rake +4 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/config/application.rb +18 -0
- data/spec/dummy/config/boot.rb +5 -0
- data/spec/dummy/config/database.yml +5 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +32 -0
- data/spec/dummy/config/environments/production.rb +37 -0
- data/spec/dummy/config/environments/test.rb +33 -0
- data/spec/dummy/config/initializers/assets.rb +4 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +0 -0
- data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/dummy/config/initializers/inflections.rb +15 -0
- data/spec/dummy/config/initializers/mime_types.rb +4 -0
- data/spec/dummy/config/initializers/session_store.rb +3 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +9 -0
- data/spec/dummy/config/locales/en.yml +23 -0
- data/spec/dummy/config/routes.rb +2 -0
- data/spec/dummy/config/secrets.yml +22 -0
- data/spec/dummy/db/schema.rb +51 -0
- data/spec/dummy/db/test.sqlite3 +0 -0
- data/spec/dummy/lib/api_constraints.rb +16 -0
- data/spec/dummy/lib/assets/.keep +0 -0
- data/spec/dummy/public/404.html +67 -0
- data/spec/dummy/public/422.html +67 -0
- data/spec/dummy/public/500.html +66 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/factories/api_subjects.rb +20 -0
- data/spec/factories/permissions.rb +6 -0
- data/spec/factories/roles.rb +5 -0
- data/spec/factories/subjects.rb +24 -0
- data/spec/gumboot/api_constraints_spec.rb +18 -0
- data/spec/gumboot/api_controller_spec.rb +7 -0
- data/spec/gumboot/api_subjects_spec.rb +7 -0
- data/spec/gumboot/application_controller_spec.rb +7 -0
- data/spec/gumboot/foreign_keys_spec.rb +7 -0
- data/spec/gumboot/permissions_spec.rb +7 -0
- data/spec/gumboot/roles_spec.rb +7 -0
- data/spec/gumboot/subjects_spec.rb +7 -0
- data/spec/lib/gumboot/strap_spec.rb +330 -0
- data/spec/spec_helper.rb +45 -0
- 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
data/.rspec
ADDED
data/.rubocop.yml
ADDED
data/Gemfile
ADDED
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
|
+
```
|