/** * Check if given name is used on scalr, account or environment scopes * * @param string $name Role's name to check * @param int $accountId Identifier of account * @param int $envId Identifier of environment * @param int $ignoreRoleId Role id to ignore * @return bool Returns TRUE if a such name has been already used on scalr or account (or environment) scopes */ public static function isNameUsed($name, $accountId, $envId, $ignoreRoleId = null) { $criteria = [['accountId' => null]]; if ($accountId) { if ($envId) { $criteria[] = ['$and' => [['accountId' => $accountId], ['envId' => null]]]; $criteria[] = ['envId' => $envId]; } else { $criteria[] = ['accountId' => $accountId]; } } $criteria = [['name' => $name], ['$or' => $criteria]]; if ($ignoreRoleId) { $criteria[] = ['id' => ['$ne' => $ignoreRoleId]]; } return !!Role::findOne($criteria); }
/** * Gets role from database using User's Environment * * @param int $roleId The identifier of the Role * @param bool $restrictToCurrentScope optional Whether it should additionally check that role corresponds to current scope * * @throws ApiErrorException * @return \Scalr\Model\Entity\Role|null Returns Role entity on success or NULL otherwise */ public function getRole($roleId, $restrictToCurrentScope = false) { $criteria = $this->getDefaultCriteria(); $criteria[] = ['id' => $roleId]; $role = Entity\Role::findOne($criteria); /* @var $role Entity\Role */ if (!$role) { throw new ApiErrorException(404, ErrorMessage::ERR_OBJECT_NOT_FOUND, "The Role either does not exist or isn't in scope for the current Environment."); } //To be over-suspicious check READ access to Role object $this->checkPermissions($role); if ($restrictToCurrentScope && $role->getScope() !== $this->getScope()) { throw new ApiErrorException(403, ErrorMessage::ERR_SCOPE_VIOLATION, "The Role is not either from the {$this->getScope()} scope or owned by your {$this->getScope()}."); } return $role; }
/** * @test * @functional */ public function testRoleGlobalVariables() { $db = \Scalr::getDb(); $testName = str_replace('-', '', $this->getTestName()); $role = Role::findOne([['envId' => static::$testEnvId]]); /* @var $role Role */ $roleId = $role->id; $uri = static::getUserApiUrl("roles/{$roleId}/global-variables"); $variables = null; $declaredNotInRole = null; do { $query = []; if (isset($variables->pagination->next)) { $parts = parse_url($variables->pagination->next); parse_str($parts['query'], $query); } $query[ApiController::QUERY_PARAM_MAX_RESULTS] = 2; $describe = $this->request($uri, Request::METHOD_GET, $query); $this->assertDescribeResponseNotEmpty($describe); $this->assertNotEmpty($describe->getBody()); $variables = $describe->getBody(); $this->assertLessThanOrEqual(2, count($variables->data)); foreach ($variables->data as $variable) { $this->assertVariableObjectNotEmpty($variable); if (empty($declaredNotInRole) && $variable->declaredIn !== ScopeInterface::SCOPE_ROLE) { $declaredNotInRole = $variable->name; } if (strpos($variable->name, $testName) !== false) { $delete = $this->request($uri . '/' . $variable->name, Request::METHOD_DELETE); $this->assertEquals(200, $delete->response->getStatus()); } } } while (!empty($variables->pagination->next)); $this->assertNotNull($declaredNotInRole); $notFoundRoleId = 10 + $db->GetOne("SELECT MAX(r.id) FROM roles r"); $describe = $this->request(static::getUserApiUrl("/roles/{$notFoundRoleId}/global-variables"), Request::METHOD_GET); $this->assertErrorMessageContains($describe, 404, ErrorMessage::ERR_OBJECT_NOT_FOUND, "The Role either does not exist or isn't in scope for the current Environment"); $adminRole = Role::findOne([['envId' => null]]); /* @var $adminRole Role */ $this->assertInstanceOf("Scalr\\Model\\Entity\\Role", $adminRole); $notAccessibleId = $adminRole->id; $this->assertNotEmpty($notAccessibleId); $describe = $this->request(self::getUserApiUrl("/roles/{$notAccessibleId}/global-variables"), Request::METHOD_GET); $this->assertErrorMessageErrorEquals(ErrorMessage::ERR_SCOPE_VIOLATION, $describe); $this->assertErrorMessageStatusEquals(403, $describe); $create = $this->request($uri, Request::METHOD_POST, [], ['invalid' => 'value']); $this->assertErrorMessageContains($create, 400, ErrorMessage::ERR_INVALID_STRUCTURE, 'You are trying to set'); $create = $this->request($uri, Request::METHOD_POST, [], ['name' => 'invalid val--ue']); $this->assertErrorMessageContains($create, 400, ErrorMessage::ERR_INVALID_VALUE, 'Name should contain only letters, numbers and underscores, start with letter and be from 2 to 128 chars long'); $create = $this->request($uri, Request::METHOD_POST, [], ['name' => 'scalr_test']); $this->assertErrorMessageContains($create, 400, ErrorMessage::ERR_INVALID_VALUE, 'prefix is reserved and cannot be used'); //test invalid category name $create = $this->request($uri, Request::METHOD_POST, [], ['name' => 'TestName', 'category' => 'invalid category']); $this->assertErrorMessageContains($create, 400, ErrorMessage::ERR_INVALID_VALUE); $create = $this->request($uri, Request::METHOD_POST); $this->assertErrorMessageContains($create, 400, ErrorMessage::ERR_INVALID_STRUCTURE, 'Invalid body'); $create = $this->request($uri, Request::METHOD_POST, [], ['name' => $testName, 'value' => $testName, 'description' => $testName]); $this->assertEquals(201, $create->response->getStatus()); $this->assertFetchResponseNotEmpty($create); $createBody = $create->getBody(); $this->assertNotEmpty($createBody); $this->assertVariableObjectNotEmpty($createBody->data); $this->assertEquals($testName, $createBody->data->name); $this->assertEquals($testName, $createBody->data->value); $this->assertEquals($testName, $createBody->data->description); $create = $this->request($uri, Request::METHOD_POST, [], ['name' => $testName]); $this->assertErrorMessageContains($create, 409, ErrorMessage::ERR_UNICITY_VIOLATION, 'Variable with name'); $fetch = $this->request($uri . '/' . $testName, Request::METHOD_GET); $this->assertEquals(200, $fetch->response->getStatus()); $this->assertFetchResponseNotEmpty($fetch); $fetchBody = $fetch->getBody(); $this->assertNotEmpty($fetchBody); $this->assertVariableObjectNotEmpty($fetchBody->data); $this->assertEquals($testName, $fetchBody->data->name); $this->assertEquals($testName, $fetchBody->data->value); $modify = $this->request($uri . '/' . $testName, Request::METHOD_PATCH, [], ['value' => '']); $this->assertEquals(200, $modify->response->getStatus()); $this->assertFetchResponseNotEmpty($modify); $modifyBody = $modify->getBody(); $this->assertNotEmpty($modifyBody); $this->assertVariableObjectNotEmpty($modifyBody->data); $this->assertEquals($testName, $modifyBody->data->name); $this->assertEquals('', $modifyBody->data->value); $modify = $this->request($uri . '/' . $testName . 'notFound', Request::METHOD_PATCH, [], ['value' => '']); $this->assertEquals(404, $modify->response->getStatus()); $this->assertErrorMessageErrorEquals(ErrorMessage::ERR_OBJECT_NOT_FOUND, $modify); $modify = $this->request($uri . '/' . $testName, Request::METHOD_PATCH, [], ['name' => '']); $this->assertErrorMessageContains($modify, 400, ErrorMessage::ERR_INVALID_STRUCTURE, 'You are trying to set'); $modify = $this->request($uri . '/' . $declaredNotInRole, Request::METHOD_PATCH, [], ['hidden' => 1]); $this->assertEquals(403, $modify->response->getStatus()); $this->assertErrorMessageErrorEquals(ErrorMessage::ERR_SCOPE_VIOLATION, $modify); $delete = $this->request($uri . '/' . $declaredNotInRole, Request::METHOD_DELETE); $this->assertEquals(403, $delete->response->getStatus()); $this->assertErrorMessageErrorEquals(ErrorMessage::ERR_SCOPE_VIOLATION, $delete); $delete = $this->request($uri . '/' . $testName . 'notfound', Request::METHOD_DELETE); $this->assertEquals(404, $delete->response->getStatus()); $this->assertErrorMessageErrorEquals(ErrorMessage::ERR_OBJECT_NOT_FOUND, $delete); $delete = $this->request($uri . '/' . $testName, Request::METHOD_DELETE); $this->assertEquals(200, $delete->response->getStatus()); }
/** * @test */ public function testComplex() { $user = $this->getUser(); $environment = $this->getEnvironment(); $fictionController = new ApiController(); //foreach iterates through values in order of ads //we need to remove rules first, then - scripts //we initialize data set for removal with non-existing rule $this->ruleToDelete(-1); /* @var $roles Role[] */ $roles = $this->getTestRoles(); /* @var $role Role */ $role = array_shift($roles); /* @var $scalrRole Role */ $scalrRole = Role::findOne([['envId' => null, 'accountId' => null]]); /* @var $script Script */ $script = static::createEntity(new Script(), ['name' => 'test-role-scripts', 'description' => 'test-role-scripts', '']); $isWindows = $role->getOs()->family == 'windows'; /* @var $version ScriptVersion */ $version = static::createEntity(new ScriptVersion(), ['scriptId' => $script->id, 'version' => $script->getLatestVersion()->version + 1, 'content' => $isWindows ? '#!cmd' : '#!/bin/sh']); $script->os = $isWindows ? 'windows' : 'linux'; $script->save(); $scalrRoleScriptData = ['trigger' => ['type' => RoleScriptAdapter::TRIGGER_SINGLE_EVENT, 'event' => ['id' => 'HostInit']], 'target' => ['type' => RoleScriptAdapter::TARGET_TRIGGERING_FARM_ROLE], 'action' => ['actionType' => RoleScriptAdapter::ACTION_SCRIPT, 'scriptVersion' => ['script' => ['id' => $script->id], 'version' => $version->version]]]; $localRoleScriptData = ['trigger' => ['type' => RoleScriptAdapter::TRIGGER_ALL_EVENTS], 'target' => ['type' => RoleScriptAdapter::TARGET_NULL], 'action' => ['actionType' => RoleScriptAdapter::ACTION_URI, 'uri' => 'https://example.com']]; //post scalr rule $response = $this->postRule($role->id, $scalrRoleScriptData); $this->assertEquals(201, $response->status, $this->printResponseError($response)); $ruleId = $response->getBody()->data->id; /* @var $rule RoleScript */ $rule = RoleScript::findPk($ruleId); $this->assertNotEmpty($rule); $this->ruleToDelete($ruleId); $this->assertObjectEqualsEntity($scalrRoleScriptData, $rule); //post local rule $response = $this->postRule($role->id, $localRoleScriptData); $this->assertEquals(201, $response->status, $this->printResponseError($response)); $ruleId = $response->getBody()->data->id; /* @var $rule RoleScript */ $rule = RoleScript::findPk($ruleId); $this->assertNotEmpty($rule); $this->ruleToDelete($ruleId); $this->assertObjectEqualsEntity($localRoleScriptData, $rule); //post rule to environment-scoped role $response = $this->postRule($scalrRole->id, $scalrRoleScriptData); $this->assertErrorMessageContains($response, 403, ErrorMessage::ERR_PERMISSION_VIOLATION); //post rule already existing $data = $scalrRoleScriptData; $data['id'] = $ruleId; $response = $this->postRule($role->id, $data); $this->assertEquals(201, $response->status, $this->printResponseError($response)); $ruleId = $response->getBody()->data->id; $this->ruleToDelete($ruleId); $this->assertNotEquals($data['id'], $ruleId); //post rule with script that does not exists $data = $scalrRoleScriptData; $data['action']['scriptVersion']['script']['id'] = Script::findOne([], ['id' => true])->id + 1; $response = $this->postRule($role->id, $data); $this->assertErrorMessageContains($response, 404, ErrorMessage::ERR_OBJECT_NOT_FOUND); //post rule with version that does not exists $data = $scalrRoleScriptData; $data['action']['scriptVersion']['version'] = Script::findPk($data['action']['scriptVersion']['script']['id'])->getLatestVersion()->version + 1; $response = $this->postRule($role->id, $data); $this->assertErrorMessageContains($response, 404, ErrorMessage::ERR_OBJECT_NOT_FOUND); //post rule with properties that not existing $data = $scalrRoleScriptData; $data['foo'] = 'bar'; $response = $this->postRule($role->id, $data); $this->assertErrorMessageContains($response, 400, ErrorMessage::ERR_INVALID_STRUCTURE); //post rule without required fields $data = $localRoleScriptData; unset($data['action']); $response = $this->postRule($role->id, $data); $this->assertErrorMessageContains($response, 400, ErrorMessage::ERR_INVALID_STRUCTURE); //post rule with invalid field $data = $localRoleScriptData; $data['action'] = ''; $response = $this->postRule($role->id, $data); $this->assertErrorMessageContains($response, 400, ErrorMessage::ERR_INVALID_STRUCTURE); //fetch rule $response = $this->getRule($role->id, $rule->id); $this->assertEquals(200, $response->status, $this->printResponseError($response)); $this->assertObjectEqualsEntity($response->getBody()->data, $rule); //fetch rule that doe not exists $response = $this->getRule($role->id, RoleScript::findOne([], ['id' => ''])->id + 1); $this->assertErrorMessageContains($response, 404, ErrorMessage::ERR_OBJECT_NOT_FOUND); //fetch rule with missmatch role id $response = $this->getRule(Role::findOne([], ['id' => ''])->id + 1, $rule->id); $this->assertErrorMessageContains($response, 400, ErrorMessage::ERR_INVALID_VALUE); //test have access to all listed rules $rules = $this->listRules($role->id); foreach ($rules as $rule) { $this->assertTrue(RoleScript::findPk($rule->id)->hasAccessPermissions($user)); } $listUri = static::getUserApiUrl("/scripts"); //test invalid filters $response = $this->request($listUri, Request::METHOD_GET, ['foo' => 'bar']); $this->assertErrorMessageContains($response, 400, ErrorMessage::ERR_INVALID_STRUCTURE); //test invalid filters values $response = $this->request($listUri, Request::METHOD_GET, ['scope' => 'foobar']); $this->assertErrorMessageContains($response, 400, ErrorMessage::ERR_INVALID_VALUE); //delete script /* @var $rule RoleScript */ $rule = static::createEntity(new RoleScript(), ['roleId' => $role->id, 'scriptId' => $script->id]); $response = $this->deleteRule($role->id, $rule->id); $this->assertEquals(200, $response->status, $this->printResponseError($response)); //delete scalr-scoped script /* @var $rule RoleScript */ $rule = static::createEntity(new RoleScript(), ['roleId' => $scalrRole->id, 'scriptId' => $script->id, 'version' => -1]); $response = $this->deleteRule($scalrRole->id, $rule->id); $this->assertErrorMessageContains($response, 403, ErrorMessage::ERR_PERMISSION_VIOLATION); //delete script that does not exists $response = $this->deleteRule($role->id, RoleScript::findOne([], ['id' => ''])->id + 1); $this->assertErrorMessageContains($response, 404, ErrorMessage::ERR_OBJECT_NOT_FOUND); }
/** * @param string $serverId * @param string $name * @param string $description * @param bool $createRole * @param string $scope * @param string $replaceRole * @param bool $replaceImage * @param int $rootVolumeSize * @param string $rootVolumeType * @param int $rootVolumeIops * @throws Exception */ public function xServerCreateSnapshotAction($serverId, $name = '', $description = '', $createRole = false, $scope = '', $replaceRole = '', $replaceImage = false, $rootVolumeSize = 0, $rootVolumeType = '', $rootVolumeIops = 0) { $this->request->restrictAccess(Acl::RESOURCE_IMAGES_ENVIRONMENT, Acl::PERM_IMAGES_ENVIRONMENT_MANAGE); $server = $this->getServerEntity($serverId); $this->request->checkPermissions($server, true); $farm = $server->getFarm(); $role = $server->getFarmRole()->getRole(); //Check for already running bundle on selected instance if ($this->db->GetOne("SELECT id FROM bundle_tasks WHERE server_id=? AND status NOT IN ('success', 'failed') LIMIT 1", array($server->serverId))) { throw new Exception(sprintf(_("Server '%s' is already synchonizing."), $server->serverId)); } $validator = new Validator(); $validator->addErrorIf(!Entity\Role::isValidName($name), 'name', "Role name is incorrect"); $validator->addErrorIf(!in_array($replaceRole, ['farm', 'all', '']), 'replaceRole', 'Invalid value'); $object = $createRole ? BundleTask::BUNDLETASK_OBJECT_ROLE : BundleTask::BUNDLETASK_OBJECT_IMAGE; $replaceType = SERVER_REPLACEMENT_TYPE::NO_REPLACE; $createScope = ScopeInterface::SCOPE_ENVIRONMENT; if ($createRole) { $this->request->restrictAccess(Acl::RESOURCE_ROLES_ENVIRONMENT, Acl::PERM_ROLES_ENVIRONMENT_MANAGE); if ($replaceRole == 'farm') { if ($farm->hasAccessPermissions($this->getUser(), $this->getEnvironment(), Acl::PERM_FARMS_UPDATE)) { $replaceType = SERVER_REPLACEMENT_TYPE::REPLACE_FARM; } else { $validator->addError('replaceRole', "You don't have permissions to update farm"); } } else { if ($replaceRole == 'all') { if ($this->request->isAllowed([Acl::RESOURCE_FARMS, Acl::RESOURCE_TEAM_FARMS, Acl::RESOURCE_OWN_FARMS], Acl::PERM_FARMS_UPDATE)) { $replaceType = SERVER_REPLACEMENT_TYPE::REPLACE_ALL; } else { $validator->addError('replaceRole', "You don't have permissions to update farms"); } } } /* @var $existRole Entity\Role */ $existRole = Entity\Role::findOne([['name' => $name], ['$or' => [['accountId' => null], ['$and' => [['accountId' => $this->getUser()->accountId], ['$or' => [['envId' => null], ['envId' => $this->getEnvironment()->id]]]]]]]]); if ($existRole) { if (empty($existRole->accountId)) { $validator->addError('name', _("Selected role name is reserved and cannot be used for custom role")); } else { if ($replaceType != SERVER_REPLACEMENT_TYPE::REPLACE_ALL) { $validator->addError('name', _("Specified role name is already used by another role. You can use this role name only if you will replace old one on ALL your farms.")); } else { if ($replaceType == SERVER_REPLACEMENT_TYPE::REPLACE_ALL && $existRole->id != $role->id) { $validator->addError('name', _("Specified role name is already in use. You cannot replace a Role different from the one you are currently snapshotting.")); } } } } if ($btId = BundleTask::getActiveTaskIdByName($name, $this->getUser()->accountId, $this->getEnvironment()->id)) { $validator->addError('name', sprintf("Specified role name is already reserved for BundleTask with ID: %d.", $btId)); } if ($replaceType != SERVER_REPLACEMENT_TYPE::NO_REPLACE) { $chk = BundleTask::getActiveTaskIdByRoleId($role->id, $this->getEnvironment()->id, BundleTask::BUNDLETASK_OBJECT_ROLE); $validator->addErrorIf($chk, 'replaceRole', sprintf("Role is already synchronizing in BundleTask: %d.", $chk)); } } else { $sc = $role->getScope(); if ($replaceImage) { if ($sc == ScopeInterface::SCOPE_ENVIRONMENT && $this->request->isAllowed(Acl::RESOURCE_ROLES_ENVIRONMENT, Acl::PERM_ROLES_ENVIRONMENT_MANAGE) || $sc == ScopeInterface::SCOPE_ACCOUNT && $this->request->isAllowed(Acl::RESOURCE_ROLES_ACCOUNT, Acl::PERM_ROLES_ACCOUNT_MANAGE)) { $replaceType = SERVER_REPLACEMENT_TYPE::REPLACE_ALL; $chk = BundleTask::getActiveTaskIdByRoleId($role->id, $this->getEnvironment()->id, BundleTask::BUNDLETASK_OBJECT_IMAGE); $validator->addErrorIf($chk, 'replaceImage', sprintf("Role is already synchronizing in BundleTask: %d.", $chk)); } else { $validator->addError('replaceImage', "You don't have permissions to replace image in role"); } } } if ($scope && ($createRole || $scope != $createScope)) { if ($createRole) { $c = $scope == ScopeInterface::SCOPE_ENVIRONMENT && $this->request->isAllowed(Acl::RESOURCE_ROLES_ENVIRONMENT, Acl::PERM_ROLES_ENVIRONMENT_MANAGE) || $scope == ScopeInterface::SCOPE_ACCOUNT && $this->request->isAllowed(Acl::RESOURCE_ROLES_ACCOUNT, Acl::PERM_ROLES_ACCOUNT_MANAGE); $validator->addErrorIf(!$c, 'scope', sprintf("You don't have permissions to create role in scope %s", $scope)); } $c = $scope == ScopeInterface::SCOPE_ENVIRONMENT && $this->request->isAllowed(Acl::RESOURCE_IMAGES_ENVIRONMENT, Acl::PERM_IMAGES_ENVIRONMENT_MANAGE) || $scope == ScopeInterface::SCOPE_ACCOUNT && $this->request->isAllowed(Acl::RESOURCE_IMAGES_ACCOUNT, Acl::PERM_IMAGES_ACCOUNT_MANAGE); $validator->addErrorIf(!$c, 'scope', sprintf("You don't have permissions to create image in scope %s", $scope)); $createScope = $scope; } $image = $role->getImage($server->platform, $server->cloudLocation)->getImage(); $rootBlockDevice = []; if ($server->platform == SERVER_PLATFORMS::EC2 && ($server->isVersionSupported('0.7') && $server->os == 'linux' || $image->isEc2HvmImage())) { if ($rootVolumeSize > 0) { $rootBlockDevice['size'] = $rootVolumeSize; } if (in_array($rootVolumeType, [CreateVolumeRequestData::VOLUME_TYPE_STANDARD, CreateVolumeRequestData::VOLUME_TYPE_GP2, CreateVolumeRequestData::VOLUME_TYPE_IO1, CreateVolumeRequestData::VOLUME_TYPE_SC1, CreateVolumeRequestData::VOLUME_TYPE_ST1])) { $rootBlockDevice['volume_type'] = $rootVolumeType; if ($rootVolumeType == CreateVolumeRequestData::VOLUME_TYPE_IO1 && $rootVolumeIops > 0) { $rootBlockDevice['iops'] = $rootVolumeIops; } } } if (!$validator->isValid($this->response)) { return; } $ServerSnapshotCreateInfo = new ServerSnapshotCreateInfo(DBServer::LoadByID($server->serverId), $name, $replaceType, $object, $description, $rootBlockDevice); $BundleTask = BundleTask::Create($ServerSnapshotCreateInfo); $BundleTask->createdById = $this->user->id; $BundleTask->createdByEmail = $this->user->getEmail(); $BundleTask->osId = $role->osId; $BundleTask->objectScope = $createScope; if ($role->getOs()->family == 'windows') { $BundleTask->osFamily = $role->getOs()->family; $BundleTask->osVersion = $role->getOs()->generation; $BundleTask->osName = ''; } else { $BundleTask->osFamily = $role->getOs()->family; $BundleTask->osVersion = $role->getOs()->version; $BundleTask->osName = $role->getOs()->name; } if (in_array($role->getOs()->family, array('redhat', 'oel', 'scientific')) && $server->platform == SERVER_PLATFORMS::EC2) { $BundleTask->bundleType = SERVER_SNAPSHOT_CREATION_TYPE::EC2_EBS_HVM; } $BundleTask->save(); $this->response->data(['bundleTaskId' => $BundleTask->id]); $this->response->success("Bundle task successfully created."); }