scim_engine 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/Rakefile +25 -0
  4. data/app/controllers/scim_engine/application_controller.rb +36 -0
  5. data/app/controllers/scim_engine/resource_types_controller.rb +22 -0
  6. data/app/controllers/scim_engine/resources_controller.rb +71 -0
  7. data/app/controllers/scim_engine/schemas_controller.rb +16 -0
  8. data/app/controllers/scim_engine/service_provider_configurations_controller.rb +8 -0
  9. data/app/models/scim_engine/authentication_error.rb +9 -0
  10. data/app/models/scim_engine/authentication_scheme.rb +12 -0
  11. data/app/models/scim_engine/bulk.rb +5 -0
  12. data/app/models/scim_engine/complex_types/base.rb +37 -0
  13. data/app/models/scim_engine/complex_types/email.rb +14 -0
  14. data/app/models/scim_engine/complex_types/name.rb +9 -0
  15. data/app/models/scim_engine/complex_types/reference.rb +9 -0
  16. data/app/models/scim_engine/error_response.rb +13 -0
  17. data/app/models/scim_engine/errors.rb +14 -0
  18. data/app/models/scim_engine/filter.rb +5 -0
  19. data/app/models/scim_engine/meta.rb +7 -0
  20. data/app/models/scim_engine/not_found_error.rb +10 -0
  21. data/app/models/scim_engine/resource_invalid_error.rb +9 -0
  22. data/app/models/scim_engine/resource_type.rb +29 -0
  23. data/app/models/scim_engine/resources/base.rb +139 -0
  24. data/app/models/scim_engine/resources/group.rb +13 -0
  25. data/app/models/scim_engine/resources/user.rb +13 -0
  26. data/app/models/scim_engine/schema/attribute.rb +91 -0
  27. data/app/models/scim_engine/schema/base.rb +35 -0
  28. data/app/models/scim_engine/schema/derived_attributes.rb +24 -0
  29. data/app/models/scim_engine/schema/email.rb +15 -0
  30. data/app/models/scim_engine/schema/group.rb +27 -0
  31. data/app/models/scim_engine/schema/name.rb +17 -0
  32. data/app/models/scim_engine/schema/reference.rb +14 -0
  33. data/app/models/scim_engine/schema/user.rb +30 -0
  34. data/app/models/scim_engine/service_provider_configuration.rb +31 -0
  35. data/app/models/scim_engine/supportable.rb +10 -0
  36. data/app/views/layouts/scim_engine/application.html.erb +14 -0
  37. data/config/routes.rb +6 -0
  38. data/lib/scim_engine.rb +13 -0
  39. data/lib/scim_engine/engine.rb +51 -0
  40. data/lib/scim_engine/version.rb +3 -0
  41. data/lib/tasks/scim_engine_tasks.rake +4 -0
  42. data/spec/controllers/scim_engine/application_controller_spec.rb +104 -0
  43. data/spec/controllers/scim_engine/resource_types_controller_spec.rb +78 -0
  44. data/spec/controllers/scim_engine/resources_controller_spec.rb +132 -0
  45. data/spec/controllers/scim_engine/schemas_controller_spec.rb +66 -0
  46. data/spec/controllers/scim_engine/service_provider_configurations_controller_spec.rb +21 -0
  47. data/spec/dummy/README.rdoc +28 -0
  48. data/spec/dummy/Rakefile +6 -0
  49. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  50. data/spec/dummy/app/assets/stylesheets/application.css +15 -0
  51. data/spec/dummy/app/controllers/application_controller.rb +5 -0
  52. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  53. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  54. data/spec/dummy/bin/bundle +3 -0
  55. data/spec/dummy/bin/rails +4 -0
  56. data/spec/dummy/bin/rake +4 -0
  57. data/spec/dummy/bin/setup +29 -0
  58. data/spec/dummy/config.ru +4 -0
  59. data/spec/dummy/config/application.rb +16 -0
  60. data/spec/dummy/config/boot.rb +5 -0
  61. data/spec/dummy/config/environment.rb +5 -0
  62. data/spec/dummy/config/environments/development.rb +41 -0
  63. data/spec/dummy/config/environments/production.rb +79 -0
  64. data/spec/dummy/config/environments/test.rb +42 -0
  65. data/spec/dummy/config/initializers/assets.rb +11 -0
  66. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  67. data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
  68. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  69. data/spec/dummy/config/initializers/inflections.rb +16 -0
  70. data/spec/dummy/config/initializers/mime_types.rb +4 -0
  71. data/spec/dummy/config/initializers/session_store.rb +3 -0
  72. data/spec/dummy/config/initializers/to_time_preserves_timezone.rb +10 -0
  73. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  74. data/spec/dummy/config/locales/en.yml +23 -0
  75. data/spec/dummy/config/routes.rb +4 -0
  76. data/spec/dummy/config/secrets.yml +22 -0
  77. data/spec/dummy/log/test.log +175 -0
  78. data/spec/dummy/public/404.html +67 -0
  79. data/spec/dummy/public/422.html +67 -0
  80. data/spec/dummy/public/500.html +66 -0
  81. data/spec/dummy/public/favicon.ico +0 -0
  82. data/spec/models/scim_engine/complex_types/email_spec.rb +23 -0
  83. data/spec/models/scim_engine/resource_type_spec.rb +21 -0
  84. data/spec/models/scim_engine/resources/base_spec.rb +246 -0
  85. data/spec/models/scim_engine/resources/base_validation_spec.rb +61 -0
  86. data/spec/models/scim_engine/resources/user_spec.rb +55 -0
  87. data/spec/models/scim_engine/schema/attribute_spec.rb +80 -0
  88. data/spec/models/scim_engine/schema/base_spec.rb +19 -0
  89. data/spec/models/scim_engine/schema/group_spec.rb +66 -0
  90. data/spec/models/scim_engine/schema/user_spec.rb +140 -0
  91. data/spec/rails_helper.rb +57 -0
  92. data/spec/spec_helper.rb +99 -0
  93. metadata +246 -0
