keycloak-admin 1.1.1 → 1.1.3

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.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/Dockerfile +24 -0
  3. data/.github/workflows/ci.yml +83 -0
  4. data/CHANGELOG.md +12 -2
  5. data/Gemfile.lock +8 -8
  6. data/README.md +277 -4
  7. data/lib/keycloak-admin/client/client_authz_permission_client.rb +81 -0
  8. data/lib/keycloak-admin/client/client_authz_policy_client.rb +76 -0
  9. data/lib/keycloak-admin/client/client_authz_resource_client.rb +93 -0
  10. data/lib/keycloak-admin/client/client_authz_scope_client.rb +71 -0
  11. data/lib/keycloak-admin/client/group_client.rb +41 -13
  12. data/lib/keycloak-admin/client/realm_client.rb +16 -0
  13. data/lib/keycloak-admin/client/role_client.rb +12 -10
  14. data/lib/keycloak-admin/client/user_client.rb +1 -0
  15. data/lib/keycloak-admin/representation/client_authz_permission_representation.rb +34 -0
  16. data/lib/keycloak-admin/representation/client_authz_policy_config_representation.rb +15 -0
  17. data/lib/keycloak-admin/representation/client_authz_policy_representation.rb +27 -0
  18. data/lib/keycloak-admin/representation/client_authz_resource_representation.rb +26 -0
  19. data/lib/keycloak-admin/representation/client_authz_scope_representation.rb +17 -0
  20. data/lib/keycloak-admin/representation/group_representation.rb +9 -5
  21. data/lib/keycloak-admin/version.rb +1 -1
  22. data/lib/keycloak-admin.rb +9 -0
  23. data/spec/client/client_authz_permission_client_spec.rb +170 -0
  24. data/spec/client/client_authz_policy_client_spec.rb +170 -0
  25. data/spec/client/client_authz_resource_client_spec.rb +150 -0
  26. data/spec/client/client_authz_scope_client_spec.rb +134 -0
  27. data/spec/client/client_client_spec.rb +2 -2
  28. data/spec/client/client_role_mappings_client_spec.rb +2 -2
  29. data/spec/client/group_client_spec.rb +137 -15
  30. data/spec/client/identity_provider_client_spec.rb +1 -1
  31. data/spec/client/realm_client_spec.rb +4 -4
  32. data/spec/client/role_client_spec.rb +12 -16
  33. data/spec/client/role_mapper_client_spec.rb +1 -1
  34. data/spec/client/token_client_spec.rb +1 -1
  35. data/spec/client/user_client_spec.rb +5 -5
  36. data/spec/configuration_spec.rb +1 -1
  37. data/spec/integration/client_authorization_spec.rb +95 -0
  38. data/spec/representation/client_authz_permission_representation_spec.rb +52 -0
  39. data/spec/representation/client_authz_policy_representation_spec.rb +47 -0
  40. data/spec/representation/client_authz_resource_representation_spec.rb +33 -0
  41. data/spec/representation/client_authz_scope_representation_spec.rb +19 -0
  42. data/spec/representation/group_representation_spec.rb +7 -0
  43. metadata +23 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bf6847f9dc60316780255644c15320d06a998331df2283e30a007c4bea951ba1
4
- data.tar.gz: 4af207ec29032148c58ff23194f4ade85aec556718bb4896c00ec29fe95000a9
3
+ metadata.gz: be47369f8365b8b32ff4ca3b09ebad45f5037212eca3022524b10ba17b0f64d6
4
+ data.tar.gz: 36a340f437ecb97ce5c415e752d5e1159ebbc5911d454f643985de541c6deaf3
5
5
  SHA512:
6
- metadata.gz: 6e03b3d8ae4f5eac52399fefcbea427d34a5afc5e08475365a8166faa5031eebb7b87cff23393e16e91e2df0f50263023b29262addff9550f79ef7bb5c739637
7
- data.tar.gz: 1c91e21ee8d74ba5ca05f773d20acdc11e3a6c8fd8ab3e8098cb75c7d44ed1c7763e3954b094012a5fe9000c1ff4cb048355e7a3a1f134b8911fc349b03919d9
6
+ metadata.gz: 6bd85e4709a0044168b7c3b3e9e2d89b1fab559070d4a318d65283db2046716439c4d1d0a534ba3cc9b1cbc89771626945877a76723bd03cc5f0e6dbebe6b266
7
+ data.tar.gz: e4b2286bac6d7e11192a1f3b6a569170849275bd3fa91aa104cdf521139a691396c8681a4152b5611e6a28e6c1457f60329b39ebca44cc4eb328f9ade9f7f605
@@ -0,0 +1,24 @@
1
+ ### Dockerfile for: tillawy/keycloak-github-actions
2
+ ##
3
+ ## To build & push
4
+ # docker buildx build . --platform linux/amd64 -t tillawy/keycloak-github-actions:25.0.1
5
+ # docker push tillawy/keycloak-github-actions:25.0.1
6
+ #
7
+ ## To Run Locally
8
+ # docker run \
9
+ # --rm \
10
+ # -p 8080:8080 \
11
+ # -e KEYCLOAK_ADMIN="admin" \
12
+ # -e KEYCLOAK_ADMIN_PASSWORD="admin" \
13
+ # -e KC_HOSTNAME="http://localhost:8080" \
14
+ # -e KC_HOSTNAME_ADMIN="http://localhost:8080" \
15
+ # -e KC_HTTP_ENABLED="true" \
16
+ # -e KC_DB="dev-file" \
17
+ # tillawy/keycloak-github-actions:25.0.1
18
+
19
+ FROM quay.io/keycloak/keycloak:25.0.1
20
+
21
+ ENTRYPOINT ["/opt/keycloak/bin/kc.sh"]
22
+
23
+ CMD ["start-dev", "--hostname=http://localhost:8080" , "--hostname-admin=http://localhost:8080" , "--http-enabled=true", "--verbose"]
24
+
@@ -0,0 +1,83 @@
1
+ # This workflow uses actions that are not certified by GitHub.
2
+ # They are provided by a third-party and are governed by
3
+ # separate terms of service, privacy policy, and support
4
+ # documentation.
5
+ # This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake
6
+ # For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby
7
+
8
+ name: Ruby
9
+
10
+ on:
11
+ push:
12
+ branches: [ "main" ]
13
+ pull_request:
14
+ branches: [ "main" ]
15
+
16
+ permissions:
17
+ contents: read
18
+
19
+ jobs:
20
+ test:
21
+ runs-on: ubuntu-latest
22
+ services:
23
+ keycloak:
24
+ image: tillawy/keycloak-github-actions:25.0.1
25
+ ports:
26
+ - 8080:8080
27
+ options: '--health-cmd "exec 3<>/dev/tcp/localhost/8080" --health-interval 5s --health-timeout 5s --health-retries 10 --health-start-period 100s'
28
+ env:
29
+ KEYCLOAK_ADMIN: "admin"
30
+ KEYCLOAK_ADMIN_PASSWORD: "admin"
31
+ KC_HOSTNAME: "http://localhost:8080"
32
+ KC_HOSTNAME_ADMIN: "http://localhost:8080"
33
+ KC_HTTP_ENABLED: "true"
34
+ KC_DB: "dev-file"
35
+
36
+ strategy:
37
+ matrix:
38
+ ruby-version: ['3.2']
39
+
40
+ steps:
41
+ - name: create realm
42
+ run: |
43
+ TOKEN=$(curl --silent --location --request POST "http://localhost:8080/realms/master/protocol/openid-connect/token" \
44
+ --header 'Content-Type: application/x-www-form-urlencoded' \
45
+ --data-urlencode 'grant_type=password' \
46
+ --data-urlencode 'username=admin' \
47
+ --data-urlencode 'password=admin' \
48
+ --data-urlencode 'client_id=admin-cli' | jq -r '.access_token')
49
+
50
+ curl --silent --show-error -L -X POST "http://localhost:8080/admin/realms" \
51
+ --header "Content-Type: application/json" \
52
+ --header "Authorization: Bearer ${TOKEN}" \
53
+ --data '{"realm":"dummy","enabled":true}'
54
+
55
+ curl --silent --show-error --request POST 'http://localhost:8080/admin/realms/dummy/clients' \
56
+ --header 'Authorization: Bearer '$TOKEN \
57
+ --header 'Content-Type: application/json' \
58
+ --data-raw '{
59
+ "clientId":"dummy-client",
60
+ "enabled":true,
61
+ "consentRequired": false,
62
+ "attributes":{},
63
+ "serviceAccountsEnabled": true,
64
+ "protocol":"openid-connect",
65
+ "publicClient":false,
66
+ "authorizationServicesEnabled": true,
67
+ "clientAuthenticatorType":"client-secret",
68
+ "redirectUris":["http://localhost:8180/demo"]
69
+ }'
70
+
71
+ - uses: actions/checkout@v4
72
+ - name: Set up Ruby
73
+ # To automatically get bug fixes and new Ruby versions for ruby/setup-ruby,
74
+ # change this to (see https://github.com/ruby/setup-ruby#versioning):
75
+ # uses: ruby/setup-ruby@v1
76
+ uses: ruby/setup-ruby@55283cc23133118229fd3f97f9336ee23a179fcf # v1.146.0
77
+ with:
78
+ ruby-version: ${{ matrix.ruby-version }}
79
+ bundler-cache: true # runs 'bundle install' and caches installed gems automatically
80
+ - name: Run tests
81
+ run: bundle exec rspec
82
+ env:
83
+ GITHUB_ACTIONS: true
data/CHANGELOG.md CHANGED
@@ -5,10 +5,20 @@ 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.1.3] - 2024-07-12
9
+
10
+ * Client Authorization management support (thanks to @tillawy)
11
+ * GitHub-actions setup to execute `rspec` (thanks to @tillawy)
12
+
13
+ ## [1.1.2] - 2024-05-22
14
+
15
+ * Add group endpoints (get, children, delete), support for group attributes (thanks to @mkrawc)
16
+ * GroupClient#save method now can update an existing group (thanks to @mkrawc)
17
+ * RoleClient#save method now can update an existing role (thanks to @mkrawc)
18
+
8
19
  ## [1.1.1] - 2024-01-21
9
20
 
10
- * Add/List realm-role/s to a group, Allow role-names with spaces, List groups assigned to role (thanks to @LiquidMagical
11
- )
21
+ * Add/List realm-role/s to a group, Allow role-names with spaces, List groups assigned to role (thanks to @LiquidMagical)
12
22
 
13
23
  ## [1.1.0] - 2023-10-03
14
24
 
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- keycloak-admin (1.1.1)
4
+ keycloak-admin (1.1.3)
5
5
  http-cookie (~> 1.0, >= 1.0.3)
6
6
  rest-client (~> 2.0)
7
7
 
@@ -9,14 +9,14 @@ GEM
9
9
  remote: https://rubygems.org/
10
10
  specs:
11
11
  byebug (11.1.3)
12
- diff-lcs (1.5.0)
12
+ diff-lcs (1.5.1)
13
13
  domain_name (0.6.20240107)
14
14
  http-accept (1.7.0)
15
- http-cookie (1.0.5)
15
+ http-cookie (1.0.6)
16
16
  domain_name (~> 0.5)
17
17
  mime-types (3.5.2)
18
18
  mime-types-data (~> 3.2015)
19
- mime-types-data (3.2023.1205)
19
+ mime-types-data (3.2024.0702)
20
20
  netrc (0.11.0)
21
21
  rest-client (2.1.0)
22
22
  http-accept (>= 1.7.0, < 2.0)
@@ -27,15 +27,15 @@ GEM
27
27
  rspec-core (~> 3.12.0)
28
28
  rspec-expectations (~> 3.12.0)
29
29
  rspec-mocks (~> 3.12.0)
30
- rspec-core (3.12.2)
30
+ rspec-core (3.12.3)
31
31
  rspec-support (~> 3.12.0)
32
- rspec-expectations (3.12.3)
32
+ rspec-expectations (3.12.4)
33
33
  diff-lcs (>= 1.2.0, < 2.0)
34
34
  rspec-support (~> 3.12.0)
35
- rspec-mocks (3.12.6)
35
+ rspec-mocks (3.12.7)
36
36
  diff-lcs (>= 1.2.0, < 2.0)
37
37
  rspec-support (~> 3.12.0)
38
- rspec-support (3.12.1)
38
+ rspec-support (3.12.2)
39
39
 
