/** * Gets specified Version for the Script taking into account both scope and authentication token * * @param string $scriptId Numeric identifier of the Script * @param int $versionNumber Script version number * * @param bool $modify optional Flag checking write permissions * * @return ScriptVersion Returns the Script Entity on success * * @throws ApiErrorException */ public function getVersion($scriptId, $versionNumber, $modify = false) { $version = ScriptVersion::findPk($scriptId, $versionNumber); if (!$version) { throw new ApiErrorException(404, ErrorMessage::ERR_OBJECT_NOT_FOUND, "Requested Version either does not exist or is not owned by your environment."); } if (!$this->hasPermissions($version, $modify)) { //Checks entity level write access permissions throw new ApiErrorException(403, ErrorMessage::ERR_PERMISSION_VIOLATION, "Insufficient permissions"); } return $version; }
/** * Forks specified script into new script * * @param string $name New script name * @param Scalr_Account_User $user User performs a fork * @param int $envId Environment of the new script * * @return Script Forked script * @throws \Exception */ public function fork($name, Scalr_Account_User $user, $envId = null) { $script = new static(); $script->name = $name; $script->description = $this->description; $script->os = $this->os; $script->isSync = $this->isSync; $script->allowScriptParameters = $this->allowScriptParameters; $script->timeout = $this->timeout; $script->accountId = $user->getAccountId() ?: NULL; $script->envId = $envId ? $envId : $this->envId; $script->createdById = $user->getId(); $script->createdByEmail = $user->getEmail(); $script->save(); $version = new ScriptVersion(); $version->scriptId = $script->id; $version->changedById = $user->getId(); $version->changedByEmail = $user->getEmail(); $version->content = $this->getLatestVersion()->content; $version->version = 1; $version->save(); return $script; }
/** * @test * * @throws \Scalr\Exception\ModelException */ public function testComplex() { $user = $this->getUser(); $environment = $this->getEnvironment(); $fictionController = new ApiController(); /* @var $script Script */ $script = static::createEntity(new Script(), ['name' => "{$this->uuid}-script", 'description' => "{$this->uuid}-description", 'envId' => $environment->id, 'createdById' => $user->getId()]); //post script version $data = ['body' => '#!cmd']; $response = $this->postVersion($script->id, $data); $this->assertEquals(201, $response->status, $this->printResponseError($response)); $versionNumber = $response->getBody()->data->version; /* @var $version ScriptVersion */ $version = ScriptVersion::findPk($script->id, $versionNumber); $this->assertNotEmpty($version); $this->assertObjectEqualsEntity($data, $version); //post script version already existing $data = ['version' => $version->version, 'body' => '#!/bin/sh']; $response = $this->postVersion($script->id, $data); $this->assertEquals(201, $response->status, $this->printResponseError($response)); $versionNumber = $response->getBody()->data->version; $this->assertNotEquals($data['version'], $versionNumber); //post script with properties that not existing $data = ['foo' => 'bar']; $response = $this->postVersion($script->id, $data); $this->assertErrorMessageContains($response, 400, ErrorMessage::ERR_INVALID_STRUCTURE); //post version to scalr-scoped script /* @var $scalrScript Script */ $scalrScript = static::createEntity(new Script(), ['name' => "{$this->uuid}-script-scalr-scoped", 'description' => "{$this->uuid}-description-scalr-scoped", 'createdById' => $user->getId()]); $data = ['body' => '#!/bin/sh']; $response = $this->postVersion($scalrScript->id, $data); $this->assertErrorMessageContains($response, 403, ErrorMessage::ERR_PERMISSION_VIOLATION); //test script fetch $response = $this->getVersion($script->id, $version->version); $this->assertEquals(200, $response->status, $this->printResponseError($response)); $this->assertObjectEqualsEntity($response->getBody()->data, $version); //test fetch script that doe not exists $response = $this->getVersion($script->id, $script->getLatestVersion()->version + 1); $this->assertErrorMessageContains($response, 404, ErrorMessage::ERR_OBJECT_NOT_FOUND); //modify script version $data = ['body' => '#!/bin/bash']; $response = $this->modifyVersion($script->id, $version->version, $data); $this->assertEquals(200, $response->status, $this->printResponseError($response)); $this->assertObjectEqualsEntity($response->getBody()->data, ScriptVersion::findPk($script->id, $version->version)); //modify property that does not exists $data = ['foo' => 'bar']; $response = $this->modifyVersion($script->id, $version->version, $data); $this->assertErrorMessageContains($response, 400, ErrorMessage::ERR_INVALID_STRUCTURE); //modify properties that not alterable $scriptVersionAdapter = new ScriptVersionAdapter($fictionController); $adapterRules = $scriptVersionAdapter->getRules(); $publicProperties = $adapterRules[BaseAdapter::RULE_TYPE_TO_DATA]; $alterableProperties = $adapterRules[ApiEntityAdapter::RULE_TYPE_ALTERABLE]; $nonAlterableProperties = array_diff(array_values($publicProperties), $alterableProperties); foreach ($nonAlterableProperties as $property) { if (in_array($property, ['id', 'version'])) { continue; } $data = [$property => 'foo']; $response = $this->modifyVersion($script->id, $version->version, $data); $this->assertErrorMessageContains($response, 400, ErrorMessage::ERR_INVALID_STRUCTURE); } //modify script that does not exists $data = ['body' => '#!powershell']; $response = $this->modifyVersion($script->id, $script->getLatestVersion()->version + 1, $data); $this->assertErrorMessageContains($response, 404, ErrorMessage::ERR_OBJECT_NOT_FOUND); //modify Scalr-scoped script version /* @var $scalrVersion ScriptVersion */ $scalrVersion = static::createEntity(new ScriptVersion(), ['scriptId' => $scalrScript->id, 'version' => 2, 'content' => '#!/bin/sh']); //modify Scalr-scoped script version $data = ['body' => '#!cmd']; $response = $this->modifyVersion($scalrScript->id, $scalrVersion->version, $data); $this->assertErrorMessageContains($response, 403, ErrorMessage::ERR_PERMISSION_VIOLATION, 'Insufficient permissions'); /* @var $version ScriptVersion */ $version = static::createEntity(new ScriptVersion(), ['scriptId' => $script->id, 'version' => $script->getLatestVersion()->version + 1, 'content' => '#!foobar']); //test have access to all listed scripts versions $versions = $this->listVersions($script->id); foreach ($versions as $version) { $this->assertTrue(ScriptVersion::findPk($script->id, $version->version)->hasAccessPermissions($user)); } $listUri = static::getUserApiUrl("/scripts/{$script->id}/script-versions/"); //test list script versions filters $filterable = $scriptVersionAdapter->getRules()[ApiEntityAdapter::RULE_TYPE_FILTERABLE]; /* @var $version ScriptVersion */ foreach ($versions as $version) { foreach ($filterable as $property) { $filterValue = $version->{$property}; $listResult = $this->listVersions($script->id, [$property => $filterValue]); if (!static::isRecursivelyEmpty($filterValue)) { foreach ($listResult as $filtered) { $this->assertEquals($filterValue, $filtered->{$property}, "Property '{$property}' mismatch"); } } } $response = $this->getVersion($script->id, $version->version); $this->assertEquals(200, $response->status, $this->printResponseError($response)); $dbScriptVersions = ScriptVersion::findPk($script->id, $version->version); $this->assertObjectEqualsEntity($response->getBody()->data, $dbScriptVersions, $scriptVersionAdapter); } //test invalid filters $response = $this->request($listUri, Request::METHOD_GET, ['foo' => 'bar']); $this->assertErrorMessageContains($response, 400, ErrorMessage::ERR_INVALID_STRUCTURE); //delete script version /* @var $version ScriptVersion */ $version = static::createEntity(new ScriptVersion(), ['scriptId' => $script->id, 'version' => $script->getLatestVersion()->version + 1, 'content' => '#!/bin/sh foobar']); $response = $this->deleteVersion($script->id, $version->version); $this->assertEquals(200, $response->status, $this->printResponseError($response)); //delete scalr-scoped script version $response = $this->deleteVersion($scalrVersion->scriptId, $scalrVersion->version); $this->assertErrorMessageContains($response, 403, ErrorMessage::ERR_PERMISSION_VIOLATION); //delete script version that does not exists $response = $this->deleteVersion($script->id, $script->getLatestVersion()->version + 1); $this->assertErrorMessageContains($response, 404, ErrorMessage::ERR_OBJECT_NOT_FOUND); $scripts = Script::find([['$or' => [['accountId' => $user->accountId], ['accountId' => null]]], ['$or' => [['envId' => $environment->id], ['envId' => null]]]]); foreach ($scripts as $script) { //test have access to all listed scripts versions $versions = $this->listVersions($script->id); foreach ($versions as $version) { $version = ScriptVersion::findPk($script->id, $version->version); $this->assertTrue($version->hasAccessPermissions($user)); } if ($version->getScope() !== ScopeInterface::SCOPE_ENVIRONMENT) { $response = $this->postVersion($version->scriptId, ['body' => '#!/bin/sh' . $this->getTestName()]); $this->assertErrorMessageContains($response, 403, ErrorMessage::ERR_PERMISSION_VIOLATION); } } }
/** * @param int $id * @param string $name * @param string $description * @param int $isSync * @param bool $allowScriptParameters * @param int $envId optional * @param int $timeout optional * @param int $version * @param RawData $content * @param string $tags * @param string $uploadType optional * @param string $uploadUrl optional * @param FileUploadData $uploadFile optional * @param bool $checkScriptParameters optional * @throws Scalr_UI_Exception_NotFound * @throws Scalr_Exception_InsufficientPermissions * @throws Exception */ public function xSaveAction($id, $name, $description, $isSync = 0, $allowScriptParameters = false, $envId = NULL, $timeout = NULL, $version, RawData $content, $tags, $uploadType = NULL, $uploadUrl = NULL, FileUploadData $uploadFile = NULL, $checkScriptParameters = false) { $this->request->restrictAccess('SCRIPTS', 'MANAGE'); $validator = new Validator(); $validator->validate($name, 'name', Validator::NOEMPTY); if ($uploadType && $uploadType == 'URL') { $validator->validate($uploadUrl, 'uploadUrl', Validator::URL); if (!$validator->isValid($this->response)) { return; } } if ($uploadType) { $content = false; if ($uploadType == 'URL') { $content = @file_get_contents($uploadUrl); $validator->validate($content, 'uploadUrl', Validator::NOEMPTY, [], 'Invalid source'); } else { if ($uploadType == 'File') { $content = $uploadFile; $validator->validate($content, 'uploadFile', Validator::NOEMPTY, [], 'Invalid source'); } else { $validator->addError('uploadType', 'Invalid source for script'); } } } $envId = $this->getEnvironmentId(true); $content = str_replace("\r\n", "\n", $content); $tagsResult = []; foreach (explode(',', $tags) as $t) { $t = trim($t); if ($t) { if (!preg_match('/^[a-zA-Z0-9-]{3,10}$/', $t)) { $validator->addError('tags', sprintf('Invalid name for tag: %s', $t)); } $tagsResult[] = $t; } } $tags = $tagsResult; $criteria = []; $criteria[] = ['name' => $name]; if ($id) { $criteria[] = ['id' => ['$ne' => $id]]; } switch ($this->request->getScope()) { case Script::SCOPE_ENVIRONMENT: $criteria[] = ['envId' => $envId]; $criteria[] = ['accountId' => $this->user->getAccountId()]; break; case Script::SCOPE_ACCOUNT: $criteria[] = ['envId' => null]; $criteria[] = ['accountId' => $this->user->getAccountId()]; break; case Script::SCOPE_SCALR: $criteria[] = ['envId' => null]; $criteria[] = ['accountId' => null]; break; } if (Script::findOne($criteria)) { $validator->addError('name', 'Script name must be unique within current scope'); } if (!$validator->isValid($this->response)) { return; } /* @var $script Script */ if ($id) { $script = Script::findPk($id); if (!$script) { throw new Scalr_UI_Exception_NotFound(); } $script->checkPermission($this->user, $envId); if (!$script->accountId && $this->user->getType() != Scalr_Account_User::TYPE_SCALR_ADMIN) { throw new Scalr_Exception_InsufficientPermissions(); } if (!$script->envId && $this->request->getScope() == ScopeInterface::SCOPE_ENVIRONMENT) { throw new Scalr_Exception_InsufficientPermissions(); } } else { $script = new Script(); $script->accountId = $this->user->getAccountId() ?: NULL; $script->createdById = $this->user->getId(); $script->createdByEmail = $this->user->getEmail(); $script->envId = $envId; $version = 1; } //check variables in script content if (!$id && $checkScriptParameters && !$allowScriptParameters) { $scriptHasParameters = Script::hasVariables($content); if (!$scriptHasParameters) { /* @var $scriptVersion ScriptVersion */ foreach ($script->getVersions() as $scriptVersion) { if ($scriptVersion->version != $version) { $scriptHasParameters = Script::hasVariables($scriptVersion->content); if ($scriptHasParameters) { break; } } } } if ($scriptHasParameters) { $this->response->data(['showScriptParametersConfirmation' => true]); $this->response->failure(); return; } } $script->name = $name; $script->description = $description; $script->timeout = $timeout ? $timeout : NULL; $script->isSync = $isSync == 1 ? 1 : 0; $script->allowScriptParameters = $allowScriptParameters ? 1 : 0; $script->os = !strncmp($content, '#!cmd', strlen('#!cmd')) || !strncmp($content, '#!powershell', strlen('#!powershell')) ? Script::OS_WINDOWS : Script::OS_LINUX; $script->save(); $scriptVersion = NULL; if ($version) { $scriptVersion = $script->getVersion($version); } if (!$scriptVersion && $script->getLatestVersion()->content !== $content) { $scriptVersion = new ScriptVersion(); $scriptVersion->scriptId = $script->id; $scriptVersion->version = $script->getLatestVersion()->version + 1; } if ($scriptVersion) { $scriptVersion->changedById = $this->user->getId(); $scriptVersion->changedByEmail = $this->user->getEmail(); $scriptVersion->content = $content; $scriptVersion->save(); } if ($this->user->getAccountId()) { Tag::setTags($tags, $this->user->getAccountId(), Tag::RESOURCE_SCRIPT, $script->id); } $this->response->success('Script successfully saved'); $this->response->data(['script' => array_merge($this->getScript($script), $this->getScriptInfo($script))]); }
/** * Gets ScriptVersion entity * * @param string $criteria search criteria * @param array $params query params * @return ScriptVersion */ protected function getScriptVersion($criteria, $params) { /* @var $sv ScriptVersion */ $sv = ScriptVersion::findOne([['scriptId' => $params['scriptId']], ['version' => $criteria]]); static::toDelete(ScriptVersion::class, [$sv->scriptId, $sv->version]); return $sv; }
/** * {@inheritdoc} * @see \Scalr\Api\DataType\ApiEntityAdapter::validateEntity() */ public function validateEntity($entity) { if (!$entity instanceof RoleScript) { throw new InvalidArgumentException(sprintf("First argument must be instance of Scalr\\Model\\Entity\\RoleScript class")); } if ($entity->id !== null) { if (!RoleScript::findPk($entity->id)) { throw new ApiErrorException(404, ErrorMessage::ERR_OBJECT_NOT_FOUND, sprintf("Could not find out the rule with ID: %d", $entity->id)); } } //Getting the role initiates check permissions $role = $this->controller->getRole($entity->roleId); if (!empty($entity->scriptId)) { if ($entity->version == ScriptVersion::LATEST_SCRIPT_VERSION) { $found = ScriptVersion::findOne([['scriptId' => $entity->scriptId]], null, ['version' => false]); } else { $found = ScriptVersion::findPk($entity->scriptId, $entity->version); } if (empty($found)) { throw new ApiErrorException(404, ErrorMessage::ERR_OBJECT_NOT_FOUND, sprintf("Could not find version %d of the script with ID: %d", $entity->version, $entity->scriptId)); } if (Script::findPk($entity->scriptId)->os == 'windows' && $role->getOs()->family != 'windows') { throw new ApiErrorException(409, ErrorMessage::ERR_OS_MISMATCH, "Script OS family does not match role OS family"); } } if (empty($entity->eventName)) { $entity->eventName = '*'; } else { if ($entity->eventName !== '*') { if (array_key_exists($entity->eventName, array_merge(EVENT_TYPE::getScriptingEventsWithScope(), EventDefinition::getList($this->controller->getUser()->id, $this->controller->getEnvironment()->id))) === false) { throw new ApiErrorException(404, ErrorMessage::ERR_OBJECT_NOT_FOUND, "Could not find out the event '{$entity->eventName}'"); } if ($entity->scriptType == OrchestrationRule::ORCHESTRATION_RULE_TYPE_CHEF && in_array($entity->eventName, EVENT_TYPE::getChefRestrictedEvents())) { throw new ApiErrorException(400, ErrorMessage::ERR_INVALID_VALUE, "Chef can't be used with {$entity->eventName}"); } } } if (!$this->controller->hasPermissions($entity, true)) { //Checks entity level write access permissions throw new ApiErrorException(403, ErrorMessage::ERR_PERMISSION_VIOLATION, "Insufficient permissions"); } }
/** * @param $name * @param \Scalr_Account_User $user * @return Script */ public function fork($name, \Scalr_Account_User $user) { $script = new self(); $script->name = $name; $script->description = $this->description; $script->os = $this->os; $script->isSync = $this->isSync; $script->timeout = $this->timeout; $script->accountId = $user->getAccountId() ? $user->getAccountId() : NULL; $script->envId = $this->envId; $script->createdById = $user->getId(); $script->createdByEmail = $user->getEmail(); $script->save(); $version = new ScriptVersion(); $version->scriptId = $script->id; $version->changedById = $user->getId(); $version->changedByEmail = $user->getEmail(); $version->content = $this->getLatestVersion()->content; $version->version = 1; $version->save(); return $script; }
/** * @param int $id * @param string $name * @param string $description * @param int $isSync * @param int $envId optional * @param int $timeout optional * @param int $version * @param RawData $content * @param string $tags * @param string $uploadType optional * @param string $uploadUrl optional * @param FileUploadData $uploadFile optional * @throws Scalr_UI_Exception_NotFound * @throws Scalr_Exception_InsufficientPermissions * @throws Exception */ public function xSaveAction($id, $name, $description, $isSync = 0, $envId = 2, $timeout = NULL, $version, RawData $content, $tags, $uploadType = NULL, $uploadUrl = NULL, FileUploadData $uploadFile = NULL) { $this->request->restrictAccess(Acl::RESOURCE_ADMINISTRATION_SCRIPTS, Acl::PERM_ADMINISTRATION_SCRIPTS_MANAGE); $validator = new Validator(); $validator->validate($name, 'name', Validator::NOEMPTY); if ($uploadType) { $content = false; if ($uploadType == 'URL') { $content = @file_get_contents($uploadUrl); $validator->validate($content, 'uploadUrl', Validator::NOEMPTY, [], 'Invalid source'); } else { if ($uploadType == 'File') { $content = $uploadFile; $validator->validate($content, 'uploadFile', Validator::NOEMPTY, [], 'Invalid source'); } else { $validator->addError('uploadType', 'Invalid source for script'); } } } $content = str_replace("\r\n", "\n", $content); $tagsResult = []; foreach (explode(',', $tags) as $t) { $t = trim($t); if ($t) { if (!preg_match('/^[a-zA-Z0-9-]{3,10}$/', $t)) { $validator->addError('tags', sprintf('Invalid name for tag: %s', $t)); } $tagsResult[] = $t; } } $tags = $tagsResult; if (!$validator->isValid($this->response)) { return; } /* @var Script $script */ if ($id) { $script = Script::findPk($id); if (!$script) { throw new Scalr_UI_Exception_NotFound(); } $script->checkPermission($this->user, $this->getEnvironmentId(true)); if (!$script->accountId && $this->user->getType() != Scalr_Account_User::TYPE_SCALR_ADMIN) { throw new Scalr_Exception_InsufficientPermissions(); } } else { $script = new Script(); $script->accountId = $this->user->getAccountId() ? $this->user->getAccountId() : NULL; $script->createdById = $this->user->getId(); $script->createdByEmail = $this->user->getEmail(); $version = 1; } if ($this->user->isScalrAdmin()) { $envId = NULL; } else { if (!in_array($envId, array_map(function ($e) { return $e['id']; }, $this->user->getEnvironments()))) { $envId = NULL; } } $script->name = $name; $script->description = $description; $script->timeout = $timeout ? $timeout : NULL; $script->isSync = $isSync == 1 ? 1 : 0; $script->envId = $envId; $script->os = !strncmp($content, '#!cmd', strlen('#!cmd')) || !strncmp($content, '#!powershell', strlen('#!powershell')) ? Script::OS_WINDOWS : Script::OS_LINUX; $script->save(); $scriptVersion = NULL; if ($version) { $scriptVersion = $script->getVersion($version); } if (!$scriptVersion && $script->getLatestVersion()->content !== $content) { $scriptVersion = new ScriptVersion(); $scriptVersion->scriptId = $script->id; $scriptVersion->version = $script->getLatestVersion()->version + 1; } if ($scriptVersion) { $scriptVersion->changedById = $this->user->getId(); $scriptVersion->changedByEmail = $this->user->getEmail(); $scriptVersion->content = $content; $scriptVersion->save(); } if ($this->user->getAccountId()) { Tag::setTags($tags, $this->user->getAccountId(), Tag::RESOURCE_SCRIPT, $script->id); } $this->response->success('Script successfully saved'); }
/** * {@inheritdoc} * @see ApiEntityAdapter::validateEntity() */ public function validateEntity($entity) { /* @var $entity OrchestrationRule */ $entityClass = $this->entityClass; if (!$entity instanceof $entityClass) { throw new InvalidArgumentException(sprintf("First argument must be instance of {$entityClass} class")); } if ($entity->id !== null) { if (!$entityClass::findPk($entity->id)) { throw new ApiErrorException(404, ErrorMessage::ERR_OBJECT_NOT_FOUND, sprintf("Could not find out the rule with ID: %d", $entity->id)); } } if (!empty($entity->scriptId)) { if ($entity->version == ScriptVersion::LATEST_SCRIPT_VERSION) { $found = ScriptVersion::findOne([['scriptId' => $entity->scriptId]], null, ['version' => false]); } else { $found = ScriptVersion::findPk($entity->scriptId, $entity->version); } /* @var $found ScriptVersion */ if (empty($found) || !$found->hasAccessPermissions($this->controller->getUser(), $this->controller->getEnvironment())) { throw new ApiErrorException(404, ErrorMessage::ERR_OBJECT_NOT_FOUND, sprintf("Could not find version %d of the script with ID: %d", $entity->version, $entity->scriptId)); } } if (empty($entity->eventName)) { $entity->eventName = '*'; } else { if ($entity->eventName !== '*') { if (array_key_exists($entity->eventName, array_merge(EVENT_TYPE::getScriptingEventsWithScope(), EventDefinition::getList($this->controller->getUser()->id, $this->controller->getScope() === ScopeInterface::SCOPE_ENVIRONMENT ? $this->controller->getEnvironment()->id : null))) === false) { throw new ApiErrorException(404, ErrorMessage::ERR_OBJECT_NOT_FOUND, "Could not find out the event '{$entity->eventName}'"); } if ($entity->scriptType == OrchestrationRule::ORCHESTRATION_RULE_TYPE_CHEF && in_array($entity->eventName, EVENT_TYPE::getChefRestrictedEvents())) { throw new ApiErrorException(400, ErrorMessage::ERR_INVALID_VALUE, "Chef can't be used with {$entity->eventName}"); } if ($entity->eventName == EVENT_TYPE::BEFORE_INSTANCE_LAUNCH && $entity->target == Script::TARGET_INSTANCE) { throw new ApiErrorException(400, ErrorMessage::ERR_INVALID_VALUE, "Event '{$entity->eventName}' will never be handled by the triggering server"); } } } if (!$this->controller->hasPermissions($entity, true)) { //Checks entity level write access permissions throw new ApiErrorException(403, ErrorMessage::ERR_PERMISSION_VIOLATION, "Insufficient permissions"); } }