Skip to content

Configuring tine for Single Sign On (SSO)

tine as Single Sign On Identity Provider (SSO IdP)

tine can act as SSO identity provider for oidc and SAML2

1) Install SSO application

  • Go to setup.php and make sure SSO is installed
  • In the UI go to Admin > Applications > SSO and make sure SSO is activated

2) Generate keys

Convert certificate to json web key

To convert the certificate into the json web key format we use the pem-jwk tool here.

npm install -g pem-jwk

You can convert the key alternatively e.g. with an online converter like https://irrte.ch/jwt-js-decode/pem2jwk.html

cd /path/to/docker-composer.yml
openssl req -x509 -newkey rsa:4096 -keyout ./conf.d/sso_key.pem -out ./conf.d/sso_cert.pem -days 730 -nodes -subj '/CN=tine-sso'
openssl pkey -in ./conf.d/sso_key.pem -out ./conf.d/sso_cert.crt -pubout
pem-jwk ./conf.d/sso_cert.crt > ./conf.d/sso_cert.jwk
sudo chown $(docker-compose exec  web sh -c "id tine20 -u"):$(docker-compose exec  web sh -c "id tine20 -g") ./conf.d/sso_*
sudo chmod 660 ./conf.d/sso_cert.* ./conf.d/sso_key.*

!!! note sso_cert.crt needs to contain both CERTIFICATE and PUBLIC KEY strings!

To check if you have a valid config, you can call this URL: https://my.tine.url/sso/saml2/idpmetadata

3) Create config

./conf.d/sso.inc.php
<?php

return [
    'SSO' => [
        'pwdLessLogin' => 'both',
        'oauth2' => [
            'enabled' => true,
            'keys' => [
                array_merge(json_decode(file_get_contents(__DIR__ . '/sso_cert.jwk'), true), [
                    'use' => 'sig',
                    'alg' => 'RS256',
                    'kid' => 'tine-sso',
                    'publickey' => __DIR__ . '/sso_cert.crt',
                    'privatekey' => __DIR__ . '/sso_key.pem',
                ]),
            ],
        ],
        'saml2' => [
            'enabled' => true,
            'tineLogout' => true,
            'keys' => [
                [
                    'privatekey' => __DIR__ . '/sso_key.pem',
                    'certificate' => __DIR__ . '/sso_cert.pem',
                ]
            ]
        ],
    ],
];

4) Clear config cache

#! /bin/sh

docker-compose exec --user tine20 web sh -c "cd /usr/share/tine20/ && php setup.php --config=\$TINE20_CONFIG_PATH --clear_cache -v"

5) Configure relaying parties

SSO Relaying parties can be configured per UI in the admin module or via cli using curl

- UI

Go to Admin > Applications > SSO and open the tab RELYING PARTIES in the SSO configuration dialog.

- CLI

Login
#! /bin/sh

URL=http://web:4000/
USER=tine20admin
read PASS

TMP=$(curl -s $URL \
  -H 'Content-Type: application/json' \
  -H 'X-Requested-With: XMLHttpRequest' \
  -H 'X-Tine20-ClientAssetHash: 64558b09a551cee0fac4c9f1dcd5c7a3ac5c0e34' \
  -H 'X-Tine20-Request-Type: JSON' \
  --insecure \
  -d '{"jsonrpc":"2.0","method":"Tinebase.login","params":{"username":"'"$USER"'","password":"'"$PASS"'"},"id":0}' \
  | jq  -r '.result.sessionId, .result.jsonKey')

i=0
for line in $TMP; do
  if [ $i -eq 0 ]; then
    SESSIONID="$line"
  else
    JSONKEY="$line"
  fi
    echo "$line"
    i=$((i+1))
done

export SESSIONID
export JSONKEY
- SAML2

The metadata URL of the tine idp (needed in config of the rp) is https://YOURTINEURL/sso/saml2/idpmetadata .

If not given tine fetches the AssertionConsumerService* and singleLogoutService* properties from the optional metaUrl.

Add SAML2 RP
#! /bin/sh

curl $URL \
  -H 'Content-Type: application/json' \
  -H 'Cookie: TINE20SESSID='"$SESSIONID"'' \
  -H 'X-Requested-With: XMLHttpRequest' \
  -H 'X-Tine20-JsonKey: '"$JSONKEY"'' \
  -H 'X-Tine20-Request-Type: JSON' \
  --insecure \
  -d '{"jsonrpc":"2.0","method":"SSO.saveRelyingParty","params":{"recordData":{
    "name": "saml-test-sp",
    "label": "saml-test-sp",
    "description": "you might test with ghcr.io/beryju/saml-test-sp",
    "logo": null,
    "config_class": "SSO_Model_Saml2RPConfig",
    "config": {
      "name": "saml-test-sp",
      "entityid": "saml-test-sp",
      "metaUrl": "http://localhost:4902/saml/metadata",
      "AssertionConsumerServiceBinding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
      "AssertionConsumerServiceLocation": "http://localhost:4902/saml/acs",
      "singleLogoutServiceLocation": "http://localhost:4902/saml/slo",
      "singleLogoutServiceBinding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
      "attributeMapping": {
        "uid": "accountEmailAddress"
      },
      "customHooks": {
        "postAuthenticate": "/etc/tine20/samlPostAuthHook.php"
      },
      "id": "0"
    }
  },"duplicateCheck":true},"id":3}'

