Imported code from old repository

This commit is contained in:
2022-11-17 11:48:29 +01:00
parent 14dfa45240
commit 0e4ab60d77
31 changed files with 6486 additions and 253 deletions

4
.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
vendor
build
cache
*.phar

View File

@@ -0,0 +1,79 @@
<?php
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
namespace CloudObjects\SDK\AccountGateway;
use ML\IRI\IRI;
class AAUIDParser {
const AAUID_INVALID = 0;
const AAUID_ACCOUNT = 1;
const AAUID_CONNECTION = 2;
const AAUID_CONNECTED_ACCOUNT = 3;
const REGEX_AAUID = "/^[a-z0-9]{16}$/";
const REGEX_QUALIFIER = "/^[A-Z]{2}$/";
/**
* Creates a new IRI object representing a AAUID from a string.
* Adds the "aauid:" prefix if necessary.
*
* @param string $aauidString An AAUID string.
* @return IRI
*/
public static function fromString($aauidString) {
return new IRI(
(substr($aauidString, 0, 6)=='aauid:') ? $aauidString : 'aauid:'.$aauidString
);
}
public static function getType(IRI $iri) {
if ($iri->getScheme()!='aauid' || $iri->getPath()=='')
return self::AAUID_INVALID;
$segments = explode(':', $iri->getPath());
switch (count($segments)) {
case 1:
return (preg_match(self::REGEX_AAUID, $segments[0]) == 1)
? self::AAUID_ACCOUNT
: self::AAUID_INVALID;
case 3;
if (preg_match(self::REGEX_AAUID, $segments[0]) != 1
|| preg_match(self::REGEX_QUALIFIER, $segments[2]) != 1)
return self::AAUID_INVALID;
switch ($segments[1]) {
case "connection":
return self::AAUID_CONNECTION;
case "account":
return self::AAUID_CONNECTED_ACCOUNT;
default:
return self::AAUID_INVALID;
}
default:
return self::AAUID_INVALID;
}
}
public static function getAAUID(IRI $iri) {
if (self::getType($iri)!=self::AAUID_INVALID) {
$segments = explode(':', $iri->getPath());
return $segments[0];
} else
return null;
}
public static function getQualifier(IRI $iri) {
if (self::getType($iri)==self::AAUID_CONNECTION
|| self::getType($iri)==self::AAUID_CONNECTED_ACCOUNT) {
$segments = explode(':', $iri->getPath());
return $segments[2];
} else
return null;
}
}

View File

@@ -0,0 +1,369 @@
<?php
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
namespace CloudObjects\SDK\AccountGateway;
use ML\IRI\IRI;
use ML\JsonLD\Document, ML\JsonLD\JsonLD, ML\JsonLD\Node;
use Symfony\Component\HttpFoundation\Request, Symfony\Component\HttpFoundation\Response;
use Symfony\Bridge\PsrHttpMessage\Factory\DiactorosFactory;
use GuzzleHttp\Client, GuzzleHttp\HandlerStack, GuzzleHttp\Middleware;
use Psr\Http\Message\RequestInterface, Psr\Http\Message\ResponseInterface;
/**
* The context of an request for an account.
*/
class AccountContext {
private $agwBaseUrl = 'https://{aauid}.aauid.net';
private $aauid;
private $accessToken;
private $dataLoader;
private $accountDomain = null;
private $connectionQualifier = null;
private $installQualifier = null;
private $accessor = null;
private $latestAccessorVersionCOID = null;
private $request; // optional
private $document;
private $client;
private $logCode = null;
/**
* Create a new context using an AAUID and an OAuth 2.0 bearer access token.
*/
public function __construct(IRI $aauid, $accessToken, DataLoader $dataLoader = null) {
if (AAUIDParser::getType($aauid) != AAUIDParser::AAUID_ACCOUNT)
throw new \Exception("Not a valid AAUID");
$this->aauid = $aauid;
$this->accessToken = $accessToken;
if ($dataLoader) {
$this->dataLoader = $dataLoader;
} else {
$this->dataLoader = new DataLoader;
}
}
private function parseHeaderIntoNode($headerName, Node $node) {
$keyValuePairs = explode(',', $this->request->getHeaderLine($headerName));
foreach ($keyValuePairs as $pair) {
$keyValue = explode('=', $pair);
$node->addPropertyValue($keyValue[0], urldecode($keyValue[1]));
}
}
private function parsePsrRequest(RequestInterface $request) {
$this->request = $request;
if ($request->hasHeader('C-Accessor')) {
// Store COID of Accessor
$this->accessor = new IRI($request->getHeaderLine('C-Accessor'));
}
if ($request->hasHeader('C-Account-Domain')) {
// Store account domain
$this->accountDomain = $request->getHeaderLine('C-Account-Domain');
}
if ($request->hasHeader('C-Accessor-Latest-Version')) {
// A new version of thie accessor is available, store its COID
$this->latestAccessorVersionCOID = new IRI($request
->getHeaderLine('C-Accessor-Latest-Version'));
}
if ($request->hasHeader('C-Account-Connection')) {
// For access from connected accounts, store qualifier
$this->connectionQualifier = $request->getHeaderLine('C-Account-Connection');
}
if ($request->hasHeader('C-Install-Connection')) {
// For access from applications, store qualifier
$this->installQualifier = $request->getHeaderLine('C-Install-Connection');
}
if ($request->hasHeader('C-Connection-Data')) {
// Copy Data into document
if (!$this->document) $this->document = new Document();
$this->parseHeaderIntoNode('C-Connection-Data',
$this->document->getGraph()->createNode('aauid:'.$this->getAAUID().':connection:'.$this->connectionQualifier));
}
}
/**
* Create a new context from the current request.
*
* @param Request $request
*/
public static function fromSymfonyRequest(Request $request) {
if (!$request->headers->has('C-AAUID') || !$request->headers->has('C-Access-Token'))
return null;
$context = new AccountContext(
new IRI('aauid:'.$request->headers->get('C-AAUID')),
$request->headers->get('C-Access-Token'));
$psr7Factory = new DiactorosFactory;
$context->parsePsrRequest($psr7Factory->createRequest($request));
return $context;
}
/**
* Create a new context from the current request.
*
* @param RequestInterface $request
*/
public static function fromPsrRequest(RequestInterface $request) {
if (!$request->hasHeader('C-AAUID') || !$request->hasHeader('C-Access-Token'))
return null;
$context = new AccountContext(
new IRI('aauid:'.$request->getHeaderLine('C-AAUID')),
$request->getHeaderLine('C-Access-Token'));
$context->parsePsrRequest($request);
return $context;
}
public function getAAUID() {
return $this->aauid;
}
public function getAccessToken() {
return $this->accessToken;
}
public function getRequest() {
return $this->request;
}
public function getDataLoader() {
return $this->dataLoader;
}
private function getDocument() {
if (!$this->document) {
$this->document = $this->dataLoader->fetchAccountGraphDataDocument($this);
}
return $this->document;
}
public function getAccount() {
return $this->getDocument()->getGraph()->getNode($this->getAAUID());
}
public function getPerson() {
return $this->getDocument()->getGraph()->getNode($this->getAAUID().':person');
}
/**
* Checks whether the context uses an account connection, which is the case when an API
* is requested by a connected account on another service.
*/
public function usesAccountConnection() {
return ($this->connectionQualifier !== null);
}
/**
* Get the qualifier of the account connection used for accessing the API.
*/
public function getConnectionQualifier() {
return $this->connectionQualifier;
}
/**
* Get the qualifier for the connection to the platform service.
* Only available when the accessor is an application.
*/
public function getInstallQualifier() {
return $this->installQualifier;
}
/**
* Get the accessor.
*/
public function getAccessorCOID() {
return $this->accessor;
}
/**
* Get the account's domain.
* Only set from external API requests, null otherwise.
*
* @return string|null
*/
public function getAccountDomain() {
return $this->accountDomain;
}
/**
* Get a connected account.
* @param $qualifier The qualifier for the account connection. If not specified, uses the connection qualifier.
*/
public function getConnectedAccount($qualifier = null) {
if (!$qualifier) $qualifier = $this->getConnectionQualifier();
if (!$qualifier) return null;
return $this->getDocument()->getGraph()->getNode($this->getAAUID().':account:'.$qualifier);
}
/**
* Get an account connection.
* @param $qualifier The qualifier for the account connection. If not specified, uses the connection qualifier.
*/
public function getAccountConnection($qualifier = null) {
if (!$qualifier) $qualifier = $this->getConnectionQualifier();
if (!$qualifier) return null;
return $this->getDocument()->getGraph()->getNode($this->getAAUID().':connection:'.$qualifier);
}
/**
* Get the connected account for a service.
* @param $service COID of the service
*/
public function getConnectedAccountForService($service) {
$accounts = $this->getDocument()->getGraph()->getNodesByType('coid://aauid.net/Account');
foreach ($accounts as $a) {
if ($a->getProperty('coid://aauid.net/isForService')
&& $a->getProperty('coid://aauid.net/isForService')->getId()==$service) return $a;
}
return null;
}
/**
* Get all account connections.
*/
public function getAllAccountConnections() {
$connections = $this->getAccount()->getProperty('coid://aauid.net/hasConnection');
if (!is_array($connections)) $connections = array($connections);
return $connections;
}
/**
* Get all connected accounts.
*/
public function getAllConnectedAccounts() {
$accounts = array();
foreach ($this->getAllAccountConnections() as $ac) {
$accounts[] = $ac->getProperty('coid://aauid.net/connectsTo');
}
return $accounts;
}
/**
* Pushes changes on the Account Graph into the Account Graph.
*/
public function pushGraphUpdates() {
$this->getClient()->post('/~/', [
'headers' => ['Content-Type' => 'application/ld+json'],
'body' => JsonLD::toString($this->getDocument()->toJsonLd())
]);
}
/**
* Specifies a template for the Account Gateway Base URL. Must be a valid URL that
* may contain an {aauid} placeholder. Call this if you want to redirect traffic
* through a proxy or a staging or mock instance of an Account Gateway. Most users
* of this SDK should never call this function.
*/
public function setAccountGatewayBaseURLTemplate($baseUrl) {
$this->agwBaseUrl = $baseUrl;
}
/**
* Get a preconfigured Guzzle client to access the Account Gateway.
* @return Client
*/
public function getClient() {
if (!$this->client) {
// Create custom handler stack with middlewares
$stack = HandlerStack::create();
$context = $this;
$stack->push(Middleware::mapResponse(function (ResponseInterface $response) use ($context) {
// If a new version of this accessor is available, store its COID
if ($response->hasHeader('C-Accessor-Latest-Version'))
$context->setLatestAccessorVersionCOID(
new IRI($response->getHeaderLine('C-Accessor-Latest-Version')));
return $response;
}));
// Prepare client options
$options = [
'base_uri' => str_replace('{aauid}', AAUIDParser::getAAUID($this->getAAUID()), $this->agwBaseUrl),
'headers' => [
'Authorization' => 'Bearer '.$this->getAccessToken()
],
'handler' => $stack
];
if (isset($this->request) && $this->request->hasHeader('X-Forwarded-For')) {
$options['headers']['X-Forwarded-For'] = $this->request->getHeaderLine('X-Forwarded-For');
}
// Create client
$this->client = new Client($options);
}
return $this->client;
}
/**
* Set a custom code for the current request in the Account Gateway logs.
*/
public function setLogCode($logCode) {
if (!$this->request) {
throw new \Exception('Not in a request context.');
}
$this->logCode = $logCode;
}
/**
* Process a response and add headers if applicable.
*/
public function processResponse(Response $response) {
if ($this->logCode) {
$response->headers->set('C-Code-For-Logger', $this->logCode);
}
}
/**
* Check whether a new version of the accessor is available. This information
* is updated from incoming and outgoing requests. If no request was executed,
* returns false.
*
* @return boolean
*/
public function isNewAccessorVersionAvailable() {
return isset($this->latestAccessorVersionCOID);
}
/**
* Get the COID of the latest accessor version, if one is available, or
* null otherwise. This information is updated from incoming and outgoing
* requests. If no request was executed, returns null.
*
* @return IRI|null
*/
public function getLatestAccessorVersionCOID() {
return $this->latestAccessorVersionCOID;
}
/**
* Set the COID of the latest accessor version. This method should only
* called from request processing codes. Most developers should not use it.
*
* @param IRI $latestAccessorVersionCOID
*/
public function setLatestAccessorVersionCOID(IRI $latestAccessorVersionCOID) {
$this->latestAccessorVersionCOID = $latestAccessorVersionCOID;
}
}

View File

@@ -0,0 +1,81 @@
<?php
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
namespace CloudObjects\SDK\AccountGateway;
use Doctrine\Common\Cache\Cache;
use ML\JsonLD\JsonLD;
use GuzzleHttp\Psr7\Request;
class DataLoader {
const CACHE_TTL = 172800; // cache at most 48 hours
private $cache;
private $cachePrefix = 'accdata:';
private $mountPointName = '~';
public function getCache() {
return $this->cache;
}
public function setCache(Cache $cache) {
$this->cache = $cache;
return $this;
}
public function getCachePrefix() {
return $this->cachePrefix;
}
public function setCachePrefix($cachePrefix) {
$this->cachePrefix = $cachePrefix;
return $this;
}
public function getMountPointName() {
return $this->mountPointName;
}
public function setMountPointName($mountPointName) {
$this->mountPointName = $mountPointName;
return $this;
}
public function fetchAccountGraphDataDocument(AccountContext $accountContext) {
$dataRequest = new Request('GET', '/'.$this->mountPointName.'/',
['Accept' => 'application/ld+json']);
if (!$this->cache || !$accountContext->getRequest()
|| !$accountContext->getRequest()->hasHeader('C-Data-Updated')) {
// No cache or no timestamp available, so always fetch from Account Gateway
$dataString = (string)$accountContext->getClient()->send($dataRequest)->getBody();
} else {
$key = $this->cachePrefix.$accountContext->getAAUID();
$remoteTimestamp = $accountContext->getRequest()->getHeaderLine('C-Data-Updated');
if ($this->cache->contains($key)) {
// Check timestamp
$cacheEntry = $this->cache->fetch($key);
$timestamp = substr($cacheEntry, 0, strpos($cacheEntry, '|'));
if ($timestamp==$remoteTimestamp) {
// Cache data is up to date, can be returned
$dataString = substr($cacheEntry, strpos($cacheEntry, '|')+1);
} else {
// Fetch from Account Gateway and update cache entry
$dataString = (string)$accountContext->getClient()->send($dataRequest)->getBody();
$this->cache->save($key, $remoteTimestamp.'|'.$dataString, self::CACHE_TTL);
}
} else {
// Fetch from Account Gateway and store in cache
$dataString = (string)$accountContext->getClient()->send($dataRequest)->getBody();
$this->cache->save($key, $remoteTimestamp.'|'.$dataString, self::CACHE_TTL);
}
}
return JsonLD::getDocument($dataString);
}
}

