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
<?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
#! /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
.
#! /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)
{
"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.
<?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).
#! /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
#! /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
to127.0.0.1
entry is required in/etc/hosts
#! /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}'