first
This commit is contained in:
commit
5aa7d034f7
3292 changed files with 465160 additions and 0 deletions
18
data/web/inc/lib/CSSminifierExtended.php
Executable file
18
data/web/inc/lib/CSSminifierExtended.php
Executable file
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
use MatthiasMullie\Minify\CSS;
|
||||
|
||||
class CSSminifierExtended extends CSS {
|
||||
|
||||
public function getDataHash() {
|
||||
return sha1(json_encode($this->accessProtected($this,'data')));
|
||||
}
|
||||
|
||||
private function accessProtected($obj, $prop) {
|
||||
$reflection = new ReflectionClass($obj);
|
||||
$property = $reflection->getProperty($prop);
|
||||
$property->setAccessible(true);
|
||||
return $property->getValue($obj);
|
||||
}
|
||||
|
||||
}
|
||||
18
data/web/inc/lib/JSminifierExtended.php
Executable file
18
data/web/inc/lib/JSminifierExtended.php
Executable file
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
use MatthiasMullie\Minify\JS;
|
||||
|
||||
class JSminifierExtended extends JS {
|
||||
|
||||
public function getDataHash() {
|
||||
return sha1(json_encode($this->accessProtected($this,'data')));
|
||||
}
|
||||
|
||||
private function accessProtected($obj, $prop) {
|
||||
$reflection = new ReflectionClass($obj);
|
||||
$property = $reflection->getProperty($prop);
|
||||
$property->setAccessible(true);
|
||||
return $property->getValue($obj);
|
||||
}
|
||||
|
||||
}
|
||||
171
data/web/inc/lib/WebAuthn/Attestation/AttestationObject.php
Executable file
171
data/web/inc/lib/WebAuthn/Attestation/AttestationObject.php
Executable file
|
|
@ -0,0 +1,171 @@
|
|||
<?php
|
||||
|
||||
namespace lbuchs\WebAuthn\Attestation;
|
||||
use lbuchs\WebAuthn\WebAuthnException;
|
||||
use lbuchs\WebAuthn\CBOR\CborDecoder;
|
||||
use lbuchs\WebAuthn\Binary\ByteBuffer;
|
||||
|
||||
/**
|
||||
* @author Lukas Buchs
|
||||
* @license https://github.com/lbuchs/WebAuthn/blob/master/LICENSE MIT
|
||||
*/
|
||||
class AttestationObject {
|
||||
private $_authenticatorData;
|
||||
private $_attestationFormat;
|
||||
private $_attestationFormatName;
|
||||
|
||||
public function __construct($binary , $allowedFormats) {
|
||||
$enc = CborDecoder::decode($binary);
|
||||
// validation
|
||||
if (!\is_array($enc) || !\array_key_exists('fmt', $enc) || !is_string($enc['fmt'])) {
|
||||
throw new WebAuthnException('invalid attestation format', WebAuthnException::INVALID_DATA);
|
||||
}
|
||||
|
||||
if (!\array_key_exists('attStmt', $enc) || !\is_array($enc['attStmt'])) {
|
||||
throw new WebAuthnException('invalid attestation format (attStmt not available)', WebAuthnException::INVALID_DATA);
|
||||
}
|
||||
|
||||
if (!\array_key_exists('authData', $enc) || !\is_object($enc['authData']) || !($enc['authData'] instanceof ByteBuffer)) {
|
||||
throw new WebAuthnException('invalid attestation format (authData not available)', WebAuthnException::INVALID_DATA);
|
||||
}
|
||||
|
||||
$this->_authenticatorData = new AuthenticatorData($enc['authData']->getBinaryString());
|
||||
$this->_attestationFormatName = $enc['fmt'];
|
||||
|
||||
// Format ok?
|
||||
if (!in_array($this->_attestationFormatName, $allowedFormats)) {
|
||||
throw new WebAuthnException('invalid atttestation format: ' . $this->_attestationFormatName, WebAuthnException::INVALID_DATA);
|
||||
}
|
||||
|
||||
|
||||
switch ($this->_attestationFormatName) {
|
||||
case 'android-key': $this->_attestationFormat = new Format\AndroidKey($enc, $this->_authenticatorData); break;
|
||||
case 'android-safetynet': $this->_attestationFormat = new Format\AndroidSafetyNet($enc, $this->_authenticatorData); break;
|
||||
case 'apple': $this->_attestationFormat = new Format\Apple($enc, $this->_authenticatorData); break;
|
||||
case 'fido-u2f': $this->_attestationFormat = new Format\U2f($enc, $this->_authenticatorData); break;
|
||||
case 'none': $this->_attestationFormat = new Format\None($enc, $this->_authenticatorData); break;
|
||||
case 'packed': $this->_attestationFormat = new Format\Packed($enc, $this->_authenticatorData); break;
|
||||
case 'tpm': $this->_attestationFormat = new Format\Tpm($enc, $this->_authenticatorData); break;
|
||||
default: throw new WebAuthnException('invalid attestation format: ' . $enc['fmt'], WebAuthnException::INVALID_DATA);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the attestation format name
|
||||
* @return string
|
||||
*/
|
||||
public function getAttestationFormatName() {
|
||||
return $this->_attestationFormatName;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the attestation public key in PEM format
|
||||
* @return AuthenticatorData
|
||||
*/
|
||||
public function getAuthenticatorData() {
|
||||
return $this->_authenticatorData;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the certificate chain as PEM
|
||||
* @return string|null
|
||||
*/
|
||||
public function getCertificateChain() {
|
||||
return $this->_attestationFormat->getCertificateChain();
|
||||
}
|
||||
|
||||
/**
|
||||
* return the certificate issuer as string
|
||||
* @return string
|
||||
*/
|
||||
public function getCertificateIssuer() {
|
||||
$pem = $this->getCertificatePem();
|
||||
$issuer = '';
|
||||
if ($pem) {
|
||||
$certInfo = \openssl_x509_parse($pem);
|
||||
if (\is_array($certInfo) && \array_key_exists('issuer', $certInfo) && \is_array($certInfo['issuer'])) {
|
||||
|
||||
$cn = $certInfo['issuer']['CN'] ?? '';
|
||||
$o = $certInfo['issuer']['O'] ?? '';
|
||||
$ou = $certInfo['issuer']['OU'] ?? '';
|
||||
|
||||
if ($cn) {
|
||||
$issuer .= $cn;
|
||||
}
|
||||
if ($issuer && ($o || $ou)) {
|
||||
$issuer .= ' (' . trim($o . ' ' . $ou) . ')';
|
||||
} else {
|
||||
$issuer .= trim($o . ' ' . $ou);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $issuer;
|
||||
}
|
||||
|
||||
/**
|
||||
* return the certificate subject as string
|
||||
* @return string
|
||||
*/
|
||||
public function getCertificateSubject() {
|
||||
$pem = $this->getCertificatePem();
|
||||
$subject = '';
|
||||
if ($pem) {
|
||||
$certInfo = \openssl_x509_parse($pem);
|
||||
if (\is_array($certInfo) && \array_key_exists('subject', $certInfo) && \is_array($certInfo['subject'])) {
|
||||
|
||||
$cn = $certInfo['subject']['CN'] ?? '';
|
||||
$o = $certInfo['subject']['O'] ?? '';
|
||||
$ou = $certInfo['subject']['OU'] ?? '';
|
||||
|
||||
if ($cn) {
|
||||
$subject .= $cn;
|
||||
}
|
||||
if ($subject && ($o || $ou)) {
|
||||
$subject .= ' (' . trim($o . ' ' . $ou) . ')';
|
||||
} else {
|
||||
$subject .= trim($o . ' ' . $ou);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $subject;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the key certificate in PEM format
|
||||
* @return string
|
||||
*/
|
||||
public function getCertificatePem() {
|
||||
return $this->_attestationFormat->getCertificatePem();
|
||||
}
|
||||
|
||||
/**
|
||||
* checks validity of the signature
|
||||
* @param string $clientDataHash
|
||||
* @return bool
|
||||
* @throws WebAuthnException
|
||||
*/
|
||||
public function validateAttestation($clientDataHash) {
|
||||
return $this->_attestationFormat->validateAttestation($clientDataHash);
|
||||
}
|
||||
|
||||
/**
|
||||
* validates the certificate against root certificates
|
||||
* @param array $rootCas
|
||||
* @return boolean
|
||||
* @throws WebAuthnException
|
||||
*/
|
||||
public function validateRootCertificate($rootCas) {
|
||||
return $this->_attestationFormat->validateRootCertificate($rootCas);
|
||||
}
|
||||
|
||||
/**
|
||||
* checks if the RpId-Hash is valid
|
||||
* @param string$rpIdHash
|
||||
* @return bool
|
||||
*/
|
||||
public function validateRpIdHash($rpIdHash) {
|
||||
return $rpIdHash === $this->_authenticatorData->getRpIdHash();
|
||||
}
|
||||
}
|
||||
423
data/web/inc/lib/WebAuthn/Attestation/AuthenticatorData.php
Executable file
423
data/web/inc/lib/WebAuthn/Attestation/AuthenticatorData.php
Executable file
|
|
@ -0,0 +1,423 @@
|
|||
<?php
|
||||
|
||||
namespace lbuchs\WebAuthn\Attestation;
|
||||
use lbuchs\WebAuthn\WebAuthnException;
|
||||
use lbuchs\WebAuthn\CBOR\CborDecoder;
|
||||
use lbuchs\WebAuthn\Binary\ByteBuffer;
|
||||
|
||||
/**
|
||||
* @author Lukas Buchs
|
||||
* @license https://github.com/lbuchs/WebAuthn/blob/master/LICENSE MIT
|
||||
*/
|
||||
class AuthenticatorData {
|
||||
protected $_binary;
|
||||
protected $_rpIdHash;
|
||||
protected $_flags;
|
||||
protected $_signCount;
|
||||
protected $_attestedCredentialData;
|
||||
protected $_extensionData;
|
||||
|
||||
|
||||
|
||||
// Cose encoded keys
|
||||
private static $_COSE_KTY = 1;
|
||||
private static $_COSE_ALG = 3;
|
||||
|
||||
// Cose EC2 ES256 P-256 curve
|
||||
private static $_COSE_CRV = -1;
|
||||
private static $_COSE_X = -2;
|
||||
private static $_COSE_Y = -3;
|
||||
|
||||
// Cose RSA PS256
|
||||
private static $_COSE_N = -1;
|
||||
private static $_COSE_E = -2;
|
||||
|
||||
private static $_EC2_TYPE = 2;
|
||||
private static $_EC2_ES256 = -7;
|
||||
private static $_EC2_P256 = 1;
|
||||
|
||||
private static $_RSA_TYPE = 3;
|
||||
private static $_RSA_RS256 = -257;
|
||||
|
||||
/**
|
||||
* Parsing the authenticatorData binary.
|
||||
* @param string $binary
|
||||
* @throws WebAuthnException
|
||||
*/
|
||||
public function __construct($binary) {
|
||||
if (!\is_string($binary) || \strlen($binary) < 37) {
|
||||
throw new WebAuthnException('Invalid authenticatorData input', WebAuthnException::INVALID_DATA);
|
||||
}
|
||||
$this->_binary = $binary;
|
||||
|
||||
// Read infos from binary
|
||||
// https://www.w3.org/TR/webauthn/#sec-authenticator-data
|
||||
|
||||
// RP ID
|
||||
$this->_rpIdHash = \substr($binary, 0, 32);
|
||||
|
||||
// flags (1 byte)
|
||||
$flags = \unpack('Cflags', \substr($binary, 32, 1))['flags'];
|
||||
$this->_flags = $this->_readFlags($flags);
|
||||
|
||||
// signature counter: 32-bit unsigned big-endian integer.
|
||||
$this->_signCount = \unpack('Nsigncount', \substr($binary, 33, 4))['signcount'];
|
||||
|
||||
$offset = 37;
|
||||
// https://www.w3.org/TR/webauthn/#sec-attested-credential-data
|
||||
if ($this->_flags->attestedDataIncluded) {
|
||||
$this->_attestedCredentialData = $this->_readAttestData($binary, $offset);
|
||||
}
|
||||
|
||||
if ($this->_flags->extensionDataIncluded) {
|
||||
$this->_readExtensionData(\substr($binary, $offset));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticator Attestation Globally Unique Identifier, a unique number
|
||||
* that identifies the model of the authenticator (not the specific instance
|
||||
* of the authenticator)
|
||||
* The aaguid may be 0 if the user is using a old u2f device and/or if
|
||||
* the browser is using the fido-u2f format.
|
||||
* @return string
|
||||
* @throws WebAuthnException
|
||||
*/
|
||||
public function getAAGUID() {
|
||||
if (!($this->_attestedCredentialData instanceof \stdClass)) {
|
||||
throw new WebAuthnException('credential data not included in authenticator data', WebAuthnException::INVALID_DATA);
|
||||
}
|
||||
return $this->_attestedCredentialData->aaguid;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the authenticatorData as binary
|
||||
* @return string
|
||||
*/
|
||||
public function getBinary() {
|
||||
return $this->_binary;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the credentialId
|
||||
* @return string
|
||||
* @throws WebAuthnException
|
||||
*/
|
||||
public function getCredentialId() {
|
||||
if (!($this->_attestedCredentialData instanceof \stdClass)) {
|
||||
throw new WebAuthnException('credential id not included in authenticator data', WebAuthnException::INVALID_DATA);
|
||||
}
|
||||
return $this->_attestedCredentialData->credentialId;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the public key in PEM format
|
||||
* @return string
|
||||
*/
|
||||
public function getPublicKeyPem() {
|
||||
$der = null;
|
||||
switch ($this->_attestedCredentialData->credentialPublicKey->kty) {
|
||||
case self::$_EC2_TYPE: $der = $this->_getEc2Der(); break;
|
||||
case self::$_RSA_TYPE: $der = $this->_getRsaDer(); break;
|
||||
default: throw new WebAuthnException('invalid key type', WebAuthnException::INVALID_DATA);
|
||||
}
|
||||
|
||||
$pem = '-----BEGIN PUBLIC KEY-----' . "\n";
|
||||
$pem .= \chunk_split(\base64_encode($der), 64, "\n");
|
||||
$pem .= '-----END PUBLIC KEY-----' . "\n";
|
||||
return $pem;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the public key in U2F format
|
||||
* @return string
|
||||
* @throws WebAuthnException
|
||||
*/
|
||||
public function getPublicKeyU2F() {
|
||||
if (!($this->_attestedCredentialData instanceof \stdClass)) {
|
||||
throw new WebAuthnException('credential data not included in authenticator data', WebAuthnException::INVALID_DATA);
|
||||
}
|
||||
return "\x04" . // ECC uncompressed
|
||||
$this->_attestedCredentialData->credentialPublicKey->x .
|
||||
$this->_attestedCredentialData->credentialPublicKey->y;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the SHA256 hash of the relying party id (=hostname)
|
||||
* @return string
|
||||
*/
|
||||
public function getRpIdHash() {
|
||||
return $this->_rpIdHash;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the sign counter
|
||||
* @return int
|
||||
*/
|
||||
public function getSignCount() {
|
||||
return $this->_signCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns true if the user is present
|
||||
* @return boolean
|
||||
*/
|
||||
public function getUserPresent() {
|
||||
return $this->_flags->userPresent;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns true if the user is verified
|
||||
* @return boolean
|
||||
*/
|
||||
public function getUserVerified() {
|
||||
return $this->_flags->userVerified;
|
||||
}
|
||||
|
||||
// -----------------------------------------------
|
||||
// PRIVATE
|
||||
// -----------------------------------------------
|
||||
|
||||
/**
|
||||
* Returns DER encoded EC2 key
|
||||
* @return string
|
||||
*/
|
||||
private function _getEc2Der() {
|
||||
return $this->_der_sequence(
|
||||
$this->_der_sequence(
|
||||
$this->_der_oid("\x2A\x86\x48\xCE\x3D\x02\x01") . // OID 1.2.840.10045.2.1 ecPublicKey
|
||||
$this->_der_oid("\x2A\x86\x48\xCE\x3D\x03\x01\x07") // 1.2.840.10045.3.1.7 prime256v1
|
||||
) .
|
||||
$this->_der_bitString($this->getPublicKeyU2F())
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns DER encoded RSA key
|
||||
* @return string
|
||||
*/
|
||||
private function _getRsaDer() {
|
||||
return $this->_der_sequence(
|
||||
$this->_der_sequence(
|
||||
$this->_der_oid("\x2A\x86\x48\x86\xF7\x0D\x01\x01\x01") . // OID 1.2.840.113549.1.1.1 rsaEncryption
|
||||
$this->_der_nullValue()
|
||||
) .
|
||||
$this->_der_bitString(
|
||||
$this->_der_sequence(
|
||||
$this->_der_unsignedInteger($this->_attestedCredentialData->credentialPublicKey->n) .
|
||||
$this->_der_unsignedInteger($this->_attestedCredentialData->credentialPublicKey->e)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* reads the flags from flag byte
|
||||
* @param string $binFlag
|
||||
* @return \stdClass
|
||||
*/
|
||||
private function _readFlags($binFlag) {
|
||||
$flags = new \stdClass();
|
||||
|
||||
$flags->bit_0 = !!($binFlag & 1);
|
||||
$flags->bit_1 = !!($binFlag & 2);
|
||||
$flags->bit_2 = !!($binFlag & 4);
|
||||
$flags->bit_3 = !!($binFlag & 8);
|
||||
$flags->bit_4 = !!($binFlag & 16);
|
||||
$flags->bit_5 = !!($binFlag & 32);
|
||||
$flags->bit_6 = !!($binFlag & 64);
|
||||
$flags->bit_7 = !!($binFlag & 128);
|
||||
|
||||
// named flags
|
||||
$flags->userPresent = $flags->bit_0;
|
||||
$flags->userVerified = $flags->bit_2;
|
||||
$flags->attestedDataIncluded = $flags->bit_6;
|
||||
$flags->extensionDataIncluded = $flags->bit_7;
|
||||
return $flags;
|
||||
}
|
||||
|
||||
/**
|
||||
* read attested data
|
||||
* @param string $binary
|
||||
* @param int $endOffset
|
||||
* @return \stdClass
|
||||
* @throws WebAuthnException
|
||||
*/
|
||||
private function _readAttestData($binary, &$endOffset) {
|
||||
$attestedCData = new \stdClass();
|
||||
if (\strlen($binary) <= 55) {
|
||||
throw new WebAuthnException('Attested data should be present but is missing', WebAuthnException::INVALID_DATA);
|
||||
}
|
||||
|
||||
// The AAGUID of the authenticator
|
||||
$attestedCData->aaguid = \substr($binary, 37, 16);
|
||||
|
||||
//Byte length L of Credential ID, 16-bit unsigned big-endian integer.
|
||||
$length = \unpack('nlength', \substr($binary, 53, 2))['length'];
|
||||
$attestedCData->credentialId = \substr($binary, 55, $length);
|
||||
|
||||
// set end offset
|
||||
$endOffset = 55 + $length;
|
||||
|
||||
// extract public key
|
||||
$attestedCData->credentialPublicKey = $this->_readCredentialPublicKey($binary, 55 + $length, $endOffset);
|
||||
|
||||
return $attestedCData;
|
||||
}
|
||||
|
||||
/**
|
||||
* reads COSE key-encoded elliptic curve public key in EC2 format
|
||||
* @param string $binary
|
||||
* @param int $endOffset
|
||||
* @return \stdClass
|
||||
* @throws WebAuthnException
|
||||
*/
|
||||
private function _readCredentialPublicKey($binary, $offset, &$endOffset) {
|
||||
$enc = CborDecoder::decodeInPlace($binary, $offset, $endOffset);
|
||||
|
||||
// COSE key-encoded elliptic curve public key in EC2 format
|
||||
$credPKey = new \stdClass();
|
||||
$credPKey->kty = $enc[self::$_COSE_KTY];
|
||||
$credPKey->alg = $enc[self::$_COSE_ALG];
|
||||
|
||||
switch ($credPKey->alg) {
|
||||
case self::$_EC2_ES256: $this->_readCredentialPublicKeyES256($credPKey, $enc); break;
|
||||
case self::$_RSA_RS256: $this->_readCredentialPublicKeyRS256($credPKey, $enc); break;
|
||||
}
|
||||
|
||||
return $credPKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* extract ES256 informations from cose
|
||||
* @param \stdClass $credPKey
|
||||
* @param \stdClass $enc
|
||||
* @throws WebAuthnException
|
||||
*/
|
||||
private function _readCredentialPublicKeyES256(&$credPKey, $enc) {
|
||||
$credPKey->crv = $enc[self::$_COSE_CRV];
|
||||
$credPKey->x = $enc[self::$_COSE_X] instanceof ByteBuffer ? $enc[self::$_COSE_X]->getBinaryString() : null;
|
||||
$credPKey->y = $enc[self::$_COSE_Y] instanceof ByteBuffer ? $enc[self::$_COSE_Y]->getBinaryString() : null;
|
||||
unset ($enc);
|
||||
|
||||
// Validation
|
||||
if ($credPKey->kty !== self::$_EC2_TYPE) {
|
||||
throw new WebAuthnException('public key not in EC2 format', WebAuthnException::INVALID_PUBLIC_KEY);
|
||||
}
|
||||
|
||||
if ($credPKey->alg !== self::$_EC2_ES256) {
|
||||
throw new WebAuthnException('signature algorithm not ES256', WebAuthnException::INVALID_PUBLIC_KEY);
|
||||
}
|
||||
|
||||
if ($credPKey->crv !== self::$_EC2_P256) {
|
||||
throw new WebAuthnException('curve not P-256', WebAuthnException::INVALID_PUBLIC_KEY);
|
||||
}
|
||||
|
||||
if (\strlen($credPKey->x) !== 32) {
|
||||
throw new WebAuthnException('Invalid X-coordinate', WebAuthnException::INVALID_PUBLIC_KEY);
|
||||
}
|
||||
|
||||
if (\strlen($credPKey->y) !== 32) {
|
||||
throw new WebAuthnException('Invalid Y-coordinate', WebAuthnException::INVALID_PUBLIC_KEY);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* extract RS256 informations from COSE
|
||||
* @param \stdClass $credPKey
|
||||
* @param \stdClass $enc
|
||||
* @throws WebAuthnException
|
||||
*/
|
||||
private function _readCredentialPublicKeyRS256(&$credPKey, $enc) {
|
||||
$credPKey->n = $enc[self::$_COSE_N] instanceof ByteBuffer ? $enc[self::$_COSE_N]->getBinaryString() : null;
|
||||
$credPKey->e = $enc[self::$_COSE_E] instanceof ByteBuffer ? $enc[self::$_COSE_E]->getBinaryString() : null;
|
||||
unset ($enc);
|
||||
|
||||
// Validation
|
||||
if ($credPKey->kty !== self::$_RSA_TYPE) {
|
||||
throw new WebAuthnException('public key not in RSA format', WebAuthnException::INVALID_PUBLIC_KEY);
|
||||
}
|
||||
|
||||
if ($credPKey->alg !== self::$_RSA_RS256) {
|
||||
throw new WebAuthnException('signature algorithm not ES256', WebAuthnException::INVALID_PUBLIC_KEY);
|
||||
}
|
||||
|
||||
if (\strlen($credPKey->n) !== 256) {
|
||||
throw new WebAuthnException('Invalid RSA modulus', WebAuthnException::INVALID_PUBLIC_KEY);
|
||||
}
|
||||
|
||||
if (\strlen($credPKey->e) !== 3) {
|
||||
throw new WebAuthnException('Invalid RSA public exponent', WebAuthnException::INVALID_PUBLIC_KEY);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* reads cbor encoded extension data.
|
||||
* @param string $binary
|
||||
* @return array
|
||||
* @throws WebAuthnException
|
||||
*/
|
||||
private function _readExtensionData($binary) {
|
||||
$ext = CborDecoder::decode($binary);
|
||||
if (!\is_array($ext)) {
|
||||
throw new WebAuthnException('invalid extension data', WebAuthnException::INVALID_DATA);
|
||||
}
|
||||
|
||||
return $ext;
|
||||
}
|
||||
|
||||
|
||||
// ---------------
|
||||
// DER functions
|
||||
// ---------------
|
||||
|
||||
private function _der_length($len) {
|
||||
if ($len < 128) {
|
||||
return \chr($len);
|
||||
}
|
||||
$lenBytes = '';
|
||||
while ($len > 0) {
|
||||
$lenBytes = \chr($len % 256) . $lenBytes;
|
||||
$len = \intdiv($len, 256);
|
||||
}
|
||||
return \chr(0x80 | \strlen($lenBytes)) . $lenBytes;
|
||||
}
|
||||
|
||||
private function _der_sequence($contents) {
|
||||
return "\x30" . $this->_der_length(\strlen($contents)) . $contents;
|
||||
}
|
||||
|
||||
private function _der_oid($encoded) {
|
||||
return "\x06" . $this->_der_length(\strlen($encoded)) . $encoded;
|
||||
}
|
||||
|
||||
private function _der_bitString($bytes) {
|
||||
return "\x03" . $this->_der_length(\strlen($bytes) + 1) . "\x00" . $bytes;
|
||||
}
|
||||
|
||||
private function _der_nullValue() {
|
||||
return "\x05\x00";
|
||||
}
|
||||
|
||||
private function _der_unsignedInteger($bytes) {
|
||||
$len = \strlen($bytes);
|
||||
|
||||
// Remove leading zero bytes
|
||||
for ($i = 0; $i < ($len - 1); $i++) {
|
||||
if (\ord($bytes[$i]) !== 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($i !== 0) {
|
||||
$bytes = \substr($bytes, $i);
|
||||
}
|
||||
|
||||
// If most significant bit is set, prefix with another zero to prevent it being seen as negative number
|
||||
if ((\ord($bytes[0]) & 0x80) !== 0) {
|
||||
$bytes = "\x00" . $bytes;
|
||||
}
|
||||
|
||||
return "\x02" . $this->_der_length(\strlen($bytes)) . $bytes;
|
||||
}
|
||||
}
|
||||
96
data/web/inc/lib/WebAuthn/Attestation/Format/AndroidKey.php
Executable file
96
data/web/inc/lib/WebAuthn/Attestation/Format/AndroidKey.php
Executable file
|
|
@ -0,0 +1,96 @@
|
|||
<?php
|
||||
|
||||
namespace lbuchs\WebAuthn\Attestation\Format;
|
||||
use lbuchs\WebAuthn\Attestation\AuthenticatorData;
|
||||
use lbuchs\WebAuthn\WebAuthnException;
|
||||
use lbuchs\WebAuthn\Binary\ByteBuffer;
|
||||
|
||||
class AndroidKey extends FormatBase {
|
||||
private $_alg;
|
||||
private $_signature;
|
||||
private $_x5c;
|
||||
|
||||
public function __construct($AttestionObject, AuthenticatorData $authenticatorData) {
|
||||
parent::__construct($AttestionObject, $authenticatorData);
|
||||
|
||||
// check u2f data
|
||||
$attStmt = $this->_attestationObject['attStmt'];
|
||||
|
||||
if (!\array_key_exists('alg', $attStmt) || $this->_getCoseAlgorithm($attStmt['alg']) === null) {
|
||||
throw new WebAuthnException('unsupported alg: ' . $attStmt['alg'], WebAuthnException::INVALID_DATA);
|
||||
}
|
||||
|
||||
if (!\array_key_exists('sig', $attStmt) || !\is_object($attStmt['sig']) || !($attStmt['sig'] instanceof ByteBuffer)) {
|
||||
throw new WebAuthnException('no signature found', WebAuthnException::INVALID_DATA);
|
||||
}
|
||||
|
||||
if (!\array_key_exists('x5c', $attStmt) || !\is_array($attStmt['x5c']) || \count($attStmt['x5c']) < 1) {
|
||||
throw new WebAuthnException('invalid x5c certificate', WebAuthnException::INVALID_DATA);
|
||||
}
|
||||
|
||||
if (!\is_object($attStmt['x5c'][0]) || !($attStmt['x5c'][0] instanceof ByteBuffer)) {
|
||||
throw new WebAuthnException('invalid x5c certificate', WebAuthnException::INVALID_DATA);
|
||||
}
|
||||
|
||||
$this->_alg = $attStmt['alg'];
|
||||
$this->_signature = $attStmt['sig']->getBinaryString();
|
||||
$this->_x5c = $attStmt['x5c'][0]->getBinaryString();
|
||||
|
||||
if (count($attStmt['x5c']) > 1) {
|
||||
for ($i=1; $i<count($attStmt['x5c']); $i++) {
|
||||
$this->_x5c_chain[] = $attStmt['x5c'][$i]->getBinaryString();
|
||||
}
|
||||
unset ($i);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* returns the key certificate in PEM format
|
||||
* @return string
|
||||
*/
|
||||
public function getCertificatePem() {
|
||||
return $this->_createCertificatePem($this->_x5c);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $clientDataHash
|
||||
*/
|
||||
public function validateAttestation($clientDataHash) {
|
||||
$publicKey = \openssl_pkey_get_public($this->getCertificatePem());
|
||||
|
||||
if ($publicKey === false) {
|
||||
throw new WebAuthnException('invalid public key: ' . \openssl_error_string(), WebAuthnException::INVALID_PUBLIC_KEY);
|
||||
}
|
||||
|
||||
// Verify that sig is a valid signature over the concatenation of authenticatorData and clientDataHash
|
||||
// using the attestation public key in attestnCert with the algorithm specified in alg.
|
||||
$dataToVerify = $this->_authenticatorData->getBinary();
|
||||
$dataToVerify .= $clientDataHash;
|
||||
|
||||
$coseAlgorithm = $this->_getCoseAlgorithm($this->_alg);
|
||||
|
||||
// check certificate
|
||||
return \openssl_verify($dataToVerify, $this->_signature, $publicKey, $coseAlgorithm->openssl) === 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* validates the certificate against root certificates
|
||||
* @param array $rootCas
|
||||
* @return boolean
|
||||
* @throws WebAuthnException
|
||||
*/
|
||||
public function validateRootCertificate($rootCas) {
|
||||
$chainC = $this->_createX5cChainFile();
|
||||
if ($chainC) {
|
||||
$rootCas[] = $chainC;
|
||||
}
|
||||
|
||||
$v = \openssl_x509_checkpurpose($this->getCertificatePem(), -1, $rootCas);
|
||||
if ($v === -1) {
|
||||
throw new WebAuthnException('error on validating root certificate: ' . \openssl_error_string(), WebAuthnException::CERTIFICATE_NOT_TRUSTED);
|
||||
}
|
||||
return $v;
|
||||
}
|
||||
}
|
||||
|
||||
141
data/web/inc/lib/WebAuthn/Attestation/Format/AndroidSafetyNet.php
Executable file
141
data/web/inc/lib/WebAuthn/Attestation/Format/AndroidSafetyNet.php
Executable file
|
|
@ -0,0 +1,141 @@
|
|||
<?php
|
||||
|
||||
|
||||
namespace lbuchs\WebAuthn\Attestation\Format;
|
||||
use lbuchs\WebAuthn\Attestation\AuthenticatorData;
|
||||
use lbuchs\WebAuthn\WebAuthnException;
|
||||
use lbuchs\WebAuthn\Binary\ByteBuffer;
|
||||
|
||||
class AndroidSafetyNet extends FormatBase {
|
||||
private $_signature;
|
||||
private $_signedValue;
|
||||
private $_x5c;
|
||||
private $_payload;
|
||||
|
||||
public function __construct($AttestionObject, AuthenticatorData $authenticatorData) {
|
||||
parent::__construct($AttestionObject, $authenticatorData);
|
||||
|
||||
// check data
|
||||
$attStmt = $this->_attestationObject['attStmt'];
|
||||
|
||||
if (!\array_key_exists('ver', $attStmt) || !$attStmt['ver']) {
|
||||
throw new WebAuthnException('invalid Android Safety Net Format', WebAuthnException::INVALID_DATA);
|
||||
}
|
||||
|
||||
if (!\array_key_exists('response', $attStmt) || !($attStmt['response'] instanceof ByteBuffer)) {
|
||||
throw new WebAuthnException('invalid Android Safety Net Format', WebAuthnException::INVALID_DATA);
|
||||
}
|
||||
|
||||
$response = $attStmt['response']->getBinaryString();
|
||||
|
||||
// Response is a JWS [RFC7515] object in Compact Serialization.
|
||||
// JWSs have three segments separated by two period ('.') characters
|
||||
$parts = \explode('.', $response);
|
||||
unset ($response);
|
||||
if (\count($parts) !== 3) {
|
||||
throw new WebAuthnException('invalid JWS data', WebAuthnException::INVALID_DATA);
|
||||
}
|
||||
|
||||
$header = $this->_base64url_decode($parts[0]);
|
||||
$payload = $this->_base64url_decode($parts[1]);
|
||||
$this->_signature = $this->_base64url_decode($parts[2]);
|
||||
$this->_signedValue = $parts[0] . '.' . $parts[1];
|
||||
unset ($parts);
|
||||
|
||||
$header = \json_decode($header);
|
||||
$payload = \json_decode($payload);
|
||||
|
||||
if (!($header instanceof \stdClass)) {
|
||||
throw new WebAuthnException('invalid JWS header', WebAuthnException::INVALID_DATA);
|
||||
}
|
||||
if (!($payload instanceof \stdClass)) {
|
||||
throw new WebAuthnException('invalid JWS payload', WebAuthnException::INVALID_DATA);
|
||||
}
|
||||
|
||||
if (!$header->x5c || !is_array($header->x5c) || count($header->x5c) === 0) {
|
||||
throw new WebAuthnException('No X.509 signature in JWS Header', WebAuthnException::INVALID_DATA);
|
||||
}
|
||||
|
||||
// algorithm
|
||||
if (!\in_array($header->alg, array('RS256', 'ES256'))) {
|
||||
throw new WebAuthnException('invalid JWS algorithm ' . $header->alg, WebAuthnException::INVALID_DATA);
|
||||
}
|
||||
|
||||
$this->_x5c = \base64_decode($header->x5c[0]);
|
||||
$this->_payload = $payload;
|
||||
|
||||
if (count($header->x5c) > 1) {
|
||||
for ($i=1; $i<count($header->x5c); $i++) {
|
||||
$this->_x5c_chain[] = \base64_decode($header->x5c[$i]);
|
||||
}
|
||||
unset ($i);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* returns the key certificate in PEM format
|
||||
* @return string
|
||||
*/
|
||||
public function getCertificatePem() {
|
||||
return $this->_createCertificatePem($this->_x5c);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $clientDataHash
|
||||
*/
|
||||
public function validateAttestation($clientDataHash) {
|
||||
$publicKey = \openssl_pkey_get_public($this->getCertificatePem());
|
||||
|
||||
// Verify that the nonce in the response is identical to the Base64 encoding
|
||||
// of the SHA-256 hash of the concatenation of authenticatorData and clientDataHash.
|
||||
if (!$this->_payload->nonce || $this->_payload->nonce !== \base64_encode(\hash('SHA256', $this->_authenticatorData->getBinary() . $clientDataHash, true))) {
|
||||
throw new WebAuthnException('invalid nonce in JWS payload', WebAuthnException::INVALID_DATA);
|
||||
}
|
||||
|
||||
// Verify that attestationCert is issued to the hostname "attest.android.com"
|
||||
$certInfo = \openssl_x509_parse($this->getCertificatePem());
|
||||
if (!\is_array($certInfo) || !$certInfo['subject'] || $certInfo['subject']['CN'] !== 'attest.android.com') {
|
||||
throw new WebAuthnException('invalid certificate CN in JWS (' . $certInfo['subject']['CN']. ')', WebAuthnException::INVALID_DATA);
|
||||
}
|
||||
|
||||
// Verify that the ctsProfileMatch attribute in the payload of response is true.
|
||||
if (!$this->_payload->ctsProfileMatch) {
|
||||
throw new WebAuthnException('invalid ctsProfileMatch in payload', WebAuthnException::INVALID_DATA);
|
||||
}
|
||||
|
||||
// check certificate
|
||||
return \openssl_verify($this->_signedValue, $this->_signature, $publicKey, OPENSSL_ALGO_SHA256) === 1;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* validates the certificate against root certificates
|
||||
* @param array $rootCas
|
||||
* @return boolean
|
||||
* @throws WebAuthnException
|
||||
*/
|
||||
public function validateRootCertificate($rootCas) {
|
||||
$chainC = $this->_createX5cChainFile();
|
||||
if ($chainC) {
|
||||
$rootCas[] = $chainC;
|
||||
}
|
||||
|
||||
$v = \openssl_x509_checkpurpose($this->getCertificatePem(), -1, $rootCas);
|
||||
if ($v === -1) {
|
||||
throw new WebAuthnException('error on validating root certificate: ' . \openssl_error_string(), WebAuthnException::CERTIFICATE_NOT_TRUSTED);
|
||||
}
|
||||
return $v;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* decode base64 url
|
||||
* @param string $data
|
||||
* @return string
|
||||
*/
|
||||
private function _base64url_decode($data) {
|
||||
return \base64_decode(\strtr($data, '-_', '+/') . \str_repeat('=', 3 - (3 + \strlen($data)) % 4));
|
||||
}
|
||||
}
|
||||
|
||||
139
data/web/inc/lib/WebAuthn/Attestation/Format/Apple.php
Executable file
139
data/web/inc/lib/WebAuthn/Attestation/Format/Apple.php
Executable file
|
|
@ -0,0 +1,139 @@
|
|||
<?php
|
||||
|
||||
|
||||
namespace lbuchs\WebAuthn\Attestation\Format;
|
||||
use lbuchs\WebAuthn\Attestation\AuthenticatorData;
|
||||
use lbuchs\WebAuthn\WebAuthnException;
|
||||
use lbuchs\WebAuthn\Binary\ByteBuffer;
|
||||
|
||||
class Apple extends FormatBase {
|
||||
private $_x5c;
|
||||
|
||||
public function __construct($AttestionObject, AuthenticatorData $authenticatorData) {
|
||||
parent::__construct($AttestionObject, $authenticatorData);
|
||||
|
||||
// check packed data
|
||||
$attStmt = $this->_attestationObject['attStmt'];
|
||||
|
||||
|
||||
// certificate for validation
|
||||
if (\array_key_exists('x5c', $attStmt) && \is_array($attStmt['x5c']) && \count($attStmt['x5c']) > 0) {
|
||||
|
||||
// The attestation certificate attestnCert MUST be the first element in the array
|
||||
$attestnCert = array_shift($attStmt['x5c']);
|
||||
|
||||
if (!($attestnCert instanceof ByteBuffer)) {
|
||||
throw new WebAuthnException('invalid x5c certificate', WebAuthnException::INVALID_DATA);
|
||||
}
|
||||
|
||||
$this->_x5c = $attestnCert->getBinaryString();
|
||||
|
||||
// certificate chain
|
||||
foreach ($attStmt['x5c'] as $chain) {
|
||||
if ($chain instanceof ByteBuffer) {
|
||||
$this->_x5c_chain[] = $chain->getBinaryString();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new WebAuthnException('invalid Apple attestation statement: missing x5c', WebAuthnException::INVALID_DATA);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* returns the key certificate in PEM format
|
||||
* @return string|null
|
||||
*/
|
||||
public function getCertificatePem() {
|
||||
return $this->_createCertificatePem($this->_x5c);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $clientDataHash
|
||||
*/
|
||||
public function validateAttestation($clientDataHash) {
|
||||
return $this->_validateOverX5c($clientDataHash);
|
||||
}
|
||||
|
||||
/**
|
||||
* validates the certificate against root certificates
|
||||
* @param array $rootCas
|
||||
* @return boolean
|
||||
* @throws WebAuthnException
|
||||
*/
|
||||
public function validateRootCertificate($rootCas) {
|
||||
$chainC = $this->_createX5cChainFile();
|
||||
if ($chainC) {
|
||||
$rootCas[] = $chainC;
|
||||
}
|
||||
|
||||
$v = \openssl_x509_checkpurpose($this->getCertificatePem(), -1, $rootCas);
|
||||
if ($v === -1) {
|
||||
throw new WebAuthnException('error on validating root certificate: ' . \openssl_error_string(), WebAuthnException::CERTIFICATE_NOT_TRUSTED);
|
||||
}
|
||||
return $v;
|
||||
}
|
||||
|
||||
/**
|
||||
* validate if x5c is present
|
||||
* @param string $clientDataHash
|
||||
* @return bool
|
||||
* @throws WebAuthnException
|
||||
*/
|
||||
protected function _validateOverX5c($clientDataHash) {
|
||||
$publicKey = \openssl_pkey_get_public($this->getCertificatePem());
|
||||
|
||||
if ($publicKey === false) {
|
||||
throw new WebAuthnException('invalid public key: ' . \openssl_error_string(), WebAuthnException::INVALID_PUBLIC_KEY);
|
||||
}
|
||||
|
||||
// Concatenate authenticatorData and clientDataHash to form nonceToHash.
|
||||
$nonceToHash = $this->_authenticatorData->getBinary();
|
||||
$nonceToHash .= $clientDataHash;
|
||||
|
||||
// Perform SHA-256 hash of nonceToHash to produce nonce
|
||||
$nonce = hash('SHA256', $nonceToHash, true);
|
||||
|
||||
$credCert = openssl_x509_read($this->getCertificatePem());
|
||||
if ($credCert === false) {
|
||||
throw new WebAuthnException('invalid x5c certificate: ' . \openssl_error_string(), WebAuthnException::INVALID_DATA);
|
||||
}
|
||||
|
||||
$keyData = openssl_pkey_get_details(openssl_pkey_get_public($credCert));
|
||||
$key = is_array($keyData) && array_key_exists('key', $keyData) ? $keyData['key'] : null;
|
||||
|
||||
|
||||
// Verify that nonce equals the value of the extension with OID ( 1.2.840.113635.100.8.2 ) in credCert.
|
||||
$parsedCredCert = openssl_x509_parse($credCert);
|
||||
$nonceExtension = isset($parsedCredCert['extensions']['1.2.840.113635.100.8.2']) ? $parsedCredCert['extensions']['1.2.840.113635.100.8.2'] : '';
|
||||
|
||||
// nonce padded by ASN.1 string: 30 24 A1 22 04 20
|
||||
// 30 — type tag indicating sequence
|
||||
// 24 — 36 byte following
|
||||
// A1 — Enumerated [1]
|
||||
// 22 — 34 byte following
|
||||
// 04 — type tag indicating octet string
|
||||
// 20 — 32 byte following
|
||||
|
||||
$asn1Padding = "\x30\x24\xA1\x22\x04\x20";
|
||||
if (substr($nonceExtension, 0, strlen($asn1Padding)) === $asn1Padding) {
|
||||
$nonceExtension = substr($nonceExtension, strlen($asn1Padding));
|
||||
}
|
||||
|
||||
if ($nonceExtension !== $nonce) {
|
||||
throw new WebAuthnException('nonce doesn\'t equal the value of the extension with OID 1.2.840.113635.100.8.2', WebAuthnException::INVALID_DATA);
|
||||
}
|
||||
|
||||
// Verify that the credential public key equals the Subject Public Key of credCert.
|
||||
$authKeyData = openssl_pkey_get_details(openssl_pkey_get_public($this->_authenticatorData->getPublicKeyPem()));
|
||||
$authKey = is_array($authKeyData) && array_key_exists('key', $authKeyData) ? $authKeyData['key'] : null;
|
||||
|
||||
if ($key === null || $key !== $authKey) {
|
||||
throw new WebAuthnException('credential public key doesn\'t equal the Subject Public Key of credCert', WebAuthnException::INVALID_DATA);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
184
data/web/inc/lib/WebAuthn/Attestation/Format/FormatBase.php
Executable file
184
data/web/inc/lib/WebAuthn/Attestation/Format/FormatBase.php
Executable file
|
|
@ -0,0 +1,184 @@
|
|||
<?php
|
||||
|
||||
|
||||
namespace lbuchs\WebAuthn\Attestation\Format;
|
||||
use lbuchs\WebAuthn\WebAuthnException;
|
||||
use lbuchs\WebAuthn\Attestation\AuthenticatorData;
|
||||
|
||||
|
||||
abstract class FormatBase {
|
||||
protected $_attestationObject = null;
|
||||
protected $_authenticatorData = null;
|
||||
protected $_x5c_chain = array();
|
||||
protected $_x5c_tempFile = null;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param Array $AttestionObject
|
||||
* @param AuthenticatorData $authenticatorData
|
||||
*/
|
||||
public function __construct($AttestionObject, AuthenticatorData $authenticatorData) {
|
||||
$this->_attestationObject = $AttestionObject;
|
||||
$this->_authenticatorData = $authenticatorData;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function __destruct() {
|
||||
// delete X.509 chain certificate file after use
|
||||
if ($this->_x5c_tempFile && \is_file($this->_x5c_tempFile)) {
|
||||
\unlink($this->_x5c_tempFile);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the certificate chain in PEM format
|
||||
* @return string|null
|
||||
*/
|
||||
public function getCertificateChain() {
|
||||
if ($this->_x5c_tempFile && \is_file($this->_x5c_tempFile)) {
|
||||
return \file_get_contents($this->_x5c_tempFile);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the key X.509 certificate in PEM format
|
||||
* @return string
|
||||
*/
|
||||
public function getCertificatePem() {
|
||||
// need to be overwritten
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* checks validity of the signature
|
||||
* @param string $clientDataHash
|
||||
* @return bool
|
||||
* @throws WebAuthnException
|
||||
*/
|
||||
public function validateAttestation($clientDataHash) {
|
||||
// need to be overwritten
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* validates the certificate against root certificates
|
||||
* @param array $rootCas
|
||||
* @return boolean
|
||||
* @throws WebAuthnException
|
||||
*/
|
||||
public function validateRootCertificate($rootCas) {
|
||||
// need to be overwritten
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* create a PEM encoded certificate with X.509 binary data
|
||||
* @param string $x5c
|
||||
* @return string
|
||||
*/
|
||||
protected function _createCertificatePem($x5c) {
|
||||
$pem = '-----BEGIN CERTIFICATE-----' . "\n";
|
||||
$pem .= \chunk_split(\base64_encode($x5c), 64, "\n");
|
||||
$pem .= '-----END CERTIFICATE-----' . "\n";
|
||||
return $pem;
|
||||
}
|
||||
|
||||
/**
|
||||
* creates a PEM encoded chain file
|
||||
* @return type
|
||||
*/
|
||||
protected function _createX5cChainFile() {
|
||||
$content = '';
|
||||
if (\is_array($this->_x5c_chain) && \count($this->_x5c_chain) > 0) {
|
||||
foreach ($this->_x5c_chain as $x5c) {
|
||||
$certInfo = \openssl_x509_parse($this->_createCertificatePem($x5c));
|
||||
// check if issuer = subject (self signed)
|
||||
if (\is_array($certInfo) && \is_array($certInfo['issuer']) && \is_array($certInfo['subject'])) {
|
||||
$selfSigned = true;
|
||||
foreach ($certInfo['issuer'] as $k => $v) {
|
||||
if ($certInfo['subject'][$k] !== $v) {
|
||||
$selfSigned = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$selfSigned) {
|
||||
$content .= "\n" . $this->_createCertificatePem($x5c) . "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($content) {
|
||||
$this->_x5c_tempFile = \sys_get_temp_dir() . '/x5c_chain_' . \base_convert(\rand(), 10, 36) . '.pem';
|
||||
if (\file_put_contents($this->_x5c_tempFile, $content) !== false) {
|
||||
return $this->_x5c_tempFile;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* returns the name and openssl key for provided cose number.
|
||||
* @param int $coseNumber
|
||||
* @return \stdClass|null
|
||||
*/
|
||||
protected function _getCoseAlgorithm($coseNumber) {
|
||||
// https://www.iana.org/assignments/cose/cose.xhtml#algorithms
|
||||
$coseAlgorithms = array(
|
||||
array(
|
||||
'hash' => 'SHA1',
|
||||
'openssl' => OPENSSL_ALGO_SHA1,
|
||||
'cose' => array(
|
||||
-65535 // RS1
|
||||
)),
|
||||
|
||||
array(
|
||||
'hash' => 'SHA256',
|
||||
'openssl' => OPENSSL_ALGO_SHA256,
|
||||
'cose' => array(
|
||||
-257, // RS256
|
||||
-37, // PS256
|
||||
-7, // ES256
|
||||
5 // HMAC256
|
||||
)),
|
||||
|
||||
array(
|
||||
'hash' => 'SHA384',
|
||||
'openssl' => OPENSSL_ALGO_SHA384,
|
||||
'cose' => array(
|
||||
-258, // RS384
|
||||
-38, // PS384
|
||||
-35, // ES384
|
||||
6 // HMAC384
|
||||
)),
|
||||
|
||||
array(
|
||||
'hash' => 'SHA512',
|
||||
'openssl' => OPENSSL_ALGO_SHA512,
|
||||
'cose' => array(
|
||||
-259, // RS512
|
||||
-39, // PS512
|
||||
-36, // ES512
|
||||
7 // HMAC512
|
||||
))
|
||||
);
|
||||
|
||||
foreach ($coseAlgorithms as $coseAlgorithm) {
|
||||
if (\in_array($coseNumber, $coseAlgorithm['cose'], true)) {
|
||||
$return = new \stdClass();
|
||||
$return->hash = $coseAlgorithm['hash'];
|
||||
$return->openssl = $coseAlgorithm['openssl'];
|
||||
return $return;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
41
data/web/inc/lib/WebAuthn/Attestation/Format/None.php
Executable file
41
data/web/inc/lib/WebAuthn/Attestation/Format/None.php
Executable file
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
|
||||
namespace lbuchs\WebAuthn\Attestation\Format;
|
||||
use lbuchs\WebAuthn\Attestation\AuthenticatorData;
|
||||
use lbuchs\WebAuthn\WebAuthnException;
|
||||
|
||||
class None extends FormatBase {
|
||||
|
||||
|
||||
public function __construct($AttestionObject, AuthenticatorData $authenticatorData) {
|
||||
parent::__construct($AttestionObject, $authenticatorData);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* returns the key certificate in PEM format
|
||||
* @return string
|
||||
*/
|
||||
public function getCertificatePem() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $clientDataHash
|
||||
*/
|
||||
public function validateAttestation($clientDataHash) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* validates the certificate against root certificates.
|
||||
* Format 'none' does not contain any ca, so always false.
|
||||
* @param array $rootCas
|
||||
* @return boolean
|
||||
* @throws WebAuthnException
|
||||
*/
|
||||
public function validateRootCertificate($rootCas) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
139
data/web/inc/lib/WebAuthn/Attestation/Format/Packed.php
Executable file
139
data/web/inc/lib/WebAuthn/Attestation/Format/Packed.php
Executable file
|
|
@ -0,0 +1,139 @@
|
|||
<?php
|
||||
|
||||
|
||||
namespace lbuchs\WebAuthn\Attestation\Format;
|
||||
use lbuchs\WebAuthn\Attestation\AuthenticatorData;
|
||||
use lbuchs\WebAuthn\WebAuthnException;
|
||||
use lbuchs\WebAuthn\Binary\ByteBuffer;
|
||||
|
||||
class Packed extends FormatBase {
|
||||
private $_alg;
|
||||
private $_signature;
|
||||
private $_x5c;
|
||||
|
||||
public function __construct($AttestionObject, AuthenticatorData $authenticatorData) {
|
||||
parent::__construct($AttestionObject, $authenticatorData);
|
||||
|
||||
// check packed data
|
||||
$attStmt = $this->_attestationObject['attStmt'];
|
||||
|
||||
if (!\array_key_exists('alg', $attStmt) || $this->_getCoseAlgorithm($attStmt['alg']) === null) {
|
||||
throw new WebAuthnException('unsupported alg: ' . $attStmt['alg'], WebAuthnException::INVALID_DATA);
|
||||
}
|
||||
|
||||
if (!\array_key_exists('sig', $attStmt) || !\is_object($attStmt['sig']) || !($attStmt['sig'] instanceof ByteBuffer)) {
|
||||
throw new WebAuthnException('no signature found', WebAuthnException::INVALID_DATA);
|
||||
}
|
||||
|
||||
$this->_alg = $attStmt['alg'];
|
||||
$this->_signature = $attStmt['sig']->getBinaryString();
|
||||
|
||||
// certificate for validation
|
||||
if (\array_key_exists('x5c', $attStmt) && \is_array($attStmt['x5c']) && \count($attStmt['x5c']) > 0) {
|
||||
|
||||
// The attestation certificate attestnCert MUST be the first element in the array
|
||||
$attestnCert = array_shift($attStmt['x5c']);
|
||||
|
||||
if (!($attestnCert instanceof ByteBuffer)) {
|
||||
throw new WebAuthnException('invalid x5c certificate', WebAuthnException::INVALID_DATA);
|
||||
}
|
||||
|
||||
$this->_x5c = $attestnCert->getBinaryString();
|
||||
|
||||
// certificate chain
|
||||
foreach ($attStmt['x5c'] as $chain) {
|
||||
if ($chain instanceof ByteBuffer) {
|
||||
$this->_x5c_chain[] = $chain->getBinaryString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* returns the key certificate in PEM format
|
||||
* @return string|null
|
||||
*/
|
||||
public function getCertificatePem() {
|
||||
if (!$this->_x5c) {
|
||||
return null;
|
||||
}
|
||||
return $this->_createCertificatePem($this->_x5c);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $clientDataHash
|
||||
*/
|
||||
public function validateAttestation($clientDataHash) {
|
||||
if ($this->_x5c) {
|
||||
return $this->_validateOverX5c($clientDataHash);
|
||||
} else {
|
||||
return $this->_validateSelfAttestation($clientDataHash);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* validates the certificate against root certificates
|
||||
* @param array $rootCas
|
||||
* @return boolean
|
||||
* @throws WebAuthnException
|
||||
*/
|
||||
public function validateRootCertificate($rootCas) {
|
||||
if (!$this->_x5c) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$chainC = $this->_createX5cChainFile();
|
||||
if ($chainC) {
|
||||
$rootCas[] = $chainC;
|
||||
}
|
||||
|
||||
$v = \openssl_x509_checkpurpose($this->getCertificatePem(), -1, $rootCas);
|
||||
if ($v === -1) {
|
||||
throw new WebAuthnException('error on validating root certificate: ' . \openssl_error_string(), WebAuthnException::CERTIFICATE_NOT_TRUSTED);
|
||||
}
|
||||
return $v;
|
||||
}
|
||||
|
||||
/**
|
||||
* validate if x5c is present
|
||||
* @param string $clientDataHash
|
||||
* @return bool
|
||||
* @throws WebAuthnException
|
||||
*/
|
||||
protected function _validateOverX5c($clientDataHash) {
|
||||
$publicKey = \openssl_pkey_get_public($this->getCertificatePem());
|
||||
|
||||
if ($publicKey === false) {
|
||||
throw new WebAuthnException('invalid public key: ' . \openssl_error_string(), WebAuthnException::INVALID_PUBLIC_KEY);
|
||||
}
|
||||
|
||||
// Verify that sig is a valid signature over the concatenation of authenticatorData and clientDataHash
|
||||
// using the attestation public key in attestnCert with the algorithm specified in alg.
|
||||
$dataToVerify = $this->_authenticatorData->getBinary();
|
||||
$dataToVerify .= $clientDataHash;
|
||||
|
||||
$coseAlgorithm = $this->_getCoseAlgorithm($this->_alg);
|
||||
|
||||
// check certificate
|
||||
return \openssl_verify($dataToVerify, $this->_signature, $publicKey, $coseAlgorithm->openssl) === 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* validate if self attestation is in use
|
||||
* @param string $clientDataHash
|
||||
* @return bool
|
||||
*/
|
||||
protected function _validateSelfAttestation($clientDataHash) {
|
||||
// Verify that sig is a valid signature over the concatenation of authenticatorData and clientDataHash
|
||||
// using the credential public key with alg.
|
||||
$dataToVerify = $this->_authenticatorData->getBinary();
|
||||
$dataToVerify .= $clientDataHash;
|
||||
|
||||
$publicKey = $this->_authenticatorData->getPublicKeyPem();
|
||||
|
||||
// check certificate
|
||||
return \openssl_verify($dataToVerify, $this->_signature, $publicKey, OPENSSL_ALGO_SHA256) === 1;
|
||||
}
|
||||
}
|
||||
|
||||
180
data/web/inc/lib/WebAuthn/Attestation/Format/Tpm.php
Executable file
180
data/web/inc/lib/WebAuthn/Attestation/Format/Tpm.php
Executable file
|
|
@ -0,0 +1,180 @@
|
|||
<?php
|
||||
|
||||
|
||||
namespace lbuchs\WebAuthn\Attestation\Format;
|
||||
use lbuchs\WebAuthn\Attestation\AuthenticatorData;
|
||||
use lbuchs\WebAuthn\WebAuthnException;
|
||||
use lbuchs\WebAuthn\Binary\ByteBuffer;
|
||||
|
||||
class Tpm extends FormatBase {
|
||||
private $_TPM_GENERATED_VALUE = "\xFF\x54\x43\x47";
|
||||
private $_TPM_ST_ATTEST_CERTIFY = "\x80\x17";
|
||||
private $_alg;
|
||||
private $_signature;
|
||||
private $_pubArea;
|
||||
private $_x5c;
|
||||
|
||||
/**
|
||||
* @var ByteBuffer
|
||||
*/
|
||||
private $_certInfo;
|
||||
|
||||
|
||||
public function __construct($AttestionObject, AuthenticatorData $authenticatorData) {
|
||||
parent::__construct($AttestionObject, $authenticatorData);
|
||||
|
||||
// check packed data
|
||||
$attStmt = $this->_attestationObject['attStmt'];
|
||||
|
||||
if (!\array_key_exists('ver', $attStmt) || $attStmt['ver'] !== '2.0') {
|
||||
throw new WebAuthnException('invalid tpm version: ' . $attStmt['ver'], WebAuthnException::INVALID_DATA);
|
||||
}
|
||||
|
||||
if (!\array_key_exists('alg', $attStmt) || $this->_getCoseAlgorithm($attStmt['alg']) === null) {
|
||||
throw new WebAuthnException('unsupported alg: ' . $attStmt['alg'], WebAuthnException::INVALID_DATA);
|
||||
}
|
||||
|
||||
if (!\array_key_exists('sig', $attStmt) || !\is_object($attStmt['sig']) || !($attStmt['sig'] instanceof ByteBuffer)) {
|
||||
throw new WebAuthnException('signature not found', WebAuthnException::INVALID_DATA);
|
||||
}
|
||||
|
||||
if (!\array_key_exists('certInfo', $attStmt) || !\is_object($attStmt['certInfo']) || !($attStmt['certInfo'] instanceof ByteBuffer)) {
|
||||
throw new WebAuthnException('certInfo not found', WebAuthnException::INVALID_DATA);
|
||||
}
|
||||
|
||||
if (!\array_key_exists('pubArea', $attStmt) || !\is_object($attStmt['pubArea']) || !($attStmt['pubArea'] instanceof ByteBuffer)) {
|
||||
throw new WebAuthnException('pubArea not found', WebAuthnException::INVALID_DATA);
|
||||
}
|
||||
|
||||
$this->_alg = $attStmt['alg'];
|
||||
$this->_signature = $attStmt['sig']->getBinaryString();
|
||||
$this->_certInfo = $attStmt['certInfo'];
|
||||
$this->_pubArea = $attStmt['pubArea'];
|
||||
|
||||
// certificate for validation
|
||||
if (\array_key_exists('x5c', $attStmt) && \is_array($attStmt['x5c']) && \count($attStmt['x5c']) > 0) {
|
||||
|
||||
// The attestation certificate attestnCert MUST be the first element in the array
|
||||
$attestnCert = array_shift($attStmt['x5c']);
|
||||
|
||||
if (!($attestnCert instanceof ByteBuffer)) {
|
||||
throw new WebAuthnException('invalid x5c certificate', WebAuthnException::INVALID_DATA);
|
||||
}
|
||||
|
||||
$this->_x5c = $attestnCert->getBinaryString();
|
||||
|
||||
// certificate chain
|
||||
foreach ($attStmt['x5c'] as $chain) {
|
||||
if ($chain instanceof ByteBuffer) {
|
||||
$this->_x5c_chain[] = $chain->getBinaryString();
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
throw new WebAuthnException('no x5c certificate found', WebAuthnException::INVALID_DATA);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* returns the key certificate in PEM format
|
||||
* @return string|null
|
||||
*/
|
||||
public function getCertificatePem() {
|
||||
if (!$this->_x5c) {
|
||||
return null;
|
||||
}
|
||||
return $this->_createCertificatePem($this->_x5c);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $clientDataHash
|
||||
*/
|
||||
public function validateAttestation($clientDataHash) {
|
||||
return $this->_validateOverX5c($clientDataHash);
|
||||
}
|
||||
|
||||
/**
|
||||
* validates the certificate against root certificates
|
||||
* @param array $rootCas
|
||||
* @return boolean
|
||||
* @throws WebAuthnException
|
||||
*/
|
||||
public function validateRootCertificate($rootCas) {
|
||||
if (!$this->_x5c) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$chainC = $this->_createX5cChainFile();
|
||||
if ($chainC) {
|
||||
$rootCas[] = $chainC;
|
||||
}
|
||||
|
||||
$v = \openssl_x509_checkpurpose($this->getCertificatePem(), -1, $rootCas);
|
||||
if ($v === -1) {
|
||||
throw new WebAuthnException('error on validating root certificate: ' . \openssl_error_string(), WebAuthnException::CERTIFICATE_NOT_TRUSTED);
|
||||
}
|
||||
return $v;
|
||||
}
|
||||
|
||||
/**
|
||||
* validate if x5c is present
|
||||
* @param string $clientDataHash
|
||||
* @return bool
|
||||
* @throws WebAuthnException
|
||||
*/
|
||||
protected function _validateOverX5c($clientDataHash) {
|
||||
$publicKey = \openssl_pkey_get_public($this->getCertificatePem());
|
||||
|
||||
if ($publicKey === false) {
|
||||
throw new WebAuthnException('invalid public key: ' . \openssl_error_string(), WebAuthnException::INVALID_PUBLIC_KEY);
|
||||
}
|
||||
|
||||
// Concatenate authenticatorData and clientDataHash to form attToBeSigned.
|
||||
$attToBeSigned = $this->_authenticatorData->getBinary();
|
||||
$attToBeSigned .= $clientDataHash;
|
||||
|
||||
// Validate that certInfo is valid:
|
||||
|
||||
// Verify that magic is set to TPM_GENERATED_VALUE.
|
||||
if ($this->_certInfo->getBytes(0, 4) !== $this->_TPM_GENERATED_VALUE) {
|
||||
throw new WebAuthnException('tpm magic not TPM_GENERATED_VALUE', WebAuthnException::INVALID_DATA);
|
||||
}
|
||||
|
||||
// Verify that type is set to TPM_ST_ATTEST_CERTIFY.
|
||||
if ($this->_certInfo->getBytes(4, 2) !== $this->_TPM_ST_ATTEST_CERTIFY) {
|
||||
throw new WebAuthnException('tpm type not TPM_ST_ATTEST_CERTIFY', WebAuthnException::INVALID_DATA);
|
||||
}
|
||||
|
||||
$offset = 6;
|
||||
$qualifiedSigner = $this->_tpmReadLengthPrefixed($this->_certInfo, $offset);
|
||||
$extraData = $this->_tpmReadLengthPrefixed($this->_certInfo, $offset);
|
||||
$coseAlg = $this->_getCoseAlgorithm($this->_alg);
|
||||
|
||||
// Verify that extraData is set to the hash of attToBeSigned using the hash algorithm employed in "alg".
|
||||
if ($extraData->getBinaryString() !== \hash($coseAlg->hash, $attToBeSigned, true)) {
|
||||
throw new WebAuthnException('certInfo:extraData not hash of attToBeSigned', WebAuthnException::INVALID_DATA);
|
||||
}
|
||||
|
||||
// Verify the sig is a valid signature over certInfo using the attestation
|
||||
// public key in aikCert with the algorithm specified in alg.
|
||||
return \openssl_verify($this->_certInfo->getBinaryString(), $this->_signature, $publicKey, $coseAlg->openssl) === 1;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* returns next part of ByteBuffer
|
||||
* @param ByteBuffer $buffer
|
||||
* @param int $offset
|
||||
* @return ByteBuffer
|
||||
*/
|
||||
protected function _tpmReadLengthPrefixed(ByteBuffer $buffer, &$offset) {
|
||||
$len = $buffer->getUint16Val($offset);
|
||||
$data = $buffer->getBytes($offset + 2, $len);
|
||||
$offset += (2 + $len);
|
||||
|
||||
return new ByteBuffer($data);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
93
data/web/inc/lib/WebAuthn/Attestation/Format/U2f.php
Executable file
93
data/web/inc/lib/WebAuthn/Attestation/Format/U2f.php
Executable file
|
|
@ -0,0 +1,93 @@
|
|||
<?php
|
||||
|
||||
|
||||
namespace lbuchs\WebAuthn\Attestation\Format;
|
||||
use lbuchs\WebAuthn\Attestation\AuthenticatorData;
|
||||
use lbuchs\WebAuthn\WebAuthnException;
|
||||
use lbuchs\WebAuthn\Binary\ByteBuffer;
|
||||
|
||||
class U2f extends FormatBase {
|
||||
private $_alg = -7;
|
||||
private $_signature;
|
||||
private $_x5c;
|
||||
|
||||
public function __construct($AttestionObject, AuthenticatorData $authenticatorData) {
|
||||
parent::__construct($AttestionObject, $authenticatorData);
|
||||
|
||||
// check u2f data
|
||||
$attStmt = $this->_attestationObject['attStmt'];
|
||||
|
||||
if (\array_key_exists('alg', $attStmt) && $attStmt['alg'] !== $this->_alg) {
|
||||
throw new WebAuthnException('u2f only accepts algorithm -7 ("ES256"), but got ' . $attStmt['alg'], WebAuthnException::INVALID_DATA);
|
||||
}
|
||||
|
||||
if (!\array_key_exists('sig', $attStmt) || !\is_object($attStmt['sig']) || !($attStmt['sig'] instanceof ByteBuffer)) {
|
||||
throw new WebAuthnException('no signature found', WebAuthnException::INVALID_DATA);
|
||||
}
|
||||
|
||||
if (!\array_key_exists('x5c', $attStmt) || !\is_array($attStmt['x5c']) || \count($attStmt['x5c']) !== 1) {
|
||||
throw new WebAuthnException('invalid x5c certificate', WebAuthnException::INVALID_DATA);
|
||||
}
|
||||
|
||||
if (!\is_object($attStmt['x5c'][0]) || !($attStmt['x5c'][0] instanceof ByteBuffer)) {
|
||||
throw new WebAuthnException('invalid x5c certificate', WebAuthnException::INVALID_DATA);
|
||||
}
|
||||
|
||||
$this->_signature = $attStmt['sig']->getBinaryString();
|
||||
$this->_x5c = $attStmt['x5c'][0]->getBinaryString();
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* returns the key certificate in PEM format
|
||||
* @return string
|
||||
*/
|
||||
public function getCertificatePem() {
|
||||
$pem = '-----BEGIN CERTIFICATE-----' . "\n";
|
||||
$pem .= \chunk_split(\base64_encode($this->_x5c), 64, "\n");
|
||||
$pem .= '-----END CERTIFICATE-----' . "\n";
|
||||
return $pem;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $clientDataHash
|
||||
*/
|
||||
public function validateAttestation($clientDataHash) {
|
||||
$publicKey = \openssl_pkey_get_public($this->getCertificatePem());
|
||||
|
||||
if ($publicKey === false) {
|
||||
throw new WebAuthnException('invalid public key: ' . \openssl_error_string(), WebAuthnException::INVALID_PUBLIC_KEY);
|
||||
}
|
||||
|
||||
// Let verificationData be the concatenation of (0x00 || rpIdHash || clientDataHash || credentialId || publicKeyU2F)
|
||||
$dataToVerify = "\x00";
|
||||
$dataToVerify .= $this->_authenticatorData->getRpIdHash();
|
||||
$dataToVerify .= $clientDataHash;
|
||||
$dataToVerify .= $this->_authenticatorData->getCredentialId();
|
||||
$dataToVerify .= $this->_authenticatorData->getPublicKeyU2F();
|
||||
|
||||
$coseAlgorithm = $this->_getCoseAlgorithm($this->_alg);
|
||||
|
||||
// check certificate
|
||||
return \openssl_verify($dataToVerify, $this->_signature, $publicKey, $coseAlgorithm->openssl) === 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* validates the certificate against root certificates
|
||||
* @param array $rootCas
|
||||
* @return boolean
|
||||
* @throws WebAuthnException
|
||||
*/
|
||||
public function validateRootCertificate($rootCas) {
|
||||
$chainC = $this->_createX5cChainFile();
|
||||
if ($chainC) {
|
||||
$rootCas[] = $chainC;
|
||||
}
|
||||
|
||||
$v = \openssl_x509_checkpurpose($this->getCertificatePem(), -1, $rootCas);
|
||||
if ($v === -1) {
|
||||
throw new WebAuthnException('error on validating root certificate: ' . \openssl_error_string(), WebAuthnException::CERTIFICATE_NOT_TRUSTED);
|
||||
}
|
||||
return $v;
|
||||
}
|
||||
}
|
||||
293
data/web/inc/lib/WebAuthn/Binary/ByteBuffer.php
Executable file
293
data/web/inc/lib/WebAuthn/Binary/ByteBuffer.php
Executable file
|
|
@ -0,0 +1,293 @@
|
|||
<?php
|
||||
|
||||
|
||||
namespace lbuchs\WebAuthn\Binary;
|
||||
use lbuchs\WebAuthn\WebAuthnException;
|
||||
|
||||
/**
|
||||
* Modified version of https://github.com/madwizard-thomas/webauthn-server/blob/master/src/Format/ByteBuffer.php
|
||||
* Copyright © 2018 Thomas Bleeker - MIT licensed
|
||||
* Modified by Lukas Buchs
|
||||
* Thanks Thomas for your work!
|
||||
*/
|
||||
class ByteBuffer implements \JsonSerializable, \Serializable {
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
public static $useBase64UrlEncoding = false;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $_data;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $_length;
|
||||
|
||||
public function __construct($binaryData) {
|
||||
$this->_data = $binaryData;
|
||||
$this->_length = \strlen($binaryData);
|
||||
}
|
||||
|
||||
|
||||
// -----------------------
|
||||
// PUBLIC STATIC
|
||||
// -----------------------
|
||||
|
||||
/**
|
||||
* create a ByteBuffer from a base64 url encoded string
|
||||
* @param string $base64url
|
||||
* @return ByteBuffer
|
||||
*/
|
||||
public static function fromBase64Url($base64url) {
|
||||
$bin = self::_base64url_decode($base64url);
|
||||
if ($bin === false) {
|
||||
throw new WebAuthnException('ByteBuffer: Invalid base64 url string', WebAuthnException::BYTEBUFFER);
|
||||
}
|
||||
return new ByteBuffer($bin);
|
||||
}
|
||||
|
||||
/**
|
||||
* create a ByteBuffer from a base64 url encoded string
|
||||
* @param string $hex
|
||||
* @return ByteBuffer
|
||||
*/
|
||||
public static function fromHex($hex) {
|
||||
$bin = \hex2bin($hex);
|
||||
if ($bin === false) {
|
||||
throw new WebAuthnException('ByteBuffer: Invalid hex string', WebAuthnException::BYTEBUFFER);
|
||||
}
|
||||
return new ByteBuffer($bin);
|
||||
}
|
||||
|
||||
/**
|
||||
* create a random ByteBuffer
|
||||
* @param string $length
|
||||
* @return ByteBuffer
|
||||
*/
|
||||
public static function randomBuffer($length) {
|
||||
if (\function_exists('random_bytes')) { // >PHP 7.0
|
||||
return new ByteBuffer(\random_bytes($length));
|
||||
|
||||
} else if (\function_exists('openssl_random_pseudo_bytes')) {
|
||||
return new ByteBuffer(\openssl_random_pseudo_bytes($length));
|
||||
|
||||
} else {
|
||||
throw new WebAuthnException('ByteBuffer: cannot generate random bytes', WebAuthnException::BYTEBUFFER);
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------
|
||||
// PUBLIC
|
||||
// -----------------------
|
||||
|
||||
public function getBytes($offset, $length) {
|
||||
if ($offset < 0 || $length < 0 || ($offset + $length > $this->_length)) {
|
||||
throw new WebAuthnException('ByteBuffer: Invalid offset or length', WebAuthnException::BYTEBUFFER);
|
||||
}
|
||||
return \substr($this->_data, $offset, $length);
|
||||
}
|
||||
|
||||
public function getByteVal($offset) {
|
||||
if ($offset < 0 || $offset >= $this->_length) {
|
||||
throw new WebAuthnException('ByteBuffer: Invalid offset', WebAuthnException::BYTEBUFFER);
|
||||
}
|
||||
return \ord(\substr($this->_data, $offset, 1));
|
||||
}
|
||||
|
||||
public function getJson($jsonFlags=0) {
|
||||
$data = \json_decode($this->getBinaryString(), null, 512, $jsonFlags);
|
||||
if (\json_last_error() !== JSON_ERROR_NONE) {
|
||||
throw new WebAuthnException(\json_last_error_msg(), WebAuthnException::BYTEBUFFER);
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function getLength() {
|
||||
return $this->_length;
|
||||
}
|
||||
|
||||
public function getUint16Val($offset) {
|
||||
if ($offset < 0 || ($offset + 2) > $this->_length) {
|
||||
throw new WebAuthnException('ByteBuffer: Invalid offset', WebAuthnException::BYTEBUFFER);
|
||||
}
|
||||
return unpack('n', $this->_data, $offset)[1];
|
||||
}
|
||||
|
||||
public function getUint32Val($offset) {
|
||||
if ($offset < 0 || ($offset + 4) > $this->_length) {
|
||||
throw new WebAuthnException('ByteBuffer: Invalid offset', WebAuthnException::BYTEBUFFER);
|
||||
}
|
||||
$val = unpack('N', $this->_data, $offset)[1];
|
||||
|
||||
// Signed integer overflow causes signed negative numbers
|
||||
if ($val < 0) {
|
||||
throw new WebAuthnException('ByteBuffer: Value out of integer range.', WebAuthnException::BYTEBUFFER);
|
||||
}
|
||||
return $val;
|
||||
}
|
||||
|
||||
public function getUint64Val($offset) {
|
||||
if (PHP_INT_SIZE < 8) {
|
||||
throw new WebAuthnException('ByteBuffer: 64-bit values not supported by this system', WebAuthnException::BYTEBUFFER);
|
||||
}
|
||||
if ($offset < 0 || ($offset + 8) > $this->_length) {
|
||||
throw new WebAuthnException('ByteBuffer: Invalid offset', WebAuthnException::BYTEBUFFER);
|
||||
}
|
||||
$val = unpack('J', $this->_data, $offset)[1];
|
||||
|
||||
// Signed integer overflow causes signed negative numbers
|
||||
if ($val < 0) {
|
||||
throw new WebAuthnException('ByteBuffer: Value out of integer range.', WebAuthnException::BYTEBUFFER);
|
||||
}
|
||||
|
||||
return $val;
|
||||
}
|
||||
|
||||
public function getHalfFloatVal($offset) {
|
||||
//FROM spec pseudo decode_half(unsigned char *halfp)
|
||||
$half = $this->getUint16Val($offset);
|
||||
|
||||
$exp = ($half >> 10) & 0x1f;
|
||||
$mant = $half & 0x3ff;
|
||||
|
||||
if ($exp === 0) {
|
||||
$val = $mant * (2 ** -24);
|
||||
} elseif ($exp !== 31) {
|
||||
$val = ($mant + 1024) * (2 ** ($exp - 25));
|
||||
} else {
|
||||
$val = ($mant === 0) ? INF : NAN;
|
||||
}
|
||||
|
||||
return ($half & 0x8000) ? -$val : $val;
|
||||
}
|
||||
|
||||
public function getFloatVal($offset) {
|
||||
if ($offset < 0 || ($offset + 4) > $this->_length) {
|
||||
throw new WebAuthnException('ByteBuffer: Invalid offset', WebAuthnException::BYTEBUFFER);
|
||||
}
|
||||
return unpack('G', $this->_data, $offset)[1];
|
||||
}
|
||||
|
||||
public function getDoubleVal($offset) {
|
||||
if ($offset < 0 || ($offset + 8) > $this->_length) {
|
||||
throw new WebAuthnException('ByteBuffer: Invalid offset', WebAuthnException::BYTEBUFFER);
|
||||
}
|
||||
return unpack('E', $this->_data, $offset)[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getBinaryString() {
|
||||
return $this->_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $buffer
|
||||
* @return bool
|
||||
*/
|
||||
public function equals($buffer) {
|
||||
return is_string($this->_data) && $this->_data === $buffer->data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getHex() {
|
||||
return \bin2hex($this->_data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isEmpty() {
|
||||
return $this->_length === 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* jsonSerialize interface
|
||||
* return binary data in RFC 1342-Like serialized string
|
||||
* @return string
|
||||
*/
|
||||
public function jsonSerialize() {
|
||||
if (ByteBuffer::$useBase64UrlEncoding) {
|
||||
return self::_base64url_encode($this->_data);
|
||||
|
||||
} else {
|
||||
return '=?BINARY?B?' . \base64_encode($this->_data) . '?=';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializable-Interface
|
||||
* @return string
|
||||
*/
|
||||
public function serialize() {
|
||||
return \serialize($this->_data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializable-Interface
|
||||
* @param string $serialized
|
||||
*/
|
||||
public function unserialize($serialized) {
|
||||
$this->_data = \unserialize($serialized);
|
||||
$this->_length = \strlen($this->_data);
|
||||
}
|
||||
|
||||
/**
|
||||
* (PHP 8 deprecates Serializable-Interface)
|
||||
* @return array
|
||||
*/
|
||||
public function __serialize() {
|
||||
return [
|
||||
'data' => \serialize($this->_data)
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* object to string
|
||||
* @return string
|
||||
*/
|
||||
public function __toString() {
|
||||
return $this->getHex();
|
||||
}
|
||||
|
||||
/**
|
||||
* (PHP 8 deprecates Serializable-Interface)
|
||||
* @param array $data
|
||||
* @return void
|
||||
*/
|
||||
public function __unserialize($data) {
|
||||
if ($data && isset($data['data'])) {
|
||||
$this->_data = \unserialize($data['data']);
|
||||
$this->_length = \strlen($this->_data);
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------
|
||||
// PROTECTED STATIC
|
||||
// -----------------------
|
||||
|
||||
/**
|
||||
* base64 url decoding
|
||||
* @param string $data
|
||||
* @return string
|
||||
*/
|
||||
protected static function _base64url_decode($data) {
|
||||
return \base64_decode(\strtr($data, '-_', '+/') . \str_repeat('=', 3 - (3 + \strlen($data)) % 4));
|
||||
}
|
||||
|
||||
/**
|
||||
* base64 url encoding
|
||||
* @param string $data
|
||||
* @return string
|
||||
*/
|
||||
protected static function _base64url_encode($data) {
|
||||
return \rtrim(\strtr(\base64_encode($data), '+/', '-_'), '=');
|
||||
}
|
||||
}
|
||||
220
data/web/inc/lib/WebAuthn/CBOR/CborDecoder.php
Executable file
220
data/web/inc/lib/WebAuthn/CBOR/CborDecoder.php
Executable file
|
|
@ -0,0 +1,220 @@
|
|||
<?php
|
||||
|
||||
|
||||
namespace lbuchs\WebAuthn\CBOR;
|
||||
use lbuchs\WebAuthn\WebAuthnException;
|
||||
use lbuchs\WebAuthn\Binary\ByteBuffer;
|
||||
|
||||
/**
|
||||
* Modified version of https://github.com/madwizard-thomas/webauthn-server/blob/master/src/Format/CborDecoder.php
|
||||
* Copyright © 2018 Thomas Bleeker - MIT licensed
|
||||
* Modified by Lukas Buchs
|
||||
* Thanks Thomas for your work!
|
||||
*/
|
||||
class CborDecoder {
|
||||
const CBOR_MAJOR_UNSIGNED_INT = 0;
|
||||
const CBOR_MAJOR_TEXT_STRING = 3;
|
||||
const CBOR_MAJOR_FLOAT_SIMPLE = 7;
|
||||
const CBOR_MAJOR_NEGATIVE_INT = 1;
|
||||
const CBOR_MAJOR_ARRAY = 4;
|
||||
const CBOR_MAJOR_TAG = 6;
|
||||
const CBOR_MAJOR_MAP = 5;
|
||||
const CBOR_MAJOR_BYTE_STRING = 2;
|
||||
|
||||
/**
|
||||
* @param ByteBuffer|string $bufOrBin
|
||||
* @return mixed
|
||||
* @throws WebAuthnException
|
||||
*/
|
||||
public static function decode($bufOrBin) {
|
||||
$buf = $bufOrBin instanceof ByteBuffer ? $bufOrBin : new ByteBuffer($bufOrBin);
|
||||
|
||||
$offset = 0;
|
||||
$result = self::_parseItem($buf, $offset);
|
||||
if ($offset !== $buf->getLength()) {
|
||||
throw new WebAuthnException('Unused bytes after data item.', WebAuthnException::CBOR);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ByteBuffer|string $bufOrBin
|
||||
* @param int $startOffset
|
||||
* @param int|null $endOffset
|
||||
* @return mixed
|
||||
*/
|
||||
public static function decodeInPlace($bufOrBin, $startOffset, &$endOffset = null) {
|
||||
$buf = $bufOrBin instanceof ByteBuffer ? $bufOrBin : new ByteBuffer($bufOrBin);
|
||||
|
||||
$offset = $startOffset;
|
||||
$data = self::_parseItem($buf, $offset);
|
||||
$endOffset = $offset;
|
||||
return $data;
|
||||
}
|
||||
|
||||
// ---------------------
|
||||
// protected
|
||||
// ---------------------
|
||||
|
||||
/**
|
||||
* @param ByteBuffer $buf
|
||||
* @param int $offset
|
||||
* @return mixed
|
||||
*/
|
||||
protected static function _parseItem(ByteBuffer $buf, &$offset) {
|
||||
$first = $buf->getByteVal($offset++);
|
||||
$type = $first >> 5;
|
||||
$val = $first & 0b11111;
|
||||
|
||||
if ($type === self::CBOR_MAJOR_FLOAT_SIMPLE) {
|
||||
return self::_parseFloatSimple($val, $buf, $offset);
|
||||
}
|
||||
|
||||
$val = self::_parseExtraLength($val, $buf, $offset);
|
||||
|
||||
return self::_parseItemData($type, $val, $buf, $offset);
|
||||
}
|
||||
|
||||
protected static function _parseFloatSimple($val, ByteBuffer $buf, &$offset) {
|
||||
switch ($val) {
|
||||
case 24:
|
||||
$val = $buf->getByteVal($offset);
|
||||
$offset++;
|
||||
return self::_parseSimple($val);
|
||||
|
||||
case 25:
|
||||
$floatValue = $buf->getHalfFloatVal($offset);
|
||||
$offset += 2;
|
||||
return $floatValue;
|
||||
|
||||
case 26:
|
||||
$floatValue = $buf->getFloatVal($offset);
|
||||
$offset += 4;
|
||||
return $floatValue;
|
||||
|
||||
case 27:
|
||||
$floatValue = $buf->getDoubleVal($offset);
|
||||
$offset += 8;
|
||||
return $floatValue;
|
||||
|
||||
case 28:
|
||||
case 29:
|
||||
case 30:
|
||||
throw new WebAuthnException('Reserved value used.', WebAuthnException::CBOR);
|
||||
|
||||
case 31:
|
||||
throw new WebAuthnException('Indefinite length is not supported.', WebAuthnException::CBOR);
|
||||
}
|
||||
|
||||
return self::_parseSimple($val);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $val
|
||||
* @return mixed
|
||||
* @throws WebAuthnException
|
||||
*/
|
||||
protected static function _parseSimple($val) {
|
||||
if ($val === 20) {
|
||||
return false;
|
||||
}
|
||||
if ($val === 21) {
|
||||
return true;
|
||||
}
|
||||
if ($val === 22) {
|
||||
return null;
|
||||
}
|
||||
throw new WebAuthnException(sprintf('Unsupported simple value %d.', $val), WebAuthnException::CBOR);
|
||||
}
|
||||
|
||||
protected static function _parseExtraLength($val, ByteBuffer $buf, &$offset) {
|
||||
switch ($val) {
|
||||
case 24:
|
||||
$val = $buf->getByteVal($offset);
|
||||
$offset++;
|
||||
break;
|
||||
|
||||
case 25:
|
||||
$val = $buf->getUint16Val($offset);
|
||||
$offset += 2;
|
||||
break;
|
||||
|
||||
case 26:
|
||||
$val = $buf->getUint32Val($offset);
|
||||
$offset += 4;
|
||||
break;
|
||||
|
||||
case 27:
|
||||
$val = $buf->getUint64Val($offset);
|
||||
$offset += 8;
|
||||
break;
|
||||
|
||||
case 28:
|
||||
case 29:
|
||||
case 30:
|
||||
throw new WebAuthnException('Reserved value used.', WebAuthnException::CBOR);
|
||||
|
||||
case 31:
|
||||
throw new WebAuthnException('Indefinite length is not supported.', WebAuthnException::CBOR);
|
||||
}
|
||||
|
||||
return $val;
|
||||
}
|
||||
|
||||
protected static function _parseItemData($type, $val, ByteBuffer $buf, &$offset) {
|
||||
switch ($type) {
|
||||
case self::CBOR_MAJOR_UNSIGNED_INT: // uint
|
||||
return $val;
|
||||
|
||||
case self::CBOR_MAJOR_NEGATIVE_INT:
|
||||
return -1 - $val;
|
||||
|
||||
case self::CBOR_MAJOR_BYTE_STRING:
|
||||
$data = $buf->getBytes($offset, $val);
|
||||
$offset += $val;
|
||||
return new ByteBuffer($data); // bytes
|
||||
|
||||
case self::CBOR_MAJOR_TEXT_STRING:
|
||||
$data = $buf->getBytes($offset, $val);
|
||||
$offset += $val;
|
||||
return $data; // UTF-8
|
||||
|
||||
case self::CBOR_MAJOR_ARRAY:
|
||||
return self::_parseArray($buf, $offset, $val);
|
||||
|
||||
case self::CBOR_MAJOR_MAP:
|
||||
return self::_parseMap($buf, $offset, $val);
|
||||
|
||||
case self::CBOR_MAJOR_TAG:
|
||||
return self::_parseItem($buf, $offset); // 1 embedded data item
|
||||
}
|
||||
|
||||
// This should never be reached
|
||||
throw new WebAuthnException(sprintf('Unknown major type %d.', $type), WebAuthnException::CBOR);
|
||||
}
|
||||
|
||||
protected static function _parseMap(ByteBuffer $buf, &$offset, $count) {
|
||||
$map = array();
|
||||
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
$mapKey = self::_parseItem($buf, $offset);
|
||||
$mapVal = self::_parseItem($buf, $offset);
|
||||
|
||||
if (!\is_int($mapKey) && !\is_string($mapKey)) {
|
||||
throw new WebAuthnException('Can only use strings or integers as map keys', WebAuthnException::CBOR);
|
||||
}
|
||||
|
||||
$map[$mapKey] = $mapVal; // todo dup
|
||||
}
|
||||
return $map;
|
||||
}
|
||||
|
||||
protected static function _parseArray(ByteBuffer $buf, &$offset, $count) {
|
||||
$arr = array();
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
$arr[] = self::_parseItem($buf, $offset);
|
||||
}
|
||||
|
||||
return $arr;
|
||||
}
|
||||
}
|
||||
593
data/web/inc/lib/WebAuthn/WebAuthn.php
Executable file
593
data/web/inc/lib/WebAuthn/WebAuthn.php
Executable file
|
|
@ -0,0 +1,593 @@
|
|||
<?php
|
||||
|
||||
namespace lbuchs\WebAuthn;
|
||||
use lbuchs\WebAuthn\Binary\ByteBuffer;
|
||||
require_once 'WebAuthnException.php';
|
||||
require_once 'Binary/ByteBuffer.php';
|
||||
require_once 'Attestation/AttestationObject.php';
|
||||
require_once 'Attestation/AuthenticatorData.php';
|
||||
require_once 'Attestation/Format/FormatBase.php';
|
||||
require_once 'Attestation/Format/None.php';
|
||||
require_once 'Attestation/Format/AndroidKey.php';
|
||||
require_once 'Attestation/Format/AndroidSafetyNet.php';
|
||||
require_once 'Attestation/Format/Apple.php';
|
||||
require_once 'Attestation/Format/Packed.php';
|
||||
require_once 'Attestation/Format/Tpm.php';
|
||||
require_once 'Attestation/Format/U2f.php';
|
||||
require_once 'CBOR/CborDecoder.php';
|
||||
|
||||
/**
|
||||
* WebAuthn
|
||||
* @author Lukas Buchs
|
||||
* @license https://github.com/lbuchs/WebAuthn/blob/master/LICENSE MIT
|
||||
*/
|
||||
class WebAuthn {
|
||||
// relying party
|
||||
private $_rpName;
|
||||
private $_rpId;
|
||||
private $_rpIdHash;
|
||||
private $_challenge;
|
||||
private $_signatureCounter;
|
||||
private $_caFiles;
|
||||
private $_formats;
|
||||
|
||||
/**
|
||||
* Initialize a new WebAuthn server
|
||||
* @param string $rpName the relying party name
|
||||
* @param string $rpId the relying party ID = the domain name
|
||||
* @param bool $useBase64UrlEncoding true to use base64 url encoding for binary data in json objects. Default is a RFC 1342-Like serialized string.
|
||||
* @throws WebAuthnException
|
||||
*/
|
||||
public function __construct($rpName, $rpId, $allowedFormats=null, $useBase64UrlEncoding=false) {
|
||||
$this->_rpName = $rpName;
|
||||
$this->_rpId = $rpId;
|
||||
$this->_rpIdHash = \hash('sha256', $rpId, true);
|
||||
ByteBuffer::$useBase64UrlEncoding = !!$useBase64UrlEncoding;
|
||||
$supportedFormats = array('android-key', 'android-safetynet', 'apple', 'fido-u2f', 'none', 'packed', 'tpm');
|
||||
|
||||
if (!\function_exists('\openssl_open')) {
|
||||
throw new WebAuthnException('OpenSSL-Module not installed');;
|
||||
}
|
||||
|
||||
if (!\in_array('SHA256', \array_map('\strtoupper', \openssl_get_md_methods()))) {
|
||||
throw new WebAuthnException('SHA256 not supported by this openssl installation.');
|
||||
}
|
||||
|
||||
// default: all format
|
||||
if (!is_array($allowedFormats)) {
|
||||
$allowedFormats = $supportedFormats;
|
||||
}
|
||||
$this->_formats = $allowedFormats;
|
||||
|
||||
// validate formats
|
||||
$invalidFormats = \array_diff($this->_formats, $supportedFormats);
|
||||
if (!$this->_formats || $invalidFormats) {
|
||||
throw new WebAuthnException('invalid formats on construct: ' . implode(', ', $invalidFormats));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* add a root certificate to verify new registrations
|
||||
* @param string $path file path of / directory with root certificates
|
||||
* @param array|null $certFileExtensions if adding a direction, all files with provided extension are added. default: pem, crt, cer, der
|
||||
*/
|
||||
public function addRootCertificates($path, $certFileExtensions=null) {
|
||||
if (!\is_array($this->_caFiles)) {
|
||||
$this->_caFiles = array();
|
||||
}
|
||||
if ($certFileExtensions === null) {
|
||||
$certFileExtensions = array('pem', 'crt', 'cer', 'der');
|
||||
}
|
||||
$path = \rtrim(\trim($path), '\\/');
|
||||
if (\is_dir($path)) {
|
||||
foreach (\scandir($path) as $ca) {
|
||||
if (\is_file($path . DIRECTORY_SEPARATOR . $ca) && \in_array(\strtolower(\pathinfo($ca, PATHINFO_EXTENSION)), $certFileExtensions)) {
|
||||
$this->addRootCertificates($path . DIRECTORY_SEPARATOR . $ca);
|
||||
}
|
||||
}
|
||||
} else if (\is_file($path) && !\in_array(\realpath($path), $this->_caFiles)) {
|
||||
$this->_caFiles[] = \realpath($path);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the generated challenge to save for later validation
|
||||
* @return ByteBuffer
|
||||
*/
|
||||
public function getChallenge() {
|
||||
return $this->_challenge;
|
||||
}
|
||||
|
||||
/**
|
||||
* generates the object for a key registration
|
||||
* provide this data to navigator.credentials.create
|
||||
* @param string $userId
|
||||
* @param string $userName
|
||||
* @param string $userDisplayName
|
||||
* @param int $timeout timeout in seconds
|
||||
* @param bool $requireResidentKey true, if the key should be stored by the authentication device
|
||||
* @param bool|string $requireUserVerification indicates that you require user verification and will fail the operation
|
||||
* if the response does not have the UV flag set.
|
||||
* Valid values:
|
||||
* true = required
|
||||
* false = preferred
|
||||
* string 'required' 'preferred' 'discouraged'
|
||||
* @param bool|null $crossPlatformAttachment true for cross-platform devices (eg. fido usb),
|
||||
* false for platform devices (eg. windows hello, android safetynet),
|
||||
* null for both
|
||||
* @param array $excludeCredentialIds a array of ids, which are already registered, to prevent re-registration
|
||||
* @return \stdClass
|
||||
*/
|
||||
public function getCreateArgs($userId, $userName, $userDisplayName, $timeout=20, $requireResidentKey=false, $requireUserVerification=false, $crossPlatformAttachment=null, $excludeCredentialIds=array()) {
|
||||
|
||||
// validate User Verification Requirement
|
||||
if (\is_bool($requireUserVerification)) {
|
||||
$requireUserVerification = $requireUserVerification ? 'required' : 'preferred';
|
||||
} else if (\is_string($requireUserVerification) && \in_array(\strtolower($requireUserVerification), ['required', 'preferred', 'discouraged'])) {
|
||||
$requireUserVerification = \strtolower($requireUserVerification);
|
||||
} else {
|
||||
$requireUserVerification = 'preferred';
|
||||
}
|
||||
|
||||
$args = new \stdClass();
|
||||
$args->publicKey = new \stdClass();
|
||||
|
||||
// relying party
|
||||
$args->publicKey->rp = new \stdClass();
|
||||
$args->publicKey->rp->name = $this->_rpName;
|
||||
$args->publicKey->rp->id = $this->_rpId;
|
||||
|
||||
$args->publicKey->authenticatorSelection = new \stdClass();
|
||||
$args->publicKey->authenticatorSelection->userVerification = $requireUserVerification;
|
||||
if ($requireResidentKey) {
|
||||
$args->publicKey->authenticatorSelection->requireResidentKey = true;
|
||||
}
|
||||
if (is_bool($crossPlatformAttachment)) {
|
||||
$args->publicKey->authenticatorSelection->authenticatorAttachment = $crossPlatformAttachment ? 'cross-platform' : 'platform';
|
||||
}
|
||||
|
||||
// user
|
||||
$args->publicKey->user = new \stdClass();
|
||||
$args->publicKey->user->id = new ByteBuffer($userId); // binary
|
||||
$args->publicKey->user->name = $userName;
|
||||
$args->publicKey->user->displayName = $userDisplayName;
|
||||
|
||||
$args->publicKey->pubKeyCredParams = array();
|
||||
$tmp = new \stdClass();
|
||||
$tmp->type = 'public-key';
|
||||
$tmp->alg = -7; // ES256
|
||||
$args->publicKey->pubKeyCredParams[] = $tmp;
|
||||
unset ($tmp);
|
||||
|
||||
$tmp = new \stdClass();
|
||||
$tmp->type = 'public-key';
|
||||
$tmp->alg = -257; // RS256
|
||||
$args->publicKey->pubKeyCredParams[] = $tmp;
|
||||
unset ($tmp);
|
||||
|
||||
// if there are root certificates added, we need direct attestation to validate
|
||||
// against the root certificate. If there are no root-certificates added,
|
||||
// anonymization ca are also accepted, because we can't validate the root anyway.
|
||||
$attestation = 'indirect';
|
||||
if (\is_array($this->_caFiles)) {
|
||||
$attestation = 'direct';
|
||||
}
|
||||
|
||||
$args->publicKey->attestation = \count($this->_formats) === 1 && \in_array('none', $this->_formats) ? 'none' : $attestation;
|
||||
$args->publicKey->extensions = new \stdClass();
|
||||
$args->publicKey->extensions->exts = true;
|
||||
$args->publicKey->timeout = $timeout * 1000; // microseconds
|
||||
$args->publicKey->challenge = $this->_createChallenge(); // binary
|
||||
|
||||
//prevent re-registration by specifying existing credentials
|
||||
$args->publicKey->excludeCredentials = array();
|
||||
|
||||
if (is_array($excludeCredentialIds)) {
|
||||
foreach ($excludeCredentialIds as $id) {
|
||||
$tmp = new \stdClass();
|
||||
$tmp->id = $id instanceof ByteBuffer ? $id : new ByteBuffer($id); // binary
|
||||
$tmp->type = 'public-key';
|
||||
$tmp->transports = array('usb', 'ble', 'nfc', 'internal');
|
||||
$args->publicKey->excludeCredentials[] = $tmp;
|
||||
unset ($tmp);
|
||||
}
|
||||
}
|
||||
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* generates the object for key validation
|
||||
* Provide this data to navigator.credentials.get
|
||||
* @param array $credentialIds binary
|
||||
* @param int $timeout timeout in seconds
|
||||
* @param bool $allowUsb allow removable USB
|
||||
* @param bool $allowNfc allow Near Field Communication (NFC)
|
||||
* @param bool $allowBle allow Bluetooth
|
||||
* @param bool $allowInternal allow client device-specific transport. These authenticators are not removable from the client device.
|
||||
* @param bool|string $requireUserVerification indicates that you require user verification and will fail the operation
|
||||
* if the response does not have the UV flag set.
|
||||
* Valid values:
|
||||
* true = required
|
||||
* false = preferred
|
||||
* string 'required' 'preferred' 'discouraged'
|
||||
* @return \stdClass
|
||||
*/
|
||||
public function getGetArgs($credentialIds=array(), $timeout=20, $allowUsb=true, $allowNfc=true, $allowBle=true, $allowInternal=true, $requireUserVerification=false) {
|
||||
|
||||
// validate User Verification Requirement
|
||||
if (\is_bool($requireUserVerification)) {
|
||||
$requireUserVerification = $requireUserVerification ? 'required' : 'preferred';
|
||||
} else if (\is_string($requireUserVerification) && \in_array(\strtolower($requireUserVerification), ['required', 'preferred', 'discouraged'])) {
|
||||
$requireUserVerification = \strtolower($requireUserVerification);
|
||||
} else {
|
||||
$requireUserVerification = 'preferred';
|
||||
}
|
||||
|
||||
$args = new \stdClass();
|
||||
$args->publicKey = new \stdClass();
|
||||
$args->publicKey->timeout = $timeout * 1000; // microseconds
|
||||
$args->publicKey->challenge = $this->_createChallenge(); // binary
|
||||
$args->publicKey->userVerification = $requireUserVerification;
|
||||
$args->publicKey->rpId = $this->_rpId;
|
||||
|
||||
if (\is_array($credentialIds) && \count($credentialIds) > 0) {
|
||||
$args->publicKey->allowCredentials = array();
|
||||
|
||||
foreach ($credentialIds as $id) {
|
||||
$tmp = new \stdClass();
|
||||
$tmp->id = $id instanceof ByteBuffer ? $id : new ByteBuffer($id); // binary
|
||||
$tmp->transports = array();
|
||||
|
||||
if ($allowUsb) {
|
||||
$tmp->transports[] = 'usb';
|
||||
}
|
||||
if ($allowNfc) {
|
||||
$tmp->transports[] = 'nfc';
|
||||
}
|
||||
if ($allowBle) {
|
||||
$tmp->transports[] = 'ble';
|
||||
}
|
||||
if ($allowInternal) {
|
||||
$tmp->transports[] = 'internal';
|
||||
}
|
||||
|
||||
$tmp->type = 'public-key';
|
||||
$args->publicKey->allowCredentials[] = $tmp;
|
||||
unset ($tmp);
|
||||
}
|
||||
}
|
||||
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the new signature counter value.
|
||||
* returns null if there is no counter
|
||||
* @return ?int
|
||||
*/
|
||||
public function getSignatureCounter() {
|
||||
return \is_int($this->_signatureCounter) ? $this->_signatureCounter : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* process a create request and returns data to save for future logins
|
||||
* @param string $clientDataJSON binary from browser
|
||||
* @param string $attestationObject binary from browser
|
||||
* @param string|ByteBuffer $challenge binary used challange
|
||||
* @param bool $requireUserVerification true, if the device must verify user (e.g. by biometric data or pin)
|
||||
* @param bool $requireUserPresent false, if the device must NOT check user presence (e.g. by pressing a button)
|
||||
* @param bool $failIfRootMismatch false, if there should be no error thrown if root certificate doesn't match
|
||||
* @return \stdClass
|
||||
* @throws WebAuthnException
|
||||
*/
|
||||
public function processCreate($clientDataJSON, $attestationObject, $challenge, $requireUserVerification=false, $requireUserPresent=true, $failIfRootMismatch=true) {
|
||||
$clientDataHash = \hash('sha256', $clientDataJSON, true);
|
||||
$clientData = \json_decode($clientDataJSON);
|
||||
$challenge = $challenge instanceof ByteBuffer ? $challenge : new ByteBuffer($challenge);
|
||||
|
||||
// security: https://www.w3.org/TR/webauthn/#registering-a-new-credential
|
||||
|
||||
// 2. Let C, the client data claimed as collected during the credential creation,
|
||||
// be the result of running an implementation-specific JSON parser on JSONtext.
|
||||
if (!\is_object($clientData)) {
|
||||
throw new WebAuthnException('invalid client data', WebAuthnException::INVALID_DATA);
|
||||
}
|
||||
|
||||
// 3. Verify that the value of C.type is webauthn.create.
|
||||
if (!\property_exists($clientData, 'type') || $clientData->type !== 'webauthn.create') {
|
||||
throw new WebAuthnException('invalid type', WebAuthnException::INVALID_TYPE);
|
||||
}
|
||||
|
||||
// 4. Verify that the value of C.challenge matches the challenge that was sent to the authenticator in the create() call.
|
||||
if (!\property_exists($clientData, 'challenge') || ByteBuffer::fromBase64Url($clientData->challenge)->getBinaryString() !== $challenge->getBinaryString()) {
|
||||
throw new WebAuthnException('invalid challenge', WebAuthnException::INVALID_CHALLENGE);
|
||||
}
|
||||
|
||||
// 5. Verify that the value of C.origin matches the Relying Party's origin.
|
||||
if (!\property_exists($clientData, 'origin') || !$this->_checkOrigin($clientData->origin)) {
|
||||
throw new WebAuthnException('invalid origin', WebAuthnException::INVALID_ORIGIN);
|
||||
}
|
||||
|
||||
// Attestation
|
||||
$attestationObject = new Attestation\AttestationObject($attestationObject, $this->_formats);
|
||||
|
||||
// 9. Verify that the RP ID hash in authData is indeed the SHA-256 hash of the RP ID expected by the RP.
|
||||
if (!$attestationObject->validateRpIdHash($this->_rpIdHash)) {
|
||||
throw new WebAuthnException('invalid rpId hash', WebAuthnException::INVALID_RELYING_PARTY);
|
||||
}
|
||||
|
||||
// 14. Verify that attStmt is a correct attestation statement, conveying a valid attestation signature
|
||||
if (!$attestationObject->validateAttestation($clientDataHash)) {
|
||||
throw new WebAuthnException('invalid certificate signature', WebAuthnException::INVALID_SIGNATURE);
|
||||
}
|
||||
|
||||
// 15. If validation is successful, obtain a list of acceptable trust anchors
|
||||
$rootValid = is_array($this->_caFiles) ? $attestationObject->validateRootCertificate($this->_caFiles) : null;
|
||||
if ($failIfRootMismatch && is_array($this->_caFiles) && !$rootValid) {
|
||||
throw new WebAuthnException('invalid root certificate', WebAuthnException::CERTIFICATE_NOT_TRUSTED);
|
||||
}
|
||||
|
||||
// 10. Verify that the User Present bit of the flags in authData is set.
|
||||
$userPresent = $attestationObject->getAuthenticatorData()->getUserPresent();
|
||||
if ($requireUserPresent && !$userPresent) {
|
||||
throw new WebAuthnException('user not present during authentication', WebAuthnException::USER_PRESENT);
|
||||
}
|
||||
|
||||
// 11. If user verification is required for this registration, verify that the User Verified bit of the flags in authData is set.
|
||||
$userVerified = $attestationObject->getAuthenticatorData()->getUserVerified();
|
||||
if ($requireUserVerification && !$userVerified) {
|
||||
throw new WebAuthnException('user not verified during authentication', WebAuthnException::USER_VERIFICATED);
|
||||
}
|
||||
|
||||
$signCount = $attestationObject->getAuthenticatorData()->getSignCount();
|
||||
if ($signCount > 0) {
|
||||
$this->_signatureCounter = $signCount;
|
||||
}
|
||||
|
||||
// prepare data to store for future logins
|
||||
$data = new \stdClass();
|
||||
$data->rpId = $this->_rpId;
|
||||
$data->attestationFormat = $attestationObject->getAttestationFormatName();
|
||||
$data->credentialId = $attestationObject->getAuthenticatorData()->getCredentialId();
|
||||
$data->credentialPublicKey = $attestationObject->getAuthenticatorData()->getPublicKeyPem();
|
||||
$data->certificateChain = $attestationObject->getCertificateChain();
|
||||
$data->certificate = $attestationObject->getCertificatePem();
|
||||
$data->certificateIssuer = $attestationObject->getCertificateIssuer();
|
||||
$data->certificateSubject = $attestationObject->getCertificateSubject();
|
||||
$data->signatureCounter = $this->_signatureCounter;
|
||||
$data->AAGUID = $attestationObject->getAuthenticatorData()->getAAGUID();
|
||||
$data->rootValid = $rootValid;
|
||||
$data->userPresent = $userPresent;
|
||||
$data->userVerified = $userVerified;
|
||||
return $data;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* process a get request
|
||||
* @param string $clientDataJSON binary from browser
|
||||
* @param string $authenticatorData binary from browser
|
||||
* @param string $signature binary from browser
|
||||
* @param string $credentialPublicKey string PEM-formated public key from used credentialId
|
||||
* @param string|ByteBuffer $challenge binary from used challange
|
||||
* @param int $prevSignatureCnt signature count value of the last login
|
||||
* @param bool $requireUserVerification true, if the device must verify user (e.g. by biometric data or pin)
|
||||
* @param bool $requireUserPresent true, if the device must check user presence (e.g. by pressing a button)
|
||||
* @return boolean true if get is successful
|
||||
* @throws WebAuthnException
|
||||
*/
|
||||
public function processGet($clientDataJSON, $authenticatorData, $signature, $credentialPublicKey, $challenge, $prevSignatureCnt=null, $requireUserVerification=false, $requireUserPresent=true) {
|
||||
$authenticatorObj = new Attestation\AuthenticatorData($authenticatorData);
|
||||
$clientDataHash = \hash('sha256', $clientDataJSON, true);
|
||||
$clientData = \json_decode($clientDataJSON);
|
||||
$challenge = $challenge instanceof ByteBuffer ? $challenge : new ByteBuffer($challenge);
|
||||
|
||||
// https://www.w3.org/TR/webauthn/#verifying-assertion
|
||||
|
||||
// 1. If the allowCredentials option was given when this authentication ceremony was initiated,
|
||||
// verify that credential.id identifies one of the public key credentials that were listed in allowCredentials.
|
||||
// -> TO BE VERIFIED BY IMPLEMENTATION
|
||||
|
||||
// 2. If credential.response.userHandle is present, verify that the user identified
|
||||
// by this value is the owner of the public key credential identified by credential.id.
|
||||
// -> TO BE VERIFIED BY IMPLEMENTATION
|
||||
|
||||
// 3. Using credential’s id attribute (or the corresponding rawId, if base64url encoding is
|
||||
// inappropriate for your use case), look up the corresponding credential public key.
|
||||
// -> TO BE LOOKED UP BY IMPLEMENTATION
|
||||
|
||||
// 5. Let JSONtext be the result of running UTF-8 decode on the value of cData.
|
||||
if (!\is_object($clientData)) {
|
||||
throw new WebAuthnException('invalid client data', WebAuthnException::INVALID_DATA);
|
||||
}
|
||||
|
||||
// 7. Verify that the value of C.type is the string webauthn.get.
|
||||
if (!\property_exists($clientData, 'type') || $clientData->type !== 'webauthn.get') {
|
||||
throw new WebAuthnException('invalid type', WebAuthnException::INVALID_TYPE);
|
||||
}
|
||||
|
||||
// 8. Verify that the value of C.challenge matches the challenge that was sent to the
|
||||
// authenticator in the PublicKeyCredentialRequestOptions passed to the get() call.
|
||||
if (!\property_exists($clientData, 'challenge') || ByteBuffer::fromBase64Url($clientData->challenge)->getBinaryString() !== $challenge->getBinaryString()) {
|
||||
throw new WebAuthnException('invalid challenge', WebAuthnException::INVALID_CHALLENGE);
|
||||
}
|
||||
|
||||
// 9. Verify that the value of C.origin matches the Relying Party's origin.
|
||||
if (!\property_exists($clientData, 'origin') || !$this->_checkOrigin($clientData->origin)) {
|
||||
throw new WebAuthnException('invalid origin', WebAuthnException::INVALID_ORIGIN);
|
||||
}
|
||||
|
||||
// 11. Verify that the rpIdHash in authData is the SHA-256 hash of the RP ID expected by the Relying Party.
|
||||
if ($authenticatorObj->getRpIdHash() !== $this->_rpIdHash) {
|
||||
throw new WebAuthnException('invalid rpId hash', WebAuthnException::INVALID_RELYING_PARTY);
|
||||
}
|
||||
|
||||
// 12. Verify that the User Present bit of the flags in authData is set
|
||||
if ($requireUserPresent && !$authenticatorObj->getUserPresent()) {
|
||||
throw new WebAuthnException('user not present during authentication', WebAuthnException::USER_PRESENT);
|
||||
}
|
||||
|
||||
// 13. If user verification is required for this assertion, verify that the User Verified bit of the flags in authData is set.
|
||||
if ($requireUserVerification && !$authenticatorObj->getUserVerified()) {
|
||||
throw new WebAuthnException('user not verificated during authentication', WebAuthnException::USER_VERIFICATED);
|
||||
}
|
||||
|
||||
// 14. Verify the values of the client extension outputs
|
||||
// (extensions not implemented)
|
||||
|
||||
// 16. Using the credential public key looked up in step 3, verify that sig is a valid signature
|
||||
// over the binary concatenation of authData and hash.
|
||||
$dataToVerify = '';
|
||||
$dataToVerify .= $authenticatorData;
|
||||
$dataToVerify .= $clientDataHash;
|
||||
|
||||
$publicKey = \openssl_pkey_get_public($credentialPublicKey);
|
||||
if ($publicKey === false) {
|
||||
throw new WebAuthnException('public key invalid', WebAuthnException::INVALID_PUBLIC_KEY);
|
||||
}
|
||||
|
||||
if (\openssl_verify($dataToVerify, $signature, $publicKey, OPENSSL_ALGO_SHA256) !== 1) {
|
||||
throw new WebAuthnException('invalid signature', WebAuthnException::INVALID_SIGNATURE);
|
||||
}
|
||||
|
||||
// 17. If the signature counter value authData.signCount is nonzero,
|
||||
// if less than or equal to the signature counter value stored,
|
||||
// is a signal that the authenticator may be cloned
|
||||
$signatureCounter = $authenticatorObj->getSignCount();
|
||||
if ($signatureCounter > 0) {
|
||||
$this->_signatureCounter = $signatureCounter;
|
||||
if ($prevSignatureCnt !== null && $prevSignatureCnt >= $signatureCounter) {
|
||||
throw new WebAuthnException('signature counter not valid', WebAuthnException::SIGNATURE_COUNTER);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads root certificates from FIDO Alliance Metadata Service (MDS) to a specific folder
|
||||
* https://fidoalliance.org/metadata/
|
||||
* @param string $certFolder Folder path to save the certificates in PEM format.
|
||||
* @param bool $deleteCerts=true
|
||||
* @return int number of cetificates
|
||||
* @throws WebAuthnException
|
||||
*/
|
||||
public function queryFidoMetaDataService($certFolder, $deleteCerts=true) {
|
||||
$url = 'https://mds.fidoalliance.org/';
|
||||
$raw = null;
|
||||
if (\function_exists('curl_init')) {
|
||||
$ch = \curl_init($url);
|
||||
\curl_setopt($ch, CURLOPT_HEADER, false);
|
||||
\curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
\curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
|
||||
\curl_setopt($ch, CURLOPT_USERAGENT, 'github.com/lbuchs/WebAuthn - A simple PHP WebAuthn server library');
|
||||
$raw = \curl_exec($ch);
|
||||
\curl_close($ch);
|
||||
} else {
|
||||
$raw = \file_get_contents($url);
|
||||
}
|
||||
|
||||
$certFolder = \rtrim(\realpath($certFolder), '\\/');
|
||||
if (!is_dir($certFolder)) {
|
||||
throw new WebAuthnException('Invalid folder path for query FIDO Alliance Metadata Service');
|
||||
}
|
||||
|
||||
if (!\is_string($raw)) {
|
||||
throw new WebAuthnException('Unable to query FIDO Alliance Metadata Service');
|
||||
}
|
||||
|
||||
$jwt = \explode('.', $raw);
|
||||
if (\count($jwt) !== 3) {
|
||||
throw new WebAuthnException('Invalid JWT from FIDO Alliance Metadata Service');
|
||||
}
|
||||
|
||||
if ($deleteCerts) {
|
||||
foreach (\scandir($certFolder) as $ca) {
|
||||
if (\substr($ca, -4) === '.pem') {
|
||||
if (\unlink($certFolder . DIRECTORY_SEPARATOR . $ca) === false) {
|
||||
throw new WebAuthnException('Cannot delete certs in folder for FIDO Alliance Metadata Service');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
list($header, $payload, $hash) = $jwt;
|
||||
$payload = Binary\ByteBuffer::fromBase64Url($payload)->getJson();
|
||||
|
||||
$count = 0;
|
||||
if (\is_object($payload) && \property_exists($payload, 'entries') && \is_array($payload->entries)) {
|
||||
foreach ($payload->entries as $entry) {
|
||||
if (\is_object($entry) && \property_exists($entry, 'metadataStatement') && \is_object($entry->metadataStatement)) {
|
||||
$description = $entry->metadataStatement->description ?? null;
|
||||
$attestationRootCertificates = $entry->metadataStatement->attestationRootCertificates ?? null;
|
||||
|
||||
if ($description && $attestationRootCertificates) {
|
||||
|
||||
// create filename
|
||||
$certFilename = \preg_replace('/[^a-z0-9]/i', '_', $description);
|
||||
$certFilename = \trim(\preg_replace('/\_{2,}/i', '_', $certFilename),'_') . '.pem';
|
||||
$certFilename = \strtolower($certFilename);
|
||||
|
||||
// add certificate
|
||||
$certContent = $description . "\n";
|
||||
$certContent .= \str_repeat('-', \mb_strlen($description)) . "\n";
|
||||
|
||||
foreach ($attestationRootCertificates as $attestationRootCertificate) {
|
||||
$count++;
|
||||
$certContent .= "\n-----BEGIN CERTIFICATE-----\n";
|
||||
$certContent .= \chunk_split(\trim($attestationRootCertificate), 64, "\n");
|
||||
$certContent .= "-----END CERTIFICATE-----\n";
|
||||
}
|
||||
|
||||
if (\file_put_contents($certFolder . DIRECTORY_SEPARATOR . $certFilename, $certContent) === false) {
|
||||
throw new WebAuthnException('unable to save certificate from FIDO Alliance Metadata Service');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $count;
|
||||
}
|
||||
|
||||
// -----------------------------------------------
|
||||
// PRIVATE
|
||||
// -----------------------------------------------
|
||||
|
||||
/**
|
||||
* checks if the origin matchs the RP ID
|
||||
* @param string $origin
|
||||
* @return boolean
|
||||
* @throws WebAuthnException
|
||||
*/
|
||||
private function _checkOrigin($origin) {
|
||||
// https://www.w3.org/TR/webauthn/#rp-id
|
||||
|
||||
// The origin's scheme must be https
|
||||
if ($this->_rpId !== 'localhost' && \parse_url($origin, PHP_URL_SCHEME) !== 'https') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// extract host from origin
|
||||
$host = \parse_url($origin, PHP_URL_HOST);
|
||||
$host = \trim($host, '.');
|
||||
|
||||
// The RP ID must be equal to the origin's effective domain, or a registrable
|
||||
// domain suffix of the origin's effective domain.
|
||||
return \preg_match('/' . \preg_quote($this->_rpId) . '$/i', $host) === 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* generates a new challange
|
||||
* @param int $length
|
||||
* @return string
|
||||
* @throws WebAuthnException
|
||||
*/
|
||||
private function _createChallenge($length = 32) {
|
||||
if (!$this->_challenge) {
|
||||
$this->_challenge = ByteBuffer::randomBuffer($length);
|
||||
}
|
||||
return $this->_challenge;
|
||||
}
|
||||
}
|
||||
27
data/web/inc/lib/WebAuthn/WebAuthnException.php
Executable file
27
data/web/inc/lib/WebAuthn/WebAuthnException.php
Executable file
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
namespace lbuchs\WebAuthn;
|
||||
|
||||
/**
|
||||
* @author Lukas Buchs
|
||||
* @license https://github.com/lbuchs/WebAuthn/blob/master/LICENSE MIT
|
||||
*/
|
||||
class WebAuthnException extends \Exception {
|
||||
const INVALID_DATA = 1;
|
||||
const INVALID_TYPE = 2;
|
||||
const INVALID_CHALLENGE = 3;
|
||||
const INVALID_ORIGIN = 4;
|
||||
const INVALID_RELYING_PARTY = 5;
|
||||
const INVALID_SIGNATURE = 6;
|
||||
const INVALID_PUBLIC_KEY = 7;
|
||||
const CERTIFICATE_NOT_TRUSTED = 8;
|
||||
const USER_PRESENT = 9;
|
||||
const USER_VERIFICATED = 10;
|
||||
const SIGNATURE_COUNTER = 11;
|
||||
const CRYPTO_STRONG = 13;
|
||||
const BYTEBUFFER = 14;
|
||||
const CBOR = 15;
|
||||
|
||||
public function __construct($message = "", $code = 0, $previous = null) {
|
||||
parent::__construct($message, $code, $previous);
|
||||
}
|
||||
}
|
||||
48
data/web/inc/lib/WebAuthn/rootCertificates/apple.pem
Executable file
48
data/web/inc/lib/WebAuthn/rootCertificates/apple.pem
Executable file
|
|
@ -0,0 +1,48 @@
|
|||
Certificate:
|
||||
Data:
|
||||
Version: 3 (0x2)
|
||||
Serial Number:
|
||||
68:1d:01:6c:7a:3c:e3:02:25:a5:01:94:28:47:57:71
|
||||
|
||||
Signature Algorithm: ecdsa-with-SHA384
|
||||
|
||||
Issuer:
|
||||
stateOrProvinceName = California
|
||||
organizationName = Apple Inc.
|
||||
commonName = Apple WebAuthn Root CA
|
||||
|
||||
Validity
|
||||
Not Before: Mar 18 18:21:32 2020 GMT
|
||||
Not After : Mar 15 00:00:00 2045 GMT
|
||||
|
||||
Subject:
|
||||
stateOrProvinceName = California
|
||||
organizationName = Apple Inc.
|
||||
commonName = Apple WebAuthn Root CA
|
||||
|
||||
Subject Public Key Info:
|
||||
Public Key Algorithm: id-ecPublicKey
|
||||
ASN1 OID: secp384r1
|
||||
|
||||
X509v3 extensions:
|
||||
X509v3 Basic Constraints: critical
|
||||
CA:TRUE
|
||||
X509v3 Subject Key Identifier:
|
||||
26:D7:64:D9:C5:78:C2:5A:67:D1:A7:DE:6B:12:D0:1B:63:F1:C6:D7
|
||||
X509v3 Key Usage: critical
|
||||
Certificate Sign, CRL Sign
|
||||
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICEjCCAZmgAwIBAgIQaB0BbHo84wIlpQGUKEdXcTAKBggqhkjOPQQDAzBLMR8w
|
||||
HQYDVQQDDBZBcHBsZSBXZWJBdXRobiBSb290IENBMRMwEQYDVQQKDApBcHBsZSBJ
|
||||
bmMuMRMwEQYDVQQIDApDYWxpZm9ybmlhMB4XDTIwMDMxODE4MjEzMloXDTQ1MDMx
|
||||
NTAwMDAwMFowSzEfMB0GA1UEAwwWQXBwbGUgV2ViQXV0aG4gUm9vdCBDQTETMBEG
|
||||
A1UECgwKQXBwbGUgSW5jLjETMBEGA1UECAwKQ2FsaWZvcm5pYTB2MBAGByqGSM49
|
||||
AgEGBSuBBAAiA2IABCJCQ2pTVhzjl4Wo6IhHtMSAzO2cv+H9DQKev3//fG59G11k
|
||||
xu9eI0/7o6V5uShBpe1u6l6mS19S1FEh6yGljnZAJ+2GNP1mi/YK2kSXIuTHjxA/
|
||||
pcoRf7XkOtO4o1qlcaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUJtdk
|
||||
2cV4wlpn0afeaxLQG2PxxtcwDgYDVR0PAQH/BAQDAgEGMAoGCCqGSM49BAMDA2cA
|
||||
MGQCMFrZ+9DsJ1PW9hfNdBywZDsWDbWFp28it1d/5w2RPkRX3Bbn/UbDTNLx7Jr3
|
||||
jAGGiQIwHFj+dJZYUJR786osByBelJYsVZd2GbHQu209b5RCmGQ21gpSAk9QZW4B
|
||||
1bWeT0vT
|
||||
-----END CERTIFICATE-----
|
||||
BIN
data/web/inc/lib/WebAuthn/rootCertificates/bsi.pem
Executable file
BIN
data/web/inc/lib/WebAuthn/rootCertificates/bsi.pem
Executable file
Binary file not shown.
37
data/web/inc/lib/WebAuthn/rootCertificates/globalSign.pem
Executable file
37
data/web/inc/lib/WebAuthn/rootCertificates/globalSign.pem
Executable file
|
|
@ -0,0 +1,37 @@
|
|||
Certificate:
|
||||
Data:
|
||||
Version: 3 (0x2)
|
||||
Serial Number:
|
||||
04:00:00:00:00:01:0f:86:26:e6:0d
|
||||
Signature Algorithm: sha1WithRSAEncryption
|
||||
Issuer: OU=GlobalSign Root CA - R2, O=GlobalSign, CN=GlobalSign
|
||||
Validity
|
||||
Not Before: Dec 15 08:00:00 2006 GMT
|
||||
Not After : Dec 15 08:00:00 2021 GMT
|
||||
Subject: OU=GlobalSign Root CA - R2, O=GlobalSign, CN=GlobalSign
|
||||
Subject Public Key Info:
|
||||
Public Key Algorithm: rsaEncryption
|
||||
Public-Key: (2048 bit)
|
||||
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDujCCAqKgAwIBAgILBAAAAAABD4Ym5g0wDQYJKoZIhvcNAQEFBQAwTDEgMB4G
|
||||
A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjIxEzARBgNVBAoTCkdsb2JhbFNp
|
||||
Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDYxMjE1MDgwMDAwWhcNMjExMjE1
|
||||
MDgwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMjETMBEG
|
||||
A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI
|
||||
hvcNAQEBBQADggEPADCCAQoCggEBAKbPJA6+Lm8omUVCxKs+IVSbC9N/hHD6ErPL
|
||||
v4dfxn+G07IwXNb9rfF73OX4YJYJkhD10FPe+3t+c4isUoh7SqbKSaZeqKeMWhG8
|
||||
eoLrvozps6yWJQeXSpkqBy+0Hne/ig+1AnwblrjFuTosvNYSuetZfeLQBoZfXklq
|
||||
tTleiDTsvHgMCJiEbKjNS7SgfQx5TfC4LcshytVsW33hoCmEofnTlEnLJGKRILzd
|
||||
C9XZzPnqJworc5HGnRusyMvo4KD0L5CLTfuwNhv2GXqF4G3yYROIXJ/gkwpRl4pa
|
||||
zq+r1feqCapgvdzZX99yqWATXgAByUr6P6TqBwMhAo6CygPCm48CAwEAAaOBnDCB
|
||||
mTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUm+IH
|
||||
V2ccHsBqBt5ZtJot39wZhi4wNgYDVR0fBC8wLTAroCmgJ4YlaHR0cDovL2NybC5n
|
||||
bG9iYWxzaWduLm5ldC9yb290LXIyLmNybDAfBgNVHSMEGDAWgBSb4gdXZxwewGoG
|
||||
3lm0mi3f3BmGLjANBgkqhkiG9w0BAQUFAAOCAQEAmYFThxxol4aR7OBKuEQLq4Gs
|
||||
J0/WwbgcQ3izDJr86iw8bmEbTUsp9Z8FHSbBuOmDAGJFtqkIk7mpM0sYmsL4h4hO
|
||||
291xNBrBVNpGP+DTKqttVCL1OmLNIG+6KYnX3ZHu01yiPqFbQfXf5WRDLenVOavS
|
||||
ot+3i9DAgBkcRcAtjOj4LaR0VknFBbVPFd5uRHg5h6h+u/N5GJG79G+dwfCMNYxd
|
||||
AfvDbbnvRG15RjF+Cv6pgsH/76tuIMRQyV+dTZsXjAzlAcmgQWpzU/qlULRuJQ/7
|
||||
TBj0/VLZjmmx6BEP3ojY+x1J96relc8geMJgEtslQIxq/H5COEBkEveegeGTLg==
|
||||
-----END CERTIFICATE-----
|
||||
130
data/web/inc/lib/WebAuthn/rootCertificates/googleHardware.pem
Executable file
130
data/web/inc/lib/WebAuthn/rootCertificates/googleHardware.pem
Executable file
|
|
@ -0,0 +1,130 @@
|
|||
Google Hardware Attestation Root certificate
|
||||
----------------------------------------------
|
||||
|
||||
https://developer.android.com/training/articles/security-key-attestation.html
|
||||
|
||||
Certificate:
|
||||
Data:
|
||||
Version: 3 (0x2)
|
||||
Serial Number:
|
||||
e8:fa:19:63:14:d2:fa:18
|
||||
Signature Algorithm: sha256WithRSAEncryption
|
||||
Issuer: serialNumber = f92009e853b6b045
|
||||
Validity
|
||||
Not Before: May 26 16:28:52 2016 GMT
|
||||
Not After : May 24 16:28:52 2026 GMT
|
||||
Subject: serialNumber = f92009e853b6b045
|
||||
Subject Public Key Info:
|
||||
Public Key Algorithm: rsaEncryption
|
||||
Public-Key: (4096 bit)
|
||||
Exponent: 65537 (0x10001)
|
||||
X509v3 extensions:
|
||||
X509v3 Subject Key Identifier:
|
||||
36:61:E1:00:7C:88:05:09:51:8B:44:6C:47:FF:1A:4C:C9:EA:4F:12
|
||||
X509v3 Authority Key Identifier:
|
||||
keyid:36:61:E1:00:7C:88:05:09:51:8B:44:6C:47:FF:1A:4C:C9:EA:4F:12
|
||||
|
||||
X509v3 Basic Constraints: critical
|
||||
CA:TRUE
|
||||
X509v3 Key Usage: critical
|
||||
Digital Signature, Certificate Sign, CRL Sign
|
||||
X509v3 CRL Distribution Points:
|
||||
|
||||
Full Name:
|
||||
URI:https://android.googleapis.com/attestation/crl/
|
||||
|
||||
Signature Algorithm: sha256WithRSAEncryption
|
||||
|
||||
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIFYDCCA0igAwIBAgIJAOj6GWMU0voYMA0GCSqGSIb3DQEBCwUAMBsxGTAXBgNV
|
||||
BAUTEGY5MjAwOWU4NTNiNmIwNDUwHhcNMTYwNTI2MTYyODUyWhcNMjYwNTI0MTYy
|
||||
ODUyWjAbMRkwFwYDVQQFExBmOTIwMDllODUzYjZiMDQ1MIICIjANBgkqhkiG9w0B
|
||||
AQEFAAOCAg8AMIICCgKCAgEAr7bHgiuxpwHsK7Qui8xUFmOr75gvMsd/dTEDDJdS
|
||||
Sxtf6An7xyqpRR90PL2abxM1dEqlXnf2tqw1Ne4Xwl5jlRfdnJLmN0pTy/4lj4/7
|
||||
tv0Sk3iiKkypnEUtR6WfMgH0QZfKHM1+di+y9TFRtv6y//0rb+T+W8a9nsNL/ggj
|
||||
nar86461qO0rOs2cXjp3kOG1FEJ5MVmFmBGtnrKpa73XpXyTqRxB/M0n1n/W9nGq
|
||||
C4FSYa04T6N5RIZGBN2z2MT5IKGbFlbC8UrW0DxW7AYImQQcHtGl/m00QLVWutHQ
|
||||
oVJYnFPlXTcHYvASLu+RhhsbDmxMgJJ0mcDpvsC4PjvB+TxywElgS70vE0XmLD+O
|
||||
JtvsBslHZvPBKCOdT0MS+tgSOIfga+z1Z1g7+DVagf7quvmag8jfPioyKvxnK/Eg
|
||||
sTUVi2ghzq8wm27ud/mIM7AY2qEORR8Go3TVB4HzWQgpZrt3i5MIlCaY504LzSRi
|
||||
igHCzAPlHws+W0rB5N+er5/2pJKnfBSDiCiFAVtCLOZ7gLiMm0jhO2B6tUXHI/+M
|
||||
RPjy02i59lINMRRev56GKtcd9qO/0kUJWdZTdA2XoS82ixPvZtXQpUpuL12ab+9E
|
||||
aDK8Z4RHJYYfCT3Q5vNAXaiWQ+8PTWm2QgBR/bkwSWc+NpUFgNPN9PvQi8WEg5Um
|
||||
AGMCAwEAAaOBpjCBozAdBgNVHQ4EFgQUNmHhAHyIBQlRi0RsR/8aTMnqTxIwHwYD
|
||||
VR0jBBgwFoAUNmHhAHyIBQlRi0RsR/8aTMnqTxIwDwYDVR0TAQH/BAUwAwEB/zAO
|
||||
BgNVHQ8BAf8EBAMCAYYwQAYDVR0fBDkwNzA1oDOgMYYvaHR0cHM6Ly9hbmRyb2lk
|
||||
Lmdvb2dsZWFwaXMuY29tL2F0dGVzdGF0aW9uL2NybC8wDQYJKoZIhvcNAQELBQAD
|
||||
ggIBACDIw41L3KlXG0aMiS//cqrG+EShHUGo8HNsw30W1kJtjn6UBwRM6jnmiwfB
|
||||
Pb8VA91chb2vssAtX2zbTvqBJ9+LBPGCdw/E53Rbf86qhxKaiAHOjpvAy5Y3m00m
|
||||
qC0w/Zwvju1twb4vhLaJ5NkUJYsUS7rmJKHHBnETLi8GFqiEsqTWpG/6ibYCv7rY
|
||||
DBJDcR9W62BW9jfIoBQcxUCUJouMPH25lLNcDc1ssqvC2v7iUgI9LeoM1sNovqPm
|
||||
QUiG9rHli1vXxzCyaMTjwftkJLkf6724DFhuKug2jITV0QkXvaJWF4nUaHOTNA4u
|
||||
JU9WDvZLI1j83A+/xnAJUucIv/zGJ1AMH2boHqF8CY16LpsYgBt6tKxxWH00XcyD
|
||||
CdW2KlBCeqbQPcsFmWyWugxdcekhYsAWyoSf818NUsZdBWBaR/OukXrNLfkQ79Iy
|
||||
ZohZbvabO/X+MVT3rriAoKc8oE2Uws6DF+60PV7/WIPjNvXySdqspImSN78mflxD
|
||||
qwLqRBYkA3I75qppLGG9rp7UCdRjxMl8ZDBld+7yvHVgt1cVzJx9xnyGCC23Uaic
|
||||
MDSXYrB4I4WHXPGjxhZuCuPBLTdOLU8YRvMYdEvYebWHMpvwGCF6bAx3JBpIeOQ1
|
||||
wDB5y0USicV3YgYGmi+NZfhA4URSh77Yd6uuJOJENRaNVTzk
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
|
||||
Certificate:
|
||||
Data:
|
||||
Version: 3 (0x2)
|
||||
Serial Number: 15352756130135856819 (0xd50ff25ba3f2d6b3)
|
||||
Signature Algorithm: sha256WithRSAEncryption
|
||||
Issuer:
|
||||
serialNumber = f92009e853b6b045
|
||||
Validity
|
||||
Not Before: Nov 22 20:37:58 2019 GMT
|
||||
Not After : Nov 18 20:37:58 2034 GMT
|
||||
Subject:
|
||||
serialNumber = f92009e853b6b045
|
||||
Subject Public Key Info:
|
||||
Public Key Algorithm: rsaEncryption
|
||||
Public-Key: (4096 bit)
|
||||
Exponent: 65537 (0x10001)
|
||||
X509v3 extensions:
|
||||
X509v3 Subject Key Identifier:
|
||||
36:61:E1:00:7C:88:05:09:51:8B:44:6C:47:FF:1A:4C:C9:EA:4F:12
|
||||
X509v3 Authority Key Identifier:
|
||||
keyid:36:61:E1:00:7C:88:05:09:51:8B:44:6C:47:FF:1A:4C:C9:EA:4F:12
|
||||
|
||||
X509v3 Basic Constraints: critical
|
||||
CA:TRUE
|
||||
X509v3 Key Usage: critical
|
||||
Certificate Sign
|
||||
Signature Algorithm: sha256WithRSAEncryption
|
||||
|
||||
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIFHDCCAwSgAwIBAgIJANUP8luj8tazMA0GCSqGSIb3DQEBCwUAMBsxGTAXBgNV
|
||||
BAUTEGY5MjAwOWU4NTNiNmIwNDUwHhcNMTkxMTIyMjAzNzU4WhcNMzQxMTE4MjAz
|
||||
NzU4WjAbMRkwFwYDVQQFExBmOTIwMDllODUzYjZiMDQ1MIICIjANBgkqhkiG9w0B
|
||||
AQEFAAOCAg8AMIICCgKCAgEAr7bHgiuxpwHsK7Qui8xUFmOr75gvMsd/dTEDDJdS
|
||||
Sxtf6An7xyqpRR90PL2abxM1dEqlXnf2tqw1Ne4Xwl5jlRfdnJLmN0pTy/4lj4/7
|
||||
tv0Sk3iiKkypnEUtR6WfMgH0QZfKHM1+di+y9TFRtv6y//0rb+T+W8a9nsNL/ggj
|
||||
nar86461qO0rOs2cXjp3kOG1FEJ5MVmFmBGtnrKpa73XpXyTqRxB/M0n1n/W9nGq
|
||||
C4FSYa04T6N5RIZGBN2z2MT5IKGbFlbC8UrW0DxW7AYImQQcHtGl/m00QLVWutHQ
|
||||
oVJYnFPlXTcHYvASLu+RhhsbDmxMgJJ0mcDpvsC4PjvB+TxywElgS70vE0XmLD+O
|
||||
JtvsBslHZvPBKCOdT0MS+tgSOIfga+z1Z1g7+DVagf7quvmag8jfPioyKvxnK/Eg
|
||||
sTUVi2ghzq8wm27ud/mIM7AY2qEORR8Go3TVB4HzWQgpZrt3i5MIlCaY504LzSRi
|
||||
igHCzAPlHws+W0rB5N+er5/2pJKnfBSDiCiFAVtCLOZ7gLiMm0jhO2B6tUXHI/+M
|
||||
RPjy02i59lINMRRev56GKtcd9qO/0kUJWdZTdA2XoS82ixPvZtXQpUpuL12ab+9E
|
||||
aDK8Z4RHJYYfCT3Q5vNAXaiWQ+8PTWm2QgBR/bkwSWc+NpUFgNPN9PvQi8WEg5Um
|
||||
AGMCAwEAAaNjMGEwHQYDVR0OBBYEFDZh4QB8iAUJUYtEbEf/GkzJ6k8SMB8GA1Ud
|
||||
IwQYMBaAFDZh4QB8iAUJUYtEbEf/GkzJ6k8SMA8GA1UdEwEB/wQFMAMBAf8wDgYD
|
||||
VR0PAQH/BAQDAgIEMA0GCSqGSIb3DQEBCwUAA4ICAQBOMaBc8oumXb2voc7XCWnu
|
||||
XKhBBK3e2KMGz39t7lA3XXRe2ZLLAkLM5y3J7tURkf5a1SutfdOyXAmeE6SRo83U
|
||||
h6WszodmMkxK5GM4JGrnt4pBisu5igXEydaW7qq2CdC6DOGjG+mEkN8/TA6p3cno
|
||||
L/sPyz6evdjLlSeJ8rFBH6xWyIZCbrcpYEJzXaUOEaxxXxgYz5/cTiVKN2M1G2ok
|
||||
QBUIYSY6bjEL4aUN5cfo7ogP3UvliEo3Eo0YgwuzR2v0KR6C1cZqZJSTnghIC/vA
|
||||
D32KdNQ+c3N+vl2OTsUVMC1GiWkngNx1OO1+kXW+YTnnTUOtOIswUP/Vqd5SYgAI
|
||||
mMAfY8U9/iIgkQj6T2W6FsScy94IN9fFhE1UtzmLoBIuUFsVXJMTz+Jucth+IqoW
|
||||
Fua9v1R93/k98p41pjtFX+H8DslVgfP097vju4KDlqN64xV1grw3ZLl4CiOe/A91
|
||||
oeLm2UHOq6wn3esB4r2EIQKb6jTVGu5sYCcdWpXr0AUVqcABPdgL+H7qJguBw09o
|
||||
jm6xNIrw2OocrDKsudk/okr/AwqEyPKw9WnMlQgLIKw1rODG2NvU9oR3GVGdMkUB
|
||||
ZutL8VuFkERQGt6vQ2OCw0sV47VMkuYbacK/xyZFiRcrPJPb41zgbQj9XAEyLKCH
|
||||
ex0SdDrx+tWUDqG8At2JHA==
|
||||
-----END CERTIFICATE-----
|
||||
31
data/web/inc/lib/WebAuthn/rootCertificates/huawei.pem
Executable file
31
data/web/inc/lib/WebAuthn/rootCertificates/huawei.pem
Executable file
|
|
@ -0,0 +1,31 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIFZDCCA0ygAwIBAgIIYsLLTehAXpYwDQYJKoZIhvcNAQELBQAwUDELMAkGA1UE
|
||||
BhMCQ04xDzANBgNVBAoMBkh1YXdlaTETMBEGA1UECwwKSHVhd2VpIENCRzEbMBkG
|
||||
A1UEAwwSSHVhd2VpIENCRyBSb290IENBMB4XDTE3MDgyMTEwNTYyN1oXDTQyMDgx
|
||||
NTEwNTYyN1owUDELMAkGA1UEBhMCQ04xDzANBgNVBAoMBkh1YXdlaTETMBEGA1UE
|
||||
CwwKSHVhd2VpIENCRzEbMBkGA1UEAwwSSHVhd2VpIENCRyBSb290IENBMIICIjAN
|
||||
BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA1OyKm3Ig/6eibB7Uz2o93UqGk2M7
|
||||
84WdfF8mvffvu218d61G5M3Px54E3kefUTk5Ky1ywHvw7Rp9KDuYv7ktaHkk+yr5
|
||||
9Ihseu3a7iM/C6SnMSGt+LfB/Bcob9Abw95EigXQ4yQddX9hbNrin3AwZw8wMjEI
|
||||
SYYDo5GuYDL0NbAiYg2Y5GpfYIqRzoi6GqDz+evLrsl20kJeCEPgJZN4Jg00Iq9k
|
||||
++EKOZ5Jc/Zx22ZUgKpdwKABkvzshEgG6WWUPB+gosOiLv++inu/9blDpEzQZhjZ
|
||||
9WVHpURHDK1YlCvubVAMhDpnbqNHZ0AxlPletdoyugrH/OLKl5inhMXNj3Re7Hl8
|
||||
WsBWLUKp6sXFf0dvSFzqnr2jkhicS+K2IYZnjghC9cOBRO8fnkonh0EBt0evjUIK
|
||||
r5ClbCKioBX8JU+d4ldtWOpp2FlxeFTLreDJ5ZBU4//bQpTwYMt7gwMK+MO5Wtok
|
||||
Ux3UF98Z6GdUgbl6nBjBe82c7oIQXhHGHPnURQO7DDPgyVnNOnTPIkmiHJh/e3vk
|
||||
VhiZNHFCCLTip6GoJVrLxwb9i4q+d0thw4doxVJ5NB9OfDMV64/ybJgpf7m3Ld2y
|
||||
E0gsf1prrRlDFDXjlYyqqpf1l9Y0u3ctXo7UpXMgbyDEpUQhq3a7txZQO/17luTD
|
||||
oA6Tz1ADavvBwHkCAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF
|
||||
MAMBAf8wHQYDVR0OBBYEFKrE03lH6G4ja+/wqWwicz16GWmhMA0GCSqGSIb3DQEB
|
||||
CwUAA4ICAQC1d3TMB+VHZdGrWJbfaBShFNiCTN/MceSHOpzBn6JumQP4N7mxCOwd
|
||||
RSsGKQxV2NPH7LTXWNhUvUw5Sek96FWx/+Oa7jsj3WNAVtmS3zKpCQ5iGb08WIRO
|
||||
cFnx3oUQ5rcO8r/lUk7Q2cN0E+rF4xsdQrH9k2cd3kAXZXBjfxfKPJTdPy1XnZR/
|
||||
h8H5EwEK5DWjSzK1wKd3G/Fxdm3E23pcr4FZgdYdOlFSiqW2TJ3Qe6lF4GOKOOyd
|
||||
WHkpu54ieTsqoYcuMKnKMjT2SLNNgv9Gu5ipaG8Olz6g9C7Htp943lmK/1Vtnhgg
|
||||
pL3rDTsFX/+ehk7OtxuNzRMD9lXUtEfok7f8XB0dcL4ZjnEhDmp5QZqC1kMubHQt
|
||||
QnTauEiv0YkSGOwJAUZpK1PIff5GgxXYfaHfBC6Op4q02ppl5Q3URl7XIjYLjvs9
|
||||
t4S9xPe8tb6416V2fe1dZ62vOXMMKHkZjVihh+IceYpJYHuyfKoYJyahLOQXZykG
|
||||
K5iPAEEtq3HPfMVF43RKHOwfhrAH5KwelUA/0EkcR4Gzth1MKEqojdnYNemkkSy7
|
||||
aNPPT4LEm5R7sV6vG1CjwbgvQrWCgc4nMb8ngdfnVF7Ydqjqi9SAqUzIk4+Uf0ZY
|
||||
+6RY5IcHdCaiPaWIE1xURQ8B0DRUURsQwXdjZhgLN/DKJpCl5aCCxg==
|
||||
-----END CERTIFICATE-----
|
||||
56
data/web/inc/lib/WebAuthn/rootCertificates/hypersecu.pem
Executable file
56
data/web/inc/lib/WebAuthn/rootCertificates/hypersecu.pem
Executable file
|
|
@ -0,0 +1,56 @@
|
|||
HyperFIDO U2F Security Key Attestation CA
|
||||
https://hypersecu.com/support/downloads/attestation
|
||||
|
||||
Last Update: 2017-01-01
|
||||
|
||||
HyperFIDO U2F Security Key devices which contain attestation certificates signed by a set of CAs.
|
||||
This file contains the CA certificates that Relying Parties (RP) need to configure their software
|
||||
with to be able to verify U2F device certificates.
|
||||
|
||||
The file will be updated as needed when we publish more CA certificates.
|
||||
|
||||
Issuer: CN=FT FIDO 0100
|
||||
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIBjTCCATOgAwIBAgIBATAKBggqhkjOPQQDAjAXMRUwEwYDVQQDEwxGVCBGSURP
|
||||
IDAxMDAwHhcNMTQwNzAxMTUzNjI2WhcNNDQwNzAzMTUzNjI2WjAXMRUwEwYDVQQD
|
||||
EwxGVCBGSURPIDAxMDAwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASxdLxJx8ol
|
||||
S3DS5cIHzunPF0gg69d+o8ZVCMJtpRtlfBzGuVL4YhaXk2SC2gptPTgmpZCV2vbN
|
||||
fAPi5gOF0vbZo3AwbjAdBgNVHQ4EFgQUXt4jWlYDgwhaPU+EqLmeM9LoPRMwPwYD
|
||||
VR0jBDgwNoAUXt4jWlYDgwhaPU+EqLmeM9LoPROhG6QZMBcxFTATBgNVBAMTDEZU
|
||||
IEZJRE8gMDEwMIIBATAMBgNVHRMEBTADAQH/MAoGCCqGSM49BAMCA0gAMEUCIQC2
|
||||
D9o9cconKTo8+4GZPyZBJ3amc8F0/kzyidX9dhrAIAIgM9ocs5BW/JfmshVP9Mb+
|
||||
Joa/kgX4dWbZxrk0ioTfJZg=
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
|
||||
Certificate:
|
||||
Data:
|
||||
Version: 3 (0x2)
|
||||
Serial Number: 4107 (0x100b)
|
||||
Signature Algorithm: ecdsa-with-SHA256
|
||||
Issuer:
|
||||
commonName = HYPERFIDO 0200
|
||||
organizationName = HYPERSECU
|
||||
countryName = CA
|
||||
Validity
|
||||
Not Before: Jan 1 00:00:00 2018 GMT
|
||||
Not After : Dec 31 23:59:59 2047 GMT
|
||||
Subject:
|
||||
commonName = HYPERFIDO 0200
|
||||
organizationName = HYPERSECU
|
||||
countryName = CA
|
||||
|
||||
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIBxzCCAWygAwIBAgICEAswCgYIKoZIzj0EAwIwOjELMAkGA1UEBhMCQ0ExEjAQ
|
||||
BgNVBAoMCUhZUEVSU0VDVTEXMBUGA1UEAwwOSFlQRVJGSURPIDAyMDAwIBcNMTgw
|
||||
MTAxMDAwMDAwWhgPMjA0NzEyMzEyMzU5NTlaMDoxCzAJBgNVBAYTAkNBMRIwEAYD
|
||||
VQQKDAlIWVBFUlNFQ1UxFzAVBgNVBAMMDkhZUEVSRklETyAwMjAwMFkwEwYHKoZI
|
||||
zj0CAQYIKoZIzj0DAQcDQgAErKUI1G0S7a6IOLlmHipLlBuxTYjsEESQvzQh3dB7
|
||||
dvxxWWm7kWL91rq6S7ayZG0gZPR+zYqdFzwAYDcG4+aX66NgMF4wHQYDVR0OBBYE
|
||||
FLZYcfMMwkQAGbt3ryzZFPFypmsIMB8GA1UdIwQYMBaAFLZYcfMMwkQAGbt3ryzZ
|
||||
FPFypmsIMAwGA1UdEwQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMAoGCCqGSM49BAMC
|
||||
A0kAMEYCIQCG2/ppMGt7pkcRie5YIohS3uDPIrmiRcTjqDclKVWg0gIhANcPNDZH
|
||||
E2/zZ+uB5ThG9OZus+xSb4knkrbAyXKX2zm/
|
||||
-----END CERTIFICATE-----
|
||||
28844
data/web/inc/lib/WebAuthn/rootCertificates/microsoftTpmCollection.pem
Executable file
28844
data/web/inc/lib/WebAuthn/rootCertificates/microsoftTpmCollection.pem
Executable file
File diff suppressed because it is too large
Load diff
11
data/web/inc/lib/WebAuthn/rootCertificates/nitro.pem
Executable file
11
data/web/inc/lib/WebAuthn/rootCertificates/nitro.pem
Executable file
|
|
@ -0,0 +1,11 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIBmjCCAT8CFBZiBJbp2fT/LaRJ8Xwl9qhX62boMAoGCCqGSM49BAMCME4xCzAJ
|
||||
BgNVBAYTAkRFMRYwFAYDVQQKDA1OaXRyb2tleSBHbWJIMRAwDgYDVQQLDAdSb290
|
||||
IENBMRUwEwYDVQQDDAxuaXRyb2tleS5jb20wIBcNMTkxMjA0MDczNTM1WhgPMjA2
|
||||
OTExMjEwNzM1MzVaME4xCzAJBgNVBAYTAkRFMRYwFAYDVQQKDA1OaXRyb2tleSBH
|
||||
bWJIMRAwDgYDVQQLDAdSb290IENBMRUwEwYDVQQDDAxuaXRyb2tleS5jb20wWTAT
|
||||
BgcqhkjOPQIBBggqhkjOPQMBBwNCAAQy6KIN2gXqaSMWdWir/Hnx58NBzjthYdNv
|
||||
k95hdt7jCpyW2cHqLdQ5Sqcvo0CuordgDOach0ZGB60w9GZY8SHJMAoGCCqGSM49
|
||||
BAMCA0kAMEYCIQDLmdy2G2mM4rZKjl6CVfjV7khilIS5D3xRQzubeqzQNAIhAKIG
|
||||
X29SfiB6K9k6Hb3q+q7bRn1o1dhV1cj592YYnu1/
|
||||
-----END CERTIFICATE-----
|
||||
41
data/web/inc/lib/WebAuthn/rootCertificates/solo.pem
Executable file
41
data/web/inc/lib/WebAuthn/rootCertificates/solo.pem
Executable file
|
|
@ -0,0 +1,41 @@
|
|||
Solokeys FIDO2/U2F Device Attestation CA
|
||||
========================================
|
||||
Data:
|
||||
Version: 1 (0x0)
|
||||
Serial Number: 14143382635911888524 (0xc44763928ff4be8c)
|
||||
Signature Algorithm: ecdsa-with-SHA256
|
||||
|
||||
Issuer:
|
||||
emailAddress = hello@solokeys.com
|
||||
commonName = solokeys.com
|
||||
organizationalUnitName = Root CA
|
||||
organizationName = Solo Keys
|
||||
stateOrProvinceName = Maryland
|
||||
countryName = US
|
||||
|
||||
Validity
|
||||
Not Before: Nov 11 12:51:42 2018 GMT
|
||||
Not After : Oct 29 12:51:42 2068 GMT
|
||||
|
||||
Subject:
|
||||
emailAddress = hello@solokeys.com
|
||||
commonName = solokeys.com
|
||||
organizationalUnitName = Root CA
|
||||
organizationName = Solo Keys
|
||||
stateOrProvinceName = Maryland
|
||||
countryName = US
|
||||
|
||||
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIB9DCCAZoCCQDER2OSj/S+jDAKBggqhkjOPQQDAjCBgDELMAkGA1UEBhMCVVMx
|
||||
ETAPBgNVBAgMCE1hcnlsYW5kMRIwEAYDVQQKDAlTb2xvIEtleXMxEDAOBgNVBAsM
|
||||
B1Jvb3QgQ0ExFTATBgNVBAMMDHNvbG9rZXlzLmNvbTEhMB8GCSqGSIb3DQEJARYS
|
||||
aGVsbG9Ac29sb2tleXMuY29tMCAXDTE4MTExMTEyNTE0MloYDzIwNjgxMDI5MTI1
|
||||
MTQyWjCBgDELMAkGA1UEBhMCVVMxETAPBgNVBAgMCE1hcnlsYW5kMRIwEAYDVQQK
|
||||
DAlTb2xvIEtleXMxEDAOBgNVBAsMB1Jvb3QgQ0ExFTATBgNVBAMMDHNvbG9rZXlz
|
||||
LmNvbTEhMB8GCSqGSIb3DQEJARYSaGVsbG9Ac29sb2tleXMuY29tMFkwEwYHKoZI
|
||||
zj0CAQYIKoZIzj0DAQcDQgAEWHAN0CCJVZdMs0oktZ5m93uxmB1iyq8ELRLtqVFL
|
||||
SOiHQEab56qRTB/QzrpGAY++Y2mw+vRuQMNhBiU0KzwjBjAKBggqhkjOPQQDAgNI
|
||||
ADBFAiEAz9SlrAXIlEu87vra54rICPs+4b0qhp3PdzcTg7rvnP0CIGjxzlteQQx+
|
||||
jQGd7rwSZuE5RWUPVygYhUstQO9zNUOs
|
||||
-----END CERTIFICATE-----
|
||||
17
data/web/inc/lib/WebAuthn/rootCertificates/trustkey.pem
Executable file
17
data/web/inc/lib/WebAuthn/rootCertificates/trustkey.pem
Executable file
|
|
@ -0,0 +1,17 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIICpTCCAkqgAwIBAgIBATAKBggqhkjOPQQDAjCBrzELMAkGA1UEBhMCS1IxETAP
|
||||
BgNVBAgMCFNlb3VsLVNpMRMwEQYDVQQHDApHYW5nbmFtLUd1MRcwFQYDVQQKDA5l
|
||||
V0JNIENvLiwgTHRkLjEiMCAGA1UECwwZQXV0aGVudGljYXRvciBBdHRlc3RhdGlv
|
||||
bjEcMBoGA1UEAwwTZVdCTSBDQSBDZXJ0aWZpY2F0ZTEdMBsGCSqGSIb3DQEJARYO
|
||||
aW5mb0BlLXdibS5jb20wHhcNMTgwNzAyMDUzMTM5WhcNMjMwNzAxMDUzMTM5WjCB
|
||||
rzELMAkGA1UEBhMCS1IxETAPBgNVBAgMCFNlb3VsLVNpMRMwEQYDVQQHDApHYW5n
|
||||
bmFtLUd1MRcwFQYDVQQKDA5lV0JNIENvLiwgTHRkLjEiMCAGA1UECwwZQXV0aGVu
|
||||
dGljYXRvciBBdHRlc3RhdGlvbjEcMBoGA1UEAwwTZVdCTSBDQSBDZXJ0aWZpY2F0
|
||||
ZTEdMBsGCSqGSIb3DQEJARYOaW5mb0BlLXdibS5jb20wWTATBgcqhkjOPQIBBggq
|
||||
hkjOPQMBBwNCAAQIfqHisi0oO/eyOqSaDrr9itG2IymBkHnSDGQIIYmT+vqA8AgO
|
||||
81momc2Ld5PGpEN6muE54wPHQjvc/yCih8u2o1UwUzASBgNVHRMBAf8ECDAGAQH/
|
||||
AgEAMB0GA1UdDgQWBBS3J/fxiAv22irdBs98SODhF7kU/jALBgNVHQ8EBAMCAQYw
|
||||
EQYJYIZIAYb4QgEBBAQDAgAHMAoGCCqGSM49BAMCA0kAMEYCIQDc41LFK4LJCBU2
|
||||
VVKIz7Z6sxPhUEkh8nLSLK6IXdkP5wIhAIeKVOZchaVO5aF7fbdXoSrcyy1YYeUe
|
||||
PLojcKI9fX84
|
||||
-----END CERTIFICATE-----
|
||||
42
data/web/inc/lib/WebAuthn/rootCertificates/yubico.pem
Executable file
42
data/web/inc/lib/WebAuthn/rootCertificates/yubico.pem
Executable file
|
|
@ -0,0 +1,42 @@
|
|||
Yubico U2F Device Attestation CA
|
||||
================================
|
||||
|
||||
Last Update: 2014-09-01
|
||||
|
||||
Yubico manufacturer U2F devices that contains device attestation
|
||||
certificates signed by a set of Yubico CAs. This file contains the CA
|
||||
certificates that Relying Parties (RP) need to configure their
|
||||
software with to be able to verify U2F device certificates.
|
||||
|
||||
This file has been signed with OpenPGP and you should verify the
|
||||
signature and the authenticity of the public key before trusting the
|
||||
content. The signature is located next to the file:
|
||||
|
||||
https://developers.yubico.com/u2f/yubico-u2f-ca-certs.txt
|
||||
https://developers.yubico.com/u2f/yubico-u2f-ca-certs.txt.sig
|
||||
|
||||
We will update this file from time to time when we publish more CA
|
||||
certificates.
|
||||
|
||||
Name: Yubico U2F Root CA Serial 457200631
|
||||
Issued: 2014-08-01
|
||||
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDHjCCAgagAwIBAgIEG0BT9zANBgkqhkiG9w0BAQsFADAuMSwwKgYDVQQDEyNZ
|
||||
dWJpY28gVTJGIFJvb3QgQ0EgU2VyaWFsIDQ1NzIwMDYzMTAgFw0xNDA4MDEwMDAw
|
||||
MDBaGA8yMDUwMDkwNDAwMDAwMFowLjEsMCoGA1UEAxMjWXViaWNvIFUyRiBSb290
|
||||
IENBIFNlcmlhbCA0NTcyMDA2MzEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
|
||||
AoIBAQC/jwYuhBVlqaiYWEMsrWFisgJ+PtM91eSrpI4TK7U53mwCIawSDHy8vUmk
|
||||
5N2KAj9abvT9NP5SMS1hQi3usxoYGonXQgfO6ZXyUA9a+KAkqdFnBnlyugSeCOep
|
||||
8EdZFfsaRFtMjkwz5Gcz2Py4vIYvCdMHPtwaz0bVuzneueIEz6TnQjE63Rdt2zbw
|
||||
nebwTG5ZybeWSwbzy+BJ34ZHcUhPAY89yJQXuE0IzMZFcEBbPNRbWECRKgjq//qT
|
||||
9nmDOFVlSRCt2wiqPSzluwn+v+suQEBsUjTGMEd25tKXXTkNW21wIWbxeSyUoTXw
|
||||
LvGS6xlwQSgNpk2qXYwf8iXg7VWZAgMBAAGjQjBAMB0GA1UdDgQWBBQgIvz0bNGJ
|
||||
hjgpToksyKpP9xv9oDAPBgNVHRMECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIBBjAN
|
||||
BgkqhkiG9w0BAQsFAAOCAQEAjvjuOMDSa+JXFCLyBKsycXtBVZsJ4Ue3LbaEsPY4
|
||||
MYN/hIQ5ZM5p7EjfcnMG4CtYkNsfNHc0AhBLdq45rnT87q/6O3vUEtNMafbhU6kt
|
||||
hX7Y+9XFN9NpmYxr+ekVY5xOxi8h9JDIgoMP4VB1uS0aunL1IGqrNooL9mmFnL2k
|
||||
LVVee6/VR6C5+KSTCMCWppMuJIZII2v9o4dkoZ8Y7QRjQlLfYzd3qGtKbw7xaF1U
|
||||
sG/5xUb/Btwb2X2g4InpiB/yt/3CpQXpiWX/K4mBvUKiGn05ZsqeY1gx4g0xLBqc
|
||||
U9psmyPzK+Vsgw2jeRQ5JlKDyqE0hebfC1tvFu0CCrJFcw==
|
||||
-----END CERTIFICATE-----
|
||||
500
data/web/inc/lib/Yubico.php
Executable file
500
data/web/inc/lib/Yubico.php
Executable file
|
|
@ -0,0 +1,500 @@
|
|||
<?php
|
||||
/**
|
||||
* Class for verifying Yubico One-Time-Passcodes
|
||||
*
|
||||
* @category Auth
|
||||
* @package Auth_Yubico
|
||||
* @author Simon Josefsson <simon@yubico.com>, Olov Danielson <olov@yubico.com>
|
||||
* @copyright 2007-2020 Yubico AB
|
||||
* @license https://opensource.org/licenses/bsd-license.php New BSD License
|
||||
* @version 2.0
|
||||
* @link https://www.yubico.com/
|
||||
*/
|
||||
|
||||
require_once 'PEAR.php';
|
||||
|
||||
/**
|
||||
* Class for verifying Yubico One-Time-Passcodes
|
||||
*
|
||||
* Simple example:
|
||||
* <code>
|
||||
* require_once 'Auth/Yubico.php';
|
||||
* $otp = "ccbbddeertkrctjkkcglfndnlihhnvekchkcctif";
|
||||
*
|
||||
* # Generate a new id+key from https://api.yubico.com/get-api-key/
|
||||
* $yubi = new Auth_Yubico('42', 'FOOBAR=');
|
||||
* $auth = $yubi->verify($otp);
|
||||
* if (PEAR::isError($auth)) {
|
||||
* print "<p>Authentication failed: " . $auth->getMessage();
|
||||
* print "<p>Debug output from server: " . $yubi->getLastResponse();
|
||||
* } else {
|
||||
* print "<p>You are authenticated!";
|
||||
* }
|
||||
* </code>
|
||||
*/
|
||||
class Auth_Yubico
|
||||
{
|
||||
/**#@+
|
||||
* @access private
|
||||
*/
|
||||
|
||||
/**
|
||||
* Yubico client ID
|
||||
* @var string
|
||||
*/
|
||||
var $_id;
|
||||
|
||||
/**
|
||||
* Yubico client key
|
||||
* @var string
|
||||
*/
|
||||
var $_key;
|
||||
|
||||
/**
|
||||
* List with URL part of validation servers
|
||||
* @var array
|
||||
*/
|
||||
var $_url_list;
|
||||
|
||||
/**
|
||||
* index to _url_list
|
||||
* @var int
|
||||
*/
|
||||
var $_url_index;
|
||||
|
||||
/**
|
||||
* Last query to server
|
||||
* @var string
|
||||
*/
|
||||
var $_lastquery;
|
||||
|
||||
/**
|
||||
* Response from server
|
||||
* @var string
|
||||
*/
|
||||
var $_response;
|
||||
|
||||
/**
|
||||
* Number of times we retried in our last validation
|
||||
* @var int
|
||||
*/
|
||||
var $_retries;
|
||||
|
||||
/**
|
||||
* Flag whether to verify HTTPS server certificates or not.
|
||||
* @var boolean
|
||||
*/
|
||||
var $_httpsverify;
|
||||
|
||||
/**
|
||||
* Maximum number of times we will retry transient HTTP errors
|
||||
* @var int
|
||||
*/
|
||||
var $_max_retries;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* Sets up the object
|
||||
* @param string $id The client identity
|
||||
* @param string $key The client MAC key (optional)
|
||||
* @param boolean $https noop
|
||||
* @param boolean $httpsverify Flag whether to use verify HTTPS
|
||||
* server certificates (optional,
|
||||
* default true)
|
||||
* @access public
|
||||
*/
|
||||
public function __construct($id, $key = '', $https = 0, $httpsverify = 1, $max_retries = 3)
|
||||
{
|
||||
$this->_id = $id;
|
||||
$this->_key = base64_decode($key);
|
||||
$this->_httpsverify = $httpsverify;
|
||||
$this->_max_retries = $max_retries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify to use a different URL part for verification.
|
||||
* The default is "https://api.yubico.com/wsapi/2.0/verify".
|
||||
*
|
||||
* @param string $url New server URL part to use
|
||||
* @access public
|
||||
* @deprecated
|
||||
*/
|
||||
function setURLpart($url)
|
||||
{
|
||||
$this->_url_list = array($url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get next URL part from list to use for validation.
|
||||
*
|
||||
* @return mixed string with URL part or false if no more URLs in list
|
||||
* @access public
|
||||
*/
|
||||
function getNextURLpart()
|
||||
{
|
||||
if ($this->_url_list) $url_list=$this->_url_list;
|
||||
else $url_list=array('https://api.yubico.com/wsapi/2.0/verify');
|
||||
|
||||
if ($this->_url_index>=count($url_list)) return false;
|
||||
else return $url_list[$this->_url_index++];
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets index to URL list
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
function URLreset()
|
||||
{
|
||||
$this->_url_index=0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add another URLpart.
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
function addURLpart($URLpart)
|
||||
{
|
||||
$this->_url_list[]=$URLpart;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the last query sent to the server, if any.
|
||||
*
|
||||
* @return string Request to server
|
||||
* @access public
|
||||
*/
|
||||
function getLastQuery()
|
||||
{
|
||||
return $this->_lastquery;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the last data received from the server, if any.
|
||||
*
|
||||
* @return string Output from server
|
||||
* @access public
|
||||
*/
|
||||
function getLastResponse()
|
||||
{
|
||||
return $this->_response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of retries that were used in the last validation
|
||||
*
|
||||
* @return int Number of retries
|
||||
* @access public
|
||||
*/
|
||||
function getRetries()
|
||||
{
|
||||
return $this->_retries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse input string into password, yubikey prefix,
|
||||
* ciphertext, and OTP.
|
||||
*
|
||||
* @param string Input string to parse
|
||||
* @param string Optional delimiter re-class, default is '[:]'
|
||||
* @return array Keyed array with fields
|
||||
* @access public
|
||||
*/
|
||||
function parsePasswordOTP($str, $delim = '[:]')
|
||||
{
|
||||
if (!preg_match("/^((.*)" . $delim . ")?" .
|
||||
"(([cbdefghijklnrtuv]{0,16})" .
|
||||
"([cbdefghijklnrtuv]{32}))$/i",
|
||||
$str, $matches)) {
|
||||
/* Dvorak? */
|
||||
if (!preg_match("/^((.*)" . $delim . ")?" .
|
||||
"(([jxe\.uidchtnbpygk]{0,16})" .
|
||||
"([jxe\.uidchtnbpygk]{32}))$/i",
|
||||
$str, $matches)) {
|
||||
return false;
|
||||
} else {
|
||||
$ret['otp'] = strtr($matches[3], "jxe.uidchtnbpygk", "cbdefghijklnrtuv");
|
||||
}
|
||||
} else {
|
||||
$ret['otp'] = $matches[3];
|
||||
}
|
||||
$ret['password'] = $matches[2];
|
||||
$ret['prefix'] = $matches[4];
|
||||
$ret['ciphertext'] = $matches[5];
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/* TODO? Add functions to get parsed parts of server response? */
|
||||
|
||||
/**
|
||||
* Parse parameters from last response
|
||||
*
|
||||
* example: getParameters("timestamp", "sessioncounter", "sessionuse");
|
||||
*
|
||||
* @param array @parameters Array with strings representing
|
||||
* parameters to parse
|
||||
* @return array parameter array from last response
|
||||
* @access public
|
||||
*/
|
||||
function getParameters($parameters)
|
||||
{
|
||||
if ($parameters == null) {
|
||||
$parameters = array('timestamp', 'sessioncounter', 'sessionuse');
|
||||
}
|
||||
$param_array = array();
|
||||
foreach ($parameters as $param) {
|
||||
if(!preg_match("/" . $param . "=([0-9]+)/", $this->_response, $out)) {
|
||||
return PEAR::raiseError('Could not parse parameter ' . $param . ' from response');
|
||||
}
|
||||
$param_array[$param]=$out[1];
|
||||
}
|
||||
return $param_array;
|
||||
}
|
||||
|
||||
function _make_curl_handle($query, $timeout=null)
|
||||
{
|
||||
flush();
|
||||
$handle = curl_init($query);
|
||||
curl_setopt($handle, CURLOPT_USERAGENT, "PEAR Auth_Yubico");
|
||||
curl_setopt($handle, CURLOPT_RETURNTRANSFER, 1);
|
||||
if (!$this->_httpsverify) {
|
||||
curl_setopt($handle, CURLOPT_SSL_VERIFYPEER, 0);
|
||||
curl_setopt($handle, CURLOPT_SSL_VERIFYHOST, 0);
|
||||
}
|
||||
curl_setopt($handle, CURLOPT_FAILONERROR, true);
|
||||
/* If timeout is set, we better apply it here as well
|
||||
* in case the validation server fails to follow it. */
|
||||
if ($timeout) {
|
||||
curl_setopt($handle, CURLOPT_TIMEOUT, $timeout);
|
||||
}
|
||||
|
||||
return $handle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify Yubico OTP against multiple URLs
|
||||
* Protocol specification 2.0 is used to construct validation requests
|
||||
*
|
||||
* @param string $token Yubico OTP
|
||||
* @param int $use_timestamp 1=>send request with ×tamp=1 to
|
||||
* get timestamp and session information
|
||||
* in the response
|
||||
* @param boolean $wait_for_all If true, wait until all
|
||||
* servers responds (for debugging)
|
||||
* @param string $sl Sync level in percentage between 0
|
||||
* and 100 or "fast" or "secure".
|
||||
* @param int $timeout Max number of seconds to wait
|
||||
* for responses
|
||||
* @param int $max_retries Max number of times we will retry on
|
||||
* transient errors.
|
||||
* @return mixed PEAR error on error, true otherwise
|
||||
* @access public
|
||||
*/
|
||||
function verify($token, $use_timestamp=null, $wait_for_all=False,
|
||||
$sl=null, $timeout=null, $max_retries=null)
|
||||
{
|
||||
/* If maximum retries is not set, default from instance */
|
||||
if (is_null($max_retries)) {
|
||||
$max_retries = $this->_max_retries;
|
||||
}
|
||||
|
||||
/* Construct parameters string */
|
||||
$ret = $this->parsePasswordOTP($token);
|
||||
if (!$ret) {
|
||||
return PEAR::raiseError('Could not parse Yubikey OTP');
|
||||
}
|
||||
$params = array('id'=>$this->_id,
|
||||
'otp'=>$ret['otp'],
|
||||
'nonce'=>md5(uniqid(rand())));
|
||||
/* Take care of protocol version 2 parameters */
|
||||
if ($use_timestamp) $params['timestamp'] = 1;
|
||||
if ($sl) $params['sl'] = $sl;
|
||||
if ($timeout) $params['timeout'] = $timeout;
|
||||
ksort($params);
|
||||
$parameters = '';
|
||||
foreach($params as $p=>$v) $parameters .= "&" . $p . "=" . $v;
|
||||
$parameters = ltrim($parameters, "&");
|
||||
|
||||
/* Generate signature. */
|
||||
if($this->_key <> "") {
|
||||
$signature = base64_encode(hash_hmac('sha1', $parameters,
|
||||
$this->_key, true));
|
||||
$signature = preg_replace('/\+/', '%2B', $signature);
|
||||
$parameters .= '&h=' . $signature;
|
||||
}
|
||||
|
||||
/* Generate and prepare request. */
|
||||
$this->_lastquery = null;
|
||||
$this->_retries = 0;
|
||||
$this->URLreset();
|
||||
|
||||
$mh = curl_multi_init();
|
||||
$ch = array();
|
||||
$retries = array();
|
||||
while($URLpart=$this->getNextURLpart())
|
||||
{
|
||||
$query = $URLpart . "?" . $parameters;
|
||||
|
||||
if ($this->_lastquery) { $this->_lastquery .= " "; }
|
||||
$this->_lastquery .= $query;
|
||||
|
||||
$handle = $this->_make_curl_handle($query, $timeout);
|
||||
curl_multi_add_handle($mh, $handle);
|
||||
|
||||
$ch[(int)$handle] = $handle;
|
||||
$retries[$query] = 0;
|
||||
}
|
||||
|
||||
/* Execute and read request. */
|
||||
$this->_response=null;
|
||||
$replay=False;
|
||||
$valid=False;
|
||||
do {
|
||||
/* Let curl do its work. */
|
||||
while (($mrc = curl_multi_exec($mh, $active))
|
||||
== CURLM_CALL_MULTI_PERFORM) {
|
||||
curl_multi_select($mh);
|
||||
}
|
||||
|
||||
while ($info = curl_multi_info_read($mh)) {
|
||||
$cinfo = curl_getinfo ($info['handle']);
|
||||
if ($info['result'] == CURLE_OK) {
|
||||
/* We have a complete response from one server. */
|
||||
|
||||
$str = curl_multi_getcontent($info['handle']);
|
||||
|
||||
if ($wait_for_all) { # Better debug info
|
||||
$this->_response .= 'URL=' . $cinfo['url'] . ' HTTP_CODE='
|
||||
. $cinfo['http_code'] . "\n"
|
||||
. $str . "\n";
|
||||
}
|
||||
|
||||
if (preg_match("/status=([a-zA-Z0-9_]+)/", $str, $out)) {
|
||||
$status = $out[1];
|
||||
|
||||
/*
|
||||
* There are 3 cases.
|
||||
*
|
||||
* 1. OTP or Nonce values doesn't match - ignore
|
||||
* response.
|
||||
*
|
||||
* 2. We have a HMAC key. If signature is invalid -
|
||||
* ignore response. Return if status=OK or
|
||||
* status=REPLAYED_OTP.
|
||||
*
|
||||
* 3. Return if status=OK or status=REPLAYED_OTP.
|
||||
*/
|
||||
if (!preg_match("/otp=".$params['otp']."/", $str) ||
|
||||
!preg_match("/nonce=".$params['nonce']."/", $str)) {
|
||||
/* Case 1. Ignore response. */
|
||||
}
|
||||
elseif ($this->_key <> "") {
|
||||
/* Case 2. Verify signature first */
|
||||
$rows = explode("\r\n", trim($str));
|
||||
$response=array();
|
||||
foreach ($rows as $key => $val) {
|
||||
/* = is also used in BASE64 encoding so we only replace the first = by # which is not used in BASE64 */
|
||||
$val = preg_replace('/=/', '#', $val, 1);
|
||||
$row = explode("#", $val);
|
||||
$response[$row[0]] = $row[1];
|
||||
}
|
||||
|
||||
$parameters=array('nonce','otp', 'sessioncounter', 'sessionuse', 'sl', 'status', 't', 'timeout', 'timestamp');
|
||||
sort($parameters);
|
||||
$check=Null;
|
||||
foreach ($parameters as $param) {
|
||||
if (array_key_exists($param, $response)) {
|
||||
if ($check) $check = $check . '&';
|
||||
$check = $check . $param . '=' . $response[$param];
|
||||
}
|
||||
}
|
||||
|
||||
$checksignature =
|
||||
base64_encode(hash_hmac('sha1', utf8_encode($check),
|
||||
$this->_key, true));
|
||||
|
||||
if($response['h'] == $checksignature) {
|
||||
if ($status == 'REPLAYED_OTP') {
|
||||
if (!$wait_for_all) { $this->_response = $str; }
|
||||
$replay=True;
|
||||
}
|
||||
if ($status == 'OK') {
|
||||
if (!$wait_for_all) { $this->_response = $str; }
|
||||
$valid=True;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* Case 3. We check the status directly */
|
||||
if ($status == 'REPLAYED_OTP') {
|
||||
if (!$wait_for_all) { $this->_response = $str; }
|
||||
$replay=True;
|
||||
}
|
||||
if ($status == 'OK') {
|
||||
if (!$wait_for_all) { $this->_response = $str; }
|
||||
$valid=True;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!$wait_for_all && ($valid || $replay))
|
||||
{
|
||||
/* We have status=OK or status=REPLAYED_OTP, return. */
|
||||
foreach ($ch as $h) {
|
||||
curl_multi_remove_handle($mh, $h);
|
||||
curl_close($h);
|
||||
}
|
||||
curl_multi_close($mh);
|
||||
if ($replay) return PEAR::raiseError('REPLAYED_OTP');
|
||||
if ($valid) return true;
|
||||
return PEAR::raiseError($status);
|
||||
}
|
||||
} else {
|
||||
/* Some kind of error, but def. not a 200 response */
|
||||
/* No status= in response body */
|
||||
$http_status_code = (int)$cinfo['http_code'];
|
||||
$query = $cinfo['url'];
|
||||
if ($http_status_code == 400 ||
|
||||
($http_status_code >= 500 && $http_status_code < 600)) {
|
||||
/* maybe retry */
|
||||
if ($retries[$query] < $max_retries) {
|
||||
$retries[$query]++; // for this server
|
||||
$this->_retries++; // for this validation attempt
|
||||
|
||||
$newhandle = $this->_make_curl_handle($query, $timeout);
|
||||
|
||||
curl_multi_add_handle($mh, $newhandle);
|
||||
$ch[(int)$newhandle] = $newhandle;
|
||||
|
||||
// Loop back up to curl_multi_exec, even if this
|
||||
// was the last handle and curl_multi_exec _was_
|
||||
// no longer active, it's active again now we've
|
||||
// added a retry.
|
||||
$active = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* Done with this handle */
|
||||
curl_multi_remove_handle($mh, $info['handle']);
|
||||
curl_close($info['handle']);
|
||||
unset ($ch[(int)$info['handle']]);
|
||||
}
|
||||
} while ($active);
|
||||
|
||||
/* Typically this is only reached for wait_for_all=true or
|
||||
* when the timeout is reached and there is no
|
||||
* OK/REPLAYED_REQUEST answer (think firewall).
|
||||
*/
|
||||
|
||||
foreach ($ch as $h) {
|
||||
curl_multi_remove_handle ($mh, $h);
|
||||
curl_close ($h);
|
||||
}
|
||||
curl_multi_close ($mh);
|
||||
|
||||
if ($replay) return PEAR::raiseError('REPLAYED_OTP');
|
||||
if ($valid) return true;
|
||||
return PEAR::raiseError('NO_VALID_ANSWER');
|
||||
}
|
||||
}
|
||||
?>
|
||||
14
data/web/inc/lib/array_merge_real.php
Executable file
14
data/web/inc/lib/array_merge_real.php
Executable file
|
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
|
||||
function array_merge_real()
|
||||
{
|
||||
$output = [];
|
||||
foreach (func_get_args() as $array) {
|
||||
foreach ($array as $key => $value) {
|
||||
$output[$key] = isset($output[$key]) ?
|
||||
array_merge($output[$key], $value) : $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
15
data/web/inc/lib/composer.json
Executable file
15
data/web/inc/lib/composer.json
Executable file
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"require": {
|
||||
"robthree/twofactorauth": "^1.6",
|
||||
"yubico/u2flib-server": "^1.0",
|
||||
"phpmailer/phpmailer": "^6.1",
|
||||
"php-mime-mail-parser/php-mime-mail-parser": "^7",
|
||||
"soundasleep/html2text": "^0.5.0",
|
||||
"ddeboer/imap": "^1.5",
|
||||
"matthiasmullie/minify": "^1.3",
|
||||
"bshaffer/oauth2-server-php": "^1.11",
|
||||
"mustangostang/spyc": "^0.6.3",
|
||||
"directorytree/ldaprecord": "^2.4",
|
||||
"twig/twig": "^3.0"
|
||||
}
|
||||
}
|
||||
1732
data/web/inc/lib/composer.lock
generated
Executable file
1732
data/web/inc/lib/composer.lock
generated
Executable file
File diff suppressed because it is too large
Load diff
7
data/web/inc/lib/sieve/SieveDumpable.php
Executable file
7
data/web/inc/lib/sieve/SieveDumpable.php
Executable file
|
|
@ -0,0 +1,7 @@
|
|||
<?php namespace Sieve;
|
||||
|
||||
interface SieveDumpable
|
||||
{
|
||||
function dump();
|
||||
function text();
|
||||
}
|
||||
47
data/web/inc/lib/sieve/SieveException.php
Executable file
47
data/web/inc/lib/sieve/SieveException.php
Executable file
|
|
@ -0,0 +1,47 @@
|
|||
<?php namespace Sieve;
|
||||
|
||||
require_once('SieveToken.php');
|
||||
|
||||
use Exception;
|
||||
|
||||
class SieveException extends Exception
|
||||
{
|
||||
protected $token_;
|
||||
|
||||
public function __construct(SieveToken $token, $arg)
|
||||
{
|
||||
$message = 'undefined sieve exception';
|
||||
$this->token_ = $token;
|
||||
|
||||
if (is_string($arg))
|
||||
{
|
||||
$message = $arg;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (is_array($arg))
|
||||
{
|
||||
$type = SieveToken::typeString(array_shift($arg));
|
||||
foreach($arg as $t)
|
||||
{
|
||||
$type .= ' or '. SieveToken::typeString($t);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$type = SieveToken::typeString($arg);
|
||||
}
|
||||
|
||||
$tokenType = SieveToken::typeString($token->type);
|
||||
$message = "$tokenType where $type expected near ". $token->text;
|
||||
}
|
||||
|
||||
parent::__construct('line '. $token->line .": $message");
|
||||
}
|
||||
|
||||
public function getLineNo()
|
||||
{
|
||||
return $this->token_->line;
|
||||
}
|
||||
|
||||
}
|
||||
233
data/web/inc/lib/sieve/SieveKeywordRegistry.php
Executable file
233
data/web/inc/lib/sieve/SieveKeywordRegistry.php
Executable file
|
|
@ -0,0 +1,233 @@
|
|||
<?php namespace Sieve;
|
||||
|
||||
class SieveKeywordRegistry
|
||||
{
|
||||
protected $registry_ = array();
|
||||
protected $matchTypes_ = array();
|
||||
protected $comparators_ = array();
|
||||
protected $addressParts_ = array();
|
||||
protected $commands_ = array();
|
||||
protected $tests_ = array();
|
||||
protected $arguments_ = array();
|
||||
|
||||
protected static $refcount = 0;
|
||||
protected static $instance = null;
|
||||
|
||||
protected function __construct()
|
||||
{
|
||||
$keywords = simplexml_load_file(dirname(__FILE__) .'/keywords.xml');
|
||||
foreach ($keywords->children() as $keyword)
|
||||
{
|
||||
switch ($keyword->getName())
|
||||
{
|
||||
case 'matchtype':
|
||||
$type =& $this->matchTypes_;
|
||||
break;
|
||||
case 'comparator':
|
||||
$type =& $this->comparators_;
|
||||
break;
|
||||
case 'addresspart':
|
||||
$type =& $this->addressParts_;
|
||||
break;
|
||||
case 'test':
|
||||
$type =& $this->tests_;
|
||||
break;
|
||||
case 'command':
|
||||
$type =& $this->commands_;
|
||||
break;
|
||||
default:
|
||||
trigger_error('Unsupported keyword type "'. $keyword->getName()
|
||||
. '" in file "keywords/'. basename($file) .'"');
|
||||
return;
|
||||
}
|
||||
|
||||
$name = (string) $keyword['name'];
|
||||
if (array_key_exists($name, $type))
|
||||
trigger_error("redefinition of $type $name - skipping");
|
||||
else
|
||||
$type[$name] = $keyword->children();
|
||||
}
|
||||
|
||||
foreach (glob(dirname(__FILE__) .'/extensions/*.xml') as $file)
|
||||
{
|
||||
$extension = simplexml_load_file($file);
|
||||
$name = (string) $extension['name'];
|
||||
|
||||
if (array_key_exists($name, $this->registry_))
|
||||
{
|
||||
trigger_error('overwriting extension "'. $name .'"');
|
||||
}
|
||||
$this->registry_[$name] = $extension;
|
||||
}
|
||||
}
|
||||
|
||||
public static function get()
|
||||
{
|
||||
if (self::$instance == null)
|
||||
{
|
||||
self::$instance = new SieveKeywordRegistry();
|
||||
}
|
||||
|
||||
self::$refcount++;
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
public function put()
|
||||
{
|
||||
if (--self::$refcount == 0)
|
||||
{
|
||||
self::$instance = null;
|
||||
}
|
||||
}
|
||||
|
||||
public function activate($extension)
|
||||
{
|
||||
if (!isset($this->registry_[$extension]))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$xml = $this->registry_[$extension];
|
||||
|
||||
foreach ($xml->children() as $e)
|
||||
{
|
||||
switch ($e->getName())
|
||||
{
|
||||
case 'matchtype':
|
||||
$type =& $this->matchTypes_;
|
||||
break;
|
||||
case 'comparator':
|
||||
$type =& $this->comparators_;
|
||||
break;
|
||||
case 'addresspart':
|
||||
$type =& $this->addressParts_;
|
||||
break;
|
||||
case 'test':
|
||||
$type =& $this->tests_;
|
||||
break;
|
||||
case 'command':
|
||||
$type =& $this->commands_;
|
||||
break;
|
||||
case 'tagged-argument':
|
||||
$xml = $e->parameter[0];
|
||||
$this->arguments_[(string) $xml['name']] = array(
|
||||
'extends' => (string) $e['extends'],
|
||||
'rules' => $xml
|
||||
);
|
||||
continue;
|
||||
default:
|
||||
trigger_error('Unsupported extension type \''.
|
||||
$e->getName() ."' in extension '$extension'");
|
||||
return;
|
||||
}
|
||||
|
||||
$name = (string) $e['name'];
|
||||
if (!isset($type[$name]) ||
|
||||
(string) $e['overrides'] == 'true')
|
||||
{
|
||||
$type[$name] = $e->children();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function isTest($name)
|
||||
{
|
||||
return (isset($this->tests_[$name]) ? true : false);
|
||||
}
|
||||
|
||||
public function isCommand($name)
|
||||
{
|
||||
return (isset($this->commands_[$name]) ? true : false);
|
||||
}
|
||||
|
||||
public function matchtype($name)
|
||||
{
|
||||
if (isset($this->matchTypes_[$name]))
|
||||
{
|
||||
return $this->matchTypes_[$name];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function addresspart($name)
|
||||
{
|
||||
if (isset($this->addressParts_[$name]))
|
||||
{
|
||||
return $this->addressParts_[$name];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function comparator($name)
|
||||
{
|
||||
if (isset($this->comparators_[$name]))
|
||||
{
|
||||
return $this->comparators_[$name];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function test($name)
|
||||
{
|
||||
if (isset($this->tests_[$name]))
|
||||
{
|
||||
return $this->tests_[$name];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function command($name)
|
||||
{
|
||||
if (isset($this->commands_[$name]))
|
||||
{
|
||||
return $this->commands_[$name];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function arguments($command)
|
||||
{
|
||||
$res = array();
|
||||
foreach ($this->arguments_ as $arg)
|
||||
{
|
||||
if (preg_match('/'.$arg['extends'].'/', $command))
|
||||
array_push($res, $arg['rules']);
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
|
||||
public function argument($name)
|
||||
{
|
||||
if (isset($this->arguments_[$name]))
|
||||
{
|
||||
return $this->arguments_[$name]['rules'];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function requireStrings()
|
||||
{
|
||||
return array_keys($this->registry_);
|
||||
}
|
||||
public function matchTypes()
|
||||
{
|
||||
return array_keys($this->matchTypes_);
|
||||
}
|
||||
public function comparators()
|
||||
{
|
||||
return array_keys($this->comparators_);
|
||||
}
|
||||
public function addressParts()
|
||||
{
|
||||
return array_keys($this->addressParts_);
|
||||
}
|
||||
public function tests()
|
||||
{
|
||||
return array_keys($this->tests_);
|
||||
}
|
||||
public function commands()
|
||||
{
|
||||
return array_keys($this->commands_);
|
||||
}
|
||||
}
|
||||
255
data/web/inc/lib/sieve/SieveParser.php
Executable file
255
data/web/inc/lib/sieve/SieveParser.php
Executable file
|
|
@ -0,0 +1,255 @@
|
|||
<?php namespace Sieve;
|
||||
|
||||
include_once 'SieveTree.php';
|
||||
include_once 'SieveScanner.php';
|
||||
include_once 'SieveSemantics.php';
|
||||
include_once 'SieveException.php';
|
||||
|
||||
class SieveParser
|
||||
{
|
||||
protected $scanner_;
|
||||
protected $script_;
|
||||
protected $tree_;
|
||||
protected $status_;
|
||||
|
||||
public function __construct($script = null)
|
||||
{
|
||||
if (isset($script))
|
||||
$this->parse($script);
|
||||
}
|
||||
|
||||
public function GetParseTree()
|
||||
{
|
||||
return $this->tree_;
|
||||
}
|
||||
|
||||
public function dumpParseTree()
|
||||
{
|
||||
return $this->tree_->dump();
|
||||
}
|
||||
|
||||
public function getScriptText()
|
||||
{
|
||||
return $this->tree_->getText();
|
||||
}
|
||||
|
||||
protected function getPrevToken_($parent_id)
|
||||
{
|
||||
$childs = $this->tree_->getChilds($parent_id);
|
||||
|
||||
for ($i = count($childs); $i > 0; --$i)
|
||||
{
|
||||
$prev = $this->tree_->getNode($childs[$i-1]);
|
||||
if ($prev->is(SieveToken::Comment|SieveToken::Whitespace))
|
||||
continue;
|
||||
|
||||
// use command owning a block or list instead of previous
|
||||
if ($prev->is(SieveToken::BlockStart|SieveToken::Comma|SieveToken::LeftParenthesis))
|
||||
$prev = $this->tree_->getNode($parent_id);
|
||||
|
||||
return $prev;
|
||||
}
|
||||
|
||||
return $this->tree_->getNode($parent_id);
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* methods for recursive descent start below
|
||||
*/
|
||||
public function passthroughWhitespaceComment($token)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function passthroughFunction($token)
|
||||
{
|
||||
$this->tree_->addChild($token);
|
||||
}
|
||||
|
||||
public function parse($script)
|
||||
{
|
||||
$this->script_ = $script;
|
||||
|
||||
$this->scanner_ = new SieveScanner($this->script_);
|
||||
|
||||
// Define what happens with passthrough tokens like whitespacs and comments
|
||||
$this->scanner_->setPassthroughFunc(
|
||||
array(
|
||||
$this, 'passthroughWhitespaceComment'
|
||||
)
|
||||
);
|
||||
|
||||
$this->tree_ = new SieveTree('tree');
|
||||
|
||||
$this->commands_($this->tree_->getRoot());
|
||||
|
||||
if (!$this->scanner_->nextTokenIs(SieveToken::ScriptEnd)) {
|
||||
$token = $this->scanner_->nextToken();
|
||||
throw new SieveException($token, SieveToken::ScriptEnd);
|
||||
}
|
||||
}
|
||||
|
||||
protected function commands_($parent_id)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
if (!$this->scanner_->nextTokenIs(SieveToken::Identifier))
|
||||
break;
|
||||
|
||||
// Get and check a command token
|
||||
$token = $this->scanner_->nextToken();
|
||||
$semantics = new SieveSemantics($token, $this->getPrevToken_($parent_id));
|
||||
|
||||
// Process eventual arguments
|
||||
$this_node = $this->tree_->addChildTo($parent_id, $token);
|
||||
$this->arguments_($this_node, $semantics);
|
||||
|
||||
$token = $this->scanner_->nextToken();
|
||||
if (!$token->is(SieveToken::Semicolon))
|
||||
{
|
||||
// TODO: check if/when semcheck is needed here
|
||||
$semantics->validateToken($token);
|
||||
|
||||
if ($token->is(SieveToken::BlockStart))
|
||||
{
|
||||
$this->tree_->addChildTo($this_node, $token);
|
||||
$this->block_($this_node, $semantics);
|
||||
continue;
|
||||
}
|
||||
|
||||
throw new SieveException($token, SieveToken::Semicolon);
|
||||
}
|
||||
|
||||
$semantics->done($token);
|
||||
$this->tree_->addChildTo($this_node, $token);
|
||||
}
|
||||
}
|
||||
|
||||
protected function arguments_($parent_id, &$semantics)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
if ($this->scanner_->nextTokenIs(SieveToken::Number|SieveToken::Tag))
|
||||
{
|
||||
// Check if semantics allow a number or tag
|
||||
$token = $this->scanner_->nextToken();
|
||||
$semantics->validateToken($token);
|
||||
$this->tree_->addChildTo($parent_id, $token);
|
||||
}
|
||||
else if ($this->scanner_->nextTokenIs(SieveToken::StringList))
|
||||
{
|
||||
$this->stringlist_($parent_id, $semantics);
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->scanner_->nextTokenIs(SieveToken::TestList))
|
||||
{
|
||||
$this->testlist_($parent_id, $semantics);
|
||||
}
|
||||
}
|
||||
|
||||
protected function stringlist_($parent_id, &$semantics)
|
||||
{
|
||||
if (!$this->scanner_->nextTokenIs(SieveToken::LeftBracket))
|
||||
{
|
||||
$this->string_($parent_id, $semantics);
|
||||
return;
|
||||
}
|
||||
|
||||
$token = $this->scanner_->nextToken();
|
||||
$semantics->startStringList($token);
|
||||
$this->tree_->addChildTo($parent_id, $token);
|
||||
|
||||
if($this->scanner_->nextTokenIs(SieveToken::RightBracket)) {
|
||||
//allow empty lists
|
||||
$token = $this->scanner_->nextToken();
|
||||
$this->tree_->addChildTo($parent_id, $token);
|
||||
$semantics->endStringList();
|
||||
return;
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
$this->string_($parent_id, $semantics);
|
||||
$token = $this->scanner_->nextToken();
|
||||
|
||||
if (!$token->is(SieveToken::Comma|SieveToken::RightBracket))
|
||||
throw new SieveException($token, array(SieveToken::Comma, SieveToken::RightBracket));
|
||||
|
||||
if ($token->is(SieveToken::Comma))
|
||||
$semantics->continueStringList();
|
||||
|
||||
$this->tree_->addChildTo($parent_id, $token);
|
||||
}
|
||||
while (!$token->is(SieveToken::RightBracket));
|
||||
|
||||
$semantics->endStringList();
|
||||
}
|
||||
|
||||
protected function string_($parent_id, &$semantics)
|
||||
{
|
||||
$token = $this->scanner_->nextToken();
|
||||
$semantics->validateToken($token);
|
||||
$this->tree_->addChildTo($parent_id, $token);
|
||||
}
|
||||
|
||||
protected function testlist_($parent_id, &$semantics)
|
||||
{
|
||||
if (!$this->scanner_->nextTokenIs(SieveToken::LeftParenthesis))
|
||||
{
|
||||
$this->test_($parent_id, $semantics);
|
||||
return;
|
||||
}
|
||||
|
||||
$token = $this->scanner_->nextToken();
|
||||
$semantics->validateToken($token);
|
||||
$this->tree_->addChildTo($parent_id, $token);
|
||||
|
||||
do
|
||||
{
|
||||
$this->test_($parent_id, $semantics);
|
||||
|
||||
$token = $this->scanner_->nextToken();
|
||||
if (!$token->is(SieveToken::Comma|SieveToken::RightParenthesis))
|
||||
{
|
||||
throw new SieveException($token, array(SieveToken::Comma, SieveToken::RightParenthesis));
|
||||
}
|
||||
$this->tree_->addChildTo($parent_id, $token);
|
||||
}
|
||||
while (!$token->is(SieveToken::RightParenthesis));
|
||||
}
|
||||
|
||||
protected function test_($parent_id, &$semantics)
|
||||
{
|
||||
// Check if semantics allow an identifier
|
||||
$token = $this->scanner_->nextToken();
|
||||
$semantics->validateToken($token);
|
||||
|
||||
// Get semantics for this test command
|
||||
$this_semantics = new SieveSemantics($token, $this->getPrevToken_($parent_id));
|
||||
$this_node = $this->tree_->addChildTo($parent_id, $token);
|
||||
|
||||
// Consume eventual argument tokens
|
||||
$this->arguments_($this_node, $this_semantics);
|
||||
|
||||
// Check that all required arguments were there
|
||||
$token = $this->scanner_->peekNextToken();
|
||||
$this_semantics->done($token);
|
||||
}
|
||||
|
||||
protected function block_($parent_id, &$semantics)
|
||||
{
|
||||
$this->commands_($parent_id, $semantics);
|
||||
|
||||
$token = $this->scanner_->nextToken();
|
||||
if (!$token->is(SieveToken::BlockEnd))
|
||||
{
|
||||
throw new SieveException($token, SieveToken::BlockEnd);
|
||||
}
|
||||
$this->tree_->addChildTo($parent_id, $token);
|
||||
}
|
||||
}
|
||||
145
data/web/inc/lib/sieve/SieveScanner.php
Executable file
145
data/web/inc/lib/sieve/SieveScanner.php
Executable file
|
|
@ -0,0 +1,145 @@
|
|||
<?php namespace Sieve;
|
||||
|
||||
include_once('SieveToken.php');
|
||||
|
||||
class SieveScanner
|
||||
{
|
||||
public function __construct(&$script)
|
||||
{
|
||||
if ($script === null)
|
||||
return;
|
||||
|
||||
$this->tokenize($script);
|
||||
}
|
||||
|
||||
public function setPassthroughFunc($callback)
|
||||
{
|
||||
if ($callback == null || is_callable($callback))
|
||||
$this->ptFn_ = $callback;
|
||||
}
|
||||
|
||||
public function tokenize(&$script)
|
||||
{
|
||||
$pos = 0;
|
||||
$line = 1;
|
||||
|
||||
$scriptLength = mb_strlen($script);
|
||||
|
||||
$unprocessedScript = $script;
|
||||
|
||||
|
||||
//create one regex to find the right match
|
||||
//avoids looping over all possible tokens: increases performance
|
||||
$nameToType = [];
|
||||
$regex = [];
|
||||
// chr(65) == 'A'
|
||||
$i = 65;
|
||||
|
||||
foreach ($this->tokenMatch_ as $type => $subregex) {
|
||||
$nameToType[chr($i)] = $type;
|
||||
$regex[] = "(?P<". chr($i) . ">^$subregex)";
|
||||
$i++;
|
||||
}
|
||||
|
||||
$regex = '/' . join('|', $regex) . '/';
|
||||
|
||||
while ($pos < $scriptLength)
|
||||
{
|
||||
if (preg_match($regex, $unprocessedScript, $match)) {
|
||||
|
||||
// only keep the group that match and we only want matches with group names
|
||||
// we can use the group name to find the token type using nameToType
|
||||
$filterMatch = array_filter(array_filter($match), 'is_string', ARRAY_FILTER_USE_KEY);
|
||||
|
||||
// the first element in filterMatch will contain the matched group and the key will be the name
|
||||
$type = $nameToType[key($filterMatch)];
|
||||
$currentMatch = current($filterMatch);
|
||||
|
||||
//create the token
|
||||
$token = new SieveToken($type, $currentMatch, $line);
|
||||
$this->tokens_[] = $token;
|
||||
|
||||
if ($type == SieveToken::Unknown)
|
||||
return;
|
||||
|
||||
// just remove the part that we parsed: don't extract the new substring using script length
|
||||
// as mb_strlen is \theta(pos) (it's linear in the position)
|
||||
$matchLength = mb_strlen($currentMatch);
|
||||
$unprocessedScript = mb_substr($unprocessedScript, $matchLength);
|
||||
|
||||
$pos += $matchLength;
|
||||
$line += mb_substr_count($currentMatch, "\n");
|
||||
} else {
|
||||
$this->tokens_[] = new SieveToken(SieveToken::Unknown, '', $line);
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$this->tokens_[] = new SieveToken(SieveToken::ScriptEnd, '', $line);
|
||||
}
|
||||
|
||||
public function nextTokenIs($type)
|
||||
{
|
||||
return $this->peekNextToken()->is($type);
|
||||
}
|
||||
|
||||
public function peekNextToken()
|
||||
{
|
||||
$offset = 0;
|
||||
do {
|
||||
$next = $this->tokens_[$this->tokenPos_ + $offset++];
|
||||
} while ($next->is(SieveToken::Comment|SieveToken::Whitespace));
|
||||
|
||||
return $next;
|
||||
}
|
||||
|
||||
public function nextToken()
|
||||
{
|
||||
$token = $this->tokens_[$this->tokenPos_++];
|
||||
|
||||
while ($token->is(SieveToken::Comment|SieveToken::Whitespace))
|
||||
{
|
||||
if ($this->ptFn_ != null)
|
||||
call_user_func($this->ptFn_, $token);
|
||||
|
||||
$token = $this->tokens_[$this->tokenPos_++];
|
||||
}
|
||||
|
||||
return $token;
|
||||
}
|
||||
|
||||
protected $ptFn_ = null;
|
||||
protected $tokenPos_ = 0;
|
||||
protected $tokens_ = array();
|
||||
protected $tokenMatch_ = array (
|
||||
SieveToken::LeftBracket => '\[',
|
||||
SieveToken::RightBracket => '\]',
|
||||
SieveToken::BlockStart => '\{',
|
||||
SieveToken::BlockEnd => '\}',
|
||||
SieveToken::LeftParenthesis => '\(',
|
||||
SieveToken::RightParenthesis => '\)',
|
||||
SieveToken::Comma => ',',
|
||||
SieveToken::Semicolon => ';',
|
||||
SieveToken::Whitespace => '[ \r\n\t]+',
|
||||
SieveToken::Tag => ':[[:alpha:]_][[:alnum:]_]*(?=\b)',
|
||||
/*
|
||||
" # match a quotation mark
|
||||
( # start matching parts that include an escaped quotation mark
|
||||
([^"]*[^"\\\\]) # match a string without quotation marks and not ending with a backlash
|
||||
? # this also includes the empty string
|
||||
(\\\\\\\\)* # match any groups of even number of backslashes
|
||||
# (thus the character after these groups are not escaped)
|
||||
\\\\" # match an escaped quotation mark
|
||||
)* # accept any number of strings that end with an escaped quotation mark
|
||||
[^"]* # accept any trailing part that does not contain any quotation marks
|
||||
" # end of the quoted string
|
||||
*/
|
||||
SieveToken::QuotedString => '"(([^"]*[^"\\\\])?(\\\\\\\\)*\\\\")*[^"]*"',
|
||||
SieveToken::Number => '[[:digit:]]+(?:[KMG])?(?=\b)',
|
||||
SieveToken::Comment => '(?:\/\*(?:[^\*]|\*(?=[^\/]))*\*\/|#[^\r\n]*\r?(\n|$))',
|
||||
SieveToken::MultilineString => 'text:[ \t]*(?:#[^\r\n]*)?\r?\n(\.[^\r\n]+\r?\n|[^\.][^\r\n]*\r?\n)*\.\r?(\n|$)',
|
||||
SieveToken::Identifier => '[[:alpha:]_][[:alnum:]_]*(?=\b)',
|
||||
SieveToken::Unknown => '[^ \r\n\t]+'
|
||||
);
|
||||
}
|
||||
6
data/web/inc/lib/sieve/SieveScript.php
Executable file
6
data/web/inc/lib/sieve/SieveScript.php
Executable file
|
|
@ -0,0 +1,6 @@
|
|||
<?php namespace Sieve;
|
||||
|
||||
class SieveScript
|
||||
{
|
||||
// TODO: implement
|
||||
}
|
||||
611
data/web/inc/lib/sieve/SieveSemantics.php
Executable file
611
data/web/inc/lib/sieve/SieveSemantics.php
Executable file
|
|
@ -0,0 +1,611 @@
|
|||
<?php namespace Sieve;
|
||||
|
||||
require_once('SieveKeywordRegistry.php');
|
||||
require_once('SieveToken.php');
|
||||
require_once('SieveException.php');
|
||||
|
||||
class SieveSemantics
|
||||
{
|
||||
protected static $requiredExtensions_ = array();
|
||||
|
||||
protected $comparator_;
|
||||
protected $matchType_;
|
||||
protected $addressPart_;
|
||||
protected $tags_ = array();
|
||||
protected $arguments_;
|
||||
protected $deps_ = array();
|
||||
protected $followupToken_;
|
||||
|
||||
public function __construct($token, $prevToken)
|
||||
{
|
||||
$this->registry_ = SieveKeywordRegistry::get();
|
||||
$command = strtolower($token->text);
|
||||
|
||||
// Check the registry for $command
|
||||
if ($this->registry_->isCommand($command))
|
||||
{
|
||||
$xml = $this->registry_->command($command);
|
||||
$this->arguments_ = $this->makeArguments_($xml);
|
||||
$this->followupToken_ = SieveToken::Semicolon;
|
||||
}
|
||||
else if ($this->registry_->isTest($command))
|
||||
{
|
||||
$xml = $this->registry_->test($command);
|
||||
$this->arguments_ = $this->makeArguments_($xml);
|
||||
$this->followupToken_ = SieveToken::BlockStart;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new SieveException($token, 'unknown command '. $command);
|
||||
}
|
||||
|
||||
// Check if command may appear at this position within the script
|
||||
if ($this->registry_->isTest($command))
|
||||
{
|
||||
if (is_null($prevToken))
|
||||
throw new SieveException($token, $command .' may not appear as first command');
|
||||
|
||||
if (!preg_match('/^(if|elsif|anyof|allof|not)$/i', $prevToken->text))
|
||||
throw new SieveException($token, $command .' may not appear after '. $prevToken->text);
|
||||
}
|
||||
else if (isset($prevToken))
|
||||
{
|
||||
switch ($command)
|
||||
{
|
||||
case 'require':
|
||||
$valid_after = 'require';
|
||||
break;
|
||||
case 'elsif':
|
||||
case 'else':
|
||||
$valid_after = '(if|elsif)';
|
||||
break;
|
||||
default:
|
||||
$valid_after = $this->commandsRegex_();
|
||||
}
|
||||
|
||||
if (!preg_match('/^'. $valid_after .'$/i', $prevToken->text))
|
||||
throw new SieveException($token, $command .' may not appear after '. $prevToken->text);
|
||||
}
|
||||
|
||||
// Check for extension arguments to add to the command
|
||||
foreach ($this->registry_->arguments($command) as $arg)
|
||||
{
|
||||
switch ((string) $arg['type'])
|
||||
{
|
||||
case 'tag':
|
||||
array_unshift($this->arguments_, array(
|
||||
'type' => SieveToken::Tag,
|
||||
'occurrence' => $this->occurrence_($arg),
|
||||
'regex' => $this->regex_($arg),
|
||||
'call' => 'tagHook_',
|
||||
'name' => $this->name_($arg),
|
||||
'subArgs' => $this->makeArguments_($arg->children())
|
||||
));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
$this->registry_->put();
|
||||
}
|
||||
|
||||
// TODO: the *Regex functions could possibly also be static properties
|
||||
protected function requireStringsRegex_()
|
||||
{
|
||||
return '('. implode('|', $this->registry_->requireStrings()) .')';
|
||||
}
|
||||
|
||||
protected function matchTypeRegex_()
|
||||
{
|
||||
return '('. implode('|', $this->registry_->matchTypes()) .')';
|
||||
}
|
||||
|
||||
protected function addressPartRegex_()
|
||||
{
|
||||
return '('. implode('|', $this->registry_->addressParts()) .')';
|
||||
}
|
||||
|
||||
protected function commandsRegex_()
|
||||
{
|
||||
return '('. implode('|', $this->registry_->commands()) .')';
|
||||
}
|
||||
|
||||
protected function testsRegex_()
|
||||
{
|
||||
return '('. implode('|', $this->registry_->tests()) .')';
|
||||
}
|
||||
|
||||
protected function comparatorRegex_()
|
||||
{
|
||||
return '('. implode('|', $this->registry_->comparators()) .')';
|
||||
}
|
||||
|
||||
protected function occurrence_($arg)
|
||||
{
|
||||
if (isset($arg['occurrence']))
|
||||
{
|
||||
switch ((string) $arg['occurrence'])
|
||||
{
|
||||
case 'optional':
|
||||
return '?';
|
||||
case 'any':
|
||||
return '*';
|
||||
case 'some':
|
||||
return '+';
|
||||
}
|
||||
}
|
||||
return '1';
|
||||
}
|
||||
|
||||
protected function name_($arg)
|
||||
{
|
||||
if (isset($arg['name']))
|
||||
{
|
||||
return (string) $arg['name'];
|
||||
}
|
||||
return (string) $arg['type'];
|
||||
}
|
||||
|
||||
protected function regex_($arg)
|
||||
{
|
||||
if (isset($arg['regex']))
|
||||
{
|
||||
return (string) $arg['regex'];
|
||||
}
|
||||
return '.*';
|
||||
}
|
||||
|
||||
protected function case_($arg)
|
||||
{
|
||||
if (isset($arg['case']))
|
||||
{
|
||||
return (string) $arg['case'];
|
||||
}
|
||||
return 'adhere';
|
||||
}
|
||||
|
||||
protected function follows_($arg)
|
||||
{
|
||||
if (isset($arg['follows']))
|
||||
{
|
||||
return (string) $arg['follows'];
|
||||
}
|
||||
return '.*';
|
||||
}
|
||||
|
||||
protected function makeValue_($arg)
|
||||
{
|
||||
if (isset($arg->value))
|
||||
{
|
||||
$res = $this->makeArguments_($arg->value);
|
||||
return array_shift($res);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an extension (test) commands parameters from XML to
|
||||
* a PHP array the {@see Semantics} class understands.
|
||||
* @param array(SimpleXMLElement) $parameters
|
||||
* @return array
|
||||
*/
|
||||
protected function makeArguments_($parameters)
|
||||
{
|
||||
$arguments = array();
|
||||
|
||||
foreach ($parameters as $arg)
|
||||
{
|
||||
// Ignore anything not a <parameter>
|
||||
if ($arg->getName() != 'parameter')
|
||||
continue;
|
||||
|
||||
switch ((string) $arg['type'])
|
||||
{
|
||||
case 'addresspart':
|
||||
array_push($arguments, array(
|
||||
'type' => SieveToken::Tag,
|
||||
'occurrence' => $this->occurrence_($arg),
|
||||
'regex' => $this->addressPartRegex_(),
|
||||
'call' => 'addressPartHook_',
|
||||
'name' => 'address part',
|
||||
'subArgs' => $this->makeArguments_($arg)
|
||||
));
|
||||
break;
|
||||
|
||||
case 'block':
|
||||
array_push($arguments, array(
|
||||
'type' => SieveToken::BlockStart,
|
||||
'occurrence' => '1',
|
||||
'regex' => '{',
|
||||
'name' => 'block',
|
||||
'subArgs' => $this->makeArguments_($arg)
|
||||
));
|
||||
break;
|
||||
|
||||
case 'comparator':
|
||||
array_push($arguments, array(
|
||||
'type' => SieveToken::Tag,
|
||||
'occurrence' => $this->occurrence_($arg),
|
||||
'regex' => 'comparator',
|
||||
'name' => 'comparator',
|
||||
'subArgs' => array( array(
|
||||
'type' => SieveToken::String,
|
||||
'occurrence' => '1',
|
||||
'call' => 'comparatorHook_',
|
||||
'case' => 'adhere',
|
||||
'regex' => $this->comparatorRegex_(),
|
||||
'name' => 'comparator string',
|
||||
'follows' => 'comparator'
|
||||
))
|
||||
));
|
||||
break;
|
||||
|
||||
case 'matchtype':
|
||||
array_push($arguments, array(
|
||||
'type' => SieveToken::Tag,
|
||||
'occurrence' => $this->occurrence_($arg),
|
||||
'regex' => $this->matchTypeRegex_(),
|
||||
'call' => 'matchTypeHook_',
|
||||
'name' => 'match type',
|
||||
'subArgs' => $this->makeArguments_($arg)
|
||||
));
|
||||
break;
|
||||
|
||||
case 'number':
|
||||
array_push($arguments, array(
|
||||
'type' => SieveToken::Number,
|
||||
'occurrence' => $this->occurrence_($arg),
|
||||
'regex' => $this->regex_($arg),
|
||||
'name' => $this->name_($arg),
|
||||
'follows' => $this->follows_($arg)
|
||||
));
|
||||
break;
|
||||
|
||||
case 'requirestrings':
|
||||
array_push($arguments, array(
|
||||
'type' => SieveToken::StringList,
|
||||
'occurrence' => $this->occurrence_($arg),
|
||||
'call' => 'setRequire_',
|
||||
'case' => 'adhere',
|
||||
'regex' => $this->requireStringsRegex_(),
|
||||
'name' => $this->name_($arg)
|
||||
));
|
||||
break;
|
||||
|
||||
case 'string':
|
||||
array_push($arguments, array(
|
||||
'type' => SieveToken::String,
|
||||
'occurrence' => $this->occurrence_($arg),
|
||||
'regex' => $this->regex_($arg),
|
||||
'case' => $this->case_($arg),
|
||||
'name' => $this->name_($arg),
|
||||
'follows' => $this->follows_($arg)
|
||||
));
|
||||
break;
|
||||
|
||||
case 'stringlist':
|
||||
array_push($arguments, array(
|
||||
'type' => SieveToken::StringList,
|
||||
'occurrence' => $this->occurrence_($arg),
|
||||
'regex' => $this->regex_($arg),
|
||||
'case' => $this->case_($arg),
|
||||
'name' => $this->name_($arg),
|
||||
'follows' => $this->follows_($arg)
|
||||
));
|
||||
break;
|
||||
|
||||
case 'tag':
|
||||
array_push($arguments, array(
|
||||
'type' => SieveToken::Tag,
|
||||
'occurrence' => $this->occurrence_($arg),
|
||||
'regex' => $this->regex_($arg),
|
||||
'call' => 'tagHook_',
|
||||
'name' => $this->name_($arg),
|
||||
'subArgs' => $this->makeArguments_($arg->children()),
|
||||
'follows' => $this->follows_($arg)
|
||||
));
|
||||
break;
|
||||
|
||||
case 'test':
|
||||
array_push($arguments, array(
|
||||
'type' => SieveToken::Identifier,
|
||||
'occurrence' => $this->occurrence_($arg),
|
||||
'regex' => $this->testsRegex_(),
|
||||
'name' => $this->name_($arg),
|
||||
'subArgs' => $this->makeArguments_($arg->children())
|
||||
));
|
||||
break;
|
||||
|
||||
case 'testlist':
|
||||
array_push($arguments, array(
|
||||
'type' => SieveToken::LeftParenthesis,
|
||||
'occurrence' => '1',
|
||||
'regex' => '\(',
|
||||
'name' => $this->name_($arg),
|
||||
'subArgs' => null
|
||||
));
|
||||
array_push($arguments, array(
|
||||
'type' => SieveToken::Identifier,
|
||||
'occurrence' => '+',
|
||||
'regex' => $this->testsRegex_(),
|
||||
'name' => $this->name_($arg),
|
||||
'subArgs' => $this->makeArguments_($arg->children())
|
||||
));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $arguments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add argument(s) expected / allowed to appear next.
|
||||
* @param array $value
|
||||
*/
|
||||
protected function addArguments_($identifier, $subArgs)
|
||||
{
|
||||
for ($i = count($subArgs); $i > 0; $i--)
|
||||
{
|
||||
$arg = $subArgs[$i-1];
|
||||
if (preg_match('/^'. $arg['follows'] .'$/si', $identifier))
|
||||
array_unshift($this->arguments_, $arg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add dependency that is expected to be fullfilled when parsing
|
||||
* of the current command is {@see done}.
|
||||
* @param array $dependency
|
||||
*/
|
||||
protected function addDependency_($type, $name, $dependencies)
|
||||
{
|
||||
foreach ($dependencies as $d)
|
||||
{
|
||||
array_push($this->deps_, array(
|
||||
'o_type' => $type,
|
||||
'o_name' => $name,
|
||||
'type' => $d['type'],
|
||||
'name' => $d['name'],
|
||||
'regex' => $d['regex']
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
protected function invoke_($token, $func, $arg = array())
|
||||
{
|
||||
if (!is_array($arg))
|
||||
$arg = array($arg);
|
||||
|
||||
$err = call_user_func_array(array(&$this, $func), $arg);
|
||||
|
||||
if ($err)
|
||||
throw new SieveException($token, $err);
|
||||
}
|
||||
|
||||
protected function setRequire_($extension)
|
||||
{
|
||||
array_push(self::$requiredExtensions_, $extension);
|
||||
$this->registry_->activate($extension);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook function that is called after a address part match was found
|
||||
* in a command. The kind of address part is remembered in case it's
|
||||
* needed later {@see done}. For address parts from a extension
|
||||
* dependency information and valid values are looked up as well.
|
||||
* @param string $addresspart
|
||||
*/
|
||||
protected function addressPartHook_($addresspart)
|
||||
{
|
||||
$this->addressPart_ = $addresspart;
|
||||
$xml = $this->registry_->addresspart($this->addressPart_);
|
||||
|
||||
if (isset($xml))
|
||||
{
|
||||
// Add possible value and dependancy
|
||||
$this->addArguments_($this->addressPart_, $this->makeArguments_($xml));
|
||||
$this->addDependency_('address part', $this->addressPart_, $xml->requires);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook function that is called after a match type was found in a
|
||||
* command. The kind of match type is remembered in case it's
|
||||
* needed later {@see done}. For a match type from extensions
|
||||
* dependency information and valid values are looked up as well.
|
||||
* @param string $matchtype
|
||||
*/
|
||||
protected function matchTypeHook_($matchtype)
|
||||
{
|
||||
$this->matchType_ = $matchtype;
|
||||
$xml = $this->registry_->matchtype($this->matchType_);
|
||||
|
||||
if (isset($xml))
|
||||
{
|
||||
// Add possible value and dependancy
|
||||
$this->addArguments_($this->matchType_, $this->makeArguments_($xml));
|
||||
$this->addDependency_('match type', $this->matchType_, $xml->requires);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook function that is called after a comparator was found in
|
||||
* a command. The comparator is remembered in case it's needed for
|
||||
* comparsion later {@see done}. For a comparator from extensions
|
||||
* dependency information is looked up as well.
|
||||
* @param string $comparator
|
||||
*/
|
||||
protected function comparatorHook_($comparator)
|
||||
{
|
||||
$this->comparator_ = $comparator;
|
||||
$xml = $this->registry_->comparator($this->comparator_);
|
||||
|
||||
if (isset($xml))
|
||||
{
|
||||
// Add possible dependancy
|
||||
$this->addDependency_('comparator', $this->comparator_, $xml->requires);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook function that is called after a tag was found in
|
||||
* a command. The tag is remembered in case it's needed for
|
||||
* comparsion later {@see done}. For a tags from extensions
|
||||
* dependency information is looked up as well.
|
||||
* @param string $tag
|
||||
*/
|
||||
protected function tagHook_($tag)
|
||||
{
|
||||
array_push($this->tags_, $tag);
|
||||
$xml = $this->registry_->argument($tag);
|
||||
|
||||
// Add possible dependancies
|
||||
if (isset($xml))
|
||||
$this->addDependency_('tag', $tag, $xml->requires);
|
||||
}
|
||||
|
||||
protected function validType_($token)
|
||||
{
|
||||
foreach ($this->arguments_ as $arg)
|
||||
{
|
||||
if ($arg['occurrence'] == '0')
|
||||
{
|
||||
array_shift($this->arguments_);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($token->is($arg['type']))
|
||||
return;
|
||||
|
||||
// Is the argument required
|
||||
if ($arg['occurrence'] != '?' && $arg['occurrence'] != '*')
|
||||
throw new SieveException($token, $arg['type']);
|
||||
|
||||
array_shift($this->arguments_);
|
||||
}
|
||||
|
||||
// Check if command expects any (more) arguments
|
||||
if (empty($this->arguments_))
|
||||
throw new SieveException($token, $this->followupToken_);
|
||||
|
||||
throw new SieveException($token, 'unexpected '. SieveToken::typeString($token->type) .' '. $token->text);
|
||||
}
|
||||
|
||||
public function startStringList($token)
|
||||
{
|
||||
$this->validType_($token);
|
||||
$this->arguments_[0]['type'] = SieveToken::String;
|
||||
$this->arguments_[0]['occurrence'] = '+';
|
||||
}
|
||||
|
||||
public function continueStringList()
|
||||
{
|
||||
$this->arguments_[0]['occurrence'] = '+';
|
||||
}
|
||||
|
||||
public function endStringList()
|
||||
{
|
||||
array_shift($this->arguments_);
|
||||
}
|
||||
|
||||
public function validateToken($token)
|
||||
{
|
||||
// Make sure the argument has a valid type
|
||||
$this->validType_($token);
|
||||
|
||||
foreach ($this->arguments_ as &$arg)
|
||||
{
|
||||
// Build regular expression according to argument type
|
||||
switch ($arg['type'])
|
||||
{
|
||||
case SieveToken::String:
|
||||
case SieveToken::StringList:
|
||||
$regex = '/^(?:text:[^\n]*\n(?P<one>'. $arg['regex'] .')\.\r?\n?|"(?P<two>'. $arg['regex'] .')")$/'
|
||||
. ($arg['case'] == 'ignore' ? 'si' : 's');
|
||||
break;
|
||||
case SieveToken::Tag:
|
||||
$regex = '/^:(?P<one>'. $arg['regex'] .')$/si';
|
||||
break;
|
||||
default:
|
||||
$regex = '/^(?P<one>'. $arg['regex'] .')$/si';
|
||||
}
|
||||
|
||||
if (preg_match($regex, $token->text, $match))
|
||||
{
|
||||
$text = ($match['one'] ? $match['one'] : $match['two']);
|
||||
|
||||
// Add argument(s) that may now appear after this one
|
||||
if (isset($arg['subArgs']))
|
||||
$this->addArguments_($text, $arg['subArgs']);
|
||||
|
||||
// Call extra processing function if defined
|
||||
if (isset($arg['call']))
|
||||
$this->invoke_($token, $arg['call'], $text);
|
||||
|
||||
// Check if a possible value of this argument may occur
|
||||
if ($arg['occurrence'] == '?' || $arg['occurrence'] == '1')
|
||||
{
|
||||
$arg['occurrence'] = '0';
|
||||
}
|
||||
else if ($arg['occurrence'] == '+')
|
||||
{
|
||||
$arg['occurrence'] = '*';
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($token->is($arg['type']) && $arg['occurrence'] == 1)
|
||||
{
|
||||
throw new SieveException($token,
|
||||
SieveToken::typeString($token->type) ." $token->text where ". $arg['name'] .' expected');
|
||||
}
|
||||
}
|
||||
|
||||
throw new SieveException($token, 'unexpected '. SieveToken::typeString($token->type) .' '. $token->text);
|
||||
}
|
||||
|
||||
public function done($token)
|
||||
{
|
||||
// Check if there are required arguments left
|
||||
foreach ($this->arguments_ as $arg)
|
||||
{
|
||||
if ($arg['occurrence'] == '+' || $arg['occurrence'] == '1')
|
||||
throw new SieveException($token, $arg['type']);
|
||||
}
|
||||
|
||||
// Check if the command depends on use of a certain tag
|
||||
foreach ($this->deps_ as $d)
|
||||
{
|
||||
switch ($d['type'])
|
||||
{
|
||||
case 'addresspart':
|
||||
$values = array($this->addressPart_);
|
||||
break;
|
||||
|
||||
case 'matchtype':
|
||||
$values = array($this->matchType_);
|
||||
break;
|
||||
|
||||
case 'comparator':
|
||||
$values = array($this->comparator_);
|
||||
break;
|
||||
|
||||
case 'tag':
|
||||
$values = $this->tags_;
|
||||
break;
|
||||
}
|
||||
|
||||
foreach ($values as $value)
|
||||
{
|
||||
if (preg_match('/^'. $d['regex'] .'$/mi', $value))
|
||||
break 2;
|
||||
}
|
||||
|
||||
throw new SieveException($token,
|
||||
$d['o_type'] .' '. $d['o_name'] .' requires use of '. $d['type'] .' '. $d['name']);
|
||||
}
|
||||
}
|
||||
}
|
||||
88
data/web/inc/lib/sieve/SieveToken.php
Executable file
88
data/web/inc/lib/sieve/SieveToken.php
Executable file
|
|
@ -0,0 +1,88 @@
|
|||
<?php namespace Sieve;
|
||||
|
||||
include_once('SieveDumpable.php');
|
||||
|
||||
class SieveToken implements SieveDumpable
|
||||
{
|
||||
const Unknown = 0x0000;
|
||||
const ScriptEnd = 0x0001;
|
||||
const LeftBracket = 0x0002;
|
||||
const RightBracket = 0x0004;
|
||||
const BlockStart = 0x0008;
|
||||
const BlockEnd = 0x0010;
|
||||
const LeftParenthesis = 0x0020;
|
||||
const RightParenthesis = 0x0040;
|
||||
const Comma = 0x0080;
|
||||
const Semicolon = 0x0100;
|
||||
const Whitespace = 0x0200;
|
||||
const Tag = 0x0400;
|
||||
const QuotedString = 0x0800;
|
||||
const Number = 0x1000;
|
||||
const Comment = 0x2000;
|
||||
const MultilineString = 0x4000;
|
||||
const Identifier = 0x8000;
|
||||
|
||||
const String = 0x4800; // Quoted | Multiline
|
||||
const StringList = 0x4802; // Quoted | Multiline | LeftBracket
|
||||
const StringListSep = 0x0084; // Comma | RightBracket
|
||||
const Unparsed = 0x2200; // Comment | Whitespace
|
||||
const TestList = 0x8020; // Identifier | LeftParenthesis
|
||||
|
||||
public $type;
|
||||
public $text;
|
||||
public $line;
|
||||
|
||||
public function __construct($type, $text, $line)
|
||||
{
|
||||
$this->text = $text;
|
||||
$this->type = $type;
|
||||
$this->line = intval($line);
|
||||
}
|
||||
|
||||
public function dump()
|
||||
{
|
||||
return '<'. SieveToken::escape($this->text) .'> type:'. SieveToken::typeString($this->type) .' line:'. $this->line;
|
||||
}
|
||||
|
||||
public function text()
|
||||
{
|
||||
return $this->text;
|
||||
}
|
||||
|
||||
public function is($type)
|
||||
{
|
||||
return (bool)($this->type & $type);
|
||||
}
|
||||
|
||||
public static function typeString($type)
|
||||
{
|
||||
switch ($type)
|
||||
{
|
||||
case SieveToken::Identifier: return 'identifier';
|
||||
case SieveToken::Whitespace: return 'whitespace';
|
||||
case SieveToken::QuotedString: return 'quoted string';
|
||||
case SieveToken::Tag: return 'tag';
|
||||
case SieveToken::Semicolon: return 'semicolon';
|
||||
case SieveToken::LeftBracket: return 'left bracket';
|
||||
case SieveToken::RightBracket: return 'right bracket';
|
||||
case SieveToken::BlockStart: return 'block start';
|
||||
case SieveToken::BlockEnd: return 'block end';
|
||||
case SieveToken::LeftParenthesis: return 'left parenthesis';
|
||||
case SieveToken::RightParenthesis: return 'right parenthesis';
|
||||
case SieveToken::Comma: return 'comma';
|
||||
case SieveToken::Number: return 'number';
|
||||
case SieveToken::Comment: return 'comment';
|
||||
case SieveToken::MultilineString: return 'multiline string';
|
||||
case SieveToken::ScriptEnd: return 'script end';
|
||||
case SieveToken::String: return 'string';
|
||||
case SieveToken::StringList: return 'string list';
|
||||
default: return 'unknown token';
|
||||
}
|
||||
}
|
||||
|
||||
protected static $tr_ = array("\r" => '\r', "\n" => '\n', "\t" => '\t');
|
||||
public static function escape($val)
|
||||
{
|
||||
return strtr($val, self::$tr_);
|
||||
}
|
||||
}
|
||||
117
data/web/inc/lib/sieve/SieveTree.php
Executable file
117
data/web/inc/lib/sieve/SieveTree.php
Executable file
|
|
@ -0,0 +1,117 @@
|
|||
<?php namespace Sieve;
|
||||
|
||||
class SieveTree
|
||||
{
|
||||
protected $childs_;
|
||||
protected $parents_;
|
||||
protected $nodes_;
|
||||
protected $max_id_;
|
||||
protected $dump_;
|
||||
|
||||
public function __construct($name = 'tree')
|
||||
{
|
||||
$this->childs_ = array();
|
||||
$this->parents_ = array();
|
||||
$this->nodes_ = array();
|
||||
$this->max_id_ = 0;
|
||||
|
||||
$this->parents_[0] = null;
|
||||
$this->nodes_[0] = $name;
|
||||
}
|
||||
|
||||
public function addChild(SieveDumpable $child)
|
||||
{
|
||||
return $this->addChildTo($this->max_id_, $child);
|
||||
}
|
||||
|
||||
public function addChildTo($parent_id, SieveDumpable $child)
|
||||
{
|
||||
if (!is_int($parent_id)
|
||||
|| !isset($this->nodes_[$parent_id]))
|
||||
return null;
|
||||
|
||||
if (!isset($this->childs_[$parent_id]))
|
||||
$this->childs_[$parent_id] = array();
|
||||
|
||||
$child_id = ++$this->max_id_;
|
||||
$this->nodes_[$child_id] = $child;
|
||||
$this->parents_[$child_id] = $parent_id;
|
||||
array_push($this->childs_[$parent_id], $child_id);
|
||||
|
||||
return $child_id;
|
||||
}
|
||||
|
||||
public function getRoot()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function getChilds($node_id)
|
||||
{
|
||||
if (!is_int($node_id)
|
||||
|| !isset($this->nodes_[$node_id]))
|
||||
return null;
|
||||
|
||||
if (!isset($this->childs_[$node_id]))
|
||||
return array();
|
||||
|
||||
return $this->childs_[$node_id];
|
||||
}
|
||||
|
||||
public function getNode($node_id)
|
||||
{
|
||||
if ($node_id == 0 || !is_int($node_id)
|
||||
|| !isset($this->nodes_[$node_id]))
|
||||
return null;
|
||||
|
||||
return $this->nodes_[$node_id];
|
||||
}
|
||||
|
||||
public function dump()
|
||||
{
|
||||
$this->dump_ = $this->nodes_[$this->getRoot()] ."\n";
|
||||
$this->dumpChilds_($this->getRoot(), ' ');
|
||||
return $this->dump_;
|
||||
}
|
||||
|
||||
protected function dumpChilds_($parent_id, $prefix)
|
||||
{
|
||||
if (!isset($this->childs_[$parent_id]))
|
||||
return;
|
||||
|
||||
$childs = $this->childs_[$parent_id];
|
||||
$last_child = count($childs);
|
||||
|
||||
for ($i=1; $i <= $last_child; ++$i)
|
||||
{
|
||||
$child_node = $this->nodes_[$childs[$i-1]];
|
||||
$infix = ($i == $last_child ? '`--- ' : '|--- ');
|
||||
$this->dump_ .= $prefix . $infix . $child_node->dump() . " (id:" . $childs[$i-1] . ")\n";
|
||||
|
||||
$next_prefix = $prefix . ($i == $last_child ? ' ' : '| ');
|
||||
$this->dumpChilds_($childs[$i-1], $next_prefix);
|
||||
}
|
||||
}
|
||||
|
||||
public function getText()
|
||||
{
|
||||
$this->dump_ = '';
|
||||
$this->childText_($this->getRoot());
|
||||
return $this->dump_;
|
||||
}
|
||||
|
||||
protected function childText_($parent_id)
|
||||
{
|
||||
if (!isset($this->childs_[$parent_id]))
|
||||
return;
|
||||
|
||||
$childs = $this->childs_[$parent_id];
|
||||
|
||||
for ($i = 0; $i < count($childs); ++$i)
|
||||
{
|
||||
$child_node = $this->nodes_[$childs[$i]];
|
||||
$this->dump_ .= $child_node->text();
|
||||
$this->childText_($childs[$i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
14
data/web/inc/lib/sieve/extensions/body.xml
Executable file
14
data/web/inc/lib/sieve/extensions/body.xml
Executable file
|
|
@ -0,0 +1,14 @@
|
|||
<?xml version='1.0' standalone='yes'?>
|
||||
|
||||
<extension name="body">
|
||||
|
||||
<test name="body">
|
||||
<parameter type="matchtype" occurrence="optional" />
|
||||
<parameter type="comparator" occurrence="optional" />
|
||||
<parameter type="tag" name="body transform" regex="(raw|content|text)" occurrence="optional">
|
||||
<parameter type="stringlist" name="content types" follows="content" />
|
||||
</parameter>
|
||||
<parameter type="stringlist" name="key list" />
|
||||
</test>
|
||||
|
||||
</extension>
|
||||
7
data/web/inc/lib/sieve/extensions/comparator-ascii-numeric.xml
Executable file
7
data/web/inc/lib/sieve/extensions/comparator-ascii-numeric.xml
Executable file
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version='1.0' standalone='yes'?>
|
||||
|
||||
<extension name="comparator-i;ascii-numeric">
|
||||
|
||||
<comparator name="i;ascii-numeric" />
|
||||
|
||||
</extension>
|
||||
9
data/web/inc/lib/sieve/extensions/copy.xml
Executable file
9
data/web/inc/lib/sieve/extensions/copy.xml
Executable file
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version='1.0' standalone='yes'?>
|
||||
|
||||
<extension name="copy">
|
||||
|
||||
<tagged-argument extends="(fileinto|redirect)">
|
||||
<parameter type="tag" name="copy" regex="copy" occurrence="optional" />
|
||||
</tagged-argument>
|
||||
|
||||
</extension>
|
||||
28
data/web/inc/lib/sieve/extensions/date.xml
Executable file
28
data/web/inc/lib/sieve/extensions/date.xml
Executable file
|
|
@ -0,0 +1,28 @@
|
|||
<?xml version='1.0' standalone='yes'?>
|
||||
|
||||
<extension name="date">
|
||||
|
||||
<test name="date">
|
||||
<parameter type="matchtype" occurrence="optional" />
|
||||
<parameter type="comparator" occurrence="optional" />
|
||||
<parameter type="tag" name="zone" regex="(zone|originalzone)" occurrence="optional">
|
||||
<parameter type="string" name="time-zone" follows="zone" />
|
||||
</parameter>
|
||||
<parameter type="string" name="header-name" />
|
||||
<parameter type="string" case="ignore" name="date-part"
|
||||
regex="(year|month|day|date|julian|hour|minute|second|time|iso8601|std11|zone|weekday)" />
|
||||
<parameter type="stringlist" name="key-list" />
|
||||
</test>
|
||||
|
||||
<test name="currentdate">
|
||||
<parameter type="matchtype" occurrence="optional" />
|
||||
<parameter type="comparator" occurrence="optional" />
|
||||
<parameter type="tag" name="zone" regex="zone" occurrence="optional">
|
||||
<parameter type="string" name="time-zone" />
|
||||
</parameter>
|
||||
<parameter type="string" case="ignore" name="date-part"
|
||||
regex="(year|month|day|date|julian|hour|minute|second|time|iso8601|std11|zone|weekday)" />
|
||||
<parameter type="stringlist" name="key-list" />
|
||||
</test>
|
||||
|
||||
</extension>
|
||||
9
data/web/inc/lib/sieve/extensions/duplicate.xml
Executable file
9
data/web/inc/lib/sieve/extensions/duplicate.xml
Executable file
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version='1.0' standalone='yes'?>
|
||||
|
||||
<extension name="duplicate">
|
||||
|
||||
<test name="duplicate">
|
||||
|
||||
</test>
|
||||
|
||||
</extension>
|
||||
22
data/web/inc/lib/sieve/extensions/editheader.xml
Executable file
22
data/web/inc/lib/sieve/extensions/editheader.xml
Executable file
|
|
@ -0,0 +1,22 @@
|
|||
<?xml version='1.0' standalone='yes'?>
|
||||
|
||||
<extension name="editheader">
|
||||
|
||||
<command name="addheader">
|
||||
<parameter type="tag" name="last" regex="last" occurrence="optional" />
|
||||
<parameter type="string" name="field name" />
|
||||
<parameter type="string" name="value" />
|
||||
</command>
|
||||
|
||||
<command name="deleteheader">
|
||||
<parameter type="tag" name="index" regex="index" occurrence="optional">
|
||||
<parameter type="number" name="field number" />
|
||||
<parameter type="tag" name="last" regex="last" occurrence="optional" />
|
||||
</parameter>
|
||||
<parameter type="matchtype" occurrence="optional" />
|
||||
<parameter type="comparator" occurrence="optional" />
|
||||
<parameter type="string" name="field name" />
|
||||
<parameter type="stringlist" name="value patterns" occurrence="optional" />
|
||||
</command>
|
||||
|
||||
</extension>
|
||||
33
data/web/inc/lib/sieve/extensions/enotify.xml
Executable file
33
data/web/inc/lib/sieve/extensions/enotify.xml
Executable file
|
|
@ -0,0 +1,33 @@
|
|||
<?xml version='1.0' standalone='yes'?>
|
||||
|
||||
<extension name="enotify">
|
||||
<command name="notify">
|
||||
<parameter type="tag" name="from" occurrence="optional">
|
||||
<parameter type="string" name="from-address" />
|
||||
</parameter>
|
||||
|
||||
<parameter type="tag" name="importance" regex="(1|2|3)" occurrence="optional" />
|
||||
|
||||
<parameter type="tag" name="options" occurrence="optional">
|
||||
<parameter type="stringlist" name="option-strings" />
|
||||
</parameter>
|
||||
|
||||
<parameter type="tag" name="message" occurrence="optional">
|
||||
<parameter type="string" name="message-text" />
|
||||
</parameter>
|
||||
|
||||
<parameter type="string" name="method" />
|
||||
</command>
|
||||
|
||||
<test name="valid_notify_method">
|
||||
<parameter type="stringlist" name="notification-uris" />
|
||||
</test>
|
||||
|
||||
<test name="notify_method_capability">
|
||||
<parameter type="string" name="notification-uri" />
|
||||
<parameter type="string" name="notification-capability" />
|
||||
<parameter type="stringlist" name="key-list" />
|
||||
</test>
|
||||
|
||||
<modifier name="encodeurl" />
|
||||
</extension>
|
||||
13
data/web/inc/lib/sieve/extensions/envelope.xml
Executable file
13
data/web/inc/lib/sieve/extensions/envelope.xml
Executable file
|
|
@ -0,0 +1,13 @@
|
|||
<?xml version='1.0' standalone='yes'?>
|
||||
|
||||
<extension name="envelope">
|
||||
|
||||
<test name="envelope">
|
||||
<parameter type="matchtype" occurrence="optional" />
|
||||
<parameter type="comparator" occurrence="optional" />
|
||||
<parameter type="addresspart" occurrence="optional" />
|
||||
<parameter type="stringlist" name="envelope-part" />
|
||||
<parameter type="stringlist" name="key" />
|
||||
</test>
|
||||
|
||||
</extension>
|
||||
13
data/web/inc/lib/sieve/extensions/environment.xml
Executable file
13
data/web/inc/lib/sieve/extensions/environment.xml
Executable file
|
|
@ -0,0 +1,13 @@
|
|||
<?xml version='1.0' standalone='yes'?>
|
||||
|
||||
<extension name="environment">
|
||||
|
||||
<test name="environment">
|
||||
<parameter type="matchtype" occurrence="optional" />
|
||||
<parameter type="comparator" occurrence="optional" />
|
||||
<parameter type="string" name="name"
|
||||
regex="(domain|host|location|name|phase|remote-host|remote-ip|version|vnd\..+)" />
|
||||
<parameter type="stringlist" name="key-list" />
|
||||
</test>
|
||||
|
||||
</extension>
|
||||
11
data/web/inc/lib/sieve/extensions/ereject.xml
Executable file
11
data/web/inc/lib/sieve/extensions/ereject.xml
Executable file
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version='1.0' standalone='yes'?>
|
||||
|
||||
<extension name="ereject">
|
||||
|
||||
<command name="ereject">
|
||||
|
||||
<parameter type="string" name="reason" />
|
||||
|
||||
</command>
|
||||
|
||||
</extension>
|
||||
10
data/web/inc/lib/sieve/extensions/fileinto.xml
Executable file
10
data/web/inc/lib/sieve/extensions/fileinto.xml
Executable file
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version='1.0' standalone='yes'?>
|
||||
|
||||
<extension name="fileinto">
|
||||
|
||||
<command name="fileinto">
|
||||
<parameter type="tag" name="create" regex="create" occurrence="optional" />
|
||||
<parameter type="string" name="folder" />
|
||||
</command>
|
||||
|
||||
</extension>
|
||||
29
data/web/inc/lib/sieve/extensions/imap4flags.xml
Executable file
29
data/web/inc/lib/sieve/extensions/imap4flags.xml
Executable file
|
|
@ -0,0 +1,29 @@
|
|||
<?xml version='1.0' standalone='yes'?>
|
||||
|
||||
<extension name="imap4flags">
|
||||
|
||||
<command name="setflag">
|
||||
<parameter type="stringlist" name="flag list" />
|
||||
</command>
|
||||
|
||||
<command name="addflag">
|
||||
<parameter type="stringlist" name="flag list" />
|
||||
</command>
|
||||
|
||||
<command name="removeflag">
|
||||
<parameter type="stringlist" name="flag list" />
|
||||
</command>
|
||||
|
||||
<test name="hasflag">
|
||||
<parameter type="matchtype" occurrence="optional" />
|
||||
<parameter type="comparator" occurrence="optional" />
|
||||
<parameter type="stringlist" name="flag list" />
|
||||
</test>
|
||||
|
||||
<tagged-argument extends="(fileinto|keep)">
|
||||
<parameter type="tag" name="flags" regex="flags" occurrence="optional">
|
||||
<parameter type="stringlist" name="flag list" />
|
||||
</parameter>
|
||||
</tagged-argument>
|
||||
|
||||
</extension>
|
||||
21
data/web/inc/lib/sieve/extensions/imapflags.xml
Executable file
21
data/web/inc/lib/sieve/extensions/imapflags.xml
Executable file
|
|
@ -0,0 +1,21 @@
|
|||
<?xml version='1.0' standalone='yes'?>
|
||||
|
||||
<extension name="imapflags">
|
||||
|
||||
<command name="mark" />
|
||||
|
||||
<command name="unmark" />
|
||||
|
||||
<command name="setflag">
|
||||
<parameter type="stringlist" name="flag list" />
|
||||
</command>
|
||||
|
||||
<command name="addflag">
|
||||
<parameter type="stringlist" name="flag list" />
|
||||
</command>
|
||||
|
||||
<command name="removeflag">
|
||||
<parameter type="stringlist" name="flag list" />
|
||||
</command>
|
||||
|
||||
</extension>
|
||||
17
data/web/inc/lib/sieve/extensions/index.xml
Executable file
17
data/web/inc/lib/sieve/extensions/index.xml
Executable file
|
|
@ -0,0 +1,17 @@
|
|||
<?xml version='1.0' standalone='yes'?>
|
||||
|
||||
<extension name="index">
|
||||
|
||||
<tagged-argument extends="(header|address|date)">
|
||||
<parameter type="tag" name="index" regex="index" occurrence="optional">
|
||||
<parameter type="number" name="field number" />
|
||||
</parameter>
|
||||
</tagged-argument>
|
||||
|
||||
<tagged-argument extends="(header|address|date)">
|
||||
<parameter type="tag" name="last" regex="last" occurrence="optional">
|
||||
<requires type="tag" name="index" regex="index" />
|
||||
</parameter>
|
||||
</tagged-argument>
|
||||
|
||||
</extension>
|
||||
8
data/web/inc/lib/sieve/extensions/mailbox.xml
Executable file
8
data/web/inc/lib/sieve/extensions/mailbox.xml
Executable file
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version='1.0' standalone='yes'?>
|
||||
|
||||
<extension name="mailbox">
|
||||
|
||||
<test name="mailboxexists">
|
||||
<parameter type="string" name="folder" />
|
||||
</test>
|
||||
</extension>
|
||||
58
data/web/inc/lib/sieve/extensions/mime.xml
Executable file
58
data/web/inc/lib/sieve/extensions/mime.xml
Executable file
|
|
@ -0,0 +1,58 @@
|
|||
<?xml version='1.0' standalone='yes'?>
|
||||
|
||||
<extension name="mime">
|
||||
<command name="foreverypart">
|
||||
<parameter type="string" name="name" occurrence="optional" />
|
||||
<block />
|
||||
</command>
|
||||
|
||||
<command name="break">
|
||||
<parameter type="string" name="name" occurrence="optional" />
|
||||
</command>
|
||||
|
||||
<tagged-argument extends="(header|address|exists)">
|
||||
<parameter type="tag" name="mime" regex="mime" occurrence="optional" />
|
||||
</tagged-argument>
|
||||
<tagged-argument extends="(header|address|exists)">
|
||||
<parameter type="tag" name="anychild" regex="anychild" occurrence="optional" />
|
||||
</tagged-argument>
|
||||
<tagged-argument extends="(header)">
|
||||
<parameter type="tag" name="type" occurrence="optional" />
|
||||
</tagged-argument>
|
||||
<tagged-argument extends="(header)">
|
||||
<parameter type="tag" name="subtype" occurrence="optional" />
|
||||
</tagged-argument>
|
||||
<tagged-argument extends="(header)">
|
||||
<parameter type="tag" name="contenttype" occurrence="optional" />
|
||||
</tagged-argument>
|
||||
<tagged-argument extends="(header)">
|
||||
<parameter type="tag" name="param" regex="param" occurrence="optional">
|
||||
<parameter type="stringlist" name="param-list" />
|
||||
</parameter>
|
||||
</tagged-argument>
|
||||
<tagged-argument extends="(header|address|exists)">
|
||||
<parameter type="stringlist" name="header-names" />
|
||||
</tagged-argument>
|
||||
<tagged-argument extends="(header)">
|
||||
<parameter type="stringlist" name="key-list" />
|
||||
</tagged-argument>
|
||||
|
||||
<action name="replace">
|
||||
<parameter type="tag" name="mime" regex="mime" occurrence="optional" />
|
||||
<parameter type="string" name="subject" occurrence="optional" />
|
||||
<parameter type="string" name="from" occurrence="optional" />
|
||||
<parameter type="string" name="replacement" />
|
||||
</action>
|
||||
|
||||
<action name="enclose">
|
||||
<parameter type="string" name="subject" occurrence="optional" />
|
||||
<parameter type="stringlist" name="headers" occurrence="optional" />
|
||||
<parameter type="string" name="text" />
|
||||
</action>
|
||||
|
||||
<action name="extracttext">
|
||||
<parameter type="tag" name="first" regex="first" occurrence="optional" />
|
||||
<parameter type="number" name="number" occurrence="optional" />
|
||||
<parameter type="string" name="varname" />
|
||||
</action>
|
||||
</extension>
|
||||
29
data/web/inc/lib/sieve/extensions/notify.xml
Executable file
29
data/web/inc/lib/sieve/extensions/notify.xml
Executable file
|
|
@ -0,0 +1,29 @@
|
|||
<?xml version='1.0' standalone='yes'?>
|
||||
|
||||
<extension name="notify">
|
||||
|
||||
<command name="notify">
|
||||
<parameter type="tag" name="method" regex="method" occurrence="optional">
|
||||
<parameter type="string" name="method-name" />
|
||||
</parameter>
|
||||
|
||||
<parameter type="tag" name="id" regex="id" occurrence="optional">
|
||||
<parameter type="string" name="message-id" />
|
||||
</parameter>
|
||||
|
||||
<parameter type="tag" name="priority" regex="(low|normal|high)" occurrence="optional" />
|
||||
|
||||
<parameter type="tag" name="message" regex="message" occurrence="optional">
|
||||
<parameter type="string" name="message-text" />
|
||||
</parameter>
|
||||
</command>
|
||||
|
||||
<command name="denotify">
|
||||
<parameter type="matchtype" occurrence="optional">
|
||||
<parameter type="string" name="message-id" />
|
||||
</parameter>
|
||||
|
||||
<parameter type="tag" name="priority" regex="(low|normal|high)" occurrence="optional" />
|
||||
</command>
|
||||
|
||||
</extension>
|
||||
11
data/web/inc/lib/sieve/extensions/regex.xml
Executable file
11
data/web/inc/lib/sieve/extensions/regex.xml
Executable file
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version='1.0' standalone='yes'?>
|
||||
|
||||
<extension name="regex">
|
||||
|
||||
<matchtype name="regex" />
|
||||
|
||||
<tagged-argument extends="set">
|
||||
<parameter type="tag" name="modifier" regex="quoteregex" occurrence="optional" />
|
||||
</tagged-argument>
|
||||
|
||||
</extension>
|
||||
11
data/web/inc/lib/sieve/extensions/reject.xml
Executable file
11
data/web/inc/lib/sieve/extensions/reject.xml
Executable file
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version='1.0' standalone='yes'?>
|
||||
|
||||
<extension name="reject">
|
||||
|
||||
<command name="reject">
|
||||
|
||||
<parameter type="string" name="reason" />
|
||||
|
||||
</command>
|
||||
|
||||
</extension>
|
||||
14
data/web/inc/lib/sieve/extensions/relational.xml
Executable file
14
data/web/inc/lib/sieve/extensions/relational.xml
Executable file
|
|
@ -0,0 +1,14 @@
|
|||
<?xml version='1.0' standalone='yes'?>
|
||||
|
||||
<extension name="relational">
|
||||
|
||||
<matchtype name="count">
|
||||
<requires type="comparator" name="i;ascii-numeric" regex="i;ascii-numeric" />
|
||||
<parameter type="string" name="relation string" regex="(lt|le|eq|ge|gt|ne)" />
|
||||
</matchtype>
|
||||
|
||||
<matchtype name="value">
|
||||
<parameter type="string" name="relation string" regex="(lt|le|eq|ge|gt|ne)" />
|
||||
</matchtype>
|
||||
|
||||
</extension>
|
||||
11
data/web/inc/lib/sieve/extensions/spamtest.xml
Executable file
11
data/web/inc/lib/sieve/extensions/spamtest.xml
Executable file
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version='1.0' standalone='yes'?>
|
||||
|
||||
<extension name="spamtest">
|
||||
|
||||
<test name="spamtest">
|
||||
<parameter type="comparator" occurrence="optional" />
|
||||
<parameter type="matchtype" occurrence="optional" />
|
||||
<parameter type="string" name="value" />
|
||||
</test>
|
||||
|
||||
</extension>
|
||||
12
data/web/inc/lib/sieve/extensions/spamtestplus.xml
Executable file
12
data/web/inc/lib/sieve/extensions/spamtestplus.xml
Executable file
|
|
@ -0,0 +1,12 @@
|
|||
<?xml version='1.0' standalone='yes'?>
|
||||
|
||||
<extension name="spamtestplus">
|
||||
|
||||
<test name="spamtest" overrides="true">
|
||||
<parameter type="comparator" occurrence="optional" />
|
||||
<parameter type="matchtype" occurrence="optional" />
|
||||
<parameter type="tag" name="percent" regex="percent" occurrence="optional" />
|
||||
<parameter type="string" name="value" />
|
||||
</test>
|
||||
|
||||
</extension>
|
||||
8
data/web/inc/lib/sieve/extensions/subaddress.xml
Executable file
8
data/web/inc/lib/sieve/extensions/subaddress.xml
Executable file
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version='1.0' standalone='yes'?>
|
||||
|
||||
<extension name="subaddress">
|
||||
|
||||
<addresspart name="user" />
|
||||
<addresspart name="detail" />
|
||||
|
||||
</extension>
|
||||
32
data/web/inc/lib/sieve/extensions/vacation-seconds.xml
Executable file
32
data/web/inc/lib/sieve/extensions/vacation-seconds.xml
Executable file
|
|
@ -0,0 +1,32 @@
|
|||
<?xml version='1.0' standalone='yes'?>
|
||||
|
||||
<extension name="vacation-seconds">
|
||||
|
||||
<command name="vacation">
|
||||
|
||||
<parameter type="tag" name="seconds" occurrence="optional" regex="seconds">
|
||||
<parameter type="number" name="period" />
|
||||
</parameter>
|
||||
|
||||
<parameter type="tag" name="addresses" occurrence="optional" regex="addresses">
|
||||
<parameter type="stringlist" name="address strings" />
|
||||
</parameter>
|
||||
|
||||
<parameter type="tag" name="subject" occurrence="optional" regex="subject">
|
||||
<parameter type="string" name="subject string" />
|
||||
</parameter>
|
||||
|
||||
<parameter type="tag" name="from" occurrence="optional" regex="from">
|
||||
<parameter type="string" name="from string" />
|
||||
</parameter>
|
||||
|
||||
<parameter type="tag" name="handle" occurrence="optional" regex="handle">
|
||||
<parameter type="string" name="handle string" />
|
||||
</parameter>
|
||||
|
||||
<parameter type="tag" name="mime" occurrence="optional" regex="mime" />
|
||||
|
||||
<parameter type="string" name="reason" />
|
||||
</command>
|
||||
|
||||
</extension>
|
||||
31
data/web/inc/lib/sieve/extensions/vacation.xml
Executable file
31
data/web/inc/lib/sieve/extensions/vacation.xml
Executable file
|
|
@ -0,0 +1,31 @@
|
|||
<?xml version='1.0' standalone='yes'?>
|
||||
|
||||
<extension name="vacation">
|
||||
|
||||
<command name="vacation">
|
||||
<parameter type="tag" name="days" occurrence="optional" regex="days">
|
||||
<parameter type="number" name="period" />
|
||||
</parameter>
|
||||
|
||||
<parameter type="tag" name="addresses" occurrence="optional" regex="addresses">
|
||||
<parameter type="stringlist" name="address strings" />
|
||||
</parameter>
|
||||
|
||||
<parameter type="tag" name="subject" occurrence="optional" regex="subject">
|
||||
<parameter type="string" name="subject string" />
|
||||
</parameter>
|
||||
|
||||
<parameter type="tag" name="from" occurrence="optional" regex="from">
|
||||
<parameter type="string" name="from string" />
|
||||
</parameter>
|
||||
|
||||
<parameter type="tag" name="handle" occurrence="optional" regex="handle">
|
||||
<parameter type="string" name="handle string" />
|
||||
</parameter>
|
||||
|
||||
<parameter type="tag" name="mime" occurrence="optional" regex="mime" />
|
||||
|
||||
<parameter type="string" name="reason" />
|
||||
</command>
|
||||
|
||||
</extension>
|
||||
21
data/web/inc/lib/sieve/extensions/variables.xml
Executable file
21
data/web/inc/lib/sieve/extensions/variables.xml
Executable file
|
|
@ -0,0 +1,21 @@
|
|||
<?xml version='1.0' standalone='yes'?>
|
||||
|
||||
<extension name="variables">
|
||||
|
||||
<command name="set">
|
||||
<parameter type="tag" name="modifier" regex="(lower|upper)" occurrence="optional" />
|
||||
<parameter type="tag" name="modifier" regex="(lower|upper)first" occurrence="optional" />
|
||||
<parameter type="tag" name="modifier" regex="quotewildcard" occurrence="optional" />
|
||||
<parameter type="tag" name="modifier" regex="length" occurrence="optional" />
|
||||
<parameter type="string" name="name" regex="[[:alpha:]_][[:alnum:]_]*" />
|
||||
<parameter type="string" name="value" />
|
||||
</command>
|
||||
|
||||
<test name="string">
|
||||
<parameter type="matchtype" occurrence="optional" />
|
||||
<parameter type="comparator" occurrence="optional" />
|
||||
<parameter type="stringlist" name="source" />
|
||||
<parameter type="stringlist" name="key list" />
|
||||
</test>
|
||||
|
||||
</extension>
|
||||
11
data/web/inc/lib/sieve/extensions/virustest.xml
Executable file
11
data/web/inc/lib/sieve/extensions/virustest.xml
Executable file
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version='1.0' standalone='yes'?>
|
||||
|
||||
<extension name="virustest">
|
||||
|
||||
<test name="virustest">
|
||||
<parameter type="comparator" occurrence="optional" />
|
||||
<parameter type="matchtype" occurrence="optional" />
|
||||
<parameter type="string" name="value" />
|
||||
</test>
|
||||
|
||||
</extension>
|
||||
17
data/web/inc/lib/sieve/extensions/vnd.dovecot.execute.xml
Executable file
17
data/web/inc/lib/sieve/extensions/vnd.dovecot.execute.xml
Executable file
|
|
@ -0,0 +1,17 @@
|
|||
<?xml version='1.0' standalone='yes'?>
|
||||
|
||||
<extension name="vnd.dovecot.execute">
|
||||
|
||||
<test name="execute">
|
||||
<parameter type="tag" name="pipe" regex="pipe" occurrence="optional"/>
|
||||
<parameter type="string" name="program-name"/>
|
||||
<parameter type="stringlist" name="arguments" occurrence="optional"/>
|
||||
</test>
|
||||
|
||||
<command name="execute">
|
||||
<parameter type="tag" name="pipe" regex="pipe" occurrence="optional"/>
|
||||
<parameter type="string" name="program-name"/>
|
||||
<parameter type="stringlist" name="arguments" occurrence="optional"/>
|
||||
</command>
|
||||
|
||||
</extension>
|
||||
15
data/web/inc/lib/sieve/extensions/vnd.dovecot.filter.xml
Executable file
15
data/web/inc/lib/sieve/extensions/vnd.dovecot.filter.xml
Executable file
|
|
@ -0,0 +1,15 @@
|
|||
<?xml version='1.0' standalone='yes'?>
|
||||
|
||||
<extension name="vnd.dovecot.filter">
|
||||
|
||||
<test name="filter">
|
||||
<parameter type="string" name="program-name"/>
|
||||
<parameter type="stringlist" name="arguments" occurrence="optional"/>
|
||||
</test>
|
||||
|
||||
<command name="filter">
|
||||
<parameter type="string" name="program-name"/>
|
||||
<parameter type="stringlist" name="arguments" occurrence="optional"/>
|
||||
</command>
|
||||
|
||||
</extension>
|
||||
11
data/web/inc/lib/sieve/extensions/vnd.dovecot.pipe.xml
Executable file
11
data/web/inc/lib/sieve/extensions/vnd.dovecot.pipe.xml
Executable file
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version='1.0' standalone='yes'?>
|
||||
|
||||
<extension name="vnd.dovecot.pipe">
|
||||
|
||||
<command name="pipe">
|
||||
<parameter type="tag" name="try" regex="try" occurrence="optional"/>
|
||||
<parameter type="string" name="program-name"/>
|
||||
<parameter type="stringlist" name="arguments" occurrence="optional"/>
|
||||
</command>
|
||||
|
||||
</extension>
|
||||
91
data/web/inc/lib/sieve/keywords.xml
Executable file
91
data/web/inc/lib/sieve/keywords.xml
Executable file
|
|
@ -0,0 +1,91 @@
|
|||
<?xml version='1.0' standalone='yes'?>
|
||||
|
||||
<keywords>
|
||||
|
||||
<matchtype name="is" />
|
||||
<matchtype name="contains" />
|
||||
<matchtype name="matches" />
|
||||
<matchtype name="value">
|
||||
<parameter type="string" name="operator" regex="(gt|ge|eq|le|lt)" />
|
||||
</matchtype>
|
||||
|
||||
|
||||
<comparator name="i;octet" />
|
||||
<comparator name="i;ascii-casemap" />
|
||||
<comparator name="i;unicode-casemap" />
|
||||
|
||||
<addresspart name="all" />
|
||||
<addresspart name="localpart" />
|
||||
<addresspart name="domain" />
|
||||
|
||||
|
||||
<command name="discard" />
|
||||
|
||||
<command name="elsif">
|
||||
<parameter type="test" name="test command" />
|
||||
<parameter type="block" />
|
||||
</command>
|
||||
|
||||
<command name="else">
|
||||
<parameter type="block" />
|
||||
</command>
|
||||
|
||||
<command name="if">
|
||||
<parameter type="test" name="test command" />
|
||||
<parameter type="block" />
|
||||
</command>
|
||||
|
||||
<command name="keep" />
|
||||
|
||||
<command name="redirect">
|
||||
<parameter type="string" name="address string" />
|
||||
</command>
|
||||
|
||||
<command name="require">
|
||||
<parameter type="requirestrings" name="require string" />
|
||||
</command>
|
||||
|
||||
<command name="stop" />
|
||||
|
||||
|
||||
<test name="address">
|
||||
<parameter type="matchtype" occurrence="optional" />
|
||||
<parameter type="comparator" occurrence="optional" />
|
||||
<parameter type="addresspart" occurrence="optional" />
|
||||
<parameter type="stringlist" name="header list" />
|
||||
<parameter type="stringlist" name="key list" />
|
||||
</test>
|
||||
|
||||
<test name="allof">
|
||||
<parameter type="testlist" name="test" />
|
||||
</test>
|
||||
|
||||
<test name="anyof">
|
||||
<parameter type="testlist" name="test" />
|
||||
</test>
|
||||
|
||||
<test name="exists">
|
||||
<parameter type="stringlist" name="header names" />
|
||||
</test>
|
||||
|
||||
<test name="false" />
|
||||
|
||||
<test name="header">
|
||||
<parameter type="matchtype" occurrence="optional" />
|
||||
<parameter type="comparator" occurrence="optional" />
|
||||
<parameter type="stringlist" name="header names" />
|
||||
<parameter type="stringlist" name="key list" />
|
||||
</test>
|
||||
|
||||
<test name="not">
|
||||
<parameter type="test" />
|
||||
</test>
|
||||
|
||||
<test name="size">
|
||||
<parameter type="tag" regex="(over|under)" />
|
||||
<parameter type="number" name="limit" />
|
||||
</test>
|
||||
|
||||
<test name="true" />
|
||||
|
||||
</keywords>
|
||||
622
data/web/inc/lib/ssp.class.php
Executable file
622
data/web/inc/lib/ssp.class.php
Executable file
|
|
@ -0,0 +1,622 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Helper functions for building a DataTables server-side processing SQL query
|
||||
*
|
||||
* The static functions in this class are just helper functions to help build
|
||||
* the SQL used in the DataTables demo server-side processing scripts. These
|
||||
* functions obviously do not represent all that can be done with server-side
|
||||
* processing, they are intentionally simple to show how it works. More complex
|
||||
* server-side processing operations will likely require a custom script.
|
||||
*
|
||||
* See https://datatables.net/usage/server-side for full details on the server-
|
||||
* side processing requirements of DataTables.
|
||||
*
|
||||
* @license MIT - https://datatables.net/license_mit
|
||||
*/
|
||||
|
||||
class SSP {
|
||||
/**
|
||||
* Create the data output array for the DataTables rows
|
||||
*
|
||||
* @param array $columns Column information array
|
||||
* @param array $data Data from the SQL get
|
||||
* @return array Formatted data in a row based format
|
||||
*/
|
||||
static function data_output ( $columns, $data )
|
||||
{
|
||||
$out = array();
|
||||
|
||||
for ( $i=0, $ien=count($data) ; $i<$ien ; $i++ ) {
|
||||
$row = array();
|
||||
|
||||
for ( $j=0, $jen=count($columns) ; $j<$jen ; $j++ ) {
|
||||
$column = $columns[$j];
|
||||
|
||||
// Is there a formatter?
|
||||
if ( isset( $column['formatter'] ) ) {
|
||||
if(empty($column['db'])){
|
||||
$row[ $column['dt'] ] = $column['formatter']( $data[$i] );
|
||||
}
|
||||
else{
|
||||
$row[ $column['dt'] ] = $column['formatter']( $data[$i][ $column['db'] ], $data[$i] );
|
||||
}
|
||||
}
|
||||
else {
|
||||
if(!empty($column['db']) && (!isset($column['dummy']) || $column['dummy'] !== true)){
|
||||
$row[ $column['dt'] ] = $data[$i][ $columns[$j]['db'] ];
|
||||
}
|
||||
else{
|
||||
$row[ $column['dt'] ] = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$out[] = $row;
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Database connection
|
||||
*
|
||||
* Obtain an PHP PDO connection from a connection details array
|
||||
*
|
||||
* @param array $conn SQL connection details. The array should have
|
||||
* the following properties
|
||||
* * host - host name
|
||||
* * db - database name
|
||||
* * user - user name
|
||||
* * pass - user password
|
||||
* * Optional: `'charset' => 'utf8'` - you might need this depending on your PHP / MySQL config
|
||||
* @return resource PDO connection
|
||||
*/
|
||||
static function db ( $conn )
|
||||
{
|
||||
if ( is_array( $conn ) ) {
|
||||
return self::sql_connect( $conn );
|
||||
}
|
||||
|
||||
return $conn;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Paging
|
||||
*
|
||||
* Construct the LIMIT clause for server-side processing SQL query
|
||||
*
|
||||
* @param array $request Data sent to server by DataTables
|
||||
* @param array $columns Column information array
|
||||
* @return string SQL limit clause
|
||||
*/
|
||||
static function limit ( $request, $columns )
|
||||
{
|
||||
$limit = '';
|
||||
|
||||
if ( isset($request['start']) && $request['length'] != -1 ) {
|
||||
$limit = "LIMIT ".intval($request['start']).", ".intval($request['length']);
|
||||
}
|
||||
|
||||
return $limit;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Ordering
|
||||
*
|
||||
* Construct the ORDER BY clause for server-side processing SQL query
|
||||
*
|
||||
* @param array $request Data sent to server by DataTables
|
||||
* @param array $columns Column information array
|
||||
* @return string SQL order by clause
|
||||
*/
|
||||
static function order ( $tableAS, $request, $columns )
|
||||
{
|
||||
$select = '';
|
||||
$order = '';
|
||||
|
||||
if ( isset($request['order']) && count($request['order']) ) {
|
||||
$selects = [];
|
||||
$orderBy = [];
|
||||
$dtColumns = self::pluck( $columns, 'dt' );
|
||||
|
||||
for ( $i=0, $ien=count($request['order']) ; $i<$ien ; $i++ ) {
|
||||
// Convert the column index into the column data property
|
||||
$columnIdx = intval($request['order'][$i]['column']);
|
||||
$requestColumn = $request['columns'][$columnIdx];
|
||||
|
||||
$columnIdx = array_search( $columnIdx, $dtColumns );
|
||||
$column = $columns[ $columnIdx ];
|
||||
|
||||
if ( $requestColumn['orderable'] == 'true' ) {
|
||||
$dir = $request['order'][$i]['dir'] === 'asc' ?
|
||||
'ASC' :
|
||||
'DESC';
|
||||
|
||||
if(isset($column['order_subquery'])) {
|
||||
$selects[] = '('.$column['order_subquery'].') AS `'.$column['db'].'_count`';
|
||||
$orderBy[] = '`'.$column['db'].'_count` '.$dir;
|
||||
} else {
|
||||
$orderBy[] = '`'.$tableAS.'`.`'.$column['db'].'` '.$dir;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( count( $selects ) ) {
|
||||
$select = ', '.implode(', ', $selects);
|
||||
}
|
||||
|
||||
if ( count( $orderBy ) ) {
|
||||
$order = 'ORDER BY '.implode(', ', $orderBy);
|
||||
}
|
||||
}
|
||||
|
||||
return [$select, $order];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Searching / Filtering
|
||||
*
|
||||
* Construct the WHERE clause for server-side processing SQL query.
|
||||
*
|
||||
* NOTE this does not match the built-in DataTables filtering which does it
|
||||
* word by word on any field. It's possible to do here performance on large
|
||||
* databases would be very poor
|
||||
*
|
||||
* @param array $request Data sent to server by DataTables
|
||||
* @param array $columns Column information array
|
||||
* @param array $bindings Array of values for PDO bindings, used in the
|
||||
* sql_exec() function
|
||||
* @return string SQL where clause
|
||||
*/
|
||||
static function filter ( $tablesAS, $request, $columns, &$bindings )
|
||||
{
|
||||
$globalSearch = array();
|
||||
$columnSearch = array();
|
||||
$joins = array();
|
||||
$dtColumns = self::pluck( $columns, 'dt' );
|
||||
|
||||
if ( isset($request['search']) && $request['search']['value'] != '' ) {
|
||||
$str = $request['search']['value'];
|
||||
|
||||
for ( $i=0, $ien=count($request['columns']) ; $i<$ien ; $i++ ) {
|
||||
$requestColumn = $request['columns'][$i];
|
||||
$columnIdx = array_search( $i, $dtColumns );
|
||||
$column = $columns[ $columnIdx ];
|
||||
|
||||
if ( $requestColumn['searchable'] == 'true' ) {
|
||||
if(!empty($column['db'])){
|
||||
$binding = self::bind( $bindings, '%'.$str.'%', PDO::PARAM_STR );
|
||||
|
||||
if(isset($column['search']['join'])) {
|
||||
$joins[] = $column['search']['join'];
|
||||
$globalSearch[] = $column['search']['where_column'].' LIKE '.$binding;
|
||||
} else {
|
||||
$globalSearch[] = "`".$tablesAS."`.`".$column['db']."` LIKE ".$binding;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Individual column filtering
|
||||
if ( isset( $request['columns'] ) ) {
|
||||
for ( $i=0, $ien=count($request['columns']) ; $i<$ien ; $i++ ) {
|
||||
$requestColumn = $request['columns'][$i];
|
||||
$columnIdx = array_search( $requestColumn['data'], $dtColumns );
|
||||
$column = $columns[ $columnIdx ];
|
||||
|
||||
$str = $requestColumn['search']['value'];
|
||||
|
||||
if ( $requestColumn['searchable'] == 'true' &&
|
||||
$str != '' ) {
|
||||
if(!empty($column['db'])){
|
||||
$binding = self::bind( $bindings, '%'.$str.'%', PDO::PARAM_STR );
|
||||
$columnSearch[] = "`".$tablesAS."`.`".$column['db']."` LIKE ".$binding;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Combine the filters into a single string
|
||||
$where = '';
|
||||
|
||||
if ( count( $globalSearch ) ) {
|
||||
$where = '('.implode(' OR ', $globalSearch).')';
|
||||
}
|
||||
|
||||
if ( count( $columnSearch ) ) {
|
||||
$where = $where === '' ?
|
||||
implode(' AND ', $columnSearch) :
|
||||
$where .' AND '. implode(' AND ', $columnSearch);
|
||||
}
|
||||
|
||||
$join = '';
|
||||
if( count($joins) ) {
|
||||
$join = implode(' ', $joins);
|
||||
}
|
||||
|
||||
if ( $where !== '' ) {
|
||||
$where = 'WHERE '.$where;
|
||||
}
|
||||
|
||||
return [$join, $where];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Perform the SQL queries needed for an server-side processing requested,
|
||||
* utilising the helper functions of this class, limit(), order() and
|
||||
* filter() among others. The returned array is ready to be encoded as JSON
|
||||
* in response to an SSP request, or can be modified if needed before
|
||||
* sending back to the client.
|
||||
*
|
||||
* @param array $request Data sent to server by DataTables
|
||||
* @param array|PDO $conn PDO connection resource or connection parameters array
|
||||
* @param string $table SQL table to query
|
||||
* @param string $primaryKey Primary key of the table
|
||||
* @param array $columns Column information array
|
||||
* @return array Server-side processing response array
|
||||
*/
|
||||
static function simple ( $request, $conn, $table, $primaryKey, $columns )
|
||||
{
|
||||
$bindings = array();
|
||||
$db = self::db( $conn );
|
||||
|
||||
// Allow for a JSON string to be passed in
|
||||
if (isset($request['json'])) {
|
||||
$request = json_decode($request['json'], true);
|
||||
}
|
||||
|
||||
// table AS
|
||||
$tablesAS = null;
|
||||
if(is_array($table)) {
|
||||
$tablesAS = $table[1];
|
||||
$table = $table[0];
|
||||
}
|
||||
|
||||
// Build the SQL query string from the request
|
||||
list($select, $order) = self::order( $tablesAS, $request, $columns );
|
||||
$limit = self::limit( $request, $columns );
|
||||
list($join, $where) = self::filter( $tablesAS, $request, $columns, $bindings );
|
||||
|
||||
// Main query to actually get the data
|
||||
$data = self::sql_exec( $db, $bindings,
|
||||
"SELECT `$tablesAS`.`".implode("`, `$tablesAS`.`", self::pluck($columns, 'db'))."`
|
||||
$select
|
||||
FROM `$table` AS `$tablesAS`
|
||||
$join
|
||||
$where
|
||||
GROUP BY `{$tablesAS}`.`{$primaryKey}`
|
||||
$order
|
||||
$limit"
|
||||
);
|
||||
|
||||
// Data set length after filtering
|
||||
$resFilterLength = self::sql_exec( $db, $bindings,
|
||||
"SELECT COUNT(DISTINCT `{$tablesAS}`.`{$primaryKey}`)
|
||||
FROM `$table` AS `$tablesAS`
|
||||
$join
|
||||
$where"
|
||||
);
|
||||
$recordsFiltered = $resFilterLength[0][0];
|
||||
|
||||
// Total data set length
|
||||
$resTotalLength = self::sql_exec( $db,
|
||||
"SELECT COUNT(`{$tablesAS}`.`{$primaryKey}`)
|
||||
FROM `$table` AS `$tablesAS`"
|
||||
);
|
||||
$recordsTotal = $resTotalLength[0][0];
|
||||
|
||||
/*
|
||||
* Output
|
||||
*/
|
||||
return array(
|
||||
"draw" => isset ( $request['draw'] ) ?
|
||||
intval( $request['draw'] ) :
|
||||
0,
|
||||
"recordsTotal" => intval( $recordsTotal ),
|
||||
"recordsFiltered" => intval( $recordsFiltered ),
|
||||
"data" => self::data_output( $columns, $data )
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The difference between this method and the `simple` one, is that you can
|
||||
* apply additional `where` conditions to the SQL queries. These can be in
|
||||
* one of two forms:
|
||||
*
|
||||
* * 'Result condition' - This is applied to the result set, but not the
|
||||
* overall paging information query - i.e. it will not effect the number
|
||||
* of records that a user sees they can have access to. This should be
|
||||
* used when you want apply a filtering condition that the user has sent.
|
||||
* * 'All condition' - This is applied to all queries that are made and
|
||||
* reduces the number of records that the user can access. This should be
|
||||
* used in conditions where you don't want the user to ever have access to
|
||||
* particular records (for example, restricting by a login id).
|
||||
*
|
||||
* In both cases the extra condition can be added as a simple string, or if
|
||||
* you are using external values, as an assoc. array with `condition` and
|
||||
* `bindings` parameters. The `condition` is a string with the SQL WHERE
|
||||
* condition and `bindings` is an assoc. array of the binding names and
|
||||
* values.
|
||||
*
|
||||
* @param array $request Data sent to server by DataTables
|
||||
* @param array|PDO $conn PDO connection resource or connection parameters array
|
||||
* @param string|array $table SQL table to query, if array second key is AS
|
||||
* @param string $primaryKey Primary key of the table
|
||||
* @param array $columns Column information array
|
||||
* @param string $join JOIN sql string
|
||||
* @param string|array $whereResult WHERE condition to apply to the result set
|
||||
* @return array Server-side processing response array
|
||||
*/
|
||||
static function complex (
|
||||
$request,
|
||||
$conn,
|
||||
$table,
|
||||
$primaryKey,
|
||||
$columns,
|
||||
$join=null,
|
||||
$whereResult=null
|
||||
) {
|
||||
$bindings = array();
|
||||
$db = self::db( $conn );
|
||||
|
||||
// table AS
|
||||
$tablesAS = null;
|
||||
if(is_array($table)) {
|
||||
$tablesAS = $table[1];
|
||||
$table = $table[0];
|
||||
}
|
||||
|
||||
// Build the SQL query string from the request
|
||||
list($select, $order) = self::order( $tablesAS, $request, $columns );
|
||||
$limit = self::limit( $request, $columns );
|
||||
list($join_filter, $where) = self::filter( $tablesAS, $request, $columns, $bindings );
|
||||
|
||||
// whereResult can be a simple string, or an assoc. array with a
|
||||
// condition and bindings
|
||||
if ( $whereResult ) {
|
||||
$str = $whereResult;
|
||||
|
||||
if ( is_array($whereResult) ) {
|
||||
$str = $whereResult['condition'];
|
||||
|
||||
if ( isset($whereResult['bindings']) ) {
|
||||
self::add_bindings($bindings, $whereResult);
|
||||
}
|
||||
}
|
||||
|
||||
$where = $where ?
|
||||
$where .' AND '.$str :
|
||||
'WHERE '.$str;
|
||||
}
|
||||
|
||||
// Main query to actually get the data
|
||||
$data = self::sql_exec( $db, $bindings,
|
||||
"SELECT `$tablesAS`.`".implode("`, `$tablesAS`.`", self::pluck($columns, 'db'))."`
|
||||
$select
|
||||
FROM `$table` AS `$tablesAS`
|
||||
$join
|
||||
$join_filter
|
||||
$where
|
||||
GROUP BY `{$tablesAS}`.`{$primaryKey}`
|
||||
$order
|
||||
$limit"
|
||||
);
|
||||
|
||||
// Data set length after filtering
|
||||
$resFilterLength = self::sql_exec( $db, $bindings,
|
||||
"SELECT COUNT(DISTINCT `{$tablesAS}`.`{$primaryKey}`)
|
||||
FROM `$table` AS `$tablesAS`
|
||||
$join
|
||||
$join_filter
|
||||
$where"
|
||||
);
|
||||
$recordsFiltered = (isset($resFilterLength[0])) ? $resFilterLength[0][0] : 0;
|
||||
|
||||
// Total data set length
|
||||
$resTotalLength = self::sql_exec( $db, $bindings,
|
||||
"SELECT COUNT(`{$tablesAS}`.`{$primaryKey}`)
|
||||
FROM `$table` AS `$tablesAS`
|
||||
$join
|
||||
$join_filter
|
||||
$where"
|
||||
);
|
||||
$recordsTotal = (isset($resTotalLength[0])) ? $resTotalLength[0][0] : 0;
|
||||
|
||||
/*
|
||||
* Output
|
||||
*/
|
||||
return array(
|
||||
"draw" => isset ( $request['draw'] ) ?
|
||||
intval( $request['draw'] ) :
|
||||
0,
|
||||
"recordsTotal" => intval( $recordsTotal ),
|
||||
"recordsFiltered" => intval( $recordsFiltered ),
|
||||
"data" => self::data_output( $columns, $data )
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Connect to the database
|
||||
*
|
||||
* @param array $sql_details SQL server connection details array, with the
|
||||
* properties:
|
||||
* * host - host name
|
||||
* * db - database name
|
||||
* * user - user name
|
||||
* * pass - user password
|
||||
* @return resource Database connection handle
|
||||
*/
|
||||
static function sql_connect ( $sql_details )
|
||||
{
|
||||
try {
|
||||
$db = @new PDO(
|
||||
"mysql:host={$sql_details['host']};dbname={$sql_details['db']}",
|
||||
$sql_details['user'],
|
||||
$sql_details['pass'],
|
||||
array( PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION )
|
||||
);
|
||||
}
|
||||
catch (PDOException $e) {
|
||||
self::fatal(
|
||||
"An error occurred while connecting to the database. ".
|
||||
"The error reported by the server was: ".$e->getMessage()
|
||||
);
|
||||
}
|
||||
|
||||
return $db;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Execute an SQL query on the database
|
||||
*
|
||||
* @param resource $db Database handler
|
||||
* @param array $bindings Array of PDO binding values from bind() to be
|
||||
* used for safely escaping strings. Note that this can be given as the
|
||||
* SQL query string if no bindings are required.
|
||||
* @param string $sql SQL query to execute.
|
||||
* @return array Result from the query (all rows)
|
||||
*/
|
||||
static function sql_exec ( $db, $bindings, $sql=null )
|
||||
{
|
||||
// Argument shifting
|
||||
if ( $sql === null ) {
|
||||
$sql = $bindings;
|
||||
}
|
||||
|
||||
$stmt = $db->prepare( $sql );
|
||||
|
||||
// Bind parameters
|
||||
if ( is_array( $bindings ) ) {
|
||||
for ( $i=0, $ien=count($bindings) ; $i<$ien ; $i++ ) {
|
||||
$binding = $bindings[$i];
|
||||
$stmt->bindValue( $binding['key'], $binding['val'], $binding['type'] );
|
||||
}
|
||||
}
|
||||
|
||||
// Execute
|
||||
try {
|
||||
$stmt->execute();
|
||||
}
|
||||
catch (PDOException $e) {
|
||||
self::fatal( "An SQL error occurred: ".$e->getMessage() );
|
||||
}
|
||||
|
||||
// Return all
|
||||
return $stmt->fetchAll( PDO::FETCH_BOTH );
|
||||
}
|
||||
|
||||
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
* Internal methods
|
||||
*/
|
||||
|
||||
/**
|
||||
* Throw a fatal error.
|
||||
*
|
||||
* This writes out an error message in a JSON string which DataTables will
|
||||
* see and show to the user in the browser.
|
||||
*
|
||||
* @param string $msg Message to send to the client
|
||||
*/
|
||||
static function fatal ( $msg )
|
||||
{
|
||||
echo json_encode( array(
|
||||
"error" => $msg
|
||||
) );
|
||||
|
||||
exit(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a PDO binding key which can be used for escaping variables safely
|
||||
* when executing a query with sql_exec()
|
||||
*
|
||||
* @param array &$a Array of bindings
|
||||
* @param * $val Value to bind
|
||||
* @param int $type PDO field type
|
||||
* @return string Bound key to be used in the SQL where this parameter
|
||||
* would be used.
|
||||
*/
|
||||
static function bind ( &$a, $val, $type )
|
||||
{
|
||||
$key = ':binding_'.count( $a );
|
||||
|
||||
$a[] = array(
|
||||
'key' => $key,
|
||||
'val' => $val,
|
||||
'type' => $type
|
||||
);
|
||||
|
||||
return $key;
|
||||
}
|
||||
|
||||
static function add_bindings(&$bindings, $vals)
|
||||
{
|
||||
foreach($vals['bindings'] as $key => $value) {
|
||||
$bindings[] = array(
|
||||
'key' => $key,
|
||||
'val' => $value,
|
||||
'type' => PDO::PARAM_STR
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Pull a particular property from each assoc. array in a numeric array,
|
||||
* returning and array of the property values from each item.
|
||||
*
|
||||
* @param array $a Array to get data from
|
||||
* @param string $prop Property to read
|
||||
* @return array Array of property values
|
||||
*/
|
||||
static function pluck ( $a, $prop )
|
||||
{
|
||||
$out = array();
|
||||
|
||||
for ( $i=0, $len=count($a) ; $i<$len ; $i++ ) {
|
||||
if ( empty($a[$i][$prop]) && $a[$i][$prop] !== 0 ) {
|
||||
continue;
|
||||
}
|
||||
if ( $prop == 'db' && isset($a[$i]['dummy']) && $a[$i]['dummy'] === true ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
//removing the $out array index confuses the filter method in doing proper binding,
|
||||
//adding it ensures that the array data are mapped correctly
|
||||
$out[$i] = $a[$i][$prop];
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return a string from an array or a string
|
||||
*
|
||||
* @param array|string $a Array to join
|
||||
* @param string $join Glue for the concatenation
|
||||
* @return string Joined string
|
||||
*/
|
||||
static function _flatten ( $a, $join = ' AND ' )
|
||||
{
|
||||
if ( ! $a ) {
|
||||
return '';
|
||||
}
|
||||
else if ( $a && is_array($a) ) {
|
||||
return implode( $join, $a );
|
||||
}
|
||||
return $a;
|
||||
}
|
||||
}
|
||||
|
||||
2
data/web/inc/lib/vendor/adldap2/adldap2/.gitattributes
vendored
Executable file
2
data/web/inc/lib/vendor/adldap2/adldap2/.gitattributes
vendored
Executable file
|
|
@ -0,0 +1,2 @@
|
|||
examples/ export-ignore
|
||||
tests/ export-ignore
|
||||
10
data/web/inc/lib/vendor/adldap2/adldap2/.github/issue_template.md
vendored
Executable file
10
data/web/inc/lib/vendor/adldap2/adldap2/.github/issue_template.md
vendored
Executable file
|
|
@ -0,0 +1,10 @@
|
|||
- Adldap2 Version: #.#
|
||||
- LDAP Type: <!-- Active Directory / OpenLDAP / FreeIPA / Sun Directory Server? -->
|
||||
- PHP Version: #.#
|
||||
|
||||
<!-- **ISSUES WITHOUT THE ABOVE INFORMATION WILL BE CLOSED!** -->
|
||||
|
||||
### Description:
|
||||
|
||||
|
||||
### Steps To Reproduce:
|
||||
3
data/web/inc/lib/vendor/adldap2/adldap2/.gitignore
vendored
Executable file
3
data/web/inc/lib/vendor/adldap2/adldap2/.gitignore
vendored
Executable file
|
|
@ -0,0 +1,3 @@
|
|||
/.idea
|
||||
/vendor
|
||||
composer.lock
|
||||
10
data/web/inc/lib/vendor/adldap2/adldap2/.scrutinizer.yml
vendored
Executable file
10
data/web/inc/lib/vendor/adldap2/adldap2/.scrutinizer.yml
vendored
Executable file
|
|
@ -0,0 +1,10 @@
|
|||
filter:
|
||||
excluded_paths:
|
||||
- tests/*
|
||||
- src/Schemas/*
|
||||
build:
|
||||
nodes:
|
||||
analysis:
|
||||
tests:
|
||||
override:
|
||||
- command: php-scrutinizer-run
|
||||
7
data/web/inc/lib/vendor/adldap2/adldap2/.styleci.yml
vendored
Executable file
7
data/web/inc/lib/vendor/adldap2/adldap2/.styleci.yml
vendored
Executable file
|
|
@ -0,0 +1,7 @@
|
|||
preset: recommended
|
||||
|
||||
enabled:
|
||||
- length_ordered_imports
|
||||
|
||||
disabled:
|
||||
- alpha_ordered_imports
|
||||
19
data/web/inc/lib/vendor/adldap2/adldap2/.travis.yml
vendored
Executable file
19
data/web/inc/lib/vendor/adldap2/adldap2/.travis.yml
vendored
Executable file
|
|
@ -0,0 +1,19 @@
|
|||
language: php
|
||||
|
||||
php:
|
||||
- 7.1
|
||||
- 7.2
|
||||
- 7.3
|
||||
- 7.4
|
||||
|
||||
before_script:
|
||||
- travis_retry composer self-update
|
||||
- travis_retry composer install --prefer-source --no-interaction
|
||||
|
||||
script: ./vendor/bin/phpunit
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- v9.0
|
||||
- v8.0
|
||||
57
data/web/inc/lib/vendor/adldap2/adldap2/composer.json
vendored
Executable file
57
data/web/inc/lib/vendor/adldap2/adldap2/composer.json
vendored
Executable file
|
|
@ -0,0 +1,57 @@
|
|||
{
|
||||
"name": "adldap2/adldap2",
|
||||
"type": "library",
|
||||
"description": "A PHP LDAP Package for humans.",
|
||||
"keywords": [
|
||||
"active directory",
|
||||
"directory",
|
||||
"ad",
|
||||
"ldap",
|
||||
"windows",
|
||||
"adldap",
|
||||
"adldap2"
|
||||
],
|
||||
"license": "MIT",
|
||||
"support": {
|
||||
"docs": "https://github.com/Adldap2/Adldap2/blob/master/readme.md",
|
||||
"issues": "https://github.com/Adldap2/Adldap2/issues",
|
||||
"source": "https://github.com/Adldap2/Adldap2",
|
||||
"email": "steven_bauman@outlook.com"
|
||||
},
|
||||
"authors": [
|
||||
{
|
||||
"name": "Steve Bauman",
|
||||
"email": "steven_bauman@outlook.com",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=7.0",
|
||||
"ext-ldap": "*",
|
||||
"ext-json": "*",
|
||||
"psr/log": "~1.0",
|
||||
"psr/simple-cache": "~1.0",
|
||||
"tightenco/collect": "~5.0|~6.0|~7.0|~8.0",
|
||||
"illuminate/contracts": "~5.0|~6.0|~7.0|~8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "~6.0|~7.0|~8.0",
|
||||
"mockery/mockery": "~1.0"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-fileinfo": "fileinfo is required when retrieving user encoded thumbnails"
|
||||
},
|
||||
"archive": {
|
||||
"exclude": ["/examples", "/tests"]
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Adldap\\": "src/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"Adldap\\Tests\\": "tests/"
|
||||
}
|
||||
}
|
||||
}
|
||||
0
data/web/inc/lib/vendor/adldap2/adldap2/docs/.nojekyll
vendored
Executable file
0
data/web/inc/lib/vendor/adldap2/adldap2/docs/.nojekyll
vendored
Executable file
16
data/web/inc/lib/vendor/adldap2/adldap2/docs/_coverpage.md
vendored
Executable file
16
data/web/inc/lib/vendor/adldap2/adldap2/docs/_coverpage.md
vendored
Executable file
|
|
@ -0,0 +1,16 @@
|
|||
<!-- _coverpage.md -->
|
||||
|
||||
# Adldap2
|
||||
|
||||
<p align="center">
|
||||
<a href="https://travis-ci.org/Adldap2/Adldap2"><img src="https://img.shields.io/travis/Adldap2/Adldap2.svg?style=flat-square"/></a>
|
||||
<a href="https://scrutinizer-ci.com/g/Adldap2/Adldap2/?branch=master"><img src="https://img.shields.io/scrutinizer/g/adLDAP2/adLDAP2/master.svg?style=flat-square"/></a>
|
||||
<a href="https://packagist.org/packages/adldap2/adldap2"><img src="https://img.shields.io/packagist/dt/adldap2/adldap2.svg?style=flat-square"/></a>
|
||||
<a href="https://packagist.org/packages/adldap2/adldap2"><img src="https://img.shields.io/packagist/v/adldap2/adldap2.svg?style=flat-square"/></a>
|
||||
<a href="https://packagist.org/packages/adldap2/adldap2"><img src="https://img.shields.io/packagist/l/adldap2/adldap2.svg?style=flat-square"/></a>
|
||||
</p>
|
||||
|
||||
> Working with LDAP doesn't need to be hard.
|
||||
|
||||
<!-- background image -->
|
||||

|
||||
27
data/web/inc/lib/vendor/adldap2/adldap2/docs/_sidebar.md
vendored
Executable file
27
data/web/inc/lib/vendor/adldap2/adldap2/docs/_sidebar.md
vendored
Executable file
|
|
@ -0,0 +1,27 @@
|
|||
<!-- _sidebar.md -->
|
||||
|
||||
* Getting Started
|
||||
|
||||
* [Introduction](/)
|
||||
* [Installation](installation.md)
|
||||
* [Setup](setup.md)
|
||||
|
||||
* Usage
|
||||
|
||||
* [Searching](searching.md)
|
||||
* [Creating & Updating](models/model.md)
|
||||
* [Events](events.md)
|
||||
* [Logging](logging.md)
|
||||
* [Working With Distiguished Names](distinguished-names.md)
|
||||
* [Troubleshooting](troubleshooting.md)
|
||||
|
||||
* Models
|
||||
* [Model (Base)](models/model.md)
|
||||
* [Computer](models/computer.md)
|
||||
* [Contact](models/contact.md)
|
||||
* [Container](models/container.md)
|
||||
* [Group](models/group.md)
|
||||
* [Organizational Unit](models/ou.md)
|
||||
* [Printer](models/printer.md)
|
||||
* [RootDse](models/root-dse.md)
|
||||
* [User](models/user.md)
|
||||
167
data/web/inc/lib/vendor/adldap2/adldap2/docs/distinguished-names.md
vendored
Executable file
167
data/web/inc/lib/vendor/adldap2/adldap2/docs/distinguished-names.md
vendored
Executable file
|
|
@ -0,0 +1,167 @@
|
|||
## Working With Distinguished Names
|
||||
|
||||
Working with DN strings are a pain, but they're about to get easier. Adldap includes a DN builder for easily modifying and
|
||||
creating DN strings.
|
||||
|
||||
> **Note**: All values inserted into DN methods are escaped. You do not need to escape **any** values before hand.
|
||||
|
||||
#### Creating a New DN
|
||||
|
||||
To create a new DN, construct a new `Adldap\Models\Attributes\DistinguishedName` instance:
|
||||
|
||||
```php
|
||||
$dn = new Adldap\Models\Attributes\DistinguishedName();
|
||||
```
|
||||
|
||||
You can also pass in a current DN string and start modifying it:
|
||||
|
||||
```php
|
||||
$currentDn = 'cn=John Doe,ou=Accounting,dc=corp,dc=acme,dc=org';
|
||||
|
||||
$dn = new Adldap\Models\Attributes\DistinguishedName($currentDn);
|
||||
```
|
||||
|
||||
#### Adding / Removing a Domain Component
|
||||
|
||||
```php
|
||||
// Add Domain Component
|
||||
$dn->addDc('corp');
|
||||
|
||||
// Remove Domain Component
|
||||
$dn->removeDc('corp');
|
||||
```
|
||||
|
||||
#### Adding / Removing an Organizational Unit
|
||||
|
||||
```php
|
||||
// Add Organizational Unit
|
||||
$dn->addOu('Accounting');
|
||||
|
||||
// Remove Organizational Unit
|
||||
$dn->removeOu('Accounting');
|
||||
```
|
||||
|
||||
#### Adding / Removing Common Names
|
||||
|
||||
```php
|
||||
// Add Common Name
|
||||
$dn->addCn('John Doe');
|
||||
|
||||
// Remove Common Name
|
||||
$dn->removeCn('John Doe');
|
||||
```
|
||||
|
||||
#### Setting a base
|
||||
|
||||
If you'd like to set the base DN, such as a domain component RDN, use the `setBase()` method:
|
||||
|
||||
```php
|
||||
$base = 'dc=corp,dc=acme,dc=org';
|
||||
|
||||
$dn->setBase($base);
|
||||
```
|
||||
|
||||
#### Creating a DN From A Model
|
||||
|
||||
When you're creating a new LDAP record, you'll need to create a distinguished name as well. Let's go through an example of
|
||||
creating a new user.
|
||||
|
||||
```php
|
||||
$user = $provider->make()->user();
|
||||
|
||||
$user->setCommonName('John Doe');
|
||||
$user->setFirstName('John');
|
||||
$user->setLastName('Doe');
|
||||
```
|
||||
|
||||
So we've set the basic information on the user, but we run into trouble when we want to put the user into a certain container
|
||||
(such as 'Accounting') which is done through the DN. Let's go through this example:
|
||||
|
||||
```php
|
||||
$dn = $user->getDnBuilder();
|
||||
|
||||
$dn->addCn($user->getCommonName());
|
||||
$dn->addOu('Accounting');
|
||||
$dn->addDc('corp');
|
||||
$dn->addDc('acme');
|
||||
$dn->addDc('org');
|
||||
|
||||
// Returns 'cn=John Doe,ou=Accounting,dc=corp,dc=acme,dc=org'
|
||||
echo $dn->get();
|
||||
|
||||
// The DistinguishedName object also contains the __toString() magic method
|
||||
// so you can also just echo the object itself
|
||||
echo $dn;
|
||||
```
|
||||
|
||||
Now we've built a DN, and all we have to do is set it on the new user:
|
||||
|
||||
```php
|
||||
$user->setDn($dn);
|
||||
|
||||
$user->save();
|
||||
```
|
||||
|
||||
#### Modifying a DN From A Model
|
||||
|
||||
When you've received a model from a search result, you can build and modify the models DN like so:
|
||||
|
||||
```php
|
||||
$user = $ad->users()->find('jdoe');
|
||||
|
||||
$dn = $user->getDnBuilder();
|
||||
|
||||
$dn->addOu('Users');
|
||||
|
||||
$user->setDn($dn)->save();
|
||||
```
|
||||
|
||||
#### Retrieving the RDN components
|
||||
|
||||
To retrieve all of the RDN components of a Distinguished Name, call `getComponents()`:
|
||||
|
||||
```php
|
||||
$dn = new Adldap\Models\Attributes\DistinguishedName(
|
||||
'cn=John Doe,ou=Accounting,dc=corp,dc=acme,dc=org'
|
||||
);
|
||||
|
||||
$components = $dn->getComponents();
|
||||
|
||||
var_dump($components);
|
||||
|
||||
// Output:
|
||||
// array:5 [▼
|
||||
// "cn" => array:1 [▼
|
||||
// 0 => "John Doe"
|
||||
// ]
|
||||
// "uid" => []
|
||||
// "ou" => array:1 [▼
|
||||
// 0 => "Accounting"
|
||||
// ]
|
||||
// "dc" => array:3 [▼
|
||||
// 0 => "corp"
|
||||
// 1 => "acme"
|
||||
// 2 => "org"
|
||||
// ]
|
||||
// "o" => []
|
||||
// ]
|
||||
```
|
||||
|
||||
You can also specify a component you would like returned by supplying it as an argument:
|
||||
|
||||
```php
|
||||
$dn = new Adldap\Models\Attributes\DistinguishedName(
|
||||
'cn=John Doe,ou=Accounting,dc=corp,dc=acme,dc=org'
|
||||
);
|
||||
|
||||
$dcs = $dn->getComponents('dc');
|
||||
|
||||
var_dump($dcs);
|
||||
|
||||
// Output:
|
||||
// array:3 [▼
|
||||
// 0 => "corp"
|
||||
// 1 => "acme"
|
||||
// 2 => "org"
|
||||
// ]
|
||||
```
|
||||
175
data/web/inc/lib/vendor/adldap2/adldap2/docs/events.md
vendored
Executable file
175
data/web/inc/lib/vendor/adldap2/adldap2/docs/events.md
vendored
Executable file
|
|
@ -0,0 +1,175 @@
|
|||
# Events
|
||||
|
||||
Adldap2 events provide a method of listening for certain LDAP actions
|
||||
that are called and execute tasks for that specific event.
|
||||
|
||||
> **Note**: The Adldap2 event dispatcher was actually derived from the
|
||||
> [Laravel Framework](https://github.com/laravel/framework) with
|
||||
> Broadcasting & Queuing omitted to remove extra dependencies
|
||||
> that would be required with implementing those features.
|
||||
>
|
||||
> If you've utilized Laravel's events before, this will feel very familiar.
|
||||
|
||||
## Registering Listeners
|
||||
|
||||
> **Note**: Before we get to registering listeners, it's crucial to know that events throughout
|
||||
> Adldap2 are fired irrespective of the current connection or provider in use.
|
||||
>
|
||||
> This means that when using multiple LDAP connections, the same events will be fired.
|
||||
>
|
||||
> This allows you to set listeners on events that occur for all LDAP connections you utilize.
|
||||
>
|
||||
> If you are required to determine which events are fired from alternate connections, see [below](#determining-the-connection).
|
||||
|
||||
To register a listener on an event, retrieve the event dispatcher and call the `listen()` method:
|
||||
|
||||
```php
|
||||
use Adldap\Auth\Events\Binding;
|
||||
|
||||
$dispatcher = \Adldap\Adldap::getEventDispatcher();
|
||||
|
||||
$dispatcher->listen(Binding::class, function (Binding $event) {
|
||||
// Do something with the Binding event information:
|
||||
|
||||
$event->connection; // Adldap\Connections\Ldap instance
|
||||
$event->username; // 'jdoe@acme.org'
|
||||
$event->password; // 'super-secret'
|
||||
});
|
||||
```
|
||||
|
||||
The first argument is the event name you would like to listen for, and the
|
||||
second is either a closure or class name that should handle the event:
|
||||
|
||||
Using a class:
|
||||
|
||||
> **Note**: When using just a class name, the class must contain a public `handle()` method that will handle the event.
|
||||
|
||||
```php
|
||||
use Adldap\Adldap;
|
||||
use Adldap\Auth\Events\Binding;
|
||||
|
||||
$dispatcher = Adldap::getEventDispatcher();
|
||||
|
||||
$dispatcher->listen(Binding::class, MyApp\BindingEventHandler::class);
|
||||
```
|
||||
|
||||
```php
|
||||
namespace MyApp;
|
||||
|
||||
use Adldap\Auth\Events\Binding;
|
||||
|
||||
class BindingEventHandler
|
||||
{
|
||||
public function handle(Binding $event)
|
||||
{
|
||||
// Handle the event...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Model Events
|
||||
|
||||
Model events are handled the same way as authentication events.
|
||||
|
||||
Simply call the event dispatcher `listen()` method with the model event you are wanting to listen for:
|
||||
|
||||
```php
|
||||
use Adldap\Models\Events\Saving;
|
||||
|
||||
$dispatcher = \Adldap\Adldap::getEventDispatcher();
|
||||
|
||||
$dispatcher->listen(Saving::class, function (Saving $event) {
|
||||
// Do something with the Saving event information:
|
||||
|
||||
// Returns the model instance being saved eg. `Adldap\Models\Entry`
|
||||
$event->getModel();
|
||||
});
|
||||
```
|
||||
|
||||
## Wildcard Event Listeners
|
||||
|
||||
You can register listeners using the `*` as a wildcard parameter to catch multiple events with the same listener.
|
||||
|
||||
Wildcard listeners will receive the event name as their first argument, and the entire event data array as their second argument:
|
||||
|
||||
```php
|
||||
$dispatcher = Adldap::getEventDispatcher();
|
||||
|
||||
// Listen for all model events.
|
||||
$dispatcher->listen('Adldap\Models\Events\*', function ($eventName, array $data) {
|
||||
echo $eventName; // Returns 'Adldap\Models\Events\Updating'
|
||||
var_dump($data); // Returns [0] => (object) Adldap\Models\Events\Updating;
|
||||
});
|
||||
|
||||
$user = $provider->search()->users()->find('jdoe');
|
||||
|
||||
$user->setTelephoneNumber('555 555-5555');
|
||||
|
||||
$user->save();
|
||||
```
|
||||
|
||||
## Determining the Connection
|
||||
|
||||
If you're using multiple LDAP connections and you require the ability to determine which events belong
|
||||
to a certain connection, you can do so by verifying the host of the LDAP connection.
|
||||
|
||||
Here's an example:
|
||||
|
||||
```php
|
||||
$dispatcher = Adldap::getEventDispatcher();
|
||||
|
||||
$dispatcher->listen(\Adldap\Models\Events\Creating::class, function ($event) {
|
||||
$connection = $event->model->getConnection();
|
||||
|
||||
$host = $connection->getHost();
|
||||
|
||||
echo $host; // Displays 'ldap://192.168.1.1:386'
|
||||
});
|
||||
```
|
||||
|
||||
Another example with auth events:
|
||||
|
||||
```php
|
||||
$dispatcher = Adldap::getEventDispatcher();
|
||||
|
||||
$dispatcher->listen(\Adldap\Auth\Events\Binding::class, function ($event) {
|
||||
$connection = $event->connection;
|
||||
|
||||
$host = $connection->getHost();
|
||||
|
||||
echo $host; // Displays 'ldap://192.168.1.1:386'
|
||||
});
|
||||
```
|
||||
|
||||
## List of Events
|
||||
|
||||
### Authentication Events
|
||||
|
||||
There are several events that are fired during initial and subsequent binds to your configured LDAP server.
|
||||
|
||||
Here is a list of all events that are fired:
|
||||
|
||||
| Event| Description |
|
||||
|---|---|
|
||||
| Adldap\Auth\Events\Attempting | When any authentication attempt is called via: `$provider->auth()->attempt()` |
|
||||
| Adldap\Auth\Events\Passed | When any authentication attempts pass via: `$provider->auth()->attempt()` |
|
||||
| Adldap\Auth\Events\Failed | When any authentication attempts fail via: `$provider->auth()->attempt()` *Or* `$provider->auth()->bind()` |
|
||||
| Adldap\Auth\Events\Binding | When any LDAP bind attempts occur via: `$provider->auth()->attempt()` *Or* `$provider->auth()->bind()` |
|
||||
| Adldap\Auth\Events\Bound | When any LDAP bind attempts are successful via: `$provider->auth()->attempt()` *Or* `$provider->auth()->bind()` |
|
||||
|
||||
### Model Events
|
||||
|
||||
There are several events that are fired during the creation, updating and deleting of all models.
|
||||
|
||||
Here is a list of all events that are fired:
|
||||
|
||||
| Event | Description |
|
||||
|---|---|
|
||||
| Adldap\Models\Events\Saving | When a model is in the process of being saved via: `$model->save()` |
|
||||
| Adldap\Models\Events\Saved | When a model has been successfully saved via: `$model->save()` |
|
||||
| Adldap\Models\Events\Creating | When a model is being created via: `$model->save()` *Or* `$model->create()` |
|
||||
| Adldap\Models\Events\Created | When a model has been successfully created via: `$model->save()` *Or* `$model->create()` |
|
||||
| Adldap\Models\Events\Updating | When a model is being updated via: `$model->save()` *Or* `$model->update()` |
|
||||
| Adldap\Models\Events\Updated | When a model has been successfully updated via: `$model->save()` *Or* `$model->update()` |
|
||||
| Adldap\Models\Events\Deleting | When a model is being deleted via: `$model->delete()` |
|
||||
| Adldap\Models\Events\Deleted | When a model has been successfully deleted via: `$model->delete()` |
|
||||
35
data/web/inc/lib/vendor/adldap2/adldap2/docs/index.html
vendored
Executable file
35
data/web/inc/lib/vendor/adldap2/adldap2/docs/index.html
vendored
Executable file
|
|
@ -0,0 +1,35 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Adldap2 Documentation</title>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="description" content="Adldap2 Documentation">
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
||||
<link rel="stylesheet" href="https://unpkg.com/docsify/lib/themes/vue.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
<script>
|
||||
window.$docsify = {
|
||||
name: 'Adldap2',
|
||||
repo: 'https://github.com/Adldap2/Adldap2',
|
||||
autoHeader: true,
|
||||
auto2top: true,
|
||||
homepage: 'readme.md',
|
||||
coverpage: true,
|
||||
search: 'auto',
|
||||
loadSidebar: true,
|
||||
subMaxLevel: 3
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<script src="https://unpkg.com/docsify/lib/docsify.min.js"></script>
|
||||
<script src="https://unpkg.com/prismjs/components/prism-php.min.js"></script>
|
||||
<script src="https://unpkg.com/docsify/lib/plugins/search.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
29
data/web/inc/lib/vendor/adldap2/adldap2/docs/installation.md
vendored
Executable file
29
data/web/inc/lib/vendor/adldap2/adldap2/docs/installation.md
vendored
Executable file
|
|
@ -0,0 +1,29 @@
|
|||
# Requirements
|
||||
|
||||
Adldap2 requires the following:
|
||||
|
||||
- PHP 7.0 or greater
|
||||
- LDAP extension enabled in PHP
|
||||
- An LDAP server (ActiveDirectory, OpenLDAP, FreeIPA etc.)
|
||||
|
||||
# Composer
|
||||
|
||||
Adldap2 uses [Composer](https://getcomposer.org) for installation.
|
||||
|
||||
Once you have composer installed, run the following command in the root directory of your project:
|
||||
|
||||
```bash
|
||||
composer require adldap2/adldap2
|
||||
```
|
||||
|
||||
Then, if your application doesn't already require Composer's autoload, you will need to do it manually.
|
||||
|
||||
Insert this line at the top of your projects PHP script (usually `index.php`):
|
||||
|
||||
```php
|
||||
require __DIR__ . '/vendor/autoload.php';
|
||||
```
|
||||
|
||||
You're all set!
|
||||
|
||||
Now, head over to the [setup guide](setup.md) to get up and running.
|
||||
74
data/web/inc/lib/vendor/adldap2/adldap2/docs/logging.md
vendored
Executable file
74
data/web/inc/lib/vendor/adldap2/adldap2/docs/logging.md
vendored
Executable file
|
|
@ -0,0 +1,74 @@
|
|||
# Logging
|
||||
|
||||
Adldap2 includes an implementation of PSR's widely supported [Logger](https://github.com/php-fig/log) interface.
|
||||
|
||||
By default, all of Adldap2's [events](events.md) will call the logger you have set to utilize.
|
||||
|
||||
> **Note**: Adldap2 does not include a file / text logger. You must implement your own.
|
||||
|
||||
## Registering & Enabling a Logger
|
||||
|
||||
To register a logger call `Adldap::setLogger()`. The logger must implement the `Psr\Log\LoggerInterface`.
|
||||
|
||||
>**Note**: Be sure to set the logger prior to creating a new `Adldap` instance. This
|
||||
> ensures all events throughout the lifecycle of the request use your logger.
|
||||
|
||||
```php
|
||||
use Adldap\Adldap;
|
||||
|
||||
Adldap::setLogger($myLogger);
|
||||
|
||||
$config = ['...'];
|
||||
|
||||
$ad = new Adldap();
|
||||
|
||||
$ad->addProvider($config);
|
||||
```
|
||||
|
||||
## Disabling Logging
|
||||
|
||||
If you need to disable the event logger after a certain set of operations, simply pass in `null` and logging will be disabled:
|
||||
|
||||
```php
|
||||
use Adldap\Adldap;
|
||||
|
||||
Adldap::setLogger($myLogger);
|
||||
|
||||
$config = ['...'];
|
||||
|
||||
$ad = new Adldap();
|
||||
|
||||
$ad->addProvider($config);
|
||||
|
||||
try {
|
||||
$ad->connect();
|
||||
|
||||
// Disable logging anything else.
|
||||
Adldap::setLogger(null);
|
||||
} catch (\Adldap\Connections\BindException $e) {
|
||||
//
|
||||
}
|
||||
```
|
||||
|
||||
## Logged Information
|
||||
|
||||
Here is a list of events that are logged along with the information included:
|
||||
|
||||
| Authentication Events | Logged |
|
||||
|---|---|
|
||||
| `Adldap\Auth\Events\Attempting` | `LDAP (ldap://192.168.1.1:389) - Operation: Adldap\Auth\Events\Attempting - Username: CN=Steve Bauman,OU=Users,DC=corp,DC=acme,DC=org` |
|
||||
| `Adldap\Auth\Events\Binding` |` LDAP (ldap://192.168.1.1:389) - Operation: Adldap\Auth\Events\Binding - Username: CN=Steve Bauman,OU=Users,DC=corp,DC=acme,DC=org` |
|
||||
| `Adldap\Auth\Events\Bound` | `LDAP (ldap://192.168.1.1:389) - Operation: Adldap\Auth\Events\Bound - Username: CN=Steve Bauman,OU=Users,DC=corp,DC=acme,DC=org` |
|
||||
| `Adldap\Auth\Events\Passed` | `LDAP (ldap://192.168.1.1:389) - Operation: Adldap\Auth\Events\Passed - Username: CN=Steve Bauman,OU=Users,DC=corp,DC=acme,DC=org` |
|
||||
| `Adldap\Auth\Events\Failed` | `LDAP (ldap://192.168.1.1:389) - Operation: Adldap\Auth\Events\Failed - Username: CN=Steve Bauman,OU=Users,DC=corp,DC=acme,DC=org - Result: Invalid Credentials` |
|
||||
|
||||
| Model Events | Logged |
|
||||
|---|---|
|
||||
| `Adldap\Models\Events\Saving` | `LDAP (ldap://192.168.1.1:389) - Operation: Saving - On: Adldap\Models\User - Distinguished Name: cn=John Doe,dc=acme,dc=org` |
|
||||
| `Adldap\Models\Events\Saved` | `LDAP (ldap://192.168.1.1:389) - Operation: Saved - On: Adldap\Models\User - Distinguished Name: cn=John Doe,dc=acme,dc=org` |
|
||||
| `Adldap\Models\Events\Creating` | `LDAP (ldap://192.168.1.1:389) - Operation: Creating - On: Adldap\Models\User - Distinguished Name: cn=John Doe,dc=acme,dc=org` |
|
||||
| `Adldap\Models\Events\Created` | `LDAP (ldap://192.168.1.1:389) - Operation: Created - On: Adldap\Models\User - Distinguished Name: cn=John Doe,dc=acme,dc=org` |
|
||||
| `Adldap\Models\Events\Updating` | `LDAP (ldap://192.168.1.1:389) - Operation: Updating - On: Adldap\Models\User - Distinguished Name: cn=John Doe,dc=acme,dc=org` |
|
||||
| `Adldap\Models\Events\Updated` | `LDAP (ldap://192.168.1.1:389) - Operation: Updated - On: Adldap\Models\User - Distinguished Name: cn=John Doe,dc=acme,dc=org` |
|
||||
| `Adldap\Models\Events\Deleting` | `LDAP (ldap://192.168.1.1:389) - Operation: Deleting - On: Adldap\Models\User - Distinguished Name: cn=John Doe,dc=acme,dc=org` |
|
||||
| `Adldap\Models\Events\Deleted` | `LDAP (ldap://192.168.1.1:389) - Operation: Deleted - On: Adldap\Models\User - Distinguished Name: cn=John Doe,dc=acme,dc=org` |
|
||||
1
data/web/inc/lib/vendor/adldap2/adldap2/docs/media/bg.svg
vendored
Executable file
1
data/web/inc/lib/vendor/adldap2/adldap2/docs/media/bg.svg
vendored
Executable file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns='http://www.w3.org/2000/svg' width='100%' height='100%' viewBox='0 0 1600 800'><rect fill='#46ff55' width='1600' height='800'/><g ><path fill='#51ff76' d='M486 705.8c-109.3-21.8-223.4-32.2-335.3-19.4C99.5 692.1 49 703 0 719.8V800h843.8c-115.9-33.2-230.8-68.1-347.6-92.2C492.8 707.1 489.4 706.5 486 705.8z'/><path fill='#57ff94' d='M1600 0H0v719.8c49-16.8 99.5-27.8 150.7-33.5c111.9-12.7 226-2.4 335.3 19.4c3.4 0.7 6.8 1.4 10.2 2c116.8 24 231.7 59 347.6 92.2H1600V0z'/><path fill='#5affb1' d='M478.4 581c3.2 0.8 6.4 1.7 9.5 2.5c196.2 52.5 388.7 133.5 593.5 176.6c174.2 36.6 349.5 29.2 518.6-10.2V0H0v574.9c52.3-17.6 106.5-27.7 161.1-30.9C268.4 537.4 375.7 554.2 478.4 581z'/><path fill='#57ffcd' d='M0 0v429.4c55.6-18.4 113.5-27.3 171.4-27.7c102.8-0.8 203.2 22.7 299.3 54.5c3 1 5.9 2 8.9 3c183.6 62 365.7 146.1 562.4 192.1c186.7 43.7 376.3 34.4 557.9-12.6V0H0z'/><path fill='#50ffe8' d='M181.8 259.4c98.2 6 191.9 35.2 281.3 72.1c2.8 1.1 5.5 2.3 8.3 3.4c171 71.6 342.7 158.5 531.3 207.7c198.8 51.8 403.4 40.8 597.3-14.8V0H0v283.2C59 263.6 120.6 255.7 181.8 259.4z'/><path fill='#7dffe9' d='M1600 0H0v136.3c62.3-20.9 127.7-27.5 192.2-19.2c93.6 12.1 180.5 47.7 263.3 89.6c2.6 1.3 5.1 2.6 7.7 3.9c158.4 81.1 319.7 170.9 500.3 223.2c210.5 61 430.8 49 636.6-16.6V0z'/><path fill='#9effe9' d='M454.9 86.3C600.7 177 751.6 269.3 924.1 325c208.6 67.4 431.3 60.8 637.9-5.3c12.8-4.1 25.4-8.4 38.1-12.9V0H288.1c56 21.3 108.7 50.6 159.7 82C450.2 83.4 452.5 84.9 454.9 86.3z'/><path fill='#baffea' d='M1600 0H498c118.1 85.8 243.5 164.5 386.8 216.2c191.8 69.2 400 74.7 595 21.1c40.8-11.2 81.1-25.2 120.3-41.7V0z'/><path fill='#d2ffea' d='M1397.5 154.8c47.2-10.6 93.6-25.3 138.6-43.8c21.7-8.9 43-18.8 63.9-29.5V0H643.4c62.9 41.7 129.7 78.2 202.1 107.4C1020.4 178.1 1214.2 196.1 1397.5 154.8z'/><path fill='#e9ffeb' d='M1315.3 72.4c75.3-12.6 148.9-37.1 216.8-72.4h-723C966.8 71 1144.7 101 1315.3 72.4z'/></g></svg>
|
||||
32
data/web/inc/lib/vendor/adldap2/adldap2/docs/models/computer.md
vendored
Executable file
32
data/web/inc/lib/vendor/adldap2/adldap2/docs/models/computer.md
vendored
Executable file
|
|
@ -0,0 +1,32 @@
|
|||
# The Computer Model
|
||||
|
||||
> **Note**: This model contains the traits `HasDescription`, `HasLastLogonAndLogOff` & `HasCriticalSystemObject`.
|
||||
> For more information, visit the documentation:
|
||||
>
|
||||
> [HasDescription](/models/traits/has-description.md),
|
||||
> [HasLastLogonAndLogOff](/models/traits/has-last-login-last-logoff.md),
|
||||
> [HasCriticalSystemObject](/models/traits/has-critical-system-object.md)
|
||||
|
||||
## Methods
|
||||
|
||||
```php
|
||||
$computer = $provider->search()->computers()->find('ACME-EXCHANGE');
|
||||
|
||||
// Returns 'Windows Server 2003'
|
||||
$computer->getOperatingSystem();
|
||||
|
||||
// Returns '5.2 (3790)';
|
||||
$computer->getOperatingSystemVersion();
|
||||
|
||||
// Returns 'Service Pack 1';
|
||||
$computer->getOperatingSystemServicePack();
|
||||
|
||||
// Returns 'ACME-DESKTOP001.corp.acme.org'
|
||||
$computer->getDnsHostName();
|
||||
|
||||
$computer->getLastLogOff();
|
||||
|
||||
$computer->getLastLogon();
|
||||
|
||||
$computer->getLastLogonTimestamp();
|
||||
```
|
||||
13
data/web/inc/lib/vendor/adldap2/adldap2/docs/models/contact.md
vendored
Executable file
13
data/web/inc/lib/vendor/adldap2/adldap2/docs/models/contact.md
vendored
Executable file
|
|
@ -0,0 +1,13 @@
|
|||
# The Contact Model
|
||||
|
||||
The Contact model extends from the base `Adldap\Models\Model` class and contains
|
||||
no specific methods / attributes that are limited to it.
|
||||
|
||||
## Creation
|
||||
|
||||
```php
|
||||
// Adldap\Models\Contact
|
||||
$contact = $provider->make()->contact([
|
||||
'cn' => 'Suzy Doe',
|
||||
]);
|
||||
```
|
||||
24
data/web/inc/lib/vendor/adldap2/adldap2/docs/models/container.md
vendored
Executable file
24
data/web/inc/lib/vendor/adldap2/adldap2/docs/models/container.md
vendored
Executable file
|
|
@ -0,0 +1,24 @@
|
|||
# The Container Model
|
||||
|
||||
> **Note**: This model contains the trait `HasDescription` & `HasCriticalSystemObject`.
|
||||
> For more information, visit the documentation:
|
||||
>
|
||||
> [HasDescription](/models/traits/has-description.md),
|
||||
> [HasCriticalSystemObject](/models/traits/has-critical-system-object.md),
|
||||
|
||||
## Creation
|
||||
|
||||
```php
|
||||
// Adldap\Models\Container
|
||||
$container = $provider->make()->container([
|
||||
'cn' => 'VPN Users',
|
||||
]);
|
||||
```
|
||||
|
||||
## Methods
|
||||
|
||||
The `Container` model contains only one unique method.
|
||||
|
||||
```php
|
||||
$flags = $container->getSystemFlags();
|
||||
```
|
||||
253
data/web/inc/lib/vendor/adldap2/adldap2/docs/models/group.md
vendored
Executable file
253
data/web/inc/lib/vendor/adldap2/adldap2/docs/models/group.md
vendored
Executable file
|
|
@ -0,0 +1,253 @@
|
|||
# The Group Model
|
||||
|
||||
> **Note**: This model contains the trait `HasMemberOf`.
|
||||
> For more information, visit the documentation:
|
||||
>
|
||||
> [HasMemberOf](/models/traits/has-member-of.md)
|
||||
|
||||
## Creation
|
||||
|
||||
```php
|
||||
// Adldap\Models\Group
|
||||
$group = $provider->make()->group([
|
||||
'cn' => 'Managers',
|
||||
]);
|
||||
|
||||
// Create group's DN through the DN Builder:
|
||||
$group = $provider->make()->group();
|
||||
|
||||
$dn = $group->getDnBuilder();
|
||||
|
||||
$dn->addOu('Workstation Computers');
|
||||
|
||||
$dn->addCn("Managers");
|
||||
|
||||
$group->setDn($dn);
|
||||
|
||||
// Or set the DN manually:
|
||||
$ou->setDn('cn=Managers,ou=Workstation Computers,dc=test,dc=local,dc=com');
|
||||
|
||||
$group->save();
|
||||
```
|
||||
|
||||
## Getting a groups members
|
||||
|
||||
When you receive a `Group` model instance, it will contain a `member`
|
||||
attribute which contains the distinguished names of all
|
||||
the members inside the group.
|
||||
|
||||
```php
|
||||
$group = $provider->search()->groups()->first();
|
||||
|
||||
foreach ($group->members as $member) {
|
||||
echo $member; // 'cn=John Doe,dc=corp,dc=acme,dc=org'
|
||||
}
|
||||
```
|
||||
|
||||
But this might not be useful, since we might actually want the models for each member.
|
||||
|
||||
This can be easily done with the `getMembers()` method on the group.
|
||||
|
||||
```php
|
||||
$group = $provider->search()->groups()->first();
|
||||
|
||||
foreach ($group->getMembers() as $member) {
|
||||
echo get_class($member); // Instance of `Adldap\Models\Model`
|
||||
|
||||
echo $member->getCommonName();
|
||||
}
|
||||
```
|
||||
|
||||
> **Note**: You should be aware however, that calling the `getMembers()` method will
|
||||
> query your `AD` server for **every** member contained in the group to retrieve
|
||||
> its model. For larger group sets it may be worth paginating them.
|
||||
|
||||
|
||||
### Paginating Group Members
|
||||
|
||||
The group you're looking for might contain hundreds / thousands of members.
|
||||
|
||||
In this case, your server might only return you a portion of the groups members.
|
||||
|
||||
To get around this limit, you need to ask your server to paginate the groups members through a select:
|
||||
|
||||
```php
|
||||
$group = $provider->search()->groups()->select('member;range=0-500')->first();
|
||||
|
||||
foreach ($group->members as $member) {
|
||||
// We'll only have 500 members in this query.
|
||||
}
|
||||
```
|
||||
|
||||
Now, when we have the group instance, we'll only have the first `500` members inside this group.
|
||||
However, calling the `getMembers()` method will automatically retrieve the rest of the members for you:
|
||||
|
||||
```php
|
||||
$group = $provider->search()->groups()->select('member;range=0-500')->first();
|
||||
|
||||
foreach ($group->getMembers() as $member) {
|
||||
// Adldap will automatically retrieve the next 500
|
||||
// records until it's retrieved all records.
|
||||
$member->getCommonName();
|
||||
}
|
||||
```
|
||||
|
||||
> **Note**: Groups containing large amounts of users (1000+) will require
|
||||
> more memory assigned to PHP. Your mileage will vary.
|
||||
|
||||
#### Paginating large sets of Group Members
|
||||
|
||||
When requesting group members from groups that contain a large amount of members
|
||||
(typically over 1000), you may receive PHP memory limit errors due to
|
||||
the large amount of the objects being created in the request.
|
||||
|
||||
To resolve this, you will need to retrieve the members manually. However using
|
||||
this route you will only be able to retrieve the members distinguished names.
|
||||
|
||||
```php
|
||||
$from = 0;
|
||||
$to = 500;
|
||||
$range = "member;range=$from-$to";
|
||||
|
||||
// Retrieve the group.
|
||||
$group = $provider->search()->select($range)->raw()->find('Accounting');
|
||||
|
||||
// Remove the count from the member array.
|
||||
unset($group[$range]['count']);
|
||||
|
||||
// The array of group members distinguished names.
|
||||
$members = $group[$range];
|
||||
|
||||
foreach ($members as $member) {
|
||||
echo $member; // 'cn=John Doe,dc=acme,dc=org'
|
||||
}
|
||||
```
|
||||
|
||||
You can then encapsulate the above example into a recursive function to retrieve the remaining group members.
|
||||
|
||||
## Getting only a groups member names
|
||||
|
||||
To retrieve only the names of the members contained in a group, call the `getMemberNames()` method:
|
||||
|
||||
```php
|
||||
foreach ($group->getMemberNames() as $name) {
|
||||
// Returns 'John Doe'
|
||||
echo $name;
|
||||
}
|
||||
```
|
||||
|
||||
> **Note**: This method does not query your server for each member to retrieve its name. It
|
||||
> only parses the distinguished names from the groups `member` attribute. This means that
|
||||
> if you have paginated group members, you will need to perform another query yourself
|
||||
> to retrieve the rest of the member names (or just call the `getMembers()` method).
|
||||
|
||||
## Setting Group Members
|
||||
|
||||
To set members that are apart of the group, you can perform this in two ways:
|
||||
|
||||
> **Note**: Remember, this will remove **all** pre-existing members, and set the new given members on the group.
|
||||
|
||||
```php
|
||||
$members = [
|
||||
'cn=John Doe,dc=corp,dc=acme,dc=org',
|
||||
'cn=Jane Doe,dc=corp,dc=acme,dc=org',
|
||||
];
|
||||
|
||||
$group->setMembers($members);
|
||||
|
||||
$group->save();
|
||||
```
|
||||
|
||||
Or manually:
|
||||
|
||||
```php
|
||||
$group->member = [
|
||||
'cn=John Doe,dc=corp,dc=acme,dc=org',
|
||||
'cn=Jane Doe,dc=corp,dc=acme,dc=org',
|
||||
];
|
||||
|
||||
$group->save();
|
||||
```
|
||||
|
||||
## Adding One Member
|
||||
|
||||
To add a single member to a group, use the `addMember()` method:
|
||||
|
||||
> **Note**: You do not need to call the `save()` method after adding a
|
||||
> member. It's automatically called so you can determine
|
||||
> if the member was successfully added.
|
||||
|
||||
```php
|
||||
// We can provide a model, or just a plain DN of the new member
|
||||
$user = $provider->search()->users()->first();
|
||||
|
||||
if ($group->addMember($user)) {
|
||||
// User was successfully added to the group!
|
||||
}
|
||||
|
||||
// Or
|
||||
|
||||
$user = 'cn=John Doe,dc=corp,dc=acme,dc=org';
|
||||
|
||||
if ($group->addMember($user)) {
|
||||
//
|
||||
}
|
||||
```
|
||||
|
||||
## Adding Multiple Group Members
|
||||
|
||||
To add multiple members to a group, use the `addMembers()` method:
|
||||
|
||||
> **Note**: You do not need to call the `save()` method after adding
|
||||
> members. It's automatically called so you can determine
|
||||
> if the members were successfully added.
|
||||
|
||||
```php
|
||||
$members = [
|
||||
'cn=John Doe,dc=corp,dc=acme,dc=org',
|
||||
'cn=Jane Doe,dc=corp,dc=acme,dc=org',
|
||||
];
|
||||
|
||||
$group->addMembers($members);
|
||||
|
||||
// Or
|
||||
|
||||
$user = $provider->search()->users()->first();
|
||||
|
||||
if ($group->addMembers($user)) {
|
||||
//
|
||||
}
|
||||
```
|
||||
|
||||
## Removing One Member
|
||||
|
||||
To remove a single member to a group, use the `removeMember()` method:
|
||||
|
||||
```php
|
||||
// We can provide a model, or just a plain DN of the existing member
|
||||
$group = $provider->search()->groups()->first();
|
||||
|
||||
$member = $group->getMembers()->first();
|
||||
|
||||
if ($group->removeMember($member)) {
|
||||
// Member was successfully removed from the group!
|
||||
}
|
||||
|
||||
// Or
|
||||
|
||||
$user = 'cn=John Doe,dc=corp,dc=acme,dc=org';
|
||||
|
||||
if ($group->removeMember($user)) {
|
||||
//
|
||||
}
|
||||
```
|
||||
|
||||
## Removing All Members
|
||||
|
||||
To remove all members, use the `removeMembers()` method:
|
||||
|
||||
```php
|
||||
if ($group->removeMembers()) {
|
||||
// All members were successfully removed!
|
||||
}
|
||||
```
|
||||
655
data/web/inc/lib/vendor/adldap2/adldap2/docs/models/model.md
vendored
Executable file
655
data/web/inc/lib/vendor/adldap2/adldap2/docs/models/model.md
vendored
Executable file
|
|
@ -0,0 +1,655 @@
|
|||
# Creating / Updating
|
||||
|
||||
## Introduction
|
||||
|
||||
Adldap2 implements the [ActiveRecord](https://en.wikipedia.org/wiki/Active_record_pattern) pattern.
|
||||
This means that each LDAP record in your directory is represented as it's own model instance.
|
||||
|
||||
## Creating
|
||||
|
||||
Creating LDAP entries manually is always a pain, but Adldap2 makes it effortless. Let's get started.
|
||||
|
||||
When you have a provider instance, call the `make()` method. This returns an `Adldap\Models\Factory` instance:
|
||||
|
||||
```php
|
||||
$factory = $provider->make();
|
||||
```
|
||||
|
||||
Or you can chain all methods if you'd prefer:
|
||||
|
||||
```php
|
||||
$user = $provider->make()->user();
|
||||
```
|
||||
|
||||
### Available Make Methods
|
||||
|
||||
When calling a make method, all of them accept an `$attributes` parameter
|
||||
to fill the model with your specified attributes.
|
||||
|
||||
```php
|
||||
// Adldap\Models\User
|
||||
$user = $provider->make()->user([
|
||||
'cn' => 'John Doe',
|
||||
]);
|
||||
|
||||
// Adldap\Models\Computer
|
||||
$computer = $provider->make()->computer([
|
||||
'cn' => 'COMP-101',
|
||||
]);
|
||||
|
||||
// Adldap\Models\Contact
|
||||
$contact = $provider->make()->contact([
|
||||
'cn' => 'Suzy Doe',
|
||||
]);
|
||||
|
||||
// Adldap\Models\Container
|
||||
$container = $provider->make()->container([
|
||||
'cn' => 'VPN Users',
|
||||
]);
|
||||
|
||||
// Adldap\Models\Group
|
||||
$group = $provider->make()->group([
|
||||
'cn' => 'Managers',
|
||||
]);
|
||||
|
||||
// Adldap\Models\OrganizationalUnit
|
||||
$ou = $provider->make()->ou([
|
||||
'name' => 'Acme',
|
||||
]);
|
||||
```
|
||||
|
||||
## Saving
|
||||
|
||||
When you have any model instance, you can call the `save()` method to persist the
|
||||
changes to your server. This method returns a `boolean`. For example:
|
||||
|
||||
```php
|
||||
$user = $provider->make()->user([
|
||||
'cn' => 'New User',
|
||||
]);
|
||||
|
||||
if ($user->save()) {
|
||||
// User was saved.
|
||||
} else {
|
||||
// There was an issue saving this user.
|
||||
}
|
||||
```
|
||||
|
||||
> **Note**: When a model is saved successfully (whether created or updated), the
|
||||
> models attributes are re-synced in the background from your LDAP server.
|
||||
>
|
||||
> This allows you to perform other operations during the same
|
||||
> request that require an existing model.
|
||||
|
||||
### Creating (Manually)
|
||||
|
||||
If you are sure the model **does not exist** already inside your LDAP directory, you can use the `create()` method:
|
||||
|
||||
```php
|
||||
$user = $provider->make()->user([
|
||||
'cn' => 'New User',
|
||||
]);
|
||||
|
||||
if ($user->create()) {
|
||||
// User was created.
|
||||
} else {
|
||||
// There was an issue creating this user.
|
||||
}
|
||||
```
|
||||
|
||||
> **Note**: When you call the create method, if the model does not have a
|
||||
> distinguished name, one will automatically be generated for you using your
|
||||
> `base_dn` set in your configuration and the models common name.
|
||||
|
||||
### Updating (Manually)
|
||||
|
||||
If you are sure the model **does exist** already inside your LDAP directory, you can use the `update()` method:
|
||||
|
||||
```php
|
||||
$user = $provider->search()->whereEquals('cn', 'John Doe')->firstOrFail();
|
||||
|
||||
$user->displayName = 'Suzy Doe';
|
||||
|
||||
if ($user->update()) {
|
||||
// User was updated.
|
||||
} else {
|
||||
// There was an issue updating this user.
|
||||
}
|
||||
```
|
||||
|
||||
## Checking Existence
|
||||
|
||||
If you need to check the existence of a model, use the property `exists`.
|
||||
|
||||
How does it know if the model exists in your LDAP directory? Well, when models are constructed from
|
||||
search results, the `exists` property on the model is set to `true`.
|
||||
|
||||
```php
|
||||
$user = $provider->search()->find('jdoe');
|
||||
|
||||
$user->exists; // Returns true.
|
||||
|
||||
if ($user->delete()) {
|
||||
$user->exists; // Returns false.
|
||||
}
|
||||
```
|
||||
|
||||
If a model is created successfully, the `exists` property is set to `true`:
|
||||
|
||||
```php
|
||||
$user = $provider->make()->user([
|
||||
'cn' => 'John Doe',
|
||||
]);
|
||||
|
||||
$user->exists; // Returns false.
|
||||
|
||||
if ($user->save()) {
|
||||
$user->exists; // Returns true.
|
||||
}
|
||||
```
|
||||
|
||||
## Attributes
|
||||
|
||||
Due to LDAPs multi-valued nature, all LDAP attributes inside a model have their own array.
|
||||
|
||||
For example, a models attributes may contain the following:
|
||||
|
||||
```php
|
||||
var_dump($user->getAttributes());
|
||||
|
||||
// Returns:
|
||||
/*
|
||||
[
|
||||
'cn' => [
|
||||
0 => 'John Doe',
|
||||
],
|
||||
'sn' => [
|
||||
0 => 'Doe',
|
||||
],
|
||||
'givenname' => [
|
||||
0 => 'John'
|
||||
],
|
||||
'useraccountcontrol' => [
|
||||
0 => 512
|
||||
],
|
||||
'mail' => [
|
||||
0 => 'jdoe@acme.org',
|
||||
1 => 'john-doe@acme.org',
|
||||
],
|
||||
'memberof' => [
|
||||
0 => 'cn=Accountants,ou=Groups,dc=acme,dc=org',
|
||||
1 => 'cn=Employees,ou=Groups,dc=acme,dc=org',
|
||||
2 => 'cn=Users,ou=Groups,dc=acme,dc=org',
|
||||
],
|
||||
]
|
||||
*/
|
||||
```
|
||||
|
||||
You can notice in the above dumped array that each attribute contains
|
||||
its own array with a value assigned to the first key.
|
||||
|
||||
Since all models extend from the base class `Adldap\Models\Model`, there
|
||||
are many useful methods that you can use on every model to easily
|
||||
retrieve these attributes you're looking for.
|
||||
|
||||
### Getting Attributes
|
||||
|
||||
You can get attributes in a few ways:
|
||||
|
||||
```php
|
||||
// Returns an array all of the users attributes.
|
||||
$user->getAttributes();
|
||||
|
||||
// Returns an array of all the users email addresses.
|
||||
// Returns `null` if non-existent.
|
||||
$user->getAttribute('mail');
|
||||
|
||||
// Returns the users first email address.
|
||||
// Returns `null` if non-existent.
|
||||
$user->getAttribute('mail', 0);
|
||||
|
||||
// Returns the users first email address.
|
||||
// Returns `null` if non-existent.
|
||||
$user->getFirstAttribute('mail');
|
||||
|
||||
// Returns an array of all the users email addresses.
|
||||
$user->mail;
|
||||
|
||||
// Returns the users first email address.
|
||||
$user->mail[0];
|
||||
```
|
||||
|
||||
#### Using a Getter
|
||||
|
||||
Some attributes have methods for easier retrieval so you don't need to look up the LDAP attribute name.
|
||||
|
||||
For example, to retrieve a users email address, use the method `getEmail()`:
|
||||
|
||||
```php
|
||||
$user->getEmail();
|
||||
```
|
||||
|
||||
##### Other Methods
|
||||
|
||||
The following methods are available on all returned models:
|
||||
|
||||
```php
|
||||
// Returns the model's 'name' attribute.
|
||||
$model->getName();
|
||||
|
||||
// Returns the model's 'cn' attribute.
|
||||
$model->getCommonName();
|
||||
|
||||
// Returns the model's 'displayname' attribute.
|
||||
$model->getDisplayName();
|
||||
|
||||
// Returns the model's 'samaccountname' attriubte.
|
||||
$model->getAccountName();
|
||||
|
||||
// Returns the model's 'samaccounttype` attribute.
|
||||
$model->getAccountType();
|
||||
|
||||
// Returns the model's 'whencreated` attribute.
|
||||
$model->getCreatedAt();
|
||||
|
||||
// Returns the model's 'whencreated` attribute in a MySQL timestamp format.
|
||||
$model->getCreatedAtDate();
|
||||
|
||||
// Returns the model's 'whencreated' attribute in unix time.
|
||||
$model->getCreatedAtTimestamp();
|
||||
|
||||
// Returns the model's 'whenchanged` attribute.
|
||||
$model->getUpdatedAt();
|
||||
|
||||
// Returns the model's 'whenchanged` attribute in a MySQL timestamp format.
|
||||
$model->getUpdatedAtDate();
|
||||
|
||||
// Returns the model's 'whenchanged` attribute in unix time.
|
||||
$model->getUpdatedAtTimestamp();
|
||||
|
||||
// Returns the model's 'objectclass' attribute.
|
||||
$model->getObjectClass();
|
||||
|
||||
// Returns the model's root object category string.
|
||||
$model->getObjectCategory();
|
||||
|
||||
// Returns the model's object category in an array.
|
||||
$model->getObjectCategoryArray();
|
||||
|
||||
// Returns the model's object category distinguished name.
|
||||
$model->getObjectCategoryDn();
|
||||
|
||||
// Returns the model's SID in binary.
|
||||
$model->getObjectSid();
|
||||
|
||||
// Returns the model's GUID in binary.
|
||||
$model->getObjectGuid();
|
||||
|
||||
// Returns the model's SID in a string.
|
||||
$model->getConvertedSid();
|
||||
|
||||
// Returns the model's GUID in a string.
|
||||
$model->getConvertedGuid();
|
||||
|
||||
// Returns the model's primary group ID.
|
||||
$model->getPrimaryGroupId();
|
||||
|
||||
// Returns the model's 'instancetype' attribute.
|
||||
$model->getInstanceType();
|
||||
|
||||
// Returns the model's 'maxpwdage' attribute.
|
||||
$model->getMaxPasswordAge();
|
||||
```
|
||||
|
||||
For more documentation on specific getters, please take a look at the relevant model documentation.
|
||||
|
||||
#### Getting Dirty (Modified) Attributes
|
||||
|
||||
You can get a models modified attributes using the `getDirty()` method:
|
||||
|
||||
```php
|
||||
$user = $provider->search()->users()->find('john');
|
||||
|
||||
// Returns array [0 => 'John Doe']
|
||||
var_dump($user->cn);
|
||||
|
||||
$user->setAttribute('cn', 'Jane Doe');
|
||||
|
||||
// Returns array ['cn' => [0 => 'Jane Doe']]
|
||||
var_dump($user->getDirty());
|
||||
|
||||
// The attribute has been modified - returns array [0 => 'Jane Doe']
|
||||
var_dump($user->cn);
|
||||
```
|
||||
|
||||
The method returns an array with the key being the modified attribute,
|
||||
and the array being the new values of the attribute.
|
||||
|
||||
#### Getting Original (Unmodified) Attributes
|
||||
|
||||
You can get a models original attributes using the `getOriginal()` method:
|
||||
|
||||
```php
|
||||
$user = $provider->search()->users()->find('john');
|
||||
|
||||
// Returns array [0 => 'John Doe']
|
||||
var_dump($user->cn);
|
||||
|
||||
$user->setAttribute('cn', 'Jane Doe');
|
||||
|
||||
// The attribute has been modified - returns array [0 => 'Jane Doe']
|
||||
var_dump($user->cn);
|
||||
|
||||
// Retrieving the original value - returns array [0 => 'John Doe']
|
||||
var_dump($user->getOriginal()['cn']);
|
||||
```
|
||||
|
||||
> **Note**: Keep in mind, when you `save()` a model, the models original
|
||||
> attributes will be re-synchronized to the models new attributes.
|
||||
|
||||
### Setting Attributes
|
||||
|
||||
Just like getting model attributes, there's multiple ways of setting attributes as well:
|
||||
|
||||
```php
|
||||
// Setting via method:
|
||||
$user->setAttribute('cn', 'John Doe');
|
||||
|
||||
// Specifying a subkey for overwriting specific attributes:
|
||||
$user->setAttribute('mail', 'other-mail@mail.com', 0);
|
||||
|
||||
// Setting the first attribute:
|
||||
$user->setFirstAttribute('mail', 'jdoe@mail.com');
|
||||
|
||||
// Setting via property:
|
||||
$user->cn = 'John Doe';
|
||||
|
||||
// Mass setting attributes:
|
||||
$user->fill([
|
||||
'cn' => 'John Doe',
|
||||
'mail' => 'jdoe@mail.com',
|
||||
]);
|
||||
```
|
||||
|
||||
#### Setting Boolean Attributes
|
||||
|
||||
When setting boolean attribute values, you cannot use `0` / `1` / `true` / `false` as these
|
||||
are simply converted to integer values when saving and your LDAP server will
|
||||
likely return an error for doing so on certain attributes.
|
||||
|
||||
You will need to use the string versions of the boolean (`'TRUE'` / `'FALSE'`) for the
|
||||
boolean attribute to be set properly on your LDAP server.
|
||||
|
||||
Here's an example:
|
||||
|
||||
```php
|
||||
$user->setFirstAttribute('msExchHideFromAddressLists', 'TRUE');
|
||||
|
||||
$user->save();
|
||||
```
|
||||
|
||||
### Creating Attributes
|
||||
|
||||
To create an attribute that does not exist on the model, you can set it like a regular property:
|
||||
|
||||
```php
|
||||
$user = $provider->search()->whereEquals('cn', 'John Doe')->firstOrFail();
|
||||
|
||||
$user->new = 'New Attribute';
|
||||
|
||||
$user->save();
|
||||
```
|
||||
|
||||
If the set attribute does not exist on the model already,
|
||||
it will automatically be created when you call the `save()` method.
|
||||
|
||||
If you'd like manually create new attributes individually, call the `createAttribute($attribute, $value)` method:
|
||||
|
||||
```php
|
||||
if ($user->createAttribute('new', 'New Attribute')) {
|
||||
// Attribute created.
|
||||
}
|
||||
```
|
||||
|
||||
### Updating Attributes
|
||||
|
||||
To modify an attribute you can either use a setter method, or by setting it manually:
|
||||
|
||||
> **Note**: You can also utilize setters to create new attributes if your model does not already have the attribute.
|
||||
|
||||
```php
|
||||
$user = $provider->search()->whereEquals('cn', 'John Doe')->firstOrFail();
|
||||
|
||||
$user->cn = 'New Name';
|
||||
|
||||
// Or use a setter:
|
||||
|
||||
$user->setCommonName('New Name');
|
||||
|
||||
$user->save();
|
||||
```
|
||||
|
||||
If you'd like to update attributes individually, call the `updateAttribute($attribute, $value)` method:
|
||||
|
||||
```php
|
||||
if ($user->updateAttribute('cn', 'New Name')) {
|
||||
// Successfully updated attribute.
|
||||
}
|
||||
```
|
||||
|
||||
### Removing Attributes
|
||||
|
||||
To remove attributes, set the attribute to `NULL`:
|
||||
|
||||
```php
|
||||
$user->cn = null;
|
||||
|
||||
$user->save();
|
||||
```
|
||||
|
||||
Or, you can call the `deleteAttribute($attribute)` method:
|
||||
|
||||
```php
|
||||
if ($user->deleteAttribute('cn')) {
|
||||
// Attribute has been deleted.
|
||||
}
|
||||
```
|
||||
|
||||
### Checking Attributes
|
||||
|
||||
#### Checking Existence of Attributes
|
||||
|
||||
To see if a model contains an attribute, use the method `hasAttribute()`:
|
||||
|
||||
```php
|
||||
// Checking if a base attribute exists:
|
||||
if ($user->hasAttribute('mail')) {
|
||||
// This user contains an email address.
|
||||
}
|
||||
|
||||
// Checking if a sub attribute exists, by key:
|
||||
if ($user->hasAttribute('mail', 1)) {
|
||||
// This user contains a second email address.
|
||||
}
|
||||
```
|
||||
|
||||
#### Counting the Models Attributes
|
||||
|
||||
To retrieve the total number of attributes, use the method `countAttributes()`:
|
||||
|
||||
```php
|
||||
$count = $user->countAttributes();
|
||||
|
||||
var_dump($count); // Returns int
|
||||
```
|
||||
|
||||
#### Checking if a Model is contained in an OU
|
||||
|
||||
To check if a model is located inside an OU, use the `inOu()` method:
|
||||
|
||||
```php
|
||||
if ($model->inOu('User Accounts')) {
|
||||
// This model is inside the 'User Accounts' OU.
|
||||
}
|
||||
```
|
||||
|
||||
You can also use an OU model instance:
|
||||
|
||||
```php
|
||||
$serviceAccounts = $provider->search()->ous()->find('Service Accounts');
|
||||
|
||||
if ($model->inOu($serviceAccounts)) {
|
||||
// This model is inside the 'Service Accounts' OU.
|
||||
}
|
||||
```
|
||||
|
||||
#### Checking if a Model is Writable
|
||||
|
||||
To check if the model can be written to, use the method `isWritable()`:
|
||||
|
||||
```php
|
||||
if ($model->isWritable()) {
|
||||
// You can modify this model.
|
||||
}
|
||||
```
|
||||
|
||||
### Force Re-Syncing A Models Attributes
|
||||
|
||||
If you need to forcefully re-sync a models attributes, use the method `syncRaw()`:
|
||||
|
||||
```php
|
||||
$user->syncRaw();
|
||||
```
|
||||
|
||||
> **Note**: This will query your LDAP server for the current model, and re-synchronize
|
||||
> it's attributes. This is only recommended if your creating / updating / deleting
|
||||
> attributes manually through your LDAP connection.
|
||||
|
||||
## Moving / Renaming
|
||||
|
||||
To move a user from one DN or OU to another, use the `move()` method:
|
||||
|
||||
> **Note**: The `move()` method is actually an alias for the `rename()` method.
|
||||
|
||||
```php
|
||||
// New parent distiguished name.
|
||||
$newParentDn = 'OU=New Ou,DC=corp,DC=local';
|
||||
|
||||
if ($user->move($newParentDn)) {
|
||||
// User was successfully moved to the new OU.
|
||||
}
|
||||
```
|
||||
|
||||
You can also provide a model to move the child model into:
|
||||
|
||||
```php
|
||||
// New parent OU.
|
||||
$newParentOu = $provider->search()->ous()->find('Accounting');
|
||||
|
||||
if ($user->move($newParentOu)) {
|
||||
// User was successfully moved to the new OU.
|
||||
}
|
||||
```
|
||||
|
||||
If you would like to keep the models old RDN along side their new RDN, pass in false in the second parameter:
|
||||
|
||||
```php
|
||||
// New parent distiguished name.
|
||||
$newParentDn = 'OU=New Ou,DC=corp,DC=local';
|
||||
|
||||
if ($user->move($newParentDn, $deleteOldRdn = false)) {
|
||||
// User was successfully moved to the new OU,
|
||||
// and their old RDN has been left in-tact.
|
||||
}
|
||||
```
|
||||
|
||||
To rename a users DN, just pass in their new relative distinguished name in the `rename()` method:
|
||||
|
||||
```php
|
||||
$newRdn = 'cn=New Name';
|
||||
|
||||
if ($user->rename($newRdn)) {
|
||||
// User was successfully renamed.
|
||||
}
|
||||
```
|
||||
|
||||
## Deleting
|
||||
|
||||
To delete a model, just call the `delete()` method:
|
||||
|
||||
```php
|
||||
$user = $provider->search()->whereEquals('cn', 'John Doe')->firstOrFail();
|
||||
|
||||
echo $user->exists; // Returns true.
|
||||
|
||||
if ($user->delete()) {
|
||||
// Successfully deleted user.
|
||||
|
||||
echo $user->exists; // Returns false.
|
||||
}
|
||||
```
|
||||
|
||||
## Extending
|
||||
|
||||
> **Note**: This feature was introduced in `v8.0.0`.
|
||||
|
||||
To use your own models, you will need to create a new [Schema](../schema.md).
|
||||
|
||||
Once you have created your own schema, you must insert it inside the construct of your provider.
|
||||
|
||||
Let's walk through this process.
|
||||
|
||||
First we'll create our model we'd like to extend / override:
|
||||
|
||||
> **Note**: Your custom model **must** extend from an existing Adldap2 model.
|
||||
> This is due to methods and attributes that only exist on these classes.
|
||||
|
||||
```php
|
||||
namespace App\Ldap\Models;
|
||||
|
||||
use Adldap\Models\User as Model;
|
||||
|
||||
class User extends Model
|
||||
{
|
||||
public function getCommonName()
|
||||
{
|
||||
// Overriding model method.
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Now, we'll create our custom schema and return our models class name:
|
||||
|
||||
```php
|
||||
namespace App\Ldap\Schemas;
|
||||
|
||||
use App\Ldap\Models\User;
|
||||
|
||||
class LdapSchema extends ActiveDirectory
|
||||
{
|
||||
public function userModel()
|
||||
{
|
||||
return User::class;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Finally, when we create a provider, we need to insert our Schema into the configuration:
|
||||
|
||||
```php
|
||||
$config = [
|
||||
'hosts' => ['...'],
|
||||
|
||||
'username' => 'admin',
|
||||
'password' => 'P@ssword',
|
||||
|
||||
'schema' => MyApp\LdapSchema::class,
|
||||
];
|
||||
|
||||
$ad = new Adldap($config);
|
||||
|
||||
$provider = $ad->connect();
|
||||
|
||||
// If `jdoe` exists, your custom model will be returned.
|
||||
$user = $provider->search()->users()->find('jdoe');
|
||||
```
|
||||
19
data/web/inc/lib/vendor/adldap2/adldap2/docs/models/organization.md
vendored
Executable file
19
data/web/inc/lib/vendor/adldap2/adldap2/docs/models/organization.md
vendored
Executable file
|
|
@ -0,0 +1,19 @@
|
|||
# The Organization Model
|
||||
|
||||
The Organization model extends from the base `Adldap\Models\Model` class and contains
|
||||
no specific methods / attributes that are limited to it.
|
||||
|
||||
## Creation
|
||||
|
||||
```php
|
||||
// Adldap\Models\Organization
|
||||
$org = $provider->make()->organization([
|
||||
'o' => 'Some Company',
|
||||
]);
|
||||
|
||||
// Set the DN manually:
|
||||
|
||||
$org->setDn('o=Some Company,dc=test,dc=local,dc=com');
|
||||
|
||||
$org->save();
|
||||
```
|
||||
27
data/web/inc/lib/vendor/adldap2/adldap2/docs/models/ou.md
vendored
Executable file
27
data/web/inc/lib/vendor/adldap2/adldap2/docs/models/ou.md
vendored
Executable file
|
|
@ -0,0 +1,27 @@
|
|||
# The OrganizationalUnit Model
|
||||
|
||||
The OrganizationalUnit model extends from the base `Adldap\Models\Model` class and contains
|
||||
no specific methods / attributes that are limited to it.
|
||||
|
||||
## Creation
|
||||
|
||||
```php
|
||||
// Adldap\Models\OrganizationalUnit
|
||||
$ou = $provider->make()->ou([
|
||||
'name' => 'Workstation Computers',
|
||||
]);
|
||||
|
||||
// Generate the OU's DN through the DN Builder:
|
||||
|
||||
$dn = $ou->getDnBuilder();
|
||||
|
||||
$dn->addOu('Workstation Computers');
|
||||
|
||||
$ou->setDn($dn);
|
||||
|
||||
// Or set the DN manually:
|
||||
|
||||
$ou->setDn('ou=Workstation Computers,dc=test,dc=local,dc=com');
|
||||
|
||||
$ou->save();
|
||||
```
|
||||
49
data/web/inc/lib/vendor/adldap2/adldap2/docs/models/printer.md
vendored
Executable file
49
data/web/inc/lib/vendor/adldap2/adldap2/docs/models/printer.md
vendored
Executable file
|
|
@ -0,0 +1,49 @@
|
|||
# The Printer Model
|
||||
|
||||
## Methods
|
||||
|
||||
```php
|
||||
$printer->getPrinterName();
|
||||
|
||||
$printer->getPrinterShareName();
|
||||
|
||||
$printer->getMemory();
|
||||
|
||||
$printer->getUrl();
|
||||
|
||||
$printer->getLocation();
|
||||
|
||||
$printer->getServerName();
|
||||
|
||||
$printer->getColorSupported();
|
||||
|
||||
$printer->getDuplexSupported();
|
||||
|
||||
$printer->getMediaSupported();
|
||||
|
||||
$printer->getStaplingSupported();
|
||||
|
||||
$printer->getPrintBinNames();
|
||||
|
||||
$printer->getPrintMaxResolution();
|
||||
|
||||
$printer->getPrintOrientations();
|
||||
|
||||
$printer->getDriverName();
|
||||
|
||||
$printer->getDriverVersion();
|
||||
|
||||
$printer->getPriority();
|
||||
|
||||
$printer->getPrintStartTime();
|
||||
|
||||
$printer->getPrintEndTime();
|
||||
|
||||
$printer->getPortName();
|
||||
|
||||
$printer->getVersionNumber();
|
||||
|
||||
$printer->getPrintRate();
|
||||
|
||||
$printer->getPrintRateUnit();
|
||||
```
|
||||
33
data/web/inc/lib/vendor/adldap2/adldap2/docs/models/root-dse.md
vendored
Executable file
33
data/web/inc/lib/vendor/adldap2/adldap2/docs/models/root-dse.md
vendored
Executable file
|
|
@ -0,0 +1,33 @@
|
|||
# The RootDse Model
|
||||
|
||||
## Getting the Root DSE
|
||||
|
||||
To get the Root DSE of your LDAP server, call the `getRootDse()` method off a new search:
|
||||
|
||||
```php
|
||||
$rootDse = $provider->search()->getRootDse();
|
||||
```
|
||||
|
||||
## Getting the schema naming context
|
||||
|
||||
To get the Root DSE schema naming context, call the `getSchemaNamingContext()`:
|
||||
|
||||
```php
|
||||
$rootDse = $provider->search()->getRootDse();
|
||||
|
||||
$context = $rootDse->getSchemaNamingContext();
|
||||
|
||||
// Returns 'cn=Schema,cn=Configuration,dc=corp,dc=acme,dc=org'
|
||||
echo $context;
|
||||
```
|
||||
|
||||
## Getting the root domain naming context
|
||||
|
||||
To get the Root DSE domain naming context, call the `getRootDomainNamingContext()`:
|
||||
|
||||
```php
|
||||
$context = $rootDse->getRootDomainNamingContext();
|
||||
|
||||
// Returns 'dc=corp,dc=acme,dc=org'
|
||||
echo $context;
|
||||
```
|
||||
13
data/web/inc/lib/vendor/adldap2/adldap2/docs/models/traits/has-critical-system-object.md
vendored
Executable file
13
data/web/inc/lib/vendor/adldap2/adldap2/docs/models/traits/has-critical-system-object.md
vendored
Executable file
|
|
@ -0,0 +1,13 @@
|
|||
# HasCriticalSystemObject Trait
|
||||
|
||||
Models that contain this trait, have the `isCriticalSystemObject` attribute.
|
||||
|
||||
There is only one method that accompanies this trait:
|
||||
|
||||
```php
|
||||
if ($model->isCriticalSystemObject()) {
|
||||
|
||||
//
|
||||
|
||||
}
|
||||
```
|
||||
11
data/web/inc/lib/vendor/adldap2/adldap2/docs/models/traits/has-description.md
vendored
Executable file
11
data/web/inc/lib/vendor/adldap2/adldap2/docs/models/traits/has-description.md
vendored
Executable file
|
|
@ -0,0 +1,11 @@
|
|||
# HasDescription Trait
|
||||
|
||||
Models that contain this trait, have the `description` attribute.
|
||||
|
||||
There are only two methods that accompany this trait:
|
||||
|
||||
```php
|
||||
$model->getDescription();
|
||||
|
||||
$model->setDescription('The models description');
|
||||
```
|
||||
16
data/web/inc/lib/vendor/adldap2/adldap2/docs/models/traits/has-last-login-last-logoff.md
vendored
Executable file
16
data/web/inc/lib/vendor/adldap2/adldap2/docs/models/traits/has-last-login-last-logoff.md
vendored
Executable file
|
|
@ -0,0 +1,16 @@
|
|||
# HasLastLoginAndLastLogoff Trait
|
||||
|
||||
Models that contain this trait have the `lastlogoff`, `lastlogon` and `lastlogontimestamp` attributes.
|
||||
|
||||
## Methods
|
||||
|
||||
```php
|
||||
// Returns the models's last log off attribute.
|
||||
$computer->getLastLogOff();
|
||||
|
||||
// Returns the models's last log on attribute.
|
||||
$computer->getLastLogon();
|
||||
|
||||
// Returns the models's last log on timestamp attribute.
|
||||
$computer->getLastLogonTimestamp();
|
||||
```
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue