Add an addon for password-based authentication against Keycloak.

This commit is contained in:
very-ape 2021-05-18 15:51:30 -07:00
parent 8bd9c6dac9
commit 344e2c6978
3 changed files with 169 additions and 0 deletions

View file

@ -0,0 +1,9 @@
Keycloak Password Auth
======================
Allows for password-based authentication against a Keycloak backend. (Should in
theory work with any OpenID Connect provider with "direct grant" enabled, but
it's only been tested against Keycloak.)
Setting up Keycloak for use with this addon is detailed [in this RedHat
blog entry](https://developers.redhat.com/blog/2020/01/29/api-login-and-jwt-token-generation-using-keycloak#set_up_a_client).

View file

@ -0,0 +1,156 @@
<?php
/**
* Name: Keycloak Password Auth
* Description: Allow password-based authentication via the user's Keycloak credentials.
* Version: 1.0
* Author: Ryan <https://verya.pe/profile/ryan>
*/
use Friendica\App;
use Friendica\Core\Hook;
use Friendica\Core\Logger;
use Friendica\Core\Renderer;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\User;
function keycloakpassword_install()
{
Hook::register('authenticate', __FILE__, 'keycloakpassword_authenticate');
}
function keycloakpassword_request($client_id, $secret, $url, $params = [])
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([
'client_id' => $client_id,
'grant_type' => 'password',
'client_secret' => $secret,
'scope' => 'openid',
] + $params));
$headers = array();
$headers[] = 'Content-Type: application/x-www-form-urlencoded';
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
$res = curl_exec($ch);
if (curl_errno($ch)) {
Logger::error(curl_error($ch));
}
curl_close($ch);
return $res;
}
function keycloakpassword_authenticate($a, &$b)
{
if (empty($b['password'])) {
return;
}
$client_id = DI::config()->get('keycloakpassword', 'client_id', null);
$endpoint = DI::config()->get('keycloakpassword', 'endpoint', null);
$secret = DI::config()->get('keycloakpassword', 'secret', null);
if (!$client_id || !$endpoint || !$secret) {
return;
}
$condition = [
'nickname' => $b['username'],
'blocked' => false,
'account_expired' => false,
'account_removed' => false
];
try {
$user = DBA::selectFirst('user', ['uid'], $condition);
} catch (Exception $e) {
return;
}
$json = keycloakpassword_request(
$client_id,
$secret,
$endpoint . '/token',
[
'username' => $b['username'],
'password' => $b['password']
]
);
$res = json_decode($json, true);
if (array_key_exists('access_token', $res) && !array_key_exists('error', $res)) {
$b['user_record'] = User::getById($user['uid']);
$b['authenticated'] = 1;
// Invalidate the Keycloak session we just created, as we have no use for it.
keycloakpassword_request(
$client_id,
$secret,
$endpoint . '/logout',
[ 'refresh_token' => res['refresh_token'] ]
);
}
}
function keycloakpassword_admin_input($key, $label, $description)
{
return [
'$' . $key => [
$key,
$label,
DI::config()->get('keycloakpassword', $key),
$description,
true, // all the fields are required
]
];
}
function keycloakpassword_addon_admin(&$a, &$o)
{
$form =
keycloakpassword_admin_input(
'client_id',
DI::l10n()->t('Client ID'),
DI::l10n()->t('The name of the OpenID Connect client you created for this addon in Keycloak.'),
) +
keycloakpassword_admin_input(
'secret',
DI::l10n()->t('Client secret'),
DI::l10n()->t('The secret assigned to the OpenID Connect client you created for this addon in Keycloak.'),
) +
keycloakpassword_admin_input(
'endpoint',
DI::l10n()->t('OpenID Connect endpoint'),
DI::l10n()->t(
'URL to the Keycloak endpoint for your client. '
. '(E.g., https://example.com/auth/realms/some-realm/protocol/openid-connect)'
),
) +
[
'$msg' => DI::session()->get('keycloakpassword-msg', false),
'$submit' => DI::l10n()->t('Save Settings'),
];
$t = Renderer::getMarkupTemplate('admin.tpl', 'addon/keycloakpassword/');
$o = Renderer::replaceMacros($t, $form);
}
function keycloakpassword_addon_admin_post(&$a)
{
if (!local_user()) {
return;
}
$set = function ($key) {
$val = (!empty($_POST[$key]) ? trim($_POST[$key]) : '');
DI::config()->set('keycloakpassword', $key, $val);
};
$set('client_id');
$set('secret');
$set('endpoint');
}

View file

@ -0,0 +1,4 @@
{{include file="field_input.tpl" field=$client_id}}
{{include file="field_input.tpl" field=$secret}}
{{include file="field_input.tpl" field=$endpoint}}
<div class="submit"><input type="submit" name="page_site" value="{{$submit}}" /></div>