azurite
Version:
An open source Azure Storage API compatible server
206 lines • 12.3 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const StorageErrorFactory_1 = tslib_1.__importDefault(require("../errors/StorageErrorFactory"));
const models_1 = require("../generated/artifacts/models");
const operation_1 = tslib_1.__importDefault(require("../generated/artifacts/operation"));
const AccountSASPermissions_1 = require("../../common/authentication/AccountSASPermissions");
const IAccountSASSignatureValues_1 = require("../../common/authentication/IAccountSASSignatureValues");
const OperationAccountSASPermission_1 = tslib_1.__importDefault(require("./OperationAccountSASPermission"));
const StrictModelNotSupportedError_1 = tslib_1.__importDefault(require("../errors/StrictModelNotSupportedError"));
const constants_1 = require("../utils/constants");
class AccountSASAuthenticator {
constructor(accountDataStore, blobMetadataStore, logger) {
this.accountDataStore = accountDataStore;
this.blobMetadataStore = blobMetadataStore;
this.logger = logger;
}
async validate(req, context) {
this.logger.info(`AccountSASAuthenticator:validate() Start validation against account Shared Access Signature pattern.`, context.contextId);
this.logger.debug("AccountSASAuthenticator:validate() Getting account properties...", context.contextId);
// Doesn't create a BlobContext here because wants to move this class into common
const account = context.context.account;
const containerName = context.context.container;
const blobName = context.context.blob;
this.logger.debug(
// tslint:disable-next-line:max-line-length
`AccountSASAuthenticator:validate() Retrieved account name from context: ${account}, container: ${containerName}, blob: ${blobName}`, context.contextId);
// TODO: Make following async
const accountProperties = this.accountDataStore.getAccount(account);
if (accountProperties === undefined) {
throw StorageErrorFactory_1.default.ResourceNotFound(context.contextId);
}
this.logger.debug("AccountSASAuthenticator:validate() Got account properties successfully.", context.contextId);
const signature = this.decodeIfExist(req.getQuery("sig"));
this.logger.debug(`AccountSASAuthenticator:validate() Retrieved signature from URL parameter sig: ${signature}`, context.contextId);
const values = this.getAccountSASSignatureValuesFromRequest(req);
if (values === undefined) {
this.logger.info(`AccountSASAuthenticator:validate() Failed to get valid account SAS values from request.`, context.contextId);
return false;
}
this.logger.debug(`AccountSASAuthenticator:validate() Successfully got valid account SAS values from request. ${JSON.stringify(values)}`, context.contextId);
if (!context.context.loose && values.encryptionScope !== undefined) {
throw new StrictModelNotSupportedError_1.default("SAS Encryption Scope 'ses'", context.contextId);
}
this.logger.info(`AccountSASAuthenticator:validate() Validate signature based account key1.`, context.contextId);
const [sig1, stringToSign1] = (0, IAccountSASSignatureValues_1.generateAccountSASSignature)(values, account, accountProperties.key1);
this.logger.debug(`AccountSASAuthenticator:validate() String to sign is: ${JSON.stringify(stringToSign1)}`, context.contextId);
this.logger.debug(`AccountSASAuthenticator:validate() Calculated signature is: ${sig1}`, context.contextId);
const sig1Pass = sig1 === signature;
this.logger.info(`AccountSASAuthenticator:validate() Signature based on key1 validation ${sig1Pass ? "passed" : "failed"}.`, context.contextId);
if (accountProperties.key2 !== undefined) {
this.logger.info(`AccountSASAuthenticator:validate() Account key2 is not empty, validate signature based account key2.`, context.contextId);
const [sig2, stringToSign2] = (0, IAccountSASSignatureValues_1.generateAccountSASSignature)(values, account, accountProperties.key2);
this.logger.debug(`AccountSASAuthenticator:validate() String to sign is: ${JSON.stringify(stringToSign2)}`, context.contextId);
this.logger.debug(`AccountSASAuthenticator:validate() Calculated signature is: ${sig2}`, context.contextId);
const sig2Pass = sig2 === signature;
this.logger.info(`AccountSASAuthenticator:validate() Signature based on key2 validation ${sig2Pass ? "passed" : "failed"}.`, context.contextId);
if (!sig2Pass && !sig1Pass) {
this.logger.info(`AccountSASAuthenticator:validate() Validate signature based account key1 and key2 failed.`, context.contextId);
return false;
}
}
else {
if (!sig1Pass) {
return false;
}
}
// When signature validation passes, we enforce account SAS validation
// Any validation errors will stop this request immediately
this.logger.info(`AccountSASAuthenticator:validate() Validate start and expiry time.`, context.contextId);
if (!this.validateTime(values.expiryTime, values.startTime)) {
this.logger.info(`AccountSASAuthenticator:validate() Validate start and expiry failed.`, context.contextId);
throw StorageErrorFactory_1.default.getAuthorizationFailure(context.contextId);
}
this.logger.info(`AccountSASAuthenticator:validate() Validate IP range.`, context.contextId);
if (!this.validateIPRange()) {
this.logger.info(`AccountSASAuthenticator:validate() Validate IP range failed.`, context.contextId);
throw StorageErrorFactory_1.default.getAuthorizationSourceIPMismatch(context.contextId);
}
this.logger.info(`AccountSASAuthenticator:validate() Validate request protocol.`, context.contextId);
if (!this.validateProtocol(values.protocol, req.getProtocol())) {
this.logger.info(`AccountSASAuthenticator:validate() Validate protocol failed.`, context.contextId);
throw StorageErrorFactory_1.default.getAuthorizationProtocolMismatch(context.contextId);
}
const operation = context.operation;
if (operation === undefined) {
throw new Error(
// tslint:disable-next-line:max-line-length
`AccountSASAuthenticator:validate() operation shouldn't be undefined. Please make sure DispatchMiddleware is hooked before authentication related middleware.`);
}
else if (operation === operation_1.default.Service_GetUserDelegationKey) {
this.logger.info(`AccountSASAuthenticator:validate() Service_GetUserDelegationKey requires OAuth credentials"
}.`, context.contextId);
throw StorageErrorFactory_1.default.getAuthenticationFailed(context.contextId, constants_1.AUTHENTICATION_BEARERTOKEN_REQUIRED);
}
const accountSASPermission = OperationAccountSASPermission_1.default.get(operation);
this.logger.debug(`AccountSASAuthenticator:validate() Got permission requirements for operation ${operation_1.default[operation]} - ${JSON.stringify(accountSASPermission)}`, context.contextId);
if (accountSASPermission === undefined) {
throw new Error(
// tslint:disable-next-line:max-line-length
`AccountSASAuthenticator:validate() OPERATION_ACCOUNT_SAS_PERMISSIONS doesn't have configuration for operation ${operation_1.default[operation]}'s account SAS permission.`);
}
if (!accountSASPermission.validateServices(values.services)) {
throw StorageErrorFactory_1.default.getAuthorizationServiceMismatch(context.contextId);
}
if (!accountSASPermission.validateResourceTypes(values.resourceTypes)) {
throw StorageErrorFactory_1.default.getAuthorizationResourceTypeMismatch(context.contextId);
}
if (!accountSASPermission.validatePermissions(values.permissions)) {
throw StorageErrorFactory_1.default.getAuthorizationPermissionMismatch(context.contextId);
}
// Check special permission requirements
// If block blob exists, then permission must be Write only
// If page blob exists, then permission must be Write only
// If append blob exists, then permission must be Write only
// If copy destination blob exists, then permission must be Write only
if (operation === operation_1.default.BlockBlob_Upload ||
operation === operation_1.default.PageBlob_Create ||
operation === operation_1.default.AppendBlob_Create ||
operation === operation_1.default.Blob_StartCopyFromURL ||
operation === operation_1.default.Blob_CopyFromURL) {
this.logger.info(`AccountSASAuthenticator:validate() For ${operation_1.default[operation]}, if blob exists, the permission must be Write.`, context.contextId);
if ((await this.blobExist(account, containerName, blobName)) &&
!values.permissions.toString().includes(AccountSASPermissions_1.AccountSASPermission.Write)) {
this.logger.info(`AccountSASAuthenticator:validate() Account SAS validation failed for special requirement.`, context.contextId);
throw StorageErrorFactory_1.default.getAuthorizationPermissionMismatch(context.contextId);
}
}
this.logger.info(`AccountSASAuthenticator:validate() Account SAS validation successfully.`, context.contextId);
return true;
}
getAccountSASSignatureValuesFromRequest(req) {
const version = this.decodeIfExist(req.getQuery("sv"));
const services = this.decodeIfExist(req.getQuery("ss"));
const resourceTypes = this.decodeIfExist(req.getQuery("srt"));
const protocol = this.decodeIfExist(req.getQuery("spr"));
const startTime = this.decodeIfExist(req.getQuery("st"));
const expiryTime = this.decodeIfExist(req.getQuery("se"));
const ipRange = this.decodeIfExist(req.getQuery("sip"));
const permissions = this.decodeIfExist(req.getQuery("sp"));
const signature = this.decodeIfExist(req.getQuery("sig"));
const encryptionScope = this.decodeIfExist(req.getQuery("ses"));
if (version === undefined ||
expiryTime === undefined ||
permissions === undefined ||
services === undefined ||
resourceTypes === undefined ||
signature === undefined) {
return undefined;
}
const accountSASValues = {
version,
protocol,
startTime,
expiryTime,
permissions,
ipRange,
services,
resourceTypes,
encryptionScope
};
return accountSASValues;
}
validateTime(expiry, start) {
const expiryTime = new Date(expiry);
const now = new Date();
if (now > expiryTime) {
return false;
}
if (start !== undefined) {
const startTime = new Date(start);
if (now < startTime) {
return false;
}
}
return true;
}
validateIPRange() {
// TODO: Emulator doesn't validate IP Address
return true;
}
validateProtocol(sasProtocol = "https,http", requestProtocol) {
if (sasProtocol.includes(",")) {
return true;
}
else {
return sasProtocol.toLowerCase() === requestProtocol;
}
}
decodeIfExist(value) {
return value === undefined ? value : decodeURIComponent(value);
}
async blobExist(account, container, blob) {
const blobModel = await this.blobMetadataStore.getBlobType(account, container, blob);
if (blobModel === undefined) {
return false;
}
if (blobModel.blobType === models_1.BlobType.BlockBlob &&
blobModel.isCommitted === false) {
return false;
}
return true;
}
}
exports.default = AccountSASAuthenticator;
//# sourceMappingURL=AccountSASAuthenticator.js.map
;