Page MenuHomec4science

tequila.php
No OneTemporary

File Metadata

Created
Wed, Mar 12, 19:53

tequila.php

<?php
// Copyright (C) 2003-2021 EPFL <https://www.epfl.ch>
// Copyright (C) 2021 Liip SA <https://www.liip.ch>
// Copyright (C) 2021 Doran Kayoumi
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>
require __DIR__ . '/vendor/autoload.php';
require_once "exception.php";
class TequilaClient {
const VERSION = "4.0.0";
const TEQUILA_BIN = "/cgi-bin/tequila";
const COOKIE_NAME = "TequilaPHP";
const COOKIE_LIFE = 0;
const BODY_GLUE = "+";
const SESSION_KEY = "Tequila-Session-Key";
const SESSION_CREATION = "Tequila-Session-Creation";
const LANGUAGE_FRENCH = 0;
const LANGUAGE_ENGLISH = 1;
const LANGUAGE_GERMAN = 2;
const SERVER_ENDPOINTS = ["createrequest", "fetchattributes", "logout"];
private $serverURL;
private $timeout;
private $logoutURL;
private $language;
private $applicationURL;
private $applicationName;
private $attributes = [];
private $key;
private $stack;
/**
* @brief Class constructor
*
* @param $server - Tequila server URL
* @param $timeout - Session timeout
* @param $applicationName - Name of the application using the client
* @param $applicationURL - (optional) URL the application using the client
* @param $language - (Default: English) Language of the application
*/
function __construct(
string $server,
int $timeout,
string $applicationName,
string $applicationURL = "",
int $language = self::LANGUAGE_ENGLISH,
callable $handler = null,
bool $debug = false
) {
$this->serverURL = $server . self::TEQUILA_BIN;
$this->timeout = $timeout;
$this->language = $language;
$this->applicationURL = $applicationURL;
$this->applicationName = $applicationName;
$this->debug = (bool) $debug;
$this->log("Debug is ".($this->debug ? "enabled" : "disabled"));
$this->stack = GuzzleHttp\HandlerStack::create($handler);
// if no application URL was specified, we try to generate it
if (empty($this->applicationURL)) {
$protocol = !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on' ? "https://" : "http://";
$this->applicationURL = $protocol . $_SERVER['SERVER_NAME'] . ":" . $_SERVER['SERVER_PORT'] . $_SERVER['PHP_SELF'];
if (!empty($_SERVER['PATH_INFO'])) {
$this->applicationURL .= $_SERVER['PATH_INFO'];
}
if (!empty($_SERVER['QUERY_STRING'])) {
$this->applicationURL .= "?" . $_SERVER['QUERY_STRING'];
}
}
$this->log("applicationURL: {$this->applicationURL}");
}
/**
* @brief User authentication to server
*
* @param $wantedAttributes - (optional) List of attributes about the user that the server will return
* @param $filters - (optional) Filters that will be applied to the user attributes
* @param $authorised - (optional) Tequila server restrictions to lift
*/
public function authenticate(
array $wantedAttributes = [],
string $filters = "",
string $authorised = "",
array $allowedRequestHosts = []
) {
$this->log(__FUNCTION__ . "(...)");
if ($this->preExistingSession()) {
return;
}
// check if a session creation was already started.
if (!empty($_COOKIE[self::COOKIE_NAME])) {
$attributes = $this->fetchAttributes($_COOKIE[self::COOKIE_NAME], $allowedRequestHosts);
if (
!empty($attributes) &&
isset($attributes['uniqueid']) &&
isset($attributes['user']) &&
isset($attributes['key'])
) {
// Only create a valid session and keep the key if the mandatory attributes are present.
$this->key = $_COOKIE[self::COOKIE_NAME];
$this->createSession($attributes);
return;
}
}
$this->key = $this->createRequest($wantedAttributes, $filters, $authorised);
setcookie(self::COOKIE_NAME, $this->key, [
'expires' => self::COOKIE_LIFE,
'httponly' => true,
"secure" => (strpos($this->applicationURL, "https://") === 0),
]);
header("Location: {$this->serverURL}/requestauth?requestkey={$this->key}");
exit;
}
/**
* @brief Logout from Tequila server
*
* @param $redirectUrl - (optional) URL to redirect to after logout
*/
public function logout(string $redirectUrl = "") {
$this->log(__FUNCTION__ . "(...)");
// Delete cookie by setting expiration time in the past with root path
setcookie(self::COOKIE_NAME, "", time() - 3600);
unset($_SESSION[self::SESSION_KEY]);
unset($_SESSION[self::SESSION_CREATION]);
$this->contactServer("logout");
$redirectUrl = empty($redirectUrl) ? $this->applicationURL : urlencode($redirectUrl);
header("Location: {$this->serverURL}/logout?urlaccess={$redirectUrl}");
unset($this->key);
}
/**
* @brief Sends an authentication request
*
* @param $wantedAttributes - (optional) List of attributes about the user that the server will return
* @param $filters - (optional) Filters that will be applied to the user attributes
* @param $authorised - (optional) Tequila server restrictions to lift
*
* @return Key returned by the Tequila server
*/
private function createRequest(
array $wantedAttributes = [],
string $filters = "",
string $authorised = ""
) : string {
$this->log(__FUNCTION__ . "(...)");
$body = [];
$body["urlaccess"] = $this->applicationURL;
$body["dontappendkey"] = "1";
$body["language"] = $this->language;
$body["service"] = $this->applicationName;
$body["request"] = implode(self::BODY_GLUE, $wantedAttributes);
$body["allows"] = $filters;
$body["require"] = $authorised;
$res = $this->contactServer('createrequest', $body);
preg_match('/^(?P<key>\w+)=(?P<value>\w+)\b$/', $res, $matches);
if ($matches["key"] == "key") {
return $matches["value"];
}
throw new TequilaException("No requestkey obtained from createRequest");
}
/**
* @brief Retrieve the attributes of an authenticated user
* (i.e. the ones requested when establishing an authentication)
*
* @param $key - the request key
*
* @return Array containing all the user attributes
*/
private function fetchAttributes(string $key, array $allowedRequestHosts = []) : array {
$this->log(__FUNCTION__ . "(...)");
$body = [];
$body["key"] = $key;
if (!empty($allowedRequestHosts)) {
$this->log("arh: ".var_export($allowedRequestHosts, true));
}
$body["allowedrequesthosts"] = implode("|", $allowedRequestHosts);
try {
$res = $this->contactServer('fetchattributes', $body);
} catch (TequilaException $e) {
// fetchAttributes failed, return empty
return [];
}
$result = [];
$attributes = explode("\n", $res);
foreach ($attributes as $attribute) {
$attribute = trim($attribute);
if (!$attribute) {
continue;
}
list($key, $val) = explode("=", $attribute, 2);
$result[$key] = $val;
}
return $result;
}
/**
* @brief Sends a POST request to one of the servers endpoints
*
* @param $endpoint - the server endpoint to contact
* @param $fields - (optional) the fields to add to the requests body
*
* @throws TequilaException if the server returns a code other than 200, that no connection could be established
* or that we're trying to acces an unknow endpoint
*
* @return Body of the server response
*/
private function contactServer($endpoint, $fields = []) : string {
// check if it's a valid endpoint
if (!in_array($endpoint, self::SERVER_ENDPOINTS)) {
throw new TequilaException("Unknown endpoint {$endpoint}");
}
$this->log(__FUNCTION__ ."(".$endpoint.",".var_export($fields, true).")");
try {
$client = new GuzzleHttp\Client([
'base_uri' => $this->serverURL . "/",
"handler" => $this->stack
]);
$client->setUserAgent("Tequila-PHP-Client/".self::VERSION);
/**
* Note: First things first, sorry for the horrible code that follows.
* So, the Tequila server doesn't understand/use normal POST requests and
* only works cleartext body.
*/
$reqBody = [];
if (is_array($fields) && count($fields)) {
foreach ($fields as $key => $val) {
$reqBody[] = "{$key}={$val}";
}
}
$response = $client->request("POST", $endpoint, [
"body" => implode("\n", $reqBody) . "\n",
]);
return $response->getBody();
} catch (GuzzleHttp\Exception\RequestException $e) {
if (!$e->hasResponse()) {
throw new TequilaException("No response from server : {$e->getMessage()}", 1, $e);
}
$response = $e->getResponse();
throw new TequilaException("Unexpected return from server : [{$response->getStatusCode()}] {$response->getReasonPhrase()}", 1, $e);
} catch (GuzzleHttp\Exception\ConnectException $e) {
throw new TequilaException("Connection to {$this->serverURL} server failed", 0, $e);
}
}
/**
* @brief Check if a session was previously established
*
* @return True if a session was previously established
* @return False if no session was previously established
*/
private function preExistingSession() : bool {
if (empty($_SESSION)) {
return false;
}
if (empty($this->attributes)) {
return false;
}
// check if the session hasn't expired.
if (
!array_key_exists(self::SESSION_CREATION, $_SESSION) or
(time() - $_SESSION[self::SESSION_CREATION]) > $this->timeout
) {
return false;
}
if (!array_key_exists(self::SESSION_KEY, $_SESSION)) {
return false;
}
$this->key = $_SESSION[self::SESSION_KEY];
return true;
}
/**
* @brief Establish a new session
*
* @param $attributes - the user attributes returned by the server
*/
private function createSession(array $attributes) {
$_SESSION[self::SESSION_CREATION] = time();
foreach ($attributes as $key => $val) {
$this->attributes[$key] = $val;
}
if (array_key_exists("key", $attributes)) {
$_SESSION[self::SESSION_KEY] = $attributes["key"];
}
}
public function getKey() : string {
return $this->key;
}
public function getAttributes() : array {
return $this->attributes;
}
/**
* If debug mode enabled
*
* @return bool
*/
public function is_debugging() {
return $this->debug;
}
/**
* A debug function, dumps to the php log
*
* @param string $msg Log message
*/
private function log($msg) {
if ($this->is_debugging()) {
error_log('tequila-php-client: ' . $msg);
}
}
}

Event Timeline