Custom attributes mappings

The mapping tine user attributes => SAML2 attributes can be customized the rp's attributeMapping property (JSON formatted)

Example to map user email to SAML2 uid
{
    "uid": "accountEmailAddress"
}

Custom attributes in SAML response

You can add custom attributes which are sent to the rp see customHooks property (JSON formatted) in the rp config.

Example post auth hook
<?php

// example SAML2 post authenticate hook
// adds the users groups to the attributes
$groupCtrl = Tinebase_Group::getInstance();
$userGroups = $groupCtrl->getMultiple($groupCtrl->getGroupMemberships($user->getId()));
$state['Attributes']['groups'] = array_values($userGroups->name); // value must be an array

- OIDC

The OIDC provider url of the tine idp (needed in the config of the rp) is https://YOURTINEURL (no trailing slash).

Add OIDC RP
#! /bin/sh

curl $URL \
  -H 'Accept: */*' \
  -H 'Content-Type: application/json' \
  -H 'Cookie: TINE20SESSID='"$SESSIONID"'' \
  -H 'X-Requested-With: XMLHttpRequest' \
  -H 'X-Tine20-JsonKey: '"$JSONKEY"'' \
  -H 'X-Tine20-Request-Type: JSON' \
  --insecure \
  -d '{"jsonrpc":"2.0","method":"SSO.saveRelyingParty","params":{"recordData":{
    "name": "oidc-test-sp",
    "label": "oidc test service provider",
    "description": "you might test with ghcr.io/beryju/oidc-test-client",
    "logo": "https://openid.net/wordpress-content/uploads/2014/09/openid-r-logo-900x360.png",
    "config_class": "SSO_Model_OAuthOIdRPConfig",
      "config": {
        "redirect_urls": [
          "http://localhost:4901/auth/callback"
        ],
        "secret": "test-secret",
        "is_confidential": false,
        "id": "0"
      }
  },"duplicateCheck":true},"id":3}'

tine as Single Sign On Relaying Party (SSO RP)

tine can act as SSO Relaying Party for OIDC

1) Install SSO application

  • Go to setup.php and make sure SSO is installed
  • In the UI go to Admin > Applications > SSO and make sure SSO is activated

2) Configure identity providers (IdP)

SSO identity providers can be configured per UI in the admin module or via cli using curl

- UI

Go to Admin > Applications > SSO and open the tab EXTERNAL IDENTITY PROVIDERS in the SSO configuration dialog.

- CLI

Login
#! /bin/sh

URL=http://web:4000/
USER=tine20admin
read PASS

TMP=$(curl -s $URL \
  -H 'Content-Type: application/json' \
  -H 'X-Requested-With: XMLHttpRequest' \
  -H 'X-Tine20-ClientAssetHash: 64558b09a551cee0fac4c9f1dcd5c7a3ac5c0e34' \
  -H 'X-Tine20-Request-Type: JSON' \
  --insecure \
  -d '{"jsonrpc":"2.0","method":"Tinebase.login","params":{"username":"'"$USER"'","password":"'"$PASS"'"},"id":0}' \
  | jq  -r '.result.sessionId, .result.jsonKey')

i=0
for line in $TMP; do
  if [ $i -eq 0 ]; then
    SESSIONID="$line"
  else
    JSONKEY="$line"
  fi
    echo "$line"
    i=$((i+1))
done

export SESSIONID
export JSONKEY

NOTE: For following setup to work correctly, a oidc-server-mock to 127.0.0.1 entry is required in /etc/hosts

Add foreign mock IDP
#! /bin/sh

curl $URL \
  -H 'Accept: */*' \
  -H 'Content-Type: application/json' \
  -H 'Cookie: TINE20SESSID='"$SESSIONID"'' \
  -H 'X-Requested-With: XMLHttpRequest' \
  -H 'X-Tine20-JsonKey: '"$JSONKEY"'' \
  -H 'X-Tine20-Request-Type: JSON' \
  --insecure \
  -d '{"jsonrpc":"2.0","method":"SSO.saveExternalIdp","params":{"recordData":{
    "name": "oidc-test-idp",
    "config_class": "SSO_Model_ExIdp_OIdConfig",
    "config": {
      "name": "test idp",
      "provider_url": "http://oidc-server-mock",
      "issuer": "http://oidc-server-mock",
      "client_id": "tine20",
      "client_secret": "tine20"
    },
    "domains": [
      {
        "domain": "mail.test"
      }
    ]
  },"duplicateCheck":true},"id":3}'