Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨(keycloak) add ralph client for generating access tokens #27

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,6 @@ jobs:
working_directory: ~/fun
steps:
- checkout
- run:
name: Create external potsie network
command: docker network create potsie_default
- run:
name: Bootstrap project
command: make bootstrap
Expand Down
7 changes: 7 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ e2e/data/video.mp4: ## generate a 5 second long video and put it in e2e/data di

bootstrap: ## bootstrap the project
bootstrap: \
network \
migrate \
run \
realm
Expand Down Expand Up @@ -109,6 +110,12 @@ migrate: ## perform database migrations
$(COMPOSE_RUN) edx_cms python manage.py cms migrate
.PHONY: migrate

network: ## create external networks
@echo "Creating external networks..."
docker network inspect potsie >/dev/null 2>&1 || docker network create potsie
docker network inspect ralph >/dev/null 2>&1 || docker network create ralph
.PHONY: network

run: ## start graylog/keycloak services
run: \
run-keycloak \
Expand Down
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,16 @@ make realm
You can now login to grafana using the following credentials:
`grafana:funfunfun`.

The `ralph` client has been configured with the specific audience `http://localhost:8100` but it can be changed through the Keycloak interface.
Two users have been created for this client:
- `ralph_admin:funfunfun` with the scope `all`
- `ralph_learner:moocmooc` with the scope `statements/read/mine`

To get an access token, you can use the following command:
```
curl -X POST -d "grant_type=password" -d "client_id=ralph" -d "client_secret=bcef3562-730d-4575-9e39-63e185f99bca" -d "username=ralph_admin" -d "password=funfunfun" http://localhost:8080/auth/realms/fun-mooc/protocol/openid-connect/token
```

## License

This work is released under the MIT license (see [LICENSE](./LICENSE)).
54 changes: 47 additions & 7 deletions bin/realm
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,18 @@ set -eo pipefail
export PATH="/opt/jboss/keycloak/bin/:${PATH}"

declare realm="${DEFAULT_REALM_NAME}"
declare user="${TEST_USER_NAME}"
declare password="${TEST_USER_PASSWORD}"
declare email="${TEST_USER_EMAIL}"
declare potsie_user="${POTSIE_TEST_USER_NAME}"
declare potsie_password="${POTSIE_TEST_USER_PASSWORD}"
declare potsie_email="${POTSIE_TEST_USER_EMAIL}"
declare client_id="${GRAFANA_CLIENT_ID}"
declare client_uuid
declare client_secret="${GRAFANA_CLIENT_SECRET}"
declare ralph_admin_user="${RALPH_ADMIN_USER_NAME}"
declare ralph_admin_password="${RALPH_ADMIN_USER_PASSWORD}"
declare ralph_admin_email="${RALPH_ADMIN_USER_EMAIL}"
declare ralph_learner_user="${RALPH_LEARNER_USER_NAME}"
declare ralph_learner_password="${RALPH_LEARNER_USER_PASSWORD}"
declare ralph_learner_email="${RALPH_LEARNER_USER_EMAIL}"

# Server login
kcadm.sh config credentials \
Expand All @@ -28,7 +34,7 @@ fi
# And (re-)create it
kcadm.sh create realms -s realm="${realm}" -s enabled=true

# Create a client along with its roles
# Create a potsie client along with its roles
echo "Will create potsie client..."
kcadm.sh create clients -r "${realm}" -f - < /tmp/config/clients/potsie.json
client_uuid=$(kcadm.sh get clients -r fun-mooc --fields id -q clientId=potsie --format csv | sed 's/"//g')
Expand All @@ -37,7 +43,41 @@ kcadm.sh create "clients/${client_uuid}/roles" -r "${realm}" -s name=editor
kcadm.sh create "clients/${client_uuid}/roles" -r "${realm}" -s name=viewer

