Skip to content

Commit

Permalink
adds client scope mapping of roles between clients (#242)
Browse files Browse the repository at this point in the history
  • Loading branch information
mukuru-shaun authored Mar 10, 2020
1 parent c78a9c3 commit dcfb7a3
Show file tree
Hide file tree
Showing 7 changed files with 299 additions and 0 deletions.
7 changes: 7 additions & 0 deletions example/roles.tf
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,13 @@ resource "keycloak_openid_hardcoded_role_protocol_mapper" "pet_app_pet_api_read_
role_id = "${keycloak_role.pet_api_read_pet.id}"
}

// Map a role from the "pet_api" api client to the "pet_app" consumer client
resource "keycloak_generic_client_role_mapper" "pet_app_pet_api_read_role_mapping" {
realm_id = "${keycloak_realm.roles_example.id}"
client_id = "${keycloak_openid_client.pet_app.id}"
role_id = "${keycloak_role.pet_api_read_pet.id}"
}

// Users and groups

resource "keycloak_group" "pet_api_base" {
Expand Down
13 changes: 13 additions & 0 deletions keycloak/generic_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,19 @@ func (keycloakClient *KeycloakClient) listGenericClients(realmId string) ([]*Gen
return clients, nil
}

func (keycloakClient *KeycloakClient) GetGenericClient(realmId, id string) (*GenericClient, error) {
var client GenericClient

err := keycloakClient.get(fmt.Sprintf("/realms/%s/clients/%s", realmId, id), &client, nil)
if err != nil {
return nil, err
}

client.RealmId = realmId

return &client, nil
}

func (keycloakClient *KeycloakClient) GetGenericClientByClientId(realmId, clientId string) (*GenericClient, error) {
var clients []GenericClient

Expand Down
43 changes: 43 additions & 0 deletions keycloak/role_scope_mapping.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package keycloak

import (
"fmt"
)

func roleScopeMappingUrl(realmId, clientId string, role *Role) string {
return fmt.Sprintf("/realms/%s/clients/%s/scope-mappings/clients/%s", realmId, clientId, role.ClientId)
}

func (keycloakClient *KeycloakClient) CreateRoleScopeMapping(realmId string, clientId string, role *Role) error {
roleUrl := roleScopeMappingUrl(realmId, clientId, role)

_, _, err := keycloakClient.post(roleUrl, []Role{*role})
if err != nil {
return err
}

return nil
}

func (keycloakClient *KeycloakClient) GetRoleScopeMapping(realmId string, clientId string, role *Role) (*Role, error) {
roleUrl := roleScopeMappingUrl(realmId, clientId, role)
var roles []Role

err := keycloakClient.get(roleUrl, &roles, nil)
if err != nil {
return nil, err
}

for _, mappedRole := range roles {
if mappedRole.Id == role.Id {
return role, nil
}
}

return nil, nil
}

func (keycloakClient *KeycloakClient) DeleteRoleScopeMapping(realmId string, clientId string, role *Role) error {
roleUrl := roleScopeMappingUrl(realmId, clientId, role)
return keycloakClient.delete(roleUrl, nil)
}
1 change: 1 addition & 0 deletions provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ func KeycloakProvider() *schema.Provider {
"keycloak_openid_client_optional_scopes": resourceKeycloakOpenidClientOptionalScopes(),
"keycloak_saml_client": resourceKeycloakSamlClient(),
"keycloak_generic_client_protocol_mapper": resourceKeycloakGenericClientProtocolMapper(),
"keycloak_generic_client_role_mapper": resourceKeycloakGenericClientRoleMapper(),
"keycloak_saml_user_attribute_protocol_mapper": resourceKeycloakSamlUserAttributeProtocolMapper(),
"keycloak_saml_user_property_protocol_mapper": resourceKeycloakSamlUserPropertyProtocolMapper(),
"keycloak_hardcoded_attribute_identity_provider_mapper": resourceKeycloakHardcodedAttributeIdentityProviderMapper(),
Expand Down
92 changes: 92 additions & 0 deletions provider/resource_keycloak_generic_client_role_mapper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package provider

import (
"fmt"

"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
"github.com/mrparkers/terraform-provider-keycloak/keycloak"
)

func resourceKeycloakGenericClientRoleMapper() *schema.Resource {
return &schema.Resource{
Create: resourceKeycloakGenericClientRoleMapperCreate,
Read: resourceKeycloakGenericClientRoleMapperRead,
Delete: resourceKeycloakGenericClientRoleMapperDelete,

Schema: map[string]*schema.Schema{
"realm_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"client_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"role_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
},
}
}

func resourceKeycloakGenericClientRoleMapperCreate(data *schema.ResourceData, meta interface{}) error {
keycloakClient := meta.(*keycloak.KeycloakClient)

realmId := data.Get("realm_id").(string)
clientId := data.Get("client_id").(string)
roleId := data.Get("role_id").(string)

role, err := keycloakClient.GetRole(realmId, roleId)
if err != nil {
return err
}

err = keycloakClient.CreateRoleScopeMapping(realmId, clientId, role)
if err != nil {
return err
}

data.SetId(fmt.Sprintf("%s/client/%s/scope-mappings/%s/%s", realmId, clientId, role.ClientId, role.Id))

return resourceKeycloakGenericClientRoleMapperRead(data, meta)
}

func resourceKeycloakGenericClientRoleMapperRead(data *schema.ResourceData, meta interface{}) error {
keycloakClient := meta.(*keycloak.KeycloakClient)

realmId := data.Get("realm_id").(string)
clientId := data.Get("client_id").(string)
roleId := data.Get("role_id").(string)

role, err := keycloakClient.GetRole(realmId, roleId)
if err != nil {
return err
}

mappedRole, err := keycloakClient.GetRoleScopeMapping(realmId, clientId, role)

if mappedRole == nil {
data.SetId("")
}

return nil
}

func resourceKeycloakGenericClientRoleMapperDelete(data *schema.ResourceData, meta interface{}) error {
keycloakClient := meta.(*keycloak.KeycloakClient)

realmId := data.Get("realm_id").(string)
clientId := data.Get("client_id").(string)
roleId := data.Get("role_id").(string)

role, err := keycloakClient.GetRole(realmId, roleId)
if err != nil {
return err
}

return keycloakClient.DeleteRoleScopeMapping(realmId, clientId, role)
}
142 changes: 142 additions & 0 deletions provider/resource_keycloak_generic_client_role_mapper_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package provider

import (
"fmt"
"github.com/hashicorp/terraform-plugin-sdk/helper/acctest"
"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/terraform"
"github.com/mrparkers/terraform-provider-keycloak/keycloak"
"testing"
)

func TestGenericRoleMapper_basic(t *testing.T) {
realmName := "terraform-" + acctest.RandString(10)
parentClientName := "client1-" + acctest.RandString(10)
parentRoleName := "role-" + acctest.RandString(10)
childClientName := "client2-" + acctest.RandString(10)

resource.Test(t, resource.TestCase{
Providers: testAccProviders,
PreCheck: func() { testAccPreCheck(t) },
Steps: []resource.TestStep{
{
Config: testKeycloakGenericRoleMapping_basic(realmName, parentClientName, parentRoleName, childClientName),
Check: testAccCheckKeycloakScopeMappingExists("keycloak_generic_client_role_mapper.child-client-with-parent-client-role"),
},
},
})
}

func TestGenericRoleMapper_createAfterManualDestroy(t *testing.T) {
var role = &keycloak.Role{}
var childClient = &keycloak.GenericClient{}

realmName := "terraform-" + acctest.RandString(10)
parentClientName := "client1-" + acctest.RandString(10)
parentRoleName := "role-" + acctest.RandString(10)
childClientName := "client2-" + acctest.RandString(10)

resource.Test(t, resource.TestCase{
Providers: testAccProviders,
PreCheck: func() { testAccPreCheck(t) },
Steps: []resource.TestStep{
{
Config: testKeycloakGenericRoleMapping_basic(realmName, parentClientName, parentRoleName, childClientName),
Check: resource.ComposeTestCheckFunc(
testAccCheckKeycloakScopeMappingExists("keycloak_generic_client_role_mapper.child-client-with-parent-client-role"),
testAccCheckKeycloakRoleFetch("keycloak_role.parent-role", role),
testAccCheckKeycloakGenericClientFetch("keycloak_openid_client.child-client", childClient),
),
},
{
PreConfig: func() {
keycloakClient := testAccProvider.Meta().(*keycloak.KeycloakClient)

err := keycloakClient.DeleteRoleScopeMapping(childClient.RealmId, childClient.Id, role)
if err != nil {
t.Fatal(err)
}
},
Config: testKeycloakGenericRoleMapping_basic(realmName, parentClientName, parentRoleName, childClientName),
Check: testAccCheckKeycloakScopeMappingExists("keycloak_generic_client_role_mapper.child-client-with-parent-client-role"),
},
},
})
}

func testKeycloakGenericRoleMapping_basic(realmName, parentClientName, parentRoleName, childClientName string) string {
return fmt.Sprintf(`
resource "keycloak_realm" "realm" {
realm = "%s"
}
resource "keycloak_openid_client" "parent-client" {
realm_id = "${keycloak_realm.realm.id}"
client_id = "%s"
access_type = "PUBLIC"
}
resource "keycloak_role" "parent-role" {
realm_id = "${keycloak_realm.realm.id}"
client_id = "${keycloak_openid_client.parent-client.id}"
name = "%s"
}
resource "keycloak_openid_client" "child-client" {
realm_id = "${keycloak_realm.realm.id}"
client_id = "%s"
access_type = "PUBLIC"
}
resource "keycloak_generic_client_role_mapper" "child-client-with-parent-client-role" {
realm_id = "${keycloak_realm.realm.id}"
client_id = "${keycloak_openid_client.child-client.id}"
role_id = "${keycloak_role.parent-role.id}"
}
`, realmName, parentClientName, parentRoleName, childClientName)
}

func testAccCheckKeycloakScopeMappingExists(resourceName string) resource.TestCheckFunc {
return func(s *terraform.State) error {
_, ok := s.RootModule().Resources[resourceName]
if !ok {
return fmt.Errorf("resource not found: %s", resourceName)
}

return nil
}
}

func testAccCheckKeycloakGenericClientFetch(resourceName string, client *keycloak.GenericClient) resource.TestCheckFunc {
return func(s *terraform.State) error {
fetchedClient, err := getGenericClientFromState(s, resourceName)
if err != nil {
return err
}

client.Id = fetchedClient.Id
client.ClientId = fetchedClient.ClientId
client.RealmId = fetchedClient.RealmId

return nil
}
}

func getGenericClientFromState(s *terraform.State, resourceName string) (*keycloak.GenericClient, error) {
keycloakClient := testAccProvider.Meta().(*keycloak.KeycloakClient)

rs, ok := s.RootModule().Resources[resourceName]
if !ok {
return nil, fmt.Errorf("resource not found: %s", resourceName)
}

id := rs.Primary.ID
realm := rs.Primary.Attributes["realm_id"]

client, err := keycloakClient.GetGenericClient(realm, id)
if err != nil {
return nil, fmt.Errorf("error getting generic client %s: %s", id, err)
}

return client, nil
}
1 change: 1 addition & 0 deletions provider/resource_keycloak_role_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,7 @@ func testAccCheckKeycloakRoleFetch(resourceName string, role *keycloak.Role) res
role.Id = fetchedRole.Id
role.Name = fetchedRole.Name
role.RealmId = fetchedRole.RealmId
role.ClientId = fetchedRole.ClientId

return nil
}
Expand Down

0 comments on commit dcfb7a3

Please sign in to comment.