View File

@@ -0,0 +1,152 @@
<?php
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
namespace CloudObjects\SDK;
use ML\IRI\IRI;
/**
* The COIDParser can be used to validate COIDs and extract information.
*/
class COIDParser {
const COID_INVALID = 0;
const COID_ROOT = 1;
const COID_UNVERSIONED = 2;
const COID_VERSIONED = 3;
const COID_VERSION_WILDCARD = 4;
const REGEX_HOSTNAME = "/^([a-z0-9-]+\.)?[a-z0-9-]+\.[a-z]+$/";
const REGEX_SEGMENT = "/^[A-Za-z-_0-9\.]+$/";
const REGEX_VERSION_WILDCARD = "/^((\^|~)(\d+\.)?\d|(\d+\.){1,2}\*)$/";
/**
* Creates a new IRI object representing a COID from a string.
* Adds the "coid://" prefix if necessary and normalizes case.
*
* @param string $coidString A COID string.
* @return IRI
*/
public static function fromString($coidString) {
$coidPre = new IRI(
(strtolower(substr($coidString, 0, 7))=='coid://') ? $coidString : 'coid://'.$coidString
);
// Normalize scheme and host segments to lower case
return new IRI('coid://'.strtolower($coidPre->getHost()).$coidPre->getPath());
}
/**
* Get the type of a COID.
*
* @param IRI $coid
* @return int|null
*/
public static function getType(IRI $coid) {
if ($coid->getScheme()!='coid' || $coid->getHost()==''
|| preg_match(self::REGEX_HOSTNAME, $coid->getHost()) != 1)
return self::COID_INVALID;
if ($coid->getPath()=='' || $coid->getPath()=='/')
return self::COID_ROOT;
$segments = explode('/', $coid->getPath());
switch (count($segments)) {
case 2:
return (preg_match(self::REGEX_SEGMENT, $segments[1]) == 1)
? self::COID_UNVERSIONED
: self::COID_INVALID;
case 3:
if (preg_match(self::REGEX_SEGMENT, $segments[1]) != 1)
return self::COID_INVALID;
if (preg_match(self::REGEX_SEGMENT, $segments[2]) == 1)
return self::COID_VERSIONED;
else
if (preg_match(self::REGEX_VERSION_WILDCARD, $segments[2]) == 1)
return self::COID_VERSION_WILDCARD;
else
return self::COID_INVALID;
default:
return self::COID_INVALID;
}
}
/**
* Checks whether the given IRI object is a valid COID.
*
* @param IRI $coid
* @return boolean
*/
public static function isValidCOID(IRI $coid) {
return (self::getType($coid)!=self::COID_INVALID);
}
/**
* Get the name segment of a valid COID or null if not available.
*
* @param IRI $coid
* @return string|null
*/
public static function getName(IRI $coid) {
if (self::getType($coid)!=self::COID_INVALID
&& self::getType($coid)!=self::COID_ROOT) {
$segments = explode('/', $coid->getPath());
return $segments[1];
} else
return null;
}
/**
* Get the version segment of a valid, versioned COID or null if not available.
*
* @param IRI $coid
* @return string|null
*/
public static function getVersion(IRI $coid) {
if (self::getType($coid)==self::COID_VERSIONED) {
$segments = explode('/', $coid->getPath());
return $segments[2];
} else
return null;
}
/**
* Get the version segment of a versioned or version wildcard COID or
* null if not available.
*
* @param IRI $coid
* @return string|null
*/
public static function getVersionWildcard(IRI $coid) {
if (self::getType($coid)==self::COID_VERSION_WILDCARD) {
$segments = explode('/', $coid->getPath());
return $segments[2];
} else
return null;
}
/**
* Returns the COID itself if it is a root COID or a new IRI object
* representing the namespace underlying the given COID.
*
* @param IRI $coid
* @return IRI|null
*/
public static function getNamespaceCOID(IRI $coid) {
switch (self::getType($coid)) {
case self::COID_ROOT:
return $coid;
case self::COID_UNVERSIONED:
case self::COID_VERSIONED:
case self::COID_VERSION_WILDCARD:
return new IRI('coid://'.$coid->getHost());
default:
return null;
}
}
}

View File

@@ -0,0 +1,72 @@
<?php
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
namespace CloudObjects\SDK\Common;
use Exception;
use ML\IRI\IRI;
use CloudObjects\SDK\NodeReader;
use Defuse\Crypto\Key, Defuse\Crypto\Crypto;
use CloudObjects\SDK\COIDParser, CloudObjects\SDK\ObjectRetriever;
use CloudObjects\SDK\Exceptions\InvalidObjectConfigurationException;
/**
* The crypto helper can be used to encrypt or decrypt data with
* the defuse PHP encryption library.
*/
class CryptoHelper {
private $objectRetriever;
private $namespace;
private $reader;
/**
* Gets a key based on the coid://common.cloudobjects.io/usesSharedEncryptionKey value
* for the default namespace.
*/
public function getSharedEncryptionKey() {
$keyValue = $this->reader->getFirstValueString($this->namespace, 'common:usesSharedEncryptionKey');
if (!isset($keyValue))
throw new InvalidObjectConfigurationException("The namespace doesn't have an encryption key.");
return Key::loadFromAsciiSafeString($keyValue);
}
/**
* Encrypt data with the default namespace's shared encryption key.
*/
public function encryptWithSharedEncryptionKey($data) {
return Crypto::encrypt($data, $this->getSharedEncryptionKey());
}
/**
* Decrypt data with the default namespace's shared encryption key.
*/
public function decryptWithSharedEncryptionKey($data) {
return Crypto::decrypt($data, $this->getSharedEncryptionKey());
}
/**
* @param ObjectRetriever $objectRetriever An initialized and authenticated object retriever.
* @param IRI|null $namespaceCoid The namespace used to retrieve keys. If this parameter is not provided, the namespace provided with the "auth_ns" configuration option from the object retriever is used.
*/
public function __construct(ObjectRetriever $objectRetriever, IRI $namespaceCoid = null) {
if (!class_exists('Defuse\Crypto\Crypto'))
throw new Exception("Run composer require defuse/php-encryption before using CryptoHelper.");
$this->objectRetriever = $objectRetriever;
$this->namespace = isset($namespaceCoid)
? $objectRetriever->getObject($namespaceCoid)
: $objectRetriever->getAuthenticatingNamespaceObject();
$this->reader = new NodeReader([
'prefixes' => [
'common' => 'coid://common.cloudobjects.io/'
]
]);
}
}

View File

@@ -0,0 +1,14 @@
<?php
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
namespace CloudObjects\SDK\Exceptions;
/**
* An Exception that is thrown when the Core API returned an error.
*/
class CoreAPIException extends \Exception {
}

View File

@@ -0,0 +1,15 @@
<?php
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
namespace CloudObjects\SDK\Exceptions;
/**
* An Exception that is thrown when an object's configuration
* doesn't match the client's expectations.
*/
class InvalidObjectConfigurationException extends \Exception {
}

View File

@@ -0,0 +1,85 @@
<?php
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
namespace CloudObjects\SDK\Helpers;
use Exception;
use CloudObjects\SDK\NodeReader, CloudObjects\SDK\ObjectRetriever;
/**
* The SDKLoader helper allows developers to quickly load common PHP SDKs
* from API providers and apply configuration stored in CloudObjects.
*/
class SDKLoader {
private $objectRetriever;
private $reader;
private $classes = [];
/**
* @param ObjectRetriever $objectRetriever An initialized and authenticated object retriever.
*/
public function __construct(ObjectRetriever $objectRetriever) {
$this->objectRetriever = $objectRetriever;
$this->reader = new NodeReader;
}
/**
* Initialize and return the SDK with the given classname.
* Throws Exception if the SDK is not supported.
*
* @param $classname Classname for the SDK's main class
* @param array $options Additional options for the SDK (if necessary)
*/
public function get($classname, array $options) {
if (!class_exists($classname))
throw new Exception("<".$classname."> is not a valid classname.");
$hashkey = md5($classname.serialize($options));
if (!isset($this->classes[$hashkey])) {
$nsNode = $this->objectRetriever->getAuthenticatingNamespaceObject();
// --- Amazon Web Services (https://aws.amazon.com/) ---
// has multiple classnames, so check for common superclass
if (is_a($classname, 'Aws\AwsClient', true)) {
$class = new $classname(array_merge($options, [
'credentials' => [
'key' => $this->reader->getFirstValueString($nsNode, 'coid://aws.3rd-party.co/accessKeyId'),
'secret' => $this->reader->getFirstValueString($nsNode, 'coid://aws.3rd-party.co/secretAccessKey')
]
]));
} else {
switch ($classname) {
// --- stream (https://getstream.io/) ---
case "GetStream\Stream\Client":
$class = new $classname(
$this->reader->getFirstValueString($nsNode, 'coid://getstreamio.3rd-party.co/key'),
$this->reader->getFirstValueString($nsNode, 'coid://getstreamio.3rd-party.co/secret')
);
break;
// --- Pusher (https://pusher.com/) ---
case "Pusher":
$class = new $classname(
$this->reader->getFirstValueString($nsNode, 'coid://pusher.3rd-party.co/key'),
$this->reader->getFirstValueString($nsNode, 'coid://pusher.3rd-party.co/secret'),
$this->reader->getFirstValueString($nsNode, 'coid://pusher.3rd-party.co/appId'),
$options
);
break;
}
}
}
if (!isset($class))
throw new Exception("No rules defined to initialize <".$classname.">.");
$this->classes[$hashkey] = $class;
return $this->classes[$hashkey];
}
}

View File

@@ -0,0 +1,110 @@
<?php
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
namespace CloudObjects\SDK\Helpers;
use ML\IRI\IRI;
use CloudObjects\SDK\COIDParser, CloudObjects\SDK\NodeReader, CloudObjects\SDK\ObjectRetriever;
/**
* The SharedSecretAuthentication helper allows developers to quickly
* implement authentication based on CloudObjects shared secrets.
*/
class SharedSecretAuthentication {
const RESULT_OK = 0;
const RESULT_INVALID_USERNAME = 1;
const RESULT_INVALID_PASSWORD = 2;
const RESULT_NAMESPACE_NOT_FOUND = 3;
const RESULT_SHARED_SECRET_NOT_RETRIEVABLE = 4;
const RESULT_SHARED_SECRET_INCORRECT = 5;
private $objectRetriever;
/**
* @param ObjectRetriever $objectRetriever An initialized and authenticated object retriever.
*/
public function __construct(ObjectRetriever $objectRetriever) {
$this->objectRetriever = $objectRetriever;
}
/**
* Verifies credentials.
* @deprecated
*
* @param ObjectRetriever $retriever Provides access to CloudObjects.
* @param string $username Username; a domain.
* @param string $password Password; a shared secret.
*
* @return integer A result constant, RESULT_OK if successful.
*/
public static function verifyCredentials(ObjectRetriever $retriever, $username, $password) {
// Validate input
$namespaceCoid = new IRI('coid://'.$username);
if (COIDParser::getType($namespaceCoid) != COIDParser::COID_ROOT)
return self::RESULT_INVALID_USERNAME;
if (strlen($password) != 40)
return self::RESULT_INVALID_PASSWORD;
// Retrieve namespace
$namespace = $retriever->getObject($namespaceCoid);
if (!isset($namespace))
return self::RESULT_NAMESPACE_NOT_FOUND;
// Read and validate shared secret
$reader = new NodeReader([
'prefixes' => [
'co' => 'coid://cloudobjects.io/'
]
]);
$sharedSecret = $reader->getAllValuesNode($namespace, 'co:hasSharedSecret');
if (count($sharedSecret) != 1)
return self::RESULT_SHARED_SECRET_NOT_RETRIEVABLE;
if ($reader->getFirstValueString($sharedSecret[0], 'co:hasTokenValue') == $password)
return self::RESULT_OK;
else
return self::RESULT_SHARED_SECRET_INCORRECT;
}
/**
* Verifies credentials.
*
* @param string $username Username; a domain.
* @param string $password Password; a shared secret.
*
* @return integer A result constant, RESULT_OK if successful.
*/
public function verify($username, $password) {
// Validate input
$namespaceCoid = new IRI('coid://'.$username);
if (COIDParser::getType($namespaceCoid) != COIDParser::COID_ROOT)
return self::RESULT_INVALID_USERNAME;
if (strlen($password) != 40)
return self::RESULT_INVALID_PASSWORD;
// Retrieve namespace
$namespace = $this->objectRetriever->getObject($namespaceCoid);
if (!isset($namespace))
return self::RESULT_NAMESPACE_NOT_FOUND;
// Read and validate shared secret
$reader = new NodeReader([
'prefixes' => [
'co' => 'coid://cloudobjects.io/'
]
]);
$sharedSecret = $reader->getAllValuesNode($namespace, 'co:hasSharedSecret');
if (count($sharedSecret) != 1)
return self::RESULT_SHARED_SECRET_NOT_RETRIEVABLE;
if ($reader->getFirstValueString($sharedSecret[0], 'co:hasTokenValue') == $password)
return self::RESULT_OK;
else
return self::RESULT_SHARED_SECRET_INCORRECT;
}
}

View File

@@ -0,0 +1,76 @@
<?php
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
namespace CloudObjects\SDK\JSON;
use ML\IRI\IRI;
use ML\JsonLD\Node;
use Webmozart\Assert\Assert;
use CloudObjects\SDK\ObjectRetriever, CloudObjects\SDK\NodeReader;
/**
* The schema validator enables the validation of data against
* JSON schemas in the CloudObjects RDF format.
*/
class SchemaValidator {
private $objectRetriever;
private $reader;
/**
* @param ObjectRetriever $objectRetriever An initialized and authenticated object retriever.
*/
public function __construct(ObjectRetriever $objectRetriever) {
$this->objectRetriever = $objectRetriever;
$this->reader = new NodeReader([
'prefixes' => [
'json' => 'coid://json.co-n.net/'
]
]);
}
/**
* Validate data against an element specification in an RDF node.
*
* @param mixed $data The data to validate.
* @param Node $node The specification to validate against.
*/
public function validateAgainstNode($data, Node $node) {
if ($this->reader->hasType($node, 'json:String'))
Assert::string($data);
elseif ($this->reader->hasType($node, 'json:Boolean'))
Assert::boolean($data);
elseif ($this->reader->hasType($node, 'json:Number'))
Assert::numeric($data);
elseif ($this->reader->hasType($node, 'json:Integer'))
Assert::integer($data);
elseif ($this->reader->hasType($node, 'json:Array'))
Assert::isArray($data);
elseif ($this->reader->hasType($node, 'json:Object')) {
Assert::isArrayAccessible($data);
foreach ($this->reader->getAllValuesNode($node, 'json:hasProperty') as $prop) {
$key = $this->reader->getFirstValueString($prop, 'json:hasKey');
if ($this->reader->getFirstValueBool($prop, 'json:isRequired') == true)
Assert::keyExists($data, $key);
if (isset($data[$key]))
$this->validateAgainstNode($data[$key], $prop);
}
}
}
/**
* Validate data against a specification stored in CloudObjects.
*
* @param mixed $data The data to validate.
* @param Node $node The COID of the specification.
*/
public function validateAgainstCOID($data, IRI $coid) {
$object = $this->objectRetriever->getObject($coid);
Assert::true($this->reader->hasType($object, 'json:Element'),
"You can only validate data against JSON elements!");
$this->validateAgainstNode($data, $object);
}
}