# Create a new user
kcadm.sh create users -r "${realm}" -s username="${user}" -s email="${email}" -s enabled=true
kcadm.sh set-password -r "${realm}" --username "${user}" --new-password "${password}"
kcadm.sh create users -r "${realm}" -s username="${potsie_user}" -s email="${potsie_email}" -s enabled=true
kcadm.sh set-password -r "${realm}" --username "${potsie_user}" --new-password "${potsie_password}"
# Add role for potsie client
kcadm.sh add-roles -r "${realm}" --uusername "${user}" --cclientid "potsie" --rolename "viewer"
kcadm.sh add-roles -r "${realm}" --uusername "${potsie_user}" --cclientid "potsie" --rolename "viewer"


# Create a ralph client along with its roles
echo "Will create ralph client..."
kcadm.sh create clients -r "${realm}" -f - < /tmp/config/clients/ralph.json
client_uuid=$(kcadm.sh get clients -r fun-mooc --fields id -q clientId=ralph --format csv | sed 's/"//g')
kcadm.sh create "clients/${client_uuid}/roles" -r "${realm}" -s name=all
kcadm.sh create "clients/${client_uuid}/roles" -r "${realm}" -s name=all/read
kcadm.sh create "clients/${client_uuid}/roles" -r "${realm}" -s name=state
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want a one-to-one role to scope relationship?
If yes, and if we want to support all supported Ralph scopes - could we add the remaining roles?
state
state/write
state/read
define
profile/write
profile/read

Also, it seems that Keycloak doesn't automatically add scopes to the user access_token based on his assigned roles.
One (manual) approach might be to add something like:

# Create Client Scopes
kcadm.sh create client-scopes -r fun-mooc -s name=all -s protocol=openid-connect
kcadm.sh create client-scopes -r fun-mooc -s name=statements/read/mine -s protocol=openid-connect
# etc...

# Get Client Scope IDs
# Note: Unfortunately, it seems `get client-scopes` doesn't support the `-q/--query` option
# See https://github.com/keycloak/keycloak/issues/22609
ALL_SCOPE_ID=$(kcadm.sh get client-scopes -r fun-mooc --fields id,name --format csv | grep \"all\" | cut -d "," -f1 | sed 's/"//g')
STATEMENTS_READ_MINE_SCOPE_ID=$(kcadm.sh get client-scopes -r fun-mooc --fields id,name --format csv | grep \"statements/read/mine\" | cut -d "," -f1 | sed 's/"//g')

# Set Client Scopes as Default Client Scopes
# Note: this is useful if we want to set available scopes in the `access_token` by default.
# If omitted, the scopes are only added to the token if requested.
kcadm.sh update "clients/${client_uuid}/default-client-scopes/${ALL_SCOPE_ID}" -r fun-mooc
kcadm.sh update "clients/${client_uuid}/default-client-scopes/${STATEMENTS_READ_MINE_SCOPE_ID}" -r fun-mooc

# Create role to client-scope mappings
ROLES=$(kcadm.sh get "clients/${client_uuid}/roles" -r fun-mooc --fields=id,name --format csv)
ALL_ROLE_ID=$(echo "${ROLES}" | grep \"all\" | cut -d "," -f1 | sed 's/"//g')
STATEMENTS_READ_MINE_ROLE_ID=$(echo "${ROLES}" | grep \"statements/read/mine\" | cut -d "," -f1 | sed 's/"//g')
kcadm.sh create "client-scopes/${ALL_SCOPE_ID}/scope-mappings/clients/${client_uuid}" \
    -r fun-mooc \
    -f - << EOF
[
    {
        "id": "${ALL_ROLE_ID}",
        "name": "all",
        "composite": false,
        "clientRole": true,
        "containerId": "${client_uuid}"
    }
]
EOF

kcadm.sh create "client-scopes/${STATEMENTS_READ_MINE_SCOPE_ID}/scope-mappings/clients/${client_uuid}" \
    -r fun-mooc \
    -f - << EOF
[
    {
        "id": "${STATEMENTS_READ_MINE_ROLE_ID}",
        "name": "statements/read/mine",
        "composite": false,
        "clientRole": true,
        "containerId": "${client_uuid}"
    }
]
EOF

However, I'm new to Keycloak; thus, maybe there is a better/automatic solution to add the scopes to the access_token/map roles to scopes?

kcadm.sh create "clients/${client_uuid}/roles" -r "${realm}" -s name=statements/read
kcadm.sh create "clients/${client_uuid}/roles" -r "${realm}" -s name=statements/read/mine
kcadm.sh create "clients/${client_uuid}/roles" -r "${realm}" -s name=statements/write

# Create an audience for this specific client
kcadm.sh create "clients/${client_uuid}/protocol-mappers/models" -r "${realm}" \
-s name=audience-mapping \
-s protocol=openid-connect \
-s protocolMapper=oidc-audience-mapper \
-s 'config."included.custom.audience"=http://localhost:8100' \
-s 'config."access.token.claim"=true' \
-s 'config."id.token.claim"=false'


# Create an admin user
kcadm.sh create users -r "${realm}" -s username="${ralph_admin_user}" -s email="${ralph_admin_email}" -s enabled=true
kcadm.sh set-password -r "${realm}" --username "${ralph_admin_user}" --new-password "${ralph_admin_password}"
# Add role for ralph user
kcadm.sh add-roles -r "${realm}" --uusername "${ralph_admin_user}" --cclientid "ralph" --rolename "all"

# Create a user that can only read its statements
kcadm.sh create users -r "${realm}" -s username="${ralph_learner_user}" -s email="${ralph_learner_email}" -s enabled=true
kcadm.sh set-password -r "${realm}" --username "${ralph_learner_user}" --new-password "${ralph_learner_password}"
# Add role for ralph user
kcadm.sh add-roles -r "${realm}" --uusername "${ralph_learner_user}" --cclientid "ralph" --rolename "statements/read/mine"
34 changes: 34 additions & 0 deletions data/keycloak/clients/ralph.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"clientId": "ralph",
"name": "Ralph",
"description": "The ultimate toolbox for your learning analytics",
"enabled": true,
"rootUrl": "http://localhost:8100",
"adminUrl": "http://localhost:8100",
"baseUrl": "/",
"clientAuthenticatorType": "client-secret",
"secret": "bcef3562-730d-4575-9e39-63e185f99bca",
"redirectUris": ["http://localhost:8100/whoami"],
"webOrigins": ["http://localhost:8100"],
"standardFlowEnabled": true,
"directAccessGrantsEnabled": true,
"fullScopeAllowed": false,
"protocol": "openid-connect",
"publicClient": false,
"protocolMappers": [
{
"name": "Roles",
"protocol": "openid-connect",
"protocolMapper": "oidc-usermodel-client-role-mapper",
"config": {
"claim.name": "roles",
"jsonType.label": "String",
"usermodel.clientRoleMapping.clientId": "ralph",
"userinfo.token.claim": "true",
"id.token.claim": "true",
"access.token.claim": "true",
"multivalued": "true"
}
}
]
}
5 changes: 5 additions & 0 deletions docker-compose.keycloak.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ services:
potsie_default:
aliases:
- keycloak
ralph:
aliases:
- keycloak
default:

keycloak_postgres:
Expand All @@ -26,3 +29,5 @@ services:
networks:
potsie_default:
external: true
ralph:
external: true
17 changes: 13 additions & 4 deletions env.d/keycloak
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,19 @@ DB_PASSWORD=password
KEYCLOAK_USER=admin
KEYCLOAK_PASSWORD=pass

# Potsie
DEFAULT_REALM_NAME=fun-mooc
TEST_USER_NAME=grafana
TEST_USER_PASSWORD=funfunfun
[email protected]

# Potsie
POTSIE_TEST_USER_NAME=grafana
POTSIE_TEST_USER_PASSWORD=funfunfun
[email protected]
GRAFANA_CLIENT_ID=potsie
GRAFANA_CLIENT_SECRET=fa9e98ee-61a1-4092-8dac-1597da0c1bb0

# Ralph
RALPH_ADMIN_USER_NAME=ralph_admin
RALPH_ADMIN_USER_PASSWORD=funfunfun
[email protected]
RALPH_LEARNER_USER_NAME=ralph_learner
RALPH_LEARNER_USER_PASSWORD=moocmooc
[email protected]