/** * Removes API key and Entities generated for test * * @throws \Scalr\Exception\ModelException */ public static function tearDownAfterClass() { foreach (array_reverse(static::$testData) as $rec) { $class = $rec['class']; $entry = $rec['pk']; $initProperties = $rec['initProp']; $entity = new $class(); /* @var $entity AbstractEntity */ foreach ($entity->getIterator()->getPrimaryKey() as $pos => $prop) { $entity->{$prop} = $entry[$pos]; } //we should init properties which will be used in delete action foreach ($initProperties as $prop => $value) { $entity->{$prop} = $value; } try { //deletePk method does not remove related objects $entity->delete(); } catch (Exception $e) { //we should remove all created Entities \Scalr::logException($e); } } static::$testData = []; if (!empty(static::$apiKeyEntity)) { static::$apiKeyEntity->delete(); } static::changeLoggerConfiguration(); }
protected function run2($stage) { $hasone = false; //Selects only those users who do not have any key yet $rs = $this->db->Execute("\n SELECT u.id FROM `account_users` u\n LEFT JOIN `account_user_apikeys` k ON k.user_id = u.id\n WHERE k.user_id IS NULL\n "); while ($rec = $rs->FetchRow()) { if (!$hasone) { $this->console->out("Initializing API keys for all users who do not have one..."); $hasone = true; } try { $apiKey = new ApiKeyEntity($rec['id']); $apiKey->save(); } catch (\Exception $e) { continue; } } }
/** * Remove API key generated for test * * @afterClass */ public static function tearDownAfterClass() { foreach (static::$testData as $class => $ids) { $ids = array_unique($ids, SORT_REGULAR); foreach ($ids as $entry) { if (!empty($entry)) { $entity = call_user_func_array([$class, 'findPk'], is_object($entry) ? [$entry] : (array) $entry); if (!empty($entity)) { try { $entity->delete(); } catch (Exception $e) { error_log($e->getMessage()); error_log($class); error_log(print_r($entry, true)); } } } } } if (!empty(static::$apiKeyEntity)) { static::$apiKeyEntity->delete(); } }
/** * Removes API key generated for test * * @afterClass */ public static function tearDownAfterClass() { ksort(static::$testData, SORT_REGULAR); foreach (static::$testData as $priority => $data) { foreach ($data as $class => $ids) { $ids = array_unique($ids, SORT_REGULAR); foreach ($ids as $entry) { if (!empty($entry)) { $entity = call_user_func_array([$class, 'findPk'], is_object($entry) ? [$entry] : (array) $entry); if (!empty($entity)) { try { $entity->delete(); } catch (Exception $e) { // error_log("{$class}:\t" . $e->getMessage() . "\n " . str_replace("\n", "\n ", print_r($entry, true))); } } } } } } if (!empty(static::$apiKeyEntity)) { static::$apiKeyEntity->delete(); } }
/** * Authentication middleware */ public function authenticationMiddleware() { $bDebug = $this->request->headers('x-scalr-debug', 0) == 1; //If API is not enabled if (!$this->getContainer()->config('scalr.system.api.enabled')) { $this->halt(403, 'API is not enabled. See scalr.system.api.enabled'); } //Authentication $keyId = $this->request->headers('x-scalr-key-id'); $signature = $this->request->headers('x-scalr-signature'); //ISO-8601 formatted date $date = trim(preg_replace('/\\s+/', '', $this->request->headers('x-scalr-date'))); if (empty($keyId) || empty($signature)) { throw new ApiErrorException(401, ErrorMessage::ERR_BAD_AUTHENTICATION, 'Unsigned request'); } elseif (empty($date) || ($time = strtotime($date)) === false) { throw new ApiErrorException(400, ErrorMessage::ERR_BAD_REQUEST, 'Missing or invalid X-Scalr-Date header'); } $sigparts = explode(' ', $signature, 2); if (empty($sigparts) || !in_array($sigparts[0], ['V1-HMAC-SHA256'])) { throw new ApiErrorException(401, ErrorMessage::ERR_BAD_AUTHENTICATION, 'Invalid signature'); } $this->apiKey = ApiKeyEntity::findPk($keyId); if (!$this->apiKey instanceof ApiKeyEntity || !$this->apiKey->active) { throw new ApiErrorException(401, ErrorMessage::ERR_BAD_AUTHENTICATION, 'Invalid API Key'); } if (abs(time() - $time) > 300) { throw new ApiErrorException(401, ErrorMessage::ERR_BAD_AUTHENTICATION, 'Request is expired.' . ($bDebug ? ' Now is ' . gmdate('Y-m-d\\TH:i:s\\Z') : '')); } $now = new \DateTime('now'); if (empty($this->apiKey->lastUsed) || $now->getTimestamp() - $this->apiKey->lastUsed->getTimestamp() > 10) { $this->apiKey->lastUsed = $now; $this->apiKey->save(); } $qstr = $this->request->get(); $canonicalStr = ''; if (!empty($qstr)) { ksort($qstr); $canonicalStr = http_build_query($qstr, null, '&', PHP_QUERY_RFC3986); } $reqBody = $this->request->getBody(); $stringToSign = $this->request->getMethod() . "\n" . $date . "\n" . $this->request->getPath() . "\n" . $canonicalStr . "\n" . (empty($reqBody) ? '' : $reqBody); if ($bDebug) { $this->meta->stringToSign = $stringToSign; } switch ($sigparts[0]) { default: throw new ApiErrorException(401, ErrorMessage::ERR_BAD_AUTHENTICATION, 'Invalid signature method. Please use "V1-HMAC-SHA256 [SIGNATURE]"'); break; case 'V1-HMAC-SHA256': $algo = strtolower(substr($sigparts[0], 8)); } $sig = base64_encode(hash_hmac($algo, $stringToSign, $this->apiKey->secretKey, 1)); if ($sig !== $sigparts[1]) { throw new ApiErrorException(401, ErrorMessage::ERR_BAD_AUTHENTICATION, 'Signature does not match'); } $user = Entity\Account\User::findPk($this->apiKey->userId); /* @var $user Entity\Account\User */ if (!$user instanceof Entity\Account\User) { throw new ApiErrorException(401, ErrorMessage::ERR_BAD_AUTHENTICATION, 'User does not exist'); } if ($user->status != Entity\Account\User::STATUS_ACTIVE) { throw new ApiErrorException(403, ErrorMessage::ERR_PERMISSION_VIOLATION, 'Inactive user status'); } if (\Scalr::config('scalr.auth_mode') == 'ldap') { try { $ldap = \Scalr::getContainer()->ldap($user->getLdapUsername(), null); if (!$ldap->isValidUsername()) { if ($bDebug && $ldap->getConfig()->debug) { $this->meta->ldapDebug = $ldap->getLog(); } throw new ApiErrorException(401, ErrorMessage::ERR_BAD_AUTHENTICATION, 'User does not exist'); } $user->applyLdapGroups($ldap->getUserGroups()); } catch (LdapException $e) { if ($bDebug && $ldap instanceof LdapClient && $ldap->getConfig()->debug) { $this->meta->ldapDebug = $ldap->getLog(); } throw new \RuntimeException($e->getMessage()); } } $this->limiter->checkAccountRateLimit($this->apiKey->keyId); //Validates API version if ($this->settings[ApiApplication::SETTING_API_VERSION] != 1) { throw new ApiErrorException(400, ErrorMessage::ERR_BAD_REQUEST, 'Invalid API version'); } if ($this->request->getBody() !== '' && strtolower($this->request->getMediaType()) !== 'application/json') { throw new ApiErrorException(400, ErrorMessage::ERR_BAD_REQUEST, 'Invalid Content-Type'); } $this->setUser($user); $container = $this->getContainer(); //Releases auditloger to ensure it will be updated $container->release('auditlogger'); //Adjusts metadata to invoke audit loger $container->setShared('auditlogger.metadata', function ($cont) use($user) { return (object) ['user' => $user, 'envId' => null, 'remoteAddr' => $this->request->getIp(), 'ruid' => null, 'requestType' => AuditLogger::REQUEST_TYPE_API, 'systemTask' => null]; }); }
/** * Add test environment, test Acl, setups test user, environment and API key */ public static function setUpBeforeClass() { if (!Scalr::getContainer()->config->defined('scalr.phpunit.apiv2')) { static::markTestIncomplete('phpunit apiv2 configurations is invalid'); } if (Scalr::getContainer()->config->defined('scalr.phpunit.apiv2.params.max_results')) { static::$maxResults = Scalr::config('scalr.phpunit.apiv2.params.max_results'); } static::$testUserId = Scalr::config('scalr.phpunit.apiv2.userid'); static::$user = User::findPk(static::$testUserId); static::$testUserType = static::$user->type; static::$testEnvId = Scalr::config('scalr.phpunit.apiv2.envid'); static::$env = Environment::findPk(static::$testEnvId); if (empty(static::$user) || empty(static::$env)) { static::markTestIncomplete('Either test environment or user is invalid.'); } $apiKeyName = static::getTestName(); $apiKeyEntity = ApiKeyEntity::findOne([['name' => $apiKeyName], ['userId' => static::$testUserId]]); if (empty($apiKeyEntity)) { $apiKeyEntity = new ApiKeyEntity(static::$testUserId); $apiKeyEntity->name = $apiKeyName; $apiKeyEntity->save(); } static::$apiKeyEntity = $apiKeyEntity; static::$defaultAcl = Scalr::getContainer()->acl; static::$data = [static::$testEnvId => []]; if (empty(static::$fullAccessAcl)) { static::$fullAccessAcl = new ApiTestAcl(); static::$fullAccessAcl->setDb(Scalr::getContainer()->adodb); static::$fullAccessAcl->createTestAccountRole(static::$user->getAccountId(), static::getTestName(ApiFixture::ACL_FULL_ACCESS), ApiTestAcl::ROLE_ID_FULL_ACCESS); static::$fullAccessAcl->aclType = ApiFixture::ACL_FULL_ACCESS; } if (empty(static::$readOnlyAccessAcl)) { static::$readOnlyAccessAcl = new ApiTestAcl(); static::$readOnlyAccessAcl->setDb(Scalr::getContainer()->adodb); static::$readOnlyAccessAcl->createTestAccountRole(static::$user->getAccountId(), static::getTestName(ApiFixture::ACL_READ_ONLY_ACCESS), ApiTestAcl::ROLE_ID_READ_ONLY_ACCESS); static::$readOnlyAccessAcl->aclType = ApiFixture::ACL_READ_ONLY_ACCESS; } }
/** * @param string $action Action * @param JsonData $keyIds JSON encoded structure * @throws Scalr_Exception_InsufficientPermissions */ public function xApiKeysActionHandlerAction($action, JsonData $keyIds) { if ($this->user->isAdmin() || !\Scalr::config('scalr.system.api.enabled')) { throw new Scalr_Exception_InsufficientPermissions(); } $processed = []; $errors = []; foreach ($keyIds as $keyId) { try { $apiKeyEntity = ApiKeyEntity::findPk($keyId); /* @var $apiKeyEntity ApiKeyEntity */ if ($apiKeyEntity->userId != $this->user->getId()) { throw new Scalr_Exception_Core('Insufficient permissions to modify API key'); } switch ($action) { case 'delete': $apiKeyEntity->delete(); $processed[] = $keyId; break; case 'activate': case 'deactivate': $apiKeyEntity->active = $action == 'activate'; $apiKeyEntity->save(); $processed[] = $keyId; break; } } catch (Exception $e) { $errors[] = $e->getMessage(); } } $num = count($keyIds); if (count($processed) == $num) { $this->response->success('All API keys processed'); } else { array_walk($errors, function (&$item) { $item = '- ' . $item; }); $this->response->warning(sprintf("Successfully processed only %d from %d API KEYS. \nFollowing errors occurred:\n%s", count($processed), $num, join($errors, ''))); } $this->response->data(['processed' => $processed]); }