40
40
  PLATFORMS
41
41
  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", "1.0.24"
15
+ gem "keycloak-admin", "1.1.3"
16
16
  ```
17
17
 
18
18
  ## Login
@@ -80,7 +80,9 @@ KeycloakAdmin.configure do |config|
80
80
  config.username = ENV["KEYCLOAK_ADMIN_USER"]
81
81
  config.password = ENV["KEYCLOAK_ADMIN_PASSWORD"]
82
82
  config.logger = Rails.logger
83
- config.rest_client_options = { verify_ssl: OpenSSL::SSL::VERIFY_NONE }
83
+
84
+ # You configure RestClient to your liking – see https://github.com/rest-client/rest-client/blob/master/lib/restclient/request.rb for available options.
85
+ config.rest_client_options = { timeout: 5 }
84
86
  end
85
87
  ```
86
88
  This example is autoloaded in a Rails environment.
@@ -100,7 +102,7 @@ All options have a default value. However, all of them can be changed in your in
100
102
  | `username` | `nil`| String | Optional | Username to access the Admin REST API. Recommended if `user_service_account` is set to `false`. | `mummy` |
101
103
  | `password` | `nil`| String | Optional | Clear password to access the Admin REST API. Recommended if `user_service_account` is set to `false`. | `bobby` |
102
104
  | `logger` | `Logger.new(STDOUT)`| Logger | Optional | The logger used by `keycloak-admin` | `Rails.logger` | 
103
- | `rest_client_options` | `{}`| Hash | Optional | Options to pass to `RestClient` | `{ verify_ssl: OpenSSL::SSL::VERIFY_NONE }` | 
105
+ | `rest_client_options` | `{}`| Hash | Optional | Options to pass to `RestClient` | `{ timeout: 5 }` | 
104
106
 
105
107
 
106
108
  ## Use Cases
@@ -131,6 +133,7 @@ All options have a default value. However, all of them can be changed in your in
131
133
  * Link/Unlink users to federated identity provider brokers
132
134
  * Execute actions emails
133
135
  * Send forgot passsword mail
136
+ * Client Authorization, create, update, get, delete Resource, Scope, Policy, Permission, Policy Enforcer
134
137
 
135
138
  ### Get an access token
136
139
 
@@ -187,10 +190,15 @@ If you want to update its entire entity. To update some specific attributes, pro
187
190
 
188
191
  ```ruby
189
192
  KeycloakAdmin.realm("a_realm").users.update("05c135c6-5ad8-4e17-b1fa-635fc089fd71", {
190
- email: "hello@gmail.com"
193
+ email: "hello@gmail.com",
194
+ username: "hello",
195
+ first_name: "Jean",
196
+ last_name: "Dupond"
191
197
  })
192
198
  ```
193
199
 
