Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package mapi-header-php for openSUSE:Factory
checked in at 2023-11-01 22:10:32
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/mapi-header-php (Old)
and /work/SRC/openSUSE:Factory/.mapi-header-php.new.17445 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "mapi-header-php"
Wed Nov 1 22:10:32 2023 rev:5 rq:1121575 version:1.3.0.a04f1af
Changes:
--------
--- /work/SRC/openSUSE:Factory/mapi-header-php/mapi-header-php.changes
2023-09-06 18:58:55.761133078 +0200
+++
/work/SRC/openSUSE:Factory/.mapi-header-php.new.17445/mapi-header-php.changes
2023-11-01 22:11:13.226423452 +0100
@@ -1,0 +2,6 @@
+Wed Nov 1 09:22:32 UTC 2023 - Jan Engelhardt <[email protected]>
+
+- Update to release 1.3 (a04f1af)
+ * Add ``KeyCloak`` and ``Token`` classes
+
+-------------------------------------------------------------------
Old:
----
mapi-header-php-1.2.0.0db2832.tar.xz
New:
----
mapi-header-php-1.3.0.a04f1af.tar.xz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ mapi-header-php.spec ++++++
--- /var/tmp/diff_new_pack.FkUTkj/_old 2023-11-01 22:11:13.794444494 +0100
+++ /var/tmp/diff_new_pack.FkUTkj/_new 2023-11-01 22:11:13.798444642 +0100
@@ -19,7 +19,7 @@
%define _empty_manifest_terminate_build 0
Name: mapi-header-php
-Version: 1.2.0.0db2832
+Version: 1.3.0.a04f1af
Release: 0
Summary: Common PHP MAPI header files for grommunio
License: AGPL-3.0-or-later
++++++ _service ++++++
--- /var/tmp/diff_new_pack.FkUTkj/_old 2023-11-01 22:11:13.826445680 +0100
+++ /var/tmp/diff_new_pack.FkUTkj/_new 2023-11-01 22:11:13.830445828 +0100
@@ -1,14 +1,14 @@
<services>
- <service name="tar_scm" mode="disabled">
+ <service name="tar_scm" mode="manual">
<param name="scm">git</param>
<param
name="url">https://github.com/grommunio/mapi-header-php</param>
<param name="revision">master</param>
<param name="versionformat">@PARENT_TAG@.@TAG_OFFSET@.%h</param>
</service>
- <service name="recompress" mode="disabled">
+ <service name="recompress" mode="manual">
<param name="file">*.tar</param>
<param name="compression">xz</param>
</service>
- <service name="set_version" mode="disabled"/>
+ <service name="set_version" mode="manual"/>
</services>
++++++ debian.changelog ++++++
--- /var/tmp/diff_new_pack.FkUTkj/_old 2023-11-01 22:11:13.850446568 +0100
+++ /var/tmp/diff_new_pack.FkUTkj/_new 2023-11-01 22:11:13.850446568 +0100
@@ -1,4 +1,4 @@
-mapi-header-php (1.2.0.0db2832) unstable; urgency=low
+mapi-header-php (1.3.0.a04f1af) unstable; urgency=low
* Initial package.
++++++ mapi-header-php-1.2.0.0db2832.tar.xz ->
mapi-header-php-1.3.0.a04f1af.tar.xz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/mapi-header-php-1.2.0.0db2832/changelog.rst
new/mapi-header-php-1.3.0.a04f1af/changelog.rst
--- old/mapi-header-php-1.2.0.0db2832/changelog.rst 2023-08-28
15:05:09.000000000 +0200
+++ new/mapi-header-php-1.3.0.a04f1af/changelog.rst 2023-10-31
15:58:17.000000000 +0100
@@ -1,3 +1,9 @@
+1.3 (2023-10-31)
+================
+
+* Add ``KeyCloak`` and ``Token`` classes
+
+
1.2 (2023-08-28)
================
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/mapi-header-php-1.2.0.0db2832/class.keycloak.php
new/mapi-header-php-1.3.0.a04f1af/class.keycloak.php
--- old/mapi-header-php-1.2.0.0db2832/class.keycloak.php 1970-01-01
01:00:00.000000000 +0100
+++ new/mapi-header-php-1.3.0.a04f1af/class.keycloak.php 2023-10-31
15:58:17.000000000 +0100
@@ -0,0 +1,331 @@
+<?php
+/*
+ * SPDX-License-Identifier: AGPL-3.0-only
+ * SPDX-FileCopyrightText: Copyright 2023 grommunio GmbH
+ *
+ * Performs several actions against a KeyCloak server.
+ */
+
+// include the token grant class
+require_once 'class.token.php';
+
+class KeyCloak {
+ public $access_token;
+ public $refresh_token;
+ public $id_token;
+ public $redirect_url;
+
+ public $last_refresh_time;
+ private static $_instance;
+ public $grant;
+ public $error;
+
+ protected $realm_id;
+ protected $client_id;
+ protected $secret;
+
+ protected $realm_url;
+ protected $realm_admin_url;
+
+ protected $public_key;
+ protected $is_public;
+
+ /**
+ * The constructor reads all required values from the KeyCloak
configuration file.
+ * This includes values for realm_id, client_id, client_secret,
server_url etc.
+ *
+ * @param mixed $keycloak_config
+ */
+ public function __construct($keycloak_config) {
+ if (gettype($keycloak_config) === 'string') {
+ $keycloak_config = json_decode($keycloak_config);
+ }
+
+ // redirect_url
+ $url = "https://" . $_SERVER['HTTP_HOST'] .
$_SERVER['REQUEST_URI'];
+ $url_exp = explode('?', $url);
+ $url = $url_exp[0];
+ if ($url[-1] == '/') {
+ $url = substr($url, 0, -1);
+ }
+ $this->redirect_url = $url;
+
+ // keycloak Realm ID
+ $this->realm_id = $keycloak_config['realm'] ?? 'grommunio';
+
+ // keycloak client ID
+ $this->client_id = $keycloak_config['resource'] ?? 'gramm';
+
+ // @type {bool} checks if client is a public client and
extracts the public key
+ $this->is_public = $keycloak_config['public-client'] ?? false;
+ $this->public_key = $this->is_public == false ? "" :
"-----BEGIN PUBLIC KEY-----\n" .
chunk_split($keycloak_config['realm-public-key'], 64, "\n") . "\n-----END
PUBLIC KEY-----\n";
+
+ // client secret => obtained if client is not a public client
+ if (!$this->is_public) {
+ $this->secret =
$keycloak_config['credentials']['secret'] ?? $keycloak_config['secret'] ?? null;
+ }
+
+ // keycloak server url
+ $auth_server_url = $keycloak_config['auth-server-url'] ??
'null';
+
+ // Root realm URL.
+ $this->realm_url = $auth_server_url . 'realms/' .
$this->realm_id;
+
+ // Root realm admin URL.
+ $this->realm_admin_url = $auth_server_url . 'admin/realms/' .
$this->realm_id;
+ }
+
+ /**
+ * Static method to instantiate and return a KeyCloak instance from the
+ * default configuration file.
+ *
+ * @return KeyCloak
+ */
+ public static function getInstance() {
+ if (is_null(KeyCloak::$_instance) &&
file_exists(GROMOX_CONFIG_PATH . 'keycloak.json')) {
+ // Read the keycloak config adapter into an instance of
the keyclaok class
+ $keycloak_file = file_get_contents(GROMOX_CONFIG_PATH .
'keycloak.json');
+ $keycloak_json = json_decode($keycloak_file, true);
+ KeyCloak::$_instance = new KeyCloak($keycloak_json);
+ }
+
+ return KeyCloak::$_instance;
+ }
+
+ /**
+ * Returns the last known refresh time.
+ *
+ * @return long
+ */
+ public function get_last_refresh_time() {
+ return $this->last_refresh_time;
+ }
+
+ /**
+ * Sets the last refresh time.
+ *
+ * @param long $time
+ */
+ public function set_last_refresh_time($time) {
+ $this->last_refresh_time = $time;
+ }
+
+ /**
+ * Oauth 2.0 Authorization flow is used to obtain Access token,
+ * refresh token and ID token from keycloak server by sending
+ * https post request (curl) to a /token web endpoint.
+ * The keycloak server will respond with grant back to the
+ * grommunio server.
+ *
+ * We implement three of this protocol:
+ * 1. OAuth 2.0 resource owner password credential grant.
+ * 2. OAuth 2.0 Client Code credential grant.
+ * 3. Refresh token grant.
+ *
+ * The password grant takes two argument:
+ *
+ * @param string $username The username
+ * @param string $password The cleartext password
+ *
+ * @return bool indicating if the request was successful nor not
+ */
+ public function password_grant_req($username, $password) {
+ $params = ['grant_type' => 'password', 'username' => $username,
'password' => $password];
+
+ return $this->request($params);
+ }
+
+ /**
+ * The Oauth 2.0 client credential code grant is the next type request
used to
+ * request access token from keycloak. The logon on the Authentication
server url
+ * (keycloak), on successful authentication. the server replies with
the credential
+ * grant code. This code will be used to request the tokens.
+ *
+ * @param string $code The code from a successful login
redirected from Keycloak
+ * @param null|string $session_host
+ *
+ * @return bool indicating if the request was successful nor not
+ */
+ public function client_credential_grant_req($code, $session_host =
null) {
+ // TODO: $session_host not used here
+ $params = ['grant_type' => 'authorization_code', 'code' =>
$code, 'client_id' => $this->client_id, 'redirect_uri' => $this->redirect_url];
+
+ return $this->request($params);
+ }
+
+ /**
+ * The Oauth 2.0 refresh token grant is the next type request used to
+ * request access token from keycloak. If the client has a valid
refresh token
+ * which has not expired. It can send a request to the server to obtain
new tokens.
+ *
+ * @return bool indicating if the request was successful nor not
+ */
+ public function refresh_grant_req() {
+ // Ensure grant exists, grant is not expired, and we have a
refresh token
+ if (!$this->grant || !$this->refresh_token) {
+ $this->grant = null;
+
+ return false;
+ }
+ $params = ['grant_type' => 'refresh_token', 'refresh_token' =>
$this->refresh_token->get_payload()];
+
+ return $this->request($params);
+ }
+
+ /**
+ * Performs a token request to the KeyCloak server with predefined
parameters.
+ *
+ * @param array $params predefined parameters used for the request
+ *
+ * @return bool indicating if the request was successful or not
+ */
+ protected function request($params) {
+ $headers = ['Content-Type: application/x-www-form-urlencoded'];
+ if ($this->is_public) {
+ $params['client_id'] = $this->client_id;
+ }
+ else {
+ array_push($headers, 'Authorization: Basic ' .
base64_encode($this->client_id . ':' . $this->secret));
+ }
+ $params['scope'] = 'openid';
+ $response = $this->http_curl_request('POST',
'/protocol/openid-connect/token', $headers, http_build_query($params));
+ if ($response['code'] < 200 || $response['code'] > 299) {
+ $this->error = $response['body'];
+ $this->grant = null;
+
+ return false;
+ }
+ $this->grant = $response['body'];
+ if (gettype($this->grant) === 'string') {
+ $this->grant = json_decode($this->grant, true);
+ }
+ else {
+ $this->grant = json_encode($this->grant);
+ }
+ $this->access_token = isset($this->grant['access_token']) ? new
Token($this->grant['access_token']) : null;
+ $this->refresh_token = isset($this->grant['refresh_token']) ?
new Token($this->grant['refresh_token']) : null;
+ $this->id_token = isset($this->grant['id_token']) ? new
Token($this->grant['id_token']) : null;
+
+ return true;
+ }
+
+ /**
+ * Validates the grant represented by the access and refresh tokens in
the grant.
+ * If the refresh token has expired too, return false.
+ *
+ * @return bool
+ */
+ public function validate_grant() {
+ if ($this->validate_token($this->access_token) &&
$this->validate_token($this->refresh_token)) {
+ return true;
+ }
+
+ return $this->refresh_grant_req();
+ }
+
+ /**
+ * Validates a token with the server.
+ *
+ * @param mixed $token
+ *
+ * @return bool
+ */
+ protected function validate_token($token) {
+ if (isset($token)) {
+ $path = "/protocol/openid-connect/token/introspect";
+ $headers = ['Content-Type:
application/x-www-form-urlencoded'];
+ $params = ['token' => $token->get_payload()];
+ if ($this->is_public) {
+ $params['client_id'] = $this->client_id;
+ }
+ else {
+ array_push($headers, 'Authorization: Basic ' .
base64_encode($this->client_id . ':' . $this->secret));
+ }
+ $response = $this->http_curl_request('POST', $path,
$headers, http_build_query($params));
+
+ if ($response['code'] < 200 || $response['code'] > 299)
{
+ return false;
+ }
+
+ try {
+ $data = json_decode($response['body'], true);
+ }
+ catch (Exception $e) {
+ return false;
+ }
+
+ return !array_key_exists('error', $data);
+ }
+
+ return false;
+ }
+
+ /**
+ * Indicates if the access token is expired.
+ *
+ * @return bool
+ */
+ public function is_expired() {
+ if (!$this->access_token) {
+ return true;
+ }
+
+ return $this->access_token->is_expired();
+ }
+
+ /**
+ * Builds a KeyCloak login url used with the client credential code.
+ *
+ * @param string $redirect_url Redirect URL to be parameterized in the
URL
+ *
+ * @return string
+ */
+ public function login_url($redirect_url) {
+ $uuid = bin2hex(openssl_random_pseudo_bytes(32));
+
+ return $this->realm_url .
'/protocol/openid-connect/auth?scope=openid&client_id=' .
urlencode($this->client_id) . '&state=' . urlencode($uuid) . '&redirect_uri=' .
urlencode($redirect_url) . '&response_type=code';
+ }
+
+ /**
+ * Builds a KeyCloak logout url.
+ *
+ * @return string
+ */
+ public function logout() {
+ $params = '?id_token_hint=' . $this->id_token->get_payload() .
'&refresh_token=' . $this->refresh_token->get_payload();
+
+ return $this->realm_url . '/protocol/openid-connect/logout' .
$params;
+ }
+
+ /*
+ * Send HTTP request via CURL.
+ *
+ * @param string $method The HTTP request to use. (Default to GET)
+ * @param array $headers The HTTP headers to be passed into the request
+ * @param string $data The data to be passed into the body of the
request
+ * @param string $domain
+ *
+ * @return array associative array with 'code' for response code and
'body' for request body
+ */
+ protected function http_curl_request($method, $domain, $headers = [],
$data = '') {
+ $request = curl_init();
+ curl_setopt($request, CURLOPT_URL, $this->realm_url . $domain);
+ if (strcmp(strtoupper($method), 'POST') == 0) {
+ curl_setopt($request, CURLOPT_POST, true);
+ curl_setopt($request, CURLOPT_POSTFIELDS, $data);
+ array_push($headers, 'Content-Length: ' .
strlen($data));
+ }
+
+ curl_setopt($request, CURLOPT_HTTPHEADER, $headers);
+ curl_setopt($request, CURLOPT_RETURNTRANSFER, true);
+
+ $response = curl_exec($request);
+ $response_code = curl_getinfo($request, CURLINFO_HTTP_CODE);
+ curl_close($request);
+
+ return [
+ 'code' => $response_code,
+ 'body' => $response,
+ ];
+ }
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/mapi-header-php-1.2.0.0db2832/class.token.php
new/mapi-header-php-1.3.0.a04f1af/class.token.php
--- old/mapi-header-php-1.2.0.0db2832/class.token.php 1970-01-01
01:00:00.000000000 +0100
+++ new/mapi-header-php-1.3.0.a04f1af/class.token.php 2023-10-31
15:58:17.000000000 +0100
@@ -0,0 +1,90 @@
+<?php
+/*
+ * SPDX-License-Identifier: AGPL-3.0-only
+ * SPDX-FileCopyrightText: Copyright 2023 grommunio GmbH
+ *
+ * Object class to parse a JSON Web Token.
+ */
+
+class Token {
+ public $token_header;
+ public $token_payload;
+ public $token_signature;
+ public $signed;
+
+ protected $_raw;
+
+ /**
+ * Constructor loading a token string received from Keycloak.
+ *
+ * @param string $keycloak_token holding
+ */
+ public function __construct($keycloak_token) {
+ $this->_raw = $keycloak_token;
+ if (isset($keycloak_token)) {
+ try {
+ $parts = explode('.', $keycloak_token);
+ $th = base64_decode($parts[0]);
+ $tp = base64_decode($parts[1]);
+ $ts = base64_decode($parts[2]);
+ $this->token_header = json_decode($th, true);
+ $this->token_payload = json_decode($tp, true);
+ $this->token_signature = $ts;
+ $this->signed = $parts[0] . '.' . $parts[1];
+ }
+ catch (Exception $e) {
+ $this->token_payload = [
+ 'expires_at' => 0,
+ ];
+ }
+ }
+ }
+
+ /**
+ * Returns the signature of the token.
+ *
+ * @return string
+ */
+ public function get_signature() {
+ return $this->token_signature;
+ }
+
+ /**
+ * Indicates if the token was singned.
+ *
+ * @return bool
+ */
+ public function get_signed() {
+ return $this->signed;
+ }
+
+ /**
+ * Returns raw payload.
+ *
+ * @return string
+ */
+ public function get_payload() {
+ return $this->_raw;
+ }
+
+ /**
+ * Returns the value of a claim if it's defined in the payload.
+ * Otherwise returns an empty string.
+ *
+ * @param string $claim
+ *
+ * @return string
+ */
+ public function get_claims($claim) {
+ return $this->token_payload[$claim] ?? '';
+ }
+
+ /**
+ * Checks if a token is expired comparing to the current time.
+ *
+ * @return bool
+ */
+ public function is_expired() {
+ return $this->token_payload['exp'] < time() ||
$this->token_payload['iat'] < time() - 86400;
+ }
+}
++++++ mapi-header-php.dsc ++++++
--- /var/tmp/diff_new_pack.FkUTkj/_old 2023-11-01 22:11:13.954450421 +0100
+++ /var/tmp/diff_new_pack.FkUTkj/_new 2023-11-01 22:11:13.958450570 +0100
@@ -1,7 +1,7 @@
Format: 1.0
Source: mapi-header-php
Architecture: any
-Version: 1.2.0.0db2832
+Version: 1.3.0.a04f1af
DEBTRANSFORM-RELEASE: 1
Maintainer: Grommunio <[email protected]>
Homepage: https://grommunio.com