first commit

This commit is contained in:
2025-02-24 22:33:42 +01:00
commit 737c037e85
18358 changed files with 5392983 additions and 0 deletions

View File

@@ -0,0 +1,9 @@
The MIT License (MIT)
Copyright (c) 2013 Oliver Vogel
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,236 @@
<?php
namespace Intervention\HttpAuth;
use Intervention\HttpAuth\Configurator\ArrayConfigurator;
abstract class AbstractVault
{
/**
* Environment
*
* @var Environment
*/
protected $environment;
/**
* Name of realm for vault
*
* @var string
*/
protected $realm;
/**
* Username for vault
* @var string
*/
protected $username;
/**
* Password for vault
*
* @var string
*/
protected $password;
/**
* Build directive for current vault
*
* @return Directive
*/
abstract public function getDirective(): Directive;
/**
* Determine if vault is accessible by given key
*
* @param Key $key
* @return bool
*/
abstract public function unlocksWithKey(Key $key): bool;
/**
* Create new instance
*
* @param mixed $realm
* @param mixed $username
* @param mixed $password
*/
public function __construct($realm, $username, $password)
{
$this->checkParameterValidity([
'realm' => $realm,
'username' => $username,
'password' => $password,
]);
$this->environment = new Environment();
$this->realm = $realm;
$this->username = $username;
$this->password = $password;
}
/**
* Throw exception if any of the given parameters are empty
*
* @param array $parameters
* @return void
*/
private function checkParameterValidity(array $parameters): void
{
foreach ($parameters as $key => $value) {
if (empty($value)) {
throw new Exception\InvalidParameterException(
'Cannot create HTTP authentication vault. Parameter "' . $key . '" cannot be empty.'
);
}
}
}
/**
* Return key from current token
*
* @return Key
*/
public function getKey(): Key
{
return $this->environment->getToken()->toKey();
}
/**
* Denies access for non-authenticated users
*
* @return void
*/
public function secure(): void
{
if (! $this->unlocksWithKey($this->getKey())) {
$this->denyAccess();
}
}
/**
* Set name of realm
*
* @param string $realm
* @return AbstractVault
*/
public function setRealm($realm): AbstractVault
{
$this->realm = $realm;
return $this;
}
/**
* Alias for setRealm()
*
* @param string $realm
* @return AbstractVault
*/
public function realm($realm): AbstractVault
{
return $this->setRealm($realm);
}
/**
* Return current realm name
*
* @return string
*/
public function getRealm()
{
return $this->realm;
}
/**
* Set username for current vault
*
* @param string $username
*/
public function setUsername($username): AbstractVault
{
$this->username = $username;
return $this;
}
/**
* Alias for setUsername()
*
* @param string $username
*/
public function username($username): AbstractVault
{
return $this->setUsername($username);
}
/**
* Return current username
*
* @return string
*/
public function getUsername()
{
return $this->username;
}
/**
* Set password for current vault
*
* @param string $password
* @return AbstractVault
*/
public function setPassword($password): AbstractVault
{
$this->password = $password;
return $this;
}
/**
* Alias for setPassword()
*
* @param string $password
* @return AbstractVault
*/
public function password($password): AbstractVault
{
return $this->setPassword($password);
}
/**
* Return current password
*
* @return string
*/
public function getPassword()
{
return $this->password;
}
/**
* Set username and password at once
*
* @param string $username
* @param string $password
* @return AbstractVault
*/
public function credentials($username, $password): AbstractVault
{
return $this->setUsername($username)->setPassword($password);
}
/**
* Sends HTTP 401 Header
*
* @return void
*/
protected function denyAccess(): void
{
$protocol = isset($_SERVER['SERVER_PROTOCOL']) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.1';
header($protocol . ' Unauthorized');
header('WWW-Authenticate: ' . (string) $this->getDirective());
exit('<strong>' . $protocol . ' 401 Unauthorized</strong>');
}
}

View File