@@ -0,0 +1,67 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>The page you were looking for doesn't exist (404)</title>
5
+ <meta name="viewport" content="width=device-width,initial-scale=1">
6
+ <style>
7
+ body {
8
+ background-color: #EFEFEF;
9
+ color: #2E2F30;
10
+ text-align: center;
11
+ font-family: arial, sans-serif;
12
+ margin: 0;
13
+ }
14
+
15
+ div.dialog {
16
+ width: 95%;
17
+ max-width: 33em;
18
+ margin: 4em auto 0;
19
+ }
20
+
21
+ div.dialog > div {
22
+ border: 1px solid #CCC;
23
+ border-right-color: #999;
24
+ border-left-color: #999;
25
+ border-bottom-color: #BBB;
26
+ border-top: #B00100 solid 4px;
27
+ border-top-left-radius: 9px;
28
+ border-top-right-radius: 9px;
29
+ background-color: white;
30
+ padding: 7px 12% 0;
31
+ box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
32
+ }
33
+
34
+ h1 {
35
+ font-size: 100%;
36
+ color: #730E15;
37
+ line-height: 1.5em;
38
+ }
39
+
40
+ div.dialog > p {
41
+ margin: 0 0 1em;
42
+ padding: 1em;
43
+ background-color: #F7F7F7;
44
+ border: 1px solid #CCC;
45
+ border-right-color: #999;
46
+ border-left-color: #999;
47
+ border-bottom-color: #999;
48
+ border-bottom-left-radius: 4px;
49
+ border-bottom-right-radius: 4px;
50
+ border-top-color: #DADADA;
51
+ color: #666;
52
+ box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
53
+ }
54
+ </style>
55
+ </head>
56
+
57
+ <body>
58
+ <!-- This file lives in public/404.html -->
59
+ <div class="dialog">
60
+ <div>
61
+ <h1>The page you were looking for doesn't exist.</h1>
62
+ <p>You may have mistyped the address or the page may have moved.</p>
63
+ </div>
64
+ <p>If you are the application owner check the logs for more information.</p>
65
+ </div>
66
+ </body>
67
+ </html>
@@ -0,0 +1,67 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>The change you wanted was rejected (422)</title>
5
+ <meta name="viewport" content="width=device-width,initial-scale=1">
6
+ <style>
7
+ body {
8
+ background-color: #EFEFEF;
9
+ color: #2E2F30;
10
+ text-align: center;
11
+ font-family: arial, sans-serif;
12
+ margin: 0;
13
+ }
14
+
15
+ div.dialog {
16
+ width: 95%;
17
+ max-width: 33em;
18
+ margin: 4em auto 0;
19
+ }
20
+
21
+ div.dialog > div {
22
+ border: 1px solid #CCC;
23
+ border-right-color: #999;
24
+ border-left-color: #999;
25
+ border-bottom-color: #BBB;
26
+ border-top: #B00100 solid 4px;
27
+ border-top-left-radius: 9px;
28
+ border-top-right-radius: 9px;
29
+ background-color: white;
30
+ padding: 7px 12% 0;
31
+ box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
32
+ }
33
+
34
+ h1 {
35
+ font-size: 100%;
36
+ color: #730E15;
37
+ line-height: 1.5em;
38
+ }
39
+
40
+ div.dialog > p {
41
+ margin: 0 0 1em;
42
+ padding: 1em;
43
+ background-color: #F7F7F7;
44
+ border: 1px solid #CCC;
45
+ border-right-color: #999;
46
+ border-left-color: #999;
47
+ border-bottom-color: #999;
48
+ border-bottom-left-radius: 4px;
49
+ border-bottom-right-radius: 4px;
50
+ border-top-color: #DADADA;
51
+ color: #666;
52
+ box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
53
+ }
54
+ </style>
55
+ </head>
56
+
57
+ <body>
58
+ <!-- This file lives in public/422.html -->
59
+ <div class="dialog">
60
+ <div>
61
+ <h1>The change you wanted was rejected.</h1>
62
+ <p>Maybe you tried to change something you didn't have access to.</p>
63
+ </div>
64
+ <p>If you are the application owner check the logs for more information.</p>
65
+ </div>
66
+ </body>
67
+ </html>
@@ -0,0 +1,66 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>We're sorry, but something went wrong (500)</title>
5
+ <meta name="viewport" content="width=device-width,initial-scale=1">
6
+ <style>
7
+ body {
8
+ background-color: #EFEFEF;
9
+ color: #2E2F30;
10
+ text-align: center;
11
+ font-family: arial, sans-serif;
12
+ margin: 0;
13
+ }
14
+
15
+ div.dialog {
16
+ width: 95%;
17
+ max-width: 33em;
18
+ margin: 4em auto 0;
19
+ }
20
+
21
+ div.dialog > div {
22
+ border: 1px solid #CCC;
23
+ border-right-color: #999;
24
+ border-left-color: #999;
25
+ border-bottom-color: #BBB;
26
+ border-top: #B00100 solid 4px;
27
+ border-top-left-radius: 9px;
28
+ border-top-right-radius: 9px;
29
+ background-color: white;
30
+ padding: 7px 12% 0;
31
+ box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
32
+ }
33
+
34
+ h1 {
35
+ font-size: 100%;
36
+ color: #730E15;
37
+ line-height: 1.5em;
38
+ }
39
+
40
+ div.dialog > p {
41
+ margin: 0 0 1em;
42
+ padding: 1em;
43
+ background-color: #F7F7F7;
44
+ border: 1px solid #CCC;
45
+ border-right-color: #999;
46
+ border-left-color: #999;
47
+ border-bottom-color: #999;
48
+ border-bottom-left-radius: 4px;
49
+ border-bottom-right-radius: 4px;
50
+ border-top-color: #DADADA;
51
+ color: #666;
52
+ box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
53
+ }
54
+ </style>
55
+ </head>
56
+
57
+ <body>
58
+ <!-- This file lives in public/500.html -->
59
+ <div class="dialog">
60
+ <div>
61
+ <h1>We're sorry, but something went wrong.</h1>
62
+ </div>
63
+ <p>If you are the application owner check the logs for more information.</p>
64
+ </div>
65
+ </body>
66
+ </html>
File without changes
@@ -0,0 +1,23 @@
1
+ require 'rails_helper'
2
+
3
+ describe ScimEngine::ComplexTypes::Email do
4
+ describe '#as_json' do
5
+ it 'adds work and primary as default' do
6
+ expect(described_class.new.as_json).to eq('type' => 'work', 'primary' => true)
7
+ end
8
+
9
+ it 'allows a custom email type' do
10
+ expect(described_class.new(type: 'home').as_json).to eq('type' => 'home', 'primary' => true)
11
+ end
12
+
13
+ it 'allows a non-primary email' do
14
+ expect(described_class.new(primary: false).as_json).to eq('type' => 'work', 'primary' => false)
15
+ end
16
+
17
+ it 'shows the set email' do
18
+ expect(described_class.new(value: 'a@b.c').as_json).to eq('value' => 'a@b.c', 'type' => 'work', 'primary' => true)
19
+ end
20
+ end
21
+
22
+ end
23
+
@@ -0,0 +1,21 @@
1
+ require 'rails_helper'
2
+
3
+ describe ScimEngine::ResourceType do
4
+ describe '#as_json' do
5
+
6
+ it 'adds the extensionSchemas' do
7
+ resource_type = ScimEngine::ResourceType.new(
8
+ endpoint: '/Gaga',
9
+ schema: 'urn:ietf:params:scim:schemas:core:2.0:User',
10
+ schemaExtensions: ['urn:ietf:params:scim:schemas:extension:enterprise:2.0:User']
11
+ )
12
+
13
+ expect(resource_type.as_json['schemaExtensions']).to eql([{
14
+ "schema" => 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User',
15
+ "required" => false
16
+ }])
17
+
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,246 @@
1
+ require 'rails_helper'
2
+
3
+ describe ScimEngine::Resources::Base do
4
+
5
+ CustomSchema = Class.new(ScimEngine::Schema::Base) do
6
+
7
+ def self.id
8
+ 'custom-id'
9
+ end
10
+
11
+ def self.scim_attributes
12
+ [
13
+ ScimEngine::Schema::Attribute.new(
14
+ name: 'name', complexType: ScimEngine::ComplexTypes::Name, required: false
15
+ ),
16
+ ScimEngine::Schema::Attribute.new(
17
+ name: 'names', multiValued: true, complexType: ScimEngine::ComplexTypes::Name, required: false
18
+ )
19
+ ]
20
+ end
21
+ end
22
+
23
+ CustomResourse = Class.new(ScimEngine::Resources::Base) do
24
+ set_schema CustomSchema
25
+ end
26
+
27
+ describe '#initialize' do
28
+ it 'builds the nested type' do
29
+ resource = CustomResourse.new(name: {
30
+ givenName: 'John',
31
+ familyName: 'Smith'
32
+ })
33
+
34
+ expect(resource.name.is_a?(ScimEngine::ComplexTypes::Name)).to be(true)
35
+ expect(resource.name.givenName).to eql('John')
36
+ expect(resource.name.familyName).to eql('Smith')
37
+ end
38
+
39
+ it 'builds an array of nested resources' do
40
+ resource = CustomResourse.new(names: [
41
+ {
42
+ givenName: 'John',
43
+ familyName: 'Smith'
44
+ },
45
+ {
46
+ givenName: 'Jane',
47
+ familyName: 'Snow'
48
+ }
49
+ ])
50
+
51
+ expect(resource.names.is_a?(Array)).to be(true)
52
+ expect(resource.names.first.is_a?(ScimEngine::ComplexTypes::Name)).to be(true)
53
+ expect(resource.names.first.givenName).to eql('John')
54
+ expect(resource.names.first.familyName).to eql('Smith')
55
+ expect(resource.names.second.is_a?(ScimEngine::ComplexTypes::Name)).to be(true)
56
+ expect(resource.names.second.givenName).to eql('Jane')
57
+ expect(resource.names.second.familyName).to eql('Snow')
58
+ expect(resource.valid?).to be(true)
59
+ end
60
+
61
+ it 'builds an array of nested resources which is invalid if the hash does not follow the schema of the complex type' do
62
+ resource = CustomResourse.new(names: [
63
+ {
64
+ givenName: 'John',
65
+ familyName: 123
66
+ }
67
+ ])
68
+
69
+ expect(resource.names.is_a?(Array)).to be(true)
70
+ expect(resource.names.first.is_a?(ScimEngine::ComplexTypes::Name)).to be(true)
71
+ expect(resource.names.first.givenName).to eql('John')
72
+ expect(resource.names.first.familyName).to eql(123)
73
+ expect(resource.valid?).to be(false)
74
+ end
75
+
76
+ end
77
+
78
+ describe '#as_json' do
79
+ it 'renders the json with the resourceType' do
80
+ resource = CustomResourse.new(name: {
81
+ givenName: 'John',
82
+ familyName: 'Smith'
83
+ })
84
+
85
+ result = resource.as_json
86
+ expect(result['schemas']).to eql(['custom-id'])
87
+ expect(result['meta']['resourceType']).to eql('CustomResourse')
88
+ expect(result['errors']).to be_nil
89
+ end
90
+ end
91
+
92
+ describe 'dynamic setters based on schema' do
93
+
94
+ CustomSchema = Class.new(ScimEngine::Schema::Base) do
95
+ def self.scim_attributes
96
+ [
97
+ ScimEngine::Schema::Attribute.new(name: 'customField', type: 'string', required: false),
98
+ ScimEngine::Schema::Attribute.new(name: 'anotherCustomField', type: 'boolean', required: false),
99
+ ScimEngine::Schema::Attribute.new(name: 'name', complexType: ScimEngine::ComplexTypes::Name, required: false)
100
+ ]
101
+ end
102
+ end
103
+
104
+ CustomNameType = Class.new(ScimEngine::ComplexTypes::Base) do
105
+ set_schema ScimEngine::Schema::Name
106
+ end
107
+
108
+ it 'defines a setter for an attribute in the schema' do
109
+ described_class.set_schema CustomSchema
110
+ resource = described_class.new(customField: '100',
111
+ anotherCustomField: true)
112
+ expect(resource.customField).to eql('100')
113
+ expect(resource.anotherCustomField).to eql(true)
114
+ expect(resource.valid?).to be(true)
115
+ end
116
+
117
+ it 'defines a setter for an attribute in the schema' do
118
+ described_class.set_schema CustomSchema
119
+ resource = described_class.new(anotherCustomField: false)
120
+ expect(resource.anotherCustomField).to eql(false)
121
+ expect(resource.valid?).to be(true)
122
+ end
123
+
124
+
125
+ it 'validates that the provided attributes match their schema' do
126
+ described_class.set_schema CustomSchema
127
+ resource = described_class.new(
128
+ name: ScimEngine::ComplexTypes::Name.new(
129
+ givenName: 'John',
130
+ familyName: 'Smith'
131
+ ))
132
+ expect(resource.valid?).to be(true)
133
+ end
134
+
135
+ it 'validates that nested types' do
136
+ described_class.set_schema CustomSchema
137
+ resource = described_class.new(
138
+ name: ScimEngine::ComplexTypes::Name.new(
139
+ givenName: 100,
140
+ familyName: 'Smith'
141
+ ))
142
+ expect(resource.valid?).to be(false)
143
+ end
144
+
145
+
146
+ it 'allows custom complex types as long as the schema matches' do
147
+ described_class.set_schema CustomSchema
148
+ resource = described_class.new(
149
+ name: CustomNameType.new(
150
+ givenName: 'John',
151
+ familyName: 'Smith'
152
+ ))
153
+ expect(resource.valid?).to be(true)
154
+ end
155
+
156
+
157
+ it 'doesnt accept email for a name' do
158
+ described_class.set_schema CustomSchema
159
+ resource = described_class.new(
160
+ name: ScimEngine::ComplexTypes::Email.new(
161
+ value: 'john@smith.com',
162
+ primary: true
163
+ ))
164
+ expect(resource.valid?).to be(false)
165
+ end
166
+
167
+ it 'doesnt accept a complex type for a string' do
168
+ described_class.set_schema CustomSchema
169
+ resource = described_class.new(
170
+ customField: ScimEngine::ComplexTypes::Email.new(
171
+ value: 'john@smith.com',
172
+ primary: true
173
+ ))
174
+ expect(resource.valid?).to be(false)
175
+ end
176
+
177
+ it 'doesnt accept a string for a boolean' do
178
+ described_class.set_schema CustomSchema
179
+ resource = described_class.new(anotherCustomField: 'value')
180
+ expect(resource.valid?).to be(false)
181
+ end
182
+
183
+ end
184
+
185
+ context 'schema extension' do
186
+ customSchema = Class.new(ScimEngine::Schema::Base) do
187
+ def self.id
188
+ 'custom-id'
189
+ end
190
+
191
+ def self.scim_attributes
192
+ [ ScimEngine::Schema::Attribute.new(name: 'name', type: 'string') ]
193
+ end
194
+ end
195
+
196
+ extensionSchema = Class.new(ScimEngine::Schema::Base) do
197
+ def self.id
198
+ 'extension-id'
199
+ end
200
+
201
+ def self.scim_attributes
202
+ [ ScimEngine::Schema::Attribute.new(name: 'relationship', type: 'string') ]
203
+ end
204
+ end
205
+
206
+ let(:resource_class) {
207
+ Class.new(ScimEngine::Resources::Base) do
208
+ set_schema customSchema
209
+ extend_schema extensionSchema
210
+
211
+
212
+ def self.endpoint
213
+ '/gaga'
214
+ end
215
+ def self.resource_type_id
216
+ 'CustomResource'
217
+ end
218
+ end
219
+ }
220
+
221
+ describe '#initialize' do
222
+ it 'allows setting extension attributes' do
223
+ resource = resource_class.new('extension-id' => {relationship: 'GAGA'})
224
+ expect(resource.relationship).to eql('GAGA')
225
+ end
226
+ end
227
+
228
+ describe '#as_json' do
229
+ it 'namespaces the extension attributes' do
230
+ resource = resource_class.new(relationship: 'GAGA')
231
+ hash = resource.as_json
232
+ expect(hash["schemas"]).to eql(['custom-id', 'extension-id'])
233
+ expect(hash["extension-id"]).to eql("relationship" => 'GAGA')
234
+ end
235
+ end
236
+
237
+ describe '.resource_type' do
238
+ it 'appends the extension schemas' do
239
+ resource_type = resource_class.resource_type('http://gaga')
240
+ expect(resource_type.meta.location).to eql('http://gaga')
241
+ expect(resource_type.schemaExtensions.count).to eql(1)
242
+ end
243
+ end
244
+
245
+ end
246
+ end