-
Notifications
You must be signed in to change notification settings - Fork 330
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
new resource: keycloak_saml_user_property_protocol_mapper (#85)
- Loading branch information
Showing
7 changed files
with
652 additions
and
0 deletions.
There are no files selected for viewing
61 changes: 61 additions & 0 deletions
61
docs/resources/keycloak_saml_user_property_protocol_mapper.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
# keycloak_saml_user_property_protocol_mapper | ||
|
||
Allows for creating and managing user property protocol mappers for | ||
SAML clients within Keycloak. | ||
|
||
SAML user property protocol mappers allow you to map properties of the Keycloak | ||
user model to an attribute in a SAML assertion. Protocol mappers | ||
can be defined for a single client, or they can be defined for a client scope which | ||
can be shared between multiple different clients. | ||
|
||
### Example Usage (Client) | ||
|
||
```hcl | ||
resource "keycloak_realm" "realm" { | ||
realm = "my-realm" | ||
enabled = true | ||
} | ||
resource "keycloak_saml_client" "saml_client" { | ||
realm_id = "${keycloak_realm.test.id}" | ||
client_id = "test-saml-client" | ||
name = "test-saml-client" | ||
} | ||
resource "keycloak_saml_user_property_protocol_mapper" "saml_user_property_mapper" { | ||
realm_id = "${keycloak_realm.test.id}" | ||
client_id = "${keycloak_saml_client.saml_client.id}" | ||
name = "email-user-property-mapper" | ||
user_property = "email" | ||
saml_attribute_name = "email" | ||
saml_attribute_name_format = "Unspecified" | ||
} | ||
``` | ||
|
||
### Argument Reference | ||
|
||
The following arguments are supported: | ||
|
||
- `realm_id` - (Required) The realm this protocol mapper exists within. | ||
- One of the following arguments is required: | ||
- `client_id` - The SAML client this protocol mapper is attached to. | ||
- `client_scope_id` - The SAML client scope this protocol mapper is attached to. | ||
- `name` - (Required) The display name of this protocol mapper in the GUI. | ||
- `user_property` - (Required) The property of the Keycloak user model to map. | ||
- `friendly_name` - (Optional) An optional human-friendly name for this attribute. | ||
- `saml_attribute_name` - (Required) The name of the SAML attribute. | ||
- `saml_attribute_name_format` - (Required) The SAML attribute Name Format. Can be one of `Unspecified`, `Basic`, or `URI Reference`. | ||
|
||
### 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_saml_user_property_protocol_mapper.saml_user_property_mapper my-realm/client/a7202154-8793-4656-b655-1dd18c181e14/71602afa-f7d1-4788-8c49-ef8fd00af0f4 | ||
$ terraform import keycloak_saml_user_property_protocol_mapper.saml_user_property_mapper my-realm/client-scope/b799ea7e-73ee-4a73-990a-1eafebe8e20a/71602afa-f7d1-4788-8c49-ef8fd00af0f4 | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
package keycloak | ||
|
||
import ( | ||
"fmt" | ||
) | ||
|
||
type SamlUserPropertyProtocolMapper struct { | ||
Id string | ||
Name string | ||
RealmId string | ||
ClientId string | ||
ClientScopeId string | ||
|
||
UserProperty string | ||
FriendlyName string | ||
SamlAttributeName string | ||
SamlAttributeNameFormat string | ||
} | ||
|
||
func (mapper *SamlUserPropertyProtocolMapper) convertToGenericProtocolMapper() *protocolMapper { | ||
return &protocolMapper{ | ||
Id: mapper.Id, | ||
Name: mapper.Name, | ||
Protocol: "saml", | ||
ProtocolMapper: "saml-user-property-mapper", | ||
Config: map[string]string{ | ||
attributeNameField: mapper.SamlAttributeName, | ||
attributeNameFormatField: mapper.SamlAttributeNameFormat, | ||
friendlyNameField: mapper.FriendlyName, | ||
userAttributeField: mapper.UserProperty, | ||
}, | ||
} | ||
} | ||
|
||
func (protocolMapper *protocolMapper) convertToSamlUserPropertyProtocolMapper(realmId, clientId, clientScopeId string) *SamlUserPropertyProtocolMapper { | ||
return &SamlUserPropertyProtocolMapper{ | ||
Id: protocolMapper.Id, | ||
Name: protocolMapper.Name, | ||
RealmId: realmId, | ||
ClientId: clientId, | ||
ClientScopeId: clientScopeId, | ||
|
||
UserProperty: protocolMapper.Config[userAttributeField], | ||
FriendlyName: protocolMapper.Config[friendlyNameField], | ||
SamlAttributeName: protocolMapper.Config[attributeNameField], | ||
SamlAttributeNameFormat: protocolMapper.Config[attributeNameFormatField], | ||
} | ||
} | ||
|
||
func (keycloakClient *KeycloakClient) GetSamlUserPropertyProtocolMapper(realmId, clientId, clientScopeId, mapperId string) (*SamlUserPropertyProtocolMapper, error) { | ||
var protocolMapper *protocolMapper | ||
|
||
err := keycloakClient.get(individualProtocolMapperPath(realmId, clientId, clientScopeId, mapperId), &protocolMapper) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return protocolMapper.convertToSamlUserPropertyProtocolMapper(realmId, clientId, clientScopeId), nil | ||
} | ||
|
||
func (keycloakClient *KeycloakClient) DeleteSamlUserPropertyProtocolMapper(realmId, clientId, clientScopeId, mapperId string) error { | ||
return keycloakClient.delete(individualProtocolMapperPath(realmId, clientId, clientScopeId, mapperId)) | ||
} | ||
|
||
func (keycloakClient *KeycloakClient) NewSamlUserPropertyProtocolMapper(mapper *SamlUserPropertyProtocolMapper) 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) UpdateSamlUserPropertyProtocolMapper(mapper *SamlUserPropertyProtocolMapper) error { | ||
path := individualProtocolMapperPath(mapper.RealmId, mapper.ClientId, mapper.ClientScopeId, mapper.Id) | ||
|
||
return keycloakClient.put(path, mapper.convertToGenericProtocolMapper()) | ||
} | ||
|
||
func (keycloakClient *KeycloakClient) ValidateSamlUserPropertyProtocolMapper(mapper *SamlUserPropertyProtocolMapper) 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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
160 changes: 160 additions & 0 deletions
160
provider/keycloak_saml_user_property_protocol_mapper.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
package provider | ||
|
||
import ( | ||
"github.com/hashicorp/terraform/helper/schema" | ||
"github.com/hashicorp/terraform/helper/validation" | ||
"github.com/mrparkers/terraform-provider-keycloak/keycloak" | ||
) | ||
|
||
func resourceKeycloakSamlUserPropertyProtocolMapper() *schema.Resource { | ||
return &schema.Resource{ | ||
Create: resourceKeycloakSamlUserPropertyProtocolMapperCreate, | ||
Read: resourceKeycloakSamlUserPropertyProtocolMapperRead, | ||
Update: resourceKeycloakSamlUserPropertyProtocolMapperUpdate, | ||
Delete: resourceKeycloakSamlUserPropertyProtocolMapperDelete, | ||
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, | ||
Required: true, | ||
}, | ||
"realm_id": { | ||
Type: schema.TypeString, | ||
Required: true, | ||
ForceNew: true, | ||
}, | ||
"client_id": { | ||
Type: schema.TypeString, | ||
Optional: true, | ||
ForceNew: true, | ||
ConflictsWith: []string{"client_scope_id"}, | ||
}, | ||
"client_scope_id": { | ||
Type: schema.TypeString, | ||
Optional: true, | ||
ForceNew: true, | ||
ConflictsWith: []string{"client_id"}, | ||
}, | ||
"user_property": { | ||
Type: schema.TypeString, | ||
Required: true, | ||
}, | ||
"friendly_name": { | ||
Type: schema.TypeString, | ||
Optional: true, | ||
}, | ||
"saml_attribute_name": { | ||
Type: schema.TypeString, | ||
Required: true, | ||
}, | ||
"saml_attribute_name_format": { | ||
Type: schema.TypeString, | ||
Required: true, | ||
ValidateFunc: validation.StringInSlice(keycloakSamlUserAttributeProtocolMapperNameFormats, false), | ||
}, | ||
}, | ||
} | ||
} | ||
|
||
func mapFromDataToSamlUserPropertyProtocolMapper(data *schema.ResourceData) *keycloak.SamlUserPropertyProtocolMapper { | ||
return &keycloak.SamlUserPropertyProtocolMapper{ | ||
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), | ||
|
||
UserProperty: data.Get("user_property").(string), | ||
FriendlyName: data.Get("friendly_name").(string), | ||
SamlAttributeName: data.Get("saml_attribute_name").(string), | ||
SamlAttributeNameFormat: data.Get("saml_attribute_name_format").(string), | ||
} | ||
} | ||
|
||
func mapFromSamlUserPropertyProtocolMapperToData(mapper *keycloak.SamlUserPropertyProtocolMapper, 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) | ||
} | ||
|
||
data.Set("user_property", mapper.UserProperty) | ||
data.Set("friendly_name", mapper.FriendlyName) | ||
data.Set("saml_attribute_name", mapper.SamlAttributeName) | ||
data.Set("saml_attribute_name_format", mapper.SamlAttributeNameFormat) | ||
} | ||
|
||
func resourceKeycloakSamlUserPropertyProtocolMapperCreate(data *schema.ResourceData, meta interface{}) error { | ||
keycloakClient := meta.(*keycloak.KeycloakClient) | ||
|
||
samlUserPropertyMapper := mapFromDataToSamlUserPropertyProtocolMapper(data) | ||
|
||
err := keycloakClient.ValidateSamlUserPropertyProtocolMapper(samlUserPropertyMapper) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
err = keycloakClient.NewSamlUserPropertyProtocolMapper(samlUserPropertyMapper) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
mapFromSamlUserPropertyProtocolMapperToData(samlUserPropertyMapper, data) | ||
|
||
return resourceKeycloakSamlUserPropertyProtocolMapperRead(data, meta) | ||
} | ||
|
||
func resourceKeycloakSamlUserPropertyProtocolMapperRead(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) | ||
|
||
samlUserPropertyMapper, err := keycloakClient.GetSamlUserPropertyProtocolMapper(realmId, clientId, clientScopeId, data.Id()) | ||
if err != nil { | ||
return handleNotFoundError(err, data) | ||
} | ||
|
||
mapFromSamlUserPropertyProtocolMapperToData(samlUserPropertyMapper, data) | ||
|
||
return nil | ||
} | ||
|
||
func resourceKeycloakSamlUserPropertyProtocolMapperUpdate(data *schema.ResourceData, meta interface{}) error { | ||
keycloakClient := meta.(*keycloak.KeycloakClient) | ||
|
||
samlUserPropertyMapper := mapFromDataToSamlUserPropertyProtocolMapper(data) | ||
|
||
err := keycloakClient.ValidateSamlUserPropertyProtocolMapper(samlUserPropertyMapper) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
err = keycloakClient.UpdateSamlUserPropertyProtocolMapper(samlUserPropertyMapper) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return resourceKeycloakSamlUserPropertyProtocolMapperRead(data, meta) | ||
} | ||
|
||
func resourceKeycloakSamlUserPropertyProtocolMapperDelete(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.DeleteSamlUserPropertyProtocolMapper(realmId, clientId, clientScopeId, data.Id()) | ||
} |
Oops, something went wrong.