UNPKG

azurite

Version:

An open source Azure Storage API compatible server

235 lines 13.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const tslib_1 = require("tslib"); const utils_1 = require("../../common/utils/utils"); const BlobStorageContext_1 = tslib_1.__importDefault(require("../context/BlobStorageContext")); const StorageErrorFactory_1 = tslib_1.__importDefault(require("../errors/StorageErrorFactory")); const operation_1 = tslib_1.__importDefault(require("../generated/artifacts/operation")); const constants_1 = require("../utils/constants"); class BlobSharedKeyAuthenticator { constructor(dataStore, logger) { this.dataStore = dataStore; this.logger = logger; } async validate(req, context) { const blobContext = new BlobStorageContext_1.default(context); const account = blobContext.account; this.logger.info(`BlobSharedKeyAuthenticator:validate() Start validation against account shared key authentication.`, blobContext.contextId); const authHeaderValue = req.getHeader(constants_1.HeaderConstants.AUTHORIZATION); if (authHeaderValue === undefined) { this.logger.info( // tslint:disable-next-line:max-line-length `BlobSharedKeyAuthenticator:validate() Request doesn't include valid authentication header. Skip shared key authentication.`, blobContext.contextId); return undefined; } else if (!authHeaderValue.startsWith("SharedKey")) { this.logger.info( // tslint:disable-next-line:max-line-length `BlobSharedKeyAuthenticator:validate() Request doesn't include shared key authentication.`, blobContext.contextId); return undefined; } // TODO: Make following async const accountProperties = this.dataStore.getAccount(account); if (accountProperties === undefined) { this.logger.error(`BlobSharedKeyAuthenticator:validate() Invalid storage account ${account}.`, blobContext.contextId); throw StorageErrorFactory_1.default.ResourceNotFound(blobContext.contextId); } const operation = context.operation; if (operation === undefined) { throw new Error( // tslint:disable-next-line:max-line-length `BlobSharedKeyAuthenticator: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(`BlobSharedKeyAuthenticator:validate() Service_GetUserDelegationKey requires OAuth credentials" }.`, context.contextId); throw StorageErrorFactory_1.default.getAuthenticationFailed(context.contextId, constants_1.AUTHENTICATION_BEARERTOKEN_REQUIRED); } const stringToSign = [ req.getMethod().toUpperCase(), this.getHeaderValueToSign(req, constants_1.HeaderConstants.CONTENT_ENCODING), this.getHeaderValueToSign(req, constants_1.HeaderConstants.CONTENT_LANGUAGE), this.getHeaderValueToSign(req, constants_1.HeaderConstants.CONTENT_LENGTH), this.getHeaderValueToSign(req, constants_1.HeaderConstants.CONTENT_MD5), this.getHeaderValueToSign(req, constants_1.HeaderConstants.CONTENT_TYPE), this.getHeaderValueToSign(req, constants_1.HeaderConstants.DATE), this.getHeaderValueToSign(req, constants_1.HeaderConstants.IF_MODIFIED_SINCE), this.getHeaderValueToSign(req, constants_1.HeaderConstants.IF_MATCH), this.getHeaderValueToSign(req, constants_1.HeaderConstants.IF_NONE_MATCH), this.getHeaderValueToSign(req, constants_1.HeaderConstants.IF_UNMODIFIED_SINCE), this.getHeaderValueToSign(req, constants_1.HeaderConstants.RANGE) ].join("\n") + "\n" + this.getCanonicalizedHeadersString(req) + this.getCanonicalizedResourceString(req, account, blobContext.authenticationPath); this.logger.info(`BlobSharedKeyAuthenticator:validate() [STRING TO SIGN]:${JSON.stringify(stringToSign)}`, blobContext.contextId); const signature1 = (0, utils_1.computeHMACSHA256)(stringToSign, accountProperties.key1); const authValue1 = `SharedKey ${account}:${signature1}`; this.logger.info(`BlobSharedKeyAuthenticator:validate() Calculated authentication header based on key1: ${authValue1}`, blobContext.contextId); if (authHeaderValue === authValue1) { this.logger.info(`BlobSharedKeyAuthenticator:validate() Signature 1 matched.`, blobContext.contextId); return true; } if (accountProperties.key2) { const signature2 = (0, utils_1.computeHMACSHA256)(stringToSign, accountProperties.key2); const authValue2 = `SharedKey ${account}:${signature2}`; this.logger.info(`BlobSharedKeyAuthenticator:validate() Calculated authentication header based on key2: ${authValue2}`, blobContext.contextId); if (authHeaderValue === authValue2) { this.logger.info(`BlobSharedKeyAuthenticator:validate() Signature 2 matched.`, blobContext.contextId); return true; } } if (context.context.isSecondary && blobContext.authenticationPath?.indexOf(account) === 1) { // JS/.net Track2 SDK will generate stringToSign from IP style URI with "-secondary" in authenticationPath, so will also compare signature with this kind stringToSign const stringToSign_secondary = [ req.getMethod().toUpperCase(), this.getHeaderValueToSign(req, constants_1.HeaderConstants.CONTENT_ENCODING), this.getHeaderValueToSign(req, constants_1.HeaderConstants.CONTENT_LANGUAGE), this.getHeaderValueToSign(req, constants_1.HeaderConstants.CONTENT_LENGTH), this.getHeaderValueToSign(req, constants_1.HeaderConstants.CONTENT_MD5), this.getHeaderValueToSign(req, constants_1.HeaderConstants.CONTENT_TYPE), this.getHeaderValueToSign(req, constants_1.HeaderConstants.DATE), this.getHeaderValueToSign(req, constants_1.HeaderConstants.IF_MODIFIED_SINCE), this.getHeaderValueToSign(req, constants_1.HeaderConstants.IF_MATCH), this.getHeaderValueToSign(req, constants_1.HeaderConstants.IF_NONE_MATCH), this.getHeaderValueToSign(req, constants_1.HeaderConstants.IF_UNMODIFIED_SINCE), this.getHeaderValueToSign(req, constants_1.HeaderConstants.RANGE) ].join("\n") + "\n" + this.getCanonicalizedHeadersString(req) + this.getCanonicalizedResourceString(req, account, // The authenticationPath looks like "/devstoreaccount1/container", add "-secondary" after account name to "/devstoreaccount1-secondary/container" blobContext.authenticationPath?.replace(account, account + "-secondary")); this.logger.info(`BlobSharedKeyAuthenticator:validate() [STRING TO SIGN_secondary]:${JSON.stringify(stringToSign_secondary)}`, blobContext.contextId); const signature1_secondary = (0, utils_1.computeHMACSHA256)(stringToSign_secondary, accountProperties.key1); const authValue1_secondary = `SharedKey ${account}:${signature1_secondary}`; this.logger.info(`BlobSharedKeyAuthenticator:validate() Calculated authentication header based on key1 and stringToSign with "-secondary": ${authValue1_secondary}`, blobContext.contextId); if (authHeaderValue === authValue1_secondary) { this.logger.info(`BlobSharedKeyAuthenticator:validate() Signature 1_secondary matched.`, blobContext.contextId); return true; } if (accountProperties.key2) { const signature2_secondary = (0, utils_1.computeHMACSHA256)(stringToSign_secondary, accountProperties.key2); const authValue2_secondary = `SharedKey ${account}:${signature2_secondary}`; this.logger.info(`BlobSharedKeyAuthenticator:validate() Calculated authentication header based on key2: ${authValue2_secondary}`, blobContext.contextId); if (authHeaderValue === authValue2_secondary) { this.logger.info(`BlobSharedKeyAuthenticator:validate() Signature 2_secondary matched.`, blobContext.contextId); return true; } } } // this.logger.info(`[URL]:${req.getUrl()}`); // this.logger.info(`[HEADERS]:${req.getHeaders().toString()}`); // this.logger.info(`[KEY]: ${request.headers.get(HeaderConstants.AUTHORIZATION)}`); this.logger.info(`BlobSharedKeyAuthenticator:validate() Validation failed.`, blobContext.contextId); return false; } /** * Retrieve header value according to shared key sign rules. * @see https://docs.microsoft.com/en-us/rest/api/storageservices/authenticate-with-shared-key * * @private * @param {WebResource} request * @param {string} headerName * @returns {string} * @memberof SharedKeyCredentialPolicy */ getHeaderValueToSign(request, headerName) { const value = request.getHeader(headerName); if (!value) { return ""; } // When using version 2015-02-21 or later, if Content-Length is zero, then // set the Content-Length part of the StringToSign to an empty string. // https://docs.microsoft.com/en-us/rest/api/storageservices/authenticate-with-shared-key if (headerName === constants_1.HeaderConstants.CONTENT_LENGTH && value === "0") { return ""; } return value; } /** * To construct the CanonicalizedHeaders portion of the signature string, follow these steps: * 1. Retrieve all headers for the resource that begin with x-ms-, including the x-ms-date header. * 2. Convert each HTTP header name to lowercase. * 3. Sort the headers lexicographically by header name, in ascending order. * Each header may appear only once in the string. * 4. Replace any linear whitespace in the header value with a single space. * 5. Trim any whitespace around the colon in the header. * 6. Finally, append a new-line character to each canonicalized header in the resulting list. * Construct the CanonicalizedHeaders string by concatenating all headers in this list into a single string. * * @private * @param {IRequest} request * @returns {string} * @memberof SharedKeyCredentialPolicy */ getCanonicalizedHeadersString(request) { const headers = []; const headersObject = request.getHeaders(); for (const name in headersObject) { if (headersObject.hasOwnProperty(name)) { const value = headersObject[name]; if (value === undefined) { headers.push({ name, value: "" }); } else if (typeof value === "string") { headers.push({ name, value }); } else { headers.push({ name, value: value.join(",") }); } } } const headersArray = headers.filter((value) => { return value.name .toLowerCase() .startsWith(constants_1.HeaderConstants.PREFIX_FOR_STORAGE); }); headersArray.sort((a, b) => { return a.name.toLowerCase().localeCompare(b.name.toLowerCase()); }); let canonicalizedHeadersStringToSign = ""; headersArray.forEach((header) => { canonicalizedHeadersStringToSign += `${header.name .toLowerCase() .trimRight()}:${header.value.trimLeft()}\n`; }); return canonicalizedHeadersStringToSign; } /** * Retrieves canonicalized resource string. * * @private * @param {IRequest} request * @returns {string} * @memberof SharedKeyCredentialPolicy */ getCanonicalizedResourceString(request, account, authenticationPath) { let path = request.getPath() || "/"; // For secondary account, we use account name (without "-secondary") for the path if (authenticationPath !== undefined) { path = authenticationPath; } let canonicalizedResourceString = ""; canonicalizedResourceString += `/${account}${path}`; const queries = (0, utils_1.getURLQueries)(request.getUrl()); const lowercaseQueries = {}; if (queries) { const queryKeys = []; for (const key in queries) { if (queries.hasOwnProperty(key)) { const lowercaseKey = key.toLowerCase(); lowercaseQueries[lowercaseKey] = queries[key]; queryKeys.push(lowercaseKey); } } queryKeys.sort(); for (const key of queryKeys) { canonicalizedResourceString += `\n${key}:${decodeURIComponent(lowercaseQueries[key].replace(/\+/g, '%20'))}`; } } return canonicalizedResourceString; } } exports.default = BlobSharedKeyAuthenticator; //# sourceMappingURL=BlobSharedKeyAuthenticator.js.map