Imported code from old repository
This commit is contained in:
@@ -0,0 +1,285 @@
|
||||
<?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\PhpMAE;
|
||||
|
||||
use Psr\Container\ContainerInterface;
|
||||
use ML\IRI\IRI, ML\JsonLD\Node;
|
||||
use DI\Container, DI\FactoryInterface, Invoker\InvokerInterface;
|
||||
use DI\Definition\Source\DefinitionArray, DI\Definition\Source\SourceChain;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use CloudObjects\SDK\ObjectRetriever, CloudObjects\SDK\COIDParser,
|
||||
CloudObjects\SDK\NodeReader;
|
||||
use CloudObjects\PhpMAE\Exceptions\PhpMAEException;
|
||||
|
||||
class ClassRepository {
|
||||
|
||||
const DEFAULT_STACK = 'coid://phpmae.dev/DefaultStack';
|
||||
|
||||
private $options;
|
||||
private $classMap = [];
|
||||
private $container;
|
||||
private $reader;
|
||||
|
||||
private function loader($classname) {
|
||||
if (isset($this->classMap[$classname])) {
|
||||
include $this->classMap[$classname];
|
||||
}
|
||||
}
|
||||
|
||||
public function __construct(ContainerInterface $container) {
|
||||
$this->options = [
|
||||
'cache_dir' => $container->get('cache_dir') . '/classes',
|
||||
'uploads_dir' => $container->get('uploads_dir'),
|
||||
'uploads' => $container->get('uploads')
|
||||
];
|
||||
|
||||
$this->container = $container;
|
||||
|
||||
$this->reader = new NodeReader([
|
||||
'prefixes' => [
|
||||
'phpmae' => 'coid://phpmae.dev/'
|
||||
]
|
||||
]);
|
||||
|
||||
// Initialize autoloader
|
||||
spl_autoload_register(array($this, 'loader'));
|
||||
}
|
||||
|
||||
private function getURIVars(IRI $uri) {
|
||||
$vars = array('uri_hash' => strtoupper(md5((string)$uri)));
|
||||
$vars['php_namespace'] = "CloudObjects\\PhpMAE\\Class_".$vars['uri_hash'];
|
||||
$vars['php_classname_local'] = (COIDParser::getType($uri)==COIDParser::COID_VERSIONED)
|
||||
? substr($uri->getPath(), 1, strrpos($uri->getPath(), '/')-1)
|
||||
: substr($uri->getPath(), 1);
|
||||
$vars['php_classname'] = $vars['php_namespace'].'\\'.$vars['php_classname_local'];
|
||||
$vars['cache_path'] = $this->options['cache_dir'].DIRECTORY_SEPARATOR.$vars['uri_hash'];
|
||||
$vars['upload_filename'] = $this->options['uploads_dir'].DIRECTORY_SEPARATOR.$vars['uri_hash'].'.php';
|
||||
if ($this->options['uploads'] == true && !is_dir($this->options['uploads_dir']))
|
||||
mkdir($this->options['uploads_dir'], 0777, true);
|
||||
return $vars;
|
||||
}
|
||||
|
||||
public function coidToClassName(IRI $uri) {
|
||||
$vars = $this->getURIVars($uri);
|
||||
return $vars['php_classname'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a path on which custom files can be cached for a object.
|
||||
* @param Node $object
|
||||
*/
|
||||
public function getCustomFilesCachePath(Node $object) {
|
||||
$path = $this->options['cache_dir'].DIRECTORY_SEPARATOR
|
||||
.strtoupper(md5($object->getId())).DIRECTORY_SEPARATOR
|
||||
.($object->getProperty(ObjectRetriever::REVISION_PROPERTY)
|
||||
? $object->getProperty(ObjectRetriever::REVISION_PROPERTY)->getValue()
|
||||
: 'LocalConfig');
|
||||
if (!is_dir($path)) mkdir($path, 0777, true);
|
||||
return $path;
|
||||
}
|
||||
|
||||
public function storeUploadedFile(Node $object, $content) {
|
||||
$uri = new IRI($object->getId());
|
||||
$vars = $this->getURIVars($uri);
|
||||
|
||||
// Fetch class description
|
||||
$revision = $object->getProperty(ObjectRetriever::REVISION_PROPERTY)
|
||||
? $object->getProperty(ObjectRetriever::REVISION_PROPERTY)->getValue()
|
||||
: 'LocalConfig';
|
||||
|
||||
// Clear cache if cached version exists
|
||||
$cachedFilename = $vars['cache_path'].DIRECTORY_SEPARATOR.$revision.".php";
|
||||
if (file_exists($cachedFilename)) unlink($cachedFilename);
|
||||
|
||||
// Store source
|
||||
file_put_contents($vars['upload_filename'], $content);
|
||||
return true;
|
||||
}
|
||||
|
||||
private function buildContainer($className, Node $object, $additionalDefinitions = []) {
|
||||
$autowiring = new DI\WhitelistReflectionBasedAutowiring;
|
||||
|
||||
$sources = [
|
||||
new DefinitionArray($this->container->get(DI\DependencyInjector::class)
|
||||
->getDependencies($object, $additionalDefinitions), $autowiring),
|
||||
new DefinitionArray([
|
||||
Engine::SKEY => \DI\autowire($className)
|
||||
], $autowiring),
|
||||
$autowiring
|
||||
];
|
||||
|
||||
$source = new SourceChain($sources);
|
||||
$source->setMutableDefinitionSource(new DefinitionArray([], $autowiring));
|
||||
|
||||
// TODO: add compilation
|
||||
|
||||
$container = new Container($source);
|
||||
$sandboxedContainer = new DI\SandboxedContainer($container);
|
||||
$container->set(ContainerInterface::class, $sandboxedContainer);
|
||||
$container->set(Container::class, $sandboxedContainer);
|
||||
$container->set(FactoryInterface::class, $sandboxedContainer);
|
||||
$container->set(InvokerInterface::class, $sandboxedContainer);
|
||||
return $sandboxedContainer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance of a class. Returns a container that includes the class itself as Engine::SKEY
|
||||
* as well as all dependencies specified by the class.
|
||||
*
|
||||
* @param Node $object The object describing the class.
|
||||
* @param RequestInterface $request The optional request, if it should be made available
|
||||
* @param array $additionalDefinitions Definitions to add to the DI container for this class
|
||||
* @param string $sourceCode Optional source code to use for the class implementation instead of retrieving.
|
||||
*/
|
||||
public function createInstance(Node $object, RequestInterface $request = null,
|
||||
array $additionalDefinitions = [], string $sourceCode = null) {
|
||||
// Check type
|
||||
if (!TypeChecker::isClass($object))
|
||||
throw new PhpMAEException("<".$object->getId()."> must have a valid type.");
|
||||
|
||||
$uri = new IRI($object->getId());
|
||||
$vars = $this->getURIVars($uri);
|
||||
if (!isset($this->classMap[$vars['php_classname']])) {
|
||||
$objectRetriever = $this->container->get(ObjectRetriever::class);
|
||||
|
||||
// Get revision
|
||||
$revision = $object->getProperty(ObjectRetriever::REVISION_PROPERTY)
|
||||
? $object->getProperty(ObjectRetriever::REVISION_PROPERTY)->getValue()
|
||||
: 'LocalConfig';
|
||||
|
||||
// Build filename where cached version should exist
|
||||
$filename = $vars['cache_path'].DIRECTORY_SEPARATOR.$revision.".php";
|
||||
$this->container->get(ErrorHandler::class)
|
||||
->addMapping($filename, $object);
|
||||
|
||||
// Check for required implemented interfaces
|
||||
$interfaces = [];
|
||||
foreach (TypeChecker::getAdditionalTypes($object) as $i) {
|
||||
$interfaceObject = $objectRetriever->get($i);
|
||||
if (!TypeChecker::isInterface($interfaceObject))
|
||||
continue;
|
||||
|
||||
$this->createInterfaceInstance($interfaceObject);
|
||||
$interfaces[] = $i;
|
||||
}
|
||||
|
||||
// Load the stack that the class requires
|
||||
$stack = $this->reader->getFirstValueString($object, 'phpmae:usesStack',
|
||||
self::DEFAULT_STACK);
|
||||
|
||||
$stackDir = __DIR__.'/../stacks/'.md5($stack);
|
||||
if (!is_dir($stackDir))
|
||||
throw new PhpMAEException("The stack <".$stack."> is not installed on this phpMAE instance.");
|
||||
|
||||
require_once $stackDir.'/vendor/autoload.php';
|
||||
|
||||
if (!file_exists($filename) || $sourceCode != null) {
|
||||
if (!isset($sourceCode)) {
|
||||
// File does not exist -> check in uploads first
|
||||
if (file_exists($vars['upload_filename'])) {
|
||||
$sourceCode = file_get_contents($vars['upload_filename']);
|
||||
} else {
|
||||
// Not found in local uploads -> download source from CloudObjects
|
||||
$sourceUrl = $object->getProperty('coid://phpmae.dev/hasSourceFile');
|
||||
if (!$sourceUrl) throw new PhpMAEException("<".$object->getId()."> does not have an implementation source file.");
|
||||
if (get_class($sourceUrl)=='ML\JsonLD\Node')
|
||||
$sourceCode = $objectRetriever->getAttachment($uri, $sourceUrl->getId());
|
||||
else
|
||||
$sourceCode = $objectRetriever->getAttachment($uri, $sourceUrl->getValue());
|
||||
}
|
||||
}
|
||||
|
||||
// Run source code through validator to ensure sanity
|
||||
// and convert to sandbox
|
||||
$validator = new ClassValidator;
|
||||
$sourceCode = $validator->validate($sourceCode, $uri, $interfaces);
|
||||
|
||||
// Add namespaces for dependencies and interfaces
|
||||
$use = '';
|
||||
$classList = array_merge(
|
||||
$this->container->get(DI\DependencyInjector::class)->getClassDependencyList($object),
|
||||
$interfaces
|
||||
);
|
||||
foreach ($classList as $cl)
|
||||
$use .= " use ".$this->coidToClassName($cl).";";
|
||||
|
||||
// Add namespace declaration
|
||||
$sourceCode = "<?php namespace ".$vars['php_namespace'].";".$use.$sourceCode;
|
||||
$sourceCode = str_replace($vars['php_classname_local'].'::', '\\'.$vars['php_classname'].'::', $sourceCode);
|
||||
|
||||
// Store
|
||||
if (!file_exists($vars['cache_path'])) mkdir($vars['cache_path'], 0777, true);
|
||||
file_put_contents($filename, $sourceCode);
|
||||
}
|
||||
|
||||
$this->classMap[$vars['php_classname']] = $filename;
|
||||
}
|
||||
|
||||
if (isset($request)) {
|
||||
$additionalDefinitions['request'] = $request;
|
||||
$additionalDefinitions[RequestInterface::class] = $request;
|
||||
}
|
||||
|
||||
return $this->buildContainer($vars['php_classname'], $object, $additionalDefinitions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance of an interface.
|
||||
*
|
||||
* @param Node $object The object describing the interface.
|
||||
*/
|
||||
public function createInterfaceInstance(Node $object) {
|
||||
// Check type
|
||||
if (!TypeChecker::isInterface($object))
|
||||
throw new PhpMAEException("<".$object->getId()."> must have a valid type.");
|
||||
|
||||
$uri = new IRI($object->getId());
|
||||
$vars = $this->getURIVars($uri);
|
||||
if (!isset($this->classMap[$vars['php_classname']])) {
|
||||
$objectRetriever = $this->container->get(ObjectRetriever::class);
|
||||
|
||||
// Get revision
|
||||
$revision = $object->getProperty(ObjectRetriever::REVISION_PROPERTY)
|
||||
? $object->getProperty(ObjectRetriever::REVISION_PROPERTY)->getValue()
|
||||
: 'LocalConfig';
|
||||
|
||||
// Build filename where cached version should exist
|
||||
$filename = $vars['cache_path'].DIRECTORY_SEPARATOR.$revision.".php";
|
||||
$this->container->get(ErrorHandler::class)
|
||||
->addMapping($filename, $object);
|
||||
|
||||
if (!file_exists($filename)) {
|
||||
// File does not exist -> check in uploads first
|
||||
if (file_exists($vars['upload_filename'])) {
|
||||
$sourceCode = file_get_contents($vars['upload_filename']);
|
||||
} else {
|
||||
// Not found in local uploads -> download source from CloudObjects
|
||||
$sourceUrl = $object->getProperty('coid://phpmae.dev/hasDefinitionFile');
|
||||
if (!$sourceUrl) throw new PhpMAEException("<".$object->getId()."> does not have a definition file.");
|
||||
if (get_class($sourceUrl)=='ML\JsonLD\Node')
|
||||
$sourceCode = $objectRetriever->getAttachment($uri, $sourceUrl->getId());
|
||||
else
|
||||
$sourceCode = $objectRetriever->getAttachment($uri, $sourceUrl->getValue());
|
||||
}
|
||||
|
||||
// Run source code through validator to ensure sanity
|
||||
$validator = new ClassValidator;
|
||||
$validator->validateInterface($sourceCode, $uri);
|
||||
|
||||
// Add namespace declaration
|
||||
$sourceCode = str_replace("<?php", "<?php namespace ".$vars['php_namespace'].";", $sourceCode);
|
||||
$sourceCode = str_replace($vars['php_classname_local'].'::', '\\'.$vars['php_classname'].'::', $sourceCode);
|
||||
|
||||
// Store
|
||||
if (!file_exists($vars['cache_path'])) mkdir($vars['cache_path'], 0777, true);
|
||||
file_put_contents($filename, $sourceCode);
|
||||
}
|
||||
|
||||
$this->classMap[$vars['php_classname']] = $filename;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
<?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\PhpMAE;
|
||||
|
||||
use CloudObjects\PhpMAE\Sandbox\CustomizedSandbox;
|
||||
use PHPSandbox\SandboxWhitelistVisitor, PHPSandbox\ValidatorVisitor;
|
||||
use PhpParser\ParserFactory, PhpParser\NodeTraverser;
|
||||
use ML\IRI\IRI;
|
||||
use CloudObjects\SDK\COIDParser;
|
||||
use CloudObjects\PhpMAE\Exceptions\PhpMAEException;
|
||||
|
||||
/**
|
||||
* Validates that classes fit the sandbox criteria.
|
||||
*/
|
||||
class ClassValidator {
|
||||
|
||||
private $sandbox;
|
||||
private $whitelisted_interfaces;
|
||||
private $whitelisted_types;
|
||||
private $aliases;
|
||||
|
||||
public function __construct() {
|
||||
$this->sandbox = new CustomizedSandbox;
|
||||
|
||||
$this->whitelisted_interfaces = [
|
||||
'Symfony\Component\EventDispatcher\EventSubscriberInterface',
|
||||
'Psr\Http\Message\RequestInterface',
|
||||
'Psr\Container\ContainerInterface',
|
||||
'Psr\SimpleCache\CacheInterface',
|
||||
'Psr\Http\Message\ResponseInterface',
|
||||
'Symfony\Component\Mailer\MailerInterface'
|
||||
];
|
||||
$this->whitelisted_types = [
|
||||
'ArrayObject', 'DateInterval', 'DateTime', 'DateTimeImmutable', 'DateTimeZone',
|
||||
'DOMElement', 'Exception',
|
||||
'SimpleXMLElement',
|
||||
'ML\IRI\IRI',
|
||||
'ML\JsonLD\JsonLD', 'ML\JsonLD\Node', 'ML\JsonLD\Graph', 'ML\JsonLD\TypedValue',
|
||||
'GuzzleHttp\Client',
|
||||
'GuzzleHttp\HandlerStack', 'GuzzleHttp\Middleware',
|
||||
'GuzzleHttp\Handler\CurlHandler',
|
||||
'GuzzleHttp\Subscriber\Oauth\Oauth1',
|
||||
'GuzzleHttp\Promise',
|
||||
'Dflydev\FigCookies\SetCookie',
|
||||
'Symfony\Component\Mime\Email',
|
||||
'CloudObjects\PhpMAE\ConfigLoader',
|
||||
'CloudObjects\PhpMAE\TwigTemplateFactory',
|
||||
'CloudObjects\PhpMAE\DI\DynamicLoader',
|
||||
'CloudObjects\SDK\ObjectRetriever',
|
||||
'CloudObjects\SDK\NodeReader',
|
||||
'CloudObjects\SDK\AccountGateway\AccountContext',
|
||||
'CloudObjects\SDK\AccountGateway\AAUIDParser',
|
||||
'CloudObjects\SDK\COIDParser',
|
||||
'CloudObjects\SDK\Common\CryptoHelper',
|
||||
'Webmozart\Assert\Assert'
|
||||
];
|
||||
}
|
||||
|
||||
public function isWhitelisted($name) {
|
||||
return in_array($name, $this->whitelisted_types)
|
||||
|| in_array($name, $this->whitelisted_interfaces);
|
||||
}
|
||||
|
||||
private function initializeWhitelist($stack = ClassRepository::DEFAULT_STACK) {
|
||||
// Generate whitelist based on alias names
|
||||
$interfaces = [];
|
||||
$types = [];
|
||||
foreach ($this->whitelisted_interfaces as $i) {
|
||||
$interfaces[] = (isset($this->aliases[$i]))
|
||||
? strtolower($this->aliases[$i]) : strtolower($i);
|
||||
// Validation of some elements relies on interfaces also being added as types
|
||||
$types[] = (isset($this->aliases[$i]))
|
||||
? strtolower($this->aliases[$i]) : strtolower($i);
|
||||
}
|
||||
foreach ($this->whitelisted_types as $t) {
|
||||
$types[] = (isset($this->aliases[$t]))
|
||||
? strtolower($this->aliases[$t]) : strtolower($t);
|
||||
}
|
||||
|
||||
// Load and apply stack
|
||||
$filename = __DIR__.'/../stacks/'.md5($stack).'/meta.json';
|
||||
if (!file_exists($filename))
|
||||
throw new PhpMAEException("The specified stack <".$stack."> is not installed.");
|
||||
$stackMeta = json_decode(file_get_contents($filename), true);
|
||||
if (isset($stackMeta['whitelisted_classes'])) {
|
||||
foreach ($stackMeta['whitelisted_classes'] as $t) {
|
||||
$types[] = (isset($this->aliases[$t]))
|
||||
? strtolower($this->aliases[$t]) : strtolower($t);
|
||||
}
|
||||
}
|
||||
|
||||
// Apply to sandbox
|
||||
$this->sandbox->whitelist([
|
||||
'interfaces' => $interfaces,
|
||||
'types' => $types,
|
||||
'classes' => $types
|
||||
]);
|
||||
}
|
||||
|
||||
public function validate($sourceCode, IRI $coid, array $interfaceCoids = [],
|
||||
$stack = ClassRepository::DEFAULT_STACK) {
|
||||
|
||||
// Initialize parser and parse source code
|
||||
$parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);
|
||||
$ast = $parser->parse($sourceCode);
|
||||
|
||||
// Parse and dump use statements
|
||||
$aliasMap = array();
|
||||
while (get_class($ast[0])=='PhpParser\Node\Stmt\Use_') {
|
||||
foreach ($ast[0]->uses as $use) {
|
||||
$name = (string)$use->name;
|
||||
$aliasMap[$use->alias] = $name;
|
||||
$this->aliases[$name] = $use->alias;
|
||||
}
|
||||
array_shift($ast);
|
||||
}
|
||||
|
||||
// Check for class definition and implemented interfaces
|
||||
if (count($ast)==1 && get_class($ast[0])=='PhpParser\Node\Stmt\Class_'
|
||||
&& isset($ast[0]->implements)) {
|
||||
|
||||
if ($ast[0]->name != COIDParser::getName($coid))
|
||||
throw new PhpMAEException("The PHP classname (".$ast[0]->name.") doesn't match the name segment of the COID (".COIDParser::getName($coid).").");
|
||||
|
||||
$interfaces = [];
|
||||
foreach ($ast[0]->implements as $i) {
|
||||
$name = (string)$i;
|
||||
$interfaces[] = (isset($aliasMap[$name])) ? $aliasMap[$name] : $name;
|
||||
}
|
||||
|
||||
// Check for any interfaces and whitelist them for validation
|
||||
foreach ($interfaceCoids as $coid) {
|
||||
if (!COIDParser::isValidCOID($coid)) continue;
|
||||
|
||||
$name = COIDParser::getName($coid);
|
||||
$found = false;
|
||||
foreach ($interfaces as $i) {
|
||||
if ($i == $name) {
|
||||
$this->whitelisted_interfaces[] = $i;
|
||||
$found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!$found)
|
||||
throw new PhpMAEException("Source code file must declare a class that implements <".$name.">.");
|
||||
}
|
||||
|
||||
// Allow self-references
|
||||
$this->whitelisted_types[] = strtolower($ast[0]->name);
|
||||
|
||||
// Initialize whitelist
|
||||
$this->initializeWhitelist($stack);
|
||||
|
||||
// Validate and prepare code in sandbox
|
||||
return $this->sandbox->prepare($sourceCode);
|
||||
|
||||
} else {
|
||||
// Throw exeption if conditions are not met
|
||||
throw new PhpMAEException("Source code file must include exactly one class declaration and must not contain further side effects.");
|
||||
}
|
||||
}
|
||||
|
||||
public function validateInterface($sourceCode, IRI $coid) {
|
||||
// Initialize parser and parse source code
|
||||
$parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);
|
||||
$ast = $parser->parse($sourceCode);
|
||||
|
||||
// Parse and dump use statements
|
||||
$aliasMap = array();
|
||||
while (get_class($ast[0])=='PhpParser\Node\Stmt\Use_') {
|
||||
foreach ($ast[0]->uses as $use) {
|
||||
$name = (string)$use->name;
|
||||
$aliasMap[$use->alias] = $name;
|
||||
$this->aliases[$name] = $use->alias;
|
||||
}
|
||||
array_shift($ast);
|
||||
}
|
||||
|
||||
// Check for class definition and implemented interfaces
|
||||
if (count($ast)==1 && get_class($ast[0])=='PhpParser\Node\Stmt\Interface_') {
|
||||
|
||||
if ($ast[0]->name != COIDParser::getName($coid))
|
||||
throw new PhpMAEException("The PHP interface name (".$ast[0]->name.") doesn't match the name segment of the COID (".COIDParser::getName($coid).").");
|
||||
|
||||
// Allow self-references
|
||||
$this->whitelisted_types[] = strtolower($ast[0]->name);
|
||||
|
||||
// Initialize whitelist
|
||||
$this->initializeWhitelist();
|
||||
|
||||
// Apply whitelist visitor
|
||||
$traverser = new NodeTraverser;
|
||||
$traverser->addVisitor(new SandboxWhitelistVisitor($this->sandbox));
|
||||
$traverser->addVisitor(new ValidatorVisitor($this->sandbox));
|
||||
$traverser->traverse($ast);
|
||||
|
||||
return;
|
||||
} else {
|
||||
// Throw exeption if conditions are not met
|
||||
throw new PhpMAEException("Source code file must include exactly one interface declaration and must not contain further side effects.");
|
||||
}
|
||||
}
|
||||
|
||||
public static function isInvokableClass($class) {
|
||||
return method_exists($class, '__invoke');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
<?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\PhpMAE\Commands;
|
||||
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class AbstractAddDependenciesCommand extends AbstractObjectCommand {
|
||||
|
||||
protected function configure() {
|
||||
$this->addOption('confjob', null, InputOption::VALUE_NONE, 'Calls "cloudobjects" to create a configuration job for the updated class.');
|
||||
}
|
||||
|
||||
protected function dependencyPrecheck() {
|
||||
$this->assertRDF();
|
||||
$this->assertPHPExists();
|
||||
}
|
||||
|
||||
protected function getObjectAndAssertType(string $coid, string $type) {
|
||||
// Retrieve configuration
|
||||
$config = shell_exec("cloudobjects get ".$coid);
|
||||
if (!isset($config))
|
||||
throw new \Exception("Could not retrieve <".$coid.">.");
|
||||
|
||||
// Parse and validate configuration
|
||||
$parser = \ARC2::getRDFXMLParser();
|
||||
$parser->parse('', $config);
|
||||
$index = $parser->getSimpleIndex(false);
|
||||
if (!isset($index) || !isset($index[$coid])
|
||||
|| !isset($index[$coid]['http://www.w3.org/1999/02/22-rdf-syntax-ns#type']))
|
||||
throw new \Exception("<".$coid."> is not a valid CloudObjects object.");
|
||||
|
||||
$hasType = false;
|
||||
foreach ($index[$coid]['http://www.w3.org/1999/02/22-rdf-syntax-ns#type'] as $property => $values) {
|
||||
if ($values['value'] == $type) {
|
||||
$hasType = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$hasType)
|
||||
throw new \Exception("<".$coid."> must have the type <".$type.">.");
|
||||
}
|
||||
|
||||
protected function addDependency(string $key, string $type, array $valuesToMerge,
|
||||
InputInterface $input, OutputInterface $output) {
|
||||
|
||||
$this->index['_:dep-'.$key] = array_merge([
|
||||
'http://www.w3.org/1999/02/22-rdf-syntax-ns#type' => [
|
||||
[ 'type' => 'uri', 'value' => $type ]
|
||||
],
|
||||
'coid://phpmae.dev/hasKey' => [
|
||||
[ 'type' => 'literal', 'value' => $key ]
|
||||
]
|
||||
], $valuesToMerge);
|
||||
|
||||
// Edit configuration
|
||||
$object = $this->index[(string)$this->coid];
|
||||
if (isset($object['coid://phpmae.dev/hasDependency'])) {
|
||||
foreach ($object['coid://phpmae.dev/hasDependency'] as $o) {
|
||||
if ($o['type'] != 'bnode') continue;
|
||||
$bnode = $this->index[$o['value']];
|
||||
if (isset($bnode['coid://phpmae.dev/hasKey']) && $bnode['coid://phpmae.dev/hasKey'][0]['value'] == $key)
|
||||
throw new \Exception("A dependency with the key '".$key."' already exists.");
|
||||
|
||||
foreach ($valuesToMerge as $k => $values)
|
||||
if (isset($bnode[$k]))
|
||||
foreach ($bnode[$k] as $a)
|
||||
foreach ($valuesToMerge[$k] as $b)
|
||||
if ($a['type'] == $b['type'] && $a['value'] == $b['value'])
|
||||
throw new \Exception("This dependency was already added.");
|
||||
}
|
||||
$object['coid://phpmae.dev/hasDependency'][] = [ 'type' => 'bnode', 'value' => '_:dep-'.$key ];
|
||||
} else
|
||||
$object['coid://phpmae.dev/hasDependency'] = [['type' => 'bnode', 'value' => '_:dep-'.$key ]];
|
||||
|
||||
// Persist configuration
|
||||
$this->index[(string)$this->coid] = $object;
|
||||
$this->updateRDFLocally($output);
|
||||
|
||||
if ($input->getOption('confjob'))
|
||||
$this->createConfigurationJob($output);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
<?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\PhpMAE\Commands;
|
||||
|
||||
use Exception;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use ML\IRI\IRI;
|
||||
use CloudObjects\SDK\COIDParser;
|
||||
use CloudObjects\PhpMAE\CredentialManager;
|
||||
|
||||
abstract class AbstractObjectCommand extends Command {
|
||||
|
||||
protected $coid;
|
||||
protected $fullName;
|
||||
protected $phpFileName;
|
||||
protected $xmlFileName;
|
||||
protected $rdfTypes;
|
||||
protected $index;
|
||||
|
||||
protected function requireCloudObjectsCLI() {
|
||||
if (!CredentialManager::isConfigured())
|
||||
throw new Exception("The 'cloudobjects' CLI tool must be installed and authorized.");
|
||||
}
|
||||
|
||||
protected function parse($coid) {
|
||||
$this->coid = COIDParser::fromString($coid);
|
||||
|
||||
if (COIDParser::getType($this->coid)!=COIDParser::COID_VERSIONED
|
||||
&& COIDParser::getType($this->coid)!=COIDParser::COID_UNVERSIONED)
|
||||
throw new Exception("Invalid COID: ".(string)$this->coid);
|
||||
|
||||
$name = COIDParser::getName($this->coid);
|
||||
$version = COIDParser::getVersion($this->coid);
|
||||
$this->fullName = isset($version) ? $name.".".$version : $name;
|
||||
$this->phpFileName = $this->fullName.'.php';
|
||||
$this->xmlFileName = $this->fullName.'.xml';
|
||||
}
|
||||
|
||||
protected function assertRDF() {
|
||||
if (!file_exists($this->xmlFileName))
|
||||
throw new Exception("File not found: ".$this->xmlFileName);
|
||||
|
||||
$parser = \ARC2::getRDFXMLParser();
|
||||
$parser->parse('', file_get_contents($this->xmlFileName));
|
||||
$index = $parser->getSimpleIndex(false);
|
||||
$id = (string)$this->coid;
|
||||
if (!isset($index) || !isset($index[$id])
|
||||
|| !isset($index[$id]['http://www.w3.org/1999/02/22-rdf-syntax-ns#type']))
|
||||
throw new Exception($this->xmlFileName." does not contain a valid RDF description of the object.");
|
||||
|
||||
$this->rdfTypes = [];
|
||||
foreach ($index[$id]['http://www.w3.org/1999/02/22-rdf-syntax-ns#type'] as $o)
|
||||
if ($o['type'] == 'uri') $this->rdfTypes[] = $o['value'];
|
||||
$this->index = $index;
|
||||
}
|
||||
|
||||
protected function updateRDFLocally(OutputInterface $output) {
|
||||
file_put_contents($this->xmlFileName,
|
||||
$this->getSerializer()->getSerializedIndex($this->index));
|
||||
$output->writeln("Updated ".$this->xmlFileName);
|
||||
}
|
||||
|
||||
protected function createConfigurationJob(OutputInterface $output) {
|
||||
$output->writeln("Calling cloudobjects configuration-job:create ...");
|
||||
passthru("cloudobjects configuration-job:create ".$this->xmlFileName);
|
||||
}
|
||||
|
||||
protected function ensureFilenameInConfig(OutputInterface $output, $interface = false) {
|
||||
$property = ($interface ? 'coid://phpmae.dev/hasDefinitionFile'
|
||||
: 'coid://phpmae.dev/hasSourceFile');
|
||||
$object = $this->index[(string)$this->coid];
|
||||
if (!isset($object[$property])) {
|
||||
$object[$property] = [[
|
||||
'value' => 'file:///'.$this->fullName.'.php',
|
||||
'type' => 'uri'
|
||||
]];
|
||||
$this->index[(string)$this->coid] = $object;
|
||||
$this->updateRDFLocally($output);
|
||||
return true;
|
||||
} else
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function assertPHPExists() {
|
||||
if (!file_exists($this->phpFileName))
|
||||
throw new Exception("File not found: ".$this->phpFileName);
|
||||
}
|
||||
|
||||
protected function getSerializer() {
|
||||
return \ARC2::getRDFXMLSerializer(array(
|
||||
'serializer_type_nodes' => true,
|
||||
'ns' => array(
|
||||
'co' => 'coid://cloudobjects.io/',
|
||||
'phpmae' => 'coid://phpmae.dev/'
|
||||
)
|
||||
));
|
||||
}
|
||||
|
||||
protected function watchPHPFile(OutputInterface $output, callable $callable) {
|
||||
$fileTime = filemtime($this->phpFileName);
|
||||
$output->writeln('Watching for changes ...');
|
||||
set_time_limit(0);
|
||||
while (true) {
|
||||
clearstatcache();
|
||||
if (filemtime($this->phpFileName) != $fileTime) {
|
||||
// File has changed ...
|
||||
sleep(1);
|
||||
$fileTime = filemtime($this->phpFileName);
|
||||
$callable();
|
||||
$output->writeln('Watching for changes ...');
|
||||
}
|
||||
sleep(0.5);
|
||||
}
|
||||
}
|
||||
|
||||
protected function getAdditionalTypes() {
|
||||
$coids = [];
|
||||
foreach ($this->rdfTypes as $t) {
|
||||
if (in_array($t, [ 'coid://phpmae.dev/Class',
|
||||
'coid://phpmae.dev/HTTPInvokableClass',
|
||||
'coid://phpmae.dev/Interface' ]))
|
||||
continue;
|
||||
$coids[] = new IRI($t);
|
||||
}
|
||||
|
||||
return $coids;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
<?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\PhpMAE\Commands;
|
||||
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use ML\IRI\IRI;
|
||||
use CloudObjects\SDK\COIDParser;
|
||||
use CloudObjects\PhpMAE\CredentialManager, CloudObjects\PhpMAE\ClassValidator;
|
||||
use CloudObjects\PhpMAE\Exceptions\PhpMAEException;
|
||||
|
||||
class ClassCreateCommand extends Command {
|
||||
|
||||
protected function configure() {
|
||||
$this->setName('class:create')
|
||||
->setDescription('Create a new class for the phpMAE.')
|
||||
->addArgument('coid', InputArgument::REQUIRED, 'The COID of the object.')
|
||||
->addOption('http-invokable', 'hi', InputOption::VALUE_NONE, 'Makes the class HTTP-invokable.')
|
||||
->addOption('force', 'f', InputOption::VALUE_NONE, 'Forces new object creation and replaces existing files.')
|
||||
->addOption('public', null, InputOption::VALUE_NONE, 'Marks new object as public.')
|
||||
->addOption('confjob', null, InputOption::VALUE_NONE, 'Calls "cloudobjects" to create a configuration job for the new class.')
|
||||
->addOption('autowire', null, InputOption::VALUE_REQUIRED, 'Creates a constructor that autowires a PHP dependency.', null)
|
||||
->addOption('implements', null, InputOption::VALUE_REQUIRED, 'Make this class an implementation of the phpMAE interface with the specified COID.', null);
|
||||
}
|
||||
|
||||
private function getAndValidateInterfaceConfig($implements) {
|
||||
// Check interface COID
|
||||
$interfaceCoid = COIDParser::fromString($implements);
|
||||
if (COIDParser::getType($interfaceCoid) != COIDParser::COID_VERSIONED
|
||||
&& COIDParser::getType($interfaceCoid) != COIDParser::COID_UNVERSIONED)
|
||||
throw new \Exception("Invalid Interface COID: ".(string)$interfaceCoid);
|
||||
|
||||
// Retrieve interface configuration
|
||||
$implements = (string)$interfaceCoid;
|
||||
$interfaceConfig = shell_exec("cloudobjects get ".$implements);
|
||||
if (!isset($interfaceConfig))
|
||||
throw new \Exception("Could not retrieve interface configuration.");
|
||||
|
||||
// Parse and validate interface configuration
|
||||
$parser = \ARC2::getRDFXMLParser();
|
||||
$parser->parse('', $interfaceConfig);
|
||||
$index = $parser->getSimpleIndex(false);
|
||||
if (!isset($index) || !isset($index[$implements])
|
||||
|| !isset($index[$implements]['http://www.w3.org/1999/02/22-rdf-syntax-ns#type']))
|
||||
throw new \Exception("<".$implements."> is not a valid CloudObjects object.");
|
||||
|
||||
$isInterface = false;
|
||||
foreach ($index[$implements]['http://www.w3.org/1999/02/22-rdf-syntax-ns#type'] as $property => $values) {
|
||||
if ($values['value'] == 'coid://phpmae.dev/Interface')
|
||||
$isInterface = true;
|
||||
}
|
||||
|
||||
if (!$isInterface || !isset($index[$implements]['coid://phpmae.dev/hasDefinitionFile']))
|
||||
throw new \Exception("<".$implements."> is not a valid phpMAE interface.");
|
||||
|
||||
return [
|
||||
'classname' => COIDParser::getName($interfaceCoid),
|
||||
'filename' => basename($index[$implements]['coid://phpmae.dev/hasDefinitionFile'][0]['value']),
|
||||
'coid' => (string)$interfaceCoid,
|
||||
];
|
||||
}
|
||||
|
||||
private function getAndParseInterfaceCode($interface, $filename) {
|
||||
// Retrieve interface code
|
||||
$interfaceCode = shell_exec("cloudobjects attachment:get ".$interface
|
||||
." ".$filename);
|
||||
|
||||
// Run through validator
|
||||
$validator = new ClassValidator;
|
||||
$validator->validateInterface($interfaceCode, new IRI($interface));
|
||||
|
||||
// Find use statements
|
||||
$matches = [];
|
||||
preg_match_all("/use\s+(.+);/", $interfaceCode, $matches);
|
||||
$use = $matches[1];
|
||||
|
||||
// Parse method definitions
|
||||
// (this is the same algorithm as the DirectoryTemplateVariableGenerator,
|
||||
// but less filtering on comment string)
|
||||
$matches = [];
|
||||
preg_match_all("/(?:\/\*\*((?:[\s\S](?!\/\*))*?)\*\/+\s*)?public\s+function\s+(\w+)\s*\((.+)\)/",
|
||||
$interfaceCode, $matches);
|
||||
|
||||
// The following groups are captured through RegExes:
|
||||
// 0 - complete definition block
|
||||
// 1 - comment string
|
||||
// 2 - method name
|
||||
// 3 - method parameters
|
||||
|
||||
$methods = [];
|
||||
for ($i = 0; $i < count($matches[0]); $i++) {
|
||||
// List methods with parameters and comment
|
||||
$methods[] = [
|
||||
'name' => $matches[2][$i],
|
||||
'params' => trim($matches[3][$i]),
|
||||
'comment' => trim($matches[1][$i])
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'methods' => $methods,
|
||||
'use' => $use
|
||||
];
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output) {
|
||||
if (!CredentialManager::isConfigured())
|
||||
throw new \Exception("The 'cloudobjects' CLI tool must be installed and authorized.");
|
||||
|
||||
$coid = COIDParser::fromString($input->getArgument('coid'));
|
||||
|
||||
if (COIDParser::getType($coid) != COIDParser::COID_VERSIONED
|
||||
&& COIDParser::getType($coid) != COIDParser::COID_UNVERSIONED)
|
||||
throw new \Exception("Invalid COID: ".(string)$coid);
|
||||
|
||||
$name = COIDParser::getName($coid);
|
||||
$version = COIDParser::getVersion($coid);
|
||||
$fullName = isset($version) ? $name.".".$version : $name;
|
||||
$invokable = $input->getOption('http-invokable');
|
||||
|
||||
if ($input->getOption('implements') != null) {
|
||||
if ($invokable)
|
||||
throw new \Exception("The 'implements' option cannot be used with 'http-invokable'.");
|
||||
|
||||
$implements = $input->getOption('implements');
|
||||
$output->writeln("Fetching configuration for ".$implements." ...");
|
||||
$interfaceData = $this->getAndValidateInterfaceConfig($implements);
|
||||
}
|
||||
|
||||
if (!file_exists($fullName.'.xml') || $input->getOption('force')) {
|
||||
// Create RDF configuration file
|
||||
$content = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
|
||||
. "<rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\"\n"
|
||||
. " xmlns:co=\"coid://cloudobjects.io/\"\n"
|
||||
. " xmlns:phpmae=\"coid://phpmae.dev/\">\n"
|
||||
. "\n"
|
||||
. " <phpmae:".($invokable ? "HTTPInvokable" : "")."Class rdf:about=\"".(string)$coid."\">\n";
|
||||
|
||||
// Mark as public if option was defined
|
||||
if ($input->getOption('public'))
|
||||
$content .= " <co:isVisibleTo rdf:resource=\"coid://cloudobjects.io/Public\" />\n"
|
||||
. " <co:permitsUsageTo rdf:resource=\"coid://cloudobjects.io/Public\" />\n";
|
||||
else
|
||||
$content .= " <co:isVisibleTo rdf:resource=\"coid://cloudobjects.io/Vendor\" />\n";
|
||||
|
||||
if (isset($implements))
|
||||
$content .= " <rdf:type rdf:resource=\"".$interfaceData['coid']."\" />\n";
|
||||
$content .=" </phpmae:".($invokable ? "HTTPInvokable" : "")."Class>\n"
|
||||
. "</rdf:RDF>";
|
||||
file_put_contents($fullName.'.xml', $content);
|
||||
$output->writeln("Written ".$fullName.".xml.");
|
||||
} else {
|
||||
// RDF configuration file already exists
|
||||
$output->writeln($fullName.".xml already exists.");
|
||||
}
|
||||
|
||||
if (!file_exists($fullName.'.php') || $input->getOption('force') !== false) {
|
||||
$useStatements = [];
|
||||
$classVariables = [];
|
||||
$constructor = '';
|
||||
|
||||
if ($input->getOption('autowire') != null) {
|
||||
$fullyQualifiedClassname = $input->getOption('autowire');
|
||||
$validator = new ClassValidator;
|
||||
if (!$validator->isWhitelisted($fullyQualifiedClassname))
|
||||
throw new PhpMAEException('Cannot autowire non-whitelisted type <'.$fullyQualifiedClassname.'>.');
|
||||
$useStatements[] = $fullyQualifiedClassname;
|
||||
$className = substr($fullyQualifiedClassname, strrpos($fullyQualifiedClassname, '\\') + 1);
|
||||
$variableName = strtolower($className[0]).substr($className, 1);
|
||||
$classVariables[] = $variableName;
|
||||
$constructor = " public function __construct(".$className." \$".$variableName.") {\n"
|
||||
. " \$this->".$variableName." = \$".$variableName.";\n"
|
||||
. " }\n\n";
|
||||
}
|
||||
|
||||
if (isset($implements)) {
|
||||
// Retrieve interface code
|
||||
$output->writeln("Fetching code for ".$interfaceData['coid']." ...");
|
||||
$parsedInterface = $this->getAndParseInterfaceCode($interfaceData['coid'], $interfaceData['filename']);
|
||||
// Add use statements
|
||||
foreach ($parsedInterface['use'] as $u)
|
||||
$useStatements[] = $u;
|
||||
}
|
||||
|
||||
// Create PHP source file
|
||||
$content = "<?php\n"
|
||||
. "\n";
|
||||
foreach ($useStatements as $u)
|
||||
$content .= "use ".$u.";\n";
|
||||
if (count($useStatements) > 0)
|
||||
$content .= "\n";
|
||||
|
||||
$content .= "/**\n"
|
||||
. " * Implementation for ".(string)$coid."\n"
|
||||
. (isset($implements) ? " * Using interface ".$interfaceData['coid']."\n" : "")
|
||||
. " */\n"
|
||||
. "class ".$name." ".(isset($implements) ? "implements ".$interfaceData['classname']." " : "")
|
||||
. "{\n"
|
||||
. "\n";
|
||||
foreach ($classVariables as $v)
|
||||
$content .= " private \$".$v.";\n";
|
||||
if (count($classVariables) > 0)
|
||||
$content .= "\n"
|
||||
. $constructor;
|
||||
|
||||
if (isset($implements)) {
|
||||
// Build PHP template with interface methods
|
||||
foreach ($parsedInterface['methods'] as $m) {
|
||||
$content .= ($m['comment'] != "" ? " /**\n ".$m['comment']."\n */\n" : "")
|
||||
. " public function ".$m['name']."(".$m['params'].") {\n"
|
||||
. " // TODO: Implement this\n"
|
||||
. " }\n\n";
|
||||
}
|
||||
} else {
|
||||
// Build PHP template for standard or HTTP-invokable class
|
||||
$content .= ($invokable
|
||||
? " public function __invoke(\$args) {\n // TODO: Add your code here\n }\n"
|
||||
: " // TODO: Add methods here ...\n")
|
||||
. "\n";
|
||||
}
|
||||
|
||||
$content .= "}";
|
||||
|
||||
file_put_contents($fullName.'.php', $content);
|
||||
$output->writeln("Written ".$fullName.".php.");
|
||||
} else {
|
||||
$output->writeln($fullName.".php already exists.");
|
||||
}
|
||||
|
||||
if ($input->getOption('confjob')) {
|
||||
$output->writeln("Calling cloudobjects ...");
|
||||
passthru("cloudobjects configuration-job:create ".$fullName.".xml");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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\PhpMAE\Commands;
|
||||
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use CloudObjects\SDK\COIDParser;
|
||||
use CloudObjects\PhpMAE\ClassValidator;
|
||||
|
||||
class ClassDeployCommand extends AbstractObjectCommand {
|
||||
|
||||
protected function configure() {
|
||||
$this->setName('class:deploy')
|
||||
->setDescription('Validates a class for the phpMAE and uploads it into CloudObjects. Updates the configuration if necessary.')
|
||||
->addArgument('coid', InputArgument::REQUIRED, 'The COID of the object.');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output) {
|
||||
$this->requireCloudObjectsCLI();
|
||||
|
||||
$this->parse($input->getArgument('coid'));
|
||||
$this->assertRDF();
|
||||
if (!in_array('coid://phpmae.dev/Class', $this->rdfTypes)
|
||||
&& !in_array('coid://phpmae.dev/HTTPInvokableClass', $this->rdfTypes))
|
||||
throw new \Exception("Object does not have a valid class type.");
|
||||
$this->assertPHPExists();
|
||||
|
||||
// Running validator
|
||||
$validator = new ClassValidator;
|
||||
$validator->validate(file_get_contents($this->fullName.'.php'),
|
||||
$this->coid, $this->getAdditionalTypes());
|
||||
$output->writeln("Validated successfully, calling cloudobjects ...");
|
||||
|
||||
passthru("cloudobjects attachment:put ".(string)$this->coid." ".$this->fullName.".php");
|
||||
|
||||
// Updates configuration if necessary
|
||||
if ($this->ensureFilenameInConfig($output, false))
|
||||
$this->createConfigurationJob($output);
|
||||
|
||||
// Print URL so developer can easily access it
|
||||
$output->writeln("");
|
||||
$visibility = isset($object['coid://cloudobjects.io/isVisibleTo'])
|
||||
? $object['coid://cloudobjects.io/isVisibleTo'][0]['value']
|
||||
: 'coid://cloudobjects.io/Vendor';
|
||||
$path = $this->coid->getHost().$this->coid->getPath();
|
||||
|
||||
switch ($visibility) {
|
||||
case "coid://cloudobjects.io/Private":
|
||||
$output->writeln("<info>Private URL for Class Execution:</info>");
|
||||
$output->writeln("➡️ http://YOUR_PHPMAE_INSTANCE/".$path);
|
||||
break;
|
||||
case "coid://cloudobjects.io/Public":
|
||||
$output->writeln("<info>Public URL for Class Execution:</info>");
|
||||
$output->writeln("➡️ https://phpmae.dev/".$path);
|
||||
break;
|
||||
case "coid://cloudobjects.io/Vendor":
|
||||
$output->writeln("<info>Authenticated URL for Class Execution:</info>");
|
||||
$output->writeln("➡️ https://".$this->coid->getHost().":SECRET@phpmae.dev/".$path);
|
||||
$output->writeln("");
|
||||
$output->writeln("To get value for SECRET:");
|
||||
$output->writeln("➡️ cloudobjects domain-providers:secret ".$this->coid->getHost()." phpmae.dev");
|
||||
break;
|
||||
}
|
||||
$output->writeln("");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
<?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\PhpMAE\Commands;
|
||||
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use ML\IRI\IRI;
|
||||
use Guzzle\Http\Exception\BadResponseException;
|
||||
use CloudObjects\SDK\COIDParser;
|
||||
use CloudObjects\PhpMAE\ClassValidator, CloudObjects\PhpMAE\TestEnvironmentManager;
|
||||
|
||||
class ClassTestEnvCommand extends AbstractObjectCommand {
|
||||
|
||||
private $container;
|
||||
|
||||
protected function getContainer() {
|
||||
if (!isset($this->container)) {
|
||||
$this->container = TestEnvironmentManager::getContainer();
|
||||
}
|
||||
|
||||
return $this->container;
|
||||
}
|
||||
|
||||
protected function configure() {
|
||||
$this->setName('class:testenv')
|
||||
->setDescription('Uploads a class into the current test environment.')
|
||||
->addArgument('coid', InputArgument::REQUIRED, 'The COID of the object.')
|
||||
->addOption('config', null, InputOption::VALUE_NONE, 'Upload the local configuration of the class to the test environment instead of retrieving it from CloudObjects.')
|
||||
->addOption('watch', null, InputOption::VALUE_NONE, 'Keep watching for changes of the file and reupload automatically.');
|
||||
}
|
||||
|
||||
private function upload(OutputInterface $output) {
|
||||
try {
|
||||
$this->validator->validate(file_get_contents($this->phpFileName),
|
||||
$this->coid, $this->getAdditionalTypes());
|
||||
$this->getContainer()->get('testenv.client')->put('/uploadTestenv?type=source&coid='.urlencode((string)$this->coid), [
|
||||
'body' => file_get_contents($this->phpFileName)
|
||||
]);
|
||||
$output->writeln('File uploaded successfully!');
|
||||
} catch (BadResponseException $e) {
|
||||
if ($e->getResponse()->getContentType()=='application/json') {
|
||||
$errorMessage = $e->getResponse()->json();
|
||||
$output->writeln('<error>'.$errorMessage['error_code'].':</error> '
|
||||
.$errorMessage['error_message']);
|
||||
} else
|
||||
$output->writeln('<error>'.get_class($e).'</error> '.(string)$e->getResponse());
|
||||
} catch (\Exception $e) {
|
||||
$output->writeln('<error>'.get_class($e).'</error> '.$e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output) {
|
||||
$this->requireCloudObjectsCLI();
|
||||
|
||||
$container = $this->getContainer();
|
||||
if (!$container->has('testenv.client'))
|
||||
throw new \Exception("No test environment configured.");
|
||||
|
||||
$this->parse($input->getArgument('coid'));
|
||||
$this->assertRDF();
|
||||
if (!in_array('coid://phpmae.dev/Class', $this->rdfTypes)
|
||||
&& !in_array('coid://phpmae.dev/HTTPInvokableClass', $this->rdfTypes))
|
||||
throw new \Exception("Object does not have a valid class type.");
|
||||
$this->assertPHPExists();
|
||||
|
||||
// Print URL so developer can easily access it
|
||||
$output->writeln("<info>Test Environment Base URL for Class Execution:</info>");
|
||||
$output->writeln("➡️ ".$container->get('testenv.url').$this->coid->getHost()
|
||||
.$this->coid->getPath());
|
||||
$output->writeln("");
|
||||
|
||||
if ($input->getOption('config')) {
|
||||
// Upload configuration before uploading implementation
|
||||
$this->ensureFilenameInConfig($output);
|
||||
$container->get('testenv.client')->put('/uploadTestenv?type=config&coid='.urlencode((string)$this->coid), [
|
||||
'body' => file_get_contents($this->xmlFileName)
|
||||
]);
|
||||
$output->writeln('Configuration uploaded successfully!');
|
||||
}
|
||||
|
||||
$this->validator = new ClassValidator;
|
||||
$this->upload($output);
|
||||
|
||||
if ($input->getOption('watch')) {
|
||||
$cmd = $this;
|
||||
$this->watchPHPFile($output, function() use ($cmd, $output) {
|
||||
$cmd->upload($output);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
<?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\PhpMAE\Commands;
|
||||
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use JsonRpc\Client;
|
||||
use CloudObjects\PhpMAE\TestEnvironmentManager;
|
||||
|
||||
class ClassTestEnvRPCCommand extends AbstractObjectCommand {
|
||||
|
||||
private $container;
|
||||
|
||||
protected function getContainer() {
|
||||
if (!isset($this->container)) {
|
||||
$this->container = TestEnvironmentManager::getContainer();
|
||||
}
|
||||
|
||||
return $this->container;
|
||||
}
|
||||
|
||||
protected function configure() {
|
||||
$this->setName('class:testenv-rpc')
|
||||
->setDescription('Executes a JSON-RPC method call on a class in the current test environment.')
|
||||
->addArgument('coid', InputArgument::REQUIRED, 'The COID of the object.')
|
||||
->addArgument('method', InputArgument::REQUIRED, 'The method name.')
|
||||
->addArgument('parameters', InputArgument::IS_ARRAY, 'The parameters for the method.');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output) {
|
||||
$this->requireCloudObjectsCLI();
|
||||
|
||||
$container = $this->getContainer();
|
||||
if (!$container->has('testenv.client'))
|
||||
throw new \Exception("No test environment configured.");
|
||||
|
||||
$this->parse($input->getArgument('coid'));
|
||||
$this->assertRDF();
|
||||
if (!in_array('coid://phpmae.dev/Class', $this->rdfTypes)
|
||||
&& !in_array('coid://phpmae.dev/HTTPInvokableClass', $this->rdfTypes))
|
||||
throw new \Exception("Object does not have a valid class type.");
|
||||
$this->assertPHPExists();
|
||||
|
||||
// Create JSON-RPC Client
|
||||
$client = new Client($container->get('testenv.url')
|
||||
.$this->coid->getHost().$this->coid->getPath());
|
||||
|
||||
// Check parameters and load files
|
||||
$parameters = [];
|
||||
foreach ($input->getArgument('parameters') as $p) {
|
||||
if ($p[0] == '@')
|
||||
$parameters[] = @file_get_contents(substr($p, 1));
|
||||
else
|
||||
$parameters[] = $p;
|
||||
}
|
||||
|
||||
// Make RPC Call
|
||||
if ($client->call($input->getArgument('method'), $parameters)
|
||||
&& isset($client->result))
|
||||
$output->writeln(is_string($client->result)
|
||||
? $client->result
|
||||
: json_encode($client->result, JSON_PRETTY_PRINT));
|
||||
else
|
||||
$output->writeln("<error>RPC has failed!</error>");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?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\PhpMAE\Commands;
|
||||
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use CloudObjects\PhpMAE\ClassValidator;
|
||||
|
||||
class ClassValidateCommand extends AbstractObjectCommand {
|
||||
|
||||
protected function configure() {
|
||||
$this->setName('class:validate')
|
||||
->setAliases([ 'validate', 'v' ])
|
||||
->setDescription('Validates a class for the phpMAE.')
|
||||
->addArgument('coid', InputArgument::REQUIRED, 'The COID of the object.')
|
||||
->addOption('watch', null, InputOption::VALUE_NONE, 'Keep watching for changes of the file and revalidate automatically.');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output) {
|
||||
$this->parse($input->getArgument('coid'));
|
||||
$this->assertRDF();
|
||||
if (!in_array('coid://phpmae.dev/Class', $this->rdfTypes)
|
||||
&& !in_array('coid://phpmae.dev/HTTPInvokableClass', $this->rdfTypes))
|
||||
throw new \Exception("Object does not have a valid class type.");
|
||||
$this->assertPHPExists();
|
||||
|
||||
// Running validator
|
||||
$validator = new ClassValidator;
|
||||
try {
|
||||
$validator->validate(file_get_contents($this->phpFileName),
|
||||
$this->coid, $this->getAdditionalTypes());
|
||||
$output->writeln("Validated successfully.");
|
||||
} catch (\Exception $e) {
|
||||
$output->writeln('<error>'.get_class($e).'</error> '.$e->getMessage());
|
||||
}
|
||||
|
||||
if ($input->getOption('watch')) {
|
||||
$cmd = $this;
|
||||
$this->watchPHPFile($output, function() use ($validator, $cmd, $output) {
|
||||
try {
|
||||
$validator->validate(file_get_contents($cmd->phpFileName),
|
||||
$this->coid, $this->getAdditionalTypes());
|
||||
$output->writeln("Validated successfully.");
|
||||
} catch (\Exception $e) {
|
||||
$output->writeln('<error>'.get_class($e).'</error> '.$e->getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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\PhpMAE\Commands;
|
||||
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use CloudObjects\SDK\COIDParser;
|
||||
|
||||
class DependenciesAddAttachmentCommand extends AbstractObjectCommand {
|
||||
|
||||
protected function configure() {
|
||||
$this->setName('dependencies:add-attachment')
|
||||
->setDescription('Adds an attachment to the specification of a class.')
|
||||
->addArgument('coid-target', InputArgument::REQUIRED, 'The COID of the target class.')
|
||||
->addArgument('filename', InputArgument::REQUIRED, 'The name of the file that should be attached.')
|
||||
->addOption('upload', null, InputOption::VALUE_NONE, 'Calls "cloudobjects" to actually upload the file to CloudObjects.');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output) {
|
||||
$this->requireCloudObjectsCLI();
|
||||
|
||||
$this->parse($input->getArgument('coid-target'));
|
||||
$this->assertRDF();
|
||||
$this->assertPHPExists();
|
||||
|
||||
$filename = $input->getArgument('filename');
|
||||
if (!file_exists($filename))
|
||||
throw new \Exception("File not found: ".$filename);
|
||||
|
||||
$fileUri = "file:///".basename($filename);
|
||||
|
||||
// Edit configuration
|
||||
$found = false;
|
||||
$object = $this->index[(string)$this->coid];
|
||||
if (isset($object['coid://phpmae.dev/usesAttachedFile'])) {
|
||||
foreach ($object['coid://phpmae.dev/usesAttachedFile'] as $o) {
|
||||
if ($o['type'] != 'uri') continue;
|
||||
if ($o['value'] == $fileUri) $found = true;
|
||||
}
|
||||
if (!$found)
|
||||
$object['coid://phpmae.dev/usesAttachedFile'][] = [ 'type' => 'uri', 'value' => $fileUri ];
|
||||
} else
|
||||
$object['coid://phpmae.dev/usesAttachedFile'] = [[ 'type' => 'uri', 'value' => $fileUri ]];
|
||||
|
||||
// Persist configuration
|
||||
if (!$found) {
|
||||
$this->index[(string)$this->coid] = $object;
|
||||
$this->updateRDFLocally($output);
|
||||
}
|
||||
|
||||
if ($input->getOption('upload') !== null) {
|
||||
$output->writeln("Calling cloudobjects ...");
|
||||
passthru("cloudobjects attachment:put ".(string)$this->coid." ".$filename);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
<?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\PhpMAE\Commands;
|
||||
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use CloudObjects\SDK\COIDParser;
|
||||
|
||||
class DependenciesAddClassCommand extends AbstractAddDependenciesCommand {
|
||||
|
||||
protected function configure() {
|
||||
$this->setName('dependencies:add-class')
|
||||
->setDescription('Adds a class dependency to the specification of a class.')
|
||||
->addArgument('coid-target', InputArgument::REQUIRED, 'The COID of the target class.')
|
||||
->addOption('key', null, InputArgument::OPTIONAL, 'The key for dependency injection. Randomly generated if not provided.', null)
|
||||
->addArgument('coid-class', InputArgument::REQUIRED, 'The COID of the dependency class.');
|
||||
|
||||
parent::configure();
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output) {
|
||||
$this->requireCloudObjectsCLI();
|
||||
|
||||
$this->parse($input->getArgument('coid-target'));
|
||||
$this->dependencyPrecheck();
|
||||
|
||||
// Check Class
|
||||
$coidClass = COIDParser::fromString($input->getArgument('coid-class'));
|
||||
if (COIDParser::getType($coidClass) != COIDParser::COID_VERSIONED
|
||||
&& COIDParser::getType($coidClass) != COIDParser::COID_UNVERSIONED)
|
||||
throw new \Exception("Invalid COID: ".$coid);
|
||||
|
||||
// Checking type
|
||||
$output->writeln("Fetching configuration for ".(string)$coidClass." ...");
|
||||
$this->getObjectAndAssertType((string)$coidClass, 'coid://phpmae.dev/Class');
|
||||
|
||||
// Add dependency
|
||||
$this->addDependency(
|
||||
$input->getOption('key') ? $input->getOption('key') : uniqid('class-'),
|
||||
'coid://phpmae.dev/ClassDependency',
|
||||
[
|
||||
'coid://phpmae.dev/hasClass' => [
|
||||
[ 'type' => 'uri', 'value' => (string)$coidClass ]
|
||||
]
|
||||
],
|
||||
$input, $output
|
||||
);
|
||||
|
||||
// Print documentation
|
||||
$className = COIDParser::getName($coidClass);
|
||||
$key = $input->getOption('key') ? $input->getOption('key')
|
||||
: strtolower($className[0]).substr($className, 1);
|
||||
$output->writeln("");
|
||||
$output->writeln("<info>Use your class dependency:</info>");
|
||||
$output->writeln("");
|
||||
$output->writeln("1) Add the class ".$className." to your constructor parameters.");
|
||||
$output->writeln("2) Assign the class to \$this->".$key.".");
|
||||
$output->writeln("");
|
||||
$output->writeln(" private \$".$key.";");
|
||||
$output->writeln("");
|
||||
$output->writeln(" public function __construct(".$className." \$".$key.") {");
|
||||
$output->writeln(" \$this->".$key." = \$".$key.";");
|
||||
$output->writeln(" }");
|
||||
$output->writeln("");
|
||||
$output->writeln("3) Use \$this->".$key." wherever required.");
|
||||
$output->writeln("");
|
||||
$output->writeln("You can find the documentation for the available class methods on this page:");
|
||||
$output->writeln("➡️ https://cloudobjects.io/".$coidClass->getHost().$coidClass->getPath());
|
||||
$output->writeln("");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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\PhpMAE\Commands;
|
||||
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use CloudObjects\SDK\COIDParser;
|
||||
|
||||
class DependenciesAddStaticTextCommand extends AbstractAddDependenciesCommand {
|
||||
|
||||
protected function configure() {
|
||||
$this->setName('dependencies:add-text')
|
||||
->setDescription('Adds a static text dependency to the specification of a class.')
|
||||
->addArgument('coid-target', InputArgument::REQUIRED, 'The COID of the target class.')
|
||||
->addArgument('key', InputArgument::REQUIRED, 'The key for dependency injection.')
|
||||
->addArgument('value', InputArgument::REQUIRED, 'The text value.');
|
||||
|
||||
parent::configure();
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output) {
|
||||
$this->requireCloudObjectsCLI();
|
||||
|
||||
$this->parse($input->getArgument('coid-target'));
|
||||
$this->assertRDF();
|
||||
$this->dependencyPrecheck();
|
||||
|
||||
// Add dependency
|
||||
$this->addDependency(
|
||||
$input->getArgument('key'),
|
||||
'coid://phpmae.dev/StaticTextDependency',
|
||||
[
|
||||
'coid://phpmae.dev/hasValue' => [
|
||||
[ 'type' => 'literal', 'value' => $input->getArgument('value') ]
|
||||
]
|
||||
],
|
||||
$input, $output
|
||||
);
|
||||
|
||||
// Print documentation
|
||||
$key = $input->getArgument('key');
|
||||
$output->writeln("");
|
||||
$output->writeln("<info>Use your static text dependency:</info>");
|
||||
$output->writeln("");
|
||||
$output->writeln("1) Make sure you have access to the dependency injection container by adding the container to your class constructor.");
|
||||
$output->writeln("2) Request the value string from the container using the key \"".$key."\".");
|
||||
$output->writeln("");
|
||||
$output->writeln(" private \$".$key.";");
|
||||
$output->writeln("");
|
||||
$output->writeln(" public function __construct(\Psr\Container\ContainerInterface \$container) {");
|
||||
$output->writeln(" \$this->".$key." = \$container->get('".$key."');");
|
||||
$output->writeln(" }");
|
||||
$output->writeln("");
|
||||
$output->writeln("3) Use \$this->".$key." wherever required.");
|
||||
$output->writeln("");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
<?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\PhpMAE\Commands;
|
||||
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use CloudObjects\SDK\COIDParser;
|
||||
|
||||
class DependenciesAddTemplateCommand extends AbstractAddDependenciesCommand {
|
||||
|
||||
protected function configure() {
|
||||
$this->setName('dependencies:add-template')
|
||||
->setDescription('Adds a Twig template dependency to the specification of a class.')
|
||||
->addArgument('coid-target', InputArgument::REQUIRED, 'The COID of the target class.')
|
||||
->addArgument('key', InputArgument::REQUIRED, 'The key for dependency injection.')
|
||||
->addArgument('filename', InputArgument::REQUIRED, 'The filename for the Twig template.')
|
||||
->addOption('upload', null, InputOption::VALUE_NONE, 'Calls "cloudobjects" to actually upload the template file to CloudObjects.');
|
||||
|
||||
parent::configure();
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output) {
|
||||
$this->requireCloudObjectsCLI();
|
||||
|
||||
$this->parse($input->getArgument('coid-target'));
|
||||
$this->dependencyPrecheck();
|
||||
|
||||
// Check filename
|
||||
$filename = $input->getArgument('filename');
|
||||
if (!file_exists($filename))
|
||||
throw new \Exception("File not found: ".$filename);
|
||||
|
||||
// Upload file if requested
|
||||
if ($input->getOption('upload')) {
|
||||
$output->writeln("Calling cloudobjects for file upload ...");
|
||||
passthru("cloudobjects attachment:put ".(string)$this->coid." ".$filename);
|
||||
}
|
||||
|
||||
// Add dependency
|
||||
$this->addDependency(
|
||||
$input->getArgument('key'),
|
||||
'coid://phpmae.dev/TwigTemplateDependency',
|
||||
[
|
||||
'coid://phpmae.dev/usesAttachedTwigFile' => [
|
||||
[ 'type' => 'uri', 'value' => "file:///".basename($filename) ]
|
||||
]
|
||||
],
|
||||
$input, $output
|
||||
);
|
||||
|
||||
// Print documentation
|
||||
$key = $input->getArgument('key');
|
||||
$output->writeln("");
|
||||
$output->writeln("<info>Use your Twig template dependency:</info>");
|
||||
$output->writeln("");
|
||||
$output->writeln("1) Make sure you have access to the dependency injection container by adding the container to your class constructor.");
|
||||
$output->writeln("2) Request the template from the container using the key \"".$key."\".");
|
||||
$output->writeln("");
|
||||
$output->writeln(" private \$".$key."Template;");
|
||||
$output->writeln("");
|
||||
$output->writeln(" public function __construct(\Psr\Container\ContainerInterface \$container) {");
|
||||
$output->writeln(" \$this->".$key."Template = \$container->get('".$key."');");
|
||||
$output->writeln(" }");
|
||||
$output->writeln("");
|
||||
$output->writeln("3) Render your template by calling \$this->".$key."Template->render(\$context).");
|
||||
$output->writeln("");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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\PhpMAE\Commands;
|
||||
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use CloudObjects\SDK\COIDParser;
|
||||
|
||||
class DependenciesAddWebAPICommand extends AbstractAddDependenciesCommand {
|
||||
|
||||
protected function configure() {
|
||||
$this->setName('dependencies:add-webapi')
|
||||
->setDescription('Adds a dependency to the specification of a class.')
|
||||
->addArgument('coid-target', InputArgument::REQUIRED, 'The COID of the target class.')
|
||||
->addArgument('key', InputArgument::REQUIRED, 'The key for dependency injection.')
|
||||
->addArgument('coid-webapi', InputArgument::REQUIRED, 'The COID of the Web API.');
|
||||
|
||||
parent::configure();
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output) {
|
||||
$this->requireCloudObjectsCLI();
|
||||
|
||||
$this->parse($input->getArgument('coid-target'));
|
||||
$this->dependencyPrecheck();
|
||||
|
||||
// Check Web API
|
||||
$coidWebAPI = COIDParser::fromString($input->getArgument('coid-webapi'));
|
||||
if (COIDParser::getType($coidWebAPI) != COIDParser::COID_VERSIONED
|
||||
&& COIDParser::getType($coidWebAPI) != COIDParser::COID_UNVERSIONED)
|
||||
throw new \Exception("Invalid COID: ".$coid);
|
||||
|
||||
// Checking type
|
||||
$output->writeln("Fetching configuration for ".(string)$coidWebAPI." ...");
|
||||
$this->getObjectAndAssertType((string)$coidWebAPI, 'coid://webapis.co-n.net/HTTPEndpoint');
|
||||
|
||||
// Add dependency
|
||||
$this->addDependency(
|
||||
$input->getArgument('key'),
|
||||
'coid://phpmae.dev/WebAPIDependency',
|
||||
[
|
||||
'coid://phpmae.dev/hasAPI' => [
|
||||
[ 'type' => 'uri', 'value' => (string)$coidWebAPI ]
|
||||
]
|
||||
],
|
||||
$input, $output
|
||||
);
|
||||
|
||||
// Print documentation
|
||||
$key = $input->getArgument('key');
|
||||
$output->writeln("");
|
||||
$output->writeln("<info>Use your WebAPI dependency:</info>");
|
||||
$output->writeln("");
|
||||
$output->writeln("1) Make sure you have access to the dependency injection container by adding the container to your class constructor.");
|
||||
$output->writeln("2) Request an API client from the container using the key \"".$key."\".");
|
||||
$output->writeln("");
|
||||
$output->writeln(" private \$".$key."Api;");
|
||||
$output->writeln("");
|
||||
$output->writeln(" public function __construct(\Psr\Container\ContainerInterface \$container) {");
|
||||
$output->writeln(" \$this->".$key."Api = \$container->get('".$key."');");
|
||||
$output->writeln(" }");
|
||||
$output->writeln("");
|
||||
$output->writeln("3) Make API requests in your class by calling methods on \$this->".$key."Api.");
|
||||
$output->writeln("");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
<?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\PhpMAE\Commands;
|
||||
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use CloudObjects\SDK\COIDParser;
|
||||
use CloudObjects\PhpMAE\CredentialManager, CloudObjects\PhpMAE\ClassValidator;
|
||||
use CloudObjects\PhpMAE\Exceptions\PhpMAEException;
|
||||
|
||||
class InterfaceCreateCommand extends Command {
|
||||
|
||||
protected function configure() {
|
||||
$this->setName('interface:create')
|
||||
->setDescription('Create a new interface for the phpMAE.')
|
||||
->addArgument('coid', InputArgument::REQUIRED, 'The COID of the object.')
|
||||
->addOption('force', 'f', InputOption::VALUE_OPTIONAL, 'Forces new object creation and replaces existing files.', false)
|
||||
->addOption('confjob', null, InputOption::VALUE_OPTIONAL, 'Calls "cloudobjects" to create a configuration job for the new interface.', false);
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output) {
|
||||
if (!CredentialManager::isConfigured())
|
||||
throw new \Exception("The 'cloudobjects' CLI tool must be installed and authorized.");
|
||||
|
||||
$coid = COIDParser::fromString($input->getArgument('coid'));
|
||||
|
||||
if (COIDParser::getType($coid)!=COIDParser::COID_VERSIONED
|
||||
&& COIDParser::getType($coid)!=COIDParser::COID_UNVERSIONED)
|
||||
throw new \Exception("Invalid COID: ".(string)$coid);
|
||||
|
||||
$name = COIDParser::getName($coid);
|
||||
$version = COIDParser::getVersion($coid);
|
||||
$fullName = isset($version) ? $name.".".$version : $name;
|
||||
|
||||
if (!file_exists($fullName.'.xml') || $input->getOption('force') !== false) {
|
||||
// Create RDF configuration file
|
||||
$content = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
|
||||
. "<rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\"\n"
|
||||
. " xmlns:co=\"coid://cloudobjects.io/\"\n"
|
||||
. " xmlns:phpmae=\"coid://phpmae.dev/\">\n"
|
||||
. "\n"
|
||||
. " <phpmae:Interface rdf:about=\"".(string)$coid."\">\n"
|
||||
. " <co:isVisibleTo rdf:resource=\"coid://cloudobjects.io/Vendor\" />\n"
|
||||
. " </phpmae:Interface>\n"
|
||||
. "</rdf:RDF>";
|
||||
file_put_contents($fullName.'.xml', $content);
|
||||
$output->writeln("Written ".$fullName.".xml.");
|
||||
} else {
|
||||
// RDF configuration file already exists
|
||||
$output->writeln($fullName.".xml already exists.");
|
||||
}
|
||||
|
||||
if (!file_exists($fullName.'.php') || $input->getOption('force') !== false) {
|
||||
// Create PHP source file
|
||||
$content = "<?php\n"
|
||||
. "\n"
|
||||
. "/**\n"
|
||||
. " * Definition for ".(string)$coid."\n"
|
||||
. " */\n"
|
||||
. "interface ".$name." {\n"
|
||||
. "\n"
|
||||
. " // Add methods here ...\n"
|
||||
. "\n"
|
||||
. "}";
|
||||
file_put_contents($fullName.'.php', $content);
|
||||
$output->writeln("Written ".$fullName.".php.");
|
||||
} else {
|
||||
$output->writeln($fullName.".php already exists.");
|
||||
}
|
||||
|
||||
if ($input->getOption('confjob') !== false) {
|
||||
$output->writeln("Calling cloudobjects ...");
|
||||
passthru("cloudobjects configuration-job:create ".$fullName.".xml");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?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\PhpMAE\Commands;
|
||||
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use CloudObjects\SDK\COIDParser;
|
||||
use CloudObjects\PhpMAE\ClassValidator;
|
||||
|
||||
class InterfaceDeployCommand extends AbstractObjectCommand {
|
||||
|
||||
protected function configure() {
|
||||
$this->setName('interface:deploy')
|
||||
->setDescription('Validates an interface for the phpMAE and uploads it into CloudObjects. Updates the configuration if necessary.')
|
||||
->addArgument('coid', InputArgument::REQUIRED, 'The COID of the object.');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output) {
|
||||
$this->parse($input->getArgument('coid'));
|
||||
$this->assertRDF();
|
||||
if (!in_array('coid://phpmae.dev/Interface', $this->rdfTypes))
|
||||
throw new \Exception("Object does not have athevalid interface type.");
|
||||
$this->assertPHPExists();
|
||||
|
||||
// Running validator
|
||||
$validator = new ClassValidator();
|
||||
$validator->validateInterface(file_get_contents($this->fullName.'.php'), $this->coid);
|
||||
$output->writeln("Validated successfully, calling cloudobjects ...");
|
||||
|
||||
passthru("cloudobjects attachment:put ".(string)$this->coid." ".$this->fullName.".php");
|
||||
|
||||
// Updates configuration if necessary
|
||||
if ($this->ensureFilenameInConfig($output, true)) {
|
||||
$this->createConfigurationJob($output);
|
||||
}
|
||||
|
||||
// Print URL so developer can easily access it
|
||||
$output->writeln("");
|
||||
$visibility = isset($object['coid://cloudobjects.io/isVisibleTo'])
|
||||
? $object['coid://cloudobjects.io/isVisibleTo'][0]['value']
|
||||
: 'coid://cloudobjects.io/Vendor';
|
||||
$path = $this->coid->getHost().$this->coid->getPath();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?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\PhpMAE\Commands;
|
||||
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use CloudObjects\PhpMAE\ClassValidator;
|
||||
|
||||
class InterfaceValidateCommand extends AbstractObjectCommand {
|
||||
|
||||
protected function configure() {
|
||||
$this->setName('interface:validate')
|
||||
->setDescription('Validates an interface for the phpMAE.')
|
||||
->addArgument('coid', InputArgument::REQUIRED, 'The COID of the object.')
|
||||
->addOption('watch', null, InputOption::VALUE_OPTIONAL, 'Keep watching for changes of the file and revalidate automatically.', null);
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output) {
|
||||
$this->parse($input->getArgument('coid'));
|
||||
$this->assertRDF();
|
||||
if (!in_array('coid://phpmae.dev/Interface', $this->rdfTypes))
|
||||
throw new \Exception("Object does not have the valid interface type.");
|
||||
$this->assertPHPExists();
|
||||
|
||||
// Running validator
|
||||
$validator = new ClassValidator;
|
||||
try {
|
||||
$validator->validateInterface(file_get_contents($this->phpFileName), $this->coid);
|
||||
$output->writeln("Validated successfully.");
|
||||
} catch (\Exception $e) {
|
||||
$output->writeln('<error>'.get_class($e).'</error> '.$e->getMessage());
|
||||
}
|
||||
|
||||
if ($input->getOption('watch') !== null) {
|
||||
$cmd = $this;
|
||||
$this->watchPHPFile($output, function() use ($validator, $cmd, $output) {
|
||||
try {
|
||||
$validator->validateInterface(file_get_contents($cmd->phpFileName), $this->coid);
|
||||
$output->writeln("Validated successfully.");
|
||||
} catch (\Exception $e) {
|
||||
$output->writeln('<error>'.get_class($e).'</error> '.$e->getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?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\PhpMAE\Commands;
|
||||
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use CloudObjects\PhpMAE\TestEnvironmentManager;
|
||||
use CloudObjects\PhpMAE\CredentialManager;
|
||||
|
||||
class TestEnvironmentStartCommand extends Command {
|
||||
|
||||
protected function configure() {
|
||||
$this->setName('testenv:start')
|
||||
->setDescription('Starts a local test environment.')
|
||||
->addOption('port', null, InputOption::VALUE_OPTIONAL, 'Use this port for test environment.', 9000)
|
||||
->addOption('host', null, InputOption::VALUE_OPTIONAL, 'Bind test environment to this host.', '127.0.0.1');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output) {
|
||||
if (!CredentialManager::isConfigured())
|
||||
throw new \Exception("The 'cloudobjects' CLI tool must be installed and authorized.");
|
||||
|
||||
$port = $input->getOption('port');
|
||||
$host = $input->getOption('host');
|
||||
|
||||
TestEnvironmentManager::setTestEnvironment('http://localhost:'.$port.'/');
|
||||
$output->writeln('Local webserver started on port '.$port.'.');
|
||||
$phar = \Phar::running();
|
||||
if (!$phar || $phar == "") {
|
||||
// Run extracted version
|
||||
$dir = realpath(__DIR__."/../../web/");
|
||||
passthru('cd '.$dir.'; php -S '.$host.':'.$port.' index.php');
|
||||
} else {
|
||||
// Run from phar
|
||||
if (strpos($phar, '.phar') !== false) {
|
||||
// Run directly
|
||||
passthru('php -S '.$host.':'.$port.' '.$phar.'/web/index.php');
|
||||
} else {
|
||||
// Create copy with .phar extension because PHP might not recognize the file otherwise
|
||||
$pharCp = sys_get_temp_dir().DIRECTORY_SEPARATOR.'phpmae.phar';
|
||||
copy(substr($phar, 7), $pharCp);
|
||||
passthru('php -S '.$host.':'.$port.' phar://'.$pharCp.'/web/index.php');
|
||||
die("THRU!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<?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\PhpMAE;
|
||||
|
||||
use ML\IRI\IRI;
|
||||
use CloudObjects\SDK\ObjectRetriever, CloudObjects\SDK\COIDParser,
|
||||
CloudObjects\SDK\NodeReader;
|
||||
|
||||
/**
|
||||
* The Config Loader helps phpMAE classes to load configuration
|
||||
* data from different CloudObjects objects.
|
||||
*/
|
||||
class ConfigLoader {
|
||||
|
||||
private $validSources;
|
||||
private $retriever;
|
||||
private $reader;
|
||||
|
||||
public function __construct(array $validSources, ObjectRetriever $retriever) {
|
||||
foreach ($validSources as $v) {
|
||||
if (!is_a($v, IRI::class))
|
||||
throw new \Exception("Invalid sources!");
|
||||
}
|
||||
$this->validSources = $validSources;
|
||||
$this->retriever = $retriever;
|
||||
$this->reader = new NodeReader([]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value for a key, trying the sources in the order given by priorities.
|
||||
*/
|
||||
public function get($key, $priorities, $default = null) {
|
||||
foreach ($priorities as $p) {
|
||||
if (isset($this->validSources[$p])) {
|
||||
$object = $this->retriever->getObject($this->validSources[$p]);
|
||||
if (!isset($object))
|
||||
continue;
|
||||
} else
|
||||
if (substr($p, -10) == '.namespace' && isset($this->validSources[substr($p, 0, -10)])) {
|
||||
$object = $this->retriever->getObject(
|
||||
COIDParser::getNamespaceCOID($this->validSources[substr($p, 0, -10)]));
|
||||
if (!isset($object))
|
||||
continue;
|
||||
}
|
||||
if (!isset($object))
|
||||
continue;
|
||||
$value = $this->reader->getFirstValueString($object, $key);
|
||||
if (isset($value))
|
||||
return $value;
|
||||
}
|
||||
|
||||
return $default; // not found
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
<?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\PhpMAE;
|
||||
|
||||
use DI;
|
||||
use Monolog\Logger;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use CloudObjects\SDK\ObjectRetriever;
|
||||
|
||||
class Configurator {
|
||||
|
||||
public static function getContainer(array $config) {
|
||||
// Object Retriever Definition
|
||||
$objectRetrieverDefinition = DI\autowire();
|
||||
$orConstructorParameters = [
|
||||
'cache_provider' => 'file',
|
||||
'cache_provider.file.directory' => $config['cache_dir'] . '/config',
|
||||
'static_config_path' => $config['uploads_dir'] . '/config',
|
||||
'logger' => DI\get(Logger::class)
|
||||
];
|
||||
|
||||
if (!isset($config['co.auth_ns']) && CredentialManager::isConfigured()) {
|
||||
// Access Object API via Account Gateway using local developer account
|
||||
$objectRetrieverDefinition->constructor($orConstructorParameters)
|
||||
->method('setClient', function() {
|
||||
return CredentialManager::getAccountContext()->getClient();
|
||||
}, '/ws/');
|
||||
} else {
|
||||
// Access Object API with specified namespace credentials
|
||||
$objectRetrieverDefinition->constructor(array_merge([
|
||||
'auth_ns' => $config['co.auth_ns'],
|
||||
'auth_secret' => $config['co.auth_secret'],
|
||||
], $orConstructorParameters));
|
||||
}
|
||||
|
||||
// Create Dependency Injection Container
|
||||
$builder = new DI\ContainerBuilder();
|
||||
$builder->addDefinitions($config);
|
||||
$builder->addDefinitions([
|
||||
Logger::class => function(ContainerInterface $config) {
|
||||
$logger = new Logger('CO');
|
||||
switch ($config->get('log.target')) {
|
||||
case "none":
|
||||
$logger->pushHandler(new \Monolog\Handler\NullHandler);
|
||||
break;
|
||||
case "errorlog":
|
||||
$logger->pushHandler(new \Monolog\Handler\ErrorLogHandler(0, $config->get('log.level')));
|
||||
break;
|
||||
default:
|
||||
throw new \Exception("Invalid log.target!");
|
||||
}
|
||||
return $logger;
|
||||
},
|
||||
ObjectRetriever::class => $objectRetrieverDefinition,
|
||||
ObjectRetrieverPool::class => DI\autowire()
|
||||
->constructorParameter('baseHostname', @$config['co.auth_ns'])
|
||||
->constructorParameter('options', [
|
||||
'cache_provider' => 'file',
|
||||
'cache_provider.file.directory' => $config['cache_dir'] . '/config',
|
||||
'static_config_path' => $config['uploads_dir'] . '/config',
|
||||
'logger' => DI\get(Logger::class)
|
||||
])
|
||||
]);
|
||||
|
||||
$builder->enableCompilation($config['cache_dir']);
|
||||
return $builder->build();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?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\PhpMAE;
|
||||
|
||||
use Cilex\Application;
|
||||
use ML\IRI\IRI;
|
||||
use CloudObjects\SDK\AccountGateway\AccountContext;
|
||||
|
||||
class CredentialManager {
|
||||
|
||||
private static $context;
|
||||
|
||||
private static function getFilename() {
|
||||
return getenv('HOME').DIRECTORY_SEPARATOR.'.cloudobjects';
|
||||
}
|
||||
|
||||
public static function isConfigured() {
|
||||
return file_exists(self::getFilename());
|
||||
}
|
||||
|
||||
public static function getAccountContext() {
|
||||
if (!self::$context) {
|
||||
if (file_exists(self::getFilename())) {
|
||||
$data = json_decode(file_get_contents(self::getFilename()), true);
|
||||
if (isset($data['aauid']) && isset($data['access_token']))
|
||||
self::$context = new AccountContext(new IRI('aauid:'.$data['aauid']), $data['access_token']);
|
||||
}
|
||||
}
|
||||
|
||||
return self::$context;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?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\PhpMAE;
|
||||
|
||||
use PHPSandbox\PHPSandbox;
|
||||
use PhpParser\Node, PhpParser\NodeVisitorAbstract;
|
||||
|
||||
/**
|
||||
* Performs additional sandbox validation steps which are specific to phpMAE and
|
||||
* not implemented in a compatible way in the PHPSandbox library.
|
||||
*/
|
||||
class CustomValidationVisitor extends NodeVisitorAbstract {
|
||||
|
||||
protected $sandbox;
|
||||
|
||||
public function __construct(PHPSandbox $sandbox) {
|
||||
$this->sandbox = $sandbox;
|
||||
}
|
||||
|
||||
public function leaveNode(Node $node) {
|
||||
if ($node instanceof Node\Expr\FuncCall) {
|
||||
if ($node->name instanceof Node\Expr\Variable) {
|
||||
$this->sandbox->validationError("Sandboxed code attempted to call a variable method!", 0, $node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,316 @@
|
||||
<?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\PhpMAE\DI;
|
||||
|
||||
use ML\JsonLD\Node;
|
||||
use ML\IRI\IRI;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Psr\SimpleCache\CacheInterface;
|
||||
use Psr\Http\Message\RequestInterface, Psr\Http\Message\ResponseInterface;
|
||||
use DI\ContainerBuilder;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Cache\FilesystemCache;
|
||||
use Slim\Http\Response;
|
||||
use Symfony\Component\Mailer\MailerInterface,
|
||||
Symfony\Component\Mailer\Transport, Symfony\Component\Mailer\Mailer;
|
||||
use CloudObjects\SDK\ObjectRetriever, CloudObjects\SDK\NodeReader, CloudObjects\SDK\COIDParser;
|
||||
use CloudObjects\SDK\AccountGateway\AAUIDParser,
|
||||
CloudObjects\SDK\AccountGateway\AccountContext;
|
||||
use CloudObjects\SDK\WebAPI\APIClientFactory;
|
||||
use CloudObjects\SDK\Common\CryptoHelper;
|
||||
use CloudObjects\PhpMAE\ObjectRetrieverPool, CloudObjects\PhpMAE\ClassRepository,
|
||||
CloudObjects\PhpMAE\ErrorHandler, CloudObjects\PhpMAE\Engine,
|
||||
CloudObjects\PhpMAE\ConfigLoader, CloudObjects\PhpMAE\TwigTemplate,
|
||||
CloudObjects\PhpMAE\TwigTemplateFactory, CloudObjects\PhpMAE\InteractiveRunController;
|
||||
use CloudObjects\PhpMAE\Exceptions\PhpMAEException;
|
||||
use CloudObjects\PhpMAE\Injectables\CacheBridge;
|
||||
|
||||
/**
|
||||
* The DependencyInjector returns all the dependencies specified for a PHP class.
|
||||
*/
|
||||
class DependencyInjector {
|
||||
|
||||
private $retrieverPool;
|
||||
private $classRepository;
|
||||
private $container;
|
||||
|
||||
public function __construct(ObjectRetrieverPool $retrieverPool, ClassRepository $classRepository,
|
||||
ContainerInterface $container) {
|
||||
|
||||
$this->retrieverPool = $retrieverPool;
|
||||
$this->classRepository = $classRepository;
|
||||
$this->container = $container;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the request comes from a CloudObjects Account Gateway and,
|
||||
* if so, configures an AccountContext for the class to use.
|
||||
*/
|
||||
private function configureAccountGateway(RequestInterface $request) {
|
||||
$definitions = [];
|
||||
|
||||
if ($request->hasHeader('C-AAUID')
|
||||
&& $request->hasHeader('C-Access-Token')) {
|
||||
|
||||
$definitions[AccountContext::class] = function() use ($request) {
|
||||
$context = AccountContext::fromPsrRequest($request);
|
||||
|
||||
if ($this->container->get('agws.data_cache') == 'file') {
|
||||
// Enable filesystem cache for account data
|
||||
$cache = new FilesystemCache($this->container->get('cache_dir') . '/acct');
|
||||
$context->getDataLoader()->setCache($cache);
|
||||
}
|
||||
|
||||
return $context;
|
||||
};
|
||||
}
|
||||
|
||||
return $definitions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all dependencies for injection.
|
||||
*
|
||||
* @param Node $object The object representing the PHP class.
|
||||
* @param array $additionalDefinitions Additional definitions to add to the container
|
||||
*/
|
||||
public function getDependencies(Node $object, array $additionalDefinitions = []) {
|
||||
$reader = new NodeReader([
|
||||
'prefixes' => [ 'phpmae' => 'coid://phpmae.dev/' ]
|
||||
]);
|
||||
|
||||
$dependencies = $reader->getAllValuesNode($object, 'phpmae:hasDependency');
|
||||
|
||||
$objectCoid = new IRI($object->getId());
|
||||
$namespaceCoid = COIDParser::getNamespaceCOID($objectCoid);
|
||||
if (substr($namespaceCoid->getHost(), -7) == '.phpmae') {
|
||||
$originalHostname = $this->container->get(InteractiveRunController::class)
|
||||
->getOriginalHostname($namespaceCoid->getHost());
|
||||
$realNamespaceCoid = isset($originalHostname) ? new IRI('coid://'.$originalHostname) : $namespaceCoid;
|
||||
} else
|
||||
$realNamespaceCoid = $namespaceCoid;
|
||||
|
||||
$definitions = [
|
||||
'cookies' => \DI\create(ArrayCollection::class),
|
||||
ObjectRetriever::class => function() use ($realNamespaceCoid) {
|
||||
// Get or create an object retriever with the identity of the namespace of this object
|
||||
return $this->retrieverPool->getObjectRetriever($realNamespaceCoid->getHost());
|
||||
},
|
||||
APIClientFactory::class => function(ContainerInterface $c) use ($realNamespaceCoid) {
|
||||
// Get an API client factory
|
||||
return new APIClientFactory($c->get(ObjectRetriever::class), $realNamespaceCoid);
|
||||
},
|
||||
DynamicLoader::class => function() {
|
||||
return new DynamicLoader($this->retrieverPool->getBaseObjectRetriever(),
|
||||
$this->classRepository);
|
||||
},
|
||||
ConfigLoader::class => function(ContainerInterface $c) use ($objectCoid, $additionalDefinitions) {
|
||||
// Get a ConfigLoader that allows the class to read the configuration
|
||||
// of various objects, including itself and additional definitions
|
||||
$configDefinitions = [
|
||||
'self' => $objectCoid
|
||||
];
|
||||
foreach ($additionalDefinitions as $key => $value)
|
||||
if (is_a($value, IRI::class))
|
||||
$configDefinitions[$key] = $value;
|
||||
|
||||
if (isset($additionalDefinitions[RequestInterface::class])) {
|
||||
$request = $c->get(RequestInterface::class);
|
||||
if ($request->hasHeader('C-Accessor'))
|
||||
$configDefinitions['accessor'] =
|
||||
new IRI($request->getHeaderLine('C-Accessor'));
|
||||
}
|
||||
|
||||
return new ConfigLoader($configDefinitions, $c->get(ObjectRetriever::class));
|
||||
},
|
||||
TwigTemplateFactory::class => function() use ($object) {
|
||||
return new TwigTemplateFactory($this->classRepository
|
||||
->getCustomFilesCachePath($object));
|
||||
},
|
||||
CryptoHelper::class => function(ContainerInterface $c) use ($realNamespaceCoid) {
|
||||
return new CryptoHelper($c->get(ObjectRetriever::class), $realNamespaceCoid);
|
||||
},
|
||||
CacheInterface::class => function(ContainerInterface $c) use ($namespaceCoid) {
|
||||
// Enable filesystem cache for custom data
|
||||
return new CacheBridge(
|
||||
new FilesystemCache($this->container->get('cache_dir') . '/custom'),
|
||||
$namespaceCoid->getHost()
|
||||
);
|
||||
},
|
||||
ResponseInterface::class => \DI\autowire(Response::class),
|
||||
MailerInterface::class => function() use ($object, $realNamespaceCoid, $reader) {
|
||||
// Find mailer DSN
|
||||
$mailerDsn = $reader->getFirstValueString($object, 'phpmae:hasClassMailerDSN');
|
||||
|
||||
if (!isset($mailerDsn)) {
|
||||
// Get DSN from namespace
|
||||
$namespaceObject = $this->retrieverPool->getBaseObjectRetriever()
|
||||
->getObject($realNamespaceCoid);
|
||||
$mailerDsn = $reader->getFirstValueString($namespaceObject, 'phpmae:hasGlobalMailerDSN');
|
||||
}
|
||||
|
||||
if (!isset($mailerDsn))
|
||||
throw new PhpMAEException("Requires class or global mailer DSN.");
|
||||
|
||||
if (substr($mailerDsn, 0, 5) != 'smtp:')
|
||||
throw new PhpMAEException("Mailer DSN must start with smtp:.");
|
||||
|
||||
$transport = Transport::fromDsn($mailerDsn);
|
||||
return new Mailer($transport);
|
||||
}
|
||||
];
|
||||
|
||||
$definitions = array_merge($definitions, $additionalDefinitions);
|
||||
|
||||
if (isset($definitions[RequestInterface::class])) {
|
||||
$definitions = array_merge($definitions, $this->configureAccountGateway($definitions[RequestInterface::class]));
|
||||
}
|
||||
|
||||
foreach ($dependencies as $d) {
|
||||
$keyedDependency = null;
|
||||
|
||||
if (!$d->getProperty('coid://phpmae.dev/hasKey'))
|
||||
throw new PhpMAEException("<".$object->getId()."> has an invalid dependency: no key!");
|
||||
|
||||
if ($reader->hasType($d, 'phpmae:StaticTextDependency')) {
|
||||
// Static Text Dependency
|
||||
$value = $reader->getFirstValueString($d, 'phpmae:hasValue');
|
||||
if (!isset($value))
|
||||
throw new PhpMAEException("<".$object->getId()."> has an invalid dependency: StaticTextDependency without value!");
|
||||
|
||||
$keyedDependency = $value;
|
||||
} else
|
||||
if ($reader->hasType($d, 'phpmae:WebAPIDependency')) {
|
||||
// Web API Dependency
|
||||
$apiCoid = $reader->getFirstValueString($d, 'phpmae:hasAPI');
|
||||
if (isset($apiCoid)) {
|
||||
// API is available to phpMAE, set up dependency
|
||||
$keyedDependency = function(ContainerInterface $c) use ($apiCoid, $object) {
|
||||
return $c->get(APIClientFactory::class)
|
||||
->getClientWithCOID(new IRI($apiCoid), true);
|
||||
};
|
||||
} else {
|
||||
// API might be a private API that wasn't shared with phpMAE,
|
||||
// we accept the dependency for now and resolve it with a
|
||||
// namespace-scoped retriever later
|
||||
$lookupKey = $reader->getFirstValueString($d, 'phpmae:hasKey');
|
||||
$keyedDependency = function(ContainerInterface $c) use ($objectCoid,
|
||||
$reader, $lookupKey, $realNamespaceCoid) {
|
||||
|
||||
$object = $c->get(ObjectRetriever::class)->get($objectCoid);
|
||||
$dependencies = $reader->getAllValuesNode($object, 'phpmae:hasDependency');
|
||||
foreach ($dependencies as $d) {
|
||||
if ($reader->getFirstValueString($d, 'phpmae:hasKey') == $lookupKey) {
|
||||
// Found dependency
|
||||
$apiCoid = $reader->getFirstValueString($d, 'phpmae:hasAPI');
|
||||
if (!isset($apiCoid))
|
||||
throw new PhpMAEException("<".$object->getId()."> has an invalid dependency: WebAPIDependency without API!");
|
||||
|
||||
return $c->get(APIClientFactory::class)
|
||||
->getClientWithCOID(new IRI($apiCoid), true);
|
||||
}
|
||||
}
|
||||
|
||||
throw new PhpMAEException("<".$object->getId()."> had problems during dependency resolution!");
|
||||
};
|
||||
}
|
||||
} else
|
||||
if ($reader->hasType($d, 'phpmae:ClassDependency')) {
|
||||
// Class Dependency
|
||||
$classCoid = $reader->getFirstValueIRI($d, 'phpmae:hasClass');
|
||||
if (!isset($classCoid))
|
||||
throw new PhpMAEException("<".$object->getId()."> has an invalid dependency: ClassDependency without class!");
|
||||
|
||||
// TODO: can we optimize this? only create dependency container in inject function
|
||||
$dependencyContainer = $this->classRepository
|
||||
->createInstance($this->retrieverPool->getBaseObjectRetriever()->getObject($classCoid), null, [ 'callerClass' => $objectCoid ]);
|
||||
$keyedDependency = function() use ($dependencyContainer) {
|
||||
return $dependencyContainer->get(Engine::SKEY);
|
||||
};
|
||||
|
||||
// Also add with classname to allow constructor autowiring
|
||||
$definitions[$this->classRepository->coidToClassName($classCoid)] = $keyedDependency;
|
||||
} else
|
||||
if ($reader->hasType($d, 'phpmae:TwigTemplateDependency')) {
|
||||
// Twig Template Dependency
|
||||
$filename = $reader->getFirstValueString($d, 'phpmae:usesAttachedTwigFile');
|
||||
if (!isset($filename))
|
||||
throw new PhpMAEException("<".$object->getId()."> has an invalid dependency: TwigTemplateDependency without attachment!");
|
||||
|
||||
$keyedDependency = function() use ($object, $objectCoid, $filename) {
|
||||
$content = $this->retrieverPool->getBaseObjectRetriever()
|
||||
->getAttachment($objectCoid, $filename);
|
||||
$cachePath = $this->classRepository->getCustomFilesCachePath($object);
|
||||
return new TwigTemplate($filename, $content, $cachePath);
|
||||
};
|
||||
} else
|
||||
throw new PhpMAEException("<".$object->getId()."> has an invalid dependency: unknown type!");
|
||||
|
||||
$definitions[$reader->getFirstValueString($d, 'phpmae:hasKey')] = $keyedDependency;
|
||||
}
|
||||
|
||||
$attachments = $reader->getAllValuesString($object, 'phpmae:usesAttachedFile');
|
||||
foreach ($attachments as $a) {
|
||||
$filename = basename($a);
|
||||
$definitions[$filename] = function() use ($objectCoid, $filename) {
|
||||
$content = $this->retrieverPool->getBaseObjectRetriever()
|
||||
->getAttachment($objectCoid, $filename);
|
||||
|
||||
if (substr($filename, -5) == '.json')
|
||||
$content = json_decode($content, true);
|
||||
|
||||
return $content;
|
||||
};
|
||||
}
|
||||
|
||||
if ($reader->hasProperty($object, 'phpmae:usesStaticAccountContext')) {
|
||||
$accountDefinition = $reader->getFirstValueNode($object, 'phpmae:usesStaticAccountContext');
|
||||
if (!$reader->hasProperty($accountDefinition, 'phpmae:hasAAUID')
|
||||
|| !$reader->hasProperty($accountDefinition, 'phpmae:hasAccessToken'))
|
||||
throw new PhpMAEException("Incomplete AccountContext definition.");
|
||||
|
||||
$definitions[AccountContext::class] = function() use ($accountDefinition, $reader) {
|
||||
return new AccountContext(
|
||||
AAUIDParser::fromString($reader->getFirstValueString($accountDefinition,
|
||||
'phpmae:hasAAUID')),
|
||||
$reader->getFirstValueString($accountDefinition, 'phpmae:hasAccessToken')
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
return $definitions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of COIDs for class dependencies.
|
||||
*
|
||||
* @param Node $object The object representing the PHP class.
|
||||
*/
|
||||
public function getClassDependencyList(Node $object) {
|
||||
$reader = new NodeReader([
|
||||
'prefixes' => [ 'phpmae' => 'coid://phpmae.dev/' ]
|
||||
]);
|
||||
|
||||
$dependencies = $reader->getAllValuesNode($object, 'phpmae:hasDependency');
|
||||
|
||||
$list = [];
|
||||
|
||||
foreach ($dependencies as $d) {
|
||||
if ($reader->hasType($d, 'phpmae:ClassDependency')) {
|
||||
// Class Dependency
|
||||
$classCoid = $reader->getFirstValueIRI($d, 'phpmae:hasClass');
|
||||
if (!isset($classCoid))
|
||||
throw new PhpMAEException("<".$object->getId()."> has an invalid dependency: ClassDependency without class!");
|
||||
|
||||
$list[] = $classCoid;
|
||||
}
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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\PhpMAE\DI;
|
||||
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use CloudObjects\SDK\ObjectRetriever;
|
||||
use CloudObjects\PhpMAE\Engine, CloudObjects\PhpMAE\ClassRepository;
|
||||
|
||||
/**
|
||||
* The DynamicLoader allows accessing other classes that are not
|
||||
* statically defined as dependencies.
|
||||
*/
|
||||
class DynamicLoader {
|
||||
|
||||
private $retriever;
|
||||
private $repository;
|
||||
|
||||
public function __construct(ObjectRetriever $retriever, ClassRepository $repository) {
|
||||
$this->retriever = $retriever;
|
||||
$this->repository = $repository;
|
||||
}
|
||||
|
||||
public function get($coid, RequestInterface $request = null) {
|
||||
$object = $this->retriever->get($coid);
|
||||
return isset($object)
|
||||
? $this->repository->createInstance($object, $request)->get(Engine::SKEY)
|
||||
: null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?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\PhpMAE\DI;
|
||||
|
||||
use Psr\Container\ContainerInterface;
|
||||
|
||||
/**
|
||||
* The SandboxedContainer is a proxy class for a PSR-11 container that
|
||||
* ensures that only interface methods are available.
|
||||
*/
|
||||
class SandboxedContainer implements ContainerInterface {
|
||||
|
||||
private $container;
|
||||
|
||||
public function __construct(ContainerInterface $container) {
|
||||
$this->container = $container;
|
||||
}
|
||||
|
||||
public function has($id) {
|
||||
return $this->container->has($id);
|
||||
}
|
||||
|
||||
public function get($id) {
|
||||
return $this->container->get($id);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?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\PhpMAE\DI;
|
||||
|
||||
use DI\Definition\ObjectDefinition;
|
||||
use DI\Definition\Source\DefinitionSource, DI\Definition\Source\Autowiring,
|
||||
DI\Definition\Source\ReflectionBasedAutowiring;
|
||||
|
||||
/**
|
||||
* This is an extension of ReflectionBasedAutowiring that only allows autowiring
|
||||
* for classes that have a definition. Used for sandboxing containers.
|
||||
*/
|
||||
class WhitelistReflectionBasedAutowiring extends ReflectionBasedAutowiring implements DefinitionSource, Autowiring {
|
||||
|
||||
public function autowire(string $name, ObjectDefinition $definition = null) {
|
||||
return isset($definition) ? parent::autowire($name, $definition) : null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,323 @@
|
||||
<?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\PhpMAE;
|
||||
|
||||
use Psr\Http\Message\RequestInterface, Psr\Http\Message\ResponseInterface,
|
||||
Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use Slim\App;
|
||||
use Slim\Http\Headers, Slim\Http\Request, Slim\Http\Response, Slim\Http\Environment;
|
||||
use ML\IRI\IRI;
|
||||
use ML\JsonLD\Node;
|
||||
use Tuupola\Middleware\HttpBasicAuthentication,
|
||||
Tuupola\Middleware\CorsMiddleware;
|
||||
use Relay\Relay;
|
||||
use Dflydev\FigCookies\SetCookie, Dflydev\FigCookies\FigResponseCookies;
|
||||
use CloudObjects\SDK\COIDParser, CloudObjects\SDK\NodeReader,
|
||||
CloudObjects\SDK\ObjectRetriever;
|
||||
use CloudObjects\SDK\Helpers\SharedSecretAuthentication;
|
||||
use CloudObjects\PhpMAE\DI\SandboxedContainer;
|
||||
use CloudObjects\PhpMAE\Exceptions\PhpMAEException;
|
||||
|
||||
class Engine implements RequestHandlerInterface {
|
||||
|
||||
const SKEY = '__self';
|
||||
|
||||
const PREPARE_TIME_LIMIT = 2;
|
||||
const CLASS_TIME_LIMIT = 5;
|
||||
|
||||
const CO_PUBLIC = 'coid://cloudobjects.io/Public';
|
||||
|
||||
private $objectRetriever;
|
||||
private $classRepository;
|
||||
private $slim;
|
||||
private $container;
|
||||
private $reader;
|
||||
|
||||
private $object;
|
||||
private $runClass;
|
||||
|
||||
public function __construct(ObjectRetriever $objectRetriever,
|
||||
ClassRepository $classRepository, App $slim,
|
||||
ErrorHandler $errorHandler, ContainerInterface $container) {
|
||||
|
||||
$this->objectRetriever = $objectRetriever;
|
||||
$this->classRepository = $classRepository;
|
||||
$this->slim = $slim;
|
||||
$this->container = $container;
|
||||
$this->reader = new NodeReader([
|
||||
'prefixes' => [
|
||||
'co' => 'coid://cloudobjects.io/',
|
||||
'phpmae' => 'coid://phpmae.dev/'
|
||||
]
|
||||
]);
|
||||
|
||||
register_shutdown_function(function(ErrorHandler $handler) {
|
||||
$handler->getErrorResponse();
|
||||
}, $errorHandler); // see: http://stackoverflow.com/questions/4410632/handle-fatal-errors-in-php-using-register-shutdown-function
|
||||
}
|
||||
|
||||
private function isObjectPublic() {
|
||||
return ($this->reader->hasProperty($this->object, 'co:isVisibleTo')
|
||||
&& $this->reader->getFirstValueIRI($this->object, 'co:isVisibleTo')
|
||||
->equals(self::CO_PUBLIC)
|
||||
&& $this->reader->hasProperty($this->object, 'co:permitsUsageTo')
|
||||
&& $this->reader->getFirstValueIRI($this->object, 'co:permitsUsageTo')
|
||||
->equals(self::CO_PUBLIC));
|
||||
}
|
||||
|
||||
private function getCORSMiddleware() {
|
||||
$defaultConfig = [
|
||||
'headers.allow' => [ 'Content-Type' ],
|
||||
'cache' => 600
|
||||
];
|
||||
|
||||
if ($this->container->has('global_cors_origins')
|
||||
&& $this->container->get('global_cors_origins') == '*')
|
||||
return new CorsMiddleware($defaultConfig); // every origin is allowed, globally
|
||||
|
||||
if ($this->reader->getFirstValueString($this->object, 'phpmae:allowsCORSOrigin') == '*')
|
||||
return new CorsMiddleware($defaultConfig); // every origin is allowed, by class
|
||||
|
||||
$origins = $this->reader->getAllValuesString($this->object, 'phpmae:allowsCORSOrigin');
|
||||
|
||||
if ($this->container->has('global_cors_origins'))
|
||||
$origins = array_merge($origins, explode('|', $this->container->get('global_cors_origins')));
|
||||
|
||||
if (count($origins) == 0 || trim($origins[0]) == '')
|
||||
return null; // no CORS enabled
|
||||
|
||||
return new CorsMiddleware(array_merge($defaultConfig, [
|
||||
'origin' => $origins
|
||||
])); // configured CORS middleware
|
||||
}
|
||||
|
||||
private function getAuthenticationMiddleware() {
|
||||
$authSchemes = explode('|', $this->container->get('client_authentication'));
|
||||
if (in_array('none', $authSchemes))
|
||||
return null; // no authentication required
|
||||
|
||||
if (in_array('none:public_only', $authSchemes)
|
||||
&& $this->isObjectPublic())
|
||||
return null; // no authentication required
|
||||
|
||||
$object = $this->object;
|
||||
return new HttpBasicAuthentication([
|
||||
'secure' => !$this->container->has('client_authentication_must_be_secure')
|
||||
|| $this->container->get('client_authentication_must_be_secure'),
|
||||
'realm' => 'phpMAE',
|
||||
'authenticator' => function($args) use ($object, $authSchemes) {
|
||||
$authenticated = false;
|
||||
foreach ($authSchemes as $as) {
|
||||
if (substr($as, 0, 14) == 'shared_secret:') {
|
||||
$authResult = (SharedSecretAuthentication::verifyCredentials(
|
||||
$this->objectRetriever, $args['user'], $args['password'])
|
||||
== SharedSecretAuthentication::RESULT_OK);
|
||||
if ($authResult != true)
|
||||
continue;
|
||||
|
||||
if (substr($as, 14) == 'runclass')
|
||||
$authenticated = (substr($object->getId(), 7, strlen($args['user']) +1) == $args['user'].'/');
|
||||
else
|
||||
$authenticated = (substr($as, 14) == 'coid://'.$args['user']);
|
||||
} elseif (substr($as, 0, 4) != 'none')
|
||||
throw new PhpMAEException("Unsupported authentication scheme!");
|
||||
|
||||
if ($authenticated == true)
|
||||
break;
|
||||
}
|
||||
|
||||
return $authenticated;
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes an invokable class.
|
||||
*/
|
||||
private function executeInvokableClass(RequestInterface $request, $args = null) {
|
||||
if (is_array($args) && count($args) > 0) {
|
||||
$input = $args;
|
||||
} else {
|
||||
$input = $request->getParsedBody();
|
||||
if (!is_array($input))
|
||||
$input = [];
|
||||
}
|
||||
|
||||
set_time_limit($this->container->has('execution_time_limit')
|
||||
? $this->container->get('execution_time_limit') : self::CLASS_TIME_LIMIT);
|
||||
$result = $this->runClass->get(self::SKEY)->__invoke($input);
|
||||
|
||||
return $this->generateResponse($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an response with adequate Content Type based on the format of the content.
|
||||
* @param mixed $content Content for the body of the response.
|
||||
*/
|
||||
public function generateResponse($content) {
|
||||
$lowercaseContent = is_string($content) ? strtolower($content) : '';
|
||||
if (!isset($content))
|
||||
// Empty response
|
||||
$response = new Response(204);
|
||||
elseif (is_string($content) && (substr($lowercaseContent, 0, 5) == '<html' || substr($lowercaseContent, 0, 14) == '<!doctype html'))
|
||||
// HTML response
|
||||
$response = (new Response)->write($content);
|
||||
elseif (is_string($content) && (substr($lowercaseContent, 0, 7) == 'http://' || substr($lowercaseContent, 0, 8) == 'https://'))
|
||||
// Redirect response
|
||||
$response = (new Response)->withRedirect($content);
|
||||
elseif (is_string($content) || is_numeric($content))
|
||||
// Plain text response
|
||||
$response = (new Response)->withHeader('Content-Type', 'text/plain')->write($content);
|
||||
elseif (is_object($content) && in_array(ResponseInterface::class, class_implements($content)))
|
||||
// Existing response to pass through
|
||||
$response = $content;
|
||||
else
|
||||
// JSON response (default)
|
||||
$response = (new Response)->withJson($content);
|
||||
|
||||
// TODO: add support for XML
|
||||
|
||||
// Add cookies if any
|
||||
if (isset($this->runClass) && $this->runClass->has('cookies')) {
|
||||
foreach ($this->runClass->get('cookies') as $cookie) {
|
||||
if (is_a($cookie, SetCookie::class))
|
||||
$response = FigResponseCookies::set($response, $cookie);
|
||||
}
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a standard class using JSON-RPC.
|
||||
*/
|
||||
private function executeJsonRPC(RequestInterface $request) {
|
||||
$transport = new JsonRPCTransport;
|
||||
$server = new JsonRPCServer($this->runClass->get(self::SKEY), $transport);
|
||||
set_time_limit($this->container->has('execution_time_limit')
|
||||
? $this->container->get('execution_time_limit') : self::CLASS_TIME_LIMIT);
|
||||
$server->receive((string)$request->getBody());
|
||||
return $this->generateResponse($transport->getResponse());
|
||||
}
|
||||
|
||||
/**
|
||||
* Start execution of a request.
|
||||
*/
|
||||
public function execute(RequestInterface $request) {
|
||||
$path = rtrim($request->getUri()->getBasePath().$request->getUri()->getPath(), '/');
|
||||
switch ($path) {
|
||||
case "":
|
||||
case "/":
|
||||
// Display homepage
|
||||
$file = ($this->container
|
||||
->get(InteractiveRunController::class)
|
||||
->isEnabled()) ? 'app.html' : 'app_disabled.html';
|
||||
|
||||
return $this->generateResponse(file_get_contents(__DIR__.'/../static/'.$file));
|
||||
case "/run":
|
||||
// Run interactive code request
|
||||
return $this->container
|
||||
->get(InteractiveRunController::class)
|
||||
->handle($request, $this);
|
||||
case "/uploadTestenv":
|
||||
// Upload into test environment (if enabled)
|
||||
return $this->container
|
||||
->get(UploadController::class)
|
||||
->handle($request);
|
||||
default:
|
||||
if (file_exists(__DIR__.'/../static'.$path)) {
|
||||
// Proxy for static files
|
||||
$filename = realpath(__DIR__.'/../static'.$path);
|
||||
$response = (new Response)->write(file_get_contents($filename));
|
||||
switch (pathinfo($filename, PATHINFO_EXTENSION)) {
|
||||
case "css":
|
||||
return $response->withHeader('Content-Type', 'text/css');
|
||||
case "js":
|
||||
return $response->withHeader('Content-Type', 'application/javascript');
|
||||
default:
|
||||
return $response;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$coid = COIDParser::fromString(substr($path, 1));
|
||||
$this->loadRunClass($coid, $request);
|
||||
|
||||
// Process a standard request for a phpMAE class
|
||||
$queue = [
|
||||
$this->getCORSMiddleware(),
|
||||
$this->getAuthenticationMiddleware(),
|
||||
$this
|
||||
];
|
||||
$relay = new Relay(
|
||||
array_filter($queue, function ($q) {
|
||||
return $q !== null;
|
||||
})
|
||||
);
|
||||
return $relay->handle($request);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a class to execute.
|
||||
*/
|
||||
public function loadRunClass(IRI $coid, RequestInterface $request = null) {
|
||||
if (COIDParser::isValidCOID($coid) && COIDParser::getType($coid) != COIDParser::COID_ROOT) {
|
||||
$this->object = $this->objectRetriever->get($coid);
|
||||
if (!isset($this->object))
|
||||
throw new PhpMAEException("The object <" . (string)$coid . "> does not exist or this phpMAE instance is not allowed to access it.");
|
||||
$this->runClass = $this->classRepository->createInstance($this->object, $request);
|
||||
} else {
|
||||
throw new PhpMAEException("You must provide a valid, non-root COID to specify the class for execution.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies the class for execution in the engine.
|
||||
*/
|
||||
public function setRunClass(DI\SandboxedContainer $runClass) {
|
||||
$this->runClass = $runClass;
|
||||
}
|
||||
|
||||
public function handle(ServerRequestInterface $request, $args = null): ResponseInterface {
|
||||
try {
|
||||
if (ClassValidator::isInvokableClass($this->runClass->get(self::SKEY))) {
|
||||
// Run as invokable class
|
||||
return $this->executeInvokableClass($request, $args);
|
||||
} else {
|
||||
// Run as RPC
|
||||
// JsonRPC
|
||||
return $this->executeJsonRPC($request);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
throw new PhpMAEException(get_class($e).": ".$e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create main request and execute.
|
||||
*/
|
||||
public function run() {
|
||||
set_time_limit(self::PREPARE_TIME_LIMIT);
|
||||
|
||||
$env = new Environment($_SERVER);
|
||||
$request = Request::createFromEnvironment($env);
|
||||
|
||||
try {
|
||||
$response = $this->execute($request);
|
||||
} catch (PhpMAEException $e) {
|
||||
// Create plain-text error response
|
||||
$response = (new Response(500))
|
||||
->withHeader('Content-Type', 'text/plain')
|
||||
->write($e->getMessage());
|
||||
}
|
||||
|
||||
$this->slim->respond($response);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?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\PhpMAE;
|
||||
|
||||
use Slim\App;
|
||||
use Slim\Http\Response;
|
||||
use ML\JsonLD\Node;
|
||||
use CloudObjects\SDK\ObjectRetriever;
|
||||
|
||||
class ErrorHandler {
|
||||
|
||||
private $classMap = [];
|
||||
private $slim;
|
||||
private $responseGenerator;
|
||||
|
||||
public function __construct(App $slim) {
|
||||
$this->slim = $slim;
|
||||
ini_set("display_errors", 0);
|
||||
$this->setResponseGenerator(function($error, $object) {
|
||||
$message = substr($error['message'], 0, strpos($error['message'], ' in /'));
|
||||
return (new Response(500))
|
||||
->withHeader('Content-Type', 'text/plain')
|
||||
->write("Error in implementation of <".$object->getId()."> at revision "
|
||||
. $object->getProperty(ObjectRetriever::REVISION_PROPERTY)->getValue()
|
||||
. ":\n".$message);
|
||||
});
|
||||
}
|
||||
|
||||
public function addMapping($filename, Node $object) {
|
||||
$this->classMap[realpath($filename)] = $object;
|
||||
}
|
||||
|
||||
public function setResponseGenerator(callable $responseGenerator) {
|
||||
$this->responseGenerator = $responseGenerator;
|
||||
}
|
||||
|
||||
public function getErrorResponse() {
|
||||
$error = error_get_last();
|
||||
if ($error !== null && in_array($error['type'], [ E_ERROR, E_COMPILE_ERROR ])
|
||||
&& isset($this->classMap[$error['file']])) {
|
||||
|
||||
$response = call_user_func($this->responseGenerator, $error, $this->classMap[$error['file']]);
|
||||
$this->slim->respond($response);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?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\PhpMAE\Exceptions;
|
||||
|
||||
class PhpMAEException extends \Exception {
|
||||
|
||||
// provides no further implementation except for what was inherited
|
||||
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
<?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\PhpMAE\Injectables;
|
||||
|
||||
use Psr\SimpleCache\CacheInterface, Psr\SimpleCache\CacheException;
|
||||
use Psr\Http\Message\MessageInterface;
|
||||
use Doctrine\Common\Cache\Cache;
|
||||
use GuzzleHttp\Psr7;
|
||||
|
||||
class CacheBridge implements CacheInterface {
|
||||
|
||||
private $cache;
|
||||
private $prefix;
|
||||
|
||||
public function __construct(Cache $cache, string $prefix) {
|
||||
$this->cache = $cache;
|
||||
$this->prefix = $prefix;
|
||||
}
|
||||
|
||||
public function get($key, $default = null) {
|
||||
$value = $this->cache->fetch($this->prefix.$key);
|
||||
if (is_array($value) && isset($value['container']) && isset($value['body'])
|
||||
&& is_object($value['container']) && is_a($value['container'], MessageInterface::class)
|
||||
&& is_string($value['body'])) {
|
||||
// Convert message interface back
|
||||
$stream = Psr7\stream_for($value['body']);
|
||||
$value = $value['container']
|
||||
->withBody($stream);
|
||||
}
|
||||
return ($value === false ? $default : $value);
|
||||
}
|
||||
|
||||
public function set($key, $value, $ttl = null) {
|
||||
if (is_object($value) && is_a($value, MessageInterface::class)) {
|
||||
// Read full stream for caching
|
||||
$stream = $value->getBody();
|
||||
$value = [
|
||||
'container' => $value,
|
||||
'body' => $stream->getContents()
|
||||
];
|
||||
$stream->rewind();
|
||||
}
|
||||
return $this->cache->save($this->prefix.$key, $value, $ttl);
|
||||
}
|
||||
|
||||
public function delete($key) {
|
||||
throw new CacheException("This method is not implemented.");
|
||||
}
|
||||
|
||||
public function clear() {
|
||||
throw new CacheException("This method is not implemented.");
|
||||
}
|
||||
|
||||
public function getMultiple($keys, $default = null) {
|
||||
throw new CacheException("This method is not implemented.");
|
||||
}
|
||||
|
||||
public function setMultiple($values, $ttl = null) {
|
||||
throw new CacheException("This method is not implemented.");
|
||||
}
|
||||
|
||||
public function deleteMultiple($keys) {
|
||||
throw new CacheException("This method is not implemented.");
|
||||
}
|
||||
|
||||
public function has($key) {
|
||||
return $this->cache->contains($this->prefix.$key);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
<?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\PhpMAE;
|
||||
|
||||
use Exception;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use ML\JsonLD\JsonLD;
|
||||
use CloudObjects\SDK\COIDParser;
|
||||
use CloudObjects\SDK\AccountGateway\AccountContext, CloudObjects\SDK\AccountGateway\AAUIDParser;
|
||||
use CloudObjects\Utilities\RDF\Arc2JsonLdConverter;
|
||||
use GuzzleHttp\Psr7\ServerRequest;
|
||||
use CloudObjects\PhpMAE\Exceptions\PhpMAEException;
|
||||
|
||||
class InteractiveRunController {
|
||||
|
||||
private $classRepository;
|
||||
private $session;
|
||||
private $mapBack = [];
|
||||
private $accountContext;
|
||||
private $domains;
|
||||
|
||||
public function __construct(ClassRepository $classRepository,
|
||||
ContainerInterface $container) {
|
||||
|
||||
$this->classRepository = $classRepository;
|
||||
$this->container = $container;
|
||||
}
|
||||
|
||||
public function isEnabled() {
|
||||
return ($this->container->has('interactive_run')
|
||||
&& $this->container->get('interactive_run') === true);
|
||||
}
|
||||
|
||||
public function getOriginalHostname($sessionHostname) {
|
||||
if (!isset($this->accountContext)) {
|
||||
// We cannot access original hostnames for anonymous users
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!isset($this->domains)) {
|
||||
// Get domains that the user has access to
|
||||
$apiResponse = json_decode($this->accountContext->getClient()->get('dr/')
|
||||
->getBody(), true);
|
||||
$this->domains = isset($apiResponse['domains']) ? $apiResponse['domains'] : [];
|
||||
}
|
||||
|
||||
$hostname = @$this->mapBack[$sessionHostname];
|
||||
return (isset($hostname) && in_array($hostname, $this->domains))
|
||||
? $hostname : null;
|
||||
}
|
||||
|
||||
private function createTemporaryClassObject($className, $config) {
|
||||
// Parse RDF/XML config
|
||||
$parser = \ARC2::getRDFXMLParser();
|
||||
$parser->parse('', $config);
|
||||
$index = $parser->getSimpleIndex(false);
|
||||
|
||||
$rewrittenIndex = [];
|
||||
$targetName = '';
|
||||
foreach ($index as $subject => $data) {
|
||||
$coid = COIDParser::fromString($subject);
|
||||
if (COIDParser::getName($coid) == $className) {
|
||||
// Rewrite namespace to not interfere with production classes
|
||||
$targetName = 'coid://'.$this->session.'.phpmae'.$coid->getPath();
|
||||
$this->mapBack[$this->session.'.phpmae'] = $coid->getHost();
|
||||
$rewrittenIndex[$targetName] = $data;
|
||||
} else
|
||||
$rewrittenIndex[$subject] = $data;
|
||||
}
|
||||
|
||||
if (empty($targetName))
|
||||
throw new Exception("Could not find configuration for <".$className.">.");
|
||||
|
||||
// Convert to JsonLD as required by the CloudObjects SDK
|
||||
$document = JsonLD::getDocument(
|
||||
JsonLD::fromRdf(Arc2JsonLdConverter::indexToQuads($rewrittenIndex))
|
||||
);
|
||||
|
||||
// Find object
|
||||
$node = $document->getGraph()->getNode($targetName);
|
||||
|
||||
if (!isset($node))
|
||||
throw new Exception("Invalid object configuration.");
|
||||
|
||||
return $node;
|
||||
}
|
||||
|
||||
public function handle(RequestInterface $request, Engine $engine) {
|
||||
if (!$this->isEnabled())
|
||||
throw new PhpMAEException("This is not an environment with interactive run enabled.");
|
||||
|
||||
if ($request->getMethod() != 'POST')
|
||||
throw new PhpMAEException("Must use POST for run requests.");
|
||||
|
||||
$runData = $request->getParsedBody();
|
||||
|
||||
$this->session = isset($runData['session'])
|
||||
? $runData['session']
|
||||
: uniqid();
|
||||
|
||||
if (isset($runData['aauid']) && isset($runData['access_token'])) {
|
||||
// We have user credentials
|
||||
$this->accountContext = new AccountContext(
|
||||
AAUIDParser::fromString($runData['aauid']), $runData['access_token']
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
$object = $this->createTemporaryClassObject($runData['class'], $runData['config']);
|
||||
$engine->setRunClass($this->classRepository
|
||||
->createInstance($object, $request, [], $runData['sourceCode'])
|
||||
);
|
||||
} catch (Exception $e) {
|
||||
// Catch validation errors
|
||||
return $engine->generateResponse([
|
||||
'status' => 'error',
|
||||
'content' => $e->getMessage(),
|
||||
'session' => $this->session
|
||||
]);
|
||||
}
|
||||
|
||||
$rpcBody = json_encode([
|
||||
'jsonrpc' => '2.0',
|
||||
'method' => $runData['method'],
|
||||
'params' => $runData['params'],
|
||||
'id' => 'PG'
|
||||
]);
|
||||
|
||||
// Update error handler for response format required by client
|
||||
$this->container->get(ErrorHandler::class)
|
||||
->setResponseGenerator(function($error, $object) use ($engine) {
|
||||
$message = substr($error['message'], 0, strpos($error['message'], ' in /'));
|
||||
return $engine->generateResponse([
|
||||
'status' => 'error',
|
||||
'content' => $message,
|
||||
'session' => $this->session
|
||||
]);
|
||||
});
|
||||
|
||||
try {
|
||||
$innerRequest = new ServerRequest('POST', $request->getUri(),
|
||||
$request->getHeaders(), $rpcBody);
|
||||
$innerResponse = $engine->handle($innerRequest);
|
||||
$rpcResponse = json_decode($innerResponse->getBody(), true);
|
||||
|
||||
if (isset($rpcResponse['result']))
|
||||
return $engine->generateResponse([
|
||||
'status' => 'success',
|
||||
'content' => $rpcResponse['result'],
|
||||
'session' => $this->session
|
||||
]);
|
||||
else
|
||||
return $engine->generateResponse([
|
||||
'status' => 'error',
|
||||
'content' => $rpcResponse['error']['message'],
|
||||
'session' => $this->session
|
||||
]);
|
||||
} catch (PhpMAEException $e) {
|
||||
return $engine->generateResponse([
|
||||
'status' => 'error',
|
||||
'content' => $e->getMessage(),
|
||||
'session' => $this->session
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,459 @@
|
||||
<?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\PhpMAE;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use JsonRpc\Base\Rpc;
|
||||
use JsonRpc\Base\Request;
|
||||
use JsonRpc\Base\Response;
|
||||
|
||||
/**
|
||||
* This is a modified version of JsonRPC\Server with custom enhancements for phpMAE.
|
||||
*/
|
||||
class JsonRPCServer
|
||||
{
|
||||
|
||||
private $handler;
|
||||
private $transport = null;
|
||||
private $logger = null;
|
||||
private $assoc = false;
|
||||
private $requests = array();
|
||||
private $responses = array();
|
||||
private $error = null;
|
||||
private $handlerError = null;
|
||||
private $refClass = null;
|
||||
|
||||
|
||||
public function __construct($methodHandler, $transport = null)
|
||||
{
|
||||
|
||||
ini_set('display_errors', '0');
|
||||
|
||||
$this->handler = $methodHandler;
|
||||
$this->transport = $transport;
|
||||
|
||||
if (!$this->transport)
|
||||
{
|
||||
$this->transport = new JsonRPCTransport;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
public function receive($input = '')
|
||||
{
|
||||
|
||||
$this->init();
|
||||
|
||||
try
|
||||
{
|
||||
$input = $input ?: $this->transport->receive();
|
||||
$json = $this->process($input);
|
||||
$this->transport->reply($json);
|
||||
}
|
||||
catch (\Exception $e)
|
||||
{
|
||||
$this->logException($e);
|
||||
exit;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
public function setTransport($transport)
|
||||
{
|
||||
$this->transport = $transport;
|
||||
}
|
||||
|
||||
|
||||
public function setLogger($logger)
|
||||
{
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
|
||||
public function setObjectsAsArrays()
|
||||
{
|
||||
$this->assoc = true;
|
||||
}
|
||||
|
||||
|
||||
private function process($json)
|
||||
{
|
||||
if (!$struct = Rpc::decode($json, $batch))
|
||||
{
|
||||
$code = is_null($struct) ? Rpc::ERR_PARSE : Rpc::ERR_REQUEST;
|
||||
$data = new Response();
|
||||
$data->createStdError($code);
|
||||
return $data->toJson();
|
||||
}
|
||||
|
||||
$this->getRequests($struct);
|
||||
$this->processRequests();
|
||||
|
||||
if (count($this->responses) == 1 && is_object($this->responses[0]))
|
||||
return $this->responses[0];
|
||||
|
||||
$data = implode(',', $this->responses);
|
||||
|
||||
return $batch && $data ? '[' . $data . ']' : $data;
|
||||
|
||||
}
|
||||
|
||||
|
||||
private function getRequests($struct)
|
||||
{
|
||||
|
||||
if (is_array($struct))
|
||||
{
|
||||
|
||||
foreach ($struct as $item)
|
||||
{
|
||||
$this->requests[] = new Request($item);
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->requests[] = new Request($struct);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private function processRequests()
|
||||
{
|
||||
|
||||
foreach ($this->requests as $request)
|
||||
{
|
||||
|
||||
# check if we got an error parsing the request, otherwise process it
|
||||
if ($request->fault)
|
||||
{
|
||||
|
||||
$this->error = array(
|
||||
'code' => Rpc::ERR_REQUEST,
|
||||
'data' => $request->fault
|
||||
);
|
||||
|
||||
# we always response to request errors
|
||||
$this->addResponse($request, null);
|
||||
|
||||
continue;
|
||||
|
||||
}
|
||||
|
||||
$result = $this->processRequest($request->method, $request->params);
|
||||
|
||||
if (!$request->notification)
|
||||
{
|
||||
$this->addResponse($request, $result);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private function processRequest($method, $params)
|
||||
{
|
||||
|
||||
$this->error = null;
|
||||
|
||||
if (!$callback = $this->getCallback($method))
|
||||
{
|
||||
$this->error = Rpc::ERR_METHOD;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$this->checkMethod($method, $params))
|
||||
{
|
||||
$this->error = Rpc::ERR_PARAMS;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->assoc)
|
||||
{
|
||||
$this->castObjectsToArrays($params);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$result = call_user_func_array($callback, $params);
|
||||
}
|
||||
catch (\Exception $e)
|
||||
{
|
||||
$this->logException($e);
|
||||
$this->error = Rpc::ERR_INTERNAL;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->error = $this->getHandlerError())
|
||||
{
|
||||
$this->clearHandlerError();
|
||||
}
|
||||
|
||||
return $result;
|
||||
|
||||
}
|
||||
|
||||
|
||||
private function addResponse($request, $result)
|
||||
{
|
||||
if (is_object($result) && is_a($result, ResponseInterface::class)) {
|
||||
$this->responses[] = $result;
|
||||
return;
|
||||
}
|
||||
|
||||
$ar = array(
|
||||
'id' => $request->id
|
||||
);
|
||||
|
||||
if ($this->error)
|
||||
{
|
||||
$ar['error'] = $this->error;
|
||||
}
|
||||
else
|
||||
{
|
||||
$ar['result'] = $result;
|
||||
}
|
||||
|
||||
$response = new Response();
|
||||
|
||||
if (!$response->create($ar))
|
||||
{
|
||||
$this->logError($response->fault);
|
||||
$response->createStdError(Rpc::ERR_INTERNAL, $request->id);
|
||||
}
|
||||
|
||||
$this->responses[] = $response->toJson();
|
||||
|
||||
}
|
||||
|
||||
|
||||
private function getCallback($method)
|
||||
{
|
||||
|
||||
$callback = array($this->handler, $method);
|
||||
|
||||
if (is_callable($callback))
|
||||
{
|
||||
return $callback;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private function checkMethod($method, &$params)
|
||||
{
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
if (!$this->refClass)
|
||||
{
|
||||
# we have already checked that handler is callable
|
||||
$this->refClass = new \ReflectionClass($this->handler);
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
$prop = $this->refClass->getProperty('error');
|
||||
|
||||
if ($prop->isPublic())
|
||||
{
|
||||
$this->handlerError = $prop;
|
||||
}
|
||||
|
||||
}
|
||||
catch (\Exception $e){}
|
||||
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$refMethod = $this->refClass->getMethod($method);
|
||||
}
|
||||
catch (\Exception $e)
|
||||
{
|
||||
# we know we are callable, so the class must be implementing __call or __callStatic
|
||||
$params = $this->getParams($params);
|
||||
return true;
|
||||
}
|
||||
|
||||
$res = true;
|
||||
|
||||
if (is_object($params))
|
||||
{
|
||||
|
||||
$named = (array) $params;
|
||||
$params = array();
|
||||
$refParams = $refMethod->getParameters();
|
||||
|
||||
foreach ($refParams as $arg)
|
||||
{
|
||||
|
||||
$argName = $arg->getName();
|
||||
|
||||
if (array_key_exists($argName, $named))
|
||||
{
|
||||
$params[] = $named[$argName];
|
||||
unset($named[$argName]);
|
||||
}
|
||||
elseif (!$arg->isOptional())
|
||||
{
|
||||
$res = false;
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ($extra = array_values($named))
|
||||
{
|
||||
$params = array_merge($params, $extra);
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
$params = $this->getParams($params);
|
||||
$reqArgs = $refMethod->getNumberOfRequiredParameters();
|
||||
$res = count($params) >= $reqArgs;
|
||||
}
|
||||
|
||||
return $res;
|
||||
|
||||
}
|
||||
catch (\Exception $e) {}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private function getParams($params)
|
||||
{
|
||||
|
||||
if (is_object($params))
|
||||
{
|
||||
$params = array_values((array) $params);
|
||||
}
|
||||
elseif (is_null($params))
|
||||
{
|
||||
$params = array();
|
||||
}
|
||||
|
||||
return $params;
|
||||
|
||||
}
|
||||
|
||||
|
||||
private function castObjectsToArrays(&$params)
|
||||
{
|
||||
|
||||
foreach ($params as &$param)
|
||||
{
|
||||
|
||||
if (is_object($param))
|
||||
{
|
||||
$param = (array) $param;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private function getHandlerError()
|
||||
{
|
||||
|
||||
if ($this->handlerError)
|
||||
{
|
||||
|
||||
if ($this->handlerError->isStatic())
|
||||
{
|
||||
return $this->handlerError->getValue();
|
||||
}
|
||||
else
|
||||
{
|
||||
return $this->handlerError->getValue($this->handler);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private function clearHandlerError()
|
||||
{
|
||||
|
||||
if ($this->handlerError)
|
||||
{
|
||||
|
||||
if ($this->handlerError->isStatic())
|
||||
{
|
||||
return $this->handlerError->setValue(null);
|
||||
}
|
||||
else
|
||||
{
|
||||
return $this->handlerError->setValue($this->handler, null);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private function logException(\Exception $e)
|
||||
{
|
||||
$message = 'Exception: '. $e->getMessage();
|
||||
$message .= ' in ' . $e->getFile() . ' on line ' . $e->getLine();
|
||||
$this->logError($message);
|
||||
}
|
||||
|
||||
private function logError($message)
|
||||
{
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
if ($this->logger)
|
||||
{
|
||||
|
||||
$callback = array($this->logger, 'addRecord');
|
||||
|
||||
$params = array(
|
||||
500,
|
||||
$message
|
||||
);
|
||||
|
||||
$result = call_user_func_array($callback, $params);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
error_log($message);
|
||||
}
|
||||
|
||||
}
|
||||
catch (\Exception $e)
|
||||
{
|
||||
error_log($e->__toString());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private function init()
|
||||
{
|
||||
$this->requests = array();
|
||||
$this->responses = array();
|
||||
$this->error = null;
|
||||
$this->handlerError = null;
|
||||
$this->refClass = null;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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\PhpMAE;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Slim\Http\Response;
|
||||
|
||||
/**
|
||||
* The JsonRPCTransport writes JsonRPC output into a Slim response.
|
||||
*/
|
||||
class JsonRPCTransport {
|
||||
|
||||
private $response;
|
||||
|
||||
public function reply($data) {
|
||||
if (is_a($data, ResponseInterface::class))
|
||||
$this->response = $data->withHeader('C-PhpMae-Passthru', '1');
|
||||
else
|
||||
$this->response = (new Response(200))
|
||||
->withHeader('Content-Type', 'application/json')
|
||||
->write($data);
|
||||
}
|
||||
|
||||
public function receive() {
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the response
|
||||
*/
|
||||
public function getResponse() {
|
||||
return $this->response;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<?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\PhpMAE;
|
||||
|
||||
use GuzzleHttp\Client;
|
||||
use CloudObjects\SDK\ObjectRetriever;
|
||||
|
||||
class ObjectRetrieverPool {
|
||||
|
||||
private $baseObjectRetriever;
|
||||
private $baseHostname;
|
||||
private $options;
|
||||
private $objectRetrievers = [];
|
||||
|
||||
public function __construct(ObjectRetriever $baseObjectRetriever, $baseHostname, $options) {
|
||||
$this->baseObjectRetriever = $baseObjectRetriever;
|
||||
$this->baseHostname = $baseHostname;
|
||||
$this->objectRetrievers[$baseHostname] = $baseObjectRetriever;
|
||||
$this->options = $options;
|
||||
}
|
||||
|
||||
public function getBaseObjectRetriever() {
|
||||
return $this->baseObjectRetriever;
|
||||
}
|
||||
|
||||
public function getObjectRetriever($hostname) {
|
||||
if (!isset($this->baseHostname))
|
||||
// Never create a new retriever when using developer credentials
|
||||
return $this->baseObjectRetriever;
|
||||
|
||||
if (!isset($this->objectRetrievers[$hostname])) {
|
||||
if (substr($hostname, -7) == '.phpmae') {
|
||||
// Get an anonymous retriever
|
||||
$retriever = new ObjectRetriever(array_merge($this->options, [
|
||||
'auth_ns' => '',
|
||||
'auth_secret' => '',
|
||||
'cache_prefix' => 'clobj:'.$hostname.':',
|
||||
'logger' => $this->options['logger']->withName('CO-'.$hostname)
|
||||
]));
|
||||
} else {
|
||||
// Get a retriever with namespace impersonation
|
||||
$config = $this->baseObjectRetriever->getClient()->getConfig();
|
||||
$config['headers']['C-Act-As'] = $hostname;
|
||||
|
||||
$retriever = new ObjectRetriever(array_merge($this->options, [
|
||||
'cache_prefix' => 'clobj:'.$hostname.':',
|
||||
'logger' => $this->options['logger']->withName('CO-'.$hostname)
|
||||
]));
|
||||
$retriever->setClient(new Client($config));
|
||||
}
|
||||
$this->objectRetrievers[$hostname] = $retriever;
|
||||
}
|
||||
|
||||
return $this->objectRetrievers[$hostname];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,302 @@
|
||||
<?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\PhpMAE;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use Psr\Http\Message\RequestInterface, Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Slim\App;
|
||||
use Tuupola\Middleware\CorsMiddleware;;
|
||||
use Slim\Http\Environment, Slim\Http\Uri, Slim\Http\Response,
|
||||
Slim\Http\Request, Slim\Http\Headers;
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Exception\BadResponseException;
|
||||
use GuzzleHttp\Psr7\ServerRequest;
|
||||
use ML\IRI\IRI;
|
||||
use ML\JsonLD\Node, ML\JsonLD\JsonLD;
|
||||
use CloudObjects\Utilities\RDF\Arc2JsonLdConverter;
|
||||
use CloudObjects\SDK\NodeReader, CloudObjects\SDK\ObjectRetriever;
|
||||
use CloudObjects\SDK\JSON\SchemaValidator;
|
||||
use CloudObjects\PhpMAE\Exceptions\PhpMAEException;
|
||||
use CloudObjects\PhpMAE\ObjectRetrieverPool;
|
||||
|
||||
class Router {
|
||||
|
||||
const STATIC_CACHE_TTL = 60;
|
||||
|
||||
private $engine;
|
||||
private $objectRetriever;
|
||||
private $container;
|
||||
|
||||
public function __construct(Engine $engine, ObjectRetriever $objectRetriever,
|
||||
ContainerInterface $container) {
|
||||
|
||||
$this->engine = $engine;
|
||||
$this->objectRetriever = $objectRetriever;
|
||||
$this->container = $container;
|
||||
}
|
||||
|
||||
private function getRequestHeaders(Request $request) {
|
||||
$headers = new Headers; // this instance is required to access the "normalizeKey" method
|
||||
$output = [];
|
||||
foreach (array_keys($request->getHeaders()) as $key) {
|
||||
$key = $headers->normalizeKey($key);
|
||||
if (substr($key, 0, 9) == 'c-phpmae-' || $key == 'host') {
|
||||
// do not forward "Host" header or custom phpMAE headers
|
||||
continue;
|
||||
}
|
||||
$output[$key] = $request->getHeaderLine($key);
|
||||
}
|
||||
return $output;
|
||||
}
|
||||
|
||||
private function configure(App $app, Node $object) {
|
||||
$reader = new NodeReader([
|
||||
'prefixes' => [
|
||||
'co' => 'coid://cloudobjects.io/',
|
||||
'phpmae' => 'coid://phpmae.dev/',
|
||||
'wa' => 'coid://webapis.co-n.net/',
|
||||
'agws' => 'coid://aauid.net/',
|
||||
]
|
||||
]);
|
||||
|
||||
if ($reader->hasProperty($object, 'phpmae:enableCORSWithOrigin')) {
|
||||
// Add CORS support to router
|
||||
$origins = $reader->getAllValuesString($object, 'phpmae:enableCORSWithOrigin');
|
||||
$app->add(new CorsMiddleware([
|
||||
'origin' => $origins,
|
||||
'headers.allow' => [ 'Content-Type' ],
|
||||
'credentials' => true,
|
||||
'cache' => 600
|
||||
]));
|
||||
}
|
||||
|
||||
$routes = array_merge(
|
||||
$reader->getAllValuesNode($object, 'phpmae:hasRoute'),
|
||||
$reader->getAllValuesNode($object, 'agws:hasMethod')
|
||||
);
|
||||
|
||||
$router = $this;
|
||||
$engine = $this->engine;
|
||||
$retriever = $this->getObjectRetriever(new IRI($object->getId()));
|
||||
|
||||
foreach ($routes as $r) {
|
||||
if (!$reader->hasProperty($r, 'wa:hasVerb') || !$reader->hasProperty($r, 'wa:hasPath'))
|
||||
throw new PhpMAEException("Incomplete route configuration! Routes must have wa:hasVerb and wa:hasPath.");
|
||||
|
||||
$app->map([ $reader->getFirstValueString($r, 'wa:hasVerb') ],
|
||||
$reader->getFirstValueString($r, 'wa:hasPath'),
|
||||
function(RequestInterface $request, ResponseInterface $response, $args) use ($r, $reader, $router, $engine, $retriever, $object) {
|
||||
if ($reader->hasProperty($r, 'phpmae:runsClass')) {
|
||||
try {
|
||||
$params = [];
|
||||
if ($reader->hasProperty($r, 'wa:hasJSONBodyWithSchema')) {
|
||||
// Validate request against schema
|
||||
$schemaValidator = new SchemaValidator($retriever);
|
||||
$schemaValidator->validateAgainstCOID($request->getParsedBody(),
|
||||
$reader->getFirstValueIRI($r, 'wa:hasJSONBodyWithSchema'));
|
||||
// Then, map to single argument
|
||||
$params = [ $request->getParsedBody() ];
|
||||
} elseif (is_array($args) && count($args) > 0) {
|
||||
// Use path arguments as params
|
||||
$params = $args;
|
||||
} elseif (is_array($request->getParsedBody())) {
|
||||
// Use body as params
|
||||
$params = $request->getParsedBody();
|
||||
} elseif (is_array($request->getQueryParams())) {
|
||||
// Use query as params
|
||||
$params = $request->getQueryParams();
|
||||
}
|
||||
} catch (InvalidArgumentException $e) {
|
||||
return (new Response(400))->withJson([
|
||||
'error' => 'InputValidationFailed',
|
||||
'message' => $e->getMessage()
|
||||
]);
|
||||
}
|
||||
|
||||
// Add static parameters from Router
|
||||
foreach ($reader->getAllValuesNode($r, 'phpmae:includeParameterWithDefault') as $p) {
|
||||
if (!$reader->hasProperty($p, 'wa:hasKey') || !$reader->hasProperty($p, 'wa:hasDefaultValue')) {
|
||||
// Ignore incomplete parameter
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($reader->hasType($p, 'wa:HeaderParameter')) {
|
||||
// Add custom header
|
||||
$request = $request->withHeader(
|
||||
'HTTP_'.strtoupper($reader->getFirstValueString($p, 'wa:hasKey')),
|
||||
$reader->getFirstValueString($p, 'wa:hasDefaultValue')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Route is mapped to a class
|
||||
$engine->loadRunClass($reader->getFirstValueIRI($r, 'phpmae:runsClass'), $request);
|
||||
|
||||
if ($reader->hasProperty($r, 'phpmae:runsMethod')) {
|
||||
// Calls to specific methods must be rewritten
|
||||
// Path, request and query parameters are merged and used as method input
|
||||
$rpcBody = json_encode([
|
||||
'jsonrpc' => '2.0',
|
||||
'method' => $reader->getFirstValueString($r, 'phpmae:runsMethod'),
|
||||
'params' => $params,
|
||||
'id' => 'R'
|
||||
]);
|
||||
$innerRequest = new ServerRequest('POST', $request->getUri(),
|
||||
$request->getHeaders(), $rpcBody);
|
||||
|
||||
$innerResponse = $engine->handle($innerRequest);
|
||||
if ($innerResponse->hasHeader('C-PhpMae-Passthru'))
|
||||
return $innerResponse->withoutHeader('C-PhpMae-Passthru');
|
||||
|
||||
$rpcResponse = json_decode($innerResponse->getBody(), true);
|
||||
|
||||
if (isset($rpcResponse['result'])) {
|
||||
return $engine->generateResponse($rpcResponse['result']);
|
||||
} else {
|
||||
return (new Response(500))->withJson($rpcResponse);
|
||||
}
|
||||
} else {
|
||||
// Generic class execution (invokable)
|
||||
return $engine->handle($request, $params);
|
||||
}
|
||||
|
||||
} elseif ($reader->hasProperty($r, 'phpmae:redirectsToURL')) {
|
||||
// Route to redirect to a specific external URL
|
||||
return (new Response)->withRedirect($reader->getFirstValueString($r, 'phpmae:redirectsToURL'));
|
||||
|
||||
} elseif ($reader->hasProperty($r, 'phpmae:redirectsToBaseURL')) {
|
||||
// Route to redirect to an external base URL
|
||||
$baseUrl = $reader->getFirstValueIRI($r, 'phpmae:redirectsToBaseURL');
|
||||
$uri = $request->getUri();
|
||||
$targetUrl = (string)$baseUrl->resolve(substr($uri->getPath(), 1) . ($uri->getQuery() != '' ? '?'.$uri->getQuery() : ''));
|
||||
|
||||
return (new Response)->withRedirect($targetUrl);
|
||||
|
||||
} elseif ($reader->hasProperty($r, 'phpmae:proxiesRequestsToBaseURL')) {
|
||||
// Route to proxy requests to an external URL
|
||||
$client = new Client([
|
||||
'base_uri' => $reader->getFirstValueString($r, 'phpmae:proxiesRequestsToBaseURL')
|
||||
]);
|
||||
$uri = $request->getUri();
|
||||
try {
|
||||
return $client->request($request->getMethod(),
|
||||
$uri->getPath() . ($uri->getQuery() != '' ? '?'.$uri->getQuery() : ''), [
|
||||
'headers' => $router->getRequestHeaders($request),
|
||||
'body' => $request->getBody()
|
||||
]);
|
||||
} catch (BadResponseException $e) {
|
||||
return $e->getResponse();
|
||||
}
|
||||
|
||||
} elseif ($reader->hasProperty($r, 'phpmae:servesStaticFileAttachment')) {
|
||||
// Rule to serve an attached file
|
||||
$etag = '"'.md5($reader->getFirstValueString($object, 'co:isAtRevision')
|
||||
.'+'.$reader->getFirstValueString($r, 'phpmae:servesStaticFileAttachment')).'"';
|
||||
|
||||
if ($request->hasHeader('If-None-Match') && strpos($request->getHeaderLine('If-None-Match'), $etag) > -1) {
|
||||
// Can use cached version
|
||||
return (new Response(304))->withHeader('ETag', $etag)
|
||||
->withHeader('Cache-Control', 'max-age='.self::STATIC_CACHE_TTL.', public');
|
||||
}
|
||||
|
||||
$attachmentContent = $retriever->getAttachment(new IRI($object->getId()),
|
||||
$reader->getFirstValueString($r, 'phpmae:servesStaticFileAttachment'));
|
||||
|
||||
if (!isset($attachmentContent))
|
||||
return (new Response(501))->write("Requested static file attachment cannot be found.");
|
||||
|
||||
return $engine->generateResponse($attachmentContent)
|
||||
->withHeader('ETag', $etag)
|
||||
->withHeader('Cache-Control', 'max-age='.self::STATIC_CACHE_TTL.', public');
|
||||
} else {
|
||||
// Route has no implementation
|
||||
return (new Response(501))->write("Route implementation not available or no access granted.");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private function getObjectRetriever(IRI $coid) {
|
||||
return $this->container->get(ObjectRetrieverPool::class)
|
||||
->getObjectRetriever($coid->getHost());
|
||||
}
|
||||
|
||||
private function getRouter(IRI $coid) {
|
||||
return $this->getObjectRetriever($coid)
|
||||
->get($coid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup and run router.
|
||||
*/
|
||||
public function run() {
|
||||
try {
|
||||
$modes = explode('|', $this->container->get('mode'));
|
||||
$fallback = false;
|
||||
|
||||
$configuration = [
|
||||
'settings' => [
|
||||
'displayErrorDetails' => true,
|
||||
],
|
||||
];
|
||||
$c = new \Slim\Container($configuration);
|
||||
$app = new App($c);
|
||||
|
||||
$router = null;
|
||||
$env = new Environment($_SERVER);
|
||||
foreach ($modes as $m) {
|
||||
switch ($m) {
|
||||
case 'router:vhost':
|
||||
if ($router == null) {
|
||||
$uri = Uri::createFromEnvironment($env);
|
||||
if ($uri->getHost() == 'localhost' || filter_var($uri->getHost(), FILTER_VALIDATE_IP) !== false) continue;
|
||||
|
||||
$namespace = $this->objectRetriever->get('coid://'.$uri->getHost());
|
||||
if (isset($namespace) && $routerCoid = $namespace->getProperty('coid://phpmae.dev/hasRouter'))
|
||||
$router = $this->getRouter(new IRI($routerCoid->getId()));
|
||||
}
|
||||
break;
|
||||
case 'router:header':
|
||||
if ($router == null) {
|
||||
$request = Request::createFromEnvironment($env);
|
||||
if ($request->hasHeader('C-PhpMae-Router-COID'))
|
||||
$router = $this->getRouter(new IRI($request->getHeaderLine('C-PhpMae-Router-COID')));
|
||||
}
|
||||
break;
|
||||
case 'default':
|
||||
$fallback = true;
|
||||
break;
|
||||
default:
|
||||
if (substr($m, 0, 14) == 'router:coid://')
|
||||
$router = $this->getRouter(new IRI(substr($m, 7)));
|
||||
else
|
||||
throw new PhpMAEException("Unsupported mode!");
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($router)) {
|
||||
$this->configure($app, $router);
|
||||
$response = $app->run(true);
|
||||
} elseif ($fallback) {
|
||||
$this->container->get(Engine::class)
|
||||
->run();
|
||||
exit;
|
||||
} else {
|
||||
throw new PhpMAEException("No router found!");
|
||||
}
|
||||
|
||||
} catch (PhpMAEException $e) {
|
||||
// Create plain-text error response
|
||||
$response = (new Response(500))
|
||||
->withHeader('Content-Type', 'text/plain')
|
||||
->write($e->getMessage());
|
||||
}
|
||||
|
||||
$app->respond($response);
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,403 @@
|
||||
<?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\PhpMAE\Sandbox;
|
||||
|
||||
use PhpParser\Node, PhpParser\NodeVisitorAbstract;
|
||||
use PHPSandbox\PHPSandbox, PHPSandbox\ValidatorVisitor,
|
||||
PHPSandbox\Error;
|
||||
|
||||
class CustomizedValidatorVisitor extends ValidatorVisitor {
|
||||
|
||||
public function leaveNode(Node $node){
|
||||
if($node instanceof Node\Arg){
|
||||
return new Node\Expr\FuncCall(new Node\Name\FullyQualified(($node->value instanceof Node\Expr\Variable) ? 'PHPSandbox\\wrapByRef' : 'PHPSandbox\\wrap'), [$node, new Node\Expr\StaticCall(new Node\Name\FullyQualified("CloudObjects\\PhpMAE\\Sandbox\\CustomizedSandbox"), 'getGlobalSandbox')], $node->getAttributes());
|
||||
} else if($node instanceof Node\Stmt\InlineHTML){
|
||||
if(!$this->sandbox->allow_escaping){
|
||||
$this->sandbox->validationError("Sandboxed code attempted to escape to HTML!", Error::ESCAPE_ERROR, $node);
|
||||
}
|
||||
}else if($node instanceof Node\Expr\Cast){
|
||||
if(!$this->sandbox->allow_casting){
|
||||
$this->sandbox->validationError("Sandboxed code attempted to cast!", Error::CAST_ERROR, $node);
|
||||
}
|
||||
if($node instanceof Node\Expr\Cast\Int_){
|
||||
return new Node\Expr\MethodCall(new Node\Expr\StaticCall(new Node\Name\FullyQualified("CloudObjects\\PhpMAE\\Sandbox\\CustomizedSandbox"), 'getGlobalSandbox'), '_intval', [new Node\Arg($node->expr)], $node->getAttributes());
|
||||
} else if($node instanceof Node\Expr\Cast\Double){
|
||||
return new Node\Expr\MethodCall(new Node\Expr\StaticCall(new Node\Name\FullyQualified("CloudObjects\\PhpMAE\\Sandbox\\CustomizedSandbox"), 'getGlobalSandbox'), '_floatval', [new Node\Arg($node->expr)], $node->getAttributes());
|
||||
} else if($node instanceof Node\Expr\Cast\Bool_){
|
||||
return new Node\Expr\MethodCall(new Node\Expr\StaticCall(new Node\Name\FullyQualified("CloudObjects\\PhpMAE\\Sandbox\\CustomizedSandbox"), 'getGlobalSandbox'), '_boolval', [new Node\Arg($node->expr)], $node->getAttributes());
|
||||
} else if($node instanceof Node\Expr\Cast\Array_){
|
||||
return new Node\Expr\MethodCall(new Node\Expr\StaticCall(new Node\Name\FullyQualified("CloudObjects\\PhpMAE\\Sandbox\\CustomizedSandbox"), 'getGlobalSandbox'), '_arrayval', [new Node\Arg($node->expr)], $node->getAttributes());
|
||||
} else if($node instanceof Node\Expr\Cast\Object_){
|
||||
return new Node\Expr\MethodCall(new Node\Expr\StaticCall(new Node\Name\FullyQualified("CloudObjects\\PhpMAE\\Sandbox\\CustomizedSandbox"), 'getGlobalSandbox'), '_objectval', [new Node\Arg($node->expr)], $node->getAttributes());
|
||||
}
|
||||
} else if($node instanceof Node\Expr\FuncCall){
|
||||
if($node->name instanceof Node\Name){
|
||||
$name = strtolower($node->name->toString());
|
||||
if(!$this->sandbox->checkFunc($name)){
|
||||
$this->sandbox->validationError("Function failed custom validation!", Error::VALID_FUNC_ERROR, $node);
|
||||
}
|
||||
if($this->sandbox->isDefinedFunc($name)){
|
||||
$args = $node->args;
|
||||
array_unshift($args, new Node\Arg(new Node\Scalar\String_($name)));
|
||||
return new Node\Expr\MethodCall(new Node\Expr\StaticCall(new Node\Name\FullyQualified("CloudObjects\\PhpMAE\\Sandbox\\CustomizedSandbox"), 'getGlobalSandbox'), 'call_func', $args, $node->getAttributes());
|
||||
}
|
||||
if($this->sandbox->overwrite_defined_funcs && in_array($name, PHPSandbox::$defined_funcs)){
|
||||
return new Node\Expr\MethodCall(new Node\Expr\StaticCall(new Node\Name\FullyQualified("CloudObjects\\PhpMAE\\Sandbox\\CustomizedSandbox"), 'getGlobalSandbox'), '_' . $name, [new Node\Arg(new Node\Expr\FuncCall(new Node\Name([$name])))], $node->getAttributes());
|
||||
}
|
||||
if($this->sandbox->overwrite_sandboxed_string_funcs && in_array($name, PHPSandbox::$sandboxed_string_funcs)){
|
||||
$args = $node->args;
|
||||
return new Node\Expr\MethodCall(new Node\Expr\StaticCall(new Node\Name\FullyQualified("CloudObjects\\PhpMAE\\Sandbox\\CustomizedSandbox"), 'getGlobalSandbox'), '_' . $name, $args, $node->getAttributes());
|
||||
}
|
||||
if($this->sandbox->overwrite_func_get_args && in_array($name, PHPSandbox::$arg_funcs)){
|
||||
if($name == 'func_get_arg'){
|
||||
$index = new Node\Arg(new Node\Scalar\LNumber(0));
|
||||
if(isset($node->args[0]) && $node->args[0] instanceof Node\Arg){
|
||||
$index = $node->args[0];
|
||||
}
|
||||
return new Node\Expr\MethodCall(new Node\Expr\StaticCall(new Node\Name\FullyQualified("CloudObjects\\PhpMAE\\Sandbox\\CustomizedSandbox"), 'getGlobalSandbox'), '_' . $name, [new Node\Arg(new Node\Expr\FuncCall(new Node\Name(['func_get_args']))), $index], $node->getAttributes());
|
||||
}
|
||||
return new Node\Expr\MethodCall(new Node\Expr\StaticCall(new Node\Name\FullyQualified("CloudObjects\\PhpMAE\\Sandbox\\CustomizedSandbox"), 'getGlobalSandbox'), '_' . $name, [new Node\Arg(new Node\Expr\FuncCall(new Node\Name(['func_get_args'])))], $node->getAttributes());
|
||||
}
|
||||
} else {
|
||||
return new Node\Expr\Ternary(
|
||||
new Node\Expr\MethodCall(new Node\Expr\StaticCall(new Node\Name\FullyQualified("CloudObjects\\PhpMAE\\Sandbox\\CustomizedSandbox"), 'getGlobalSandbox'), 'check_func', [new Node\Arg($node->name)], $node->getAttributes()),
|
||||
$node,
|
||||
new Node\Expr\ConstFetch(new Node\Name('null'))
|
||||
);
|
||||
}
|
||||
} else if($node instanceof Node\Stmt\Function_){
|
||||
if(!$this->sandbox->allow_functions){
|
||||
$this->sandbox->validationError("Sandboxed code attempted to define function!", Error::DEFINE_FUNC_ERROR, $node);
|
||||
}
|
||||
if(!$this->sandbox->checkKeyword('function')){
|
||||
$this->sandbox->validationError("Keyword failed custom validation!", Error::VALID_KEYWORD_ERROR, $node, 'function');
|
||||
}
|
||||
if(!$node->name){
|
||||
$this->sandbox->validationError("Sandboxed code attempted to define unnamed function!", Error::DEFINE_FUNC_ERROR, $node, '');
|
||||
}
|
||||
if($this->sandbox->isDefinedFunc($node->name)){
|
||||
$this->sandbox->validationError("Sandboxed code attempted to redefine function!", Error::DEFINE_FUNC_ERROR, $node, $node->name);
|
||||
}
|
||||
if($node->byRef && !$this->sandbox->allow_references){
|
||||
$this->sandbox->validationError("Sandboxed code attempted to define function return by reference!", Error::BYREF_ERROR, $node);
|
||||
}
|
||||
} else if($node instanceof Node\Expr\Closure){
|
||||
if(!$this->sandbox->allow_closures){
|
||||
$this->sandbox->validationError("Sandboxed code attempted to create a closure!", Error::CLOSURE_ERROR, $node);
|
||||
}
|
||||
} else if($node instanceof Node\Stmt\Class_){
|
||||
if(!$this->sandbox->allow_classes){
|
||||
$this->sandbox->validationError("Sandboxed code attempted to define class!", Error::DEFINE_CLASS_ERROR, $node);
|
||||
}
|
||||
if(!$this->sandbox->checkKeyword('class')){
|
||||
$this->sandbox->validationError("Keyword failed custom validation!", Error::VALID_KEYWORD_ERROR, $node, 'class');
|
||||
}
|
||||
if(!$node->name){
|
||||
$this->sandbox->validationError("Sandboxed code attempted to define unnamed class!", Error::DEFINE_CLASS_ERROR, $node, '');
|
||||
}
|
||||
if(!$this->sandbox->checkClass($node->name)){
|
||||
$this->sandbox->validationError("Class failed custom validation!", Error::VALID_CLASS_ERROR, $node, $node->name);
|
||||
}
|
||||
if($node->extends instanceof Node\Name){
|
||||
if(!$this->sandbox->checkKeyword('extends')){
|
||||
$this->sandbox->validationError("Keyword failed custom validation!", Error::VALID_KEYWORD_ERROR, $node, 'extends');
|
||||
}
|
||||
if(!$node->extends->toString()){
|
||||
$this->sandbox->validationError("Sandboxed code attempted to extend unnamed class!", Error::DEFINE_CLASS_ERROR, $node, '');
|
||||
}
|
||||
if(!$this->sandbox->checkClass($node->extends->toString(), true)){
|
||||
$this->sandbox->validationError("Class extension failed custom validation!", Error::VALID_CLASS_ERROR, $node, $node->extends->toString());
|
||||
}
|
||||
}
|
||||
if(is_array($node->implements)){
|
||||
if(!$this->sandbox->checkKeyword('implements')){
|
||||
$this->sandbox->validationError("Keyword failed custom validation!", Error::VALID_KEYWORD_ERROR, $node, 'implements');
|
||||
}
|
||||
foreach($node->implements as $implement){
|
||||
/**
|
||||
* @var Node\Name $implement
|
||||
*/
|
||||
if(!$implement->toString()){
|
||||
$this->sandbox->validationError("Sandboxed code attempted to implement unnamed interface!", Error::DEFINE_INTERFACE_ERROR, $node, '');
|
||||
}
|
||||
if(!$this->sandbox->checkInterface($implement->toString())){
|
||||
$this->sandbox->validationError("Interface failed custom validation!", Error::VALID_INTERFACE_ERROR, $node, $implement->toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if($node instanceof Node\Stmt\Interface_){
|
||||
if(!$this->sandbox->allow_interfaces){
|
||||
$this->sandbox->validationError("Sandboxed code attempted to define interface!", Error::DEFINE_INTERFACE_ERROR, $node);
|
||||
}
|
||||
if(!$this->sandbox->checkKeyword('interface')){
|
||||
$this->sandbox->validationError("Keyword failed custom validation!", Error::VALID_KEYWORD_ERROR, $node, 'interface');
|
||||
}
|
||||
if(!$node->name){
|
||||
$this->sandbox->validationError("Sandboxed code attempted to define unnamed interface!", Error::DEFINE_INTERFACE_ERROR, $node, '');
|
||||
}
|
||||
if(!$this->sandbox->checkInterface($node->name)){
|
||||
$this->sandbox->validationError("Interface failed custom validation!", Error::VALID_INTERFACE_ERROR, $node, $node->name);
|
||||
}
|
||||
} else if($node instanceof Node\Stmt\Trait_){
|
||||
if(!$this->sandbox->allow_traits){
|
||||
$this->sandbox->validationError("Sandboxed code attempted to define trait!", Error::DEFINE_TRAIT_ERROR, $node);
|
||||
}
|
||||
if(!$this->sandbox->checkKeyword('trait')){
|
||||
$this->sandbox->validationError("Keyword failed custom validation!", Error::VALID_KEYWORD_ERROR, $node, 'trait');
|
||||
}
|
||||
if(!$node->name){
|
||||
$this->sandbox->validationError("Sandboxed code attempted to define unnamed trait!", Error::DEFINE_TRAIT_ERROR, $node, '');
|
||||
}
|
||||
if(!$this->sandbox->checkTrait($node->name)){
|
||||
$this->sandbox->validationError("Trait failed custom validation!", Error::VALID_TRAIT_ERROR, $node, $node->name);
|
||||
}
|
||||
} else if($node instanceof Node\Stmt\TraitUse){
|
||||
if(!$this->sandbox->checkKeyword('use')){
|
||||
$this->sandbox->validationError("Keyword failed custom validation!", Error::VALID_KEYWORD_ERROR, $node, 'use');
|
||||
}
|
||||
if(is_array($node->traits)){
|
||||
foreach($node->traits as $trait){
|
||||
/**
|
||||
* @var Node\Name $trait
|
||||
*/
|
||||
if(!$trait->toString()){
|
||||
$this->sandbox->validationError("Sandboxed code attempted to use unnamed trait!", Error::DEFINE_TRAIT_ERROR, $node, '');
|
||||
}
|
||||
if(!$this->sandbox->checkTrait($trait->toString())){
|
||||
$this->sandbox->validationError("Trait failed custom validation!", Error::VALID_TRAIT_ERROR, $node, $trait->toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if($node instanceof Node\Expr\Yield_){
|
||||
if(!$this->sandbox->allow_generators){
|
||||
$this->sandbox->validationError("Sandboxed code attempted to create a generator!", Error::GENERATOR_ERROR, $node);
|
||||
}
|
||||
if(!$this->sandbox->checkKeyword('yield')){
|
||||
$this->sandbox->validationError("Keyword failed custom validation!", Error::VALID_KEYWORD_ERROR, $node, 'yield');
|
||||
}
|
||||
} else if($node instanceof Node\Stmt\Global_){
|
||||
if(!$this->sandbox->allow_globals){
|
||||
$this->sandbox->validationError("Sandboxed code attempted to use global keyword!", Error::GLOBALS_ERROR, $node);
|
||||
}
|
||||
if(!$this->sandbox->checkKeyword('global')){
|
||||
$this->sandbox->validationError("Keyword failed custom validation!", Error::VALID_KEYWORD_ERROR, $node, 'global');
|
||||
}
|
||||
foreach($node->vars as $var){
|
||||
/**
|
||||
* @var Node\Expr\Variable $var
|
||||
*/
|
||||
if($var instanceof Node\Expr\Variable){
|
||||
if(!$this->sandbox->checkGlobal($var->name)){
|
||||
$this->sandbox->validationError("Global failed custom validation!", Error::VALID_GLOBAL_ERROR, $node, $var->name);
|
||||
}
|
||||
} else {
|
||||
$this->sandbox->validationError("Sandboxed code attempted to pass non-variable to global keyword!", Error::DEFINE_GLOBAL_ERROR, $node);
|
||||
}
|
||||
}
|
||||
} else if($node instanceof Node\Expr\Variable){
|
||||
if(!is_string($node->name)){
|
||||
$this->sandbox->validationError("Sandboxed code attempted dynamically-named variable call!", Error::DYNAMIC_VAR_ERROR, $node);
|
||||
}
|
||||
if($node->name == $this->sandbox->name){
|
||||
$this->sandbox->validationError("Sandboxed code attempted to access the PHPSandbox instance!", Error::SANDBOX_ACCESS_ERROR, $node);
|
||||
}
|
||||
if(in_array($node->name, PHPSandbox::$superglobals)){
|
||||
if(!$this->sandbox->checkSuperglobal($node->name)){
|
||||
$this->sandbox->validationError("Superglobal failed custom validation!", Error::VALID_SUPERGLOBAL_ERROR, $node, $node->name);
|
||||
}
|
||||
if($this->sandbox->overwrite_superglobals){
|
||||
return new Node\Expr\MethodCall(new Node\Expr\StaticCall(new Node\Name\FullyQualified("CloudObjects\\PhpMAE\\Sandbox\\CustomizedSandbox"), 'getGlobalSandbox'), '_get_superglobal', [new Node\Arg(new Node\Scalar\String_($node->name))], $node->getAttributes());
|
||||
}
|
||||
} else {
|
||||
if(!$this->sandbox->checkVar($node->name)){
|
||||
$this->sandbox->validationError("Variable failed custom validation!", Error::VALID_VAR_ERROR, $node, $node->name);
|
||||
}
|
||||
}
|
||||
} else if($node instanceof Node\Stmt\StaticVar){
|
||||
if(!$this->sandbox->allow_static_variables){
|
||||
$this->sandbox->validationError("Sandboxed code attempted to create static variable!", Error::STATIC_VAR_ERROR, $node);
|
||||
}
|
||||
if(!is_string($node->name)){
|
||||
$this->sandbox->validationError("Sandboxed code attempted dynamically-named static variable call!", Error::DYNAMIC_STATIC_VAR_ERROR, $node);
|
||||
}
|
||||
if(!$this->sandbox->checkVar($node->name)){
|
||||
$this->sandbox->validationError("Variable failed custom validation!", Error::VALID_VAR_ERROR, $node, $node->name);
|
||||
}
|
||||
if($node->default instanceof Node\Expr\New_){
|
||||
$node->default = $node->default->args[0];
|
||||
}
|
||||
} else if($node instanceof Node\Stmt\Const_){
|
||||
$this->sandbox->validationError("Sandboxed code cannot use const keyword in the global scope!", Error::GLOBAL_CONST_ERROR, $node);
|
||||
} else if($node instanceof Node\Expr\ConstFetch){
|
||||
if(!$node->name instanceof Node\Name){
|
||||
$this->sandbox->validationError("Sandboxed code attempted dynamically-named constant call!", Error::DYNAMIC_CONST_ERROR, $node);
|
||||
}
|
||||
if(!$this->sandbox->checkConst($node->name->toString())){
|
||||
$this->sandbox->validationError("Constant failed custom validation!", Error::VALID_CONST_ERROR, $node, $node->name->toString());
|
||||
}
|
||||
} else if($node instanceof Node\Expr\ClassConstFetch || $node instanceof Node\Expr\StaticCall || $node instanceof Node\Expr\StaticPropertyFetch){
|
||||
$class = $node->class;
|
||||
if(!$class instanceof Node\Name){
|
||||
$this->sandbox->validationError("Sandboxed code attempted dynamically-named class call!", Error::DYNAMIC_CLASS_ERROR, $node);
|
||||
}
|
||||
if($this->sandbox->isDefinedClass($class)){
|
||||
$node->class = new Node\Name($this->sandbox->getDefinedClass($class));
|
||||
}
|
||||
/**
|
||||
* @var Node\Name $class
|
||||
*/
|
||||
if(!$this->sandbox->checkClass($class->toString())){
|
||||
$this->sandbox->validationError("Class constant failed custom validation!", Error::VALID_CLASS_ERROR, $node, $class->toString());
|
||||
}
|
||||
return $node;
|
||||
} else if($node instanceof Node\Param && $node->type instanceof Node\Name){
|
||||
$class = $node->type->toString();
|
||||
if($this->sandbox->isDefinedClass($class)){
|
||||
$node->type = new Node\Name($this->sandbox->getDefinedClass($class));
|
||||
}
|
||||
return $node;
|
||||
} else if($node instanceof Node\Expr\New_){
|
||||
if(!$this->sandbox->allow_objects){
|
||||
$this->sandbox->validationError("Sandboxed code attempted to create object!", Error::CREATE_OBJECT_ERROR, $node);
|
||||
}
|
||||
if(!$this->sandbox->checkKeyword('new')){
|
||||
$this->sandbox->validationError("Keyword failed custom validation!", Error::VALID_KEYWORD_ERROR, $node, 'new');
|
||||
}
|
||||
if(!$node->class instanceof Node\Name){
|
||||
$this->sandbox->validationError("Sandboxed code attempted dynamically-named class call!", Error::DYNAMIC_CLASS_ERROR, $node);
|
||||
}
|
||||
$class = $node->class->toString();
|
||||
if($this->sandbox->isDefinedClass($class)){
|
||||
$node->class = new Node\Name($this->sandbox->getDefinedClass($class));
|
||||
}
|
||||
$this->sandbox->checkType($class);
|
||||
return $node;
|
||||
} else if($node instanceof Node\Expr\ErrorSuppress){
|
||||
if(!$this->sandbox->allow_error_suppressing){
|
||||
$this->sandbox->validationError("Sandboxed code attempted to suppress error!", Error::ERROR_SUPPRESS_ERROR, $node);
|
||||
}
|
||||
} else if($node instanceof Node\Expr\AssignRef){
|
||||
if(!$this->sandbox->allow_references){
|
||||
$this->sandbox->validationError("Sandboxed code attempted to assign by reference!", Error::BYREF_ERROR, $node);
|
||||
}
|
||||
} else if($node instanceof Node\Stmt\HaltCompiler){
|
||||
if(!$this->sandbox->allow_halting){
|
||||
$this->sandbox->validationError("Sandboxed code attempted to halt compiler!", Error::HALT_ERROR, $node);
|
||||
}
|
||||
if(!$this->sandbox->checkKeyword('halt')){
|
||||
$this->sandbox->validationError("Keyword failed custom validation!", Error::VALID_KEYWORD_ERROR, $node, 'halt');
|
||||
}
|
||||
} else if($node instanceof Node\Stmt\Namespace_){
|
||||
if(!$this->sandbox->allow_namespaces){
|
||||
$this->sandbox->validationError("Sandboxed code attempted to define namespace!", Error::DEFINE_NAMESPACE_ERROR, $node);
|
||||
}
|
||||
if(!$this->sandbox->checkKeyword('namespace')){
|
||||
$this->sandbox->validationError("Keyword failed custom validation!", Error::VALID_KEYWORD_ERROR, $node, 'namespace');
|
||||
}
|
||||
if($node->name instanceof Node\Name){
|
||||
$namespace = $node->name->toString();
|
||||
$this->sandbox->checkNamespace($namespace);
|
||||
if(!$this->sandbox->isDefinedNamespace($namespace)){
|
||||
$this->sandbox->defineNamespace($namespace);
|
||||
}
|
||||
} else {
|
||||
$this->sandbox->validationError("Sandboxed code attempted use invalid namespace!", Error::DEFINE_NAMESPACE_ERROR, $node);
|
||||
}
|
||||
return $node->stmts;
|
||||
} else if($node instanceof Node\Stmt\Use_){
|
||||
if(!$this->sandbox->allow_aliases){
|
||||
$this->sandbox->validationError("Sandboxed code attempted to use namespace and/or alias!", Error::DEFINE_ALIAS_ERROR, $node);
|
||||
}
|
||||
if(!$this->sandbox->checkKeyword('use')){
|
||||
$this->sandbox->validationError("Keyword failed custom validation!", Error::VALID_KEYWORD_ERROR, $node, 'use');
|
||||
}
|
||||
foreach($node->uses as $use){
|
||||
/**
|
||||
* @var Node\Stmt\UseUse $use
|
||||
*/
|
||||
if($use instanceof Node\Stmt\UseUse && $use->name instanceof Node\Name && (is_string($use->alias) || is_null($use->alias))){
|
||||
$this->sandbox->checkAlias($use->name->toString());
|
||||
if($use->alias){
|
||||
if(!$this->sandbox->checkKeyword('as')){
|
||||
$this->sandbox->validationError("Keyword failed custom validation!", Error::VALID_KEYWORD_ERROR, $node, 'as');
|
||||
}
|
||||
}
|
||||
$this->sandbox->defineAlias($use->name->toString(), $use->alias);
|
||||
} else {
|
||||
$this->sandbox->validationError("Sandboxed code attempted use invalid namespace or alias!", Error::DEFINE_ALIAS_ERROR, $node);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} else if($node instanceof Node\Expr\ShellExec){
|
||||
if($this->sandbox->isDefinedFunc('shell_exec')){
|
||||
$args = [
|
||||
new Node\Arg(new Node\Scalar\String_('shell_exec')),
|
||||
new Node\Arg(new Node\Scalar\String_(implode('', $node->parts)))
|
||||
];
|
||||
return new Node\Expr\MethodCall(new Node\Expr\StaticCall(new Node\Name\FullyQualified("CloudObjects\\PhpMAE\\Sandbox\\CustomizedSandbox"), 'getGlobalSandbox'), 'call_func', $args, $node->getAttributes());
|
||||
}
|
||||
if($this->sandbox->hasWhitelistedFuncs()){
|
||||
if(!$this->sandbox->isWhitelistedFunc('shell_exec')){
|
||||
$this->sandbox->validationError("Sandboxed code attempted to use shell execution backticks when the shell_exec function is not whitelisted!", Error::BACKTICKS_ERROR, $node);
|
||||
}
|
||||
} else if($this->sandbox->hasBlacklistedFuncs() && $this->sandbox->isBlacklistedFunc('shell_exec')){
|
||||
$this->sandbox->validationError("Sandboxed code attempted to use shell execution backticks when the shell_exec function is blacklisted!", Error::BACKTICKS_ERROR, $node);
|
||||
}
|
||||
if(!$this->sandbox->allow_backticks){
|
||||
$this->sandbox->validationError("Sandboxed code attempted to use shell execution backticks!", Error::BACKTICKS_ERROR, $node);
|
||||
}
|
||||
} else if($name = $this->isMagicConst($node)){
|
||||
if(!$this->sandbox->checkMagicConst($name)){
|
||||
$this->sandbox->validationError("Magic constant failed custom validation!", Error::VALID_MAGIC_CONST_ERROR, $node, $name);
|
||||
}
|
||||
if($this->sandbox->isDefinedMagicConst($name)){
|
||||
return new Node\Expr\MethodCall(new Node\Expr\StaticCall(new Node\Name\FullyQualified("CloudObjects\\PhpMAE\\Sandbox\\CustomizedSandbox"), 'getGlobalSandbox'), '_get_magic_const', [new Node\Arg(new Node\Scalar\String_($name))], $node->getAttributes());
|
||||
}
|
||||
} else if($name = $this->isKeyword($node)){
|
||||
if(!$this->sandbox->checkKeyword($name)){
|
||||
$this->sandbox->validationError("Keyword failed custom validation!", Error::VALID_KEYWORD_ERROR, $node, $name);
|
||||
}
|
||||
if($node instanceof Node\Expr\Include_ && !$this->sandbox->allow_includes){
|
||||
$this->sandbox->validationError("Sandboxed code attempted to include files!", Error::INCLUDE_ERROR, $node, $name);
|
||||
} else if($node instanceof Node\Expr\Include_ &&
|
||||
(
|
||||
($node->type == Node\Expr\Include_::TYPE_INCLUDE && $this->sandbox->isDefinedFunc('include'))
|
||||
|| ($node->type == Node\Expr\Include_::TYPE_INCLUDE_ONCE && $this->sandbox->isDefinedFunc('include_once'))
|
||||
|| ($node->type == Node\Expr\Include_::TYPE_REQUIRE && $this->sandbox->isDefinedFunc('require'))
|
||||
|| ($node->type == Node\Expr\Include_::TYPE_REQUIRE_ONCE && $this->sandbox->isDefinedFunc('require_once'))
|
||||
)){
|
||||
return new Node\Expr\MethodCall(new Node\Expr\StaticCall(new Node\Name\FullyQualified("CloudObjects\\PhpMAE\\Sandbox\\CustomizedSandbox"), 'getGlobalSandbox'), 'call_func', [new Node\Arg(new Node\Scalar\String_($name)), new Node\Arg($node->expr)], $node->getAttributes());
|
||||
} else if($node instanceof Node\Expr\Include_ && $this->sandbox->sandbox_includes){
|
||||
switch($node->type){
|
||||
case Node\Expr\Include_::TYPE_INCLUDE_ONCE:
|
||||
return new Node\Expr\MethodCall(new Node\Expr\StaticCall(new Node\Name\FullyQualified("CloudObjects\\PhpMAE\\Sandbox\\CustomizedSandbox"), 'getGlobalSandbox'), '_include_once', [new Node\Arg($node->expr)], $node->getAttributes());
|
||||
break;
|
||||
case Node\Expr\Include_::TYPE_REQUIRE:
|
||||
return new Node\Expr\MethodCall(new Node\Expr\StaticCall(new Node\Name\FullyQualified("CloudObjects\\PhpMAE\\Sandbox\\CustomizedSandbox"), 'getGlobalSandbox'), '_require', [new Node\Arg($node->expr)], $node->getAttributes());
|
||||
break;
|
||||
case Node\Expr\Include_::TYPE_REQUIRE_ONCE:
|
||||
return new Node\Expr\MethodCall(new Node\Expr\StaticCall(new Node\Name\FullyQualified("CloudObjects\\PhpMAE\\Sandbox\\CustomizedSandbox"), 'getGlobalSandbox'), '_require_once', [new Node\Arg($node->expr)], $node->getAttributes());
|
||||
break;
|
||||
case Node\Expr\Include_::TYPE_INCLUDE:
|
||||
default:
|
||||
return new Node\Expr\MethodCall(new Node\Expr\StaticCall(new Node\Name\FullyQualified("CloudObjects\\PhpMAE\\Sandbox\\CustomizedSandbox"), 'getGlobalSandbox'), '_include', [new Node\Arg($node->expr)], $node->getAttributes());
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if($name = $this->isOperator($node)){
|
||||
if(!$this->sandbox->checkOperator($name)){
|
||||
$this->sandbox->validationError("Operator failed custom validation!", Error::VALID_OPERATOR_ERROR, $node, $name);
|
||||
}
|
||||
} else if($name = $this->isPrimitive($node)){
|
||||
if(!$this->sandbox->checkPrimitive($name)){
|
||||
$this->sandbox->validationError("Primitive failed custom validation!", Error::VALID_PRIMITIVE_ERROR, $node, $name);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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\PhpMAE\Sandbox;
|
||||
|
||||
use CloudObjects\PhpMAE\Exceptions\PhpMAEException;
|
||||
|
||||
class FunctionExecutor {
|
||||
|
||||
private static $trusted = false;
|
||||
private static $packageWhitelist = [ 'function_exists', 'extension_loaded',
|
||||
'defined', 'ini_get', 'is_callable', 'class_exists', 'get_called_class',
|
||||
'stream_for' ];
|
||||
|
||||
public static function setTrusted(bool $trusted) {
|
||||
self::$trusted = $trusted;
|
||||
}
|
||||
|
||||
public static function execute() {
|
||||
$call = func_get_args();
|
||||
if (self::$trusted == true || in_array($call[0], self::$packageWhitelist))
|
||||
return call_user_func_array($call[0], array_slice($call, 1));
|
||||
else
|
||||
throw new PhpMAEException("A package attempted to call the non-whitelisted PHP function ".$call[0]."()!");
|
||||
}
|
||||
|
||||
public static function returnNull() {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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\PhpMAE\Sandbox;
|
||||
|
||||
use PhpParser\Node, PhpParser\NodeVisitorAbstract;
|
||||
use PHPSandbox\PHPSandbox, PHPSandbox\Error;
|
||||
|
||||
class FunctionExecutorWrapperVisitor extends NodeVisitorAbstract {
|
||||
|
||||
private $sandbox;
|
||||
|
||||
public function __construct(PHPSandbox $sandbox) {
|
||||
$this->sandbox = $sandbox;
|
||||
}
|
||||
|
||||
public function leaveNode(Node $node){
|
||||
if ($node instanceof Node\Expr\FuncCall) {
|
||||
if($node->name instanceof Node\Name) {
|
||||
$name = strtolower($node->name->toString());
|
||||
try {
|
||||
$this->sandbox->checkFunc($name);
|
||||
} catch (\Exception $e) {
|
||||
return new Node\Expr\StaticCall(
|
||||
new Node\Name\FullyQualified("CloudObjects\\PhpMAE\\Sandbox\\FunctionExecutor"), 'execute',
|
||||
array_merge([ new Node\Scalar\String_($name) ], $node->args),
|
||||
$node->getAttributes());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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\PhpMAE;
|
||||
|
||||
use Defuse\Crypto\Key, Defuse\Crypto\Crypto;
|
||||
use Symfony\Component\HttpFoundation\Request, Symfony\Component\HttpFoundation\Response,
|
||||
Symfony\Component\HttpFoundation\Cookie;
|
||||
|
||||
/**
|
||||
* The session identifies an individual over multiple requests and allows
|
||||
* storing encrypted information in a cookie.
|
||||
*/
|
||||
class Session implements \ArrayAccess {
|
||||
|
||||
private $id;
|
||||
private $key;
|
||||
private $data;
|
||||
|
||||
public static function createFromRequestWithKey(Request $request, $keyString) {
|
||||
$session = new Session;
|
||||
|
||||
if ($request->cookies->has('session')) {
|
||||
try {
|
||||
// attempt to restore session from encrypted data
|
||||
$key = Key::loadFromAsciiSafeString($keyString);
|
||||
$sessionContent = json_decode(Crypto::decrypt($request->cookies->get('session'), $key), true);
|
||||
if (!isset($sessionContent['id']) || !isset($sessionContent['data']))
|
||||
throw new \Exception;
|
||||
$session->id = $sessionContent['id'];
|
||||
$session->data = $sessionContent['data'];
|
||||
} catch (\Exception $e) {
|
||||
// session could not be restored, create a new one anonymously
|
||||
$session->id = uniqid();
|
||||
$session->data = [];
|
||||
}
|
||||
} else {
|
||||
// no session found, create one
|
||||
$session->id = uniqid();
|
||||
$session->data = [];
|
||||
}
|
||||
|
||||
$session->key = $keyString;
|
||||
|
||||
return $session;
|
||||
}
|
||||
|
||||
public function persistToResponse(Response $response) {
|
||||
$key = Key::loadFromAsciiSafeString($this->key);
|
||||
$sessionContent = json_encode([
|
||||
'id' => $this->id,
|
||||
'data' => $this->data
|
||||
]);
|
||||
$response->headers->setCookie(new Cookie('session',
|
||||
Crypto::encrypt($sessionContent, $key),
|
||||
new \DateTime('1 hour')));
|
||||
}
|
||||
|
||||
public function getId() {
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function offsetExists($offset) {
|
||||
return isset($this->data[$offset]);
|
||||
}
|
||||
|
||||
public function offsetGet($offset) {
|
||||
return $this->data[$offset];
|
||||
}
|
||||
|
||||
public function offsetSet($offset, $value) {
|
||||
$this->data[$offset] = $value;
|
||||
}
|
||||
|
||||
public function offsetUnset($offset) {
|
||||
unset($this->data[$offset]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<?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\PhpMAE;
|
||||
|
||||
use DI\ContainerBuilder;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use GuzzleHttp\Client;
|
||||
|
||||
class TestEnvironmentManager {
|
||||
|
||||
private static function getFilename() {
|
||||
return getenv('HOME').DIRECTORY_SEPARATOR.'.phpmae';
|
||||
}
|
||||
|
||||
public static function getContainer() {
|
||||
$definitions = [];
|
||||
|
||||
if (file_exists(self::getFilename())) {
|
||||
$data = json_decode(file_get_contents(self::getFilename()), true);
|
||||
if (isset($data['testenv_url'])) {
|
||||
$definitions['testenv.url'] = $data['testenv_url'];
|
||||
$definitions['testenv.client'] = \DI\factory(function (ContainerInterface $c) {
|
||||
return new Client([ 'base_uri' => $c->get('testenv.url') ]);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$containerBuilder = new ContainerBuilder;
|
||||
$containerBuilder->addDefinitions($definitions);
|
||||
return $containerBuilder->build();
|
||||
|
||||
}
|
||||
|
||||
public static function setTestEnvironment($url) {
|
||||
file_put_contents(self::getFileName(), json_encode(array(
|
||||
'testenv_url' => $url
|
||||
)));
|
||||
}
|
||||
|
||||
public static function unsetTestEnvironment() {
|
||||
file_put_contents(self::getFileName(), json_encode(array()));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?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\PhpMAE;
|
||||
|
||||
class TwigTemplate {
|
||||
|
||||
private $key;
|
||||
private $environment;
|
||||
|
||||
public function __construct($key, $content, $cachePath) {
|
||||
$this->key = $key;
|
||||
$this->environment = new \Twig_Environment(
|
||||
new \Twig_Loader_Array([ $key => $content ]),
|
||||
[ 'cache' => $cachePath ]
|
||||
);
|
||||
}
|
||||
|
||||
public function render($context) {
|
||||
return $this->environment->render($this->key, $context);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?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\PhpMAE;
|
||||
|
||||
class TwigTemplateFactory {
|
||||
|
||||
private $cachePath;
|
||||
|
||||
public function __construct($cachePath) {
|
||||
$this->cachePath = $cachePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Twig template.
|
||||
*
|
||||
* @param string $string The template content.
|
||||
* @param string $id An identifier that is used to cache the compiled template.
|
||||
* If not provided, a hash of the template content is used instead.
|
||||
*/
|
||||
public function fromString(string $string, string $id = null) {
|
||||
if ($id == null) $id = md5($string);
|
||||
return new TwigTemplate($id, $string, $this->cachePath);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<?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\PhpMAE;
|
||||
|
||||
use ML\IRI\IRI;
|
||||
use ML\JsonLD\Node;
|
||||
use CloudObjects\SDK\NodeReader;
|
||||
|
||||
class TypeChecker {
|
||||
|
||||
private static $reader;
|
||||
|
||||
public static function isType(Node $object, $typeString) {
|
||||
if (!isset(self::$reader))
|
||||
self::$reader = new NodeReader([ 'prefixes' => [
|
||||
'phpmae' => 'coid://phpmae.dev/'
|
||||
]]);
|
||||
|
||||
return self::$reader->hasType($object, $typeString);
|
||||
}
|
||||
|
||||
public static function isClass(Node $object) {
|
||||
return self::isType($object, 'phpmae:Class')
|
||||
|| self::isType($object, 'phpmae:HTTPInvokableClass');
|
||||
}
|
||||
|
||||
public static function isInterface(Node $object) {
|
||||
return self::isType($object, 'phpmae:Interface');
|
||||
}
|
||||
|
||||
public static function getAdditionalTypes(Node $object) {
|
||||
$types = $object->getType();
|
||||
if (!is_array($types))
|
||||
return [];
|
||||
|
||||
$coids = [];
|
||||
foreach ($types as $t) {
|
||||
if (in_array($t->getId(), [ 'coid://phpmae.dev/Class',
|
||||
'coid://phpmae.dev/HTTPInvokableClass',
|
||||
'coid://phpmae.dev/Interface',
|
||||
])) continue;
|
||||
$coids[] = new IRI($t->getId());
|
||||
}
|
||||
|
||||
return $coids;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
<?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\PhpMAE;
|
||||
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Slim\Http\Response;
|
||||
use ML\IRI\IRI;
|
||||
use ML\JsonLD\JsonLD;
|
||||
use CloudObjects\Utilities\RDF\Arc2JsonLdConverter;
|
||||
use CloudObjects\SDK\ObjectRetriever;
|
||||
use CloudObjects\PhpMAE\Exceptions\PhpMAEException;
|
||||
|
||||
class UploadController {
|
||||
|
||||
private $container;
|
||||
private $objectRetriever;
|
||||
private $classRepository;
|
||||
|
||||
public function __construct(ContainerInterface $container, ObjectRetriever $objectRetriever,
|
||||
ClassRepository $classRepository) {
|
||||
|
||||
$this->container = $container;
|
||||
$this->objectRetriever = $objectRetriever;
|
||||
$this->classRepository = $classRepository;
|
||||
}
|
||||
|
||||
private function uploadSource(RequestInterface $request) {
|
||||
$object = $this->objectRetriever->get($request->getQueryParam('coid'));
|
||||
if (!$object)
|
||||
throw new PhpMAEException("Unable to retrieve object.");
|
||||
|
||||
$content = (string)$request->getBody();
|
||||
$validator = new ClassValidator;
|
||||
|
||||
try {
|
||||
$validator->validate($content,
|
||||
new IRI($object->getId()),
|
||||
TypeChecker::getAdditionalTypes($object));
|
||||
} catch (\Exception $e) {
|
||||
$response = (new Response(400))->withJson([
|
||||
'error_code' => get_class($e),
|
||||
'error_message' => $e->getMessage()
|
||||
]);
|
||||
return $response;
|
||||
}
|
||||
|
||||
if ($this->classRepository->storeUploadedFile($object, $content))
|
||||
return new Response(201);
|
||||
else
|
||||
return new Response(500);
|
||||
}
|
||||
|
||||
private function uploadConfig(RequestInterface $request) {
|
||||
// Parse RDF/XML config
|
||||
$parser = \ARC2::getRDFXMLParser();
|
||||
$parser->parse('', (string)$request->getBody());
|
||||
$index = $parser->getSimpleIndex(false);
|
||||
|
||||
// Convert to JsonLD as required by the CloudObjects SDK
|
||||
$jsonLdConfig = JsonLD::compact(
|
||||
JsonLD::fromRdf(Arc2JsonLdConverter::indexToQuads($index)));
|
||||
|
||||
// Validate config
|
||||
if (!isset($jsonLdConfig->{'@id'})
|
||||
|| $jsonLdConfig->{'@id'} != $request->getQueryParam('coid')) {
|
||||
throw new PhpMAEException("Uploaded configuration does not correspond to object.");
|
||||
}
|
||||
|
||||
// Store configuration
|
||||
$iri = new IRI($request->getQueryParam('coid'));
|
||||
$path = $this->container->get('uploads_dir')
|
||||
.DIRECTORY_SEPARATOR.'config'
|
||||
.DIRECTORY_SEPARATOR.$iri->getHost()
|
||||
.$iri->getPath();
|
||||
if (!is_dir($path)) mkdir($path, 0777, true);
|
||||
|
||||
file_put_contents($path.DIRECTORY_SEPARATOR.'object.jsonld', json_encode($jsonLdConfig));
|
||||
|
||||
return new Response(201);
|
||||
}
|
||||
|
||||
public function handle(RequestInterface $request) {
|
||||
if (!$this->container->has('uploads') || $this->container->get('uploads') !== true)
|
||||
throw new PhpMAEException("This is not a test environment where uploads are permitted.");
|
||||
|
||||
if ($request->getMethod() != 'PUT')
|
||||
throw new PhpMAEException("Must use PUT for uploading to test environment.");
|
||||
|
||||
switch ($request->getQueryParam('type')) {
|
||||
case "source":
|
||||
return $this->uploadSource($request);
|
||||
break;
|
||||
case "config":
|
||||
return $this->uploadConfig($request);
|
||||
break;
|
||||
default:
|
||||
throw new PhpMAEException("Invalid test environment request.");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user