@@ -0,0 +1,89 @@
<?php
namespace Intervention\HttpAuth;
class Directive
{
/**
* Type of directive (basic|digest)
*
* @var string
*/
protected $type;
/**
* Array of parameters
*
* @var array
*/
protected $parameters = [];
/**
* Create new instance
*
* @param string $type
* @param array $parameters
*/
public function __construct($type, $parameters = [])
{
$this->type = $type;
$this->parameters = $parameters;
}
/**
* Format current instance
*
* @return string
*/
public function format(): string
{
return sprintf(
'%s %s',
ucfirst(strtolower($this->type)),
$this->getParametersString()
);
}
/**
* Return current type
*
* @return string
*/
public function getType()
{
return $this->type;
}
/**
* Return value of given key from all parameters, if existing
*
* @param mixed $key
* @return mixed
*/
public function getParameter($key)
{
return array_key_exists($key, $this->parameters) ? $this->parameters[$key] : null;
}
/**
* Format current parameters as string
*
* @return string
*/
private function getParametersString(): string
{
return implode(', ', array_map(function ($key, $value) {
return sprintf('%s="%s"', $key, $value);
}, array_keys($this->parameters), $this->parameters));
}
/**
* Cast object to string
*
* @return string
*/
public function __toString(): string
{
return $this->format();
}
}

View File

@@ -0,0 +1,55 @@
<?php
namespace Intervention\HttpAuth;
use Exception;
class Environment
{
/**
* Available auth tokens
*
* @var array
*/
protected $tokenClassnames = [
Token\PhpAuthUser::class,
Token\HttpAuthentification::class,
Token\RedirectHttpAuthorization::class,
Token\PhpAuthDigest::class,
Token\HttpAuthorization::class,
];
/**
* Get first active auth token from all available tokens
*
* @return TokenInterface
*/
public function getToken(): TokenInterface
{
foreach ($this->tokenClassnames as $classname) {
if ($auth = $this->getActiveTokenOrNull($classname)) {
return $auth;
}
}
return new Token\NullToken();
}
/**
* Try to parse auth token from given classname. Returns token object
* if token is active and could be parsed or null.
*
* @param string $classname
* @return TokenInterface|null
*/
private function getActiveTokenOrNull($classname)
{
try {
$auth = new $classname();
} catch (Exception $e) {
$auth = null;
}
return $auth;
}
}

View File

@@ -0,0 +1,10 @@
<?php
namespace Intervention\HttpAuth\Exception;
use RuntimeException;
class AuthentificationException extends RuntimeException
{
//
}

View File

@@ -0,0 +1,10 @@
<?php
namespace Intervention\HttpAuth\Exception;
use RuntimeException;
class InvalidParameterException extends RuntimeException
{
//
}

View File

@@ -0,0 +1,10 @@
<?php
namespace Intervention\HttpAuth\Exception;
use RuntimeException;
class NotSupportedException extends RuntimeException
{
//
}

View File

@@ -0,0 +1,218 @@
<?php
namespace Intervention\HttpAuth;
class HttpAuth
{
/**
* Authentication type
*
* @var string
*/
protected static $type = 'basic';
/**
* Name of authentication realm
*
* @var string
*/
protected static $realm = 'Secured Resource';
/**
* Username
*
* @var string
*/
protected static $username = 'admin';
/**
* Password
*
* @var string
*/
protected static $password = 'secret';
/**
* Static factory method
*
* @param array $config
* @return HttpAuth
*/
public static function make(array $config = []): HttpAuth
{
return (new self())->configure($config);
}
/**
* Configure current instance by array
*
* @param array $config
* @return HttpAuth
*/
private function configure(array $config = []): HttpAuth
{
foreach ($config as $key => $value) {
if (isset(static::${$key})) {
static::${$key} = $value;
}
}
return $this;
}
/**
* Create vault by current parameters and secure it
*
* @return void
*/
public function secure(): void
{
$this->getVault()->secure();
}
/**
* Create HTTP basic auth instance
*
* @return HttpAuth
*/
public function basic(): HttpAuth
{
static::$type = 'basic';
return $this;
}
/**
* Create HTTP digest auth instance
*
* @return HttpAuth
*/
public function digest(): HttpAuth
{
static::$type = 'digest';
return $this;
}
/**
* Set type of configured vault
*
* @param string $value
* @return HttpAuth
*/
public function type($value): HttpAuth
{
static::$type = $value;
return $this;
}
/**
* Set realm name of configured vault
*
* @param string $value
* @return HttpAuth
*/
public function realm($value): HttpAuth
{
static::$realm = $value;
return $this;
}
/**
* Set username of configured vault
*
* @param string $value
* @return HttpAuth
*/
public function username($value): HttpAuth
{
static::$username = $value;
return $this;
}
/**
* Set password of configured vault
*
* @param string $value
* @return HttpAuth
*/
public function password($value): HttpAuth
{
static::$password = $value;
return $this;
}
/**
* Set credentials for configured vault
*
* @param string $username
* @param string $password
* @return HttpAuth
*/
public function credentials($username, $password): HttpAuth
{
return $this->username($username)->password($password);
}
/**
* Get type of current instance
*
* @return mixed
*/
public function getType()
{
return static::$type;
}
/**
* Get realm of current instance
*
* @return mixed
*/
public function getRealm()
{
return static::$realm;
}
/**
* Get username of current instance
*
* @return mixed
*/
public function getUsername()
{
return static::$username;
}
/**
* Get password of current instance
*
* @return mixed
*/
public function getPassword()
{
return static::$password;
}
/**
* Return ready configured vault
*
* @return AbstractVault
*/
protected function getVault(): AbstractVault
{
$classname = sprintf('%s\Vault\%sVault', __NAMESPACE__, ucfirst(strtolower(static::$type)));
if (! class_exists($classname)) {
throw new Exception\NotSupportedException(
'Unable to create HTTP authentication vault of type "' . static::$type . '".'
);
}
return new $classname(static::$realm, static::$username, static::$password);
}
}

