scimaenaga 0.4.1 → 0.6.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dea244dd3c4735fe8f156571c07c630dbae4cee67cf46c53757aa77d74afb135
4
- data.tar.gz: 52cd9e9e2459231d3b415083f28bbac20beac60952d4e2dc6531412ee419492a
3
+ metadata.gz: 3959060e28dd1554d42df95f07f0bf311ce1f59da0f0d9bce9a3467193874d9b
4
+ data.tar.gz: c6ffc092a2584e829db8011b76d7c0e8f73c3ebe2422d993682409db90e59439
5
5
  SHA512:
6
- metadata.gz: e48ad1a2a9108a211f9bb5d15a258292212556281f30403d0d6d5e19a2a3b3dda882f635e11bbde0260fb16bfde43b926e79ae77faac713e7c494189928609eb
7
- data.tar.gz: 9d737fe588c932bae532792c141f54a67f5d952874ea7e32151c17d01cc1d6abf3ef4dd25a8639e029be0781b721c23d70cf468a9945d1e33f84a627552acb1d
6
+ metadata.gz: 013aba54f061b96a3ebe964f285a0d26bfd41062270ca50572c7633bb1a0a7e40c6668d7a5709da9b80c4883e14b07f75f14bb2e1a18dc44d5c90bcb80abe868
7
+ data.tar.gz: f31c1577377e803693ba5932ff1c64f794e2de44b5d55fd2ad1e89219a9d020e499002ef8e83a97ce81316e3d267abd44b1bd57639f69060234eeecf301bb730
data/MIT-LICENSE CHANGED
@@ -1,4 +1,5 @@
1
1
  Copyright 2018 Lessonly
2
+ Copyright 2021 Studist Corporation
2
3
 
3
4
  Permission is hereby granted, free of charge, to any person obtaining
4
5
  a copy of this software and associated documentation files (the
data/README.md CHANGED
@@ -1,10 +1,8 @@
1
- [![Tests](https://github.com/StudistCorporation/scim_rails/actions/workflows/test.yaml/badge.svg)](https://github.com/StudistCorporation/scim_rails/actions/workflows/test.yaml)
2
- [![Inline docs](http://inch-ci.org/github/lessonly/scim_rails.svg?branch=master)](http://inch-ci.org/github/lessonly/scim_rails)
3
- [![Maintainability](https://api.codeclimate.com/v1/badges/ddfb6a891d2f0d1122ae/maintainability)](https://codeclimate.com/github/lessonly/scim_rails/maintainability)
1
+ [![Tests](https://github.com/StudistCorporation/scimaenaga/actions/workflows/test.yaml/badge.svg)](https://github.com/StudistCorporation/scimaenaga/actions/workflows/test.yaml)
4
2
 
5
- # ScimRails
3
+ # Scimaenaga
6
4
 
7
- NOTE: This Gem is not yet fully SCIM complaint. It was developed with the main function of interfacing with Okta. There are features of SCIM that this Gem does not implement as described in the SCIM documentation or that have been left out completely.
5
+ NOTE: This Gem is not yet fully SCIM complaint. It was developed with the main function of interfacing with Azure AD. There are features of SCIM that this Gem does not implement as described in the SCIM documentation or that have been left out completely.
8
6
 
9
7
  #### What is SCIM?
10
8
 
@@ -12,14 +10,14 @@ SCIM stands for System for Cross-domain Identity Management. At its core, it is
12
10
 
13
11
  To learn more about SCIM 2.0 you can read the documentation at [RFC 7643](https://tools.ietf.org/html/rfc7643) and [RFC 7644](https://tools.ietf.org/html/rfc7644).
14
12
 
15
- The goal of the Gem is to offer a relatively painless way of adding SCIM 2.0 to your app. This Gem should be fully compatible with Okta's SCIM implementation. This project is ongoing and will hopefully be fully SCIM compliant in time. Pull requests that assist in meeting that goal are welcome!
13
+ The goal of the Gem is to offer a relatively painless way of adding SCIM 2.0 to your app. This Gem should be fully compatible with Azure AD's SCIM implementation. This project is ongoing and will hopefully be fully SCIM compliant in time. Pull requests that assist in meeting that goal are welcome!
16
14
 
17
15
  ## Installation
18
16
 
19
17
  Add this line to your application's Gemfile:
20
18
 
21
19
  ```ruby
22
- gem 'scim_rails'
20
+ gem 'scimaenaga', require: 'scim_rails'
23
21
  ```
24
22
 
25
23
  And then execute:
@@ -31,7 +29,7 @@ $ bundle
31
29
  Or install it yourself as:
32
30
 
33
31
  ```bash
34
- $ gem install scim_rails
32
+ $ gem install scimaenaga
35
33
  ```
36
34
 
37
35
  Generate the config file with:
@@ -238,21 +236,9 @@ Sample request:
238
236
  $ curl -X PUT 'http://username:password@localhost:3000/scim/v2/Users/1' -d '{"schemas":["urn:ietf:params:scim:schemas:core:2.0:User"],"userName":"test@example.com","name":{"givenName":"Test","familyName":"User"},"emails":[{"primary":true,"value":"test@example.com","type":"work"}],"displayName":"Test User","active":true}' -H 'Content-Type: application/scim+json'
239
237
  ```
240
238
 
241
- ### Deprovision / Reprovision
242
-
243
- The PATCH request was implemented to work with Okta. Okta updates profiles with PUT and deprovisions / reprovisions with PATCH. This implementation of PATCH is not SCIM compliant as it does not update a single attribute on the user profile but instead only sends a status update request to the record.
244
-
245
- We would like to implement PATCH to be fully SCIM compliant in future releases.
246
-
247
- Sample request:
248
-
249
- ```bash
250
- $ curl -X PATCH 'http://username:password@localhost:3000/scim/v2/Users/1' -d '{"schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"], "Operations": [{"op": "replace", "value": { "active": false }}]}' -H 'Content-Type: application/scim+json'
251
- ```
252
-
253
239
  ### Error Handling
254
240
 
255
- By default, scim_rails will output any unhandled exceptions to your configured rails logs.
241
+ By default, scimaenaga will output any unhandled exceptions to your configured rails logs.
256
242
 
257
243
  If you would like, you can supply a custom handler for exceptions in the initializer. The only requirement is that the value you supply responds to `#call`.
258
244
 
@@ -266,7 +252,7 @@ end
266
252
 
267
253
  ## Contributing
268
254
 
269
- ### [Code of Conduct](https://github.com/lessonly/scim_rails/blob/master/CODE_OF_CONDUCT.md)
255
+ ### [Code of Conduct](https://github.com/StudistCorporation/scimaenaga/blob/master/CODE_OF_CONDUCT.md)
270
256
 
271
257
  ### Pull Requests
272
258
 
@@ -9,17 +9,19 @@ module ScimRails
9
9
  )
10
10
 
11
11
  groups = @company
12
- .public_send(ScimRails.config.scim_groups_scope)
13
- .where(
14
- "#{ScimRails.config.scim_groups_model.connection.quote_column_name(query.attribute)} #{query.operator} ?",
15
- query.parameter
16
- )
17
- .order(ScimRails.config.scim_groups_list_order)
12
+ .public_send(ScimRails.config.scim_groups_scope)
13
+ .where(
14
+ "#{ScimRails.config.scim_groups_model
15
+ .connection.quote_column_name(query.attribute)}
16
+ #{query.operator} ?",
17
+ query.parameter
18
+ )
19
+ .order(ScimRails.config.scim_groups_list_order)
18
20
  else
19
21
  groups = @company
20
- .public_send(ScimRails.config.scim_groups_scope)
21
- .preload(:users)
22
- .order(ScimRails.config.scim_groups_list_order)
22
+ .public_send(ScimRails.config.scim_groups_scope)
23
+ .preload(:users)
24
+ .order(ScimRails.config.scim_groups_list_order)
23
25
  end
24
26
 
25
27
  counts = ScimCount.new(
@@ -33,64 +35,75 @@ module ScimRails
33
35
 
34
36
  def show
35
37
  group = @company
36
- .public_send(ScimRails.config.scim_groups_scope)
37
- .find(params[:id])
38
+ .public_send(ScimRails.config.scim_groups_scope)
39
+ .find(params[:id])
38
40
  json_scim_response(object: group)
39
41
  end
40
42
 
41
43
  def create
42
44
  group = @company
43
- .public_send(ScimRails.config.scim_groups_scope)
44
- .create!(permitted_group_params)
45
+ .public_send(ScimRails.config.scim_groups_scope)
46
+ .create!(permitted_group_params)
45
47
 
46
48
  json_scim_response(object: group, status: :created)
47
49
  end
48
50
 
49
51
  def put_update
50
52
  group = @company
51
- .public_send(ScimRails.config.scim_groups_scope)
52
- .find(params[:id])
53
+ .public_send(ScimRails.config.scim_groups_scope)
54
+ .find(params[:id])
53
55
  group.update!(permitted_group_params)
54
56
  json_scim_response(object: group)
55
57
  end
56
58
 
59
+ def patch_update
60
+ group = @company
61
+ .public_send(ScimRails.config.scim_groups_scope)
62
+ .find(params[:id])
63
+ patch = ScimPatch.new(params, ScimRails.config.mutable_group_attributes_schema)
64
+ patch.save(group)
65
+
66
+ json_scim_response(object: group)
67
+ end
68
+
57
69
  def destroy
58
70
  unless ScimRails.config.group_destroy_method
59
71
  raise ScimRails::ExceptionHandler::UnsupportedDeleteRequest
60
72
  end
73
+
61
74
  group = @company
62
- .public_send(ScimRails.config.scim_groups_scope)
63
- .find(params[:id])
75
+ .public_send(ScimRails.config.scim_groups_scope)
76
+ .find(params[:id])
64
77
  group.public_send(ScimRails.config.group_destroy_method)
65
78
  head :no_content
66
79
  end
67
80
 
68
81
  private
69
82
 
70
- def permitted_group_params
71
- converted = mutable_attributes.each.with_object({}) do |attribute, hash|
72
- hash[attribute] = find_value_for(attribute)
73
- end
74
- return converted unless params[:members]
83
+ def permitted_group_params
84
+ converted = mutable_attributes.each.with_object({}) do |attribute, hash|
85
+ hash[attribute] = find_value_for(attribute)
86
+ end
87
+ return converted unless params[:members]
75
88
 
76
- converted.merge(member_params)
77
- end
89
+ converted.merge(member_params)
90
+ end
78
91
 
79
- def member_params
80
- {
81
- ScimRails.config.group_member_relation_attribute =>
82
- params[:members].map do |member|
83
- member[ScimRails.config.group_member_relation_schema.keys.first]
84
- end
85
- }
86
- end
92
+ def member_params
93
+ {
94
+ ScimRails.config.group_member_relation_attribute =>
95
+ params[:members].map do |member|
96
+ member[ScimRails.config.group_member_relation_schema.keys.first]
97
+ end,
98
+ }
99
+ end
87
100
 
88
- def mutable_attributes
89
- ScimRails.config.mutable_group_attributes
90
- end
101
+ def mutable_attributes
102
+ ScimRails.config.mutable_group_attributes
103
+ end
91
104
 
92
- def controller_schema
93
- ScimRails.config.mutable_group_attributes_schema
94
- end
105
+ def controller_schema
106
+ ScimRails.config.mutable_group_attributes_schema
107
+ end
95
108
  end
96
109
  end
@@ -2,6 +2,8 @@
2
2
 
3
3
  module ScimRails
4
4
  class ScimUsersController < ScimRails::ApplicationController
5
+
6
+
5
7
  def index
6
8
  if params[:filter].present?
7
9
  query = ScimRails::ScimQueryParser.new(
@@ -9,16 +11,17 @@ module ScimRails
9
11
  )
10
12
 
11
13
  users = @company
12
- .public_send(ScimRails.config.scim_users_scope)
13
- .where(
14
- "#{ScimRails.config.scim_users_model.connection.quote_column_name(query.attribute)} #{query.operator} ?",
15
- query.parameter
16
- )
17
- .order(ScimRails.config.scim_users_list_order)
14
+ .public_send(ScimRails.config.scim_users_scope)
15
+ .where(
16
+ "#{ScimRails.config.scim_users_model
17
+ .connection.quote_column_name(query.attribute)} #{query.operator} ?",
18
+ query.parameter
19
+ )
20
+ .order(ScimRails.config.scim_users_list_order)
18
21
  else
19
22
  users = @company
20
- .public_send(ScimRails.config.scim_users_scope)
21
- .order(ScimRails.config.scim_users_list_order)
23
+ .public_send(ScimRails.config.scim_users_scope)
24
+ .order(ScimRails.config.scim_users_list_order)
22
25
  end
23
26
 
24
27
  counts = ScimCount.new(
@@ -32,20 +35,23 @@ module ScimRails
32
35
 
33
36
  def create
34
37
  if ScimRails.config.scim_user_prevent_update_on_create
35
- user = @company.public_send(ScimRails.config.scim_users_scope).create!(permitted_user_params)
38
+ user = @company
39
+ .public_send(ScimRails.config.scim_users_scope)
40
+ .create!(permitted_user_params)
36
41
  else
37
42
  username_key = ScimRails.config.queryable_user_attributes[:userName]
38
43
  find_by_username = {}
39
44
  find_by_username[username_key] = permitted_user_params[username_key]
40
45
  user = @company
41
- .public_send(ScimRails.config.scim_users_scope)
42
- .find_or_create_by(find_by_username)
46
+ .public_send(ScimRails.config.scim_users_scope)
47
+ .find_or_create_by(find_by_username)
43
48
  user.update!(permitted_user_params)
44
49
  end
45
- update_status(user) unless put_active_param.nil?
46
50
  json_scim_response(object: user, status: :created)
47
51
  end
48
52
 
53
+
54
+
49
55
  def show
50
56
  user = @company.public_send(ScimRails.config.scim_users_scope).find(params[:id])
51
57
  json_scim_response(object: user)
@@ -53,72 +59,28 @@ module ScimRails
53
59
 
54
60
  def put_update
55
61
  user = @company.public_send(ScimRails.config.scim_users_scope).find(params[:id])
56
- update_status(user) unless put_active_param.nil?
57
62
  user.update!(permitted_user_params)
58
63
  json_scim_response(object: user)
59
64
  end
60
65
 
61
- # TODO: PATCH will only deprovision or reprovision users.
62
- # This will work just fine for Okta but is not SCIM compliant.
63
66
  def patch_update
64
67
  user = @company.public_send(ScimRails.config.scim_users_scope).find(params[:id])
65
- update_status(user)
68
+ patch = ScimPatch.new(params, ScimRails.config.mutable_user_attributes_schema)
69
+ patch.save(user)
70
+
66
71
  json_scim_response(object: user)
67
72
  end
68
73
 
69
74
  private
70
75
 
71
- def permitted_user_params
72
- ScimRails.config.mutable_user_attributes.each.with_object({}) do |attribute, hash|
73
- hash[attribute] = find_value_for(attribute)
76
+ def permitted_user_params
77
+ ScimRails.config.mutable_user_attributes.each.with_object({}) do |attribute, hash|
78
+ hash[attribute] = find_value_for(attribute)
79
+ end
74
80
  end
75
- end
76
81
 
77
- def controller_schema
78
- ScimRails.config.mutable_user_attributes_schema
79
- end
80
-
81
- def update_status(user)
82
- user.public_send(ScimRails.config.user_reprovision_method) if active?
83
- user.public_send(ScimRails.config.user_deprovision_method) unless active?
84
- end
85
-
86
- def active?
87
- active = put_active_param
88
- active = patch_active_param if active.nil?
89
-
90
- case active
91
- when true, "true", 1
92
- true
93
- when false, "false", 0
94
- false
95
- else
96
- raise ActiveRecord::RecordInvalid
82
+ def controller_schema
83
+ ScimRails.config.mutable_user_attributes_schema
97
84
  end
98
- end
99
-
100
- def put_active_param
101
- params[:active]
102
- end
103
-
104
- def patch_active_param
105
- handle_invalid = lambda do
106
- raise ScimRails::ExceptionHandler::UnsupportedPatchRequest
107
- end
108
-
109
- operations = params["Operations"] || {}
110
-
111
- valid_operation = operations.find(handle_invalid) do |operation|
112
- valid_patch_operation?(operation)
113
- end
114
-
115
- valid_operation.dig("value", "active")
116
- end
117
-
118
- def valid_patch_operation?(operation)
119
- operation["op"].casecmp("replace") &&
120
- operation["value"] &&
121
- [true, false].include?(operation["value"]["active"])
122
- end
123
85
  end
124
86
  end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Parse PATCH request
4
+ class ScimPatch
5
+ attr_accessor :operations
6
+
7
+ def initialize(params, mutable_attributes_schema)
8
+ # FIXME: raise proper error.
9
+ unless params['schemas'] == ['urn:ietf:params:scim:api:messages:2.0:PatchOp']
10
+ raise StandardError
11
+ end
12
+ if params['Operations'].nil?
13
+ raise ScimRails::ExceptionHandler::UnsupportedPatchRequest
14
+ end
15
+
16
+ @operations = params['Operations'].map do |operation|
17
+ ScimPatchOperation.new(operation['op'], operation['path'], operation['value'],
18
+ mutable_attributes_schema)
19
+ end
20
+ end
21
+
22
+ def save(model)
23
+ model.transaction do
24
+ @operations.each do |operation|
25
+ operation.save(model)
26
+ end
27
+ model.save! if model.changed?
28
+ end
29
+ rescue ActiveRecord::RecordNotFound
30
+ raise
31
+ rescue StandardError
32
+ raise ScimRails::ExceptionHandler::UnsupportedPatchRequest
33
+ end
34
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Parse One of "Operations" in PATCH request
4
+ class ScimPatchOperation
5
+ attr_accessor :op, :path_scim, :path_sp, :value
6
+
7
+ def initialize(op, path, value, mutable_attributes_schema)
8
+ # FIXME: Raise proper Error
9
+ raise StandardError unless op.downcase.in? %w[add replace remove]
10
+
11
+ # No path is not supported.
12
+ # FIXME: Raise proper Error
13
+ raise ScimRails::ExceptionHandler::UnsupportedPatchRequest if path.nil?
14
+ raise ScimRails::ExceptionHandler::UnsupportedPatchRequest if value.nil?
15
+
16
+ @op = op.downcase.to_sym
17
+ @path_scim = path
18
+ @path_sp = convert_path(path, mutable_attributes_schema)
19
+ @value = convert_bool_if_string(value, @path_scim)
20
+ end
21
+
22
+ def save(model)
23
+ if @path_scim == 'members' # Only members are supported for value is an array
24
+ update_member_ids = @value.map do |v|
25
+ v[ScimRails.config.group_member_relation_schema.keys.first].to_s
26
+ end
27
+
28
+ current_member_ids = model
29
+ .public_send(ScimRails.config.group_member_relation_attribute).map(&:to_s)
30
+ case @op
31
+ when :add
32
+ member_ids = current_member_ids.concat(update_member_ids)
33
+ when :replace
34
+ member_ids = current_member_ids.concat(update_member_ids)
35
+ when :remove
36
+ member_ids = current_member_ids - update_member_ids
37
+ end
38
+
39
+ # Only the member addition process is saved by each ids
40
+ model.public_send("#{ScimRails.config.group_member_relation_attribute}=",
41
+ member_ids.uniq)
42
+ return
43
+ end
44
+
45
+ case @op
46
+ when :add, :replace
47
+ model.attributes = { @path_sp => @value }
48
+ when :remove
49
+ model.attributes = { @path_sp => nil }
50
+ end
51
+ end
52
+
53
+ private
54
+
55
+ def convert_path(path, mutable_attributes_schema)
56
+ # For now, library does not support Multi-Valued Attributes properly.
57
+ # examle:
58
+ # path = 'emails[type eq "work"].value'
59
+ # mutable_attributes_schema = {
60
+ # emails: [
61
+ # {
62
+ # value: :mail_address,
63
+ # }
64
+ # ],
65
+ # }
66
+ #
67
+ # Library ignores filter conditions (like [type eq "work"])
68
+ # and always uses the first element of the array
69
+ dig_keys = path.gsub(/\[(.+?)\]/, '.0').split('.').map do |step|
70
+ step == '0' ? 0 : step.to_sym
71
+ end
72
+ mutable_attributes_schema.dig(*dig_keys)
73
+ end
74
+
75
+ def convert_bool_if_string(value, path)
76
+ # This method correct value in requests from Azure AD according to SCIM.
77
+ # When path is not active, do nothing and return
78
+ return value if path != 'active'
79
+
80
+ case value
81
+ when 'true', 'True'
82
+ return true
83
+ when 'false', 'False'
84
+ return false
85
+ else
86
+ return value
87
+ end
88
+ end
89
+ end
@@ -5,7 +5,7 @@ module ScimRails
5
5
  attr_accessor :query_elements, :query_attributes
6
6
 
7
7
  def initialize(query_string, queryable_attributes)
8
- self.query_elements = query_string.split
8
+ self.query_elements = query_string.gsub(/\[(.+?)\]/, ".0").split
9
9
  self.query_attributes = queryable_attributes
10
10
  end
11
11
 
@@ -13,9 +13,11 @@ module ScimRails
13
13
  attribute = query_elements[0]
14
14
  raise ScimRails::ExceptionHandler::InvalidQuery if attribute.blank?
15
15
 
16
- attribute = attribute.to_sym
16
+ dig_keys = attribute.split(".").map do |step|
17
+ step == "0" ? 0 : step.to_sym
18
+ end
17
19
 
18
- mapped_attribute = query_attributes[attribute]
20
+ mapped_attribute = query_attributes.dig(*dig_keys)
19
21
  raise ScimRails::ExceptionHandler::InvalidQuery if mapped_attribute.blank?
20
22
 
21
23
  mapped_attribute
data/config/routes.rb CHANGED
@@ -8,5 +8,6 @@ ScimRails::Engine.routes.draw do
8
8
  post 'scim/v2/Groups', action: :create, controller: 'scim_groups'
9
9
  get 'scim/v2/Groups/:id', action: :show, controller: 'scim_groups'
10
10
  put 'scim/v2/Groups/:id', action: :put_update, controller: 'scim_groups'
11
+ patch 'scim/v2/Groups/:id', action: :patch_update, controller: 'scim_groups'
11
12
  delete 'scim/v2/Groups/:id', action: :destroy, controller: 'scim_groups'
12
13
  end
@@ -48,12 +48,6 @@ ScimRails.configure do |config|
48
48
  # For example, [:created_at, :id] or { created_at: :desc }.
49
49
  # config.scim_users_list_order = :id
50
50
 
51
- # Method called on user model to deprovision a user.
52
- config.user_deprovision_method = :archive!
53
-
54
- # Method called on user model to reprovision a user.
55
- config.user_reprovision_method = :unarchive!
56
-
57
51
  # Hash of queryable attribtues on the user model. If
58
52
  # the attribute is not listed in this hash it cannot
59
53
  # be queried by this Gem. The structure of this hash
@@ -42,8 +42,6 @@ module ScimRails
42
42
  :signing_secret,
43
43
  :signing_algorithm,
44
44
  :user_attributes,
45
- :user_deprovision_method,
46
- :user_reprovision_method,
47
45
  :user_schema,
48
46
  :group_schema,
49
47
  :group_destroy_method
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ScimRails
4
- VERSION = "0.4.1"
4
+ VERSION = '0.6.2'
5
5
  end