ecoportal-scim 0.1.0
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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +46 -0
- data/LICENSE +21 -0
- data/README.md +107 -0
- data/lib/ecoportal/api/common/concerns/end_point.rb +28 -0
- data/lib/ecoportal/api/common/concerns.rb +10 -0
- data/lib/ecoportal/api/common/scim/client.rb +58 -0
- data/lib/ecoportal/api/common/scim/includer.rb +16 -0
- data/lib/ecoportal/api/common/scim.rb +11 -0
- data/lib/ecoportal/api/common.scim.rb +13 -0
- data/lib/ecoportal/api/scim/groups/group.rb +17 -0
- data/lib/ecoportal/api/scim/groups.rb +107 -0
- data/lib/ecoportal/api/scim/schemas/schema.rb +11 -0
- data/lib/ecoportal/api/scim/schemas.rb +14 -0
- data/lib/ecoportal/api/scim/service_provider_config.rb +9 -0
- data/lib/ecoportal/api/scim/users/user.rb +119 -0
- data/lib/ecoportal/api/scim/users.rb +59 -0
- data/lib/ecoportal/api/scim.rb +80 -0
- data/lib/ecoportal/api/scim_version.rb +5 -0
- data/lib/ecoportal/scim.rb +10 -0
- metadata +195 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: a7ad495de878dc33d4bc202d34b37b8409a42acf1ab47777864e325cdfebcb29
|
|
4
|
+
data.tar.gz: 25369ed1936d1e264cd8cf2098fd706f576980fad5b4ca8ebfa92b9184459042
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 057dc11619874eb6c4b8e625095e5997a5f6c620f7faa1a8409a9c45c2690299ea734d60357ca305a056c1c4f11e7582db9455beb06adf9b67cf80e409edf46a
|
|
7
|
+
data.tar.gz: 668f1f294873a9307b61de0ee7bcaf4d4badd44dda945813c108283ed8bdded477e322781b8996749cbf078f8f4222db7b9d98f41ea562e071eb3d86d37a3ef3
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Change Log
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
## [Unreleased]
|
|
6
|
+
|
|
7
|
+
## [0.1.0] - 2026-11-01
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- First commit.
|
|
12
|
+
- `Groups` endpoint (list, get, patch), mirroring the `Users` endpoint architecture.
|
|
13
|
+
Supports the SCIM `PatchOp` membership flip (`add_members`/`remove_members`) needed
|
|
14
|
+
by the permission-verification test suite.
|
|
15
|
+
- `Users` endpoint (list, get) -- read-only for now. `#each` enumerates the SCIM
|
|
16
|
+
`ListResponse` `Resources` the same way as `Groups`. `User` resource stays a Hash-shape
|
|
17
|
+
placeholder (see `Groups::Group`) pending the attribute model needed for the
|
|
18
|
+
APIv0-People adapter.
|
|
19
|
+
- `Users::User` attribute readers (`id`, `user_name`, `name`/`given_name`/`family_name`,
|
|
20
|
+
`active?`, `external_id`, `emails`, `phone_numbers`), as an optional read-only wrapper
|
|
21
|
+
over a SCIM `User` Hash (`Users::User.new(scim.users.get(id))`). `Users#list`/`#get`
|
|
22
|
+
keep returning plain Hashes -- no change to the endpoint's public interface.
|
|
23
|
+
- `Users::User#manager`/`#manager_id`/`#enterprise_extension` readers for the SCIM
|
|
24
|
+
enterprise-user extension's `manager` attribute, verified against the ecoPortal backend
|
|
25
|
+
(internal code spec `scim-users`): namespaced under
|
|
26
|
+
`urn:ietf:params:scim:schemas:extension:enterprise:2.0:User`, shape narrowed to `{value}`
|
|
27
|
+
only (no `$ref`/`displayName`), `value` is the supervisor's ecoPortal-internal id.
|
|
28
|
+
- README: documented the Users resource semantics (`active` is the enabled account, not the
|
|
29
|
+
archived flag; 500-per-page cap and paging; rate-limit guidance; no Bulk support) and how to
|
|
30
|
+
read the ecoPortal `requiredfields`/`schemafields` extension attributes from a `Users#list`
|
|
31
|
+
response.
|
|
32
|
+
|
|
33
|
+
### Changed
|
|
34
|
+
|
|
35
|
+
### Fixed
|
|
36
|
+
|
|
37
|
+
- `Ecoportal::API::Scim#initialize` passed `scim_key:` to `Common::Scim::Client`, whose
|
|
38
|
+
keyword is `api_key:` -- the top-level entry point raised `ArgumentError` for every
|
|
39
|
+
endpoint (`users`/`groups`/`schemas`).
|
|
40
|
+
|
|
41
|
+
- **Packaging allowlist hardened from root directory to full path.** `spec.files` allowlisted
|
|
42
|
+
`lib/` and `exe/` by directory, which says nothing about what a file under those roots IS --
|
|
43
|
+
only `.rb` now ships out of `lib/`. Preemptive: this gem has not leaked anything (confirmed
|
|
44
|
+
no non-`.rb` file is tracked under `lib/`), but the same directory-keyed allowlist shape had
|
|
45
|
+
already caused a near-miss in this gem before its first release, and a real incident in
|
|
46
|
+
ecoportal-api-graphql (2.2.0 shipped five `CLAUDE.md` files that lived under its `lib/`).
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 ecoPortal
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# Ecoportal::API::Scim
|
|
2
|
+
|
|
3
|
+
This is a basic collection of helpers that can be used to interact with the
|
|
4
|
+
[ecoPortal](https://www.ecoportal.com) SCIM integration.
|
|
5
|
+
|
|
6
|
+
## Installation
|
|
7
|
+
|
|
8
|
+
Add this line to your application's Gemfile:
|
|
9
|
+
|
|
10
|
+
```ruby
|
|
11
|
+
gem 'ecoportal-scim', require: %w[ecoportal/scim]
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
And then execute:
|
|
15
|
+
|
|
16
|
+
$ bundle
|
|
17
|
+
|
|
18
|
+
Or install it yourself as:
|
|
19
|
+
|
|
20
|
+
$ gem install ecoportal-scim
|
|
21
|
+
|
|
22
|
+
## Endpoints
|
|
23
|
+
|
|
24
|
+
`Ecoportal::API::Scim` exposes four SCIM resources, each reachable from the top-level client:
|
|
25
|
+
|
|
26
|
+
- `Groups` -- list, get and patch SCIM groups, including member add/remove.
|
|
27
|
+
- `Users` -- list and get SCIM users (read-only).
|
|
28
|
+
- `Schemas` -- list the SCIM schemas the api advertises.
|
|
29
|
+
- `ServiceProviderConfig` -- fetch the api's SCIM service provider configuration.
|
|
30
|
+
|
|
31
|
+
```ruby
|
|
32
|
+
scim = Ecoportal::API::Scim.new(scim_id, scim_key)
|
|
33
|
+
|
|
34
|
+
scim.users.get(user_id) # => Hash, the SCIM User resource
|
|
35
|
+
scim.groups.list # => Hash, a SCIM ListResponse
|
|
36
|
+
scim.schemas.list
|
|
37
|
+
scim.service_provider_config.get
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Groups
|
|
41
|
+
|
|
42
|
+
`Groups#add_members`/`#remove_members` send a SCIM `PatchOp` `add`/`remove` operation against
|
|
43
|
+
the group's `members` path (RFC 7644 section 3.5.2).
|
|
44
|
+
|
|
45
|
+
**Hazard -- `remove_members(id, [])` or `remove_members(id, nil)`:** both send
|
|
46
|
+
`{"op"=>"remove", "path"=>"members", "value"=>[]}` -- a `remove` with a `path` and an empty
|
|
47
|
+
`value` array, not an absent `value`. Per RFC 7644 section 3.5.2.2, a `remove` with a `path`
|
|
48
|
+
and **no** `value` means "remove everything at that path"; whether the ecoPortal SCIM server
|
|
49
|
+
treats "empty `value` array" the same way as "no `value`" is **unverified**. If it does,
|
|
50
|
+
passing an empty or nil list -- an easy thing to do by accident, e.g. forwarding an empty
|
|
51
|
+
filter result straight into `remove_members` -- could empty the entire group instead of being
|
|
52
|
+
a no-op. **Guard the empty/nil case yourself** (skip the call, or assert non-empty) until this
|
|
53
|
+
is confirmed against the api.
|
|
54
|
+
|
|
55
|
+
### Users
|
|
56
|
+
|
|
57
|
+
`Users#list` and `Users#get` return the api's plain parsed Hash bodies -- that return contract
|
|
58
|
+
is unchanged and does not require anything below.
|
|
59
|
+
|
|
60
|
+
`Users::User` is an optional, read-only wrapper for when you want typed accessors instead of
|
|
61
|
+
raw Hash keys:
|
|
62
|
+
|
|
63
|
+
```ruby
|
|
64
|
+
user = Ecoportal::API::Scim::Users::User.new(scim.users.get(user_id))
|
|
65
|
+
|
|
66
|
+
user.id # => the SCIM `id`
|
|
67
|
+
user.user_name # => the SCIM `userName`
|
|
68
|
+
user.name # => { 'givenName' => ..., 'familyName' => ... }
|
|
69
|
+
user.given_name
|
|
70
|
+
user.family_name
|
|
71
|
+
user.active? # => true only when the api reports `active: true`
|
|
72
|
+
user.external_id
|
|
73
|
+
user.emails # => the `emails` multi-valued attribute, [] if absent
|
|
74
|
+
user.phone_numbers # => the `phoneNumbers` multi-valued attribute, [] if absent
|
|
75
|
+
user.doc # => the raw Hash it wraps
|
|
76
|
+
user['userName'] # => raw Hash access, for attributes without a reader
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
#### `manager` / supervisor
|
|
80
|
+
|
|
81
|
+
The supervisor relationship is exposed under the SCIM enterprise-user extension, not the core
|
|
82
|
+
`User` schema:
|
|
83
|
+
|
|
84
|
+
```ruby
|
|
85
|
+
user.enterprise_extension # => the Hash keyed by the enterprise extension URI, {} if absent
|
|
86
|
+
user.manager # => the raw `manager` complex attribute, {} if absent
|
|
87
|
+
user.manager_id # => manager['value'], nil if absent
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
A few things worth knowing before relying on this:
|
|
91
|
+
|
|
92
|
+
- **Narrower than stock SCIM.** ecoPortal's `manager` only ever carries `value` -- there is no
|
|
93
|
+
`$ref` and no `displayName`, unlike the generic SCIM enterprise-user schema (RFC 7643 4.3).
|
|
94
|
+
Code written against the RFC shape will look for fields that never arrive.
|
|
95
|
+
- **Read/write asymmetry.** On read, `manager_id` is always the supervisor's ecoPortal-internal
|
|
96
|
+
id, never their `externalId`. On write (directly against the api, not yet exposed by this
|
|
97
|
+
gem), ecoPortal accepts either the internal id or the `externalId` and resolves it server-side.
|
|
98
|
+
- **Absent means omitted, not null.** When a user has no supervisor, ecoPortal leaves `manager`
|
|
99
|
+
out of the enterprise extension entirely -- it is never emitted as `null` or an empty object.
|
|
100
|
+
This gem's `#manager` reader normalizes that absence to `{}` (and `#manager_id` to `nil`);
|
|
101
|
+
`#enterprise_extension` itself returns `{}` only if the api omitted the extension namespace.
|
|
102
|
+
|
|
103
|
+
The behaviour described above is what the `Users::User` readers are written against. If ecoPortal changes it, those readers need to change with it.
|
|
104
|
+
|
|
105
|
+
## Changelog
|
|
106
|
+
|
|
107
|
+
See {file:CHANGELOG.md} for a list of changes.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
module Ecoportal
|
|
2
|
+
module API
|
|
3
|
+
module Common
|
|
4
|
+
module Concerns
|
|
5
|
+
module EndPoint
|
|
6
|
+
class << self
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
def included(base)
|
|
10
|
+
super
|
|
11
|
+
|
|
12
|
+
base.extend Ecoportal::API::Common::BaseClass
|
|
13
|
+
base.send :include, Ecoportal::API::Common::Logging
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
attr_reader :client
|
|
18
|
+
|
|
19
|
+
# @param client [Common::Client] a `Common::Client` object that holds the configuration of the api connection.
|
|
20
|
+
# @return [Users] an instance object ready to make user Scim interactions.
|
|
21
|
+
def initialize(client)
|
|
22
|
+
@client = client
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
module Ecoportal
|
|
2
|
+
module API
|
|
3
|
+
module Common
|
|
4
|
+
module Scim
|
|
5
|
+
# @see Ecoportal::API::Common::Client
|
|
6
|
+
class Client < Ecoportal::API::Common::Client
|
|
7
|
+
MAIN_END_POINT = 'scim'.freeze
|
|
8
|
+
|
|
9
|
+
attr_accessor :logger
|
|
10
|
+
|
|
11
|
+
# @note the `scim_key` will be automatically added as parameter
|
|
12
|
+
# `Authentication: Bearer` in the header of the http requests.
|
|
13
|
+
def initialize(
|
|
14
|
+
api_key:,
|
|
15
|
+
scim_id:,
|
|
16
|
+
version: 'v2',
|
|
17
|
+
host: 'live.ecoportal.com',
|
|
18
|
+
logger: nil
|
|
19
|
+
)
|
|
20
|
+
@scim_id = scim_id
|
|
21
|
+
|
|
22
|
+
super(api_key: api_key, version: version, host: host, logger: logger)
|
|
23
|
+
|
|
24
|
+
if host.match(/^localhost|^127\.0\.0\.1/)
|
|
25
|
+
@base_uri = "http://#{host}/scim/"
|
|
26
|
+
else
|
|
27
|
+
@base_uri = "https://#{host}/scim/"
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def delete(_path)
|
|
32
|
+
raise "DELETE operation does not have integration for api #{@version}"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Full URl builder of the request
|
|
36
|
+
# @param path [String] the tail that completes the url of the request.
|
|
37
|
+
# @return [String] the final url.
|
|
38
|
+
def url_for(path)
|
|
39
|
+
"#{super('')}/#{scim_id}#{path}"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Creates a HTTP object adding the `Authorization: Bearer KEY` param to the header.
|
|
43
|
+
# @note It configures HTTP so it only allows body data in json+scim format.
|
|
44
|
+
# @return [HTTP] HTTP object.
|
|
45
|
+
def base_request
|
|
46
|
+
@base_request ||= HTTP.headers(
|
|
47
|
+
'Authorization' => "Bearer #{api_key}"
|
|
48
|
+
).accept(:'json+scim')
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
attr_reader :scim_id
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module Ecoportal
|
|
2
|
+
module API
|
|
3
|
+
module Common
|
|
4
|
+
unless Ecoportal::API.const_defined?(:SCIM_VERSION)
|
|
5
|
+
msg = "Correct way to require this gem is `require 'ecoportal/scim`"
|
|
6
|
+
raise LoadError, msg
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
require_relative 'common/concerns'
|
|
13
|
+
require_relative 'common/scim'
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module Ecoportal
|
|
2
|
+
module API
|
|
3
|
+
class Scim
|
|
4
|
+
class Groups
|
|
5
|
+
# SCIM Group resource, per RFC 7643 section 4.2.
|
|
6
|
+
# @note kept as a placeholder: `Groups` works against the plain parsed Hash
|
|
7
|
+
# bodies returned by the api (`id`, `displayName`, `members` -- an array
|
|
8
|
+
# of `{value, display}`). When it gains attribute readers, mirror the
|
|
9
|
+
# Hash-wrapper shape of `Users::User`.
|
|
10
|
+
class Group
|
|
11
|
+
# SCIM core schema URI for `Group`, per RFC 7643 section 4.2.
|
|
12
|
+
SCHEMA = 'urn:ietf:params:scim:schemas:core:2.0:Group'.freeze
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
module Ecoportal
|
|
2
|
+
module API
|
|
3
|
+
class Scim
|
|
4
|
+
# @attr_reader client [Common::Client] a `Common::Client` object that
|
|
5
|
+
# holds the configuration of the api connection.
|
|
6
|
+
class Groups
|
|
7
|
+
include Ecoportal::API::Common::Concerns::EndPoint
|
|
8
|
+
include Enumerable
|
|
9
|
+
|
|
10
|
+
class_resolver :group_class, 'Ecoportal::API::Scim::Groups::Group'
|
|
11
|
+
|
|
12
|
+
END_POINT = '/Groups'.freeze
|
|
13
|
+
PATCH_SCHEMA = 'urn:ietf:params:scim:api:messages:2.0:PatchOp'.freeze
|
|
14
|
+
|
|
15
|
+
# Lists the groups of the organization.
|
|
16
|
+
# @note per RFC 7644 section 3.4.2, the response is a SCIM `ListResponse`
|
|
17
|
+
# (`totalResults`, `startIndex`, `itemsPerPage`, `Resources`).
|
|
18
|
+
# @param filter [String] a SCIM filter expression.
|
|
19
|
+
# @param start_index [Integer] the 1-based index of the first result (SCIM `startIndex`).
|
|
20
|
+
# @param count [Integer] the maximum number of results (SCIM `count`).
|
|
21
|
+
# @return [Hash] the SCIM `ListResponse`.
|
|
22
|
+
def list(filter: nil, start_index: nil, count: nil)
|
|
23
|
+
params = {}
|
|
24
|
+
params[:filter] = filter if filter
|
|
25
|
+
params[:startIndex] = start_index if start_index
|
|
26
|
+
params[:count] = count if count
|
|
27
|
+
|
|
28
|
+
response = client.get(END_POINT, params: params)
|
|
29
|
+
body = response.body
|
|
30
|
+
return body if response.success?
|
|
31
|
+
|
|
32
|
+
raise "Could not list groups - Error #{response.status}: #{body}"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Iterates all the groups returned by `list`.
|
|
36
|
+
# @param filter [String] a SCIM filter expression.
|
|
37
|
+
# @yield [group] does something with the group resource.
|
|
38
|
+
# @yieldparam group [Hash]
|
|
39
|
+
def each(filter: nil, &block)
|
|
40
|
+
return to_enum(:each, filter: filter) unless block_given?
|
|
41
|
+
|
|
42
|
+
list(filter: filter).fetch('Resources', []).each(&block)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Gets a group via api.
|
|
46
|
+
# @param id [String] the `id` of the target group.
|
|
47
|
+
# @return [Hash] the group resource.
|
|
48
|
+
def get(id)
|
|
49
|
+
response = client.get("#{END_POINT}/#{CGI.escape(id)}")
|
|
50
|
+
body = response.body
|
|
51
|
+
return body if response.success?
|
|
52
|
+
|
|
53
|
+
raise "Could not get group #{id} - Error #{response.status}: #{body}"
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Sends a raw SCIM `PatchOp` request against a group.
|
|
57
|
+
# @note per RFC 7644 section 3.5.2.
|
|
58
|
+
# @param id [String] the `id` of the target group.
|
|
59
|
+
# @param operations [Array<Hash>] the SCIM `PatchOp` `Operations`.
|
|
60
|
+
# @return [Hash] the updated group resource.
|
|
61
|
+
def patch(id, operations:)
|
|
62
|
+
data = {
|
|
63
|
+
schemas: [PATCH_SCHEMA],
|
|
64
|
+
Operations: operations
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
response = client.patch("#{END_POINT}/#{CGI.escape(id)}", data: data)
|
|
68
|
+
body = response.body
|
|
69
|
+
return body if response.success?
|
|
70
|
+
|
|
71
|
+
raise "Could not patch group #{id} - Error #{response.status}: #{body}"
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Adds members to a group.
|
|
75
|
+
# @param id [String] the `id` of the target group.
|
|
76
|
+
# @param user_ids [Array<String>, String] the `id`s of the users to add.
|
|
77
|
+
# @return [Hash] the updated group resource.
|
|
78
|
+
def add_members(id, user_ids)
|
|
79
|
+
patch(id, operations: [member_operation('add', user_ids)])
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Removes members from a group.
|
|
83
|
+
# @param id [String] the `id` of the target group.
|
|
84
|
+
# @param user_ids [Array<String>, String] the `id`s of the users to remove.
|
|
85
|
+
# @return [Hash] the updated group resource.
|
|
86
|
+
def remove_members(id, user_ids)
|
|
87
|
+
patch(id, operations: [member_operation('remove', user_ids)])
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
private
|
|
91
|
+
|
|
92
|
+
# @param op_name [String] `add` or `remove`.
|
|
93
|
+
# @param user_ids [Array<String>, String] the `id`s of the target users.
|
|
94
|
+
# @return [Hash] a single SCIM `PatchOp` operation targeting `members`.
|
|
95
|
+
def member_operation(op_name, user_ids)
|
|
96
|
+
{
|
|
97
|
+
'op' => op_name,
|
|
98
|
+
'path' => 'members',
|
|
99
|
+
'value' => Array(user_ids).map { |user_id| { 'value' => user_id } }
|
|
100
|
+
}
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
require_relative 'groups/group'
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module Ecoportal
|
|
2
|
+
module API
|
|
3
|
+
class Scim
|
|
4
|
+
class Schemas
|
|
5
|
+
include Ecoportal::API::Common::Concerns::EndPoint
|
|
6
|
+
include Enumerable
|
|
7
|
+
|
|
8
|
+
class_resolver :user_class, 'Ecoportal::API::Scim::Schemas::Schema'
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
require_relative 'schemas/schema'
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
module Ecoportal
|
|
2
|
+
module API
|
|
3
|
+
class Scim
|
|
4
|
+
class Users
|
|
5
|
+
# SCIM User resource, per RFC 7643 section 4.1.
|
|
6
|
+
# @note the `Users` endpoint keeps returning the plain parsed Hash bodies of
|
|
7
|
+
# the api (its public interface is unchanged): this class is an optional,
|
|
8
|
+
# read-only wrapper over one of those Hashes, offering snake_case readers
|
|
9
|
+
# for the attributes this api actually returns.
|
|
10
|
+
# @example
|
|
11
|
+
# user = Ecoportal::API::Scim::Users::User.new(scim.users.get(id))
|
|
12
|
+
# user.user_name # => 'alice.example@example.com'
|
|
13
|
+
class User
|
|
14
|
+
# SCIM core schema URI for `User`, per RFC 7643 section 4.1.
|
|
15
|
+
SCHEMA = 'urn:ietf:params:scim:schemas:core:2.0:User'.freeze
|
|
16
|
+
|
|
17
|
+
# @return [Hash] the raw SCIM resource Hash this object wraps.
|
|
18
|
+
attr_reader :doc
|
|
19
|
+
|
|
20
|
+
# @param doc [Hash] a SCIM `User` resource, as returned by `Users#get`
|
|
21
|
+
# or by one entry of `Users#list`'s `Resources`.
|
|
22
|
+
def initialize(doc = {})
|
|
23
|
+
@doc = doc || {}
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# @return [String, nil] the SCIM `id` (the api-side identifier).
|
|
27
|
+
def id
|
|
28
|
+
doc['id']
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# @return [String, nil] the SCIM `userName`.
|
|
32
|
+
def user_name
|
|
33
|
+
doc['userName']
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# @return [Hash] the SCIM `name` complex attribute (`givenName`, `familyName`).
|
|
37
|
+
def name
|
|
38
|
+
doc['name'] || {}
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# @return [String, nil] the SCIM `name.givenName`.
|
|
42
|
+
def given_name
|
|
43
|
+
name['givenName']
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# @return [String, nil] the SCIM `name.familyName`.
|
|
47
|
+
def family_name
|
|
48
|
+
name['familyName']
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# @note only `true` when the api reports the boolean `true`: a missing
|
|
52
|
+
# `active` attribute is **not** treated as active.
|
|
53
|
+
# @return [Boolean] the SCIM `active` flag.
|
|
54
|
+
def active?
|
|
55
|
+
doc['active'] == true
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# @return [String, nil] the SCIM `externalId`.
|
|
59
|
+
def external_id
|
|
60
|
+
doc['externalId']
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# @return [Array<Hash>] the SCIM `emails` multi-valued attribute
|
|
64
|
+
# (each entry a `{value, primary, ...}` Hash). Empty when absent.
|
|
65
|
+
def emails
|
|
66
|
+
doc['emails'] || []
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# @return [Array<Hash>] the SCIM `phoneNumbers` multi-valued attribute
|
|
70
|
+
# (each entry a `{value, primary, ...}` Hash). Empty when absent.
|
|
71
|
+
def phone_numbers
|
|
72
|
+
doc['phoneNumbers'] || []
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# SCIM enterprise-user extension schema URI, per RFC 7643 section 4.3.
|
|
76
|
+
# ecoPortal namespaces `manager` under this URI (see `#manager`).
|
|
77
|
+
ENTERPRISE_SCHEMA = 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User'.freeze
|
|
78
|
+
|
|
79
|
+
# @return [Hash] the SCIM enterprise-user extension namespace Hash,
|
|
80
|
+
# keyed by `ENTERPRISE_SCHEMA` at the top level of the resource.
|
|
81
|
+
# Empty when the api did not include the extension.
|
|
82
|
+
def enterprise_extension
|
|
83
|
+
doc[ENTERPRISE_SCHEMA] || {}
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# @note ecoPortal's `manager` complex type carries only `value` -- no
|
|
87
|
+
# `$ref` or `displayName` (unlike the generic SCIM enterprise-user
|
|
88
|
+
# schema).
|
|
89
|
+
# @return [Hash] the SCIM `manager` complex attribute, namespaced
|
|
90
|
+
# under `ENTERPRISE_SCHEMA`. Empty when the api did not include a
|
|
91
|
+
# supervisor (ecoPortal omits the attribute entirely rather than
|
|
92
|
+
# emitting a null/empty one).
|
|
93
|
+
def manager
|
|
94
|
+
enterprise_extension['manager'] || {}
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# @note this is the supervisor's ecoPortal-internal id, not their
|
|
98
|
+
# `externalId` -- the two are accepted interchangeably on writes
|
|
99
|
+
# but only the internal id is ever returned on reads.
|
|
100
|
+
# @return [String, nil] the SCIM `manager.value`.
|
|
101
|
+
def manager_id
|
|
102
|
+
manager['value']
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# @param key [String] a raw SCIM attribute name (i.e. `'userName'`).
|
|
106
|
+
# @return [Object, nil] the raw value, for attributes without a reader.
|
|
107
|
+
def [](key)
|
|
108
|
+
doc[key]
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# @return [Hash] the raw SCIM resource Hash.
|
|
112
|
+
def to_h
|
|
113
|
+
doc
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
module Ecoportal
|
|
2
|
+
module API
|
|
3
|
+
class Scim
|
|
4
|
+
# @attr_reader client [Common::Client] a `Common::Client` object that
|
|
5
|
+
# holds the configuration of the api connection.
|
|
6
|
+
class Users
|
|
7
|
+
include Ecoportal::API::Common::Concerns::EndPoint
|
|
8
|
+
include Enumerable
|
|
9
|
+
|
|
10
|
+
class_resolver :user_class, 'Ecoportal::API::Scim::Users::User'
|
|
11
|
+
|
|
12
|
+
END_POINT = '/Users'.freeze
|
|
13
|
+
|
|
14
|
+
# Lists the users of the organization.
|
|
15
|
+
# @note per RFC 7644 section 3.4.2, the response is a SCIM `ListResponse`
|
|
16
|
+
# (`totalResults`, `startIndex`, `itemsPerPage`, `Resources`).
|
|
17
|
+
# @param filter [String] a SCIM filter expression.
|
|
18
|
+
# @param start_index [Integer] the 1-based index of the first result (SCIM `startIndex`).
|
|
19
|
+
# @param count [Integer] the maximum number of results (SCIM `count`).
|
|
20
|
+
# @return [Hash] the SCIM `ListResponse`.
|
|
21
|
+
def list(filter: nil, start_index: nil, count: nil)
|
|
22
|
+
params = {}
|
|
23
|
+
params[:filter] = filter if filter
|
|
24
|
+
params[:startIndex] = start_index if start_index
|
|
25
|
+
params[:count] = count if count
|
|
26
|
+
|
|
27
|
+
response = client.get(END_POINT, params: params)
|
|
28
|
+
body = response.body
|
|
29
|
+
return body if response.success?
|
|
30
|
+
|
|
31
|
+
raise "Could not list users - Error #{response.status}: #{body}"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Iterates all the users returned by `list`.
|
|
35
|
+
# @param filter [String] a SCIM filter expression.
|
|
36
|
+
# @yield [user] does something with the user resource.
|
|
37
|
+
# @yieldparam user [Hash]
|
|
38
|
+
def each(filter: nil, &block)
|
|
39
|
+
return to_enum(:each, filter: filter) unless block_given?
|
|
40
|
+
|
|
41
|
+
list(filter: filter).fetch('Resources', []).each(&block)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Gets a user via api.
|
|
45
|
+
# @param id [String] the `id` of the target user.
|
|
46
|
+
# @return [Hash] the user resource.
|
|
47
|
+
def get(id)
|
|
48
|
+
response = client.get("#{END_POINT}/#{CGI.escape(id)}")
|
|
49
|
+
body = response.body
|
|
50
|
+
return body if response.success?
|
|
51
|
+
|
|
52
|
+
raise "Could not get user #{id} - Error #{response.status}: #{body}"
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
require_relative 'users/user'
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
module Ecoportal
|
|
2
|
+
module API
|
|
3
|
+
# @attr_reader client [Common::Client] a `Common::Client` object that holds the configuration of the api connection.
|
|
4
|
+
# @attr_reader logger [Logger] the logger.
|
|
5
|
+
class Scim
|
|
6
|
+
unless Ecoportal::API.const_defined?(:SCIM_VERSION)
|
|
7
|
+
msg = "Correct way to require this gem is `require 'ecoportal/scim`"
|
|
8
|
+
raise LoadError, msg
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
extend Ecoportal::API::Common::BaseClass
|
|
12
|
+
include Ecoportal::API::Common::Logging
|
|
13
|
+
|
|
14
|
+
VERSION = 'v2'.freeze
|
|
15
|
+
|
|
16
|
+
class_resolver :users_class, 'Ecoportal::API::Scim::Users'
|
|
17
|
+
class_resolver :groups_class, 'Ecoportal::API::Scim::Groups'
|
|
18
|
+
class_resolver :schemas_class, 'Ecoportal::API::Scim::Schemas'
|
|
19
|
+
class_resolver :service_provider_config_class, 'Ecoportal::API::Scim::ServiceProviderConfig'
|
|
20
|
+
|
|
21
|
+
# Creates an `V2` object to scope version specific api requests.
|
|
22
|
+
# @note
|
|
23
|
+
# - You should use either `api_key` or `user_key` and `org_key`
|
|
24
|
+
# - The const `VERSION` determineds the api version that client will query against.
|
|
25
|
+
# - This means that each subclass of `V2` should define their own `VERSION` constant.
|
|
26
|
+
# @param api_key [String] the key version to stablish the api connection.
|
|
27
|
+
# @param user_key [String] the user key used for the api connection (requires `org_key`).
|
|
28
|
+
# @param org_key [String] the org key used for the api connection (requires `user_key`).
|
|
29
|
+
# @param host [String] api server domain.
|
|
30
|
+
# @param logger [Logger] an object with `Logger` interface to generate logs.
|
|
31
|
+
def initialize(
|
|
32
|
+
scim_id,
|
|
33
|
+
scim_key,
|
|
34
|
+
host: 'live.ecoportal.com',
|
|
35
|
+
logger: default_logger
|
|
36
|
+
)
|
|
37
|
+
@logger = logger
|
|
38
|
+
@client = Common::Scim::Client.new(
|
|
39
|
+
api_key: scim_key,
|
|
40
|
+
scim_id: scim_id,
|
|
41
|
+
host: host,
|
|
42
|
+
version: self.class::VERSION,
|
|
43
|
+
logger: @logger
|
|
44
|
+
)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# @return [Common::Scim::Client] the low-level SCIM http client.
|
|
48
|
+
attr_reader :client
|
|
49
|
+
|
|
50
|
+
# Obtain specific object for User resource.
|
|
51
|
+
# @return [User] an instance object ready to make User Scim interactions.
|
|
52
|
+
def users
|
|
53
|
+
users_class.new(client)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Obtain specific object for Group resource.
|
|
57
|
+
# @return [Groups] an instance object ready to make Group Scim interactions.
|
|
58
|
+
def groups
|
|
59
|
+
groups_class.new(client)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Obtain specific object for Schemas resource.
|
|
63
|
+
# @return [Schemas] an instance object ready to make Schemas Scim interactions.
|
|
64
|
+
def schemas
|
|
65
|
+
schemas_class.new(client)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Obtain specific object for Schemas resource.
|
|
69
|
+
# @return [ServiceProviderConfig] an instance object ready to make
|
|
70
|
+
# ServiceProviderConfig Scim interactions.
|
|
71
|
+
def service_provider_config
|
|
72
|
+
service_provider_config_class.new(client)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
require_relative 'scim/schemas'
|
|
79
|
+
require_relative 'scim/users'
|
|
80
|
+
require_relative 'scim/groups'
|
metadata
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: ecoportal-scim
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Oscar Segura
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: rake
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: 13.0.3
|
|
19
|
+
- - "<"
|
|
20
|
+
- !ruby/object:Gem::Version
|
|
21
|
+
version: '14'
|
|
22
|
+
type: :development
|
|
23
|
+
prerelease: false
|
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
25
|
+
requirements:
|
|
26
|
+
- - ">="
|
|
27
|
+
- !ruby/object:Gem::Version
|
|
28
|
+
version: 13.0.3
|
|
29
|
+
- - "<"
|
|
30
|
+
- !ruby/object:Gem::Version
|
|
31
|
+
version: '14'
|
|
32
|
+
- !ruby/object:Gem::Dependency
|
|
33
|
+
name: redcarpet
|
|
34
|
+
requirement: !ruby/object:Gem::Requirement
|
|
35
|
+
requirements:
|
|
36
|
+
- - ">="
|
|
37
|
+
- !ruby/object:Gem::Version
|
|
38
|
+
version: 3.6.0
|
|
39
|
+
- - "<"
|
|
40
|
+
- !ruby/object:Gem::Version
|
|
41
|
+
version: '4'
|
|
42
|
+
type: :development
|
|
43
|
+
prerelease: false
|
|
44
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
45
|
+
requirements:
|
|
46
|
+
- - ">="
|
|
47
|
+
- !ruby/object:Gem::Version
|
|
48
|
+
version: 3.6.0
|
|
49
|
+
- - "<"
|
|
50
|
+
- !ruby/object:Gem::Version
|
|
51
|
+
version: '4'
|
|
52
|
+
- !ruby/object:Gem::Dependency
|
|
53
|
+
name: rspec
|
|
54
|
+
requirement: !ruby/object:Gem::Requirement
|
|
55
|
+
requirements:
|
|
56
|
+
- - ">="
|
|
57
|
+
- !ruby/object:Gem::Version
|
|
58
|
+
version: 3.12.0
|
|
59
|
+
- - "<"
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '4'
|
|
62
|
+
type: :development
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - ">="
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: 3.12.0
|
|
69
|
+
- - "<"
|
|
70
|
+
- !ruby/object:Gem::Version
|
|
71
|
+
version: '4'
|
|
72
|
+
- !ruby/object:Gem::Dependency
|
|
73
|
+
name: rubocop
|
|
74
|
+
requirement: !ruby/object:Gem::Requirement
|
|
75
|
+
requirements:
|
|
76
|
+
- - "~>"
|
|
77
|
+
- !ruby/object:Gem::Version
|
|
78
|
+
version: '1'
|
|
79
|
+
type: :development
|
|
80
|
+
prerelease: false
|
|
81
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
82
|
+
requirements:
|
|
83
|
+
- - "~>"
|
|
84
|
+
- !ruby/object:Gem::Version
|
|
85
|
+
version: '1'
|
|
86
|
+
- !ruby/object:Gem::Dependency
|
|
87
|
+
name: rubocop-rake
|
|
88
|
+
requirement: !ruby/object:Gem::Requirement
|
|
89
|
+
requirements:
|
|
90
|
+
- - "~>"
|
|
91
|
+
- !ruby/object:Gem::Version
|
|
92
|
+
version: '0'
|
|
93
|
+
type: :development
|
|
94
|
+
prerelease: false
|
|
95
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
96
|
+
requirements:
|
|
97
|
+
- - "~>"
|
|
98
|
+
- !ruby/object:Gem::Version
|
|
99
|
+
version: '0'
|
|
100
|
+
- !ruby/object:Gem::Dependency
|
|
101
|
+
name: webmock
|
|
102
|
+
requirement: !ruby/object:Gem::Requirement
|
|
103
|
+
requirements:
|
|
104
|
+
- - "~>"
|
|
105
|
+
- !ruby/object:Gem::Version
|
|
106
|
+
version: '3'
|
|
107
|
+
type: :development
|
|
108
|
+
prerelease: false
|
|
109
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
110
|
+
requirements:
|
|
111
|
+
- - "~>"
|
|
112
|
+
- !ruby/object:Gem::Version
|
|
113
|
+
version: '3'
|
|
114
|
+
- !ruby/object:Gem::Dependency
|
|
115
|
+
name: yard
|
|
116
|
+
requirement: !ruby/object:Gem::Requirement
|
|
117
|
+
requirements:
|
|
118
|
+
- - "~>"
|
|
119
|
+
- !ruby/object:Gem::Version
|
|
120
|
+
version: '0.9'
|
|
121
|
+
type: :development
|
|
122
|
+
prerelease: false
|
|
123
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
124
|
+
requirements:
|
|
125
|
+
- - "~>"
|
|
126
|
+
- !ruby/object:Gem::Version
|
|
127
|
+
version: '0.9'
|
|
128
|
+
- !ruby/object:Gem::Dependency
|
|
129
|
+
name: ecoportal-api
|
|
130
|
+
requirement: !ruby/object:Gem::Requirement
|
|
131
|
+
requirements:
|
|
132
|
+
- - "~>"
|
|
133
|
+
- !ruby/object:Gem::Version
|
|
134
|
+
version: '0.10'
|
|
135
|
+
- - ">="
|
|
136
|
+
- !ruby/object:Gem::Version
|
|
137
|
+
version: 0.10.12
|
|
138
|
+
type: :runtime
|
|
139
|
+
prerelease: false
|
|
140
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
141
|
+
requirements:
|
|
142
|
+
- - "~>"
|
|
143
|
+
- !ruby/object:Gem::Version
|
|
144
|
+
version: '0.10'
|
|
145
|
+
- - ">="
|
|
146
|
+
- !ruby/object:Gem::Version
|
|
147
|
+
version: 0.10.12
|
|
148
|
+
email:
|
|
149
|
+
- oscar@ecoportal.co.nz
|
|
150
|
+
executables: []
|
|
151
|
+
extensions: []
|
|
152
|
+
extra_rdoc_files: []
|
|
153
|
+
files:
|
|
154
|
+
- CHANGELOG.md
|
|
155
|
+
- LICENSE
|
|
156
|
+
- README.md
|
|
157
|
+
- lib/ecoportal/api/common.scim.rb
|
|
158
|
+
- lib/ecoportal/api/common/concerns.rb
|
|
159
|
+
- lib/ecoportal/api/common/concerns/end_point.rb
|
|
160
|
+
- lib/ecoportal/api/common/scim.rb
|
|
161
|
+
- lib/ecoportal/api/common/scim/client.rb
|
|
162
|
+
- lib/ecoportal/api/common/scim/includer.rb
|
|
163
|
+
- lib/ecoportal/api/scim.rb
|
|
164
|
+
- lib/ecoportal/api/scim/groups.rb
|
|
165
|
+
- lib/ecoportal/api/scim/groups/group.rb
|
|
166
|
+
- lib/ecoportal/api/scim/schemas.rb
|
|
167
|
+
- lib/ecoportal/api/scim/schemas/schema.rb
|
|
168
|
+
- lib/ecoportal/api/scim/service_provider_config.rb
|
|
169
|
+
- lib/ecoportal/api/scim/users.rb
|
|
170
|
+
- lib/ecoportal/api/scim/users/user.rb
|
|
171
|
+
- lib/ecoportal/api/scim_version.rb
|
|
172
|
+
- lib/ecoportal/scim.rb
|
|
173
|
+
homepage: https://www.ecoportal.com
|
|
174
|
+
licenses:
|
|
175
|
+
- MIT
|
|
176
|
+
metadata:
|
|
177
|
+
rubygems_mfa_required: 'true'
|
|
178
|
+
rdoc_options: []
|
|
179
|
+
require_paths:
|
|
180
|
+
- lib
|
|
181
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
182
|
+
requirements:
|
|
183
|
+
- - ">="
|
|
184
|
+
- !ruby/object:Gem::Version
|
|
185
|
+
version: 3.2.2
|
|
186
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
187
|
+
requirements:
|
|
188
|
+
- - ">="
|
|
189
|
+
- !ruby/object:Gem::Version
|
|
190
|
+
version: '0'
|
|
191
|
+
requirements: []
|
|
192
|
+
rubygems_version: 4.0.8
|
|
193
|
+
specification_version: 4
|
|
194
|
+
summary: Ruby client implementation for ecoPortal SCIM
|
|
195
|
+
test_files: []
|