Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow writable complex types in extensions #61

Merged
merged 5 commits into from
Sep 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 35 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -458,7 +458,7 @@ Whatever you provide in the `::id` method in your extension class will be used a
```json
{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
"Operations":[
"Operations": [
{
"op": "replace",
"path": "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:organization",
Expand All @@ -470,7 +470,41 @@ Whatever you provide in the `::id` method in your extension class will be used a

Resource extensions can provide any fields you choose, under any ID/URN you choose, to either RFC-described resources or entirely custom SCIM resources. There are no hard-coded assumptions or other "magic" that might require you to only extend RFC-described resources with RFC-described extensions. Of course, if you use custom resources or custom extensions that are not described by the SCIM RFCs, then the SCIM API you provide may only work with custom-written API callers that are aware of your bespoke resources and/or extensions.

Extensions can also contain complex attributes such as groups. For instance, if you want the ability to write to groups from the User resource perspective (since 'groups' collection in a SCIM User resource is read-only), you can add one attribute to your extension like this:

```ruby
Scimitar::Schema::Attribute.new(name: "userGroups", multiValued: true, complexType: Scimitar::ComplexTypes::ReferenceGroup, mutability: "writeOnly"),
```

Then map it in your `scim_attributes_map`:

```ruby
userGroups: [
{
list: :groups,
find_with: ->(value) { Group.find(value["value"]) },
using: {
value: :id,
display: :name
}
}
]
```

And write to it like this:

```json
{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
"Operations": [
{
"op": "replace",
"path": "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:userGroups",
"value": [{ "value": "1" }]
}
]
}
```

## Security

Expand Down
2 changes: 1 addition & 1 deletion app/models/scimitar/resources/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ def self.find_attribute(*path)
end

def self.complex_scim_attributes
schema.scim_attributes.select(&:complexType).group_by(&:name)
schemas.flat_map(&:scim_attributes).select(&:complexType).group_by(&:name)
end

def complex_type_from_hash(scim_attribute, attr_value)
Expand Down
13 changes: 12 additions & 1 deletion spec/apps/dummy/app/models/mock_user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ class MockUser < ActiveRecord::Base
work_phone_number
organization
department
mock_groups
}

has_and_belongs_to_many :mock_groups
Expand Down Expand Up @@ -92,7 +93,17 @@ def self.scim_attributes_map
# "spec/apps/dummy/config/initializers/scimitar.rb".
#
organization: :organization,
department: :department
department: :department,
userGroups: [
{
list: :mock_groups,
find_with: ->(value) { MockGroup.find(value["value"]) },
using: {
value: :id,
display: :display_name
}
}
]
}
end

Expand Down
11 changes: 10 additions & 1 deletion spec/models/scimitar/resources/base_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,10 @@ def self.id
end

def self.scim_attributes
[ Scimitar::Schema::Attribute.new(name: 'relationship', type: 'string', required: true) ]
[
Scimitar::Schema::Attribute.new(name: 'relationship', type: 'string', required: true),
Scimitar::Schema::Attribute.new(name: "userGroups", multiValued: true, complexType: Scimitar::ComplexTypes::ReferenceGroup, mutability: "writeOnly")
]
end
end

Expand All @@ -291,6 +294,12 @@ def self.resource_type_id
resource = resource_class.new('extension-id' => {relationship: 'GAGA'})
expect(resource.relationship).to eql('GAGA')
end

it 'allows setting complex extension attributes' do
user_groups = [{ value: '123' }, { value: '456'}]
resource = resource_class.new('extension-id' => {userGroups: user_groups})
expect(resource.userGroups.map(&:value)).to eql(['123', '456'])
end
end # "context '#initialize' do"

context '#as_json' do
Expand Down