]> git.mxchange.org Git - friendica-addons.git/commitdiff
Add an addon for password-based authentication against Keycloak.
authorvery-ape <git@verya.pe>
Tue, 18 May 2021 22:51:30 +0000 (15:51 -0700)
committervery-ape <git@verya.pe>
Tue, 18 May 2021 22:51:30 +0000 (15:51 -0700)
keycloakpassword/README.md [new file with mode: 0644]
keycloakpassword/keycloakpassword.php [new file with mode: 0644]
keycloakpassword/templates/admin.tpl [new file with mode: 0755]

diff --git a/keycloakpassword/README.md b/keycloakpassword/README.md
new file mode 100644 (file)
index 0000000..0dc60c6
--- /dev/null
@@ -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).
diff --git a/keycloakpassword/keycloakpassword.php b/keycloakpassword/keycloakpassword.php
new file mode 100644 (file)
index 0000000..c9ae892
--- /dev/null
@@ -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');
+}
diff --git a/keycloakpassword/templates/admin.tpl b/keycloakpassword/templates/admin.tpl
new file mode 100755 (executable)
index 0000000..449bf7e
--- /dev/null
@@ -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>