azurite
Version:
An open source Azure Storage API compatible server
241 lines • 12.7 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const utils_1 = require("../../common/utils/utils");
const QueueStorageContext_1 = tslib_1.__importDefault(require("../context/QueueStorageContext"));
const StorageErrorFactory_1 = tslib_1.__importDefault(require("../errors/StorageErrorFactory"));
const constants_1 = require("../utils/constants");
class QueueSharedKeyAuthenticator {
constructor(dataStore, logger) {
this.dataStore = dataStore;
this.logger = logger;
}
async validate(req, context) {
const queueContext = new QueueStorageContext_1.default(context);
const account = queueContext.account;
this.logger.info(`QueueSharedKeyAuthenticator:validate() Start validation against account shared key authentication.`, queueContext.contextID);
const authHeaderValue = req.getHeader(constants_1.HeaderConstants.AUTHORIZATION);
if (authHeaderValue === undefined) {
this.logger.info(
// tslint:disable-next-line:max-line-length
`QueueSharedKeyAuthenticator:validate() Request doesn't include valid authentication header. Skip shared key authentication.`, queueContext.contextID);
return undefined;
}
// TODO: Make following async
const accountProperties = this.dataStore.getAccount(account);
if (accountProperties === undefined) {
this.logger.error(`QueueSharedKeyAuthenticator:validate() Invalid storage account ${account}.`, queueContext.contextID);
throw StorageErrorFactory_1.default.ResourceNotFound(context.contextID);
}
const authType = authHeaderValue.split(" ")[0];
const authValue = authHeaderValue.split(" ")[1];
const headersToSign = this.getHeadersToSign(authType, req);
const stringToSign = headersToSign +
this.getCanonicalizedResourceString(authType, req, account, queueContext.authenticationPath);
this.logger.info(`QueueSharedKeyAuthenticator:validate() [STRING TO SIGN]:${JSON.stringify(stringToSign)}`, queueContext.contextID);
const signature1 = (0, utils_1.computeHMACSHA256)(stringToSign, accountProperties.key1);
const authValue1 = `${account}:${signature1}`;
this.logger.info(`QueueSharedKeyAuthenticator:validate() Calculated authentication header based on key1: ${authValue1}`, queueContext.contextID);
if (authValue === authValue1) {
this.logger.info(`QueueSharedKeyAuthenticator:validate() Signature 1 matched.`, queueContext.contextID);
return true;
}
if (accountProperties.key2) {
const signature2 = (0, utils_1.computeHMACSHA256)(stringToSign, accountProperties.key2);
const authValue2 = `${account}:${signature2}`;
this.logger.info(`QueueSharedKeyAuthenticator:validate() Calculated authentication header based on key2: ${authValue2}`, queueContext.contextID);
if (authValue === authValue2) {
this.logger.info(`QueueSharedKeyAuthenticator:validate() Signature 2 matched.`, queueContext.contextID);
return true;
}
}
if (context.context.isSecondary && queueContext.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 = headersToSign +
this.getCanonicalizedResourceString(authType, req, account,
// The authenticationPath looks like "/devstoreaccount1/queue", add "-secondary" after account name to "/devstoreaccount1-secondary/queue"
queueContext.authenticationPath?.replace(account, account + "-secondary"));
this.logger.info(`QueueSharedKeyAuthenticator:validate() [STRING TO SIGN_secondary]:${JSON.stringify(stringToSign_secondary)}`, queueContext.contextID);
const signature1_secondary = (0, utils_1.computeHMACSHA256)(stringToSign_secondary, accountProperties.key1);
const authValue1_secondary = `${account}:${signature1_secondary}`;
this.logger.info(`QueueSharedKeyAuthenticator:validate() Calculated authentication header based on key1: ${authValue1_secondary}`, queueContext.contextID);
if (authValue === authValue1_secondary) {
this.logger.info(`QueueSharedKeyAuthenticator:validate() Signature 1_secondary matched.`, queueContext.contextID);
return true;
}
if (accountProperties.key2) {
const signature2_secondary = (0, utils_1.computeHMACSHA256)(stringToSign_secondary, accountProperties.key2);
const authValue2_secondary = `${account}:${signature2_secondary}`;
this.logger.info(`QueueSharedKeyAuthenticator:validate() Calculated authentication header based on key2: ${authValue2_secondary}`, queueContext.contextID);
if (authValue === authValue2_secondary) {
this.logger.info(`QueueSharedKeyAuthenticator:validate() Signature 2_secondary matched.`, queueContext.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(`QueueSharedKeyAuthenticator:validate() Validation failed.`, queueContext.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;
}
// TODO: doc
/**
* Retrieves canonicalized resource string.
*
* @private
* @param {IRequest} request
* @returns {string}
* @memberof SharedKeyCredentialPolicy
*/
getCanonicalizedResourceString(type, 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) {
if (type === "SharedKey") {
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'))}`;
}
}
else if (type === "SharedKeyLite") {
for (const key in queries) {
if (queries.hasOwnProperty(key) && key.toLowerCase() === "comp") {
canonicalizedResourceString += `?comp=${decodeURIComponent(queries[key].replace(/\+/g, '%20'))}`;
}
}
}
}
return canonicalizedResourceString;
}
// TODO: DOC
/**
* Get the StringToSign of headers for SharedKey or SharedKeyLite
*
* @private
* @param {"SharedKey" | "SharedKeyLite"} type
* @param {IRequest} req
* @returns {string}
* @memberof QueueSharedKeyAuthenticator
*/
getHeadersToSign(type, req) {
if (type === "SharedKey") {
return ([
req.getMethod().toUpperCase(),
this.getHeaderValueToSign(req, constants_1.HeaderConstants.CONTENT_LANGUAGE),
this.getHeaderValueToSign(req, constants_1.HeaderConstants.CONTENT_ENCODING),
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));
}
else if (type === "SharedKeyLite") {
return ([
req.getMethod().toUpperCase(),
this.getHeaderValueToSign(req, constants_1.HeaderConstants.CONTENT_MD5),
this.getHeaderValueToSign(req, constants_1.HeaderConstants.CONTENT_TYPE),
this.getHeaderValueToSign(req, constants_1.HeaderConstants.DATE)
].join("\n") +
"\n" +
this.getCanonicalizedHeadersString(req));
}
return "";
}
}
exports.default = QueueSharedKeyAuthenticator;
//# sourceMappingURL=QueueSharedKeyAuthenticator.js.map
;