9. Developer quick start#
Important
This section covers a highly technical topic. It addresses app developers who want to integrate an app with the Guardian. Familiarity with using the command line, working with an HTTP API, and writing code is necessary to understand this section.
This section assumes familiarity with the following Univention documentation:
This section provides a walk-through of the steps necessary to integrate an app with the Guardian. It refers to the example outlined in What is the Guardian?:
Example Organization is an app developer who creates Cake Express, which allows people to order cakes for company events. Administrators can install the app from Univention App Center. They want to integrate Cake Express with the Guardian.
Note
The example scripts in this section assume that:
You installed the app on the same UCS system as the Management API.
You installed the app on the same UCS system as the Keycloak server.
If either of these two things isn’t true, you need to find a way for the UCS app infrastructure maintainer to communicate their locations to the script at run time.
9.1. Management API#
This section describes how to get started with the Management API to integrate your app with the Guardian.
9.1.1. Getting a Keycloak token#
The first thing that Example Organization needs to do is to write a join script for its app. The app needs to interact with the Management API. To do this, the join script must obtain a token from Keycloak to authenticate all calls to the API.
Listing 9.1 shows the variables you need to obtain a token from Keycloak.
binduser=Administrator
bindpwd=password
CLIENT_ID=guardian-scripts
GUARDIAN_KEYCLOAK_URL=$(ucr get guardian-management-api/oauth/keycloak-uri)
SYSTEM_KEYCLOAK_URL=$(ucr get keycloak/server/sso/fqdn)
KEYCLOAK_BASE_URL=${GUARDIAN_KEYCLOAK_URL:-$SYSTEM_KEYCLOAK_URL}
KEYCLOAK_URL="$KEYCLOAK_BASE_URL/realms/ucs/protocol/openid-connect/token"
if [[ ! $KEYCLOAK_URL == http ]]; then
KEYCLOAK_URL="https://$KEYCLOAK_URL"
fi
Note
In a typical join script,
the arguments --binduser
, --bindpwd
, and --bindpwdfile
are available,
which specify an administrator user,
and either a password or a file for parsing the password.
The example assumes that the join script has already parsed these parameters
into the variables binduser
and bindpwd
.
You can retrieve the token with the following command:
$ token=$(curl -d "client_id=$CLIENT_ID" \
-d "username=$binduser" \
-d "password=$bindpwd" \
-d "grant_type=password" \
$KEYCLOAK_URL | sed 's/.*"access_token":"\([[:alnum:]\.-_-]*\)".*/\1/')
Important
All commands in subsequent sections reference token
.
You may need to refresh the token several times,
if you are entering commands manually.
9.1.2. Registering an app#
Example Organization now needs to tell the Guardian about its app Cake Express. To do this, it needs to take the token from the previous section and make a request to the Management API.
$ MANAGEMENT_SERVER="$(hostname).$(ucr get domainname)/guardian/management"
$ curl -X POST \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $token" \
-d '{"name":"cake-express", "display_name":"Cake Express"}' \
$MANAGEMENT_SERVER/apps/register
Example Organization is now ready to start setting up the Guardian to work with Cake Express.
Important
All names in Guardian are lower-case ASCII alphanumeric with either underscores or hyphens. The encoding for display names is only limited by the character support for the PostgreSQL database that Guardian uses.
9.1.3. Registering namespaces#
A namespace is a categorization to store everything that an app wants to use in Guardian, like roles and permissions.
Every app gets a default
namespace to use.
Example Organization wants to manage three different facets of Cake Express:
cakes
Category for everything related to what’s actually sold.
orders
Category for administration of orders.
users
Category for managing other users of Cake Express.
Later, Example Organization creates some roles in each of these namespaces for tasks in Cake Express. Listing 9.2 to Listing 9.4 show how Example Organization creates these namespaces for the app in the app’s join script:
$ curl -X POST \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $token" \
-d '{"name":"cakes", "display_name":"Cakes"}' \
$MANAGEMENT_SERVER/namespaces/cake-express
$ curl -X POST \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $token" \
-d '{"name":"orders", "display_name":"Orders"}' \
$MANAGEMENT_SERVER/namespaces/cake-express
$ curl -X POST \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $token" \
-d '{"name":"users", "display_name":"Users"}' \
$MANAGEMENT_SERVER/namespaces/cake-express
9.1.4. Registering roles#
Example Organization wants to create three different roles for users of Cake Express:
cake-express:cakes:cake-orderer
Someone who can order cakes from Cake Express.
cake-express:orders:finance-manager
Someone who manages the expenses for the orders.
cake-express:users:user-manager
Someone who manages other users within Cake Express.
Example Organization also wants to create a role for some of their cakes:
cake-express:cakes:birthday-cake
A cake just for employee birthdays.
Each role consists of the following parts, separated by a colon (:
):
Listing 9.5 to Listing 9.8 show how Example Organization creates these roles for the app in the app’s join script:
$ curl -X POST \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $token" \
-d '{"name":"cake-orderer", "display_name":"Cake Orderer"}' \
$MANAGEMENT_SERVER/roles/cake-express/cakes
$ curl -X POST \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $token" \
-d '{"name":"finance-manager", "display_name":"Finance Manager"}' \
$MANAGEMENT_SERVER/roles/cake-express/orders
$ curl -X POST \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $token" \
-d '{"name":"user-manager", "display_name":"User Manager"}' \
$MANAGEMENT_SERVER/roles/cake-express/users
$ curl -X POST \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $token" \
-d '{"name":"birthday-cake", "display_name":"Birthday Cake"}' \
$MANAGEMENT_SERVER/roles/cake-express/cakes
9.1.5. Registering permissions#
Example Organization wants to provide some permissions that define what users of Cake Express want to do:
cake-express:cakes:order-cake
Users with this permission can order cakes.
cake-express:orders:cancel-order
Users can cancel a cake order.
cake-express:users:manage-notifications
Users can manage cake notifications.
Listing 9.9 to Listing 9.11 show how Example Organization creates these permissions for the app in the app’s join script:
$ curl -X POST \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $token" \
-d '{"name":"order-cake", "display_name":"order cake"}' \
$MANAGEMENT_SERVER/permissions/cake-express/cakes
$ curl -X POST \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $token" \
-d '{"name":"cancel-order", "display_name":"cancel order"}' \
$MANAGEMENT_SERVER/permissions/cake-express/orders
$ curl -X POST \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $token" \
-d '{"name":"manage-notifications", "display_name":"manage notifications"}' \
$MANAGEMENT_SERVER/permissions/cake-express/users
9.1.6. Registering capabilities#
Finally, Example Organization wants to define some default capabilities for its application. The guardian app administrator who installs Cake Express can change these later. These default capabilities make it easier for Cake Express to work out of the box.
The app would like to create the following capabilities:
Users with the
cake-orderer
role can order cakes.Users with the
finance-manager
role, or the person who ordered the cake, have the permission to cancel the cake order.Users with the
user-manager
role have the permission to manage cake notifications. Users can also manage their own notifications for cakes sent to them, except for notifications related to birthday cakes.
9.1.6.1. Create Cake order#
Listing 9.12 shows how Example Organization creates the capability for ordering cake:
$ curl -X POST \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $token" \
-d '{
"name": "cake-orderer-can-order-cake",
"display_name": "Cake Orderers can order cake",
"role": {
"app_name": "cake-express",
"namespace_name": "cakes",
"name": "cake-orderer"
},
"conditions": [],
"relation": "AND",
"permissions": [
{
"app_name": "cake-express",
"namespace_name": "cakes",
"name": "order-cake"
}
]
}' \
$MANAGEMENT_SERVER/capabilities/cake-express/cakes
9.1.6.2. Cancel cake order#
Listing 9.13 and
Listing 9.14
show how Example Organization creates the capability for canceling an order.
The action requires two POST
requests to create the capability:
$ curl -X POST \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $token" \
-d '{
"name": "finance-manager-can-cancel-order",
"display_name": "Finance Manager can cancel orders",
"role": {
"app_name": "cake-express",
"namespace_name": "orders",
"name": "finance-manager"
},
"conditions": [],
"relation": "AND",
"permissions": [
{
"app_name": "cake-express",
"namespace_name": "orders",
"name": "cancel-order"
}
]
}' \
$MANAGEMENT_SERVER/capabilities/cake-express/orders
$ curl -X POST \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $token" \
-d '{
"name": "self-can-cancel-order",
"display_name": "Users can cancel their own order",
"role": {
"app_name": "cake-express",
"namespace_name": "cakes",
"name": "cake-orderer"
},
"conditions": [
{
"app_name": "guardian",
"namespace_name": "builtin",
"name": "target_field_equals_actor_field",
"parameters": [
{
"name": "actor_field",
"value": "id"
},
{
"name": "target_field",
"value": "orderer_id"
}
]
}
],
"relation": "AND",
"permissions": [
{
"app_name": "cake-express",
"namespace_name": "orders",
"name": "cancel-order"
}
]
}' \
$MANAGEMENT_SERVER/capabilities/cake-express/orders
9.1.6.3. Manage notifications#
Listing 9.15 and
Listing 9.16
show how Example Organization creates the capability for managing notifications.
The action requires two POST
requests to create the capability:
$ curl -X POST \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $token" \
-d '{
"name": "user-manager-can-manage-notifications",
"display_name": "User Managers can manage cake notifications",
"role": {
"app_name": "cake-express",
"namespace_name": "users",
"name": "user-manager"
},
"conditions": [],
"relation": "AND",
"permissions": [
{
"app_name": "cake-express",
"namespace_name": "users",
"name": "manage-notifications"
}
]
}' \
$MANAGEMENT_SERVER/capabilities/cake-express/users
$ curl -X POST \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $token" \
-d '{
"name": "self-can-manage-notifications",
"display_name": "Users can manage their own notifications, except for birthday cakes",
"role": {
"app_name": "cake-express",
"namespace_name": "cakes",
"name": "cake-orderer"
},
"conditions": [
{
"app_name": "guardian",
"namespace_name": "builtin",
"name": "target_field_equals_actor_field",
"parameters": [
{
"name": "actor_field",
"value": "id"
},
{
"name": "target_field",
"value": "recipient_id"
}
]
},
{
"app_name": "guardian",
"namespace_name": "builtin",
"name": "target_does_not_have_role",
"parameters": [
{
"name": "role",
"value": "cake-express:cakes:birthday-cake"
}
]
}
],
"relation": "AND",
"permissions": [
{
"app_name": "cake-express",
"namespace_name": "users",
"name": "manage-notifications"
}
]
}' \
$MANAGEMENT_SERVER/capabilities/cake-express/users
Example Organization is now done with the join script and is ready to start using Guardian with their application.
9.1.7. Registering custom conditions#
The Guardian comes with several built-in conditions, documented in the section Conditions Reference.
However, some apps need to write their own custom conditions, and the Management API provides an endpoint to facilitate this. The endpoint requires knowledge of Rego.
Suppose that Example Organization tracks whether or not a user likes cakes or not, and wants to provide the guardian app administrator with a condition that allows them to opt users out of receiving a cake, without knowing how Cake Express stores their cake preferences.
Listing 9.17 shows the Rego code for the custom condition.
package guardian.conditions
import future.keywords.if
import future.keywords.in
condition("cake-express:users:recipient-likes-cakes", _, condition_data) if {
condition_data.target.old.attributes.recipient["likes_cakes"]
} else = false
You can test this code in the Rego Playground provided by the Open Policy Agent.
Listing 9.18 shows the code for the Playground.
Paste it into the Editor section.
Click the Evaluate button in the Rego Playground.
The OUTPUT section shows the result true
.
package guardian.conditions
import future.keywords.if
import future.keywords.in
condition("cake-express:users:recipient-likes-cakes", _, condition_data) if {
condition_data.target.old.attributes.recipient["likes_cakes"]
} else = false
result := condition(
"cake-express:users:recipient-likes-cakes",
{},
{"target":
{"old":
{"attributes":
{"recipient": {"likes_cakes": true}}
}
}
}
)
The code requires base64
encoding before sending it to the API.
To create the base64
encoding, apply the following steps:
Save your Rego code in a file.
Encode it with the command
base64 $FILENAME
.Copy the
base64
encoded Rego code and paste it as value to the attributecode
as shown in Listing 9.19.
Listing 9.19 shows how Example Organization creates a custom condition:
$ curl -X POST \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $token" \
-d '{
"name": "recipient-likes-cakes",
"display_name": "recipient likes cakes",
"documentation": "True if the user recieving a cake likes cakes",
"parameters": [],
"code": "cGFja2FnZSBndWFyZGlhbi5jb25kaXRpb25zCgppbXBvcnQgZnV0dXJlLmtleXdvcmRzLmlmCmltcG9ydCBmdXR1cmUua2V5d29yZHMuaW4KCmNvbmRpdGlvbigiY2FrZS1leHByZXNzOnVzZXJzOnJlY2lwaWVudC1saWtlcy1jYWtlcyIsIF8sIGNvbmRpdGlvbl9kYXRhKSBpZiB7CiAgICBjb25kaXRpb25fZGF0YS50YXJnZXQub2xkLmF0dHJpYnV0ZXMucmVjaXBpZW50WyJsaWtlc19jYWtlcyJdCn0gZWxzZSA9IGZhbHNl"
}' \
$MANAGEMENT_SERVER/conditions/cake-express/users
Listing 9.20 shows how Example Organization then updates the existing capability for ordering cakes:
$ curl -X PUT \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $token" \
-d '{
"display_name": "Cake Orderers can order cake",
"role": {
"app_name": "cake-express",
"namespace_name": "cakes",
"name": "cake-orderer"
},
"conditions": [
{
"app_name": "cake-express",
"namespace_name": "users",
"name": "recipient-likes-cakes",
"parameters": []
}
],
"relation": "AND",
"permissions": [
{
"app_name": "cake-express",
"namespace_name": "cakes",
"name": "order-cake"
}
]
}' \
$MANAGEMENT_SERVER/capabilities/cake-express/cakes/cake-orderer-can-order-cake