Skip to content
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: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,6 @@ cypress.env.json

# OIDC plugin activation flag
activate-oidc-plugin

# Docker cert files for keycloak
docker/certs
60 changes: 59 additions & 1 deletion docker/bootstrap.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ function get_host_and_port($value, $default_port)
'atom.elasticsearch_host' => getenv_or_fail('ATOM_ELASTICSEARCH_HOST'),
'atom.memcached_host' => getenv_or_fail('ATOM_MEMCACHED_HOST'),
'atom.gearmand_host' => getenv_or_fail('ATOM_GEARMAND_HOST'),
'atom.keycloak_host' => getenv_default('ATOM_KEYCLOAK_HOST', ''),
'atom.keycloak_port' => getenv_default('ATOM_KEYCLOAK_PORT', '9000'),
'atom.mysql_dsn' => getenv_or_fail('ATOM_MYSQL_DSN'),
'atom.mysql_username' => getenv_or_fail('ATOM_MYSQL_USERNAME'),
'atom.mysql_password' => getenv_or_fail('ATOM_MYSQL_PASSWORD'),
Expand Down Expand Up @@ -113,7 +115,7 @@ function get_host_and_port($value, $default_port)
read_only: false
htmlpurifier_enabled: false
csp:
response_header: Content-Security-Policy
response_header: Content-Security-Policy-Report-Only

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This change should perhaps be in its own commit as it seems unrelated to adding Keycloak to the dev Docker env.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If you want to avoid using -Report-Only, you should be able to add the Keycloak host to the CSP header/meta connect-src. It must be the host without path, so it works with all redirects.

directives: >
default-src 'self';
font-src 'self' https://fonts.gstatic.com;
Expand All @@ -124,8 +126,64 @@ function get_host_and_port($value, $default_port)
worker-src 'self' blob:;
connect-src 'self' https://*.google-analytics.com https://*.analytics.google.com https://*.googletagmanager.com https://*.googleapis.com *.google.com https://*.gstatic.com data: blob:;
frame-ancestors 'self';
EOT;

if (!empty($CONFIG['atom.keycloak_host'])) {
$keycloakParts = get_host_and_port($CONFIG['atom.keycloak_host'], $CONFIG['atom.keycloak_port']);
$keycloakBaseUrl = 'http://localhost:' . $keycloakParts['port'];

$app_yml .= <<<EOT

oidc:
providers:
primary:
url: 'http://{$CONFIG["atom.keycloak_host"]}:{$CONFIG["atom.keycloak_port"]}/realms/artefactual'
issuer: '{$keycloakBaseUrl}/realms/artefactual'
client_id: 'artefactual-atom'
client_secret: 'example-secret'
authorization_endpoint: '{$keycloakBaseUrl}/realms/artefactual/protocol/openid-connect/auth'
token_endpoint: '{$keycloakBaseUrl}/realms/artefactual/protocol/openid-connect/token'
userinfo_endpoint: '{$keycloakBaseUrl}/realms/artefactual/protocol/openid-connect/userinfo'
jwks_uri: '{$keycloakBaseUrl}/realms/artefactual/protocol/openid-connect/certs'

send_oidc_logout: true

enable_refresh_token_use: true

server_cert: 'docker/certs/cert.pem'

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This will require generating certs and adding them to the certs folder


set_groups_from_attributes: true
user_groups:
administrator:
attribute_value: 'atom-admin'
group_id: 100
editor:
attribute_value: 'atom-editor'
group_id: 101
contributor:
attribute_value: 'atom-contributor'
group_id: 102
translator:
attribute_value: 'atom-translator'
group_id: 103

scopes:
- 'openid'
- 'profile'
- 'email'

roles_source: 'access-token'
roles_path:
- 'realm_access'
- 'roles'

user_matching_source: 'oidc-email'
auto_create_atom_user: true
redirect_url: 'http://127.0.0.1:63001/index.php/oidc/login'
logout_redirect_url: 'http://127.0.0.1:63001'

EOT;
}

file_put_contents(_ATOM_DIR.'/apps/qubit/config/app.yml', $app_yml);
}
Expand Down
37 changes: 37 additions & 0 deletions docker/docker-compose.keycloak.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
volumes:
keycloak_data:

networks:
default:
name: keycloak
external: true

services:

