Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Service account realm roles support #202

Merged
59 changes: 59 additions & 0 deletions keycloak/openid_client_service_account_realm_role.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package keycloak

import (
"fmt"
)

type OpenidClientServiceAccountRealmRole struct {
Id string `json:"id"`
RealmId string `json:"-"`
ServiceAccountUserId string `json:"-"`
Name string `json:"name,omitempty"`
Description string `json:"description"`
}

func (keycloakClient *KeycloakClient) NewOpenidClientServiceAccountRealmRole(serviceAccountRole *OpenidClientServiceAccountRealmRole) error {
serviceAccountRoles := []OpenidClientServiceAccountRealmRole{*serviceAccountRole}

_, _, err := keycloakClient.post(fmt.Sprintf("/realms/%s/users/%s/role-mappings/realm", serviceAccountRole.RealmId, serviceAccountRole.ServiceAccountUserId), serviceAccountRoles)

if err != nil {
return err
}
return nil
}

func (keycloakClient *KeycloakClient) DeleteOpenidClientServiceAccountRealmRole(realm, serviceAccountUserId, roleId string) error {
serviceAccountRole, err := keycloakClient.GetOpenidClientServiceAccountRealmRole(realm, serviceAccountUserId, roleId)
if err != nil {
return err
}
serviceAccountRoles := []OpenidClientServiceAccountRealmRole{*serviceAccountRole}
err = keycloakClient.delete(fmt.Sprintf("/realms/%s/users/%s/role-mappings/realm", realm, serviceAccountUserId), &serviceAccountRoles)
if err != nil {
return err
}
return nil
}

func (keycloakClient *KeycloakClient) GetOpenidClientServiceAccountRealmRole(realm, serviceAccountUserId, roleId string) (*OpenidClientServiceAccountRealmRole, error) {
serviceAccountRoles := []OpenidClientServiceAccountRealmRole{
{
Id: roleId,
RealmId: realm,
ServiceAccountUserId: serviceAccountUserId,
},
}
err := keycloakClient.get(fmt.Sprintf("/realms/%s/users/%s/role-mappings/realm", realm, serviceAccountUserId), &serviceAccountRoles, nil)
if err != nil {
return nil, err
}
for _, serviceAccountRole := range serviceAccountRoles {
if serviceAccountRole.Id == roleId {
serviceAccountRole.RealmId = realm
serviceAccountRole.ServiceAccountUserId = serviceAccountUserId
return &serviceAccountRole, nil
}
}
return &OpenidClientServiceAccountRealmRole{}, nil
}
1 change: 1 addition & 0 deletions provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ func KeycloakProvider() *schema.Provider {
"keycloak_openid_client_authorization_scope": resourceKeycloakOpenidClientAuthorizationScope(),
"keycloak_openid_client_authorization_permission": resourceKeycloakOpenidClientAuthorizationPermission(),
"keycloak_openid_client_service_account_role": resourceKeycloakOpenidClientServiceAccountRole(),
"keycloak_openid_client_service_account_realm_role": resourceKeycloakOpenidClientServiceAccountRealmRole(),
"keycloak_role": resourceKeycloakRole(),
},
Schema: map[string]*schema.Schema{
Expand Down
125 changes: 125 additions & 0 deletions provider/resource_keycloak_openid_client_service_account_realm_role.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package provider

import (
"fmt"
"github.com/hashicorp/terraform/helper/schema"
"github.com/mrparkers/terraform-provider-keycloak/keycloak"
"strings"
)

func resourceKeycloakOpenidClientServiceAccountRealmRole() *schema.Resource {
return &schema.Resource{
Create: resourceKeycloakOpenidClientServiceAccountRealmRoleCreate,
Read: resourceKeycloakOpenidClientServiceAccountRealmRoleRead,
Delete: resourceKeycloakOpenidClientServiceAccountRealmRoleDelete,
Importer: &schema.ResourceImporter{
State: resourceKeycloakOpenidClientServiceAccountRealmRoleImport,
},
Schema: map[string]*schema.Schema{
"service_account_user_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"realm_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"role": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
},
}
}

func getOpenidClientServiceAccountRealmRoleFromData(data *schema.ResourceData, keycloakClient *keycloak.KeycloakClient) (*keycloak.OpenidClientServiceAccountRealmRole, error) {
roleName := data.Get("role").(string)
realmId := data.Get("realm_id").(string)
serviceAccountRoleId := data.Get("service_account_user_id").(string)

role, err := keycloakClient.GetRoleByName(realmId, "", roleName)
if err != nil {
if keycloak.ErrorIs404(err) {
role = &keycloak.Role{Id: ""}
} else {
return nil, err
}
}

return &keycloak.OpenidClientServiceAccountRealmRole{
Id: role.Id,
Name: roleName,
RealmId: realmId,
ServiceAccountUserId: serviceAccountRoleId,
}, nil
}

func setOpenidClientServiceAccountRealmRoleData(data *schema.ResourceData, serviceAccountRole *keycloak.OpenidClientServiceAccountRealmRole) {
data.SetId(fmt.Sprintf("%s/%s", serviceAccountRole.ServiceAccountUserId, serviceAccountRole.Id))
data.Set("realm_id", serviceAccountRole.RealmId)
data.Set("service_account_user_id", serviceAccountRole.ServiceAccountUserId)
data.Set("role", serviceAccountRole.Name)
}

func resourceKeycloakOpenidClientServiceAccountRealmRoleCreate(data *schema.ResourceData, meta interface{}) error {
keycloakClient := meta.(*keycloak.KeycloakClient)
serviceAccountRole, err := getOpenidClientServiceAccountRealmRoleFromData(data, keycloakClient)
if err != nil {
return err
}

err = keycloakClient.NewOpenidClientServiceAccountRealmRole(serviceAccountRole)
if err != nil {
return err
}
setOpenidClientServiceAccountRealmRoleData(data, serviceAccountRole)
return resourceKeycloakOpenidClientServiceAccountRealmRoleRead(data, meta)
}

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

serviceAccountRole, err := getOpenidClientServiceAccountRealmRoleFromData(data, keycloakClient)
if err != nil {
return err
}

serviceAccountRole, err = keycloakClient.GetOpenidClientServiceAccountRealmRole(serviceAccountRole.RealmId, serviceAccountRole.ServiceAccountUserId, serviceAccountRole.Id)
if err != nil {
return handleNotFoundError(err, data)
}

setOpenidClientServiceAccountRealmRoleData(data, serviceAccountRole)

return nil
}

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

