in src/table/authentication/TableSASAuthenticator.ts [24:280]
public async validate(
req: IRequest,
context: Context
): Promise<boolean | undefined> {
this.logger.info(
`TableSASAuthenticator:validate() Start validation against table service Shared Access Signature pattern.`,
context.contextID
);
this.logger.debug(
"TableSASAuthenticator:validate() Getting account properties...",
context.contextID
);
const tableContext = new TableStorageContext(context);
const account = tableContext.account;
if (account === undefined) {
throw RangeError(
`TableSASAuthenticator:validate() account is undefined in context.`
);
}
const tableName = tableContext.tableName;
if (tableName === undefined) {
this.logger.error(
`TableSASAuthenticator:validate() table name is undefined in context.`,
context.contextID
);
return undefined;
}
this.logger.debug(
// tslint:disable-next-line:max-line-length
`TableSASAuthenticator:validate() Retrieved account name from context: ${account}, table: ${tableName}`,
context.contextID
);
// TODO: Make following async
const accountProperties = this.accountDataStore.getAccount(account);
if (accountProperties === undefined) {
throw StorageErrorFactory.ResourceNotFound(
context
);
}
this.logger.debug(
"TableSASAuthenticator:validate() Got account properties successfully.",
context.contextID
);
// Extract table service SAS authentication required parameters
const signature = this.decodeIfExist(req.getQuery("sig"));
this.logger.debug(
`TableSASAuthenticator:validate() Retrieved signature from URL parameter sig: ${signature}`,
context.contextID
);
if (signature === undefined) {
this.logger.debug(
`TableSASAuthenticator:validate() No signature found in request. Skip table service SAS validation.`,
context.contextID
);
return undefined;
}
const values = this.getTableSASSignatureValuesFromRequest(
req,
tableName,
context
);
if (values === undefined) {
this.logger.info(
// tslint:disable-next-line:max-line-length
`TableSASAuthenticator:validate() Failed to get valid table service SAS values from request. Skip table service SAS validation.`,
context.contextID
);
return undefined;
}
this.logger.debug(
`TableSASAuthenticator:validate() Successfully got valid table service SAS values from request. ${JSON.stringify(
values
)}`,
context.contextID
);
this.logger.info(
`TableSASAuthenticator:validate() Validate signature based account key1.`,
context.contextID
);
const [sig1, stringToSign1] = generateTableSASSignature(
values,
account,
accountProperties.key1
);
this.logger.debug(
`TableSASAuthenticator:validate() String to sign is: ${JSON.stringify(
stringToSign1
)}`,
context.contextID!
);
this.logger.debug(
`TableSASAuthenticator:validate() Calculated signature is: ${sig1}`,
context.contextID!
);
const sig1Pass = sig1 === signature;
this.logger.info(
`TableSASAuthenticator:validate() Signature based on key1 validation ${
sig1Pass ? "passed" : "failed"
}.`,
context.contextID
);
if (accountProperties.key2 !== undefined) {
this.logger.info(
`TableSASAuthenticator:validate() Account key2 is not empty, validate signature based account key2.`,
context.contextID
);
const [sig2, stringToSign2] = generateTableSASSignature(
values,
account,
accountProperties.key2
);
this.logger.debug(
`TableSASAuthenticator:validate() String to sign is: ${JSON.stringify(
stringToSign2
)}`,
context.contextID!
);
this.logger.debug(
`TableSASAuthenticator:validate() Calculated signature is: ${sig2}`,
context.contextID!
);
const sig2Pass = sig2 === signature;
this.logger.info(
`TableSASAuthenticator:validate() Signature based on key2 validation ${
sig2Pass ? "passed" : "failed"
}.`,
context.contextID
);
if (!sig2Pass && !sig1Pass) {
this.logger.info(
`TableSASAuthenticator:validate() Validate signature based account key1 and key2 failed.`,
context.contextID
);
return false;
}
} else {
if (!sig1Pass) {
return false;
}
}
// When signature validation passes, we enforce table 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
if (values.identifier !== undefined) {
const accessPolicy:
| AccessPolicy
| undefined = await this.getTableAccessPolicyByIdentifier(
account,
tableName,
values.identifier,
context
);
if (accessPolicy === undefined) {
this.logger.warn(
`TableSASAuthenticator:validate() Cannot get access policy defined for table ${tableName} with id ${values.identifier}.`,
context.contextID
);
throw StorageErrorFactory.getAuthorizationFailure(context);
}
values.startTime = accessPolicy.start;
values.expiryTime = accessPolicy.expiry;
values.permissions = accessPolicy.permission;
}
this.logger.info(
`TableSASAuthenticator:validate() Validate start and expiry time.`,
context.contextID
);
if (!this.validateTime(values.expiryTime, values.startTime)) {
this.logger.info(
`TableSASAuthenticator:validate() Validate start and expiry failed.`,
context.contextID
);
throw StorageErrorFactory.getAuthorizationFailure(context);
}
this.logger.info(
`TableSASAuthenticator:validate() Validate IP range.`,
context.contextID
);
if (!this.validateIPRange()) {
this.logger.info(
`TableSASAuthenticator:validate() Validate IP range failed.`,
context.contextID
);
throw StorageErrorFactory.getAuthorizationSourceIPMismatch(context);
}
this.logger.info(
`TableSASAuthenticator:validate() Validate request protocol.`,
context.contextID
);
if (!this.validateProtocol(values.protocol, req.getProtocol())) {
this.logger.info(
`TableSASAuthenticator:validate() Validate protocol failed.`,
context.contextID
);
throw StorageErrorFactory.getAuthorizationProtocolMismatch(context);
}
const operation = context.operation;
if (operation === undefined) {
throw new Error(
// tslint:disable-next-line:max-line-length
`TableSASAuthenticator:validate() Operation shouldn't be undefined. Please make sure DispatchMiddleware is hooked before authentication related middleware.`
);
}
const tableSASPermission = OPERATION_TABLE_SAS_TABLE_PERMISSIONS.get(
operation
);
this.logger.debug(
`TableSASAuthenticator:validate() Got permission requirements for operation ${
Operation[operation]
} - ${JSON.stringify(tableSASPermission)}`,
context.contextID
);
if (tableSASPermission === undefined) {
throw new Error(
// tslint:disable-next-line:max-line-length
`TableSASAuthenticator:validate() ${"OPERATION_TABLE_SAS_TABLE_PERMISSIONS"} doesn't have configuration for operation ${
Operation[operation]
}'s table service SAS permission.`
);
}
if (!tableSASPermission.validatePermissions(values.permissions!)) {
throw StorageErrorFactory.getAuthorizationPermissionMismatch(context);
}
this.logger.info(
`TableSASAuthenticator:validate() Table service SAS validation successfully.`,
context.contextID
);
// TODO: Handle enforced response headers defined in table service SAS
return true;
}