diff --git a/lib/core/Constants.js b/lib/core/Constants.js index f86614d..8e6ad3b 100644 --- a/lib/core/Constants.js +++ b/lib/core/Constants.js @@ -105,7 +105,7 @@ const CopyStatus = { // See allowed operations below in comments const ServiceSAS = { - Container: { + Blob: { // Read the content, properties, metadata or block list of any blob in the container. Use any blob in the container as the source of a copy operation. READ: 'r', // Add a block to any append blob in the container. @@ -120,21 +120,7 @@ const ServiceSAS = { DELETE: 'd', // List blobs in the container. LIST: 'l' - }, - Blob: { - // Read the content, properties, metadata and block list. Use the blob as the source of a copy operation. - READ: 'r', - // Add a block to an append blob. - ADD: 'a', - // Write a new blob, snapshot a blob, or copy a blob to a new blob. - CREATE: 'c', - // Create or write content, properties, metadata, or block list. Snapshot or lease the blob. Resize the blob (page blob only). - // Use the blob as the destination of a copy operation. - WRITE: 'w', - // Delete the blob. - DELETE: 'd' } - } module.exports = { @@ -145,5 +131,6 @@ module.exports = { Usage: Usage, Operations: Operations, CopyStatus: CopyStatus, - BlockListType: BlockListType + BlockListType: BlockListType, + ServiceSAS: ServiceSAS } \ No newline at end of file diff --git a/lib/core/ErrorCodes.js b/lib/core/ErrorCodes.js index 3e85ed4..59c9e7e 100644 --- a/lib/core/ErrorCodes.js +++ b/lib/core/ErrorCodes.js @@ -15,6 +15,10 @@ class ErrorCode { module.exports = { // GENERAL InvalidXml: new ErrorCode('Invalid XML.', 400, 'One of the XML nodes specified in the request body is not supported.'), + AuthenticationFailed: new ErrorCode('AuthenticationFailed', 403, 'Server failed to authenticate the request. Make sure the value of the Authorization header is formed correctly including the signature.'), + AuthorizationPermissionMismatch: new ErrorCode('AuthorizationPermissionMismatch', 403, 'This request is not authorized to perform this operation using this permission.'), + AuthorizationResourceTypeMismatch: new ErrorCode('AuthorizationResourceTypeMismatch', 403, 'This request is not authorized to perform this operation using this resource type.'), + // BLOB ContainerNotFound: new ErrorCode('ContainerNotFound', 404, 'The specified container does not exist.'), ContainerAlreadyExists: new ErrorCode('ContainerAlreadyExists', 409, 'The specified container already exists.'), diff --git a/lib/middleware/blob/validation.js b/lib/middleware/blob/validation.js index 072843c..aab8772 100644 --- a/lib/middleware/blob/validation.js +++ b/lib/middleware/blob/validation.js @@ -4,6 +4,7 @@ const BbPromise = require('bluebird'), Operations = require('./../../core/Constants').Operations, Usage = require('./../../core/Constants').Usage, StorageEntityType = require('./../../core/Constants').StorageEntityType, + SasOperation = require('./../../core/Constants').ServiceSAS, AzuriteContainerRequest = require('./../../model/blob/AzuriteContainerRequest'), AzuriteBlobRequest = require('./../../model/blob/AzuriteBlobRequest'), sm = require('./../../core/blob/StorageManager'), @@ -36,6 +37,7 @@ const BbPromise = require('bluebird'), BlobLeaseUsageValidation = require('./../../validation/blob/BlobLeaseUsage'), BlockListValidation = require('./../../validation/blob/BlockList'), AbortCopyValidation = require('./../../validation/blob/AbortCopy'), + ServiceSignatureValidation = require('./../../validation/blob/ServiceSignature'), CopyStatusValidation = require('./../../validation/blob/CopyStatus'); module.exports = (req, res, next) => { @@ -83,6 +85,7 @@ validations[Operations.Container.DELETE_CONTAINER] = (request, valContext) => { validations[Operations.Blob.PUT_BLOB] = (request, valContext) => { valContext + .run(ServiceSignatureValidation, { sasOperation: SasOperation.Blob.CREATE }) .run(MD5Val) .run(ContainerExistsVal) .run(CompatibleBlobTypeVal) @@ -95,6 +98,7 @@ validations[Operations.Blob.PUT_BLOB] = (request, valContext) => { validations[Operations.Blob.APPEND_BLOCK] = (request, valContext) => { valContext + .run(ServiceSignatureValidation, { sasOperation: SasOperation.Blob.ADD }) .run(BlobExistsVal) .run(ContentLengthExistsVal) .run(BlockPageSizeVal) @@ -108,6 +112,7 @@ validations[Operations.Blob.APPEND_BLOCK] = (request, valContext) => { validations[Operations.Blob.DELETE_BLOB] = (request, valContext) => { valContext + .run(ServiceSignatureValidation, { sasOperation: SasOperation.Blob.DELETE }) .run(BlobExistsVal) .run(AssociatedSnapshotDeletion, { collection: sm.db.getCollection(request.containerName) }) .run(BlobLeaseUsageValidation, { usage: Usage.Write }) @@ -116,6 +121,7 @@ validations[Operations.Blob.DELETE_BLOB] = (request, valContext) => { validations[Operations.Blob.GET_BLOB] = (request, valContext) => { valContext + .run(ServiceSignatureValidation, { sasOperation: SasOperation.Blob.READ }) .run(BlobExistsVal) .run(BlobCommittedVal) .run(RangeVal) @@ -125,11 +131,13 @@ validations[Operations.Blob.GET_BLOB] = (request, valContext) => { validations[Operations.Container.LIST_BLOBS] = (request, valContext) => { valContext + .run(ServiceSignatureValidation, { sasOperation: SasOperation.Blob.LIST }) .run(ContainerExistsVal); } validations[Operations.Blob.PUT_BLOCK] = (request, valContext) => { valContext + .run(ServiceSignatureValidation, { sasOperation: SasOperation.Blob.WRITE }) .run(ContainerExistsVal) .run(ContentLengthExistsVal) .run(BlockPageSizeVal) @@ -140,6 +148,7 @@ validations[Operations.Blob.PUT_BLOCK] = (request, valContext) => { validations[Operations.Blob.PUT_BLOCK_LIST] = (request, valContext) => { valContext + .run(ServiceSignatureValidation, { sasOperation: SasOperation.Blob.WRITE }) .run(ContainerExistsVal) .run(CompatibleBlobTypeVal) .run(BlockListValidation, { storageManager: sm }) @@ -149,6 +158,7 @@ validations[Operations.Blob.PUT_BLOCK_LIST] = (request, valContext) => { validations[Operations.Blob.GET_BLOCK_LIST] = (request, valContext) => { valContext + .run(ServiceSignatureValidation, { sasOperation: SasOperation.Blob.READ }) .run(ContainerExistsVal) .run(BlobExistsVal) .run(IsOfBlobTypeVal, { entityType: StorageEntityType.BlockBlob }) @@ -157,6 +167,7 @@ validations[Operations.Blob.GET_BLOCK_LIST] = (request, valContext) => { validations[Operations.Blob.SET_BLOB_METADATA] = (request, valContext) => { valContext + .run(ServiceSignatureValidation, { sasOperation: SasOperation.Blob.WRITE }) .run(ContainerExistsVal) .run(BlobExistsVal) .run(ConditionalRequestHeadersVal, { usage: Usage.Write }) @@ -165,6 +176,7 @@ validations[Operations.Blob.SET_BLOB_METADATA] = (request, valContext) => { validations[Operations.Blob.GET_BLOB_METADATA] = (request, valContext) => { valContext + .run(ServiceSignatureValidation, { sasOperation: SasOperation.Blob.READ }) .run(ContainerExistsVal) .run(BlobExistsVal) .run(BlobCommittedVal) @@ -174,6 +186,7 @@ validations[Operations.Blob.GET_BLOB_METADATA] = (request, valContext) => { validations[Operations.Blob.GET_BLOB_PROPERTIES] = (request, valContext) => { valContext + .run(ServiceSignatureValidation, { sasOperation: SasOperation.Blob.READ }) .run(ContainerExistsVal) .run(BlobExistsVal) .run(BlobCommittedVal) @@ -183,6 +196,7 @@ validations[Operations.Blob.GET_BLOB_PROPERTIES] = (request, valContext) => { validations[Operations.Blob.SET_BLOB_PROPERTIES] = (request, valContext) => { valContext + .run(ServiceSignatureValidation, { sasOperation: SasOperation.Blob.WRITE }) .run(ContainerExistsVal) .run(BlobExistsVal) .run(ConditionalRequestHeadersVal, { usage: Usage.Write }) @@ -210,6 +224,7 @@ validations[Operations.Container.GET_CONTAINER_PROPERTIES] = (request, valContex validations[Operations.Blob.PUT_PAGE] = (request, valContext) => { valContext + .run(ServiceSignatureValidation, { sasOperation: SasOperation.Blob.WRITE }) .run(ContainerExistsVal) .run(BlobExistsVal) .run(ContentLengthExistsVal) @@ -225,6 +240,7 @@ validations[Operations.Blob.PUT_PAGE] = (request, valContext) => { validations[Operations.Blob.GET_PAGE_RANGES] = (request, valContext) => { valContext + .run(ServiceSignatureValidation, { sasOperation: SasOperation.Blob.READ }) .run(ContainerExistsVal) .run(BlobExistsVal) .run(PageAlignmentVal) @@ -234,6 +250,7 @@ validations[Operations.Blob.GET_PAGE_RANGES] = (request, valContext) => { validations[Operations.Container.SET_CONTAINER_ACL] = (request, valContext) => { valContext + .run(ServiceSignatureValidation, { sasOperation: SasOperation.Blob.WRITE }) .run(ContainerExistsVal) .run(NumOfSignedIdentifiersVal) .run(ConditionalRequestHeadersVal, { usage: Usage.Write }) @@ -242,12 +259,14 @@ validations[Operations.Container.SET_CONTAINER_ACL] = (request, valContext) => { validations[Operations.Container.GET_CONTAINER_ACL] = (request, valContext) => { valContext + .run(ServiceSignatureValidation, { sasOperation: SasOperation.Blob.READ }) .run(ContainerExistsVal) .run(ContainerLeaseUsageValidation, { usage: Usage.Other }); } validations[Operations.Blob.SNAPSHOT_BLOB] = (request, valContext) => { valContext + .run(ServiceSignatureValidation, { sasOperation: SasOperation.Blob.CREATE }) .run(ContainerExistsVal) .run(BlobExistsVal) .run(ConditionalRequestHeadersVal, { usage: Usage.Write }) @@ -265,6 +284,7 @@ validations[Operations.Container.LEASE_CONTAINER] = (request, valContext) => { validations[Operations.Blob.LEASE_BLOB] = (request, valContext) => { valContext + .run(ServiceSignatureValidation, { sasOperation: SasOperation.Blob.WRITE }) .run(ContainerExistsVal) .run(BlobExistsVal) .run(LeaseDurationValidation) @@ -279,6 +299,7 @@ validations[Operations.Blob.COPY_BLOB] = (request, valContext) => { const ret = sm._getCollectionAndContainer((request.copySourceName()).sourceContainerName), sourceContainerProxy = ret.containerProxy; valContext + .run(ServiceSignatureValidation, { sasOperation: SasOperation.Blob.WRITE }) .run(ContainerExistsVal, { containerProxy: sourceContainerProxy }) .run(BlobExistsVal, { blobProxy: sourceBlobProxy }); diff --git a/lib/validation/blob/ServiceSignature.js b/lib/validation/blob/ServiceSignature.js index 20f785b..3b7630b 100644 --- a/lib/validation/blob/ServiceSignature.js +++ b/lib/validation/blob/ServiceSignature.js @@ -12,17 +12,58 @@ class ServiceSignature { constructor() { } - validate({ request = undefined, moduleOptions = undefined }) { - const sm = moduleOptions.storageManager, - accessPolicy = moduleOptions.accessPolicy; - + validate({ request = undefined, containerProxy = undefined, blobProxy = undefined, moduleOptions = undefined }) { if (request.auth === undefined) { + // NOOP: No Service Signature signature was defined in the request return; } - + if (!request.auth.sasValid) { + throw new AError(ErrorCodes.AuthenticationFailed); + } + + const operation = moduleOptions.sasOperation, + accessPolicy = request.auth.accessPolicy, + resource = accessPolicy.ResourceTypes; + + let start = undefined, + expiry = undefined, + permissions = undefined; + + if (request.auth.accessPolicy.Id !== undefined) { + const si = (containerProxy.original.signedIdentifiers !== undefined) + ? containerProxy.original.signedIdentifiers.filter((i) => { + return i.Id === request.auth.accessPolicy.Id; + })[0] + : undefined; + if (si === undefined) { + throw new AError(ErrorCodes.AuthenticationFailed); + } + start = Date.parse(si.AccessPolicy.Start); + expiry = Date.parse(si.AccessPolicy.Expiry); + permissions = si.AccessPolicy.Permission; + } else { + start = Date.parse(accessPolicy.Start); // Possibly NaN + expiry = Date.parse(accessPolicy.Expiry); // Possibly NaN + permissions = accessPolicy.Permissions; + } - + // Time Validation + if (isNaN(start) || isNaN(expiry) || now < start || now > expiry) { + throw new AError(ErrorCodes.AuthenticationFailed); + } + + // Permission Validation + if (!permissions.includes(operation)) { + throw new AError(ErrorCodes.AuthorizationPermissionMismatch); + } + + // Resource Validation + if (resource !== undefined && + (resource === 'b' && blobProxy === undefined) || + (resource === 'c' && blobProxy !== undefined)) { + throw new AError(ErrorCodes.AuthorizationResourceTypeMismatch); + } } } diff --git a/lib/xml/blob/SignedIdentifierXmlModel.js b/lib/xml/blob/SignedIdentifierXmlModel.js index 50a345b..358672b 100644 --- a/lib/xml/blob/SignedIdentifierXmlModel.js +++ b/lib/xml/blob/SignedIdentifierXmlModel.js @@ -5,13 +5,13 @@ class SignedIdentifiers { this.SignedIdentifier = []; } - addSignedIdentifier(id, start, expiry, permission) { + addSignedIdentifier(id, start, expiry, permissionlist) { this.SignedIdentifier.push({ Id: id, AccessPolicy: { Start: start, Expiry: expiry, - Permission: permission + Permission: permissionlist } }); }