azurite
Version:
An open source Azure Storage API compatible server
215 lines • 12 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const QueueStorageContext_1 = tslib_1.__importDefault(require("../context/QueueStorageContext"));
const StorageErrorFactory_1 = tslib_1.__importDefault(require("../errors/StorageErrorFactory"));
const operation_1 = tslib_1.__importDefault(require("../generated/artifacts/operation"));
const IQueueSASSignatureValues_1 = require("./IQueueSASSignatureValues");
const OperationQueueSASPermission_1 = require("./OperationQueueSASPermission");
class QueueSASAuthenticator {
constructor(accountDataStore, queueMetadataStore, logger) {
this.accountDataStore = accountDataStore;
this.queueMetadataStore = queueMetadataStore;
this.logger = logger;
}
async validate(req, context) {
this.logger.info(`QueueSASAuthenticator:validate() Start validation against queue service Shared Access Signature pattern.`, context.contextID);
this.logger.debug("QueueSASAuthenticator:validate() Getting account properties...", context.contextID);
const queueContext = new QueueStorageContext_1.default(context);
const account = queueContext.account;
if (account === undefined) {
throw RangeError(`QueueSASAuthenticator:validate() account is undefined in context.`);
}
const queueName = queueContext.queue;
if (queueName === undefined) {
this.logger.error(`QueueSASAuthenticator:validate() queue name is undefined in context.`, context.contextID);
return undefined;
}
this.logger.debug(
// tslint:disable-next-line:max-line-length
`QueueSASAuthenticator:validate() Retrieved account name from context: ${account}, queue: ${queueName}`, 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("QueueSASAuthenticator:validate() Got account properties successfully.", context.contextID);
// Extract blob service SAS authentication required parameters
const signature = this.decodeIfExist(req.getQuery("sig"));
this.logger.debug(`QueueSASAuthenticator:validate() Retrieved signature from URL parameter sig: ${signature}`, context.contextID);
if (signature === undefined) {
this.logger.debug(`QueueSASAuthenticator:validate() No signature found in request. Skip Queue service SAS validation.`, context.contextID);
return undefined;
}
const values = this.getQueueSASSignatureValuesFromRequest(req, queueName);
if (values === undefined) {
this.logger.info(
// tslint:disable-next-line:max-line-length
`QueueSASAuthenticator:validate() Failed to get valid queue service SAS values from request. Skip queue service SAS validation.`, context.contextID);
return undefined;
}
this.logger.debug(`QueueSASAuthenticator:validate() Successfully got valid queue service SAS values from request. ${JSON.stringify(values)}`, context.contextID);
this.logger.info(`QueueSASAuthenticator:validate() Validate signature based account key1.`, context.contextID);
const [sig1, stringToSign1] = (0, IQueueSASSignatureValues_1.generateQueueSASSignature)(values, account, accountProperties.key1);
this.logger.debug(`QueueSASAuthenticator:validate() String to sign is: ${JSON.stringify(stringToSign1)}`, context.contextID);
this.logger.debug(`QueueSASAuthenticator:validate() Calculated signature is: ${sig1}`, context.contextID);
const sig1Pass = sig1 === signature;
this.logger.info(`QueueSASAuthenticator:validate() Signature based on key1 validation ${sig1Pass ? "passed" : "failed"}.`, context.contextID);
if (!sig1Pass) {
if (accountProperties.key2 === undefined) {
return false;
}
this.logger.info(`QueueSASAuthenticator:validate() Account key2 is not empty, validate signature based account key2.`, context.contextID);
const [sig2, stringToSign2] = (0, IQueueSASSignatureValues_1.generateQueueSASSignature)(values, account, accountProperties.key2);
this.logger.debug(`QueueSASAuthenticator:validate() String to sign is: ${JSON.stringify(stringToSign2)}`, context.contextID);
this.logger.debug(`QueueSASAuthenticator:validate() Calculated signature is: ${sig2}`, context.contextID);
const sig2Pass = sig2 === signature;
this.logger.info(`QueueSASAuthenticator:validate() Signature based on key2 validation ${sig2Pass ? "passed" : "failed"}.`, context.contextID);
if (!sig2Pass) {
this.logger.info(`QueueSASAuthenticator:validate() Validate signature based account key1 and key2 failed.`, context.contextID);
return false;
}
}
// When signature validation passes, we enforce queue service SAS validation
// Any validation errors will stop this request immediately
// TODO: Validate permissions from ACL identifier by extract permissions, start time and expiry time from ACL
// TODO: Set ACL without given start time.
if (values.identifier !== undefined) {
const accessPolicy = await this.getQueueAccessPolicyByIdentifier(account, queueName, values.identifier);
if (accessPolicy === undefined) {
this.logger.warn(`QueueSASAuthenticator:validate() Cannot get access policy defined for queue ${queueName} with id ${values.identifier}.`, context.contextID);
throw StorageErrorFactory_1.default.getAuthorizationFailure(context.contextID);
}
// As Azure Storage, SAS with identifier should not contains any overlap values.
if (values.startTime !== undefined ||
values.expiryTime !== undefined ||
values.permissions !== undefined) {
throw StorageErrorFactory_1.default.getAuthorizationFailure(context.contextID);
}
values.startTime = accessPolicy.start;
values.expiryTime = accessPolicy.expiry;
values.permissions = accessPolicy.permission;
}
this.logger.info(`QueueSASAuthenticator:validate() Validate start and expiry time.`, context.contextID);
if (!this.validateTime(values.expiryTime, values.startTime)) {
this.logger.info(`QueueSASAuthenticator:validate() Validate start and expiry failed.`, context.contextID);
throw StorageErrorFactory_1.default.getAuthorizationFailure(context.contextID);
}
this.logger.info(`QueueSASAuthenticator:validate() Validate IP range.`, context.contextID);
if (!this.validateIPRange()) {
this.logger.info(`QueueSASAuthenticator:validate() Validate IP range failed.`, context.contextID);
throw StorageErrorFactory_1.default.getAuthorizationSourceIPMismatch(context.contextID);
}
this.logger.info(`QueueSASAuthenticator:validate() Validate request protocol.`, context.contextID);
if (!this.validateProtocol(values.protocol, req.getProtocol())) {
this.logger.info(`QueueSASAuthenticator: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
`QueueSASAuthenticator:validate() Operation shouldn't be undefined. Please make sure DispatchMiddleware is hooked before authentication related middleware.`);
}
const queueSASPermission = OperationQueueSASPermission_1.OPERATION_QUEUE_SAS_PERMISSIONS.get(operation);
this.logger.debug(`QueueSASAuthenticator:validate() Got permission requirements for operation ${operation_1.default[operation]} - ${JSON.stringify(queueSASPermission)}`, context.contextID);
if (queueSASPermission === undefined) {
throw new Error(
// tslint:disable-next-line:max-line-length
`QueueSASAuthenticator:validate() OPERATION_QUEUE_SAS_PERMISSIONS doesn't have configuration for operation ${operation_1.default[operation]}'s queue service SAS permission.`);
}
if (!queueSASPermission.validatePermissions(values.permissions)) {
throw StorageErrorFactory_1.default.getAuthorizationPermissionMismatch(context.contextID);
}
return true;
}
getQueueSASSignatureValuesFromRequest(req, queueName) {
const version = this.decodeIfExist(req.getQuery("sv"));
const protocol = this.decodeIfExist(req.getQuery("spr"));
const startTime = this.decodeIfExist(req.getQuery("st"));
const expiryTime = this.decodeIfExist(req.getQuery("se"));
const permissions = this.decodeIfExist(req.getQuery("sp"));
const ipRange = this.decodeIfExist(req.getQuery("sip"));
const identifier = this.decodeIfExist(req.getQuery("si"));
if (!identifier && (!permissions || !expiryTime)) {
this.logger.warn(
// tslint:disable-next-line:max-line-length
`QueueSASAuthenticator:generateQueueSASSignature(): Must provide 'permissions' and 'expiryTime' for Queue SAS generation when 'identifier' is not provided.`);
return undefined;
}
if (version === undefined) {
this.logger.warn(
// tslint:disable-next-line:max-line-length
`QueueSASAuthenticator:generateQueueSASSignature(): Must provide 'version'.`);
return undefined;
}
const queueSASValues = {
version,
protocol,
startTime,
expiryTime,
permissions,
ipRange,
identifier,
queueName
};
return queueSASValues;
}
validateTime(expiry, start) {
// start is optional, expire is required, per https://learn.microsoft.com/en-us/rest/api/storageservices/create-service-sas#specify-the-access-policy
if (expiry === undefined) {
return false;
}
const now = new Date();
if (expiry !== undefined) {
const expiryTime = new Date(expiry);
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 getQueueAccessPolicyByIdentifier(account, queue, id) {
try {
const queueModel = await this.queueMetadataStore.getQueue(account, queue);
if (queueModel === undefined) {
return undefined;
}
if (queueModel.queueAcl === undefined) {
return undefined;
}
for (const acl of queueModel.queueAcl) {
if (acl.id === id) {
return acl.accessPolicy;
}
}
return undefined;
}
catch (err) {
return undefined;
}
}
}
exports.default = QueueSASAuthenticator;
//# sourceMappingURL=QueueSASAuthenticator.js.map
;