powerhome-scimitar 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (116) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +21 -0
  3. data/README.md +708 -0
  4. data/Rakefile +16 -0
  5. data/app/controllers/scimitar/active_record_backed_resources_controller.rb +257 -0
  6. data/app/controllers/scimitar/application_controller.rb +157 -0
  7. data/app/controllers/scimitar/resource_types_controller.rb +28 -0
  8. data/app/controllers/scimitar/resources_controller.rb +203 -0
  9. data/app/controllers/scimitar/schemas_controller.rb +21 -0
  10. data/app/controllers/scimitar/service_provider_configurations_controller.rb +8 -0
  11. data/app/models/scimitar/authentication_error.rb +9 -0
  12. data/app/models/scimitar/authentication_scheme.rb +18 -0
  13. data/app/models/scimitar/bulk.rb +8 -0
  14. data/app/models/scimitar/complex_types/address.rb +12 -0
  15. data/app/models/scimitar/complex_types/base.rb +83 -0
  16. data/app/models/scimitar/complex_types/email.rb +12 -0
  17. data/app/models/scimitar/complex_types/entitlement.rb +12 -0
  18. data/app/models/scimitar/complex_types/ims.rb +12 -0
  19. data/app/models/scimitar/complex_types/name.rb +12 -0
  20. data/app/models/scimitar/complex_types/phone_number.rb +12 -0
  21. data/app/models/scimitar/complex_types/photo.rb +12 -0
  22. data/app/models/scimitar/complex_types/reference_group.rb +12 -0
  23. data/app/models/scimitar/complex_types/reference_member.rb +12 -0
  24. data/app/models/scimitar/complex_types/role.rb +12 -0
  25. data/app/models/scimitar/complex_types/x509_certificate.rb +12 -0
  26. data/app/models/scimitar/engine_configuration.rb +32 -0
  27. data/app/models/scimitar/error_response.rb +32 -0
  28. data/app/models/scimitar/errors.rb +14 -0
  29. data/app/models/scimitar/filter.rb +11 -0
  30. data/app/models/scimitar/filter_error.rb +22 -0
  31. data/app/models/scimitar/invalid_syntax_error.rb +9 -0
  32. data/app/models/scimitar/lists/count.rb +64 -0
  33. data/app/models/scimitar/lists/query_parser.rb +745 -0
  34. data/app/models/scimitar/meta.rb +7 -0
  35. data/app/models/scimitar/not_found_error.rb +10 -0
  36. data/app/models/scimitar/resource_invalid_error.rb +9 -0
  37. data/app/models/scimitar/resource_type.rb +29 -0
  38. data/app/models/scimitar/resources/base.rb +190 -0
  39. data/app/models/scimitar/resources/group.rb +13 -0
  40. data/app/models/scimitar/resources/mixin.rb +1524 -0
  41. data/app/models/scimitar/resources/user.rb +13 -0
  42. data/app/models/scimitar/schema/address.rb +25 -0
  43. data/app/models/scimitar/schema/attribute.rb +132 -0
  44. data/app/models/scimitar/schema/base.rb +90 -0
  45. data/app/models/scimitar/schema/derived_attributes.rb +24 -0
  46. data/app/models/scimitar/schema/email.rb +10 -0
  47. data/app/models/scimitar/schema/entitlement.rb +10 -0
  48. data/app/models/scimitar/schema/group.rb +27 -0
  49. data/app/models/scimitar/schema/ims.rb +10 -0
  50. data/app/models/scimitar/schema/name.rb +20 -0
  51. data/app/models/scimitar/schema/phone_number.rb +10 -0
  52. data/app/models/scimitar/schema/photo.rb +10 -0
  53. data/app/models/scimitar/schema/reference_group.rb +23 -0
  54. data/app/models/scimitar/schema/reference_member.rb +21 -0
  55. data/app/models/scimitar/schema/role.rb +10 -0
  56. data/app/models/scimitar/schema/user.rb +52 -0
  57. data/app/models/scimitar/schema/vdtp.rb +18 -0
  58. data/app/models/scimitar/schema/x509_certificate.rb +22 -0
  59. data/app/models/scimitar/service_provider_configuration.rb +60 -0
  60. data/app/models/scimitar/supportable.rb +14 -0
  61. data/app/views/layouts/scimitar/application.html.erb +14 -0
  62. data/config/initializers/scimitar.rb +111 -0
  63. data/config/routes.rb +6 -0
  64. data/lib/scimitar/engine.rb +63 -0
  65. data/lib/scimitar/support/hash_with_indifferent_case_insensitive_access.rb +216 -0
  66. data/lib/scimitar/support/utilities.rb +51 -0
  67. data/lib/scimitar/version.rb +13 -0
  68. data/lib/scimitar.rb +29 -0
  69. data/spec/apps/dummy/app/controllers/custom_create_mock_users_controller.rb +25 -0
  70. data/spec/apps/dummy/app/controllers/custom_destroy_mock_users_controller.rb +24 -0
  71. data/spec/apps/dummy/app/controllers/custom_replace_mock_users_controller.rb +25 -0
  72. data/spec/apps/dummy/app/controllers/custom_request_verifiers_controller.rb +30 -0
  73. data/spec/apps/dummy/app/controllers/custom_save_mock_users_controller.rb +24 -0
  74. data/spec/apps/dummy/app/controllers/custom_update_mock_users_controller.rb +25 -0
  75. data/spec/apps/dummy/app/controllers/mock_groups_controller.rb +13 -0
  76. data/spec/apps/dummy/app/controllers/mock_users_controller.rb +13 -0
  77. data/spec/apps/dummy/app/models/mock_group.rb +83 -0
  78. data/spec/apps/dummy/app/models/mock_user.rb +132 -0
  79. data/spec/apps/dummy/config/application.rb +18 -0
  80. data/spec/apps/dummy/config/boot.rb +2 -0
  81. data/spec/apps/dummy/config/environment.rb +2 -0
  82. data/spec/apps/dummy/config/environments/test.rb +38 -0
  83. data/spec/apps/dummy/config/initializers/cookies_serializer.rb +3 -0
  84. data/spec/apps/dummy/config/initializers/scimitar.rb +61 -0
  85. data/spec/apps/dummy/config/initializers/session_store.rb +3 -0
  86. data/spec/apps/dummy/config/routes.rb +45 -0
  87. data/spec/apps/dummy/db/migrate/20210304014602_create_mock_users.rb +24 -0
  88. data/spec/apps/dummy/db/migrate/20210308020313_create_mock_groups.rb +10 -0
  89. data/spec/apps/dummy/db/migrate/20210308044214_create_join_table_mock_groups_mock_users.rb +13 -0
  90. data/spec/apps/dummy/db/schema.rb +48 -0
  91. data/spec/controllers/scimitar/application_controller_spec.rb +296 -0
  92. data/spec/controllers/scimitar/resource_types_controller_spec.rb +94 -0
  93. data/spec/controllers/scimitar/resources_controller_spec.rb +247 -0
  94. data/spec/controllers/scimitar/schemas_controller_spec.rb +83 -0
  95. data/spec/controllers/scimitar/service_provider_configurations_controller_spec.rb +22 -0
  96. data/spec/models/scimitar/complex_types/address_spec.rb +18 -0
  97. data/spec/models/scimitar/complex_types/email_spec.rb +21 -0
  98. data/spec/models/scimitar/lists/count_spec.rb +147 -0
  99. data/spec/models/scimitar/lists/query_parser_spec.rb +830 -0
  100. data/spec/models/scimitar/resource_type_spec.rb +21 -0
  101. data/spec/models/scimitar/resources/base_spec.rb +485 -0
  102. data/spec/models/scimitar/resources/base_validation_spec.rb +86 -0
  103. data/spec/models/scimitar/resources/mixin_spec.rb +3562 -0
  104. data/spec/models/scimitar/resources/user_spec.rb +68 -0
  105. data/spec/models/scimitar/schema/attribute_spec.rb +99 -0
  106. data/spec/models/scimitar/schema/base_spec.rb +64 -0
  107. data/spec/models/scimitar/schema/group_spec.rb +87 -0
  108. data/spec/models/scimitar/schema/user_spec.rb +720 -0
  109. data/spec/requests/active_record_backed_resources_controller_spec.rb +1354 -0
  110. data/spec/requests/application_controller_spec.rb +61 -0
  111. data/spec/requests/controller_configuration_spec.rb +17 -0
  112. data/spec/requests/engine_spec.rb +45 -0
  113. data/spec/spec_helper.rb +101 -0
  114. data/spec/spec_helper_spec.rb +30 -0
  115. data/spec/support/hash_with_indifferent_case_insensitive_access_spec.rb +169 -0
  116. metadata +321 -0