View File

@@ -0,0 +1,364 @@
<?php
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
namespace CloudObjects\SDK;
use ML\JsonLD\Node;
use ML\IRI\IRI;
/**
* The NodeReader provides some convenience methods for reading information
* from an object graph node.
*/
class NodeReader {
private $prefixes = [];
public function __construct(array $options = []) {
if (isset($options['prefixes']))
$this->prefixes = $options['prefixes'];
}
private function expand($uri) {
if (!is_string($uri)) $uri = (string)$uri;
$scheme = parse_url($uri, PHP_URL_SCHEME);
if (isset($scheme) && isset($this->prefixes[$scheme]))
return str_replace($scheme.':', $this->prefixes[$scheme], $uri);
else
return $uri;
}
/**
* Checks whether a node has a certain type.
*
* @param Node $node The node to work on.
* @param string|object $type The type to check for.
* @return boolean
*/
public function hasType(Node $node = null, $type) {
if (!isset($node))
return false;
$type = $this->expand($type);
$typesFromNode = $node->getType();
if (!isset($typesFromNode))
return false;
if (is_array($typesFromNode)) {
foreach ($typesFromNode as $t)
if (is_a($t, 'ML\JsonLD\Node')
&& $t->getId() == $type)
return true;
} else
if (is_a($typesFromNode, 'ML\JsonLD\Node')
&& $typesFromNode->getId() == $type)
return true;
else
return false;
return false;
}
private function getFirstValue(Node $node = null, $property, $default = null) {
if (!isset($node))
return $default;
$valueFromNode = $node->getProperty($this->expand($property));
if (!isset($valueFromNode))
return $default;
if (is_array($valueFromNode))
return $valueFromNode[0];
return $valueFromNode;
}
/**
* Reads a property from a node and converts it into a string.
* If the property has multiple values only the first is returned.
* If no value is found or the node is null, the default is returned.
*
* @param Node $node The node to work on.
* @param string|object $property The property to read.
* @param $default The default that is returned if no value for the property exists on the node.
* @return string|null
*/
public function getFirstValueString(Node $node = null, $property, $default = null) {
$valueFromNode = $this->getFirstValue($node, $property, $default);
if ($valueFromNode == $default)
return $default;
if (is_a($valueFromNode, 'ML\JsonLD\Node'))
return $valueFromNode->getId();
else
return $valueFromNode->getValue();
}
/**
* Reads a property from a node and converts it into a boolean.
* If the property has multiple values only the first is returned.
* If no value is found or the node is null, the default is returned.
*
* @param Node $node The node to work on.
* @param string|object $property The property to read.
* @param $default The default that is returned if no value for the property exists on the node.
* @return bool|null
*/
public function getFirstValueBool(Node $node = null, $property, $default = null) {
return (in_array(
$this->getFirstValueString($node, $property, $default),
[ '1', 'true' ]
));
}
/**
* Reads a property from a node and converts it into an integer.
* If the property has multiple values only the first is returned.
* If no value is found or the node is null, the default is returned.
*
* @param Node $node The node to work on.
* @param string|object $property The property to read.
* @param $default The default that is returned if no value for the property exists on the node.
* @return int|null
*/
public function getFirstValueInt(Node $node = null, $property, $default = null) {
$value = $this->getFirstValueString($node, $property);
if (is_numeric($value))
return (int)($value);
return $default;
}
/**
* Reads a property from a node and converts it into an float.
* If the property has multiple values only the first is returned.
* If no value is found or the node is null, the default is returned.
*
* @param Node $node The node to work on.
* @param string|object $property The property to read.
* @param $default The default that is returned if no value for the property exists on the node.
* @return float|null
*/
public function getFirstValueFloat(Node $node = null, $property, $default = null) {
$value = $this->getFirstValueString($node, $property);
if (is_numeric($value))
return (float)($value);
return $default;
}
/**
* Reads a property from a node and converts it into a IRI.
* If the property has multiple values only the first is returned.
* If no value is found, value is a literal or the node is null, the default is returned.
*
* @param Node $node The node to work on.
* @param string|object $property The property to read.
* @param $default The default that is returned if no value for the property exists on the node.
* @return string|null
*/
public function getFirstValueIRI(Node $node = null, $property, IRI $default = null) {
$valueFromNode = $this->getFirstValue($node, $property, $default);
if ($valueFromNode == $default)
return $default;
if (is_a($valueFromNode, 'ML\JsonLD\Node'))
return new IRI($valueFromNode->getId());
else
return $default;
}
/**
* Reads a property from a node and returns it as a Node.
* If the property has multiple values only the first is returned.
* If no value is found, value is a literal or the node is null, the default is returned.
*
* @param Node $node The node to work on.
* @param string|object $property The property to read.
* @param $default The default that is returned if no value for the property exists on the node.
* @return string|null
*/
public function getFirstValueNode(Node $node = null, $property, Node $default = null) {
$valueFromNode = $this->getFirstValue($node, $property, $default);
if ($valueFromNode == $default)
return $default;
if (is_a($valueFromNode, 'ML\JsonLD\Node'))
return $valueFromNode;
else
return $default;
}
/**
* Checks whether a node has a specific value for a property.
*
* @param Node $node The node to work on.
* @param string|object $property The property to read.
* @param string|object $value The expected value.
* @return boolean
*/
public function hasPropertyValue(Node $node = null, $property, $value) {
if (!isset($node))
return false;
$valuesFromNode = $node->getProperty($this->expand($property));
if (!isset($valuesFromNode))
return false;
if (!is_array($valuesFromNode))
$valuesFromNode = array($valuesFromNode);
foreach ($valuesFromNode as $v) {
if (is_a($v, 'ML\JsonLD\Node')) {
if ($v->getId() == $this->expand($value))
return true;
} else {
if ($v->getValue() == $value)
return true;
}
}
return false;
}
/**
* Checks whether the node has at least one value for a property.
*
* @param Node $node The node to work on.
* @param string|object $property The property to read.
* @return boolean
*/
public function hasProperty(Node $node = null, $property) {
if (!isset($node))
return false;
return ($node->getProperty($this->expand($property)) != null);
}
private function getAllValues(Node $node = null, $property) {
if (!isset($node))
return [];
$valueFromNode = $node->getProperty($this->expand($property));
if (!isset($valueFromNode))
return [];
if (!is_array($valueFromNode))
$valueFromNode = [$valueFromNode];
return $valueFromNode;
}
/**
* Get the language-tagged-string for the property in the specified language.
* If no value is found for the specified language, the default is returned.
*/
public function getLocalizedString(Node $node = null, $property, $language, $default = null) {
$values = $this->getAllValues($node, $property);
foreach ($values as $v) {
if (is_a($v, 'ML\JsonLD\LanguageTaggedString') && $v->getLanguage() == $language)
return $v->getValue();
}
return $default;
}
/**
* Reads all values from a node and returns them as a string array.
*
* @param Node $node The node to work on.
* @param string|object $property The property to read.
* @return array<string>
*/
public function getAllValuesString(Node $node = null, $property) {
$allValues = $this->getAllValues($node, $property);
$output = [];
foreach ($allValues as $a)
if (is_a($a, 'ML\JsonLD\Node'))
$output[] = $a->getId();
else
$output[] = $a->getValue();
return $output;
}
/**
* Reads all values from a node and returns them as a boolean array.
*
* @param Node $node The node to work on.
* @param string|object $property The property to read.
* @return array<bool>
*/
public function getAllValuesBool(Node $node = null, $property) {
$allValues = $this->getAllValuesString($node, $property);
$output = [];
foreach ($allValues as $a)
$output = in_array($a, [ '1', 'true' ]);
return $output;
}
/**
* Reads all values from a node and returns them as an integer array.
*
* @param Node $node The node to work on.
* @param string|object $property The property to read.
* @return array<bool>
*/
public function getAllValuesInt(Node $node = null, $property) {
$allValues = $this->getAllValuesString($node, $property);
$output = [];
foreach ($allValues as $a)
$output = (int)$a;
return $output;
}
/**
* Reads all values from a node and returns them as a float array.
*
* @param Node $node The node to work on.
* @param string|object $property The property to read.
* @return array<bool>
*/
public function getAllValuesFloat(Node $node = null, $property) {
$allValues = $this->getAllValuesString($node, $property);
$output = [];
foreach ($allValues as $a)
$output = (float)$a;
return $output;
}
/**
* Reads all values from a node and returns them as a IRI array.
* Only converts the Node IDs of nodes into IRI, literal values are skipped.
*
* @param Node $node The node to work on.
* @param string|object $property The property to read.
* @return array<IRI>
*/
public function getAllValuesIRI(Node $node = null, $property) {
$allValues = $this->getAllValues($node, $property);
$output = [];
foreach ($allValues as $a)
if (is_a($a, 'ML\JsonLD\Node'))
$output[] = new IRI($a->getId());
return $output;
}
/**
* Reads all values from a node and returns them as a Node array.
* Returns only nodes, literal values are skipped.
*
* @param Node $node The node to work on.
* @param string|object $property The property to read.
* @return array<Node>
*/
public function getAllValuesNode(Node $node = null, $property) {
$allValues = $this->getAllValues($node, $property);
$output = [];
foreach ($allValues as $a)
if (is_a($a, 'ML\JsonLD\Node'))
$output[] = $a;
return $output;
}
}

View File

