Repository: mrparkers/terraform-provider-keycloak
Pull request #203 (2020-01-23 21:25:26)

Support admin_url attribute in openid clients
This PR adds support for the `admin_url` attribute when creating or updating openid clients


Patch:
diff --git a/docs/resources/keycloak_openid_client.md b/docs/resources/keycloak_openid_client.md
index 33f7d1e..ae8c9fb 100644
--- a/docs/resources/keycloak_openid_client.md
+++ b/docs/resources/keycloak_openid_client.md
@@ -53,6 +53,7 @@ should be treated with the same care as a password. If omitted, Keycloak will ge
 wildcards in the form of an asterisk can be used here. This attribute must be set if either `standard_flow_enabled` or `implicit_flow_enabled`
 is set to `true`.
 - `web_origins` - (Optional) A list of allowed CORS origins. `+` can be used to permit all valid redirect URIs, and `*` can be used to permit all origins.
+- `admin_url` - (Optional) URL to the admin interface of the client.
 - `base_url` - (Optional) Default URL to use when the auth server needs to redirect or link back to the client.
 - `pkce_code_challenge_method` - (Optional) The challenge method to use for Proof Key for Code Exchange. Can be either `plain` or `S256` or set to empty value ``.
 - `full_scope_allowed` - (Optional) - Allow to include all roles mappings in the access token.
@@ -62,7 +63,7 @@ is set to `true`.
 In addition to the arguments listed above, the following computed attributes are exported:
 
 - `service_account_user_id` - When service accounts are enabled for this client, this attribute is the unique ID for the Keycloak user that represents this service account.
- 
+
 
 ### Import
 
diff --git a/keycloak/openid_client.go b/keycloak/openid_client.go
index cf90e39..9abce61 100644
--- a/keycloak/openid_client.go
+++ b/keycloak/openid_client.go
@@ -43,6 +43,7 @@ type OpenidClient struct {
 	AuthorizationServicesEnabled bool                               `json:"authorizationServicesEnabled"`
 	ValidRedirectUris            []string                           `json:"redirectUris"`
 	WebOrigins                   []string                           `json:"webOrigins"`
+	AdminUrl                     string                             `json:"adminUrl"`
 	BaseUrl                      string                             `json:"baseUrl"`
 	FullScopeAllowed             bool                               `json:"fullScopeAllowed"`
 	Attributes                   OpenidClientAttributes             `json:"attributes"`
diff --git a/keycloak/openid_client_service_account_realm_role.go b/keycloak/openid_client_service_account_realm_role.go
new file mode 100644
index 0000000..1a50273
--- /dev/null
+++ b/keycloak/openid_client_service_account_realm_role.go
@@ -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
+}
diff --git a/provider/provider.go b/provider/provider.go
index d113364..77130c7 100644
--- a/provider/provider.go
+++ b/provider/provider.go
@@ -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{
diff --git a/provider/resource_keycloak_openid_client.go b/provider/resource_keycloak_openid_client.go
index bd17be1..f1ea4c3 100644
--- a/provider/resource_keycloak_openid_client.go
+++ b/provider/resource_keycloak_openid_client.go
@@ -86,6 +86,10 @@ func resourceKeycloakOpenidClient() *schema.Resource {
 				Set:      schema.HashString,
 				Optional: true,
 			},
+			"admin_url": {
+				Type:     schema.TypeString,
+				Optional: true,
+			},
 			"base_url": {
 				Type:     schema.TypeString,
 				Optional: true,
@@ -181,6 +185,7 @@ func getOpenidClientFromData(data *schema.ResourceData) (*keycloak.OpenidClient,
 		},
 		ValidRedirectUris: validRedirectUris,
 		WebOrigins:        webOrigins,
+		AdminUrl:          data.Get("admin_url").(string),
 		BaseUrl:           data.Get("base_url").(string),
 	}
 
@@ -240,6 +245,7 @@ func setOpenidClientData(keycloakClient *keycloak.KeycloakClient, data *schema.R
 	data.Set("service_accounts_enabled", client.ServiceAccountsEnabled)
 	data.Set("valid_redirect_uris", client.ValidRedirectUris)
 	data.Set("web_origins", client.WebOrigins)
+	data.Set("admin_url", client.AdminUrl)
 	data.Set("base_url", client.BaseUrl)
 	data.Set("authorization_services_enabled", client.AuthorizationServicesEnabled)
 	data.Set("full_scope_allowed", client.FullScopeAllowed)
diff --git a/provider/resource_keycloak_openid_client_service_account_realm_role.go b/provider/resource_keycloak_openid_client_service_account_realm_role.go
new file mode 100644
index 0000000..880b61f
--- /dev/null
+++ b/provider/resource_keycloak_openid_client_service_account_realm_role.go
@@ -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
+}


Test patch:
diff --git a/provider/resource_keycloak_openid_client_service_account_realm_role_test.go b/provider/resource_keycloak_openid_client_service_account_realm_role_test.go
new file mode 100644
index 0000000..443194f
--- /dev/null
+++ b/provider/resource_keycloak_openid_client_service_account_realm_role_test.go
@@ -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)
+}
diff --git a/provider/resource_keycloak_openid_client_test.go b/provider/resource_keycloak_openid_client_test.go
index 5be419d..f3b9ac1 100644
--- a/provider/resource_keycloak_openid_client_test.go
+++ b/provider/resource_keycloak_openid_client_test.go
@@ -120,6 +120,23 @@ func TestAccKeycloakOpenidClient_accessType(t *testing.T) {
 		},
 	})
 }
+func TestAccKeycloakOpenidClient_adminUrl(t *testing.T) {
+	realmName := "terraform-" + acctest.RandString(10)
+	clientId := "terraform-" + acctest.RandString(10)
+	adminUrl := "https://www.example.com/admin"
+
+	resource.Test(t, resource.TestCase{
+		Providers:    testAccProviders,
+		PreCheck:     func() { testAccPreCheck(t) },
+		CheckDestroy: testAccCheckKeycloakOpenidClientDestroy(),
+		Steps: []resource.TestStep{
+			{
+				Config: testKeycloakOpenidClient_adminUrl(realmName, clientId, adminUrl),
+				Check:  testAccCheckKeycloakOpenidClientAdminUrl("keycloak_openid_client.client", adminUrl),
+			},
+		},
+	})
+}
 
 func TestAccKeycloakOpenidClient_baseUrl(t *testing.T) {
 	realmName := "terraform-" + acctest.RandString(10)
@@ -165,6 +182,7 @@ func TestAccKeycloakOpenidClient_updateInPlace(t *testing.T) {
 		ServiceAccountsEnabled:    serviceAccountsEnabled,
 		ValidRedirectUris:         []string{acctest.RandString(10), acctest.RandString(10), acctest.RandString(10), acctest.RandString(10)},
 		WebOrigins:                []string{acctest.RandString(10), acctest.RandString(10), acctest.RandString(10)},
+		AdminUrl:                  acctest.RandString(20),
 		BaseUrl:                   acctest.RandString(20),
 	}
 
@@ -183,6 +201,7 @@ func TestAccKeycloakOpenidClient_updateInPlace(t *testing.T) {
 		ServiceAccountsEnabled:    !serviceAccountsEnabled,
 		ValidRedirectUris:         []string{acctest.RandString(10), acctest.RandString(10)},
 		WebOrigins:                []string{acctest.RandString(10), acctest.RandString(10), acctest.RandString(10), acctest.RandString(10), acctest.RandString(10)},
+		AdminUrl:                  acctest.RandString(20),
 		BaseUrl:                   acctest.RandString(20),
 	}
 
@@ -213,6 +232,21 @@ func TestAccKeycloakOpenidClient_updateInPlace(t *testing.T) {
 	})
 }
 
+func testAccCheckKeycloakOpenidClientAdminUrl(resourceName string, adminUrl string) resource.TestCheckFunc {
+	return func(s *terraform.State) error {
+		client, err := getOpenidClientFromState(s, resourceName)
+		if err != nil {
+			return err
+		}
+
+		if client.AdminUrl != adminUrl {
+			return fmt.Errorf("expected openid client to have adminUrl set to %s, but got %s", adminUrl, client.AdminUrl)
+		}
+
+		return nil
+	}
+}
+
 func TestAccKeycloakOpenidClient_secret(t *testing.T) {
 	realmName := "terraform-" + acctest.RandString(10)
 	clientId := "terraform-" + acctest.RandString(10)
@@ -602,6 +636,20 @@ resource "keycloak_openid_client" "client" {
 	`, realm, clientId, accessType)
 }
 
+func testKeycloakOpenidClient_adminUrl(realm, clientId, adminUrl string) string {
+	return fmt.Sprintf(`
+resource "keycloak_realm" "realm" {
+	realm = "%s"
+}
+resource "keycloak_openid_client" "client" {
+	client_id   = "%s"
+	realm_id    = "${keycloak_realm.realm.id}"
+	admin_url = "%s"
+	access_type = "PUBLIC"
+}
+	`, realm, clientId, adminUrl)
+}
+
 func testKeycloakOpenidClient_baseUrl(realm, clientId, baseUrl string) string {
 	return fmt.Sprintf(`
 resource "keycloak_realm" "realm" {
@@ -739,9 +787,10 @@ resource "keycloak_openid_client" "client" {
 
 	valid_redirect_uris          = %s
 	web_origins                  = %s
+	admin_url					 = "%s"
 	base_url                     = "%s"
 }
-	`, openidClient.RealmId, openidClient.ClientId, openidClient.Name, openidClient.Enabled, openidClient.Description, openidClient.ClientSecret, openidClient.StandardFlowEnabled, openidClient.ImplicitFlowEnabled, openidClient.DirectAccessGrantsEnabled, openidClient.ServiceAccountsEnabled, arrayOfStringsForTerraformResource(openidClient.ValidRedirectUris), arrayOfStringsForTerraformResource(openidClient.WebOrigins), openidClient.BaseUrl)
+	`, openidClient.RealmId, openidClient.ClientId, openidClient.Name, openidClient.Enabled, openidClient.Description, openidClient.ClientSecret, openidClient.StandardFlowEnabled, openidClient.ImplicitFlowEnabled, openidClient.DirectAccessGrantsEnabled, openidClient.ServiceAccountsEnabled, arrayOfStringsForTerraformResource(openidClient.ValidRedirectUris), arrayOfStringsForTerraformResource(openidClient.WebOrigins), openidClient.AdminUrl, openidClient.BaseUrl)
 }
 
 func testKeycloakOpenidClient_secret(realm, clientId, clientSecret string) string {