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

Add OIDC audience resolve protocol mapper #606

Merged
merged 6 commits into from
Oct 8, 2021
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
90 changes: 90 additions & 0 deletions keycloak/openid_audience_resolve_protocol_mapper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package keycloak

import (
"fmt"
)

const AudienceResolveMapperName = "audience resolve"

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

func (mapper *OpenIdAudienceResolveProtocolMapper) convertToGenericProtocolMapper() *protocolMapper {
return &protocolMapper{
Id: mapper.Id,
Name: AudienceResolveMapperName,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it should be up to the user to specify the name for this mapper, so it should be part of the schema instead of hardcoded to "audience resolve".

I realize that this isn't super importan, because 1) it isn't editable on the Keycloak side (which I just learned today 😄) and 2) there's no reason to attach two different audience resolve protocol mappers to the same client / client scope.

However, since Keycloak allows you to create two instances of this protocol mapper attached to the same client with different names, this provider should allow it too.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okey doke, updated to have that behavior.

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,
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")
}

if mapper.ClientId != "" && mapper.ClientScopeId != "" {
thyming marked this conversation as resolved.
Show resolved Hide resolved
return fmt.Errorf("validation error: ClientId and ClientScopeId cannot both be set")
}

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

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

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
111 changes: 111 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,111 @@
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{
"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(),
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("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