@@ -0,0 +1,464 @@
<?php
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
namespace CloudObjects\SDK;
use Exception;
use ML\IRI\IRI, ML\JsonLD\JsonLD;
use Doctrine\Common\Cache\RedisCache;
use Psr\Log\LoggerInterface, Psr\Log\LoggerAwareTrait;
use GuzzleHttp\ClientInterface, GuzzleHttp\Client, GuzzleHttp\HandlerStack;
use GuzzleHttp\Exception\RequestException;
use Kevinrob\GuzzleCache\CacheMiddleware, Kevinrob\GuzzleCache\Storage\DoctrineCacheStorage;
use Kevinrob\GuzzleCache\Strategy\PrivateCacheStrategy;
use CloudObjects\SDK\Exceptions\CoreAPIException;
use CloudObjects\SDK\AccountGateway\AccountContext;
/**
* The ObjectRetriever provides access to objects on CloudObjects.
*/
class ObjectRetriever {
use LoggerAwareTrait;
private $client;
private $prefix;
private $options;
private $cache;
private $objects;
const CO_API_URL = 'https://api.cloudobjects.net/';
const REVISION_PROPERTY = 'coid://cloudobjects.io/isAtRevision';
public function __construct($options = []) {
// Merge options with defaults
$this->options = array_merge([
'cache_provider' => 'none',
'cache_prefix' => 'clobj:',
'cache_ttl' => 60,
'static_config_path' => null,
'auth_ns' => null,
'auth_secret' => null,
'api_base_url' => null,
'logger' => null,
'timeout' => 20,
'connect_timeout' => 5
], $options);
// Set up object cache
switch ($this->options['cache_provider']) {
case 'none':
// no caching
$this->cache = null;
break;
case 'redis':
// caching with Redis
$redis = new \Redis();
$redis->pconnect(
isset($this->options['cache_provider.redis.host']) ? $this->options['cache_provider.redis.host'] : '127.0.0.1',
isset($this->options['cache_provider.redis.port']) ? $this->options['cache_provider.redis.port'] : 6379);
$this->cache = new RedisCache();
$this->cache->setRedis($redis);
break;
case 'file':
// caching on the filesystem
$this->cache = new \Doctrine\Common\Cache\FilesystemCache(
isset($this->options['cache_provider.file.directory']) ? $this->options['cache_provider.file.directory'] : sys_get_temp_dir()
);
break;
default:
throw new Exception('Valid values for cache_provider are: none, redis, file');
}
// Set up logger
if (is_a($this->options['logger'], LoggerInterface::class))
$this->setLogger($this->options['logger']);
// Set up handler stack
$stack = HandlerStack::create();
// Add HTTP cache if specified
if (isset($this->cache)) {
$stack->push(
new CacheMiddleware(
new PrivateCacheStrategy(
new DoctrineCacheStorage($this->cache)
)
)
);
}
// Initialize client
$options = [
'base_uri' => isset($options['api_base_url']) ? $options['api_base_url'] : self::CO_API_URL,
'handler' => $stack,
'connect_timeout' => $this->options['connect_timeout'],
'timeout' => $this->options['timeout']
];
if (isset($this->options['auth_ns']) && isset($this->options['auth_secret']))
$options['auth'] = [$this->options['auth_ns'], $this->options['auth_secret']];
$this->client = new Client($options);
}
private function logInfoWithTime($message, $ts) {
if (isset($this->logger))
$this->logger->info($message, [ 'elapsed_ms' => round((microtime(true) - $ts) * 1000) ]);
}
private function getFromCache($id) {
return (isset($this->cache) && $this->cache->contains($this->options['cache_prefix'].$id))
? $this->cache->fetch($this->options['cache_prefix'].$id) : null;
}
private function putIntoCache($id, $data, $ttl) {
if (isset($this->cache))
$this->cache->save($this->options['cache_prefix'].$id, $data, $ttl);
}
/**
* Get the HTTP client that is used to access the API.
*/
public function getClient() {
return $this->client;
}
/**
* Set the HTTP client that is used to access the API.
*
* @param ClientInterface $client The HTTP client.
* @param string $prefix An optional prefix (e.g. an AccountGateway mountpoint)
*/
public function setClient(ClientInterface $client, $prefix = null) {
$this->client = $client;
$this->prefix = $prefix;
}
/**
* Creates a clone of this object retriever that uses the given account
* context to access the API as a developer API. Cache settings are inherited
* but the prefix is extended to keep cache content specific to account.
*
* @param AccountContext $accountContext
* @param string $mountpointName The name for the API mountpoint.
*/
public function withAccountContext(AccountContext $accountContext, string $mountpointName) {
$newRetriever = new self($this->options);
$newRetriever->options['cache_prefix'] .= (string)$accountContext->getAAUID();
$newRetriever->client = $accountContext->getClient();
$newRetriever->prefix = '/'.$mountpointName.'/';
return $newRetriever;
}
/**
* Get an object description from CloudObjects. Attempts to get object
* from in-memory cache first, stored static configurations next,
* configured external cache third, and finally calls the Object API
* on CloudObjects Core. Returns null if the object was not found.
*
* @param IRI $coid COID of the object
* @return Node|null
*/
public function getObject(IRI $coid) {
if (!COIDParser::isValidCOID($coid))
throw new Exception("Not a valid COID.");
$uriString = (string)$coid;
if (isset($this->objects[$uriString]))
// Return from in-memory cache if it exists
return $this->objects[$uriString];
$ts = microtime(true);
if (isset($this->options['static_config_path'])) {
$location = realpath($this->options['static_config_path'].DIRECTORY_SEPARATOR.
$coid->getHost().str_replace('/', DIRECTORY_SEPARATOR, $coid->getPath())
.DIRECTORY_SEPARATOR.'object.jsonld');
if ($location && file_exists($location)) {
$object = $location;
$this->logInfoWithTime('Fetched <'.$uriString.'> from static configuration.', $ts);
}
}
if (!isset($object)) {
$object = $this->getFromCache($uriString);
if (isset($object))
$this->logInfoWithTime('Fetched <'.$uriString.'> from object cache.', $ts);
}
if (!isset($object)) {
try {
$response = $this->client
->get((isset($this->prefix) ? $this->prefix : '').$coid->getHost().$coid->getPath().'/object',
['headers' => ['Accept' => 'application/ld+json']]);
$object = (string)$response->getBody();
$this->putIntoCache($uriString, $object, $this->options['cache_ttl']);
$this->logInfoWithTime('Fetched <'.$uriString.'> from Core API ['.$response->getStatusCode().'].', $ts);
} catch (RequestException $e) {
if ($e->hasResponse())
$this->logInfoWithTime('Object <'.$uriString.'> not found in Core API ['.$e->getResponse()->getStatusCode().'].', $ts);
else
$this->logInfoWithTime('Object <'.$uriString.'> could not be retrieved from Core API.', $ts);
return null;
}
}
$document = JsonLD::getDocument($object);
$this->objects[$uriString] = $document->getGraph()->getNode($uriString);
return $this->objects[$uriString];
}
/**
* Fetch all object descriptions for objects in a specific namespace
* and with a certain type from CloudObjects. Adds individual objects
* to cache and returns a list of COIDs (as IRI) for them. The list
* itself is not cached, which means that every call of this function
* goes to the Object API.
*
* @param IRI $namespaceCoid COID of the namespace
* @param $type RDF type that objects should have
* @return array<IRI>
*/
public function fetchObjectsInNamespaceWithType(IRI $namespaceCoid, $type) {
if (COIDParser::getType($namespaceCoid) != COIDParser::COID_ROOT)
throw new Exception("Not a valid namespace COID.");
$ts = microtime(true);
$type = (string)$type;
try {
$response = $this->client
->get((isset($this->prefix) ? $this->prefix : '').$namespaceCoid->getHost().'/all',
[
'headers' => [ 'Accept' => 'application/ld+json' ],
'query' => [ 'type' => $type ]
]);
$document = JsonLD::getDocument((string)$response->getBody());
$allObjects = $document->getGraph()->getNodesByType($type);
$allIris = [];
foreach ($allObjects as $object) {
$iri = new IRI($object->getId());
if (!COIDParser::isValidCOID($iri)) continue;
if ($iri->getHost() != $namespaceCoid->getHost()) continue;
$this->objects[$object->getId()] = $object;
$this->putIntoCache($object->getId(), $object, $this->options['cache_ttl']);
$allIris[] = $iri;
}
$this->logInfoWithTime('Fetched all objects with <'.$type.'> for <'.$namespaceCoid->getHost().'> from Core API ['.$response->getStatusCode().'].', $ts);
} catch (Exception $e) {
throw new CoreAPIException;
}
return $allIris;
}
/**
* Fetch all object descriptions for objects in a specific namespace
* from CloudObjects. Adds individual objects to cache and returns a
* list of COIDs (as IRI) for them. The list itself is not cached,
* which means that every call of this function goes to the Object API.
*
* @param IRI $namespaceCoid COID of the namespace
* @return array<IRI>
*/
public function fetchAllObjectsInNamespace(IRI $namespaceCoid) {
if (COIDParser::getType($namespaceCoid) != COIDParser::COID_ROOT)
throw new Exception("Not a valid namespace COID.");
$ts = microtime(true);
try {
$response = $this->client
->get((isset($this->prefix) ? $this->prefix : '').$namespaceCoid->getHost().'/all',
[ 'headers' => [ 'Accept' => 'application/ld+json' ] ]);
$document = JsonLD::getDocument((string)$response->getBody());
$allObjects = $document->getGraph()->getNodes();
$allIris = [];
foreach ($allObjects as $object) {
$iri = new IRI($object->getId());
if (!COIDParser::isValidCOID($iri)) continue;
if ($iri->getHost() != $namespaceCoid->getHost()) continue;
$this->objects[$object->getId()] = $object;
$this->putIntoCache($object->getId(), $object, $this->options['cache_ttl']);
$allIris[] = $iri;
}
$this->logInfoWithTime('Fetched all objects for <'.$namespaceCoid->getHost().'> from Core API ['.$response->getStatusCode().'].', $ts);
} catch (Exception $e) {
throw new CoreAPIException;
}
return $allIris;
}
/**
* Fetch a list of COIDs for all objects in a specific namespace
* from CloudObjects, but not the objects itself. The list is not cached,
* which means that every call of this function goes to the Object API.
*
* @param IRI $namespaceCoid COID of the namespace
* @return array<IRI>
*/
public function getCOIDListForNamespace(IRI $namespaceCoid) {
if (COIDParser::getType($namespaceCoid) != COIDParser::COID_ROOT)
throw new Exception("Not a valid namespace COID.");
$ts = microtime(true);
try {
$response = $this->client
->get((isset($this->prefix) ? $this->prefix : '').$namespaceCoid->getHost().'/coids',
[ 'headers' => [ 'Accept' => 'application/ld+json' ] ]);
$document = JsonLD::getDocument((string)$response->getBody());
$containerNode = $document->getGraph()->getNode('co-namespace-members://'.$namespaceCoid->getHost());
$reader = new NodeReader([ 'prefixes' => [ 'rdfs' => 'http://www.w3.org/2000/01/rdf-schema#' ]]);
$allIris = $reader->getAllValuesIRI($containerNode, 'rdfs:member');
$this->logInfoWithTime('Fetched object list for <'.$namespaceCoid->getHost().'> from Core API ['.$response->getStatusCode().'].', $ts);
} catch (Exception $e) {
throw new CoreAPIException;
}
return $allIris;
}
/**
* Fetch a list of COIDs for all objects in a specific namespace
* from CloudObjects, but not the objects itself. The list is not cached,
* which means that every call of this function goes to the Object API.
*
* @param IRI $namespaceCoid COID of the namespace
* @param $type RDF type that objects should have
* @return array<IRI>
*/
public function getCOIDListForNamespaceWithType(IRI $namespaceCoid, $type) {
if (COIDParser::getType($namespaceCoid) != COIDParser::COID_ROOT)
throw new Exception("Not a valid namespace COID.");
$ts = microtime(true);
$type = (string)$type;
try {
$response = $this->client
->get((isset($this->prefix) ? $this->prefix : '').$namespaceCoid->getHost().'/coids',
[
'headers' => [ 'Accept' => 'application/ld+json' ],
'query' => [ 'type' => $type ]
]);
$document = JsonLD::getDocument((string)$response->getBody());
$containerNode = $document->getGraph()->getNode('co-namespace-members://'.$namespaceCoid->getHost());
$reader = new NodeReader([ 'prefixes' => [ 'rdfs' => 'http://www.w3.org/2000/01/rdf-schema#' ]]);
$allIris = $reader->getAllValuesIRI($containerNode, 'rdfs:member');
$this->logInfoWithTime('Fetched object list with <'.$type.'> for <'.$namespaceCoid->getHost().'> from Core API ['.$response->getStatusCode().'].', $ts);
} catch (Exception $e) {
throw new CoreAPIException;
}
return $allIris;
}
/**
* Get an object description from CloudObjects. Shorthand method for
* "getObject" which allows passing the COID as string instead of IRI.
*
* @param any $coid
* @return Node|null
*/
public function get($coid) {
if (is_string($coid))
return $this->getObject(new IRI($coid));
if (is_object($coid) && get_class($coid)=='ML\IRI\IRI')
return $this->getObject($coid);
throw new Exception('COID must be passed as a string or an IRI object.');
}
/**
* Get a object's attachment.
*
* @param IRI $coid
* @param string $filename
*/
public function getAttachment(IRI $coid, $filename) {
$object = $this->getObject($coid);
if (!$object)
// Cannot get attachment for non-existing object
return null;
$ts = microtime(true);
$cacheId = $object->getId().'#'.$filename;
$fileData = $this->getFromCache($cacheId);
// Parse cached data into revision and content
if (isset($fileData)) {
$this->logInfoWithTime('Fetched attachment <'.$filename.'> for <'.$object->getId().'> from object cache.', $ts);
list($fileRevision, $fileContent) = explode('#', $fileData, 2);
}
if (!isset($fileData)
|| $fileRevision!=$object->getProperty(self::REVISION_PROPERTY)->getValue()) {
// Does not exist in cache or is outdated, fetch from CloudObjects
try {
$response = $this->client->get((isset($this->prefix) ? $this->prefix : '').$coid->getHost().$coid->getPath()
.'/'.basename($filename));
$fileContent = $response->getBody()->getContents();
$fileData = $object->getProperty(self::REVISION_PROPERTY)->getValue().'#'.$fileContent;
$this->putIntoCache($cacheId, $fileData, 0);
$this->logInfoWithTime('Fetched attachment <'.$filename.'> for <'.$object->getId().'> from Core API ['.$response->getStatusCode().'].', $ts);
} catch (Exception $e) {
$this->logInfoWithTime('Attachment <'.$filename.'> for <'.$object->getId().'> not found in Core API ['.$e->getResponse()->getStatusCode().'].', $ts);
// ignore exception - treat as non-existing file
}
}
return $fileContent;
}
/**
* Retrieve the object that describes the namespace provided with the "auth_ns"
* configuration option.
*
* @return Node
*/
public function getAuthenticatingNamespaceObject() {
if (!isset($this->options['auth_ns']))
throw new Exception("Missing 'auth_ns' configuration option.");
$namespaceCoid = COIDParser::fromString($this->options['auth_ns']);
if (COIDParser::getType($namespaceCoid) != COIDParser::COID_ROOT)
throw new Exception("The 'auth_ns' configuration option is not a valid namespace/root COID.");
return $this->getObject($namespaceCoid);
}
}

View File

