update tests, add dependencies

Signed-off-by: Fabian Hippmann <fabian.hippmann@moonshiner.at>
This commit is contained in:
Fabian Hippmann
2021-07-06 15:13:25 +02:00
committed by Fredrik Adelöw
parent 876a5351d8
commit 1756d252ce
4 changed files with 74 additions and 110 deletions
+1 -1
View File
@@ -45,7 +45,7 @@
"express": "^4.17.1",
"express-promise-router": "^4.1.0",
"express-session": "^1.17.1",
"fs-extra": "9.1.0",
"google-auth-library": "^7.2.0",
"helmet": "^4.0.0",
"jose": "^1.27.1",
"jwt-decode": "^3.1.0",
@@ -15,33 +15,14 @@
*/
import { getVoidLogger } from '@backstage/backend-common';
import express from 'express';
import { JWT } from 'jose';
import { AwsAlbAuthProvider } from './provider';
import { GcpIAPProvider } from './provider';
import { AuthResponse } from '../types';
const jwtMock = JWT as jest.Mocked<any>;
jest.mock('google-auth-library');
const mockKey = async () => {
return `-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEnuN4LlaJhaUpx+qZFTzYCrSBLk0I
yOlxJ2VW88mLAQGJ7HPAvOdylxZsItMnzCuqNzZvie8m/NJsOjhDncVkrw==
-----END PUBLIC KEY-----
`;
};
jest.mock('jose');
jest.mock('cross-fetch', () => ({
__esModule: true,
default: async () => {
return {
text: async () => {
return mockKey();
},
};
},
}));
const setCredentialsMock = jest.fn();
const getAccessTokenMock = jest.fn();
const identityResolutionCallbackMock = async (): Promise<AuthResponse<any>> => {
return {
@@ -66,7 +47,7 @@ beforeEach(() => {
jest.clearAllMocks();
});
describe('AwsALBAuthProvider', () => {
describe('GcpIAPProvider', () => {
const catalogApi = {
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */
addLocation: jest.fn(),
@@ -98,16 +79,11 @@ describe('AwsALBAuthProvider', () => {
describe('should transform to type OAuthResponse', () => {
it('when JWT is valid and identity is resolved successfully', async () => {
const provider = new AwsAlbAuthProvider(getVoidLogger(), catalogApi, {
region: 'us-west-2',
const provider = new GcpIAPProvider(getVoidLogger(), catalogApi, {
identityResolutionCallback: identityResolutionCallbackMock,
issuer: 'foo',
audience: 'foo',
});
jwtMock.verify.mockImplementationOnce(() => ({
sub: 'foo',
}));
await provider.refresh(mockRequest, mockResponse);
expect(mockResponse.json).toHaveBeenCalledWith({
@@ -124,10 +100,9 @@ describe('AwsALBAuthProvider', () => {
});
describe('should fail when', () => {
it('JWT is missing', async () => {
const provider = new AwsAlbAuthProvider(getVoidLogger(), catalogApi, {
region: 'us-west-2',
const provider = new GcpIAPProvider(getVoidLogger(), catalogApi, {
identityResolutionCallback: identityResolutionCallbackMock,
issuer: 'foo',
audience: 'foo',
});
await provider.refresh(mockRequestWithoutJwt, mockResponse);
@@ -135,44 +110,12 @@ describe('AwsALBAuthProvider', () => {
expect(mockResponse.status).toHaveBeenCalledWith(401);
});
it('JWT is invalid', async () => {
const provider = new AwsAlbAuthProvider(getVoidLogger(), catalogApi, {
region: 'us-west-2',
identityResolutionCallback: identityResolutionCallbackMock,
issuer: 'foo',
});
jwtMock.verify.mockImplementationOnce(() => {
throw new Error('bad JWT');
});
await provider.refresh(mockRequest, mockResponse);
expect(mockResponse.status).toHaveBeenCalledWith(401);
});
it('issuer is invalid', async () => {
const provider = new AwsAlbAuthProvider(getVoidLogger(), catalogApi, {
region: 'us-west-2',
identityResolutionCallback: identityResolutionCallbackMock,
issuer: 'foobar',
});
jwtMock.verify.mockReturnValueOnce({});
await provider.refresh(mockRequest, mockResponse);
expect(mockResponse.status).toHaveBeenCalledWith(401);
});
it('identity resolution callback rejects', async () => {
const provider = new AwsAlbAuthProvider(getVoidLogger(), catalogApi, {
region: 'us-west-2',
const provider = new GcpIAPProvider(getVoidLogger(), catalogApi, {
identityResolutionCallback: identityResolutionCallbackRejectedMock,
issuer: 'foo',
audience: 'foo',
});
jwtMock.verify.mockReturnValueOnce({});
await provider.refresh(mockRequest, mockResponse);
expect(mockResponse.status).toHaveBeenCalledWith(401);
@@ -13,16 +13,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
AuthProviderFactoryOptions,
AuthProviderRouteHandlers,
AuthResponse
} from '@backstage/plugin-auth-backend';
import {
AuthProviderRouteHandlers,
AuthProviderFactoryOptions,
ExperimentalIdentityResolver,
} from '../types';
import express from 'express';
import { Logger } from 'winston';
import { CatalogApi } from '@backstage/catalog-client';
@@ -54,9 +53,12 @@ export class GcpIAPProvider implements AuthProviderRouteHandlers {
async refresh(req: express.Request, res: express.Response): Promise<void> {
const expectedAudience = this.options.audience;
const jwtToken = req.header(IAP_JWT_HEADER);
if (jwtToken === undefined) {
res.status(401);
res.end();
return;
}
const oAuth2Client = new OAuth2Client();
const verify = async () => {
const response = await oAuth2Client.getIapPublicKeys();
@@ -64,30 +66,32 @@ export class GcpIAPProvider implements AuthProviderRouteHandlers {
jwtToken,
response.pubkeys,
expectedAudience,
['https://cloud.google.com/iap']
['https://cloud.google.com/iap'],
);
return ticket.payload;
}
return ticket.getPayload();
};
try {
const user = await verify();
if (user === undefined) {
this.logger.error('gcp iap proxy user returned undefined');
res.status(401);
res.end();
return;
}
const resolvedEntity = await this.options.identityResolutionCallback(
{
email: user.email
email: user.email,
},
this.catalogClient,
);
res.json(resolvedEntity);
} catch (e) {
const resolvedEntity = await this.options.identityResolutionCallback(
{},
this.catalogClient,
);
res.json(resolvedEntity);
this.logger.error('Verification failed with', e);
res.status(401);
res.end();
return;
}
res.status(200);
res.end();
@@ -98,7 +102,6 @@ export class GcpIAPProvider implements AuthProviderRouteHandlers {
}
}
export const createGcpIAPProvider = (_options?: GcpIAPProviderOptions) => {
return ({
logger,