keycloak-admin 0.7.9 → 1.0.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: ddc2a67d3273ab4353daeb48dce261f5ba27a310fa91e9947e751da86d55a124
4
- data.tar.gz: 5850f80b6f5c1f76ade40a92bba444559fcc1f637bee0eaf1cb4b917cbfd7f4c
3
+ metadata.gz: 5651d2f6cb0f65225b0d58aa8a6087098c79b98e3d2d92fa293efc584b65248e
4
+ data.tar.gz: d737f6b39c07ccfb357ab41475308a6f155c490abdcdef077ba51c7fc3307870
5
5
  SHA512:
6
- metadata.gz: 210d92708eb39e0f221cd54f7c5d1fec917f8a1cab5bafb6ec1b1f706b60e634acf55bd38396bf5017ac95eef4aaed2d3892700ec9cbcc84757128ed02f2d46f
7
- data.tar.gz: 45e19fa08576cb1f468f0fc9dd6841da93e0bafe91002361ad78610637a41e438bfc7e7a04f5741eab87bc161cab4982b7d9fd88a482862ede548c03b2e3c287
6
+ metadata.gz: ee0a70fc6b10d9b687f73f6a0742780adf9310d12bbfb2ade247dfb8cebaa29b389cb830dfb75115979f3533573d5a2657264a4e805c040f4a98dec35b0366f5
7
+ data.tar.gz: 2451d6bb9f6b5de496096b02fd181c53bc40e1a5749dbf990bc3b312a0d951e247976bf1cb93fb54f264cb95088b5c8c2d4ea779bdc542890e43837d28970e93
data/.gitignore CHANGED
@@ -7,3 +7,4 @@ test/dummy/log/*.log
7
7
  test/dummy/tmp/
8
8
  *.gem
9
9
  .idea/
10
+ .byebug_history
data/CHANGELOG.md CHANGED
@@ -5,6 +5,23 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.0.2] - 2022-03-11
9
+
10
+ * Create `Client`
11
+ * Create `Identity Provider` (Breaking change: `IdentityProviderRepresentation.configuration` has been renamed to `IdentityProviderRepresentation.config`)
12
+ * Add `Identity Provider Mapping`
13
+
14
+ ## [1.0.1] - 2021-10-14
15
+
16
+ * List all `Identity Providers`
17
+ * Add Group on Users (thanks to @tomuench)
18
+ * Remove Group from Users (thanks to @tomuench)
19
+
20
+ ## [1.0.0] - 2021-08-03
21
+
22
+ * Add `totp` on Users
23
+ * Add `required_actions` on Users
24
+
8
25
  ## [0.7.9] - 2020-10-22
9
26
 
10
27
  * Extend `search` function to use complex queries (thanks to @hobbypunk90)
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- keycloak-admin (0.7.9)
4
+ keycloak-admin (1.0.2)
5
5
  http-cookie (~> 1.0, >= 1.0.3)
6
6
  rest-client (~> 2.0)
7
7
 
@@ -13,11 +13,11 @@ GEM
13
13
  domain_name (0.5.20190701)
14
14
  unf (>= 0.0.5, < 1.0.0)
15
15
  http-accept (1.7.0)
16
- http-cookie (1.0.3)
16
+ http-cookie (1.0.4)
17
17
  domain_name (~> 0.5)
18
- mime-types (3.3.1)
18
+ mime-types (3.4.1)
19
19
  mime-types-data (~> 3.2015)
20
- mime-types-data (3.2020.0512)
20
+ mime-types-data (3.2022.0105)
21
21
  netrc (0.11.0)
22
22
  rest-client (2.1.0)
23
23
  http-accept (>= 1.7.0, < 2.0)
@@ -39,7 +39,7 @@ GEM
39
39
  rspec-support (3.7.0)
40
40
  unf (0.1.4)
41
41
  unf_ext
42
- unf_ext (0.0.7.7)
42
+ unf_ext (0.0.8)
43
43
 
44
44
  PLATFORMS
45
45
  ruby
data/README.md CHANGED
@@ -12,7 +12,7 @@ This gem *does not* require Rails.
12
12
  For example, using `bundle`, add this line to your Gemfile.
13
13
 
14
14
  ```ruby
15
- gem "keycloak-admin", "0.7.9"
15
+ gem "keycloak-admin", "1.0.2"
16
16
  ```
17
17
 
18
18
  ## Login
@@ -114,12 +114,17 @@ All options have a default value. However, all of them can be changed in your in
114
114
  * Impersonate a user
115
115
  * Exchange a configurable token
116
116
  * Get list of clients
117
+ * Create clients
117
118
  * Get list of groups, create/save a group
118
119
  * Get list of roles, save a role
119
120
  * Get list of realms, save/update/delete a realm
120
121
  * Get list of client role mappings for a user/group
121
122
  * Save client role mappings for a user/group
122
123
  * Save realm-level role mappings for a user/group
124
+ * Add a Group on a User
125
+ * Remove a Group from a User
126
+ * Get list of Identity Providers
127
+ * Create Identity Providers
123
128
  * Link/Unlink users to federated identity provider brokers
124
129
  * Execute actions emails
125
130
  * Send forgot passsword mail
@@ -367,6 +372,16 @@ group_id = "3a63b5c0-ef8a-47fd-86ed-b5fead18d9b8"
367
372
  KeycloakAdmin.realm("a_realm").group(group_id).role_mapper.save_realm_level(role_list)
368
373
  ```
369
374
 
375
+ ### Get list of identity providers
376
+
377
+ Note: This client requires the `realm-management.view-identity-providers` role.
378
+
379
+ Returns an array of `KeycloakAdmin::IdentityProviderRepresentation`.
380
+
381
+ ```ruby
382
+ KeycloakAdmin.realm("a_realm").identity_providers.list
383
+ ```
384
+
370
385
  ## How to execute library tests
371
386
 
372
387
  From the `keycloak-admin-api` directory:
@@ -374,4 +389,5 @@ From the `keycloak-admin-api` directory:
374
389
  ```
375
390
  $ docker build . -t keycloak-admin:test
376
391
  $ docker run -v `pwd`:/usr/src/app/ keycloak-admin:test bundle exec rspec spec
377
- ```
392
+ ```
393
+
@@ -6,6 +6,14 @@ module KeycloakAdmin
6
6
  @realm_client = realm_client
7
7
  end
8
8
 
9
+ def save(client_representation)
10
+ execute_http do
11
+ RestClient::Resource.new(clients_url, @configuration.rest_client_options).post(
12
+ client_representation.to_json, headers
13
+ )
14
+ end
15
+ end
16
+
9
17
  def list
10
18
  response = execute_http do
11
19
  RestClient::Resource.new(clients_url, @configuration.rest_client_options).get(headers)
@@ -0,0 +1,51 @@
1
+ module KeycloakAdmin
2
+ class IdentityProviderClient < Client
3
+ def initialize(configuration, realm_client)
4
+ super(configuration)
5
+ raise ArgumentError.new("realm must be defined") unless realm_client.name_defined?
6
+ @realm_client = realm_client
7
+ end
8
+
9
+ def create(identity_provider_representation)
10
+ execute_http do
11
+ RestClient::Resource.new(identity_providers_url, @configuration.rest_client_options).post(
12
+ identity_provider_representation.to_json, headers
13
+ )
14
+ end
15
+ end
16
+
17
+ def add_mapping(identity_provider_alias, identity_provider_mapping_representation)
18
+ execute_http do
19
+ RestClient::Resource.new(identity_provider_mappers_url(identity_provider_alias), @configuration.rest_client_options).post(
20
+ identity_provider_mapping_representation.to_json, headers
21
+ )
22
+ end
23
+ end
24
+
25
+ def list
26
+ response = execute_http do
27
+ RestClient::Resource.new(identity_providers_url, @configuration.rest_client_options).get(headers)
28
+ end
29
+ JSON.parse(response).map { |provider_as_hash| IdentityProviderRepresentation.from_hash(provider_as_hash) }
30
+ end
31
+
32
+ def get(internal_id_or_alias=nil)
33
+ response = execute_http do
34
+ RestClient::Resource.new(identity_providers_url, @configuration.rest_client_options).get(headers)
35
+ end
36
+ IdentityProviderRepresentation.from_hash(JSON.parse(response))
37
+ end
38
+
39
+ def identity_providers_url(internal_id_or_alias=nil)
40
+ if internal_id_or_alias
41
+ "#{@realm_client.realm_admin_url}/identity-provider/instances/#{internal_id_or_alias}"
42
+ else
43
+ "#{@realm_client.realm_admin_url}/identity-provider/instances"
44
+ end
45
+ end
46
+
47
+ def identity_provider_mappers_url(internal_id_or_alias)
48
+ "#{identity_providers_url(internal_id_or_alias)}/mappers"
49
+ end
50
+ end
51
+ end
@@ -83,6 +83,10 @@ module KeycloakAdmin
83
83
  UserClient.new(@configuration, self)
84
84
  end
85
85
 
86
+ def identity_providers
87
+ IdentityProviderClient.new(@configuration, self)
88
+ end
89
+
86
90
  def user(user_id)
87
91
  UserResource.new(@configuration, self, user_id)
88
92
  end
@@ -31,6 +31,27 @@ module KeycloakAdmin
31
31
  )
32
32
  end
33
33
 
34
+ def add_group(user_id, group_id)
35
+ RestClient::Request.execute(
36
+ @configuration.rest_client_options.merge(
37
+ method: :put,
38
+ url: "#{users_url(user_id)}/groups/#{group_id}",
39
+ payload: {},
40
+ headers: headers
41
+ )
42
+ )
43
+ end
44
+
45
+ def remove_group(user_id, group_id)
46
+ RestClient::Request.execute(
47
+ @configuration.rest_client_options.merge(
48
+ method: :delete,
49
+ url: "#{users_url(user_id)}/groups/#{group_id}",
50
+ headers: headers
51
+ )
52
+ )
53
+ end
54
+
34
55
  def get(user_id)
35
56
  response = execute_http do
36
57
  RestClient::Resource.new(users_url(user_id), @configuration.rest_client_options).get(headers)
@@ -1,15 +1,70 @@
1
1
  module KeycloakAdmin
2
2
  class ClientRepresentation < Representation
3
3
  attr_accessor :id,
4
- :name,
5
- :client_id
6
- # TODO: Add more attributes
4
+ :name,
5
+ :client_id,
6
+ :description,
7
+ :client_authenticator_type,
8
+ :always_display_in_console,
9
+ :surrogate_auth_required,
10
+ :redirect_uris,
11
+ :web_origins,
12
+ :not_before,
13
+ :bearer_only,
14
+ :consent_required,
15
+ :standard_flow_enabled,
16
+ :implicit_flow_enabled,
17
+ :direct_access_grants_enabled,
18
+ :service_accounts_enabled,
19
+ :authorization_services_enabled,
20
+ :public_client,
21
+ :frontchannel_logout,
22
+ :protocol,
23
+ :base_url,
24
+ :root_url,
25
+ :attributes,
26
+ :authentication_flow_binding_overrides,
27
+ :full_scope_allowed,
28
+ :node_re_registration_timeout,
29
+ :attributes,
30
+ :protocol_mappers,
31
+ :default_client_scopes,
32
+ :optional_client_scopes,
33
+ :access
7
34
 
8
35
  def self.from_hash(hash)
9
- client = new
10
- client.id = hash["id"]
11
- client.name = hash["name"]
12
- client.client_id = hash["clientId"]
36
+ client = new
37
+ client.id = hash["id"]
38
+ client.name = hash["name"]
39
+ client.client_id = hash["clientId"]
40
+ client.description = hash["description"]
41
+ client.client_authenticator_type = hash["clientAuthenticatorType"]
42
+ client.always_display_in_console = hash["alwaysDisplayInConsole"] || false
43
+ client.surrogate_auth_required = hash["surrogateAuthRequired"] || false
44
+ client.redirect_uris = hash["redirectUris"] || false
45
+ client.web_origins = hash["webOrigins"] || false
46
+ client.not_before = hash["notBefore"] || false
47
+ client.bearer_only = hash["bearerOnly"] || false
48
+ client.consent_required = hash["consentRequired"] || false
49
+ client.standard_flow_enabled = hash["standardFlowEnabled"] || false
50
+ client.implicit_flow_enabled = hash["implicitFlowEnabled"] || false
51
+ client.direct_access_grants_enabled = hash["directAccessGrantsEnabled"] || false
52
+ client.service_accounts_enabled = hash["serviceAccountsEnabled"] || false
53
+ client.authorization_services_enabled = hash["authorizationServicesEnabled"] || false
54
+ client.public_client = hash["publicClient"] || false
55
+ client.frontchannel_logout = hash["frontchannelLogout"] || false
56
+ client.protocol = hash["protocol"]
57
+ client.base_url = hash["baseUrl"]
58
+ client.root_url = hash["rootUrl"]
59
+ client.attributes = hash["attributes"] || {}
60
+ client.authentication_flow_binding_overrides = hash["authenticationFlowBindingOverrides"] || {}
61
+ client.full_scope_allowed = hash["fullScopeAllowed"] || false
62
+ client.node_re_registration_timeout = hash["nodeReRegistrationTimeout"] || -1
63
+ client.attributes = hash["attributes"]
64
+ client.protocol_mappers = (hash["protocolMappers"] || []).map { |protocol_mapper_hash| ProtocolMapperRepresentation.from_hash(protocol_mapper_hash) }
65
+ client.default_client_scopes = hash["defaultClientScopes"] || []
66
+ client.optional_client_scopes = hash["optionalClientScopes"] || []
67
+ client.access = hash["access"] || {}
13
68
  client
14
69
  end
15
70
  end
@@ -0,0 +1,19 @@
1
+ module KeycloakAdmin
2
+ class IdentityProviderMapperRepresentation < Representation
3
+ attr_accessor :id,
4
+ :name,
5
+ :identity_provider_alias,
6
+ :identity_provider_mapper,
7
+ :config
8
+
9
+ def self.from_hash(hash)
10
+ client = new
11
+ client.id = hash["id"]
12
+ client.name = hash["name"]
13
+ client.identity_provider_alias = hash["identityProviderAlias"]
14
+ client.identity_provider_mapper = hash["identityProviderMapper"]
15
+ client.config = hash["config"]
16
+ client
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,67 @@
1
+ module KeycloakAdmin
2
+ class IdentityProviderRepresentation < Representation
3
+ attr_accessor :alias,
4
+ :display_name,
5
+ :internal_id,
6
+ :provider_id,
7
+ :enabled,
8
+ :update_profile_first_login_mode,
9
+ :trust_email,
10
+ :store_token,
11
+ :add_read_token_role_on_create,
12
+ :authenticate_by_default,
13
+ :link_only,
14
+ :first_broker_login_flow_alias,
15
+ :config
16
+
17
+ def self.from_hash(hash)
18
+ if hash.nil?
19
+ nil
20
+ else
21
+ new(
22
+ hash["alias"],
23
+ hash["displayName"],
24
+ hash["internalId"],
25
+ hash["providerId"],
26
+ hash["enabled"],
27
+ hash["updateProfileFirstLoginMode"],
28
+ hash["trustEmail"],
29
+ hash["storeToken"],
30
+ hash["addReadTokenRoleOnCreate"],
31
+ hash["authenticateByDefault"],
32
+ hash["linkOnly"],
33
+ hash["firstBrokerLoginFlowAlias"],
34
+ hash["config"]
35
+ )
36
+ end
37
+ end
38
+
39
+ def initialize(alias_name,
40
+ display_name,
41
+ internal_id,
42
+ provider_id,
43
+ enabled,
44
+ update_profile_first_login_mode,
45
+ trust_email,
46
+ store_token,
47
+ add_read_token_role_on_create,
48
+ authenticate_by_default,
49
+ link_only,
50
+ first_broker_login_flow_alias,
51
+ config)
52
+ @alias = alias_name
53
+ @display_name = display_name
54
+ @internal_id = internal_id
55
+ @provider_id = provider_id
56
+ @enabled = enabled
57
+ @update_profile_first_login_mode = update_profile_first_login_mode
58
+ @trust_email = trust_email
59
+ @store_token = store_token
60
+ @add_read_token_role_on_create = add_read_token_role_on_create
61
+ @authenticate_by_default = authenticate_by_default
62
+ @link_only = link_only
63
+ @first_broker_login_flow_alias = first_broker_login_flow_alias
64
+ @config = config || {}
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,19 @@
1
+ module KeycloakAdmin
2
+ class ProtocolMapperRepresentation < Representation
3
+ attr_accessor :config,
4
+ :id,
5
+ :name,
6
+ :protocol,
7
+ :protocol_mapper
8
+
9
+ def self.from_hash(hash)
10
+ rep = new
11
+ rep.id = hash["id"]
12
+ rep.config = hash["config"]
13
+ rep.name = hash["name"]
14
+ rep.protocol = hash["protocol"]
15
+ rep.protocol_mapper = hash["protocolMapper"]
16
+ rep
17
+ end
18
+ end
19
+ end
@@ -10,8 +10,10 @@ module KeycloakAdmin
10
10
  :email_verified,
11
11
  :first_name,
12
12
  :last_name,
13
+ :totp,
13
14
  :credentials,
14
- :federated_identities
15
+ :federated_identities,
16
+ :required_actions
15
17
 
16
18
  def self.from_hash(hash)
17
19
  user = new
@@ -25,6 +27,8 @@ module KeycloakAdmin
25
27
  user.first_name = hash["firstName"]
26
28
  user.last_name = hash["lastName"]
27
29
  user.attributes = hash["attributes"]
30
+ user.required_actions = hash["requiredActions"] || []
31
+ user.totp = hash["totp"] || false
28
32
  user.credentials = hash["credentials"]&.map{ |hash| CredentialRepresentation.from_hash(hash) } || []
29
33
  user.federated_identities = hash["federatedIdentities"]&.map { |hash| FederatedIdentityRepresentation.from_hash(hash) } || []
30
34
  user
@@ -1,3 +1,3 @@
1
1
  module KeycloakAdmin
2
- VERSION = "0.7.9"
2
+ VERSION = "1.0.2"
3
3
  end
@@ -10,9 +10,11 @@ require_relative "keycloak-admin/client/role_client"
10
10
  require_relative "keycloak-admin/client/role_mapper_client"
11
11
  require_relative "keycloak-admin/client/token_client"
12
12
  require_relative "keycloak-admin/client/user_client"
13
+ require_relative "keycloak-admin/client/identity_provider_client"
13
14
  require_relative "keycloak-admin/client/configurable_token_client"
14
15
  require_relative "keycloak-admin/representation/camel_json"
15
16
  require_relative "keycloak-admin/representation/representation"
17
+ require_relative "keycloak-admin/representation/protocol_mapper_representation"
16
18
  require_relative "keycloak-admin/representation/client_representation"
17
19
  require_relative "keycloak-admin/representation/group_representation"
18
20
  require_relative "keycloak-admin/representation/token_representation"
@@ -23,6 +25,8 @@ require_relative "keycloak-admin/representation/realm_representation"
23
25
  require_relative "keycloak-admin/representation/role_representation"
24
26
  require_relative "keycloak-admin/representation/federated_identity_representation"
25
27
  require_relative "keycloak-admin/representation/user_representation"
28
+ require_relative "keycloak-admin/representation/identity_provider_mapper_representation"
29
+ require_relative "keycloak-admin/representation/identity_provider_representation"
26
30
  require_relative "keycloak-admin/resource/base_role_containing_resource"
27
31
  require_relative "keycloak-admin/resource/group_resource"
28
32
  require_relative "keycloak-admin/resource/user_resource"
@@ -0,0 +1,92 @@
1
+ RSpec.describe KeycloakAdmin::IdentityProviderClient do
2
+ describe "#identity_providers_url" do
3
+ let(:realm_name) { "valid-realm" }
4
+ let(:provider_id) { nil }
5
+
6
+ before(:each) do
7
+ @built_url = KeycloakAdmin.realm(realm_name).identity_providers.identity_providers_url(provider_id)
8
+ end
9
+
10
+ context "when provider_id is not defined" do
11
+ let(:provider_id) { nil }
12
+ it "returns a proper url without provider id" do
13
+ expect(@built_url).to eq "http://auth.service.io/auth/admin/realms/valid-realm/identity-provider/instances"
14
+ end
15
+ end
16
+
17
+ context "when provider_id is defined" do
18
+ let(:provider_id) { "95985b21-d884-4bbd-b852-cb8cd365afc2" }
19
+ it "returns a proper url with the provider id" do
20
+ expect(@built_url).to eq "http://auth.service.io/auth/admin/realms/valid-realm/identity-provider/instances/95985b21-d884-4bbd-b852-cb8cd365afc2"
21
+ end
22
+ end
23
+ end
24
+
25
+ describe "#list" do
26
+ let(:realm_name) { "valid-realm" }
27
+ let(:json_response) do
28
+ <<-JSON
29
+ [
30
+ {
31
+ "alias": "acme",
32
+ "displayName": "ACME",
33
+ "internalId": "20fea77e-ae3d-411e-9467-2b3a20cd3e6d",
34
+ "providerId": "saml",
35
+ "enabled": true,
36
+ "updateProfileFirstLoginMode": "on",
37
+ "trustEmail": true,
38
+ "storeToken": false,
39
+ "addReadTokenRoleOnCreate": false,
40
+ "authenticateByDefault": false,
41
+ "linkOnly": false,
42
+ "firstBrokerLoginFlowAlias": "first broker login",
43
+ "config": {
44
+ "hideOnLoginPage": "",
45
+ "validateSignature": "true",
46
+ "samlXmlKeyNameTranformer": "KEY_ID",
47
+ "signingCertificate": "",
48
+ "postBindingLogout": "false",
49
+ "nameIDPolicyFormat": "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent",
50
+ "postBindingResponse": "true",
51
+ "backchannelSupported": "",
52
+ "signatureAlgorithm": "RSA_SHA256",
53
+ "wantAssertionsEncrypted": "false",
54
+ "xmlSigKeyInfoKeyNameTransformer": "CERT_SUBJECT",
55
+ "useJwksUrl": "true",
56
+ "wantAssertionsSigned": "true",
57
+ "postBindingAuthnRequest": "true",
58
+ "forceAuthn": "",
59
+ "wantAuthnRequestsSigned": "true",
60
+ "singleSignOnServiceUrl": "https://login.microsoftonline.com/test/saml2",
61
+ "addExtensionsElementWithKeyInfo": "false"
62
+ }
63
+ }
64
+ ]
65
+ JSON
66
+ end
67
+ before(:each) do
68
+ @identity_provider_client = KeycloakAdmin.realm(realm_name).identity_providers
69
+
70
+ stub_token_client
71
+ allow_any_instance_of(RestClient::Resource).to receive(:get).and_return json_response
72
+ end
73
+
74
+ it "lists identity providers" do
75
+ identity_providers = @identity_provider_client.list
76
+ expect(identity_providers.length).to eq 1
77
+ expect(identity_providers[0].alias).to eq "acme"
78
+ end
79
+
80
+ it "passes rest client options" do
81
+ rest_client_options = {verify_ssl: OpenSSL::SSL::VERIFY_NONE}
82
+ allow_any_instance_of(KeycloakAdmin::Configuration).to receive(:rest_client_options).and_return rest_client_options
83
+
84
+ expect(RestClient::Resource).to receive(:new).with(
85
+ "http://auth.service.io/auth/admin/realms/valid-realm/identity-provider/instances", rest_client_options).and_call_original
86
+
87
+ identity_providers = @identity_provider_client.list
88
+ expect(identity_providers.length).to eq 1
89
+ expect(identity_providers[0].alias).to eq "acme"
90
+ end
91
+ end
92
+ end
@@ -158,7 +158,7 @@ RSpec.describe KeycloakAdmin::TokenClient do
158
158
  @user_client = KeycloakAdmin.realm(realm_name).users
159
159
 
160
160
  stub_token_client
161
- allow_any_instance_of(RestClient::Resource).to receive(:get).and_return '{"username":"test_username","createdTimestamp":1559347200}'
161
+ allow_any_instance_of(RestClient::Resource).to receive(:get).and_return '{"username":"test_username","createdTimestamp":1559347200, "requiredActions":["CONFIGURE_TOTP"], "totp": true}'
162
162
  end
163
163
 
164
164
  it "parses the response" do
@@ -175,6 +175,8 @@ RSpec.describe KeycloakAdmin::TokenClient do
175
175
 
176
176
  user = @user_client.get('test_user_id')
177
177
  expect(user.username).to eq 'test_username'
178
+ expect(user.totp).to be true
179
+ expect(user.required_actions).to eq ["CONFIGURE_TOTP"]
178
180
  end
179
181
  end
180
182
 
@@ -0,0 +1,119 @@
1
+ RSpec.describe KeycloakAdmin::UserRepresentation do
2
+ describe "#to_json" do
3
+ before(:each) do
4
+ @client = KeycloakAdmin::ClientRepresentation.from_hash(
5
+ {
6
+ "id" => "c9104bc7-04d8-4348-b4df-8d883f9f6095",
7
+ "clientId" => "clien-test",
8
+ "name" => "Client TEST",
9
+ "description" => "Test to parse a client repsentation",
10
+ "surrogateAuthRequired" => false,
11
+ "enabled" => true,
12
+ "alwaysDisplayInConsole" => false,
13
+ "clientAuthenticatorType" => "client-secret",
14
+ "redirectUris" => [],
15
+ "webOrigins" => [],
16
+ "notBefore" => 0,
17
+ "bearerOnly" => false,
18
+ "consentRequired" => false,
19
+ "standardFlowEnabled" => false,
20
+ "implicitFlowEnabled" => false,
21
+ "directAccessGrantsEnabled" => false,
22
+ "serviceAccountsEnabled" => true,
23
+ "publicClient" => false,
24
+ "frontchannelLogout" => false,
25
+ "protocol" => "openid-connect",
26
+ "attributes" => {
27
+ "saml.assertion.signature" => "false",
28
+ "access.token.lifespan" => "86400",
29
+ "saml.multivalued.roles" => "false",
30
+ "saml.force.post.binding" => "false",
31
+ "saml.encrypt" => "false",
32
+ "saml.server.signature" => "false",
33
+ "backchannel.logout.revoke.offline.tokens" => "false",
34
+ "saml.server.signature.keyinfo.ext" => "false",
35
+ "exclude.session.state.from.auth.response" => "false",
36
+ "backchannel.logout.session.required" => "true",
37
+ "saml_force_name_id_format" => "false",
38
+ "saml.client.signature" => "false",
39
+ "tls.client.certificate.bound.access.tokens" => "false",
40
+ "saml.authnstatement" => "false",
41
+ "display.on.consent.screen" => "false",
42
+ "saml.onetimeuse.condition" => "false"
43
+ },
44
+ "authenticationFlowBindingOverrides" => {},
45
+ "fullScopeAllowed" => true,
46
+ "nodeReRegistrationTimeout" => -1,
47
+ "protocolMappers" => [
48
+ {
49
+ "id" => "2220432a-e953-422c-b176-62b65e085fe5",
50
+ "name" => "Client Host",
51
+ "protocol" => "openid-connect",
52
+ "protocolMapper" => "oidc-usersessionmodel-note-mapper",
53
+ "consentRequired" => false,
54
+ "config" => {
55
+ "user.session.note" => "clientHost",
56
+ "userinfo.token.claim" => "true",
57
+ "id.token.claim" => "true",
58
+ "access.token.claim" => "true",
59
+ "claim.name" => "clientHost",
60
+ "jsonType.label" => "String"
61
+ }
62
+ },
63
+ {
64
+ "id" => "5509e428-574d-4137-b396-9108244f31ee",
65
+ "name" => "Client IP Address",
66
+ "protocol" => "openid-connect",
67
+ "protocolMapper" => "oidc-usersessionmodel-note-mapper",
68
+ "consentRequired" => false,
69
+ "config" => {
70
+ "user.session.note" => "clientAddress",
71
+ "userinfo.token.claim" => "true",
72
+ "id.token.claim" => "true",
73
+ "access.token.claim" => "true",
74
+ "claim.name" => "clientAddress",
75
+ "jsonType.label" => "String"
76
+ }
77
+ },
78
+ {
79
+ "id" => "44504b93-dbce-48b8-9570-9a48d5421ae9",
80
+ "name" => "Client ID",
81
+ "protocol" => "openid-connect",
82
+ "protocolMapper" => "oidc-usersessionmodel-note-mapper",
83
+ "consentRequired" => false,
84
+ "config" => {
85
+ "user.session.note" => "clientId",
86
+ "userinfo.token.claim" => "true",
87
+ "id.token.claim" => "true",
88
+ "access.token.claim" => "true",
89
+ "claim.name" => "clientId",
90
+ "jsonType.label" => "String"
91
+ }
92
+ }
93
+ ],
94
+ "defaultClientScopes" => [
95
+ "web-origins",
96
+ "roles",
97
+ "profile",
98
+ "email"
99
+ ],
100
+ "optionalClientScopes" => [
101
+ "address",
102
+ "phone",
103
+ "offline_access",
104
+ "microprofile-jwt"
105
+ ],
106
+ "access" => {
107
+ "view" => true,
108
+ "configure" => true,
109
+ "manage" => true
110
+ }
111
+ }
112
+ )
113
+ end
114
+
115
+ it "can convert to json" do
116
+ expect(@client.to_json).to eq "{\"id\":\"c9104bc7-04d8-4348-b4df-8d883f9f6095\",\"name\":\"Client TEST\",\"clientId\":\"clien-test\",\"description\":\"Test to parse a client repsentation\",\"clientAuthenticatorType\":\"client-secret\",\"alwaysDisplayInConsole\":false,\"surrogateAuthRequired\":false,\"redirectUris\":[],\"webOrigins\":[],\"notBefore\":0,\"bearerOnly\":false,\"consentRequired\":false,\"standardFlowEnabled\":false,\"implicitFlowEnabled\":false,\"directAccessGrantsEnabled\":false,\"serviceAccountsEnabled\":true,\"authorizationServicesEnabled\":false,\"publicClient\":false,\"frontchannelLogout\":false,\"protocol\":\"openid-connect\",\"baseUrl\":null,\"rootUrl\":null,\"attributes\":{\"saml.assertion.signature\":\"false\",\"access.token.lifespan\":\"86400\",\"saml.multivalued.roles\":\"false\",\"saml.force.post.binding\":\"false\",\"saml.encrypt\":\"false\",\"saml.server.signature\":\"false\",\"backchannel.logout.revoke.offline.tokens\":\"false\",\"saml.server.signature.keyinfo.ext\":\"false\",\"exclude.session.state.from.auth.response\":\"false\",\"backchannel.logout.session.required\":\"true\",\"saml_force_name_id_format\":\"false\",\"saml.client.signature\":\"false\",\"tls.client.certificate.bound.access.tokens\":\"false\",\"saml.authnstatement\":\"false\",\"display.on.consent.screen\":\"false\",\"saml.onetimeuse.condition\":\"false\"},\"authenticationFlowBindingOverrides\":{},\"fullScopeAllowed\":true,\"nodeReRegistrationTimeout\":-1,\"protocolMappers\":[{\"id\":\"2220432a-e953-422c-b176-62b65e085fe5\",\"config\":{\"user.session.note\":\"clientHost\",\"userinfo.token.claim\":\"true\",\"id.token.claim\":\"true\",\"access.token.claim\":\"true\",\"claim.name\":\"clientHost\",\"jsonType.label\":\"String\"},\"name\":\"Client Host\",\"protocol\":\"openid-connect\",\"protocolMapper\":\"oidc-usersessionmodel-note-mapper\"},{\"id\":\"5509e428-574d-4137-b396-9108244f31ee\",\"config\":{\"user.session.note\":\"clientAddress\",\"userinfo.token.claim\":\"true\",\"id.token.claim\":\"true\",\"access.token.claim\":\"true\",\"claim.name\":\"clientAddress\",\"jsonType.label\":\"String\"},\"name\":\"Client IP Address\",\"protocol\":\"openid-connect\",\"protocolMapper\":\"oidc-usersessionmodel-note-mapper\"},{\"id\":\"44504b93-dbce-48b8-9570-9a48d5421ae9\",\"config\":{\"user.session.note\":\"clientId\",\"userinfo.token.claim\":\"true\",\"id.token.claim\":\"true\",\"access.token.claim\":\"true\",\"claim.name\":\"clientId\",\"jsonType.label\":\"String\"},\"name\":\"Client ID\",\"protocol\":\"openid-connect\",\"protocolMapper\":\"oidc-usersessionmodel-note-mapper\"}],\"defaultClientScopes\":[\"web-origins\",\"roles\",\"profile\",\"email\"],\"optionalClientScopes\":[\"address\",\"phone\",\"offline_access\",\"microprofile-jwt\"],\"access\":{\"view\":true,\"configure\":true,\"manage\":true}}"
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,24 @@
1
+ RSpec.describe KeycloakAdmin::UserRepresentation do
2
+ describe "#to_json" do
3
+ before(:each) do
4
+ @mapper = KeycloakAdmin::IdentityProviderMapperRepresentation.from_hash(
5
+ {
6
+ "id" => "91895ce9-b225-4274-993e-c8e6b8e490f0",
7
+ "name" => "IDP",
8
+ "identityProviderAlias" => "test",
9
+ "identityProviderMapper" => "hardcoded-attribute-idp-mapper",
10
+ "config" => {
11
+ "syncMode" => "INHERIT",
12
+ "attribute.value" => "test",
13
+ "attributes" => "[]",
14
+ "attribute" => "keycloak.idp"
15
+ }
16
+ }
17
+ )
18
+ end
19
+
20
+ it "can convert to json" do
21
+ expect(@mapper.to_json).to eq "{\"id\":\"91895ce9-b225-4274-993e-c8e6b8e490f0\",\"name\":\"IDP\",\"identityProviderAlias\":\"test\",\"identityProviderMapper\":\"hardcoded-attribute-idp-mapper\",\"config\":{\"syncMode\":\"INHERIT\",\"attribute.value\":\"test\",\"attributes\":\"[]\",\"attribute\":\"keycloak.idp\"}}"
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,113 @@
1
+ RSpec.describe KeycloakAdmin::IdentityProviderRepresentation do
2
+ describe "#from_hash" do
3
+ before(:each) do
4
+ json = <<-JSON
5
+ {
6
+ "alias": "acme",
7
+ "displayName": "ACME",
8
+ "internalId": "20fea77e-ae3d-411e-9467-2b3a20cd3e6d",
9
+ "providerId": "saml",
10
+ "enabled": true,
11
+ "updateProfileFirstLoginMode": "on",
12
+ "trustEmail": true,
13
+ "storeToken": false,
14
+ "addReadTokenRoleOnCreate": false,
15
+ "authenticateByDefault": false,
16
+ "linkOnly": false,
17
+ "firstBrokerLoginFlowAlias": "first broker login",
18
+ "config": {
19
+ "hideOnLoginPage": "",
20
+ "validateSignature": "true",
21
+ "samlXmlKeyNameTranformer": "KEY_ID",
22
+ "signingCertificate": "",
23
+ "postBindingLogout": "false",
24
+ "nameIDPolicyFormat": "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent",
25
+ "postBindingResponse": "true",
26
+ "backchannelSupported": "",
27
+ "signatureAlgorithm": "RSA_SHA256",
28
+ "wantAssertionsEncrypted": "false",
29
+ "xmlSigKeyInfoKeyNameTransformer": "CERT_SUBJECT",
30
+ "useJwksUrl": "true",
31
+ "wantAssertionsSigned": "true",
32
+ "postBindingAuthnRequest": "true",
33
+ "forceAuthn": "",
34
+ "wantAuthnRequestsSigned": "true",
35
+ "singleSignOnServiceUrl": "https://login.microsoftonline.com/test/saml2",
36
+ "addExtensionsElementWithKeyInfo": "false"
37
+ }
38
+ }
39
+ JSON
40
+ payload = JSON.parse(json)
41
+ @identity_provider = KeycloakAdmin::IdentityProviderRepresentation.from_hash(payload)
42
+ end
43
+
44
+ it "parses the alias" do
45
+ expect(@identity_provider.alias).to eq "acme"
46
+ end
47
+
48
+ it "parses the display name" do
49
+ expect(@identity_provider.display_name).to eq "ACME"
50
+ end
51
+
52
+ it "parses the internalId" do
53
+ expect(@identity_provider.internal_id).to eq "20fea77e-ae3d-411e-9467-2b3a20cd3e6d"
54
+ end
55
+
56
+ it "parses the provider id" do
57
+ expect(@identity_provider.provider_id).to eq "saml"
58
+ end
59
+
60
+ it "parses the enabled" do
61
+ expect(@identity_provider.enabled).to eq true
62
+ end
63
+
64
+ it "parses the update_profile_first_login_mode" do
65
+ expect(@identity_provider.update_profile_first_login_mode).to eq "on"
66
+ end
67
+
68
+ it "parses the trust_email" do
69
+ expect(@identity_provider.trust_email).to eq true
70
+ end
71
+
72
+ it "parses the store_token" do
73
+ expect(@identity_provider.store_token).to eq false
74
+ end
75
+
76
+ it "parses the add_read_token_role_on_create" do
77
+ expect(@identity_provider.add_read_token_role_on_create).to eq false
78
+ end
79
+
80
+ it "parses the authenticate_by_default" do
81
+ expect(@identity_provider.authenticate_by_default).to eq false
82
+ end
83
+
84
+ it "parses the link_only" do
85
+ expect(@identity_provider.link_only).to eq false
86
+ end
87
+
88
+ it "parses the first_broker_login_flow_alias" do
89
+ expect(@identity_provider.first_broker_login_flow_alias).to eq "first broker login"
90
+ end
91
+
92
+ it "parses the configuration as a hash with camel properties" do
93
+ expect(@identity_provider.config["hideOnLoginPage"]).to eq ""
94
+ expect(@identity_provider.config["validateSignature"]).to eq "true"
95
+ expect(@identity_provider.config["samlXmlKeyNameTranformer"]).to eq "KEY_ID"
96
+ expect(@identity_provider.config["signingCertificate"]).to eq ""
97
+ expect(@identity_provider.config["postBindingLogout"]).to eq "false"
98
+ expect(@identity_provider.config["nameIDPolicyFormat"]).to eq "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"
99
+ expect(@identity_provider.config["postBindingResponse"]).to eq "true"
100
+ expect(@identity_provider.config["backchannelSupported"]).to eq ""
101
+ expect(@identity_provider.config["signatureAlgorithm"]).to eq "RSA_SHA256"
102
+ expect(@identity_provider.config["wantAssertionsEncrypted"]).to eq "false"
103
+ expect(@identity_provider.config["xmlSigKeyInfoKeyNameTransformer"]).to eq "CERT_SUBJECT"
104
+ expect(@identity_provider.config["useJwksUrl"]).to eq "true"
105
+ expect(@identity_provider.config["wantAssertionsSigned"]).to eq "true"
106
+ expect(@identity_provider.config["postBindingAuthnRequest"]).to eq "true"
107
+ expect(@identity_provider.config["forceAuthn"]).to eq ""
108
+ expect(@identity_provider.config["wantAuthnRequestsSigned"]).to eq "true"
109
+ expect(@identity_provider.config["singleSignOnServiceUrl"]).to eq "https://login.microsoftonline.com/test/saml2"
110
+ expect(@identity_provider.config["addExtensionsElementWithKeyInfo"]).to eq "false"
111
+ end
112
+ end
113
+ end
@@ -9,7 +9,7 @@ RSpec.describe KeycloakAdmin::UserRepresentation do
9
9
  end
10
10
 
11
11
  it "can convert to json" do
12
- expect(@user.to_json).to eq '{"id":null,"createdTimestamp":1559836000,"origin":null,"username":"test_username","email":null,"enabled":true,"emailVerified":null,"firstName":null,"lastName":null,"attributes":null,"credentials":[],"federatedIdentities":[]}'
12
+ expect(@user.to_json).to eq '{"id":null,"createdTimestamp":1559836000,"origin":null,"username":"test_username","email":null,"enabled":true,"emailVerified":null,"firstName":null,"lastName":null,"attributes":null,"requiredActions":[],"totp":false,"credentials":[],"federatedIdentities":[]}'
13
13
  end
14
14
  end
15
15
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: keycloak-admin
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.9
4
+ version: 1.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lorent Lempereur
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-10-22 00:00:00.000000000 Z
11
+ date: 2022-03-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: http-cookie
@@ -94,6 +94,7 @@ files:
94
94
  - lib/keycloak-admin/client/client_role_mappings_client.rb
95
95
  - lib/keycloak-admin/client/configurable_token_client.rb
96
96
  - lib/keycloak-admin/client/group_client.rb
97
+ - lib/keycloak-admin/client/identity_provider_client.rb
97
98
  - lib/keycloak-admin/client/realm_client.rb
98
99
  - lib/keycloak-admin/client/role_client.rb
99
100
  - lib/keycloak-admin/client/role_mapper_client.rb
@@ -105,8 +106,11 @@ files:
105
106
  - lib/keycloak-admin/representation/credential_representation.rb
106
107
  - lib/keycloak-admin/representation/federated_identity_representation.rb
107
108
  - lib/keycloak-admin/representation/group_representation.rb
109
+ - lib/keycloak-admin/representation/identity_provider_mapper_representation.rb
110
+ - lib/keycloak-admin/representation/identity_provider_representation.rb
108
111
  - lib/keycloak-admin/representation/impersonation_redirection_representation.rb
109
112
  - lib/keycloak-admin/representation/impersonation_representation.rb
113
+ - lib/keycloak-admin/representation/protocol_mapper_representation.rb
110
114
  - lib/keycloak-admin/representation/realm_representation.rb
111
115
  - lib/keycloak-admin/representation/representation.rb
112
116
  - lib/keycloak-admin/representation/role_representation.rb
@@ -121,12 +125,16 @@ files:
121
125
  - spec/client/client_spec.rb
122
126
  - spec/client/configurable_token_client_spec.rb
123
127
  - spec/client/group_client_spec.rb
128
+ - spec/client/identity_provider_client_spec.rb
124
129
  - spec/client/realm_client_spec.rb
125
130
  - spec/client/role_client_spec.rb
126
131
  - spec/client/role_mapper_client_spec.rb
127
132
  - spec/client/token_client_spec.rb
128
133
  - spec/client/user_client_spec.rb
129
134
  - spec/configuration_spec.rb
135
+ - spec/representation/client_representation_spec.rb
136
+ - spec/representation/identity_provider_mapper_representation_spec.rb
137
+ - spec/representation/identity_provider_representation_spec.rb
130
138
  - spec/representation/impersonation_representation_spec.rb
131
139
  - spec/representation/user_representation_spec.rb
132
140
  - spec/resource/group_resource_spec.rb
@@ -136,7 +144,7 @@ homepage: https://github.com/looorent/keycloak-admin-ruby
136
144
  licenses:
137
145
  - MIT
138
146
  metadata: {}
139
- post_install_message:
147
+ post_install_message:
140
148
  rdoc_options: []
141
149
  require_paths:
142
150
  - lib
@@ -151,8 +159,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
151
159
  - !ruby/object:Gem::Version
152
160
  version: '0'
153
161
  requirements: []
154
- rubygems_version: 3.0.3
155
- signing_key:
162
+ rubygems_version: 3.2.3
163
+ signing_key:
156
164
  specification_version: 4
157
165
  summary: Keycloak Admin REST API client written in Ruby
158
166
  test_files: []