# SAML IdP Attribute Mapping

Attribute mapping configures Teleport SAML Identity Provider to assert custom user attributes in SAML response. The Teleport SAML IdP supports three configurable fields for attribute mapping:

- `name`: Name of the outgoing attribute. Required. Name should be unique across attribute mapping.

- `value`: Value defined using a [predicate expression](https://goteleport.com/docs/ver/17.x/reference/predicate-language.md), which can reference Teleport usernames, roles or traits. Required.

- `name_format`: SAML attribute name format. Optional. The following formats are supported:

  - `unspecified`: value equals to `urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified`. Used as a default value.
  - `uri`: value equals to `urn:oasis:names:tc:SAML:2.0:attrname-format:uri`.
  - `basic`: value equals to `urn:oasis:names:tc:SAML:2.0:attrname-format:basic`.

Attribute mapping can be configured when adding a SAML application in Teleport web UI, or with `saml_idp_service_provider` resource spec created with `tctl create` or via API.

```
kind: saml_idp_service_provider
metadata:
  name: example.com
spec:
  entity_id: https://example.com/saml/metadata
  acs_url: https://example.com/saml/metadata
  attribute_mapping:
  - name: username
    value: uid
  - name: firstname
    name_format: basic # optional, unspecified used as default if no value is provided.
    value: user.spec.traits.firstname
  - name: groups
    name_format: urn:oasis:names:tc:SAML:2.0:attrname-format:basic # optional, full urn format.
    value: user.spec.roles

```

## Prerequisites

- A running Teleport Enterprise cluster. If you want to get started with Teleport, [sign up](https://goteleport.com/signup) for a free trial or [set up a demo environment](https://goteleport.com/docs/ver/17.x/get-started/deploy-community.md).

- The `tctl` and `tsh` clients.

  Installing `tctl` and `tsh` clients

  1. Determine the version of your Teleport cluster. The `tctl` and `tsh` clients must be at most one major version behind your Teleport cluster version. Send a GET request to the Proxy Service at `/v1/webapi/find` and use a JSON query tool to obtain your cluster version. Replace teleport.example.com:443 with the web address of your Teleport Proxy Service:

     ```
     $ TELEPORT_DOMAIN=teleport.example.com:443
     $ TELEPORT_VERSION="$(curl -s https://$TELEPORT_DOMAIN/v1/webapi/find | jq -r '.server_version')"
     ```

  2. Follow the instructions for your platform to install `tctl` and `tsh` clients:

     **Mac**

     Download the signed macOS .pkg installer for Teleport, which includes the `tctl` and `tsh` clients:

     ```
     $ curl -O https://cdn.teleport.dev/teleport-${TELEPORT_VERSION?}.pkg
     ```

     In Finder double-click the `pkg` file to begin installation.

     ---

     DANGER

     Using Homebrew to install Teleport is not supported. The Teleport package in Homebrew is not maintained by Teleport and we can't guarantee its reliability or security.

     ---

     **Windows - Powershell**

     ```
     $ curl.exe -O https://cdn.teleport.dev/teleport-v${TELEPORT_VERSION?}-windows-amd64-bin.zip
     Unzip the archive and move the `tctl` and `tsh` clients to your %PATH%
     NOTE: Do not place the `tctl` and `tsh` clients in the System32 directory, as this can cause issues when using WinSCP.
     Use %SystemRoot% (C:\Windows) or %USERPROFILE% (C:\Users\<username>) instead.
     ```

     **Linux**

     All of the Teleport binaries in Linux installations include the `tctl` and `tsh` clients. For more options (including RPM/DEB packages and downloads for i386/ARM/ARM64) see our [installation page](https://goteleport.com/docs/ver/17.x/installation.md).

     ```
     $ curl -O https://cdn.teleport.dev/teleport-v${TELEPORT_VERSION?}-linux-amd64-bin.tar.gz
     $ tar -xzf teleport-v${TELEPORT_VERSION?}-linux-amd64-bin.tar.gz
     $ cd teleport
     $ sudo ./install
     Teleport binaries have been copied to /usr/local/bin
     ```

* To check that you can connect to your Teleport cluster, sign in with `tsh login`, then verify that you can run `tctl` commands using your current credentials. For example, run the following command, assigning teleport.example.com to the domain name of the Teleport Proxy Service in your cluster and email\@example.com to your Teleport username:
  ```
  $ tsh login --proxy=teleport.example.com --user=email@example.com
  $ tctl status
  Cluster  teleport.example.com
  Version  17.7.20
  CA pin   sha256:abdc1245efgh5678abdc1245efgh5678abdc1245efgh5678abdc1245efgh5678
  ```
  If you can connect to the cluster and run the `tctl status` command, you can use your current credentials to run subsequent `tctl` commands from your workstation. If you host your own Teleport cluster, you can also run `tctl` commands on the computer that hosts the Teleport Auth Service for full permissions.
* Teleport user with permission to create a service provider resource. The preset `editor` role has this permission.
* If you're new to SAML, consider reviewing our [SAML Identity Provider Reference](https://goteleport.com/docs/ver/17.x/reference/access-controls/saml-idp.md) before proceeding.

## Predicate expressions

Attribute values are authored using predicate expressions.

When a service provider is created with attribute mapping, internally, the attribute mapping details (attribute name, name format and the corresponding value) are embedded as a SAML requested attribute elements in the service provider entity descriptor.

Then, during an SSO request, SAML assertion service extracts the expressions from the entity descriptor and passes it to the predicate expression evaluator along with the authenticated user context.

Once the expressions are evaluated, the resulting values are asserted in the SAML response under the requested attribute name.

### Evaluation context

The following user attributes are available for mapping between Teleport IdP and service providers:

| Attributes | Syntax                                                       |
| ---------- | ------------------------------------------------------------ |
| Username   | `uid` or `user.metadata.name`.                               |
| Roles      | `eduPersonAffiliation` or `user.spec.roles`.                 |
| Traits     | `user.spec.traits.firstname`, `user.spec.traits.groups` etc. |

Given a correct and supported predicate expression, attributes will be mapped as long as the requested attributes are present in Teleport. Attribute mapping that points to a non-existent value will not be included in SAML assertion.

### Predicate expressions syntax

Predicate expressions for attribute mapping are evaluated against user attributes that can be accessed using evaluation context listed above.

The supported functions and methods are listed below, along with the usage syntax and its result, evaluated against the following reference user spec file:

```
# reference user spec file
kind: user
metadata:
  name: foobar
spec:
  roles:
    - access
    - editor
    - dev-ssh
  traits:
    firstname:
      - foo
    lastname:
      - BAR
    displayname:
      - foo bar
    email:
      - foobar@example.com
    groups:
      - okta-admin
      - dev-sso
      - dev-rdp

```

### Methods

#### `add`

Add new value(s). Works on `user.spec.roles` and `user.spec.traits.groups`.

| Expression                           | Result                                 |
| ------------------------------------ | -------------------------------------- |
| `user.spec.roles.add("staging-ssh")` | `access, editor, dev-ssh, staging-ssh` |

You can also add an entirely new value that is not available in the user spec file. E.g.:

| Expression                                   | Result     |
| -------------------------------------------- | ---------- |
| `set().add("prod-ssh")` or `set("prod-ssh")` | `prod-ssh` |

#### `remove`

Remove value(s). Works on `user.spec.roles` and `user.spec.traits.groups`

| Expression                                   | Result    |
| -------------------------------------------- | --------- |
| `user.spec.roles.remove("editor", "access")` | `dev-ssh` |

#### `contains`

Returns boolean value for matching expression. To be used in helper functions such as `ifelse`. Works on `user.spec.roles` and `user.spec.traits.groups`.

| Expression                                       | Result |
| ------------------------------------------------ | ------ |
| `user.spec.traits.groups.contains("okta-admin")` | `true` |

### Helper functions

#### `strings.upper`

Transform string to upper.

| Expression                                  | Result |
| ------------------------------------------- | ------ |
| `strings.upper(user.spec.traits.firstname)` | `FOO`  |

#### `strings.lower`

Transform string to lower.

| Expression                                 | Result |
| ------------------------------------------ | ------ |
| `strings.lower(user.spec.traits.lastname)` | `bar`  |

#### `strings.replaceall`

Replace all matching strings.

| Expression                                                    | Result                         |
| ------------------------------------------------------------- | ------------------------------ |
| `strings.replaceall(user.spec.traits.groups, "-", "+")`       | `okta+admin, dev+sso, dev+rdp` |
| `strings.replaceall(user.spec.traits.groups, "admin", "dev")` | `okta-dev, dev-sso, dev-rdp`   |

#### `strings.split`

Split string at matching character.

| Expression                                    | Result                       |
| --------------------------------------------- | ---------------------------- |
| `strings.split(user.spec.traits.groups, "-")` | `okta, admin, dev, sso, rdp` |

#### `ifelse`

Conditionally return values. To be used in conjunction with methods such as `contains`.

Signature: `ifelse(condition, "value to return if condition is true", "value to return if condition is false")`

| Expression                                                                                                                  | Result                                    |
| --------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------- |
| `ifelse(user.spec.traits.groups.contains("okta-admin"), user.spec.traits.groups.add("new group"), user.spec.traits.groups)` | `okta-admin, dev-sso, dev-rdp, new group` |

#### `union`

Combine values in `user.spec.traits.groups` and `user.spec.roles`.

| Expression                                                             | Result                                                  |
| ---------------------------------------------------------------------- | ------------------------------------------------------- |
| `union(user.spec.traits.groups, user.spec.roles)`                      | `okta-admin, dev-sso, dev-rdp, access, editor, dev-ssh` |
| `union(user.spec.traits.groups.remove("okta-admin"), user.spec.roles)` | `dev-sso, dev-rdp, access, editor, dev-ssh`             |

## Testing attribute mapping

### `test-attribute-mapping` command

Attribute mapping can be tested using `tctl idp saml test-attribute-mapping` command. `test-attribute-mapping` command accepts three arguments.

- `--users`: user names or names of files containing user spec. Required.
- `--sp`: name of file containing service provider spec with attribute mapping. Required.
- `--format`: `yaml` or `json`. Optional. Text output by default if the flag is not provided.

E.g.: Test with user name and service provider spec file:

```
test with username and service provider file
$ tctl idp saml test-attribute-mapping --user user1 --sp sp.yml
User: user1
Attribute Name Attribute Value
-------------- -----------------------------
firstname      foo
lastname       bar
roles          access, editor, dev-ssh
groups         okta-admin, dev-sso, dev-rdp
```

Test with user spec file and service provider spec file:

```
$ tctl idp saml test-attribute-mapping --user user.yml --sp sp.yml
```

Print result in format of choice.

```
$ tctl idp saml test-attribute-mapping --user user.yml --sp sp.yml --format (json/yaml)
```
