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(), $e->getCode(), $e);
}
}
$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');
$container->set('auditlogger.request', function () {
return $this;
});
}