@@ -0,0 +1,224 @@
<?php
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
namespace CloudObjects\SDK\WebAPI;
use Exception;
use ML\IRI\IRI;
use ML\JsonLD\Node;
use CloudObjects\SDK\NodeReader;
use GuzzleHttp\Client, GuzzleHttp\HandlerStack, GuzzleHttp\Middleware;
use Psr\Http\Message\RequestInterface;
use CloudObjects\SDK\COIDParser, CloudObjects\SDK\ObjectRetriever;
use CloudObjects\SDK\Exceptions\InvalidObjectConfigurationException,
CloudObjects\SDK\Exceptions\CoreAPIException;
/**
* The APIClientFactory can be used to create a preconfigured Guzzle HTTP API client
* based on the configuration data available for an API on CloudObjects.
*/
class APIClientFactory {
const DEFAULT_CONNECT_TIMEOUT = 5;
const DEFAULT_TIMEOUT = 20;
private $objectRetriever;
private $namespace;
private $reader;
private $apiClients = [];
private function configureAPIKeyAuthentication(Node $api, array $clientConfig) {
// see also: https://coid.link/webapis.co-n.net/APIKeyAuthentication
$apiKey = $this->reader->getFirstValueString($api, 'wa:hasFixedAPIKey');
if (!isset($apiKey)) {
$apiKeyProperty = $this->reader->getFirstValueString($api, 'wa:usesAPIKeyFrom');
if (!isset($apiKeyProperty))
throw new InvalidObjectConfigurationException("An API must have either a fixed API key or a defined API key property.");
$apiKey = $this->reader->getFirstValueString($this->namespace, $apiKeyProperty);
if (!isset($apiKey))
throw new InvalidObjectConfigurationException("The namespace does not have a value for <".$apiKeyProperty.">.");
}
$parameter = $this->reader->getFirstValueNode($api, 'wa:usesAuthenticationParameter');
if (!isset($parameter) || !$this->reader->hasProperty($parameter, 'wa:hasKey'))
throw new InvalidObjectConfigurationException("The API does not declare a parameter for inserting the API key.");
$parameterName = $this->reader->getFirstValueString($parameter, 'wa:hasKey');
if ($this->reader->hasType($parameter, 'wa:HeaderParameter'))
$clientConfig['headers'][$parameterName] = $apiKey;
elseif ($this->reader->hasType($parameter, 'wa:QueryParameter')) {
// Guzzle currently doesn't merge query strings from default options and the request itself,
// therefore we're implementing this behavior with a custom middleware
$handler = HandlerStack::create();
$handler->push(Middleware::mapRequest(function (RequestInterface $request) use ($parameterName, $apiKey) {
$uri = $request->getUri();
$uri = $uri->withQuery(
(!empty($uri->getQuery()) ? $uri->getQuery().'&' : '')
. urlencode($parameterName).'='.urlencode($apiKey)
);
return $request->withUri($uri);
}));
$clientConfig['handler'] = $handler;
}
else
throw new InvalidObjectConfigurationException("The authentication parameter must be either <wa:HeaderParameter> or <wa:QueryParameter>.");
return $clientConfig;
}
private function configureBearerTokenAuthentication(Node $api, array $clientConfig) {
// see also: https://coid.link/webapis.co-n.net/HTTPBasicAuthentication
$accessToken = $this->reader->getFirstValueString($api, 'oauth2:hasFixedBearerToken');
if (!isset($accessToken)) {
$tokenProperty = $this->reader->getFirstValueString($api, 'oauth2:usesFixedBearerTokenFrom');
if (!isset($tokenProperty))
throw new InvalidObjectConfigurationException("An API must have either a fixed access token or a defined token property.");
$accessToken = $this->reader->getFirstValueString($this->namespace, $tokenProperty);
if (!isset($accessToken))
throw new InvalidObjectConfigurationException("The namespace does not have a value for <".$tokenProperty.">.");
}
$clientConfig['headers']['Authorization'] = 'Bearer ' . $accessToken;
return $clientConfig;
}
private function configureBasicAuthentication(Node $api, array $clientConfig) {
// see also: https://coid.link/webapis.co-n.net/HTTPBasicAuthentication
$username = $this->reader->getFirstValueString($api, 'wa:hasFixedUsername');
$password = $this->reader->getFirstValueString($api, 'wa:hasFixedPassword');
if (!isset($username)) {
$usernameProperty = $this->reader->getFirstValueString($api, 'wa:usesUsernameFrom');
if (!isset($usernameProperty))
throw new InvalidObjectConfigurationException("An API must have either a fixed username or a defined username property.");
$username = $this->reader->getFirstValueString($this->namespace, $usernameProperty);
if (!isset($username))
throw new InvalidObjectConfigurationException("The namespace does not have a value for <".$usernameProperty.">.");
}
if (!isset($password)) {
$passwordProperty = $this->reader->getFirstValueString($api, 'wa:usesPasswordFrom');
if (!isset($passwordProperty))
throw new InvalidObjectConfigurationException("An API must have either a fixed password or a defined password property.");
$password = $this->reader->getFirstValueString($this->namespace, $passwordProperty);
if (!isset($password))
throw new InvalidObjectConfigurationException("The namespace does not have a value for <".$passwordProperty.">.");
}
$clientConfig['auth'] = [$username, $password];
return $clientConfig;
}
private function configureSharedSecretBasicAuthentication(Node $api, array $clientConfig) {
// see also: https://coid.link/webapis.co-n.net/SharedSecretAuthenticationViaHTTPBasic
$username = COIDParser::fromString($this->namespace->getId())->getHost();
$apiCoid = COIDParser::fromString($api->getId());
$providerNamespaceCoid = COIDParser::getNamespaceCOID($apiCoid);
$providerNamespace = $this->objectRetriever->get($providerNamespaceCoid);
$sharedSecret = $this->reader->getAllValuesNode($providerNamespace, 'co:hasSharedSecret');
if (count($sharedSecret) != 1)
throw new CoreAPIException("Could not retrieve the shared secret.");
$password = $this->reader->getFirstValueString($sharedSecret[0], 'co:hasTokenValue');
$clientConfig['auth'] = [$username, $password];
return $clientConfig;
}
private function createClient(Node $api, bool $specificClient = false) {
if (!$this->reader->hasType($api, 'wa:HTTPEndpoint'))
throw new InvalidObjectConfigurationException("The API node must have the type <coid://webapis.co-n.net/HTTPEndpoint>.");
$baseUrl = $this->reader->getFirstValueString($api, 'wa:hasBaseURL');
if (!isset($baseUrl))
throw new InvalidObjectConfigurationException("The API must have a base URL.");
$clientConfig = [
'base_uri' => $baseUrl,
'connect_timeout' => self::DEFAULT_CONNECT_TIMEOUT,
'timeout' => self::DEFAULT_TIMEOUT
];
if ($this->reader->hasPropertyValue($api, 'wa:supportsAuthenticationMechanism',
'wa:APIKeyAuthentication'))
$clientConfig = $this->configureAPIKeyAuthentication($api, $clientConfig);
elseif ($this->reader->hasPropertyValue($api, 'wa:supportsAuthenticationMechanism',
'oauth2:FixedBearerTokenAuthentication'))
$clientConfig = $this->configureBearerTokenAuthentication($api, $clientConfig);
elseif ($this->reader->hasPropertyValue($api, 'wa:supportsAuthenticationMechanism',
'wa:HTTPBasicAuthentication'))
$clientConfig = $this->configureBasicAuthentication($api, $clientConfig);
elseif ($this->reader->hasPropertyValue($api, 'wa:supportsAuthenticationMechanism',
'wa:SharedSecretAuthenticationViaHTTPBasic'))
$clientConfig = $this->configureSharedSecretBasicAuthentication($api, $clientConfig);
if ($specificClient == false)
return new Client($clientConfig);
if ($this->reader->hasType($api, 'wa:GraphQLEndpoint')) {
if (!class_exists('GraphQL\Client'))
throw new Exception("Install the gmostafa/php-graphql-client package to retrieve a specific client for wa:GraphQLEndpoint objects.");
return new \GraphQL\Client($clientConfig['base_uri'],
isset($clientConfig['headers']) ? $clientConfig['headers'] : []);
} else
return new Client($clientConfig);
}
/**
* @param ObjectRetriever $objectRetriever An initialized and authenticated object retriever.
* @param IRI|null $namespaceCoid The namespace of the API client. Used to retrieve credentials. If this parameter is not provided, the namespace provided with the "auth_ns" configuration option from the object retriever is used.
*/
public function __construct(ObjectRetriever $objectRetriever, IRI $namespaceCoid = null) {
$this->objectRetriever = $objectRetriever;
$this->namespace = isset($namespaceCoid)
? $objectRetriever->getObject($namespaceCoid)
: $objectRetriever->getAuthenticatingNamespaceObject();
$this->reader = new NodeReader([
'prefixes' => [
'co' => 'coid://cloudobjects.io/',
'wa' => 'coid://webapis.co-n.net/',
'oauth2' => 'coid://oauth2.co-n.net/'
]
]);
}
/**
* Get an API client for the WebAPI with the specified COID.
*
* @param IRI $apiCoid WebAPI COID
* @param boolean $specificClient If TRUE, returns a specific client class based on the API type. If FALSE, always returns a Guzzle client. Defaults to FALSE.
* @return Client
*/
public function getClientWithCOID(IRI $apiCoid, bool $specificClient = false) {
$idString = (string)$apiCoid.(string)$specificClient;
if (!isset($this->apiClients[$idString])) {
$object = $this->objectRetriever->getObject($apiCoid);
if (!isset($object))
throw new CoreAPIException("Could not retrieve API <".(string)$apiCoid.">.");
$this->apiClients[$idString] = $this->createClient($object, $specificClient);
}
return $this->apiClients[$idString];
}
}

494
LICENSE
View File

@@ -1,373 +1,363 @@
Mozilla Public License Version 2.0 Mozilla Public License, version 2.0
==================================
1. Definitions 1. Definitions
--------------
1.1. "Contributor" 1.1. "Contributor"
means each individual or legal entity that creates, contributes to
the creation of, or owns Covered Software. means each individual or legal entity that creates, contributes to the
creation of, or owns Covered Software.
1.2. "Contributor Version" 1.2. "Contributor Version"
means the combination of the Contributions of others (if any) used
by a Contributor and that particular Contributor's Contribution. means the combination of the Contributions of others (if any) used by a
Contributor and that particular Contributor's Contribution.
1.3. "Contribution" 1.3. "Contribution"
means Covered Software of a particular Contributor.
means Covered Software of a particular Contributor.
1.4. "Covered Software" 1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached
the notice in Exhibit A, the Executable Form of such Source Code means Source Code Form to which the initial Contributor has attached the
Form, and Modifications of such Source Code Form, in each case notice in Exhibit A, the Executable Form of such Source Code Form, and
including portions thereof. Modifications of such Source Code Form, in each case including portions
thereof.
1.5. "Incompatible With Secondary Licenses" 1.5. "Incompatible With Secondary Licenses"
means means
(a) that the initial Contributor has attached the notice described a. that the initial Contributor has attached the notice described in
in Exhibit B to the Covered Software; or Exhibit B to the Covered Software; or
(b) that the Covered Software was made available under the terms of b. that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the version 1.1 or earlier of the License, but not also under the terms of
terms of a Secondary License. a Secondary License.
1.6. "Executable Form" 1.6. "Executable Form"
means any form of the work other than Source Code Form.
means any form of the work other than Source Code Form.
1.7. "Larger Work" 1.7. "Larger Work"
means a work that combines Covered Software with other material, in
a separate file or files, that is not Covered Software. means a work that combines Covered Software with other material, in a
separate file or files, that is not Covered Software.
1.8. "License" 1.8. "License"
means this document.
means this document.
1.9. "Licensable" 1.9. "Licensable"
means having the right to grant, to the maximum extent possible,
whether at the time of the initial grant or subsequently, any and means having the right to grant, to the maximum extent possible, whether
all of the rights conveyed by this License. at the time of the initial grant or subsequently, any and all of the
rights conveyed by this License.
1.10. "Modifications" 1.10. "Modifications"
means any of the following:
(a) any file in Source Code Form that results from an addition to, means any of the following:
deletion from, or modification of the contents of Covered
Software; or
(b) any new file in Source Code Form that contains any Covered a. any file in Source Code Form that results from an addition to,
Software. deletion from, or modification of the contents of Covered Software; or
b. any new file in Source Code Form that contains any Covered Software.
1.11. "Patent Claims" of a Contributor 1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such means any patent claim(s), including without limitation, method,
Contributor that would be infringed, but for the grant of the process, and apparatus claims, in any patent Licensable by such
License, by the making, using, selling, offering for sale, having Contributor that would be infringed, but for the grant of the License,
made, import, or transfer of either its Contributions or its by the making, using, selling, offering for sale, having made, import,
Contributor Version. or transfer of either its Contributions or its Contributor Version.
1.12. "Secondary License" 1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU
Lesser General Public License, Version 2.1, the GNU Affero General means either the GNU General Public License, Version 2.0, the GNU Lesser
Public License, Version 3.0, or any later versions of those General Public License, Version 2.1, the GNU Affero General Public
licenses. License, Version 3.0, or any later versions of those licenses.
1.13. "Source Code Form" 1.13. "Source Code Form"
means the form of the work preferred for making modifications.
means the form of the work preferred for making modifications.
1.14. "You" (or "Your") 1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that means an individual or a legal entity exercising rights under this
controls, is controlled by, or is under common control with You. For License. For legal entities, "You" includes any entity that controls, is
purposes of this definition, "control" means (a) the power, direct controlled by, or is under common control with You. For purposes of this
or indirect, to cause the direction or management of such entity, definition, "control" means (a) the power, direct or indirect, to cause
whether by contract or otherwise, or (b) ownership of more than the direction or management of such entity, whether by contract or
fifty percent (50%) of the outstanding shares or beneficial otherwise, or (b) ownership of more than fifty percent (50%) of the
ownership of such entity. outstanding shares or beneficial ownership of such entity.
2. License Grants and Conditions 2. License Grants and Conditions
--------------------------------
2.1. Grants 2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free, Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license: non-exclusive license:
(a) under intellectual property rights (other than patent or trademark) a. under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available, Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and as part of a Larger Work; and
(b) under Patent Claims of such Contributor to make, use, sell, offer b. under Patent Claims of such Contributor to make, use, sell, offer for
for sale, have made, import, and otherwise transfer either its sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version. Contributions or its Contributor Version.
2.2. Effective Date 2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first become effective for each Contribution on the date the Contributor first
distributes such Contribution. distributes such Contribution.
2.3. Limitations on Grant Scope 2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License. distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor: Contributor:
(a) for any code that a Contributor has removed from Covered Software; a. for any code that a Contributor has removed from Covered Software; or
or
(b) for infringements caused by: (i) Your and any other third party's b. for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor Contributions with other software (except as part of its Contributor
Version); or Version); or
(c) under Patent Claims infringed by Covered Software in the absence of c. under Patent Claims infringed by Covered Software in the absence of
its Contributions. its Contributions.
This License does not grant any rights in the trademarks, service marks, This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4). the notice requirements in Section 3.4).
2.4. Subsequent Licenses 2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3). permitted under the terms of Section 3.3).
2.5. Representation 2.5. Representation
Each Contributor represents that the Contributor believes its Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights Contributions are its original creation(s) or it has sufficient rights to
to grant the rights to its Contributions conveyed by this License. grant the rights to its Contributions conveyed by this License.
2.6. Fair Use 2.6. Fair Use
This License is not intended to limit any rights You have under This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other applicable copyright doctrines of fair use, fair dealing, or other
equivalents. equivalents.
2.7. Conditions 2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in
in Section 2.1. Section 2.1.
3. Responsibilities 3. Responsibilities
-------------------
3.1. Distribution of Source Form 3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code attempt to alter or restrict the recipients' rights in the Source Code
Form. Form.
3.2. Distribution of Executable Form 3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then: If You distribute Covered Software in Executable Form then:
(a) such Covered Software must also be made available in Source Code a. such Covered Software must also be made available in Source Code Form,
Form, as described in Section 3.1, and You must inform recipients of as described in Section 3.1, and You must inform recipients of the
the Executable Form how they can obtain a copy of such Source Code Executable Form how they can obtain a copy of such Source Code Form by
Form by reasonable means in a timely manner, at a charge no more reasonable means in a timely manner, at a charge no more than the cost
than the cost of distribution to the recipient; and of distribution to the recipient; and
(b) You may distribute such Executable Form under the terms of this b. You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter license for the Executable Form does not attempt to limit or alter the
the recipients' rights in the Source Code Form under this License. recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work 3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice, You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary Software under the terms of either this License or such Secondary
License(s). License(s).
3.4. Notices 3.4. Notices
You may not remove or alter the substance of any license notices You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty, (including copyright notices, patent notices, disclaimers of warranty, or
or limitations of liability) contained within the Source Code Form of limitations of liability) contained within the Source Code Form of the
the Covered Software, except that You may alter any license notices to Covered Software, except that You may alter any license notices to the
the extent required to remedy known factual inaccuracies. extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms 3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support, You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support, liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any disclaimers of warranty and limitations of liability specific to any
jurisdiction. jurisdiction.
4. Inability to Comply Due to Statute or Regulation 4. Inability to Comply Due to Statute or Regulation
---------------------------------------------------
If it is impossible for You to comply with any of the terms of this If it is impossible for You to comply with any of the terms of this License
License with respect to some or all of the Covered Software due to with respect to some or all of the Covered Software due to statute,
statute, judicial order, or regulation then You must: (a) comply with judicial order, or regulation then You must: (a) comply with the terms of
the terms of this License to the maximum extent possible; and (b) this License to the maximum extent possible; and (b) describe the
describe the limitations and the code they affect. Such description must limitations and the code they affect. Such description must be placed in a
be placed in a text file included with all distributions of the Covered text file included with all distributions of the Covered Software under
Software under this License. Except to the extent prohibited by statute this License. Except to the extent prohibited by statute or regulation,
or regulation, such description must be sufficiently detailed for a such description must be sufficiently detailed for a recipient of ordinary
recipient of ordinary skill to be able to understand it. skill to be able to understand it.
5. Termination 5. Termination
--------------
5.1. The rights granted under this License will terminate automatically 5.1. The rights granted under this License will terminate automatically if You
if You fail to comply with any of its terms. However, if You become fail to comply with any of its terms. However, if You become compliant,
compliant, then the rights granted under this License from a particular then the rights granted under this License from a particular Contributor
Contributor are reinstated (a) provisionally, unless and until such are reinstated (a) provisionally, unless and until such Contributor
Contributor explicitly and finally terminates Your grants, and (b) on an explicitly and finally terminates Your grants, and (b) on an ongoing
ongoing basis, if such Contributor fails to notify You of the basis, if such Contributor fails to notify You of the non-compliance by
non-compliance by some reasonable means prior to 60 days after You have some reasonable means prior to 60 days after You have come back into
come back into compliance. Moreover, Your grants from a particular compliance. Moreover, Your grants from a particular Contributor are
Contributor are reinstated on an ongoing basis if such Contributor reinstated on an ongoing basis if such Contributor notifies You of the
notifies You of the non-compliance by some reasonable means, this is the non-compliance by some reasonable means, this is the first time You have
first time You have received notice of non-compliance with this License received notice of non-compliance with this License from such
from such Contributor, and You become compliant prior to 30 days after Contributor, and You become compliant prior to 30 days after Your receipt
Your receipt of the notice. of the notice.
5.2. If You initiate litigation against any entity by asserting a patent 5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions, infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate. 2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all 5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user
end user license agreements (excluding distributors and resellers) which license agreements (excluding distributors and resellers) which have been
have been validly granted by You or Your distributors under this License validly granted by You or Your distributors under this License prior to
prior to termination shall survive termination. termination shall survive termination.
************************************************************************ 6. Disclaimer of Warranty
* *
* 6. Disclaimer of Warranty *
* ------------------------- *
* *
* Covered Software is provided under this License on an "as is" *
* basis, without warranty of any kind, either expressed, implied, or *
* statutory, including, without limitation, warranties that the *
* Covered Software is free of defects, merchantable, fit for a *
* particular purpose or non-infringing. The entire risk as to the *
* quality and performance of the Covered Software is with You. *
* Should any Covered Software prove defective in any respect, You *
* (not any Contributor) assume the cost of any necessary servicing, *
* repair, or correction. This disclaimer of warranty constitutes an *
* essential part of this License. No use of any Covered Software is *
* authorized under this License except under this disclaimer. *
* *
************************************************************************
************************************************************************ Covered Software is provided under this License on an "as is" basis,
* * without warranty of any kind, either expressed, implied, or statutory,
* 7. Limitation of Liability * including, without limitation, warranties that the Covered Software is free
* -------------------------- * of defects, merchantable, fit for a particular purpose or non-infringing.
* * The entire risk as to the quality and performance of the Covered Software
* Under no circumstances and under no legal theory, whether tort * is with You. Should any Covered Software prove defective in any respect,
* (including negligence), contract, or otherwise, shall any * You (not any Contributor) assume the cost of any necessary servicing,
* Contributor, or anyone who distributes Covered Software as * repair, or correction. This disclaimer of warranty constitutes an essential
* permitted above, be liable to You for any direct, indirect, * part of this License. No use of any Covered Software is authorized under
* special, incidental, or consequential damages of any character * this License except under this disclaimer.
* including, without limitation, damages for lost profits, loss of *
* goodwill, work stoppage, computer failure or malfunction, or any * 7. Limitation of Liability
* and all other commercial damages or losses, even if such party *
* shall have been informed of the possibility of such damages. This * Under no circumstances and under no legal theory, whether tort (including
* limitation of liability shall not apply to liability for death or * negligence), contract, or otherwise, shall any Contributor, or anyone who
* personal injury resulting from such party's negligence to the * distributes Covered Software as permitted above, be liable to You for any
* extent applicable law prohibits such limitation. Some * direct, indirect, special, incidental, or consequential damages of any
* jurisdictions do not allow the exclusion or limitation of * character including, without limitation, damages for lost profits, loss of
* incidental or consequential damages, so this exclusion and * goodwill, work stoppage, computer failure or malfunction, or any and all
* limitation may not apply to You. * other commercial damages or losses, even if such party shall have been
* * informed of the possibility of such damages. This limitation of liability
************************************************************************ shall not apply to liability for death or personal injury resulting from
such party's negligence to the extent applicable law prohibits such
limitation. Some jurisdictions do not allow the exclusion or limitation of
incidental or consequential damages, so this exclusion and limitation may
not apply to You.
8. Litigation 8. Litigation
-------------
Any litigation relating to this License may be brought only in the Any litigation relating to this License may be brought only in the courts
courts of a jurisdiction where the defendant maintains its principal of a jurisdiction where the defendant maintains its principal place of
place of business and such litigation shall be governed by laws of that business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions. jurisdiction, without reference to its conflict-of-law provisions. Nothing
Nothing in this Section shall prevent a party's ability to bring in this Section shall prevent a party's ability to bring cross-claims or
cross-claims or counter-claims. counter-claims.
9. Miscellaneous 9. Miscellaneous
----------------
This License represents the complete agreement concerning the subject This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides necessary to make it enforceable. Any law or regulation which provides that
that the language of a contract shall be construed against the drafter the language of a contract shall be construed against the drafter shall not
shall not be used to construe this License against a Contributor. be used to construe this License against a Contributor.
10. Versions of the License 10. Versions of the License
---------------------------
10.1. New Versions 10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or 10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a publish new versions of this License. Each version will be given a
distinguishing version number. distinguishing version number.
10.2. Effect of New Versions 10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software, of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license or under the terms of any subsequent version published by the license
steward. steward.
10.3. Modified Versions 10.3. Modified Versions
If you create software not governed by this License, and you want to If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that any references to the name of the license steward (except to note that
such modified license differs from this License). such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary 10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses Licenses If You choose to distribute Source Code Form that is
Incompatible With Secondary Licenses under the terms of this version of
If You choose to distribute Source Code Form that is Incompatible With the License, the notice described in Exhibit B of this License must be
Secondary Licenses under the terms of this version of the License, the attached.
notice described in Exhibit B of this License must be attached.
Exhibit A - Source Code Form License Notice Exhibit A - Source Code Form License Notice
-------------------------------------------
This Source Code Form is subject to the terms of the Mozilla Public This Source Code Form is subject to the
License, v. 2.0. If a copy of the MPL was not distributed with this terms of the Mozilla Public License, v.
file, You can obtain one at https://mozilla.org/MPL/2.0/. 2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular If it is not possible or desirable to put the notice in a particular file,
file, then You may include the notice in a location (such as a LICENSE then You may include the notice in a location (such as a LICENSE file in a
file in a relevant directory) where a recipient would be likely to look relevant directory) where a recipient would be likely to look for such a
for such a notice. notice.
You may add additional accurate notices of copyright ownership. You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice Exhibit B - "Incompatible With Secondary Licenses" Notice
---------------------------------------------------------
This Source Code Form is "Incompatible With Secondary Licenses", as This Source Code Form is "Incompatible
defined by the Mozilla Public License, v. 2.0. With Secondary Licenses", as defined by
the Mozilla Public License, v. 2.0.