View File

@@ -0,0 +1,187 @@
<?php
namespace Intervention\HttpAuth;
class Key
{
/**
* Realm
*
* @var string
*/
private $realm;
/**
* Username
*
* @var string
*/
private $username;
/**
* Password
*
* @var string
*/
private $password;
/**
* QOP
*
* @var string
*/
private $qop;
/**
* Nonce
*
* @var string
*/
private $nonce;
/**
* Opaque
*
* @var string
*/
private $opaque;
/**
* NC
*
* @var string
*/
private $nc;
/**
* uri
*
* @var string
*/
private $uri;
/**
* cnonce
*
* @var string
*/
private $cnonce;
/**
* Response
*
* @var string
*/
private $response;
public function getRealm()
{
return $this->realm;
}
/**
* Return current username
*
* @return string
*/
public function getUsername()
{
return $this->username;
}
/**
* Return current password
*
* @return string
*/
public function getPassword()
{
return $this->password;
}
/**
* Return current qop
*
* @return string
*/
public function getQop()
{
return $this->qop;
}
/**
* Return current nonce
*
* @return string
*/
public function getNonce()
{
return $this->nonce;
}
/**
* Return current opaque
*
* @return string
*/
public function getOpaque()
{
return $this->opaque;
}
/**
* Return current uri
*
* @return string
*/
public function getUri()
{
return $this->uri;
}
/**
* Return current nc
*
* @return string
*/
public function getNc()
{
return $this->nc;
}
/**
* Return current cnonce
*
* @return string
*/
public function getCnonce()
{
return $this->cnonce;
}
/**
* Return current response
*
* @return string
*/
public function getResponse()
{
return $this->response;
}
/**
* Set property to given value on current instance
*
* @param string $name
* @param mixed $value
* @return Key
*/
public function setProperty($name, $value): Key
{
if (property_exists($this, $name)) {
$this->{$name} = $value;
}
return $this;
}
}

View File

@@ -0,0 +1,18 @@
<?php
namespace Intervention\HttpAuth\Laravel\Facades;
use Illuminate\Support\Facades\Facade;
class HttpAuth extends Facade
{
/**
* Return facade accessor
*
* @return string
*/
protected static function getFacadeAccessor()
{
return 'httpauth';
}
}

View File

@@ -0,0 +1,62 @@
<?php
namespace Intervention\HttpAuth\Laravel;
use Illuminate\Support\ServiceProvider;
use Intervention\HttpAuth\HttpAuth;
class HttpAuthServiceProvider extends ServiceProvider
{
/**
* Indicates if loading of the provider is deferred.
*
* @var bool
*/
protected $defer = false;
/**
* Bootstrap the application events.
*
* @return void
*/
public function boot()
{
$this->publishes([
__DIR__ . '/../config/config.php' => config_path('httpauth.php')
]);
}
/**
* Register the service provider.
*
* @return void
*/
public function register()
{
// merge default config
$this->mergeConfigFrom(
__DIR__ . '/../config/config.php',
'httpauth'
);
// register singleton
$this->app->singleton('httpauth', function ($app) {
return HttpAuth::make($app['config']->get('httpauth'));
});
// bind classname
$this->app->bind('Intervention\HttpAuth\HttpAuth', function ($app) {
return HttpAuth::make($app['config']->get('httpauth'));
});
}
/**
* Get the services provided by the provider.
*
* @return array
*/
public function provides()
{
return ['httpauth'];
}
}

