Skip to content
Snippets Groups Projects
Commit 82adedbb authored by lukasmatusiewicz's avatar lukasmatusiewicz
Browse files

Update PrivacyIDEA.php

cleanup, comments update, php8 update
parent 0a2b099e
No related branches found
No related tags found
No related merge requests found
<?php <?php
/*
* Copyright 2024 NetKnights GmbH - lukas.matusiewicz@netknights.it
* <p>
* Licensed under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3;
* you may not use this file except in compliance with the License.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
//namespace PrivacyIdea\PHPClient; //namespace PrivacyIDEA\PHPClient;
const AUTHENTICATORDATA = "authenticatordata"; const AUTHENTICATORDATA = "authenticatordata";
const CLIENTDATA = "clientdata"; const CLIENTDATA = "clientdata";
...@@ -10,63 +22,65 @@ const USERHANDLE = "userhandle"; ...@@ -10,63 +22,65 @@ const USERHANDLE = "userhandle";
const ASSERTIONCLIENTEXTENSIONS = "assertionclientextensions"; const ASSERTIONCLIENTEXTENSIONS = "assertionclientextensions";
/** /**
* All the API requests which you need are already done and set to methods in this class. * PHP client to aid develop plugins for the privacyIDEA authentication server.
* All you have to do is include the SDK-Autoloader to your PHP file * Include the Client-Autoloader to your PHP file or simply install it using Composer.
* and call the methods adding the needed parameters.
* *
* @author Lukas Matusiewicz <lukas.matusiewicz@netknights.it> * @author Lukas Matusiewicz <lukas.matusiewicz@netknights.it>
*/ */
class PrivacyIDEA class PrivacyIDEA
{ {
/* @var string UserAgent to use in requests made to privacyIDEA. */ /* @var string User agent name which should be forwarded to the privacyIDEA server. */
public $userAgent = ""; public string $userAgent = "";
/* @var string URL of the privacyIDEA server. */ /* @var string URL of the privacyIDEA server. */
public $serverURL = ""; public string $serverURL = "";
/* @var string User's realm. */
public string $realm = "";
/* @var string Here is realm of users account. */ /* @var bool Disable host verification for SSL. */
public $realm = ""; public bool $sslVerifyHost = true;
/* @var bool Host verification can be disabled in SSL. */ /* @var bool Disable peer verification for SSL. */
public $sslVerifyHost = true; public bool $sslVerifyPeer = true;
/* @var bool Peer verification can be disabled in SSL. */ /* @var string Account name for privacyIDEA service account. Required to use the /validate/triggerchallenge endpoint. */
public $sslVerifyPeer = true; public string $serviceAccountName = "";
/* @var string Account name for a service account to the privacyIDEA server. This is required to use the /validate/triggerchallenge endpoint. */ /* @var string Password for privacyIDEA service account. Required to use the /validate/triggerchallenge endpoint. */
public $serviceAccountName = ""; public string $serviceAccountPass = "";
/* @var string Password for a service account to the privacyIDEA server. This is required to use the /validate/triggerchallenge endpoint. */ /* @var string Realm for privacyIDEA service account. Optional to use the /validate/triggerchallenge endpoint. */
public $serviceAccountPass = ""; public string $serviceAccountRealm = "";
/* @var string Realm for a service account to the privacyIDEA server. This is required to use the /validate/triggerchallenge endpoint. This is optional. */ /* @var bool Send the "client" parameter to allow using the original IP address in the privacyIDEA policies. */
public $serviceAccountRealm = ""; public bool $forwardClientIP = false;
/* @var object Implementation of the PILog interface. */ /* @var object|null Implementation of the PILog interface. */
public $logger = null; public ?object $logger = null;
/** /**
* PrivacyIDEA constructor. * PrivacyIDEA constructor.
* @param $userAgent string the user agent that should be used for the requests made * @param $userAgent string User agent.
* @param $serverURL string the url of the privacyIDEA server * @param $serverURL string privacyIDEA server URL.
*/ */
public function __construct($userAgent, $serverURL) public function __construct(string $userAgent, string $serverURL)
{ {
$this->userAgent = $userAgent; $this->userAgent = $userAgent;
$this->serverURL = $serverURL; $this->serverURL = $serverURL;
} }
/** /**
* Try to authenticate the user with the /validate/check endpoint. * Try to authenticate the user by the /validate/check endpoint.
* *
* @param $username string * @param string $username Username to authenticate.
* @param $pass string this can be the OTP, but also the PIN to trigger a token or PIN+OTP depending on the configuration of the server. * @param string $pass This can be the OTP, but also the PIN to trigger a token or PIN+OTP depending on the configuration of the server.
* @param null $transactionID Optional transaction ID. Used to reference a challenge that was triggered beforehand. * @param string|null $transactionID Optional transaction ID. Used to reference a challenge that was triggered beforehand.
* @param null $headers Optional headers array to forward to the server. * @param array|null $headers Optional headers to forward to the server.
* @return PIResponse|null null if response was empty or malformed, or parameter missing * @return PIResponse|null Returns PIResponse object or null if response was empty or malformed, or some parameter is missing.
* @throws PIBadRequestException * @throws PIBadRequestException If an error occurs during the request.
*/ */
public function validateCheck($username, $pass, $transactionID = null, $headers=null) public function validateCheck(string $username, string $pass, string $transactionID = null, array $headers = null): ?PIResponse
{ {
assert('string' === gettype($username)); assert('string' === gettype($username));
assert('string' === gettype($pass)); assert('string' === gettype($pass));
...@@ -109,12 +123,12 @@ class PrivacyIDEA ...@@ -109,12 +123,12 @@ class PrivacyIDEA
* Trigger all challenges for the given username. * Trigger all challenges for the given username.
* This function requires a service account to be set. * This function requires a service account to be set.
* *
* @param string $username * @param string $username Username for which the challenges should be triggered.
* @param null $headers Optional headers array to forward to the server. * @param array|null $headers Optional headers to forward to the server.
* @return PIResponse|null null if response was empty or malformed, or parameter missing * @return PIResponse|null Returns PIResponse object or null if response was empty or malformed, or some parameter is missing.
* @throws PIBadRequestException * @throws PIBadRequestException If an error occurs during the request.
*/ */
public function triggerChallenge($username, $headers = null) public function triggerChallenge(string $username, array $headers = null): ?PIResponse
{ {
assert('string' === gettype($username)); assert('string' === gettype($username));
...@@ -151,14 +165,14 @@ class PrivacyIDEA ...@@ -151,14 +165,14 @@ class PrivacyIDEA
} }
/** /**
* Poll for the status of a transaction (challenge). * Poll for the transaction status.
* *
* @param $transactionID string transactionId of the push challenge that was triggered before * @param $transactionID string Transaction ID of the triggered challenge.
* @param null $headers Optional headers array to forward to the server. * @param array|null $headers Optional headers to forward to the server.
* @return bool true if the Push request has been accepted, false otherwise. * @return bool True if the push request has been accepted, false otherwise.
* @throws PIBadRequestException * @throws PIBadRequestException If an error occurs during the request.
*/ */
public function pollTransaction($transactionID, $headers = null) public function pollTransaction(string $transactionID, array $headers = null): bool
{ {
assert('string' === gettype($transactionID)); assert('string' === gettype($transactionID));
...@@ -181,79 +195,17 @@ class PrivacyIDEA ...@@ -181,79 +195,17 @@ class PrivacyIDEA
} }
/** /**
* Check if user already has token and if not, enroll a new token * Send request to /validate/check endpoint with the data required to authenticate using WebAuthn token.
* *
* @param string $username * @param string $username Username to authenticate.
* @param string $genkey * @param string $transactionID Transaction ID of the triggered challenge.
* @param string $type * @param string $webAuthnSignResponse WebAuthn sign response.
* @param string $description * @param string $origin Origin required to authenticate using WebAuthn token.
* @param null $headers Optional headers array to forward to the server. * @param array|null $headers Optional headers to forward to the server.
* @return mixed Object representing the response of the server or null if parameters are missing * @return PIResponse|null Returns PIResponse object or null if response was empty or malformed, or some parameter is missing.
* @throws PIBadRequestException * @throws PIBadRequestException If an error occurs during the request.
*/ */
public function enrollToken($username, $genkey, $type, $description = "", $headers = null) // No return type because mixed not allowed yet public function validateCheckWebAuthn(string $username, string $transactionID, string $webAuthnSignResponse, string $origin, array $headers = null): ?PIResponse
{
assert('string' === gettype($username));
assert('string' === gettype($type));
assert('string' === gettype($genkey));
if (isset($description))
{
assert('string' === gettype($description));
}
// Check if parameters contain the required keys
if (empty($username) || empty($type))
{
$this->debugLog("Token enrollment not possible because parameters are not complete");
return null;
}
$params["user"] = $username;
$params["realm"] = $this->realm;
$params["genkey"] = $genkey;
$params["type"] = $type;
$params["description"] = in_array("description", $params) ? $description : "";
$authToken = $this->getAuthToken();
// If error occurred in getAuthToken() - return this error in PIResponse object
$authTokenHeader = array("authorization:" . $authToken);
if (!empty($headers))
{
$headers = array_merge($headers, $authTokenHeader);
}
else
{
$headers = $authTokenHeader;
}
// Check if user has token
$tokenInfo = json_decode($this->sendRequest(array("user" => $username, "realm" => $params["realm"]), $headers, 'GET', '/token'));
if (!empty($tokenInfo->result->value->tokens))
{
$this->debugLog("enrollToken: User already has a token.");
return null;
}
else
{
// Call /token/init endpoint and return the response
return json_decode($this->sendRequest($params, $headers, 'POST', '/token/init'));
}
}
/**
* Sends a request to /validate/check with the data required to authenticate with a WebAuthn token.
*
* @param string $username
* @param string $transactionID
* @param string $webAuthnSignResponse
* @param string $origin
* @param null $headers Optional headers array to forward to the server.
* @return PIResponse|null returns null if the response was empty or malformed
* @throws PIBadRequestException
*/
public function validateCheckWebAuthn($username, $transactionID, $webAuthnSignResponse, $origin, $headers = null)
{ {
assert('string' === gettype($username)); assert('string' === gettype($username));
assert('string' === gettype($transactionID)); assert('string' === gettype($transactionID));
...@@ -312,16 +264,16 @@ class PrivacyIDEA ...@@ -312,16 +264,16 @@ class PrivacyIDEA
} }
/** /**
* Sends a request to /validate/check with the data required to authenticate with an U2F token. * Sends request to /validate/check endpoint with the data required to authenticate using U2F token.
* *
* @param string $username * @param string $username Username to authenticate.
* @param string $transactionID * @param string $transactionID Transaction ID of the triggered challenge.
* @param string $u2fSignResponse * @param string $u2fSignResponse U2F sign response.
* @param null $headers Optional headers array to forward to the server. * @param array|null $headers Optional headers to forward to the server.
* @return PIResponse|null * @return PIResponse|null Returns PIResponse object or null if response was empty or malformed, or some parameter is missing.
* @throws PIBadRequestException * @throws PIBadRequestException If an error occurs during the request.
*/ */
public function validateCheckU2F($username, $transactionID, $u2fSignResponse, $headers = null) public function validateCheckU2F(string $username, string $transactionID, string $u2fSignResponse, array $headers = null): ?PIResponse
{ {
assert('string' === gettype($username)); assert('string' === gettype($username));
assert('string' === gettype($transactionID)); assert('string' === gettype($transactionID));
...@@ -362,21 +314,21 @@ class PrivacyIDEA ...@@ -362,21 +314,21 @@ class PrivacyIDEA
} }
/** /**
* Check if service account and pass are set * Check if name and pass of service account are set.
* @return bool * @return bool
*/ */
public function serviceAccountAvailable() public function serviceAccountAvailable(): bool
{ {
return (!empty($this->serviceAccountName) && !empty($this->serviceAccountPass)); return (!empty($this->serviceAccountName) && !empty($this->serviceAccountPass));
} }
/** /**
* Retrieves an auth token from the server using the service account. An auth token is required for some requests to privacyIDEA. * Retrieves the auth token from the server using the service account. An auth token is required for some requests to the privacyIDEA.
* *
* @return string the auth token or empty string if the response did not contain a token or no service account is configured. * @return string Auth token or empty string if the response did not contain a token or no service account is configured.
* @throws PIBadRequestException if an error occurs during the request * @throws PIBadRequestException If an error occurs during the request.
*/ */
public function getAuthToken() public function getAuthToken(): string
{ {
if (!$this->serviceAccountAvailable()) if (!$this->serviceAccountAvailable())
{ {
...@@ -401,26 +353,42 @@ class PrivacyIDEA ...@@ -401,26 +353,42 @@ class PrivacyIDEA
return @$response['result']['value']['token'] ?: ""; return @$response['result']['value']['token'] ?: "";
} }
$this->debugLog("/auth response did not contain a auth token."); $this->debugLog("/auth response did not contain the auth token.");
return ""; return "";
} }
/** /**
* Send a request to an endpoint with the specified parameters and headers. * Send requests to the endpoint with specified parameters and headers.
* *
* @param $params array request parameters * @param $params array Request parameters.
* @param $headers array headers fields * @param $headers array Headers to forward.
* @param $httpMethod string GET or POST * @param $httpMethod string GET or POST.
* @param $endpoint string endpoint of the privacyIDEA API (e.g. /validate/check) * @param $endpoint string Endpoint of the privacyIDEA API (e.g. /validate/check).
* @return string returns a string with the response from server * @return string Returns a string with the server response.
* @throws PIBadRequestException if an error occurs * @throws PIBadRequestException If an error occurs.
*/ */
public function sendRequest(array $params, array $headers, $httpMethod, $endpoint) public function sendRequest(array $params, array $headers, string $httpMethod, string $endpoint): string
{ {
assert('array' === gettype($params)); assert('array' === gettype($params));
assert('array' === gettype($headers)); assert('array' === gettype($headers));
assert('string' === gettype($httpMethod)); assert('string' === gettype($httpMethod));
assert('string' === gettype($endpoint)); assert('string' === gettype($endpoint));
// Add the client parameter if wished.
if ($this->forwardClientIP === true)
{
$serverHeaders = $_SERVER;
foreach (array("X-Forwarded-For", "HTTP_X_FORWARDED_FOR", "REMOTE_ADDR") as $clientKey)
{
if (array_key_exists($clientKey, $serverHeaders))
{
$clientIP = $serverHeaders[$clientKey];
$this->debugLog("Forwarding Client IP: " . $clientKey . ": " . $clientIP);
$params['client'] = $clientIP;
break;
}
}
}
$this->debugLog("Sending " . http_build_query($params, '', ', ') . " to " . $endpoint); $this->debugLog("Sending " . http_build_query($params, '', ', ') . " to " . $endpoint);
...@@ -486,7 +454,7 @@ class PrivacyIDEA ...@@ -486,7 +454,7 @@ class PrivacyIDEA
if (!$response) if (!$response)
{ {
// Handle error // Handle the error
$curlErrno = curl_errno($curlInstance); $curlErrno = curl_errno($curlInstance);
$this->errorLog("Bad request: " . curl_error($curlInstance) . " errno: " . $curlErrno); $this->errorLog("Bad request: " . curl_error($curlInstance) . " errno: " . $curlErrno);
throw new PIBadRequestException("Unable to reach the authentication server (" . $curlErrno . ")"); throw new PIBadRequestException("Unable to reach the authentication server (" . $curlErrno . ")");
...@@ -508,26 +476,20 @@ class PrivacyIDEA ...@@ -508,26 +476,20 @@ class PrivacyIDEA
} }
/** /**
* This function relays messages to the PILogger implementation * This function relays messages to the PILogger implementation.
* @param $message * @param string $message Debug message to log.
*/ */
function debugLog($message) function debugLog(string $message): void
{ {
if ($this->logger != null) $this->logger?->piDebug("privacyIDEA-PHP-Client: " . $message);
{
$this->logger->piDebug("privacyIDEA-PHP-Client: " . $message);
}
} }
/** /**
* This function relays messages to the PILogger implementation * This function relays messages to the PILogger implementation
* @param $message * @param string $message Error message to log.
*/ */
function errorLog($message) function errorLog(string $message): void
{ {
if ($this->logger != null) $this->logger?->piError("privacyIDEA-PHP-Client: " . $message);
{
$this->logger->piError("privacyIDEA-PHP-Client: " . $message);
}
} }
} }
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment