update tests, add dependencies
Signed-off-by: Fabian Hippmann <fabian.hippmann@moonshiner.at>
This commit is contained in:
committed by
Fredrik Adelöw
parent
876a5351d8
commit
1756d252ce
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user