serviceAccountRole, err := getOpenidClientServiceAccountRealmRoleFromData(data, keycloakClient)
if err != nil {
return err
}

err = keycloakClient.DeleteOpenidClientServiceAccountRealmRole(serviceAccountRole.RealmId, serviceAccountRole.ServiceAccountUserId, serviceAccountRole.Id)
if err != nil {
return handleNotFoundError(err, data)
}
return nil
}

func resourceKeycloakOpenidClientServiceAccountRealmRoleImport(d *schema.ResourceData, _ interface{}) ([]*schema.ResourceData, error) {
parts := strings.Split(d.Id(), "/")
if len(parts) != 2 {
return nil, fmt.Errorf("Invalid import. Supported import formats: {{realmId}}/{{serviceAccountUserId}}/{{roleId}}")
}
d.Set("realm_id", parts[0])
d.Set("service_account_user_id", parts[1])
d.SetId(fmt.Sprintf("%s/%s", parts[1], parts[2]))

return []*schema.ResourceData{d}, nil
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
package provider

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

func TestAccKeycloakOpenidClientServiceAccountRealmRole_basic(t *testing.T) {
realmName := "terraform-" + acctest.RandString(10)
clientId := "terraform-" + acctest.RandString(10)

resource.Test(t, resource.TestCase{
Providers: testAccProviders,
PreCheck: func() { testAccPreCheck(t) },
CheckDestroy: testAccCheckKeycloakOpenidClientServiceAccountRealmRoleDestroy(),
Steps: []resource.TestStep{
{
Config: testKeycloakOpenidClientServiceAccountRealmRole_basic(realmName, clientId),
Check: testAccCheckKeycloakOpenidClientServiceAccountRealmRoleExists("keycloak_openid_client_service_account_realm_role.test"),
},
},
})
}

func TestAccKeycloakOpenidClientServiceAccountRealmRole_createAfterManualDestroy(t *testing.T) {
var serviceAccountRole = &keycloak.OpenidClientServiceAccountRealmRole{}

realmName := "terraform-" + acctest.RandString(10)
clientId := "terraform-" + acctest.RandString(10)

resource.Test(t, resource.TestCase{
Providers: testAccProviders,
PreCheck: func() { testAccPreCheck(t) },
CheckDestroy: testAccCheckKeycloakOpenidClientServiceAccountRealmRoleDestroy(),
Steps: []resource.TestStep{
{
Config: testKeycloakOpenidClientServiceAccountRealmRole_basic(realmName, clientId),
Check: testAccCheckKeycloakOpenidClientServiceAccountRealmRoleFetch("keycloak_openid_client_service_account_realm_role.test", serviceAccountRole),
},
{
PreConfig: func() {
keycloakClient := testAccProvider.Meta().(*keycloak.KeycloakClient)

err := keycloakClient.DeleteOpenidClientServiceAccountRealmRole(serviceAccountRole.RealmId, serviceAccountRole.ServiceAccountUserId, serviceAccountRole.Id)
if err != nil {
t.Fatal(err)
}
},
Config: testKeycloakOpenidClientServiceAccountRealmRole_basic(realmName, clientId),
Check: testAccCheckKeycloakOpenidClientServiceAccountRealmRoleExists("keycloak_openid_client_service_account_realm_role.test"),
},
},
})
}

func TestAccKeycloakOpenidClientServiceAccountRealmRole_basicUpdateRealm(t *testing.T) {
firstRealm := "terraform-" + acctest.RandString(10)
secondRealm := "terraform-" + acctest.RandString(10)
clientId := "terraform-" + acctest.RandString(10)

resource.Test(t, resource.TestCase{
Providers: testAccProviders,
PreCheck: func() { testAccPreCheck(t) },
CheckDestroy: testAccCheckKeycloakOpenidClientServiceAccountRealmRoleDestroy(),
Steps: []resource.TestStep{
{
Config: testKeycloakOpenidClientServiceAccountRealmRole_basic(firstRealm, clientId),
Check: resource.ComposeTestCheckFunc(
testAccCheckKeycloakOpenidClientServiceAccountRealmRoleExists("keycloak_openid_client_service_account_realm_role.test"),
resource.TestCheckResourceAttr("keycloak_openid_client_service_account_realm_role.test", "realm_id", firstRealm),
),
},
{
Config: testKeycloakOpenidClientServiceAccountRealmRole_basic(secondRealm, clientId),
Check: resource.ComposeTestCheckFunc(
testAccCheckKeycloakOpenidClientServiceAccountRealmRoleExists("keycloak_openid_client_service_account_realm_role.test"),
resource.TestCheckResourceAttr("keycloak_openid_client_service_account_realm_role.test", "realm_id", secondRealm),
),
},
},
})
}

func testAccCheckKeycloakOpenidClientServiceAccountRealmRoleExists(resourceName string) resource.TestCheckFunc {
return func(s *terraform.State) error {
_, err := getKeycloakOpenidClientServiceAccountRealmRoleFromState(s, resourceName)
if err != nil {
return err
}

return nil
}
}

func testAccCheckKeycloakOpenidClientServiceAccountRealmRoleFetch(resourceName string, serviceAccountRole *keycloak.OpenidClientServiceAccountRealmRole) resource.TestCheckFunc {
return func(s *terraform.State) error {
fetchedServiceAccountRole, err := getKeycloakOpenidClientServiceAccountRealmRoleFromState(s, resourceName)
if err != nil {
return err
}

serviceAccountRole.ServiceAccountUserId = fetchedServiceAccountRole.ServiceAccountUserId
serviceAccountRole.RealmId = fetchedServiceAccountRole.RealmId
serviceAccountRole.Id = fetchedServiceAccountRole.Id

return nil
}
}

func testAccCheckKeycloakOpenidClientServiceAccountRealmRoleDestroy() resource.TestCheckFunc {
return func(s *terraform.State) error {
for _, rs := range s.RootModule().Resources {
if rs.Type != "keycloak_openid_client_service_account_realm_role" {
continue
}

realmId := rs.Primary.Attributes["realm_id"]
serviceAccountUserId := rs.Primary.Attributes["service_account_user_id"]
id := strings.Split(rs.Primary.ID, "/")[1]

keycloakClient := testAccProvider.Meta().(*keycloak.KeycloakClient)

serviceAccountRole, _ := keycloakClient.GetOpenidClientServiceAccountRealmRole(realmId, serviceAccountUserId, id)
if serviceAccountRole != nil {
return fmt.Errorf("service account role exists")
}
}

return nil
}
}

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

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

realmId := rs.Primary.Attributes["realm_id"]
serviceAccountUserId := rs.Primary.Attributes["service_account_user_id"]
id := strings.Split(rs.Primary.ID, "/")[1]

serviceAccountRole, err := keycloakClient.GetOpenidClientServiceAccountRealmRole(realmId, serviceAccountUserId, id)
if err != nil {
return nil, fmt.Errorf("error getting service account role mapping: %s", err)
}

return serviceAccountRole, nil
}

func testKeycloakOpenidClientServiceAccountRealmRole_basic(realm, clientId string) string {
return fmt.Sprintf(`
resource keycloak_realm test {
realm = "%s"
}

resource keycloak_openid_client test {
client_id = "%s"
realm_id = "${keycloak_realm.test.id}"
access_type = "CONFIDENTIAL"
service_accounts_enabled = true
}

resource keycloak_openid_client_service_account_realm_role test {
service_account_user_id = "${keycloak_openid_client.test.service_account_user_id}"
realm_id = "${keycloak_realm.test.id}"
role = "offline_access"
}
`, realm, clientId)
}