import IAccountDataStore from "../../common/IAccountDataStore" ; import ILogger from "../../common/ILogger" ; import StorageErrorFactory from "../errors/StorageErrorFactory" ; import Operation from "../generated/artifacts/operation" ; import Context from "../generated/Context" ; import IRequest from "../generated/IRequest" ; import { generateAccountSASSignature , IAccountSASSignatureValues } from "./IAccountSASSignatureValues" ; import IAuthenticator from "./IAuthenticator" ; import OPERATION_ACCOUNT_SAS_PERMISSIONS from "./OperationAccountSASPermission" ; export default class AccountSASAuthenticator implements IAuthenticator { public constructor ( private readonly accountDataStore , private readonly logger ) { } public 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 ) ; const account = context . context . account ; const queueName = context . context . queue ; this . logger . debug ( `AccountSASAuthenticator:validate() Retrieved account name from context: ${ account }, queue: ${ queueName }` , context . contextID ) ; const accountProperties = this . accountDataStore . getAccount ( account ) ; if ( accountProperties === undefined ) { throw StorageErrorFactory . getInvalidOperation ( context . contextID ! , "Invalid storage account." ) ; } 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 ) ; this . logger . info ( `AccountSASAuthenticator:validate() Validate signature based account key1.` , context . contextID ) ; const [ sig1 , stringToSign1 ] = 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 ( ! sig1Pass ) { if ( accountProperties . key2 === undefined ) { return false ; } this . logger . info ( `AccountSASAuthenticator:validate() Account key2 is not empty, validate signature based account key2.` , context . contextID ) ; const [ sig2 , stringToSign2 ] = 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 ) { this . logger . info ( `AccountSASAuthenticator:validate() Validate signature based account key1 and key2 failed.` , context . contextID ) ; return false ; } } 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 . 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 . 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 . getAuthorizationProtocolMismatch ( context . contextID ! ) ; } const operation = context . operation ; if ( operation === undefined ) { throw new Error ( `AccountSASAuthenticator:validate() operation shouldn't be undefined. Please make sure DispatchMiddleware is hooked before authentication related middleware.` ) ; } const accountSASPermission = OPERATION_ACCOUNT_SAS_PERMISSIONS . get ( operation ) ; this . logger . debug ( `AccountSASAuthenticator:validate() Got permission requirements for operation ${ Operation [ operation ] } - ${ JSON . stringify ( accountSASPermission ) }` , context . contextID ) ; if ( accountSASPermission === undefined ) { throw new Error ( `AccountSASAuthenticator:validate() OPERATION_ACCOUNT_SAS_PERMISSIONS doesn't have configuration for operation ${ Operation [ operation ] }'s account SAS permission.` ) ; } if ( ! accountSASPermission . validateServices ( values . services ) ) { throw StorageErrorFactory . getAuthorizationServiceMismatch ( context . contextID ! ) ; } if ( ! accountSASPermission . validateResourceTypes ( values . resourceTypes ) ) { throw StorageErrorFactory . getAuthorizationResourceTypeMismatch ( context . contextID ! ) ; } if ( ! accountSASPermission . validatePermissions ( values . permissions ) ) { throw StorageErrorFactory . getAuthorizationPermissionMismatch ( context . contextID ! ) ; } this . logger . info ( `AccountSASAuthenticator:validate() Account SAS validation successfully.` , context . contextID ) ; return true ; } private 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" ) ) ; 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 } ; return accountSASValues ; } private 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 ; } private validateIPRange ( ) { return true ; } private validateProtocol ( sasProtocol = "https,http" , requestProtocol ) { if ( sasProtocol . includes ( "," ) ) { return true ; } else { return sasProtocol . toLowerCase ( ) === requestProtocol ; } } private decodeIfExist ( value ? ) { return value === undefined ? value : decodeURIComponent ( value ) ; } }