200
+ Attention point: Since Keycloak 24.0.4, when updating a user, all the writable profile attributes must be passed, otherwise they will be removed. (https://www.keycloak.org/docs/24.0.4/upgrading/)
201
+
194
202
  ### Delete a user
195
203
 
196
204
  ```ruby
@@ -465,6 +473,271 @@ Returns an array of `KeycloakAdmin::IdentityProviderRepresentation`.
465
473
  KeycloakAdmin.realm("a_realm").identity_providers.list
466
474
  ```
467
475
 
476
+ ### Manage [Client Authorization Resources & Scopes](https://www.keycloak.org/docs/latest/authorization_services/index.html#_resource_overview)
477
+
478
+ In order to use authorization, you need to enable the client's `authorization_services_enabled` attribute.
479
+
480
+ ```ruby
481
+ client_id = "dummy-client"
482
+ client = KeycloakAdmin.realm("realm_a").clients.find_by_client_id(client_id)
483
+ client.authorization_services_enabled = true
484
+ KeycloakAdmin.realm("a_realm").clients.update(client)
485
+ ```
486
+
487
+ ### Create a scope
488
+
489
+ Returns added `KeycloakAdmin::ClientAuthzScopeRepresentation`
490
+
491
+ ```ruby
492
+ KeycloakAdmin.realm("a_realm").authz_scopes(client_id).create!("POST_1", "POST 1 scope description", "http://icon.url")
493
+ ````
494
+
495
+ ### Search for scope
496
+
497
+ Returns array of `KeycloakAdmin::ClientAuthzScopeRepresentation`
498
+
499
+ ```ruby
500
+ KeycloakAdmin.realm("a_realm").authz_scopes(client.id).search("POST")
501
+ ```
502
+
503
+ ### Get one scope by its id
504
+
505
+ Returns `KeycloakAdmin::ClientAuthzScopeRepresentation`
506
+
507
+ ```ruby
508
+ KeycloakAdmin.realm("a_realm").authz_scopes(client.id).get(scope_id)
509
+ ```
510
+
511
+ ### Delete one scope
512
+
513
+ ```ruby
514
+ KeycloakAdmin.realm("a_realm").authz_scopes(client.id).delete(scope.id)
515
+ ```
516
+
517
+ ### Create a client authorization resource
518
+
519
+ note: for scopes, use {name: scope.name} to reference the scope object
520
+
521
+ Returns added `KeycloakAdmin::ClientAuthzResourceRepresentation`
522
+
523
+ ```ruby
524
+ KeycloakAdmin.realm("realm_id")
525
+ .authz_resources(client.id)
526
+ .create!(
527
+ "Dummy Resource",
528
+ "type",
529
+ ["/resource_1/*", "/resource_1/"],
530
+ true,
531
+ "display_name",
532
+ [ {name: scope_1.name} ],
533
+ {"attribute": ["value_1", "value_2"]}
534
+ )
535
+ ```
536
+
537
+ ### Update a client authorization resource
538
+
539
+ Returns updated `KeycloakAdmin::ClientAuthzResourceRepresentation`
540
+
541
+ note: for scopes, use {name: scope.name} to reference the scope object
542
+
543
+ ```ruby
544
+ KeycloakAdmin.realm("realm_a")
545
+ .authz_resources(client.id)
546
+ .update(resource.id,
547
+ {
548
+ "name": "Dummy Resource",
549
+ "type": "type",
550
+ "owner_managed_access": true,
551
+ "display_name": "display_name",
552
+ "attributes": {"a":["b","c"]},
553
+ "uris": [ "/resource_1/*" , "/resource_1/" ],
554
+ "scopes":[
555
+ {name: scope_1.name},
556
+ {name: scope_2.name}
557
+ ],
558
+ "icon_uri": "https://icon.url"
559
+ })
560
+ ```
561
+
562
+ ### Find client authorization resources by (name, type, uri, owner, scope)
563
+
564
+ Returns array of `KeycloakAdmin::ClientAuthzResourceRepresentation`
565
+
566
+ ```ruby
567
+ KeycloakAdmin.realm("realm_a").authz_resources(client.id).find_by("Dummy Resource", "", "", "", "")
568
+ ```
569
+ or
570
+ ```ruby
571
+ KeycloakAdmin.realm("realm_a").authz_resources(client.id).find_by("", "type", "", "", "")
572
+ ```
573
+
574
+ ### Get client authorization resource by its id
575
+
576
+ Returns `KeycloakAdmin::ClientAuthzResourceRepresentation`
577
+
578
+ ```ruby
579
+ KeycloakAdmin.realm("realm_a").authz_resources(client.id).get(resource.id)
580
+ ```
581
+
582
+ ### delete a client authorization resource
583
+
584
+ ```ruby
585
+ KeycloakAdmin.realm("realm_a").authz_resources(client.id).delete(resource.id)
586
+ ```
587
+
588
+ ### Create a client authorization policy
589
+
590
+ Note: for the moment only `role` policies are supported.
591
+
592
+ Returns added `KeycloakAdmin::ClientAuthzPolicyRepresentation`
593
+
594
+ ```ruby
595
+ KeycloakAdmin.realm("realm_a")
596
+ .authz_policies(client.id, 'role')
597
+ .create!("Policy 1",
598
+ "description",
599
+ "role",
600
+ "POSITIVE",
601
+ "UNANIMOUS",
602
+ true,
603
+ [{id: realm_role.id, required: true}]
604
+ )
605
+ ```
606
+
607
+ ### Find client authorization policies by (name, type)
608
+
609
+ Returns array of `KeycloakAdmin::ClientAuthzPolicyRepresentation`
610
+
611
+ ```ruby
612
+ KeycloakAdmin.realm("realm_a").authz_policies(client.id, 'role').find_by("Policy 1", "role")
613
+ ```
614
+
615
+ ### Get client authorization policy by its id
616
+
617
+ Returns `KeycloakAdmin::ClientAuthzPolicyRepresentation`
618
+
619
+ ```ruby
620
+ KeycloakAdmin.realm("realm_a").authz_policies(client.id, 'role').get(policy.id)
621
+ ```
622
+
623
+ ### Delete a client authorization policy
624
+
625
+ ```ruby
626
+ KeycloakAdmin.realm("realm_a").authz_policies(client.id, 'role').delete(policy.id)
627
+ ```
628
+
629
+ ### Create a client authorization permission (Resource type)
630
+
631
+ Returns added `KeycloakAdmin::ClientAuthzPermissionRepresentation`
632
+
633
+ ```ruby
634
+ KeycloakAdmin.realm("realm_a")
635
+ .authz_permissions(client.id, :resource)
636
+ .create!("Dummy Resource Permission",
637
+ "resource description",
638
+ "UNANIMOUS",
639
+ "POSITIVE",
640
+ [resource.id],
641
+ [policy.id],
642
+ nil,
643
+ ""
644
+ )
645
+ ```
646
+
647
+ ### Create a client authorization permission (Scope type)
648
+
649
+ Returns added `KeycloakAdmin::ClientAuthzPermissionRepresentation`
650
+
651
+ ```ruby
652
+ KeycloakAdmin.realm("realm_a")
653
+ .authz_permissions(client.id, :scope)
654
+ .create!("Dummy Scope Permission",
655
+ "scope description",
656
+ "UNANIMOUS",
657
+ "POSITIVE",
658
+ [resource.id],
659
+ [policy.id],
660
+ [scope_1.id, scope_2.id],
661
+ ""
662
+ )
663
+ ```
664
+
665
+ ### List a resource authorization permissions (all: scope or resource)
666
+
667
+ Return array of `KeycloakAdmin::ClientAuthzPermissionRepresentation`
668
+
669
+ ```ruby
670
+ KeycloakAdmin.realm("realm_a").authz_permissions(client.id, "", resource.id).list
671
+ ```
672
+
673
+ ### List a resource authorization permissions (by type: resource)
674
+
675
+ Return array of `KeycloakAdmin::ClientAuthzPermissionRepresentation`
676
+
677
+ ```ruby
678
+ KeycloakAdmin.realm("realm_a").authz_permissions(client.id, 'resource').list
679
+ ```
680
+ ### List a resource authorization permissions (by type: scope)
681
+
682
+ Return array of `KeycloakAdmin::ClientAuthzPermissionRepresentation`
683
+
684
+ ```ruby
685
+ authz_permissions(client.id, 'scope').list.size
686
+ ```
687
+
688
+ ### Find client authorization permissions by (name, type, scope)
689
+
690
+ Return array of `KeycloakAdmin::ClientAuthzPermissionRepresentation`
691
+
692
+ ```ruby
693
+ KeycloakAdmin.realm("realm_a").authz_permissions(client.id, "resource").find_by(resource_permission.name, nil)
694
+ ```
695
+ or
696
+ ```ruby
697
+ KeycloakAdmin.realm("realm_a").authz_permissions(client.id, "resource").find_by(resource_permission.name, nil)
698
+ ```
699
+ or
700
+ ```ruby
701
+ KeycloakAdmin.realm("realm_a").authz_permissions(client.id, "resource").find_by(resource_permission.name, resource.id)
702
+
703
+ ```
704
+ or
705
+ ```ruby
706
+ KeycloakAdmin.realm("realm_a").authz_permissions(client.id, "scope").find_by(scope_permission.name, resource.id)
707
+ ```
708
+ or
709
+ ```ruby
710
+ KeycloakAdmin.realm("realm_a").authz_permissions(client.id, "scope").find_by(scope_permission.name, resource.id, "POST_1")
711
+ ```
712
+ or
713
+ ```ruby
714
+ KeycloakAdmin.realm("realm_a").authz_permissions(client.id, "resource").find_by(nil, resource.id)
715
+ ```
716
+ or
717
+ ```ruby
718
+ KeycloakAdmin.realm("realm_a").authz_permissions(client.id, "scope").find_by(nil, resource.id)
719
+ ```
720
+ or
721
+ ```ruby
722
+ KeycloakAdmin.realm("realm_a").authz_permissions(client.id, "scope").find_by(nil, resource.id, "POST_1")
723
+ ```
724
+ or
725
+ ```ruby
726
+ KeycloakAdmin.realm("realm_a").authz_permissions(client.id, "scope").find_by(scope_permission.name, nil)
727
+ ```
728
+
729
+ ### Delete a client authorization permission, scope type
730
+
731
+ ```ruby
732
+ KeycloakAdmin.realm("realm_a").authz_permissions(client.id, 'scope').delete(scope.id)
733
+ ```
734
+
735
+ ### Delete a client authorization permission, resource type
736
+
737
+ ```ruby
738
+ KeycloakAdmin.realm("realm_a").authz_permissions(client.id, 'resource').delete(resource_permission.id)
739
+ ```
740
+
468
741
  ## How to execute library tests
469
742
 
470
743
  From the `keycloak-admin-api` directory:
@@ -0,0 +1,81 @@
1
+ module KeycloakAdmin
2
+ class ClientAuthzPermissionClient < Client
3
+ def initialize(configuration, realm_client, client_id, type, resource_id = nil)
4
+ super(configuration)
5
+ raise ArgumentError.new("realm must be defined") unless realm_client.name_defined?
6
+ raise ArgumentError.new("bad permission type") if !resource_id && !%i[resource scope].include?(type.to_sym)
7
+
8
+ @realm_client = realm_client
9
+ @client_id = client_id
10
+ @type = type
11
+ @resource_id = resource_id
12
+ end
13
+
14
+ def delete(permission_id)
15
+ execute_http do
16
+ RestClient::Resource.new(authz_permission_url(@client_id, nil, nil, permission_id), @configuration.rest_client_options).delete(headers)
17
+ end
18
+ true
19
+ end
20
+
21
+ def find_by(name, resource, scope = nil)
22
+ response = execute_http do
23
+ url = "#{authz_permission_url(@client_id)}?name=#{name}&resource=#{resource}&type=#{@type}&scope=#{scope}&deep=true&first=0&max=100"
24
+ RestClient::Resource.new(url, @configuration.rest_client_options).get(headers)
25
+ end
26
+ JSON.parse(response).map { |role_as_hash| ClientAuthzPermissionRepresentation.from_hash(role_as_hash) }
27
+ end
28
+
29
+ def create!(name, description, decision_strategy,logic = "POSITIVE", resources = [], policies = [], scopes = [], resource_type = nil)
30
+ response = save(build(name, description, decision_strategy, logic, resources, policies, scopes, resource_type))
31
+ ClientAuthzPermissionRepresentation.from_hash(JSON.parse(response))
32
+ end
33
+
34
+ def save(permission_representation)
35
+ execute_http do
36
+ RestClient::Resource.new(authz_permission_url(@client_id, nil, permission_representation.type), @configuration.rest_client_options).post(
37
+ create_payload(permission_representation), headers
38
+ )
39
+ end
40
+ end
41
+
42
+ def list
43
+ response = execute_http do
44
+ RestClient::Resource.new(authz_permission_url(@client_id, @resource_id), @configuration.rest_client_options).get(headers)
45
+ end
46
+ JSON.parse(response).map { |role_as_hash| ClientAuthzPermissionRepresentation.from_hash(role_as_hash) }
47
+ end
48
+
49
+ def get(permission_id)
50
+ response = execute_http do
51
+ RestClient::Resource.new(authz_permission_url(@client_id, nil, @type, permission_id), @configuration.rest_client_options).get(headers)
52
+ end
53
+ ClientAuthzPermissionRepresentation.from_hash(JSON.parse(response))
54
+ end
55
+
56
+ def authz_permission_url(client_id, resource_id = nil, type = nil, id = nil)
57
+ if resource_id
58
+ "#{@realm_client.realm_admin_url}/clients/#{client_id}/authz/resource-server/resource/#{resource_id}/permissions"
59
+ elsif id
60
+ "#{@realm_client.realm_admin_url}/clients/#{client_id}/authz/resource-server/permission/#{type}/#{id}"
61
+ else
62
+ "#{@realm_client.realm_admin_url}/clients/#{client_id}/authz/resource-server/permission/#{type}"
63
+ end
64
+ end
65
+
66
+ def build(name, description, decision_strategy, logic, resources, policies, scopes, resource_type)
67
+ policy = ClientAuthzPermissionRepresentation.new
68
+ policy.name = name
69
+ policy.description = description
70
+ policy.type = @type
71
+ policy.decision_strategy = decision_strategy
72
+ policy.resource_type = resource_type
73
+ policy.resources = resources
74
+ policy.policies = policies
75
+ policy.scopes = scopes
76
+ policy.logic = logic
77
+ policy
78
+ end
79
+
80
+ end
81
+ end
@@ -0,0 +1,76 @@
1
+ module KeycloakAdmin
2
+ class ClientAuthzPolicyClient < Client
3
+ def initialize(configuration, realm_client, client_id, type)
4
+ super(configuration)
5
+ raise ArgumentError.new("realm must be defined") unless realm_client.name_defined?
6
+ raise ArgumentError.new("type must be defined") unless type
7
+ raise ArgumentError.new("only 'role' policies supported") unless type.to_sym == :role
8
+
9
+ @realm_client = realm_client
10
+ @client_id = client_id
11
+ @type = type
12
+ end
13
+
14
+ def create!(name, description, type, logic, decision_strategy, fetch_roles, roles)
15
+ response = save(build(name, description, type, logic, decision_strategy, fetch_roles, roles))
16
+ ClientAuthzPolicyRepresentation.from_hash(JSON.parse(response))
17
+ end
18
+
19
+ def save(policy_representation)
20
+ execute_http do
21
+ RestClient::Resource.new(authz_policy_url(@client_id, @type), @configuration.rest_client_options).post(
22
+ create_payload(policy_representation), headers
23
+ )
24
+ end
25
+ end
26
+
27
+ def get(policy_id)
28
+ response = execute_http do
29
+ RestClient::Resource.new(authz_policy_url(@client_id, @type, policy_id), @configuration.rest_client_options).get(headers)
30
+ end
31
+ ClientAuthzPolicyRepresentation.from_hash(JSON.parse(response))
32
+ end
33
+
34
+ def find_by(name, type)
35
+ response = execute_http do
36
+ url = "#{authz_policy_url(@client_id, @type)}?permission=false&name=#{name}&type=#{type}&first=0&max=11"
37
+ RestClient::Resource.new(url, @configuration.rest_client_options).get(headers)
38
+ end
39
+ JSON.parse(response).map { |role_as_hash| ClientAuthzPolicyRepresentation.from_hash(role_as_hash) }
40
+ end
41
+
42
+ def delete(policy_id)
43
+ execute_http do
44
+ RestClient::Resource.new(authz_policy_url(@client_id, @type, policy_id), @configuration.rest_client_options).delete(headers)
45
+ end
46
+ true
47
+ end
48
+
49
+ def list
50
+ response = execute_http do
51
+ RestClient::Resource.new(authz_policy_url(@client_id, @type), @configuration.rest_client_options).get(headers)
52
+ end
53
+ JSON.parse(response).map { |role_as_hash| ClientAuthzPolicyRepresentation.from_hash(role_as_hash) }
54
+ end
55
+
56
+ def authz_policy_url(client_id, type, id = nil)
57
+ if id
58
+ "#{@realm_client.realm_admin_url}/clients/#{client_id}/authz/resource-server/policy/#{type}/#{id}"
59
+ else
60
+ "#{@realm_client.realm_admin_url}/clients/#{client_id}/authz/resource-server/policy/#{type}?permission=false"
61
+ end
62
+ end
63
+
64
+ def build(name, description, type, logic, decision_strategy, fetch_roles, roles=[])
65
+ policy = ClientAuthzPolicyRepresentation.new
66
+ policy.name = name
67
+ policy.description = description
68
+ policy.type = type
69
+ policy.logic = logic
70
+ policy.decision_strategy = decision_strategy
71
+ policy.fetch_roles = fetch_roles
72
+ policy.roles = roles
73
+ policy
74
+ end
75
+ end
76
+ end