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

Reply via email to