View File

@@ -0,0 +1,48 @@
<?php
namespace Intervention\HttpAuth\Token;
use Intervention\HttpAuth\Key;
class HttpAuthentification extends NullToken
{
/**
* Parsed authentification value
*
* @var string
*/
protected $value;
/**
* Transform current instance to key object
*
* @return Key
*/
public function toKey(): Key
{
list($username, $password) = explode(':', base64_decode(substr($this->value, 6)));
$key = new Key();
$key->setProperty('username', $username);
$key->setProperty('password', $password);
return $key;
}
/**
* Parse environment variables and store value in object
*
* @return bool "true" if value was found or "false"
*/
protected function parse(): bool
{
$value = $this->getArrayValue($_SERVER, 'HTTP_AUTHENTICATION');
if (strtolower(substr($value, 0, 5)) === 'basic') {
$this->value = $value;
return true;
}
return false;
}
}

View File

@@ -0,0 +1,23 @@
<?php
namespace Intervention\HttpAuth\Token;
class HttpAuthorization extends PhpAuthDigest
{
/**
* Parse environment variables and store value in object
*
* @return bool "true" if value was found or "false"
*/
protected function parse(): bool
{
$value = $this->getArrayValue($_SERVER, 'HTTP_AUTHORIZATION');
if (strtolower(substr($value, 0, 6)) === 'digest') {
$this->value = $value;
return true;
}
return false;
}
}

View File

@@ -0,0 +1,57 @@
<?php
namespace Intervention\HttpAuth\Token;
use Intervention\HttpAuth\Exception\AuthentificationException;
use Intervention\HttpAuth\Key;
use Intervention\HttpAuth\TokenInterface;
class NullToken implements TokenInterface
{
/**
* Create new instance
*/
public function __construct()
{
if ($this->parse() === false) {
throw new AuthentificationException('Failed to parse token');
}
}
/**
* Transform current instance to key object
*
* @return Key
*/
public function toKey(): Key
{
return new Key();
}
/**
* Parse environment variables and store value in object
*
* @return bool "true" if value was found or "false"
*/
protected function parse(): bool
{
return true;
}
/**
* Return the value of given key in given array data.
* Returns null if key doesn't exists
*
* @param array $data
* @param mixed $key
* @return mixed
*/
protected function getArrayValue($data, $key)
{
if (array_key_exists($key, $data)) {
return $data[$key];
}
return null;
}
}

View File

@@ -0,0 +1,49 @@
<?php
namespace Intervention\HttpAuth\Token;
use Intervention\HttpAuth\Key;
class PhpAuthDigest extends NullToken
{
/**
* Parsed authentification value
*
* @var string
*/
protected $value;
/**
* Transform current instance to key object
*
* @return Key
*/
public function toKey(): Key
{
$authKey = new Key();
preg_match_all('@(\w+)=(?:(?:")([^"]+)"|([^\s,$]+))@', $this->value, $matches, PREG_SET_ORDER);
foreach ($matches as $m) {
$key = $m[1];
$value = $m[2] ? $m[2] : $m[3];
$authKey->setProperty($key, $value);
}
return $authKey;
}
/**
* Parse environment variables and store value in object
*
* @return bool "true" if value was found or "false"
*/
protected function parse(): bool
{
if ($value = $this->getArrayValue($_SERVER, 'PHP_AUTH_DIGEST')) {
$this->value = $value;
return true;
}
return false;
}
}

View File

@@ -0,0 +1,53 @@
<?php
namespace Intervention\HttpAuth\Token;
use Intervention\HttpAuth\Key;
class PhpAuthUser extends NullToken
{
/**
* Parsed authentification username
*
* @var string
*/
protected $username;
/**
* Parsed authentification password
*
* @var string
*/
protected $password;
/**
* Transform current instance to key object
*
* @return Key
*/
public function toKey(): Key
{
$key = new Key();
$key->setProperty('username', $this->username);
$key->setProperty('password', $this->password);
return $key;
}
/**
* Parse environment variables and store value in object
*
* @return bool "true" if value was found or "false"
*/
protected function parse(): bool
{
if ($username = $this->getArrayValue($_SERVER, 'PHP_AUTH_USER')) {
$this->username = $username;
$this->password = array_key_exists('PHP_AUTH_PW', $_SERVER) ? $_SERVER['PHP_AUTH_PW'] : null;
return true;
}
return false;
}
}

