Skip to content

Commit

Permalink
feat: Support PKCE on OIDC clients
Browse files Browse the repository at this point in the history
Fixes #169
  • Loading branch information
PSanetra committed Oct 23, 2019
1 parent 02c3ed9 commit 111c2c7
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 2 deletions.
5 changes: 5 additions & 0 deletions keycloak/openid_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,14 @@ type OpenidClient struct {
AuthorizationServicesEnabled bool `json:"authorizationServicesEnabled"`
ValidRedirectUris []string `json:"redirectUris"`
WebOrigins []string `json:"webOrigins"`
Attributes OpenidClientAttributes `json:"attributes"`
AuthorizationSettings *OpenidClientAuthorizationSettings `json:"authorizationSettings,omitempty"`
}

type OpenidClientAttributes struct {
PkceCodeChallengeMethod string `json:"pkce.code.challenge.method"`
}

func (keycloakClient *KeycloakClient) GetOpenidClientServiceAccountUserId(realmId, clientId string) (*User, error) {
var serviceAccountUser User
err := keycloakClient.get(fmt.Sprintf("/realms/%s/clients/%s/service-account-user", realmId, clientId), &serviceAccountUser, nil)
Expand Down
13 changes: 11 additions & 2 deletions provider/resource_keycloak_openid_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
var (
keycloakOpenidClientAccessTypes = []string{"CONFIDENTIAL", "PUBLIC", "BEARER-ONLY"}
keycloakOpenidClientAuthorizationPolicyEnforcementMode = []string{"ENFORCING", "PERMISSIVE", "DISABLED"}
keycloakOpenidClientPkceCodeChallengeMethod = []string{"", "plain", "S256"}
)

func resourceKeycloakOpenidClient() *schema.Resource {
Expand Down Expand Up @@ -90,6 +91,11 @@ func resourceKeycloakOpenidClient() *schema.Resource {
Optional: true,
Default: false,
},
"pkce_code_challenge_method": {
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.StringInSlice(keycloakOpenidClientPkceCodeChallengeMethod, false),
},
"service_account_user_id": {
Type: schema.TypeString,
Computed: true,
Expand Down Expand Up @@ -154,8 +160,11 @@ func getOpenidClientFromData(data *schema.ResourceData) (*keycloak.OpenidClient,
ImplicitFlowEnabled: data.Get("implicit_flow_enabled").(bool),
DirectAccessGrantsEnabled: data.Get("direct_access_grants_enabled").(bool),
ServiceAccountsEnabled: data.Get("service_accounts_enabled").(bool),
ValidRedirectUris: validRedirectUris,
WebOrigins: webOrigins,
Attributes: keycloak.OpenidClientAttributes{
PkceCodeChallengeMethod: data.Get("pkce_code_challenge_method").(string),
},
ValidRedirectUris: validRedirectUris,
WebOrigins: webOrigins,
}

if !openidClient.ImplicitFlowEnabled && !openidClient.StandardFlowEnabled {
Expand Down
60 changes: 60 additions & 0 deletions provider/resource_keycloak_openid_client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,35 @@ func TestAccKeycloakOpenidClient_bearerClientNoGrantsValidation(t *testing.T) {
})
}

func TestAccKeycloakOpenidClient_pkceCodeChallengeMethod(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: testAccCheckKeycloakOpenidClientDestroy(),
Steps: []resource.TestStep{
{
Config: testKeycloakOpenidClient_pkceChallengeMethod(realmName, clientId, "invalidMethod"),
ExpectError: regexp.MustCompile(`config is invalid: expected pkce_code_challenge_method to be one of \[ plain S256\], got invalidMethod`),
},
{
Config: testKeycloakOpenidClient_pkceChallengeMethod(realmName, clientId, ""),
Check: testAccCheckKeycloakOpenidClientHasPkceCodeChallengeMethod("keycloak_openid_client.client", ""),
},
{
Config: testKeycloakOpenidClient_pkceChallengeMethod(realmName, clientId, "plain"),
Check: testAccCheckKeycloakOpenidClientHasPkceCodeChallengeMethod("keycloak_openid_client.client", "plain"),
},
{
Config: testKeycloakOpenidClient_pkceChallengeMethod(realmName, clientId, "S256"),
Check: testAccCheckKeycloakOpenidClientHasPkceCodeChallengeMethod("keycloak_openid_client.client", "S256"),
},
},
})
}

func testAccCheckKeycloakOpenidClientExistsWithCorrectProtocol(resourceName string) resource.TestCheckFunc {
return func(s *terraform.State) error {
client, err := getOpenidClientFromState(s, resourceName)
Expand Down Expand Up @@ -403,6 +432,21 @@ func testAccCheckKeycloakOpenidClientDestroy() resource.TestCheckFunc {
}
}

func testAccCheckKeycloakOpenidClientHasPkceCodeChallengeMethod(resourceName, pkceCodeChallengeMethod string) resource.TestCheckFunc {
return func(s *terraform.State) error {
client, err := getOpenidClientFromState(s, resourceName)
if err != nil {
return err
}

if client.Attributes.PkceCodeChallengeMethod != pkceCodeChallengeMethod {
return fmt.Errorf("expected openid client %s to have pkce code challenge method value of %s, but got %s", client.ClientId, pkceCodeChallengeMethod, client.ClientSecret)
}

return nil
}
}

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

Expand Down Expand Up @@ -450,6 +494,22 @@ resource "keycloak_openid_client" "client" {
`, realm, clientId, accessType)
}

func testKeycloakOpenidClient_pkceChallengeMethod(realm, clientId, pkceChallengeMethod 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}"
access_type = "CONFIDENTIAL"
pkce_code_challenge_method = "%s"
}
`, realm, clientId, pkceChallengeMethod)
}

func testKeycloakOpenidClient_updateRealmBefore(realmOne, realmTwo, clientId string) string {
return fmt.Sprintf(`
resource "keycloak_realm" "realm_1" {
Expand Down

0 comments on commit 111c2c7

Please sign in to comment.