Skip to content

Commit

Permalink
feat: new resource - keycloak_openid_audience_resolve_protocol_mapper (
Browse files Browse the repository at this point in the history
  • Loading branch information
thyming authored Oct 8, 2021
1 parent a9cb860 commit 51479ea
Show file tree
Hide file tree
Showing 6 changed files with 558 additions and 0 deletions.
78 changes: 78 additions & 0 deletions docs/resources/openid_audience_resolve_protocol_mapper.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
---
page_title: "keycloak_openid_audience_resolve_protocol_mapper Resource"
---

# keycloak\_openid\_audience\_resolve\_protocol\_mapper Resource

Allows for creating the "Audience Resolve" OIDC protocol mapper within Keycloak.

This protocol mapper is useful to avoid manual management of audiences, instead relying on the presence of client roles
to imply which audiences are appropriate for the token. See the
[Keycloak docs](https://www.keycloak.org/docs/latest/server_admin/#_audience_resolve) for more details.

## Example Usage (Client)

```hcl
resource "keycloak_realm" "realm" {
realm = "my-realm"
enabled = true
}
resource "keycloak_openid_client" "openid_client" {
realm_id = keycloak_realm.realm.id
client_id = "client"
name = "client"
enabled = true
access_type = "CONFIDENTIAL"
valid_redirect_uris = [
"http://localhost:8080/openid-callback"
]
}
resource "keycloak_openid_audience_resolve_protocol_mapper" "audience_mapper" {
realm_id = keycloak_realm.realm.id
client_id = keycloak_openid_client.openid_client.id
name = "my-audience-resolve-mapper"
}
```

## Example Usage (Client Scope)

```hcl
resource "keycloak_realm" "realm" {
realm = "my-realm"
enabled = true
}
resource "keycloak_openid_client_scope" "client_scope" {
realm_id = keycloak_realm.realm.id
name = "test-client-scope"
}
resource "keycloak_openid_audience_protocol_mapper" "audience_mapper" {
realm_id = keycloak_realm.realm.id
client_scope_id = keycloak_openid_client_scope.client_scope.id
}
```

## Argument Reference

- `realm_id` - (Required) The realm this protocol mapper exists within.
- `name` - (Optional) The display name of this protocol mapper in the GUI. Defaults to "audience resolve".
- `client_id` - (Optional) The client this protocol mapper should be attached to. Conflicts with `client_scope_id`. One of `client_id` or `client_scope_id` must be specified.
- `client_scope_id` - (Optional) The client scope this protocol mapper should be attached to. Conflicts with `client_id`. One of `client_id` or `client_scope_id` must be specified.

## Import

Protocol mappers can be imported using one of the following formats:
- Client: `{{realm_id}}/client/{{client_keycloak_id}}/{{protocol_mapper_id}}`
- Client Scope: `{{realm_id}}/client-scope/{{client_scope_keycloak_id}}/{{protocol_mapper_id}}`

Example:

```bash
$ terraform import keycloak_openid_audience_protocol_mapper.audience_mapper my-realm/client/a7202154-8793-4656-b655-1dd18c181e14/71602afa-f7d1-4788-8c49-ef8fd00af0f4
$ terraform import keycloak_openid_audience_protocol_mapper.audience_mapper my-realm/client-scope/b799ea7e-73ee-4a73-990a-1eafebe8e20a/71602afa-f7d1-4788-8c49-ef8fd00af0f4
```
7 changes: 7 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki
github.com/agext/levenshtein v1.2.2 h1:0S/Yg6LYmFJ5stwQeRp6EeOcCbj7xiqQSdNelsXvaqE=
github.com/agext/levenshtein v1.2.2/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
github.com/andybalholm/crlf v0.0.0-20171020200849-670099aa064f/go.mod h1:k8feO4+kXDxro6ErPXBRTJ/ro2mf0SsFG8s7doP9kJE=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/apparentlymart/go-cidr v1.0.1 h1:NmIwLZ/KdsjIUlhf+/Np40atNXm/+lZ5txfTJ/SpF+U=
github.com/apparentlymart/go-cidr v1.0.1/go.mod h1:EBcsNrHc3zQeuaeCeCtQruQm+n9/YjEn/vI25Lg7Gwc=
Expand All @@ -60,6 +61,7 @@ github.com/apparentlymart/go-textseg/v12 v12.0.0/go.mod h1:S/4uRK2UtaQttw1GenVJE
github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw=
github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/aws/aws-sdk-go v1.15.78/go.mod h1:E3/ieXAlvM0XWO57iftYVDLLvQ824smPP3ATZkfNZeM=
github.com/aws/aws-sdk-go v1.25.3 h1:uM16hIw9BotjZKMZlX05SN2EFtaWfi/NonPKIARiBLQ=
Expand All @@ -86,13 +88,16 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0=
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4=
github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E=
github.com/go-git/go-billy/v5 v5.2.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
github.com/go-git/go-billy/v5 v5.3.1 h1:CPiOUAzKtMRvolEKw+bG1PLRpT7D3LIs3/3ey4Aiu34=
github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
github.com/go-git/go-git-fixtures/v4 v4.2.1 h1:n9gGL1Ct/yIw+nfsfr8s4+sbhT+Ncu2SubfXjIWgci8=
github.com/go-git/go-git-fixtures/v4 v4.2.1/go.mod h1:K8zd3kDUAykwTdDCr+I0per6Y6vMiRR/nnVTBtavnB0=
github.com/go-git/go-git/v5 v5.4.2 h1:BXyZu9t0VkbiHtqrsvdq39UDhGJTl1h55VW6CSC4aY4=
github.com/go-git/go-git/v5 v5.4.2/go.mod h1:gQ1kArt6d+n+BGd+/B/I74HwRTLhth2+zti4ihgckDc=
Expand Down Expand Up @@ -232,6 +237,7 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A=
github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
Expand Down Expand Up @@ -268,6 +274,7 @@ github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw=
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
Expand Down
86 changes: 86 additions & 0 deletions keycloak/openid_audience_resolve_protocol_mapper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package keycloak

import (
"fmt"
)

type OpenIdAudienceResolveProtocolMapper struct {
Id string
Name string
RealmId string
ClientId string
ClientScopeId string
}

func (mapper *OpenIdAudienceResolveProtocolMapper) convertToGenericProtocolMapper() *protocolMapper {
return &protocolMapper{
Id: mapper.Id,
Name: mapper.Name,
Protocol: "openid-connect",
ProtocolMapper: "oidc-audience-resolve-mapper",
Config: map[string]string{},
}
}

func (protocolMapper *protocolMapper) convertToOpenIdAudienceResolveProtocolMapper(realmId, clientId, clientScopeId string) (*OpenIdAudienceResolveProtocolMapper, error) {
return &OpenIdAudienceResolveProtocolMapper{
Id: protocolMapper.Id,
Name: protocolMapper.Name,
RealmId: realmId,
ClientId: clientId,
ClientScopeId: clientScopeId,
}, nil
}

func (keycloakClient *KeycloakClient) GetOpenIdAudienceResolveProtocolMapper(realmId, clientId, clientScopeId, mapperId string) (*OpenIdAudienceResolveProtocolMapper, error) {
var protocolMapper *protocolMapper

err := keycloakClient.get(individualProtocolMapperPath(realmId, clientId, clientScopeId, mapperId), &protocolMapper, nil)
if err != nil {
return nil, err
}

return protocolMapper.convertToOpenIdAudienceResolveProtocolMapper(realmId, clientId, clientScopeId)
}

func (keycloakClient *KeycloakClient) DeleteOpenIdAudienceResolveProtocolMapper(realmId, clientId, clientScopeId, mapperId string) error {
return keycloakClient.delete(individualProtocolMapperPath(realmId, clientId, clientScopeId, mapperId), nil)
}

func (keycloakClient *KeycloakClient) NewOpenIdAudienceResolveProtocolMapper(mapper *OpenIdAudienceResolveProtocolMapper) error {
path := protocolMapperPath(mapper.RealmId, mapper.ClientId, mapper.ClientScopeId)

_, location, err := keycloakClient.post(path, mapper.convertToGenericProtocolMapper())
if err != nil {
return err
}

mapper.Id = getIdFromLocationHeader(location)

return nil
}

func (keycloakClient *KeycloakClient) UpdateOpenIdAudienceResolveProtocolMapper(mapper *OpenIdAudienceResolveProtocolMapper) error {
path := individualProtocolMapperPath(mapper.RealmId, mapper.ClientId, mapper.ClientScopeId, mapper.Id)

return keycloakClient.put(path, mapper.convertToGenericProtocolMapper())
}

func (keycloakClient *KeycloakClient) ValidateOpenIdAudienceResolveProtocolMapper(mapper *OpenIdAudienceResolveProtocolMapper) error {
if mapper.ClientId == "" && mapper.ClientScopeId == "" {
return fmt.Errorf("validation error: one of ClientId or ClientScopeId must be set")
}

protocolMappers, err := keycloakClient.listGenericProtocolMappers(mapper.RealmId, mapper.ClientId, mapper.ClientScopeId)
if err != nil {
return err
}

for _, protocolMapper := range protocolMappers {
if protocolMapper.Name == mapper.Name && protocolMapper.Id != mapper.Id {
return fmt.Errorf("validation error: a protocol mapper with name %s already exists for this client", mapper.Name)
}
}

return nil
}
1 change: 1 addition & 0 deletions provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ func KeycloakProvider(client *keycloak.KeycloakClient) *schema.Provider {
"keycloak_openid_full_name_protocol_mapper": resourceKeycloakOpenIdFullNameProtocolMapper(),
"keycloak_openid_hardcoded_claim_protocol_mapper": resourceKeycloakOpenIdHardcodedClaimProtocolMapper(),
"keycloak_openid_audience_protocol_mapper": resourceKeycloakOpenIdAudienceProtocolMapper(),
"keycloak_openid_audience_resolve_protocol_mapper": resourceKeycloakOpenIdAudienceResolveProtocolMapper(),
"keycloak_openid_hardcoded_role_protocol_mapper": resourceKeycloakOpenIdHardcodedRoleProtocolMapper(),
"keycloak_openid_user_realm_role_protocol_mapper": resourceKeycloakOpenIdUserRealmRoleProtocolMapper(),
"keycloak_openid_user_client_role_protocol_mapper": resourceKeycloakOpenIdUserClientRoleProtocolMapper(),
Expand Down
120 changes: 120 additions & 0 deletions provider/resource_keycloak_openid_audience_resolve_protocol_mapper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package provider

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

func resourceKeycloakOpenIdAudienceResolveProtocolMapper() *schema.Resource {
return &schema.Resource{
Create: resourceKeycloakOpenIdAudienceResolveProtocolMapperCreate,
Read: resourceKeycloakOpenIdAudienceResolveProtocolMapperRead,
//Update: resourceKeycloakOpenIdAudienceResolveProtocolMapperUpdate,
Delete: resourceKeycloakOpenIdAudienceResolveProtocolMapperDelete,
Importer: &schema.ResourceImporter{
// import a mapper tied to a client:
// {{realmId}}/client/{{clientId}}/{{protocolMapperId}}
// or a client scope:
// {{realmId}}/client-scope/{{clientScopeId}}/{{protocolMapperId}}
State: genericProtocolMapperImport,
},
Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Description: "A human-friendly name that will appear in the Keycloak console.",
Default: "audience resolve",
},
"realm_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: "The realm id where the associated client or client scope exists.",
},
"client_id": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Description: "The mapper's associated client. Cannot be used at the same time as client_scope_id.",
ConflictsWith: []string{"client_scope_id"},
},
"client_scope_id": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Description: "The mapper's associated client scope. Cannot be used at the same time as client_id.",
ConflictsWith: []string{"client_id"},
},
},
}
}