View File

@@ -0,0 +1,23 @@
<?php
namespace Intervention\HttpAuth\Token;
class RedirectHttpAuthorization extends HttpAuthentification
{
/**
* Parse environment variables and store value in object
*
* @return bool "true" if value was found or "false"
*/
protected function parse(): bool
{
$value = $this->getArrayValue($_SERVER, 'REDIRECT_HTTP_AUTHORIZATION');
if (strtolower(substr($value, 0, 5)) === 'basic') {
$this->value = $value;
return true;
}
return false;
}
}

View File

@@ -0,0 +1,13 @@
<?php
namespace Intervention\HttpAuth;
interface TokenInterface
{
/**
* Transform current instance to key object
*
* @return Key
*/
public function toKey(): Key;
}

View File

@@ -0,0 +1,37 @@
<?php
namespace Intervention\HttpAuth\Vault;
use Intervention\HttpAuth\AbstractVault;
use Intervention\HttpAuth\Directive;
use Intervention\HttpAuth\Key;
class BasicVault extends AbstractVault
{
/**
* Determine if given key is able to unlock (access) vault.
*
* @param Key $key
* @return bool
*/
public function unlocksWithKey(Key $key): bool
{
$username_match = $this->getUsername() == $key->getUsername();
$password_match = $this->getPassword() == $key->getPassword();
return $username_match && $password_match;
}
/**
* Return auth directive
*
* @return Directive
*/
public function getDirective(): Directive
{
return new Directive('basic', [
'realm' => $this->getRealm(),
'charset' => 'UTF-8',
]);
}
}

View File

@@ -0,0 +1,67 @@
<?php
namespace Intervention\HttpAuth\Vault;
use Intervention\HttpAuth\AbstractVault;
use Intervention\HttpAuth\Directive;
use Intervention\HttpAuth\Key;
class DigestVault extends AbstractVault
{
/**
* Determine if given key is able to unlock (access) vault.
*
* @param Key $key
* @return bool
*/
public function unlocksWithKey(Key $key): bool
{
$username_match = $key->getUsername() == $this->getUsername();
$hash_match = $key->getResponse() == $this->getKeyHash($key);
return $username_match && $hash_match;
}
/**
* Build and return hash from given key/vault
*
* @param Key $key
* @return string
*/
private function getKeyHash(Key $key): string
{
return md5(implode(':', [
md5(sprintf('%s:%s:%s', $key->getUsername(), $this->getRealm(), $this->getPassword())),
$key->getNonce(),
$key->getNc(),
$key->getCnonce(),
$key->getQop(),
md5(sprintf('%s:%s', $this->getRequestMethod(), $key->getUri())),
]));
}
/**
* Return HTTP request method
*
* @return string
*/
private function getRequestMethod()
{
return isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : 'GET';
}
/**
* Return auth directive
*
* @return Directive
*/
public function getDirective(): Directive
{
return new Directive('digest', [
'realm' => $this->getRealm(),
'qop' => 'auth',
'nonce' => uniqid(),
'opaque' => md5($this->getRealm()),
]);
}
}

View File

@@ -0,0 +1,49 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Authentication type
|--------------------------------------------------------------------------
|
| Intervention HttpAuth supports "basic" and "digest" authentication
| implementations. "Basic" is the simplest technique, while "Digest" applies
| hash functions to the password before sending it over the network.
|
| Supported: "basic", "digest"
|
*/
'type' => 'basic',
/*
|--------------------------------------------------------------------------
| Authentication name
|--------------------------------------------------------------------------
|
| Name of secured resource. Clients must authentificate to each named realm.
|
*/
'realm' => 'Secured',
/*
|--------------------------------------------------------------------------
| Authentication username
|--------------------------------------------------------------------------
|
| Username to access the secured realm in combination with a password.
|
*/
'username' => 'admin',
/*
|--------------------------------------------------------------------------
| Password
|--------------------------------------------------------------------------
|
| Password to access the secured realm in combination with the username.
|
*/
'password' => '1234'
];