File Manager

Current Directory: /home/astoriaah/www/plugins/system/shlib/shl_packages/system
Viewing File: /home/astoriaah/www/plugins/system/shlib/shl_packages/system/auth.php
<?php /** * Shlib - programming library * * @author Yannick Gaultier * @copyright (c) Yannick Gaultier 2020 * @package shlib * @license http://www.gnu.org/copyleft/gpl.html GNU/GPL * @version 0.4.0.716 * @date 2020-12-07 */ use Joomla\String\StringHelper; defined('_JEXEC') or die; /** * Simple external authentication system * */ class ShlSystem_Auth { const UUID4_WITH_DASHES = 1; const UUID4_NO_DASHES = 2; const UUID4_UPPERCASE = 1; const UUID4_LOWERCASE = 2; /** * Sign an outgoing request with (our) standard headers * * Note that they query should be used to send the request without being modified or added to, at least if the * signature is being checked by the receiving end. * * Also, the query variables are alphabetically sorted on the array key (ie the query variable name) * prior to signature being computed so as to normalize the input and insure repeatability on both ends * * @param array $query Key/value array of query variables * @param string $authKey A secret key shared between emitter and receiver * @param string $origin Optional. the origin making the request. Formatted as scheme://full.host.tld[/path] No * trailing slash * @param string $extra Optional. A string passed as-is (and signed) with the request as X-WBLR-AUTH-EXTRA header * * @return Object 'query' => built query string (ie p1=123&p2=456...), 'urlEncodedQuery' => same as query but url * encoded, 'headers' => key/value array of headers to be sent */ public static function signRequest( $query, $authKey, $origin = '', $extra = '' ) { $accessKey = self::splitAuthKey($authKey); $origin = StringHelper::rtrim($origin, '/'); $extra = is_string($extra) ? StringHelper::trim($extra) : 'n/a'; $request = new stdClass(); $headers = array( 'X-WBLR-AUTH-TS' => time(), 'X-WBLR-AUTH-ID' => $accessKey['key'], 'X-WBLR-AUTH-TOKEN' => self::uuidv4(self::UUID4_NO_DASHES), 'X-WBLR-AUTH-ORIGIN' => empty($origin) ? '' : hash('sha256', $origin), 'X-WBLR-AUTH-EXTRA' => empty($extra) ? '' : $extra ); // normalize ksort($query); // build the request, to be signed $queryString = array(); $queryUrlEncoded = array(); foreach ($query as $key => $value) { $queryString[] = $key . '=' . $value; $queryUrlEncoded[] = $key . '=' . urlencode($value); } $queryString = implode('&', $queryString); $queryUrlEncoded = implode('&', $queryUrlEncoded); $base = $headers['X-WBLR-AUTH-TS'] . $headers['X-WBLR-AUTH-ID'] . $headers['X-WBLR-AUTH-TOKEN'] . $headers['X-WBLR-AUTH-ORIGIN'] . $headers['X-WBLR-AUTH-EXTRA'] . $accessKey['secret'] . $queryString; $headers['X-WBLR-AUTH-SIG'] = hash('sha256', $base); $request->query = $queryString; $request->urlEncodedQuery = $queryUrlEncoded; $request->headers = $headers; return $request; } /** * Split user-provided weeblrpress.com access key in * 2 parts: public and private * * @param $authKey * * @return array */ private static function splitAuthKey($authKey) { $authKey = StringHelper::trim($authKey); $splitKey = array('key' => '', 'secret' => ''); if (64 != strlen($authKey)) { return $splitKey; } $splitKey['key'] = substr($authKey, 0, 32); $splitKey['secret'] = substr($authKey, 32); return $splitKey; } /** * Verify the integrity of an incoming request * * @param string $secretKey the user secret key * @param array $query Key/value array of query variables * @param array $incomingHeaders * @param bool $urlDecodeBeforeVerify Whether query string should be urldecode-d before auth is verified * * @return Object 'status' => HTTP status code, 'message' => Description of the response status */ public static function verifyRequest( $secretKey, $query, $incomingHeaders, $allowedTimeSkew, $urlDecodeBeforeVerify = true ) { $verifiedRequest = new stdClass(); $verifiedRequest->code = 200; $verifiedRequest->message = 'OK'; $headers = array_merge( array( 'X-WBLR-AUTH-TS' => '', 'X-WBLR-AUTH-ID' => '', 'X-WBLR-AUTH-TOKEN' => '', 'X-WBLR-AUTH-ORIGIN' => '', 'X-WBLR-AUTH-EXTRA' => '', 'X-WBLR-AUTH-SIG' => '', ), $incomingHeaders ); // prevent edge cases when values are not supplied // NB: origin and extra are optional, depends on use case if ( empty($headers['X-WBLR-AUTH-TS']) || empty($headers['X-WBLR-AUTH-ID']) || empty($headers['X-WBLR-AUTH-TOKEN']) || empty($headers['X-WBLR-AUTH-SIG']) ) { $verifiedRequest->message = 'Not authorized (invalid headers).'; $verifiedRequest->code = 403; } if (!self::hasValidTimeSkew( $headers['X-WBLR-AUTH-TS'], $allowedTimeSkew ) ) { $verifiedRequest->message = 'Not authorized (invalid timestamp).'; $verifiedRequest->code = 403; } if (!self::hasValidSignature($secretKey, $query, $headers, $urlDecodeBeforeVerify)) { $verifiedRequest->message = 'Not authorized (invalid signature).'; $verifiedRequest->code = 403; } return $verifiedRequest; } /** * Check whether the request time stamp is older than a given threshold * * An allowedTimeStamp value of 0 disables the test * * @param int $requestTimeStamp * @param int $allowedTimeSkew * * @return bool */ private static function hasValidTimeSkew($requestTimeStamp, $allowedTimeSkew) { $skew = time() - (int) $requestTimeStamp; if (!empty($allowedTimeSkew) && abs($skew) > $allowedTimeSkew) { return false; } return true; } /** * Run signing method on query to verify it matches the passed signature * * @param string $secretKey the user secret key * @param array $request Key/value array of query variables * @param array $headers * @param bool $urlDecodeBeforeVerify Whether query string should be urldecode-d before auth is verified */ private static function hasValidSignature($secretKey, $query, $headers, $urlDecodeBeforeVerify = false) { $base = $headers['X-WBLR-AUTH-TS'] . $headers['X-WBLR-AUTH-ID'] . $headers['X-WBLR-AUTH-TOKEN'] . $headers['X-WBLR-AUTH-ORIGIN'] . $headers['X-WBLR-AUTH-EXTRA'] . $secretKey; // sort query array by key, to normalize hash building ksort($query); // build up query string $queryString = array(); foreach ($query as $key => $value) { $queryString[] = $key . '=' . ($urlDecodeBeforeVerify ? urldecode($value) : $value); } $base .= implode('&', $queryString); // now verify signature against the one passed in the request $computedSignature = hash('sha256', $base); if ($computedSignature != $headers['X-WBLR-AUTH-SIG']) { return false; } return true; } /** * From http://stackoverflow.com/questions/2040240/php-function-to-generate-v4-uuid * * @param bool $dashes If true, dashes are removed from output (default is to keep them) * @param bool $case If true, uuid is lowercased (default is to uppercase if) * @param string $data 16 characters of random data. If not provided, openssl_random_pseudo_bytes(16) is used * * @return string */ public static function uuidv4($dashes = self::UUID4_WITH_DASHES, $case = self::UUID4_UPPERCASE, $data = null) { if (is_null($data)) { $data = openssl_random_pseudo_bytes(16); } $data[6] = chr(ord($data[6]) & 0x0f | 0x40); // set version to 0100 $data[8] = chr(ord($data[8]) & 0x3f | 0x80); // set bits 6-7 to 10 $template = self::UUID4_WITH_DASHES == $dashes ? '%s%s-%s-%s-%s-%s%s%s' : '%s%s%s%s%s%s%s%s'; $uuid = self::UUID4_UPPERCASE == $case ? strtoupper(vsprintf($template, str_split(bin2hex($data), 4))) : strtolower(vsprintf($template, str_split(bin2hex($data), 4))); return $uuid; } }