func mapFromDataToOpenIdAudienceResolveProtocolMapper(data *schema.ResourceData) *keycloak.OpenIdAudienceResolveProtocolMapper {
return &keycloak.OpenIdAudienceResolveProtocolMapper{
Id: data.Id(),
Name: data.Get("name").(string),
RealmId: data.Get("realm_id").(string),
ClientId: data.Get("client_id").(string),
ClientScopeId: data.Get("client_scope_id").(string),
}
}

func mapFromOpenIdAudienceResolveMapperToData(mapper *keycloak.OpenIdAudienceResolveProtocolMapper, data *schema.ResourceData) {
data.SetId(mapper.Id)
data.Set("name", mapper.Name)
data.Set("realm_id", mapper.RealmId)

if mapper.ClientId != "" {
data.Set("client_id", mapper.ClientId)
} else {
data.Set("client_scope_id", mapper.ClientScopeId)
}
}

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

openIdAudienceResolveMapper := mapFromDataToOpenIdAudienceResolveProtocolMapper(data)

err := keycloakClient.ValidateOpenIdAudienceResolveProtocolMapper(openIdAudienceResolveMapper)
if err != nil {
return err
}

err = keycloakClient.NewOpenIdAudienceResolveProtocolMapper(openIdAudienceResolveMapper)
if err != nil {
return err
}

mapFromOpenIdAudienceResolveMapperToData(openIdAudienceResolveMapper, data)

return resourceKeycloakOpenIdAudienceResolveProtocolMapperRead(data, meta)
}

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

realmId := data.Get("realm_id").(string)
clientId := data.Get("client_id").(string)
clientScopeId := data.Get("client_scope_id").(string)

openIdAudienceResolveMapper, err := keycloakClient.GetOpenIdAudienceResolveProtocolMapper(realmId, clientId, clientScopeId, data.Id())
if err != nil {
return handleNotFoundError(err, data)
}

mapFromOpenIdAudienceResolveMapperToData(openIdAudienceResolveMapper, data)

return nil
}

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

realmId := data.Get("realm_id").(string)
clientId := data.Get("client_id").(string)
clientScopeId := data.Get("client_scope_id").(string)

return keycloakClient.DeleteOpenIdAudienceResolveProtocolMapper(realmId, clientId, clientScopeId, data.Id())
}
Loading

0 comments on commit 51479ea

Please sign in to comment.