Skip to content

Commit

Permalink
Consistent types for Auth APIs
Browse files Browse the repository at this point in the history
Issue: ARSN-457
  • Loading branch information
williamlardier committed Jan 22, 2025
1 parent 03b0373 commit d00fa4c
Show file tree
Hide file tree
Showing 4 changed files with 292 additions and 15 deletions.
23 changes: 23 additions & 0 deletions lib/auth/AuthInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,29 @@ export type AuthInfoType = {
IAMdisplayName: string;
};

export type AuthorizationResults = {
isAllowed: boolean,
isImplicit: boolean,
arn: string,
action: string,
versionId?: string,
}[];

export type AccountQuota = {
account: string,
quota: bigint,
};

export type AccountInfos = {
accountQuota?: AccountQuota,
};

export type AuthV4Results = {
userInfo: AuthInfoType,
authorizationResults: AuthorizationResults,
accountQuota: AccountQuota,
};

/**
* Class containing requester's information received from Vault
* @param {object} info from Vault including arn, canonicalID,
Expand Down
22 changes: 11 additions & 11 deletions lib/auth/Vault.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Logger } from 'werelogs';
import errors from '../errors';
import AuthInfo, { AuthInfoType } from './AuthInfo';
import AuthInfo, { AccountInfos, AuthInfoType, AuthorizationResults, AuthV4Results } from './AuthInfo';
import { AccountInfo } from 'aws-sdk/clients/sso';

/** vaultSignatureCb parses message from Vault and instantiates
* @param err - error from vault
Expand All @@ -15,18 +16,17 @@ function vaultSignatureCb(
authInfo: {
message: {
message: string,
body: {
userInfo: AuthInfoType,
authorizationResults: { [key: string]: any },
accountQuota: {
account: string,
quota: string,
},
},
body: AuthV4Results,
},
},
log: Logger,
callback: (err: Error | null, data?: any, results?: any, params?: any, infos?: any) => void,
callback: (
err: Error | null,
data?: AuthInfoType,
results?: AuthorizationResults,
params?: any,
infos?: AccountInfos,
) => void,
streamingV4Params?: any
) {
// vaultclient API guarantees that it returns:
Expand All @@ -52,7 +52,7 @@ function vaultSignatureCb(
},
});

const info = authInfo.message.body;
const info = authInfo.message.body as AuthV4Results;
const userInfo = new AuthInfo(info.userInfo);
const authorizationResults = info.authorizationResults;
const auditLog: { accountDisplayName: string, IAMdisplayName?: string } =
Expand Down
33 changes: 29 additions & 4 deletions lib/auth/backends/in_memory/Backend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,20 @@ import { calculateSigningKey, hashSignature } from './vaultUtilities';
import Indexer from './Indexer';
import BaseBackend from '../base';
import { Accounts } from './types';
import { AccountQuota, AuthInfoType, AuthorizationResults, AuthV4Results } from '../../AuthInfo';

function _formatResponse(userInfoToSend: any) {
function _formatResponse(
userInfoToSend: AuthInfoType,
authorizationResults: AuthorizationResults,
accountQuota: AccountQuota,
): { message: { body: AuthV4Results } } {
return {
message: {
body: { userInfo: userInfoToSend },
body: {
userInfo: userInfoToSend,
authorizationResults,
accountQuota,
},
},
};
}
Expand Down Expand Up @@ -61,7 +70,15 @@ class InMemoryBackend extends BaseBackend {
// @ts-ignore
IAMdisplayName: entity.IAMdisplayName,
};
const vaultReturnObject = this.formatResponse(userInfoToSend);
const vaultReturnObject = this.formatResponse(
userInfoToSend,
[],
{
account: entity.canonicalID,
// account quota is not supported in this backend
accountQuota: 0,
}
);
return callback(null, vaultReturnObject);
}

Expand Down Expand Up @@ -92,7 +109,15 @@ class InMemoryBackend extends BaseBackend {
// @ts-ignore
IAMdisplayName: entity.IAMdisplayName,
};
const vaultReturnObject = this.formatResponse(userInfoToSend);
const vaultReturnObject = this.formatResponse(
userInfoToSend,
[],
{
account: entity.canonicalID,
// account quota is not supported in this backend
accountQuota: 0,
}
);
return callback(null, vaultReturnObject);
}

Expand Down
229 changes: 229 additions & 0 deletions tests/unit/auth/Vault.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
'use strict'; // eslint-disable-line strict

const assert = require('assert');
const sinon = require('sinon');

const Vault = require('../../../lib/auth/Vault').default;
const AuthInfo = require('../../../lib/auth/AuthInfo').default;
const DummyRequestLogger = require('../helpers').DummyRequestLogger;

const log = new DummyRequestLogger();

const mockUserInfo = {
arn: 'arn:aws:iam::123456789012:user/testUser',
canonicalID: 'canonical123',
shortid: '123456789012',
email: '[email protected]',
accountDisplayName: 'TestAccount',
IAMdisplayName: 'TestUser',
};

describe('Vault class', () => {
let vault;
let mockClient;
let sandbox;

beforeEach(() => {
sandbox = sinon.createSandbox();
mockClient = {
verifySignatureV4: sandbox.stub(),
verifySignatureV2: sandbox.stub(),
healthcheck: sandbox.stub(),
report: sandbox.stub(),
getCanonicalIds: sandbox.stub(),
getEmailAddresses: sandbox.stub(),
getAccountIds: sandbox.stub(),
checkPolicies: sandbox.stub(),
getOrCreateEncryptionKeyId: sandbox.stub(),
};

vault = new Vault(mockClient, 'mockImpl');
});

afterEach(() => {
sandbox.restore();
});

describe('authenticateV4Request', () => {
const mockParams = {
version: 4,
log,
data: {
accessKey: 'testAccessKey',
signatureFromRequest: 'testSignature',
region: 'us-east-1',
stringToSign: 'testStringToSign',
scopeDate: '20250122',
authType: 'header',
signatureVersion: '4',
signatureAge: 0,
timestamp: Date.now(),
credentialScope: 'testScope',
securityToken: 'testToken',
algo: 'sha256',
log,
},
};

it('should handle successful authentication with quota', done => {
const mockAccountQuota = {
account: '123456789012',
quota: BigInt(1000),
};

const mockResponse = {
message: {
message: 'Success',
body: {
userInfo: mockUserInfo,
authorizationResults: [{
isAllowed: true,
isImplicit: false,
arn: mockUserInfo.arn,
action: 'testAction',
}],
accountQuota: mockAccountQuota,
},
},
};

mockClient.verifySignatureV4.callsFake(
(_stringToSign, _signature, _accessKey, _region, _scopeDate,
_options, callback) => {
callback(null, mockResponse);
}
);

vault.authenticateV4Request(mockParams, [], (err, data, results,
_params, infos) => {
assert.strictEqual(err, null);
assert(data instanceof AuthInfo);
assert.strictEqual(data.getCanonicalID(), mockUserInfo.canonicalID);
assert.deepStrictEqual(infos.accountQuota, mockAccountQuota);
done();
});
});

it('should handle authentication with missing quota', done => {
const mockResponse = {
message: {
message: 'Success',
body: {
userInfo: mockUserInfo,
authorizationResults: [{
isAllowed: true,
isImplicit: false,
arn: mockUserInfo.arn,
action: 'testAction',
}],
},
},
};

mockClient.verifySignatureV4.callsFake(
(_stringToSign, _signature, _accessKey, _region, _scopeDate,
_options, callback) => {
callback(null, mockResponse);
}
);

vault.authenticateV4Request(mockParams, [], (err, data, results,
_params, infos) => {
assert.strictEqual(err, null);
assert(data instanceof AuthInfo);
assert.deepStrictEqual(infos.accountQuota, {});
done();
});
});

it('should handle authentication failure', done => {
const mockError = new Error('Authentication failed');
mockClient.verifySignatureV4.callsFake(
(_stringToSign, _signature, _accessKey, _region, _scopeDate,
_options, callback) => {
callback(mockError);
}
);

vault.authenticateV4Request(mockParams, [], err => {
assert.strictEqual(err, mockError);
done();
});
});

it('should properly serialize request contexts', done => {
const mockRequestContexts = [{
serialize: () => ({ serialized: 'context' }),
}];

const mockResponse = {
message: {
message: 'Success',
body: {
userInfo: mockUserInfo,
authorizationResults: [{
isAllowed: true,
isImplicit: false,
arn: mockUserInfo.arn,
action: 'testAction',
}],
},
},
};

mockClient.verifySignatureV4.callsFake(
(_stringToSign, _signature, _accessKey, _region, _scopeDate,
options, callback) => {
assert.deepStrictEqual(options.requestContext,
[{ serialized: 'context' }]);
callback(null, mockResponse);
}
);

vault.authenticateV4Request(mockParams, mockRequestContexts,
(err, data) => {
assert.strictEqual(err, null);
assert(data instanceof AuthInfo);
done();
});
});

it('should handle quota with large numbers', done => {
const largeQuota = {
account: '123456789012',
quota: BigInt('9007199254740992'),
};

const mockResponse = {
message: {
message: 'Success',
body: {
userInfo: mockUserInfo,
authorizationResults: [{
isAllowed: true,
isImplicit: false,
arn: mockUserInfo.arn,
action: 'testAction',
}],
accountQuota: largeQuota,
},
},
};

mockClient.verifySignatureV4.callsFake(
(_stringToSign, _signature, _accessKey, _region, _scopeDate,
_options, callback) => {
callback(null, mockResponse);
}
);

vault.authenticateV4Request(mockParams, [], (err, _data, _results,
_params, infos) => {
assert.strictEqual(err, null);
assert.strictEqual(infos.accountQuota.quota.toString(),
'9007199254740992');
done();
});
});
});
});

0 comments on commit d00fa4c

Please sign in to comment.