View File

@@ -1,2 +1,66 @@
# CloudObjects-PHP-SDK # CloudObjects PHP SDK
[![Latest Stable Version](https://poser.pugx.org/cloudobjects/sdk/v/stable)](https://packagist.org/packages/cloudobjects/sdk) [![Total Downloads](https://poser.pugx.org/cloudobjects/sdk/downloads)](https://packagist.org/packages/cloudobjects/sdk)
The CloudObjects PHP SDK provides simple access to [CloudObjects](https://cloudobjects.io/) from PHP-based applications. It wraps the [Object API](https://coid.link/cloudobjects.io/ObjectAPI/1.0) to fetch objects from the CloudObjects Core database and provides object-based access to their RDF description. A two-tiered caching mechanism (in-memory and Doctrine cache drivers) is included. The SDK also contains a helper class to validate COIDs.
## Installation
The SDK is [distributed through packagist](https://packagist.org/packages/cloudobjects/sdk). Add `cloudobjects/sdk` to the `require` section of your `composer.json`, like this:
````json
{
"require": {
"cloudobjects/sdk" : ">=0.7"
}
}
````
## Retrieving Objects
In order to retrieve objects from the CloudObjects Core database you need to create an instance of `CloudObjects\SDK\ObjectRetriever`. Then you can call `getObject()`. This method returns an `ML\JsonLD\Node` instance or `null` if the object is not found. You can use the object interface of the [JsonLD library](https://github.com/lanthaler/JsonLD/) to read the information from the object.
Here's a simple example:
````php
use ML\IRI\IRI;
use CloudObjects\SDK\ObjectRetriever;
/* ... */
$retriever = new ObjectRetriever();
$object = $this->retriever->getObject(new IRI('coid://cloudobjects.io'));
if (isset($object))
echo $object->getProperty('http://www.w3.org/2000/01/rdf-schema#label')->getValue();
else
echo "Object not found.";
````
### Configuration
You can pass an array of configuration options to the ObjectRetriever's constructor:
| Option | Description | Default |
|---|---|---|
| `cache_provider` | The type of cache used. Currently supports `redis`, `file` and `none`. | `none` |
| `cache_prefix` | A prefix used for cache IDs. Normally this should not be set but might be necessary on shared caches. | `clobj:` |
| `cache_ttl` | Determines how long objects can remain cached. | `60` |
| `auth_ns` | The namespace of the service that this retriever acts for. If not set the API is accessed anonymously. | `null` |
| `auth_secret` | The shared secret between the namespace in `auth_ns` and `cloudobjects.io` for authenticated. If not set the API is accessed anonymously. | `null` |
#### For `redis` cache:
| Option | Description | Default |
|---|---|---|
| `cache_provider.redis.host` | The hostname or IP of the Redis instance. | `127.0.0.1` |
| `cache_provider.redis.port` | The port number of the Redis instance. | `6379` |
#### For `file` cache:
| Option | Description | Default |
|---|---|---|
| `cache_provider.file.directory` | The directory to store cache data in. | The system's temporary directory. |
## License
The PHP SDK is licensed under Mozilla Public License (see LICENSE file).

39
composer.json Normal file
View File

@@ -0,0 +1,39 @@
{
"name": "cloudobjects/sdk",
"description": "CloudObjects SDK for PHP for working with COIDs and object descriptions from CloudObjects.",
"keywords": ["cloudobjects", "sdk"],
"homepage": "https://github.com/CloudObjects/CloudObjects-PHP-SDK",
"license": "MPL-2.0",
"require" : {
"ml/json-ld": ">=1.0.7",
"doctrine/common" : ">=2.6.1",
"doctrine/cache" : "1.*",
"guzzlehttp/guzzle" : ">=6.0",
"psr/log": "^1.1",
"kevinrob/guzzle-cache-middleware": "^3.2",
"webmozart/assert": "^1.6"
},
"authors": [
{
"name": "Lukas Rosenstock"
}
],
"autoload": {
"psr-0": {
"CloudObjects\\SDK" : ""
}
},
"require-dev" : {
"phpunit/phpunit": ">=4.8.0,<5.0",
"symfony/http-foundation" : ">=4.0",
"symfony/psr-http-message-bridge" : ">=1.1.0",
"zendframework/zend-diactoros" : "~1.8.6",
"defuse/php-encryption" : "^2.2"
},
"suggest" : {
"symfony/http-foundation" : "Required to use parseSymfonyRequest() in AccountContext.",
"symfony/psr-http-message-bridge" : "Required to use parseSymfonyRequest() in AccountContext.",
"zendframework/zend-diactoros" : "Required to use parseSymfonyRequest() in AccountContext.",
"defuse/php-encryption": "Required to use CryptoHelper"
}
}

3206
composer.lock generated Normal file

File diff suppressed because it is too large Load Diff

23
makefile Normal file
View File

@@ -0,0 +1,23 @@
.PHONY: all
all: vendor build
composer.lock: composer.json
# Updating Dependencies with Composer
composer update -o
vendor: composer.lock
# Installing Dependencies with Composer
composer install -o
sami.phar:
# Get a copy of sami (only on PHP 7)
@if [ `php -v | awk '{ if ($$1 == "PHP") { print substr($$2,0,1) }}'` = "7" ]; then\
wget http://get.sensiolabs.org/sami.phar;\
fi
build: sami.phar
# Building documentation with sami.phar (only on PHP 7)
@if [ `php -v | awk '{ if ($$1 == "PHP") { print substr($$2,0,1) }}'` = "7" ]; then\
php sami.phar update sami-config.php --force;\
fi

20
phpunit.xml Normal file
View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
syntaxCheck="false"
bootstrap="./tests/bootstrap.php">
<testsuites>
<testsuite name="OfflineTests">
<directory>./tests/OfflineTests/</directory>
</testsuite>
<testsuite name="OnlineTests">
<directory>./tests/OnlineTests/</directory>
</testsuite>
</testsuites>
</phpunit>

View File

@@ -0,0 +1,63 @@
<?php
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
namespace CloudObjects\SDK\AccountGateway;
use ML\IRI\IRI;
class AAUIDParserTest extends \PHPUnit_Framework_TestCase {
public function testValidAccountAAUID() {
$aauid = new IRI('aauid:abcd1234abcd1234');
$this->assertEquals(AAUIDParser::AAUID_ACCOUNT, AAUIDParser::getType($aauid));
$this->assertEquals('abcd1234abcd1234', AAUIDParser::getAAUID($aauid));
}
public function testInvalidAccountAAUID() {
$aauid = new IRI('aauid:abcd1234abcd123');
$this->assertEquals(AAUIDParser::AAUID_INVALID, AAUIDParser::getType($aauid));
$this->assertNull(AAUIDParser::getAAUID($aauid));
}
public function testValidAccountConnectionAAUID() {
$aauid = new IRI('aauid:abcd1234abcd1234:connection:AA');
$this->assertEquals(AAUIDParser::AAUID_CONNECTION, AAUIDParser::getType($aauid));
$this->assertEquals('abcd1234abcd1234', AAUIDParser::getAAUID($aauid));
$this->assertEquals('AA', AAUIDParser::getQualifier($aauid));
}
public function testInvalidAccountConnectionAAUID() {
$aauid = new IRI('aauid:abcd1234abcd1234:connection:AAA');
$this->assertEquals(AAUIDParser::AAUID_INVALID, AAUIDParser::getType($aauid));
$this->assertNull(AAUIDParser::getAAUID($aauid));
$this->assertNull(AAUIDParser::getQualifier($aauid));
}
public function testValidConnectedAccountAAUID() {
$aauid = new IRI('aauid:abcd1234abcd1234:account:AA');
$this->assertEquals(AAUIDParser::AAUID_CONNECTED_ACCOUNT, AAUIDParser::getType($aauid));
$this->assertEquals('abcd1234abcd1234', AAUIDParser::getAAUID($aauid));
$this->assertEquals('AA', AAUIDParser::getQualifier($aauid));
}
public function testInvalidConnectedAccountAAUID() {
$aauid = new IRI('aauid:abcd1234abcd1234:account:X9');
$this->assertEquals(AAUIDParser::AAUID_INVALID, AAUIDParser::getType($aauid));
$this->assertNull(AAUIDParser::getAAUID($aauid));
$this->assertNull(AAUIDParser::getQualifier($aauid));
}
public function testFromStringValid() {
$aauid1 = new IRI('aauid:5678defg8765gfed');
$aauid2 = AAUIDParser::fromString('aauid:5678defg8765gfed');
$this->assertEquals($aauid1, $aauid2);
$aauid1 = new IRI('aauid:5678defg8765gfed');
$aauid2 = AAUIDParser::fromString('5678defg8765gfed');
$this->assertEquals($aauid1, $aauid2);
}
}

View File

@@ -0,0 +1,34 @@
<?php
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
namespace CloudObjects\SDK\AccountGateway;
use GuzzleHttp\Psr7\Request as GuzzlePsrRequest;
use Symfony\Component\HttpFoundation\Request as SymfonyRequest;
class AccountContextParseTest extends \PHPUnit_Framework_TestCase {
public function testParsePsrRequest() {
$request = new GuzzlePsrRequest('GET', '/', [
'C-AAUID' => '1234123412341234', 'C-Access-Token' => 'test'
]);
$context = AccountContext::fromPsrRequest($request);
$this->assertNotNull($context);
$this->assertEquals('1234123412341234', AAUIDParser::getAAUID($context->getAAUID()));
}
public function testParseSymfonyRequest() {
$request = SymfonyRequest::create('/', 'GET', [], [], [], [
'HTTP_C_AAUID' => '1234123412341234', 'HTTP_C_ACCESS_TOKEN' => 'test'
]);
$context = AccountContext::fromSymfonyRequest($request);
$this->assertNotNull($context);
$this->assertEquals('1234123412341234', AAUIDParser::getAAUID($context->getAAUID()));
}
}

View File

@@ -0,0 +1,33 @@
<?php
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
namespace CloudObjects\SDK\AccountGateway;
use ML\IRI\IRI;
class AccountContextTest extends \PHPUnit_Framework_TestCase {
private $context;
protected function setUp() {
$this->context = new AccountContext(new IRI('aauid:aaaabbbbccccdddd'), 'DUMMY');
}
public function testDefaultGatewayBaseURL() {
$this->assertEquals('https://aaaabbbbccccdddd.aauid.net', $this->context->getClient()->getConfig('base_uri'));
}
public function testSetAccountGatewayBaseURLTemplateWithPlaceholder() {
$this->context->setAccountGatewayBaseURLTemplate('http://{aauid}.localhost');
$this->assertEquals('http://aaaabbbbccccdddd.localhost', $this->context->getClient()->getConfig('base_uri'));
}
public function testSetAccountGatewayBaseURLTemplateWithoutPlaceholder() {
$this->context->setAccountGatewayBaseURLTemplate('http://localhost');
$this->assertEquals('http://localhost', $this->context->getClient()->getConfig('base_uri'));
}
}

View File

@@ -0,0 +1,130 @@
<?php
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
namespace CloudObjects\SDK;
use ML\IRI\IRI;
class COIDParserTest extends \PHPUnit_Framework_TestCase {
public function testRootCOID() {
$coid = new IRI('coid://example.com');
$this->assertEquals(COIDParser::COID_ROOT, COIDParser::getType($coid));
}
public function testInvalidRootCOID() {
$coid = new IRI('coid://example');
$this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid));
$coid = new IRI('coid://exämple.com');
$this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid));
$coid = new IRI('coid://ex&mple.com');
$this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid));
}
public function testInvalidCOID() {
$coid = new IRI('http://example.com');
$this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid));
$coid = new IRI('example.com');
$this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid));
$coid = new IRI('COID://example.com');
$this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid));
$coid = new IRI('Coid://example.com');
$this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid));
$coid = new IRI('coid://EXAMPLE.COM');
$this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid));
$coid = new IRI('coid://exAMPle.CoM');
$this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid));
}
public function testUnversionedCOID() {
$coid = new IRI('coid://example.com/Example');
$this->assertEquals(COIDParser::COID_UNVERSIONED, COIDParser::getType($coid));
}
public function testInvalidUnversionedCOID() {
$coid = new IRI('coid://example.com/Exümple');
$this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid));
$coid = new IRI('coid://example.com/Examp%e');
$this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid));
}
public function testVersionedCOID() {
$coid = new IRI('coid://example.com/Example/1.0');
$this->assertEquals(COIDParser::COID_VERSIONED, COIDParser::getType($coid));
$coid = new IRI('coid://example.com/Example/alpha');
$this->assertEquals(COIDParser::COID_VERSIONED, COIDParser::getType($coid));
}
public function testInvalidVersionedCOID() {
$coid = new IRI('coid://example.com/Example/1.$');
$this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid));
}
public function testVersionWildcardCOID() {
$coid = new IRI('coid://example.com/Example/^1.0');
$this->assertEquals(COIDParser::COID_VERSION_WILDCARD, COIDParser::getType($coid));
$coid = new IRI('coid://example.com/Example/~1.0');
$this->assertEquals(COIDParser::COID_VERSION_WILDCARD, COIDParser::getType($coid));
$coid = new IRI('coid://example.com/Example/1.*');
$this->assertEquals(COIDParser::COID_VERSION_WILDCARD, COIDParser::getType($coid));
}
public function testInvalidVersionWildcardCOID() {
$coid = new IRI('coid://example.com/Example/^1.*');
$this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid));
$coid = new IRI('coid://example.com/Example/1.a.*');
$this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid));
}
public function testIRICaseSensitivity() {
$coid1 = new IRI('coid://example.com/example/1.0');
$coid2 = new IRI('coid://example.com/Example/1.0');
$this->assertFalse($coid1->equals($coid2));
}
public function testRootFromString() {
$coid1 = new IRI('coid://example.com');
$coid2 = COIDParser::fromString('coid://example.com');
$coid3 = COIDParser::fromString('example.com');
$this->assertTrue($coid1->equals($coid2));
$this->assertTrue($coid1->equals($coid3));
}
public function testUnversionedFromString() {
$coid1 = new IRI('coid://example.com/Example');
$coid2 = COIDParser::fromString('coid://example.com/Example');
$coid3 = COIDParser::fromString('example.com/Example');
$this->assertTrue($coid1->equals($coid2));
$this->assertTrue($coid1->equals($coid3));
}
public function testVersionedFromString() {
$coid1 = new IRI('coid://example.com/Example/1.0');
$coid2 = COIDParser::fromString('coid://example.com/Example/1.0');
$coid3 = COIDParser::fromString('example.com/Example/1.0');
$this->assertTrue($coid1->equals($coid2));
$this->assertTrue($coid1->equals($coid3));
}
public function testNormalizeRootFromString() {
$coid1 = new IRI('coid://example.com');
$coid2 = COIDParser::fromString('COID://example.com');
$coid3 = COIDParser::fromString('ExAmple.COM');
$this->assertTrue($coid1->equals($coid2));
$this->assertTrue($coid1->equals($coid3));
}
public function testNormalizeNonRootFromString() {
$coid1 = new IRI('coid://example.com/Example');
$coid2 = COIDParser::fromString('COID://example.com/Example');
$coid3 = COIDParser::fromString('ExAmple.COM/Example');
$coid4 = COIDParser::fromString('ExAmple.COM/EXample');
$this->assertTrue($coid1->equals($coid2));
$this->assertTrue($coid1->equals($coid3));
$this->assertFalse($coid1->equals($coid4));
}
}