keycloak:
image: quay.io/keycloak/keycloak:${latest:-latest}
platform: linux/amd64
environment:
KC_BOOTSTRAP_ADMIN_USER: admin
KC_BOOTSTRAP_ADMIN_PASSWORD: admin
KC_HOSTNAME: localhost
KC_HOSTNAME_PORT: 9000
KC_LOG_LEVEL: DEBUG
KC_REALM_NAME: artefactual
KC_IMPORT: /opt/keycloak/data/import/realm.json
command:
- start-dev
- --import-realm
volumes:
- keycloak_data:/opt/keycloak/data
- ./realm.json:/opt/keycloak/data/import/realm.json:ro
ports:
- "9000:8080"
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health/ready"]
interval: 15s
timeout: 2s
retries: 5
start_period: 40s
2 changes: 2 additions & 0 deletions docker/etc/environment
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,5 @@ ATOM_MYSQL_DSN=mysql:host=percona;port=3306;dbname=atom;charset=utf8mb4
ATOM_MYSQL_USERNAME=atom
ATOM_MYSQL_PASSWORD=atom_12345
NODE_ENV=development
ATOM_KEYCLOAK_HOST=host.docker.internal
ATOM_KEYCLOAK_PORT=9000

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The OIDC plugin will need activation. This commit allows the plugin to be activated using an env var.

56 changes: 56 additions & 0 deletions docker/realm.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
{
"id": "artefactual",
"realm": "artefactual",
"sslRequired": "none",
"enabled": true,
"eventsEnabled": true,
"eventsExpiration": 900,
"adminEventsEnabled": true,
"adminEventsDetailsEnabled": true,
"attributes": {
"adminEventsExpiration": "900"
},
"roles": {
"realm": [
{
"name": "atom-admin"
}
]
},
"clients": [
{
"id": "artefactual-atom",
"clientId": "artefactual-atom",
"name": "atom",
"enabled": true,
"rootUrl": "http://127.0.0.1:63001",
"adminUrl": "http://127.0.0.1:63001",
"baseUrl": "http://127.0.0.1:63001",
"clientAuthenticatorType": "client-secret",
"secret": "example-secret",
"redirectUris": ["http://127.0.0.1:63001/*"],
"webOrigins": ["http://127.0.0.1:63001"],
"standardFlowEnabled": true,
"serviceAccountsEnabled": true,
"authorizationServicesEnabled": true,
"publicClient": false
}
],
"users": [
{
"id": "demo",
"email": "demo@example.com",
"username": "demo",
"enabled": true,
"emailVerified": true,
"credentials": [
{
"temporary": false,
"type": "password",
"value": "demo"
}
],
"realmRoles": ["atom-admin"]
}
]
}
17 changes: 16 additions & 1 deletion plugins/arOidcPlugin/lib/oidcUser.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -455,7 +455,22 @@ protected function setOidcProviderDetails(string $providerId = ''): bool
$this->oidcClient->setProviderUrl($provider['url']);
$this->oidcClient->setClientID($provider['client_id']);
$this->oidcClient->setClientSecret($provider['client_secret']);
$this->oidcClient->setIssuer($provider['url']);
if (isset($provider['issuer'])) {
$this->oidcClient->setIssuer($provider['issuer']);

$endpointOverrides = [];
foreach (['authorization_endpoint', 'token_endpoint', 'userinfo_endpoint', 'jwks_uri', 'end_session_endpoint'] as $endpointName) {
if (!empty($provider[$endpointName])) {
$endpointOverrides[$endpointName] = $provider[$endpointName];
}
}

if (!empty($endpointOverrides)) {
$this->oidcClient->providerConfigParam($endpointOverrides);
}
} else {
$this->oidcClient->setProviderUrl($provider['url']);
}
Comment on lines +458 to +473

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I noticed a new OIDC param 'issuer' - what is this for?


return true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ public function execute($request)
$this->context->user->setAttribute('atom-login-referer', $request->getReferer());
}

if ($request->isMethod('post') || isset($_REQUEST['code'])) {
if (null !== $providerId = $this->context->user->parseProviderIdFromUrl($this->context->user->getAttribute('atom-login-referer', null))) {
if (($request->isMethod('post') || isset($_REQUEST['code'])) && null !== $this->context->user->getAttribute('atom-login-referer')) {
if (null !== $providerId = $this->context->user->parseProviderIdFromUrl($this->context->user->getAttribute('atom-login-referer'))) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why are the checks duplicated?

$this->context->user->validateProviderId($providerId, true);
}

Expand Down
Loading