@@ -0,0 +1,7 @@
1
+ module Scimitar
2
+ class Meta
3
+ include ActiveModel::Model
4
+
5
+ attr_accessor :resourceType, :created, :lastModified, :location, :version
6
+ end
7
+ end
@@ -0,0 +1,10 @@
1
+ module Scimitar
2
+
3
+ class NotFoundError < ErrorResponse
4
+
5
+ def initialize(id)
6
+ super(status: 404, detail: "Resource #{id.inspect} not found")
7
+ end
8
+
9
+ end
10
+ end
@@ -0,0 +1,9 @@
1
+ module Scimitar
2
+ class ResourceInvalidError < ErrorResponse
3
+
4
+ def initialize(error_message)
5
+ super(status: 400, scimType: 'invalidValue', detail: "Operation failed since record has become invalid: #{error_message}")
6
+ end
7
+
8
+ end
9
+ end
@@ -0,0 +1,29 @@
1
+ module Scimitar
2
+ # Provides info about a resource type. Instances of this class are used to provide info through the /ResourceTypes endpoint of a SCIM service provider.
3
+ class ResourceType
4
+ include ActiveModel::Model
5
+ attr_accessor :meta, :endpoint, :schema, :schemas, :id, :name, :schemaExtensions
6
+
7
+ def initialize(attributes = {})
8
+ default_attributes = {
9
+ meta: Meta.new(
10
+ 'resourceType': 'ResourceType'
11
+ ),
12
+ schemas: ['urn:ietf:params:scim:schemas:core:2.0:ResourceType']
13
+ }
14
+ super(default_attributes.merge(attributes))
15
+ end
16
+
17
+
18
+ def as_json(options = {})
19
+ without_extensions = super(except: 'schemaExtensions')
20
+ if schemaExtensions.present?
21
+ extensions = schemaExtensions.map{|extension| {"schema" => extension, "required" => false}}
22
+ without_extensions.merge('schemaExtensions' => extensions)
23
+ else
24
+ without_extensions
25
+ end
26
+ end
27
+
28
+ end
29
+ end
@@ -0,0 +1,190 @@
1
+ module Scimitar
2
+ module Resources
3
+ # The base class for all SCIM resources.
4
+ class Base
5
+ include ActiveModel::Model
6
+ include Scimitar::Schema::DerivedAttributes
7
+ include Scimitar::Errors
8
+
9
+ attr_accessor :id, :externalId, :meta
10
+ attr_reader :errors
11
+ validate :validate_resource
12
+
13
+ def initialize(options = {})
14
+ flattened_attributes = flatten_extension_attributes(options)
15
+ ci_all_attributes = Scimitar::Support::HashWithIndifferentCaseInsensitiveAccess.new
16
+ camel_attributes = {}
17
+
18
+ # Create a map where values are the schema-correct-case attribute names
19
+ # and the values are set the same, but since the ci_all_attributes data
20
+ # type is HashWithIndifferentCaseInsensitiveAccess, lookups in this are
21
+ # case insensitive. Thus, arbitrary case input data can be mapped to
22
+ # the case correctness required for ActiveModel's attribute accessors.
23
+ #
24
+ self.class.all_attributes.each { |attr| ci_all_attributes[attr] = attr }
25
+
26
+ flattened_attributes.each do | key, value |
27
+ if ci_all_attributes.key?(key)
28
+ camel_attributes[ci_all_attributes[key]] = value
29
+ end
30
+ end
31
+
32
+ super(camel_attributes)
33
+ constantize_complex_types(camel_attributes)
34
+
35
+ @errors = ActiveModel::Errors.new(self)
36
+ end
37
+
38
+ def flatten_extension_attributes(options)
39
+ flattened = options.dup
40
+ self.class.extended_schemas.each do |extended_schema|
41
+ if extension_attrs = flattened.delete(extended_schema.id)
42
+ flattened.merge!(extension_attrs)
43
+ end
44
+ end
45
+ flattened
46
+ end
47
+
48
+ # Can be used to extend an existing resource type's schema. For example:
49
+ #
50
+ # module Scim
51
+ # module Schema
52
+ # class MyExtension < Scimitar::Schema::Base
53
+ #
54
+ # def initialize(options = {})
55
+ # super(name: 'ExtendedGroup',
56
+ # id: self.class.id,
57
+ # description: 'Represents extra info about a group',
58
+ # scim_attributes: self.class.scim_attributes)
59
+ # end
60
+ #
61
+ # def self.id
62
+ # 'urn:ietf:params:scim:schemas:extension:extendedgroup:2.0:Group'
63
+ # end
64
+ #
65
+ # def self.scim_attributes
66
+ # [Scimitar::Schema::Attribute.new(name: 'someAddedAttribute',
67
+ # type: 'string',
68
+ # required: true,
69
+ # canonicalValues: ['FOO', 'BAR'])]
70
+ # end
71
+ # end
72
+ # end
73
+ # end
74
+ #
75
+ # Scimitar::Resources::Group.extend_schema Scim::Schema::MyExtension
76
+ #
77
+ def self.extend_schema(schema)
78
+ derive_attributes_from_schema(schema)
79
+ extended_schemas << schema
80
+ end
81
+
82
+ def self.extended_schemas
83
+ @extended_schemas ||= []
84
+ end
85
+
86
+ def self.schemas
87
+ ([schema] + extended_schemas).flatten
88
+ end
89
+
90
+ def self.all_attributes
91
+ scim_attributes = schemas.map(&:scim_attributes).flatten.map(&:name)
92
+ scim_attributes + [:id, :externalId, :meta]
93
+ end
94
+
95
+ # Calls to Scimitar::Schema::Base::find_attribute for each of the schemas
96
+ # in ::schemas, in order returned (so main schema would be first, then
97
+ # any extended schemas searched next). Returns the first match found, or
98
+ # +nil+.
99
+ #
100
+ # See Scimitar::Schema::Base::find_attribute for details on parameters,
101
+ # more about the return value and other general information.
102
+ #
103
+ def self.find_attribute(*path)
104
+ found_attribute = nil
105
+
106
+ self.schemas.each do | schema |
107
+ found_attribute = schema.find_attribute(*path)
108
+ break unless found_attribute.nil?
109
+ end
110
+
111
+ return found_attribute
112
+ end
113
+
114
+ def self.complex_scim_attributes
115
+ schemas.flat_map(&:scim_attributes).select(&:complexType).group_by(&:name)
116
+ end
117
+
118
+ def complex_type_from_hash(scim_attribute, attr_value)
119
+ if attr_value.is_a?(Hash)
120
+ scim_attribute.complexType.new(attr_value)
121
+ else
122
+ attr_value
123
+ end
124
+ end
125
+
126
+ def constantize_complex_types(hash)
127
+ hash.with_indifferent_access.each_pair do |attr_name, attr_value|
128
+ scim_attribute = self.class.complex_scim_attributes[attr_name].try(:first)
129
+
130
+ if scim_attribute && scim_attribute.complexType
131
+ if scim_attribute.multiValued
132
+ self.send("#{attr_name}=", attr_value&.map {|attr_for_each_item| complex_type_from_hash(scim_attribute, attr_for_each_item)})
133
+ else
134
+ self.send("#{attr_name}=", complex_type_from_hash(scim_attribute, attr_value))
135
+ end
136
+ end
137
+ end
138
+ end
139
+
140
+ # Renders *in full* as JSON; typically used for write-based operations...
141
+ #
142
+ # record = self.storage_class().new
143
+ # record.from_scim!(scim_hash: scim_resource.as_json())
144
+ # self.save!(record)
145
+ #
146
+ # ...so all fields, even those marked "returned: false", are included.
147
+ # Use Scimitar::Resources::Mixin::to_scim to obtain a SCIM object with
148
+ # non-returnable fields omitted, rendering *that* as JSON via #to_json.
149
+ #
150
+ def as_json(options = {})
151
+ self.meta = Meta.new unless self.meta && self.meta.is_a?(Meta)
152
+ self.meta.resourceType = self.class.resource_type_id
153
+
154
+ original_hash = super(options).except('errors')
155
+ original_hash.merge!('schemas' => self.class.schemas.map(&:id))
156
+
157
+ self.class.extended_schemas.each do |extension_schema|
158
+ extension_attributes = extension_schema.scim_attributes.map(&:name)
159
+ original_hash.merge!(extension_schema.id => original_hash.extract!(*extension_attributes))
160
+ end
161
+
162
+ original_hash
163
+ end
164
+
165
+ def self.resource_type_id
166
+ name.demodulize
167
+ end
168
+
169
+ def self.resource_type(location)
170
+ resource_type = ResourceType.new(
171
+ endpoint: endpoint,
172
+ schema: schema.id,
173
+ id: resource_type_id,
174
+ name: resource_type_id,
175
+ schemaExtensions: extended_schemas.map(&:id)
176
+ )
177
+
178
+ resource_type.meta.location = location
179
+ resource_type
180
+ end
181
+
182
+ def validate_resource
183
+ self.class.schema.valid?(self)
184
+ self.class.extended_schemas.each do |extended_schema|
185
+ extended_schema.valid?(self)
186
+ end
187
+ end
188
+ end
189
+ end
190
+ end
@@ -0,0 +1,13 @@
1
+ module Scimitar
2
+ module Resources
3
+ class Group < Base
4
+
5
+ set_schema Schema::Group
6
+
7
+ def self.endpoint
8
+ '/Groups'
9
+ end
10
+
11
+ end
12
+ end
13
+ end