View File

@@ -0,0 +1,46 @@
<?php
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
namespace CloudObjects\SDK\Common;
use InvalidArgumentException;
use GuzzleHttp\Client, GuzzleHttp\Handler\MockHandler,
GuzzleHttp\HandlerStack, GuzzleHttp\Psr7\Response;
use CloudObjects\SDK\ObjectRetriever;
class CryptoHelperTest extends \PHPUnit_Framework_TestCase {
private $retriever;
private $graph;
private function setMockResponse(Response $response) {
$mock = new MockHandler([$response]);
$handler = HandlerStack::create($mock);
$this->retriever->setClient(new Client(['handler' => $handler]));
}
public function setUp() {
$this->retriever = new ObjectRetriever([
'auth_ns' => 'test.cloudobjects.io',
'auth_secret' => 'TEST'
]);
}
public function testEncryptDecrypt() {
$this->setMockResponse(new Response(200,
[ 'Content-Type' => 'application/ld+json' ],
'{"@context":{"common":"coid:\/\/common.cloudobjects.io\/"},"@id":"coid:\/\/test.cloudobjects.io","common:usesSharedEncryptionKey": "def0000092c63296feb07f6b44f323351ab2e570fb04c2dff73c3119fd1103234ea03f5af094d33e8fb5122c5cf73f745957a5f8f47b4fc3c43bc86fb631969f4c591831"}'));
$cryptoHelper = new CryptoHelper($this->retriever);
$cleartext = "CLEARTEXT";
$ciphertext = $cryptoHelper->encryptWithSharedEncryptionKey($cleartext);
$encryptedDecryptedText = $cryptoHelper->decryptWithSharedEncryptionKey($ciphertext);
$this->assertEquals($cleartext, $encryptedDecryptedText);
}
}

View File

@@ -0,0 +1,175 @@
<?php
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
namespace CloudObjects\SDK\JSON;
use InvalidArgumentException;
use ML\JsonLD\JsonLD;
use CloudObjects\SDK\ObjectRetriever;
class SchemaValidatorTest extends \PHPUnit_Framework_TestCase {
private $schemaValidator;
private $graph;
public function setUp() {
$this->schemaValidator = new SchemaValidator(new ObjectRetriever);
$this->graph = JsonLD::getDocument('{}')->getGraph();
}
public function testString() {
$node = $this->graph->createNode();
$node->setType($this->graph->createNode('coid://json.co-n.net/String'));
$this->schemaValidator->validateAgainstNode("Test", $node);
}
public function testNotString() {
$this->setExpectedException(InvalidArgumentException::class);
$node = $this->graph->createNode();
$node->setType($this->graph->createNode('coid://json.co-n.net/String'));
$this->schemaValidator->validateAgainstNode(9, $node);
}
public function testNumber() {
$node = $this->graph->createNode();
$node->setType($this->graph->createNode('coid://json.co-n.net/Number'));
$this->schemaValidator->validateAgainstNode(3.5, $node);
}
public function testNotNumber() {
$this->setExpectedException(InvalidArgumentException::class);
$node = $this->graph->createNode();
$node->setType($this->graph->createNode('coid://json.co-n.net/Number'));
$this->schemaValidator->validateAgainstNode("ABC", $node);
}
public function testInteger() {
$node = $this->graph->createNode();
$node->setType($this->graph->createNode('coid://json.co-n.net/Integer'));
$this->schemaValidator->validateAgainstNode(12, $node);
}
public function testNotInteger() {
$this->setExpectedException(InvalidArgumentException::class);
$node = $this->graph->createNode();
$node->setType($this->graph->createNode('coid://json.co-n.net/Integer'));
$this->schemaValidator->validateAgainstNode(1.4, $node);
}
public function testArray() {
$node = $this->graph->createNode();
$node->setType($this->graph->createNode('coid://json.co-n.net/Array'));
$this->schemaValidator->validateAgainstNode([ 1, 2, "foo" ], $node);
}
public function testNotArray() {
$this->setExpectedException(InvalidArgumentException::class);
$node = $this->graph->createNode();
$node->setType($this->graph->createNode('coid://json.co-n.net/Array'));
$this->schemaValidator->validateAgainstNode("NANANA", $node);
}
public function testObject() {
$node = $this->graph->createNode();
$node->setType($this->graph->createNode('coid://json.co-n.net/Object'));
$this->schemaValidator->validateAgainstNode([
'a' => 'A',
'b' => 'B'
], $node);
}
public function testNotObject() {
$this->setExpectedException(InvalidArgumentException::class);
$node = $this->graph->createNode();
$node->setType($this->graph->createNode('coid://json.co-n.net/Object'));
$this->schemaValidator->validateAgainstNode(5, $node);
}
public function testObjectWithProperty() {
$stringNode = $this->graph->createNode();
$stringNode->setProperty('coid://json.co-n.net/hasKey', 'a');
$stringNode->setType($this->graph->createNode('coid://json.co-n.net/String'));
$node = $this->graph->createNode();
$node->setType($this->graph->createNode('coid://json.co-n.net/Object'));
$node->setProperty('coid://json.co-n.net/hasProperty', $stringNode);
$this->schemaValidator->validateAgainstNode([
'a' => 'A',
'b' => 'B'
], $node);
}
public function testObjectWithPropertyTypeError() {
$this->setExpectedException(InvalidArgumentException::class);
$stringNode = $this->graph->createNode();
$stringNode->setProperty('coid://json.co-n.net/hasKey', 'a');
$stringNode->setType($this->graph->createNode('coid://json.co-n.net/String'));
$node = $this->graph->createNode();
$node->setType($this->graph->createNode('coid://json.co-n.net/Object'));
$node->setProperty('coid://json.co-n.net/hasProperty', $stringNode);
$this->schemaValidator->validateAgainstNode([
'a' => 0,
'b' => 'B'
], $node);
}
public function testObjectWithRequiredProperty() {
$stringNode = $this->graph->createNode();
$stringNode->setProperty('coid://json.co-n.net/hasKey', 'a');
$stringNode->setProperty('coid://json.co-n.net/isRequired', 'true');
$stringNode->setType($this->graph->createNode('coid://json.co-n.net/String'));
$node = $this->graph->createNode();
$node->setType($this->graph->createNode('coid://json.co-n.net/Object'));
$node->setProperty('coid://json.co-n.net/hasProperty', $stringNode);
$this->schemaValidator->validateAgainstNode([
'a' => 'A',
'b' => 'B'
], $node);
}
public function testObjectWithRequiredPropertyTypeError() {
$this->setExpectedException(InvalidArgumentException::class);
$stringNode = $this->graph->createNode();
$stringNode->setProperty('coid://json.co-n.net/hasKey', 'a');
$stringNode->setProperty('coid://json.co-n.net/isRequired', 'true');
$stringNode->setType($this->graph->createNode('coid://json.co-n.net/String'));
$node = $this->graph->createNode();
$node->setType($this->graph->createNode('coid://json.co-n.net/Object'));
$node->setProperty('coid://json.co-n.net/hasProperty', $stringNode);
$this->schemaValidator->validateAgainstNode([
'a' => 0,
'b' => 'B'
], $node);
}
public function testObjectWithRequiredPropertyMissing() {
$this->setExpectedException(InvalidArgumentException::class);
$stringNode = $this->graph->createNode();
$stringNode->setProperty('coid://json.co-n.net/hasKey', 'a');
$stringNode->setProperty('coid://json.co-n.net/isRequired', 'true');
$stringNode->setType($this->graph->createNode('coid://json.co-n.net/String'));
$node = $this->graph->createNode();
$node->setType($this->graph->createNode('coid://json.co-n.net/Object'));
$node->setProperty('coid://json.co-n.net/hasProperty', $stringNode);
$this->schemaValidator->validateAgainstNode([
'b' => 'B',
'c' => 'C'
], $node);
}
}

View File

@@ -0,0 +1,156 @@
<?php
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
namespace CloudObjects\SDK;
use ML\IRI\IRI;
use GuzzleHttp\Client, GuzzleHttp\Handler\MockHandler,
GuzzleHttp\HandlerStack, GuzzleHttp\Psr7\Response;
class NodeReaderMockTest extends \PHPUnit_Framework_TestCase {
private $retriever;
private $reader;
private function setMockResponse(Response $response) {
$mock = new MockHandler([$response]);
$handler = HandlerStack::create($mock);
$this->retriever->setClient(new Client(['handler' => $handler]));
}
private function useRootResourceMock() {
$this->setMockResponse(new Response(200,
['Content-Type' => 'application/ld+json'],
'{"@context":{"co":"coid:\/\/cloudobjects.io\/","rdf":"http:\/\/www.w3.org\/1999\/02\/22-rdf-syntax-ns#","agws":"coid:\/\/aauid.net\/","rdfs":"http:\/\/www.w3.org\/2000\/01\/rdf-schema#"},"@id":"coid:\/\/cloudobjects.io","@type":["agws:Service","co:Namespace"],"co:isAtRevision":"6-fbea0c90b2c5e5300e4039ed99be9b2d","co:isVisibleTo":{"@id":"co:Public"},"co:recommendsPrefix":"co","co:wasUpdatedAt":{"@type":"http:\/\/www.w3.org\/2001\/XMLSchema#dateTime","@value":"2017-01-16T17:29:22+00:00"},"rdfs:comment":"The CloudObjects namespace defines the essential objects.","rdfs:label":"CloudObjects"}'));
}
protected function setUp() {
$this->retriever = new ObjectRetriever;
$this->reader = new NodeReader([
'prefixes' => [
'co' => 'coid://cloudobjects.io/',
'rdfs' => 'http://www.w3.org/2000/01/rdf-schema#'
]
]);
}
public function testHasType1() {
$coid = new IRI('coid://cloudobjects.io');
$this->useRootResourceMock();
$object = $this->retriever->getObject($coid);
$this->assertTrue($this->reader->hasType($object, 'coid://cloudobjects.io/Namespace'));
$this->assertTrue($this->reader->hasType($object, 'co:Namespace'));
$this->assertFalse($this->reader->hasType($object, 'coid://cloudobjects.io/MemberRole'));
$this->assertFalse($this->reader->hasType($object, 'co:MemberRole'));
}
public function testHasPropertyValue1() {
$coid = new IRI('coid://cloudobjects.io');
$this->useRootResourceMock();
$object = $this->retriever->getObject($coid);
$this->assertTrue($this->reader->hasPropertyValue($object, 'http://www.w3.org/2000/01/rdf-schema#label', 'CloudObjects'));
$this->assertTrue($this->reader->hasPropertyValue($object, 'rdfs:label', 'CloudObjects'));
}
public function testGetFirstValueString1() {
$coid = new IRI('coid://cloudobjects.io');
$this->useRootResourceMock();
$object = $this->retriever->getObject($coid);
$this->assertEquals('CloudObjects', $this->reader->getFirstValueString($object, 'http://www.w3.org/2000/01/rdf-schema#label'));
$this->assertEquals('CloudObjects', $this->reader->getFirstValueString($object, 'rdfs:label'));
$this->assertNull($this->reader->getFirstValueString($object, 'coid://cloudobjects.io/makesTriplesVisibleTo'));
$this->assertNull($this->reader->getFirstValueString($object, 'co:makesTriplesVisibleTo'));
$this->assertEquals('theDefaultValue', $this->reader->getFirstValueString($object, 'coid://cloudobjects.io/makesTriplesVisibleTo', 'theDefaultValue'));
$this->assertEquals('theDefaultValue', $this->reader->getFirstValueString($object, 'co:makesTriplesVisibleTo', 'theDefaultValue'));
}
public function testGetFirstValueIRI1() {
$coid = new IRI('coid://cloudobjects.io');
$this->useRootResourceMock();
$object = $this->retriever->getObject($coid);
$this->assertInstanceOf('ML\IRI\IRI', $this->reader->getFirstValueIRI($object, 'coid://cloudobjects.io/isVisibleTo'));
$this->assertInstanceOf('ML\IRI\IRI', $this->reader->getFirstValueIRI($object, 'co:isVisibleTo'));
$this->assertEquals(new IRI('coid://cloudobjects.io/Public'), $this->reader->getFirstValueIRI($object, 'coid://cloudobjects.io/isVisibleTo'));
$this->assertEquals(new IRI('coid://cloudobjects.io/Public'), $this->reader->getFirstValueIRI($object, 'co:isVisibleTo'));
}
public function testGetFirstValueNode1() {
$coid = new IRI('coid://cloudobjects.io');
$this->useRootResourceMock();
$object = $this->retriever->getObject($coid);
$this->assertInstanceOf('ML\JsonLD\Node', $this->reader->getFirstValueNode($object, 'coid://cloudobjects.io/isVisibleTo'));
$this->assertInstanceOf('ML\JsonLD\Node', $this->reader->getFirstValueNode($object, 'co:isVisibleTo'));
$this->assertEquals('coid://cloudobjects.io/Public', $this->reader->getFirstValueNode($object, 'coid://cloudobjects.io/isVisibleTo')->getId());
$this->assertEquals('coid://cloudobjects.io/Public', $this->reader->getFirstValueNode($object, 'co:isVisibleTo')->getId());
}
public function testGetAllValuesString1() {
$coid = new IRI('coid://cloudobjects.io');
$this->useRootResourceMock();
$object = $this->retriever->getObject($coid);
$this->assertCount(1, $this->reader->getAllValuesString($object, 'http://www.w3.org/2000/01/rdf-schema#label'));
$this->assertCount(1, $this->reader->getAllValuesString($object, 'rdfs:label'));
$this->assertEquals('CloudObjects', $this->reader->getAllValuesString($object, 'http://www.w3.org/2000/01/rdf-schema#label')[0]);
$this->assertEquals('CloudObjects', $this->reader->getAllValuesString($object, 'rdfs:label')[0]);
$this->assertCount(0, $this->reader->getAllValuesString($object, 'coid://cloudobjects.io/makesTriplesVisibleTo'));
$this->assertCount(0, $this->reader->getAllValuesString($object, 'co:makesTriplesVisibleTo'));
$this->assertCount(2, $this->reader->getAllValuesString($object, '@type'));
}
public function testGetAllValuesIRI1() {
$coid = new IRI('coid://cloudobjects.io');
$this->useRootResourceMock();
$object = $this->retriever->getObject($coid);
$this->assertCount(0, $this->reader->getAllValuesIRI($object, 'http://www.w3.org/2000/01/rdf-schema#label'));
$this->assertCount(0, $this->reader->getAllValuesIRI($object, 'rdfs:label'));
$this->assertCount(1, $this->reader->getAllValuesIRI($object, 'coid://cloudobjects.io/isVisibleTo'));
$this->assertCount(1, $this->reader->getAllValuesIRI($object, 'co:isVisibleTo'));
$this->assertCount(2, $this->reader->getAllValuesIRI($object, '@type'));
$this->assertInstanceOf('ML\IRI\IRI', $this->reader->getAllValuesIRI($object, 'coid://cloudobjects.io/isVisibleTo')[0]);
$this->assertInstanceOf('ML\IRI\IRI', $this->reader->getAllValuesIRI($object, 'co:isVisibleTo')[0]);
$this->assertEquals(new IRI('coid://cloudobjects.io/Public'), $this->reader->getAllValuesIRI($object, 'coid://cloudobjects.io/isVisibleTo')[0]);
$this->assertEquals(new IRI('coid://cloudobjects.io/Public'), $this->reader->getAllValuesIRI($object, 'co:isVisibleTo')[0]);
}
public function testGetAllValuesNode1() {
$coid = new IRI('coid://cloudobjects.io');
$this->useRootResourceMock();
$object = $this->retriever->getObject($coid);
$this->assertCount(0, $this->reader->getAllValuesNode($object, 'http://www.w3.org/2000/01/rdf-schema#label'));
$this->assertCount(0, $this->reader->getAllValuesNode($object, 'rdfs:label'));
$this->assertCount(1, $this->reader->getAllValuesNode($object, 'coid://cloudobjects.io/isVisibleTo'));
$this->assertCount(1, $this->reader->getAllValuesNode($object, 'co:isVisibleTo'));
$this->assertCount(2, $this->reader->getAllValuesNode($object, '@type'));
$this->assertInstanceOf('ML\JsonLD\Node', $this->reader->getAllValuesNode($object, 'coid://cloudobjects.io/isVisibleTo')[0]);
$this->assertInstanceOf('ML\JsonLD\Node', $this->reader->getAllValuesNode($object, 'co:isVisibleTo')[0]);
$this->assertEquals('coid://cloudobjects.io/Public', $this->reader->getAllValuesNode($object, 'coid://cloudobjects.io/isVisibleTo')[0]->getId());
$this->assertEquals('coid://cloudobjects.io/Public', $this->reader->getAllValuesNode($object, 'co:isVisibleTo')[0]->getId());
}
}

View File

@@ -0,0 +1,40 @@
<?php
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
namespace CloudObjects\SDK;
use ML\IRI\IRI;
use GuzzleHttp\Client, GuzzleHttp\Handler\MockHandler,
GuzzleHttp\HandlerStack, GuzzleHttp\Psr7\Response;
class ObjectRetrieverMockTest extends \PHPUnit_Framework_TestCase {
private $retriever;
private function setMockResponse(Response $response) {
$mock = new MockHandler([$response]);
$handler = HandlerStack::create($mock);
$this->retriever->setClient(new Client(['handler' => $handler]));
}
protected function setUp() {
$this->retriever = new ObjectRetriever;
}
public function testGetRootResource() {
$this->setMockResponse(new Response(200,
['Content-Type' => 'application/ld+json'],
'{"@context":{"cloudobjects":"coid:\/\/cloudobjects.io\/","rdf":"http:\/\/www.w3.org\/1999\/02\/22-rdf-syntax-ns#","rdfs":"http:\/\/www.w3.org\/2000\/01\/rdf-schema#"},"@id":"coid:\/\/cloudobjects.io","@type":"cloudobjects:Namespace","cloudobjects:hasPublicListing":"true","cloudobjects:revision":"1-325baa62b76105f56dc09386f5a2ec91","rdfs:comment":"The CloudObjects namespace defines the essential objects.","rdfs:label":"CloudObjects"}'));
$coid = new IRI('coid://cloudobjects.io');
$object = $this->retriever->getObject($coid);
$this->assertNotNull($object);
$this->assertEquals((string)$coid, $object->getID());
$this->assertNotNull($object->getProperty('http://www.w3.org/2000/01/rdf-schema#label'));
$this->assertEquals('CloudObjects', $object->getProperty('http://www.w3.org/2000/01/rdf-schema#label')->getValue());
}
}

View File

@@ -0,0 +1,38 @@
<?php
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
namespace CloudObjects\SDK\JSON;
use InvalidArgumentException;
use ML\IRI\IRI;
use CloudObjects\SDK\ObjectRetriever;
class SchemaValidatorPublicTest extends \PHPUnit_Framework_TestCase {
private $schemaValidator;
public function setUp() {
$this->schemaValidator = new SchemaValidator(new ObjectRetriever);
}
public function testAddress() {
$this->schemaValidator->validateAgainstCOID([
'locality' => 'Frankfurt',
'region' => 'Hessen',
'country-name' => 'Germany'
], new IRI('coid://json.co-n.net/Address'));
}
public function testNotAddress() {
$this->setExpectedException(InvalidArgumentException::class);
$this->schemaValidator->validateAgainstCOID([
'region' => 'Hessen',
'country-name' => 'Germany'
], new IRI('coid://json.co-n.net/Address'));
}
}

View File

@@ -0,0 +1,60 @@
<?php
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
namespace CloudObjects\SDK;
use ML\IRI\IRI;
class ObjectRetrieverTest extends \PHPUnit_Framework_TestCase {
private $retriever;
protected function setUp() {
$this->retriever = new ObjectRetriever;
}
private function stringifyItems(array $input) {
$output = [];
foreach ($input as $i)
$output[] = (string)$i;
return $output;
}
public function testGetRootObject() {
$coid = new IRI('coid://cloudobjects.io');
$object = $this->retriever->getObject($coid);
$this->assertNotNull($object);
$this->assertEquals((string)$coid, $object->getID());
$this->assertNotNull($object->getProperty('http://www.w3.org/2000/01/rdf-schema#label'));
$this->assertEquals('CloudObjects', $object->getProperty('http://www.w3.org/2000/01/rdf-schema#label')->getValue());
}
public function testGetCOIDList() {
$coid = new IRI('coid://cloudobjects.io');
$list = $this->stringifyItems(
$this->retriever->getCOIDListForNamespace($coid)
);
$this->assertNotEmpty($list);
$this->assertContains('coid://cloudobjects.io/isVisibleTo', $list);
$this->assertContains('coid://cloudobjects.io/Public', $list);
$this->assertNotContains('coid://json.co-n.net/Element', $list);
}
public function testGetFilteredCOIDList() {
$coid = new IRI('coid://cloudobjects.io');
$list = $this->stringifyItems(
$this->retriever->getCOIDListForNamespaceWithType($coid, 'coid://cloudobjects.io/Audience')
);
$this->assertNotEmpty($list);
$this->assertNotContains('coid://cloudobjects.io/isVisibleTo', $list);
$this->assertContains('coid://cloudobjects.io/Public', $list);
$this->assertContains('coid://cloudobjects.io/Private', $list);
}
}

7
tests/bootstrap.php Normal file
View File

@@ -0,0 +1,7 @@
<?php
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
require_once __DIR__."/../vendor/autoload.php";