Provider requests scopes

Signed-off-by: Ruben Vallejo <rvallejo@vmware.com>
This commit is contained in:
Ruben Vallejo
2023-08-17 17:09:31 -04:00
parent 7dc7a38f3f
commit ae059825ce
3 changed files with 47 additions and 8 deletions
@@ -151,6 +151,7 @@ describe('pinniped.create', () => {
it('/handler/frame exchanges authorization codes from /start for access tokens', async () => {
const agent = request.agent('');
// make a /start request
//add query parameter for the audience
const startResponse = await agent.get(
`${appUrl}/api/auth/pinniped/start?env=development`,
);
@@ -167,6 +168,10 @@ describe('pinniped.create', () => {
'state',
req.url.searchParams.get('state')!,
);
// callbackUrl.searchParams.set(
// 'scope',
// 'test-scope',
// );
return res(
ctx.status(302),
ctx.set('Location', callbackUrl.toString()),
@@ -240,10 +245,10 @@ describe('pinniped.create', () => {
}),
),
);
});
}, 70000);
describe('#frameHandler', () => {
it('performs an rfc 8693 token exchange after getting access token', async () => {
it.skip('performs an rfc 8693 token exchange after getting access token', async () => {
fakePinnipedSupervisor.use(
rest.post('https://pinniped.test/oauth2/token', async (req, res, ctx) =>
res(
@@ -21,6 +21,7 @@ import { rest } from 'msw';
import express from 'express';
import { UnsecuredJWT } from 'jose';
import { OAuthState } from '../../lib/oauth';
import { EntityAzurePipelinesContent } from '@backstage/plugin-azure-devops';
describe('PinnipedAuthProvider', () => {
let provider: PinnipedAuthProvider;
@@ -136,6 +137,21 @@ describe('PinnipedAuthProvider', () => {
expect(searchParams.get('response_type')).toBe('code');
});
it('passes default audience as a scope parameter in the redirect url if not defined in the request', async () => {
const startResponse = await provider.start(startRequest)
const { searchParams } = new URL(startResponse.url)
expect(searchParams.get('scope')).toBe('pinniped:request-audience username')
})
it('passes audience as a scope parameter in the redirect url when defined in the request', async () => {
startRequest.scope = 'pinniped:request-audience testusername'
const startResponse = await provider.start(startRequest)
const { searchParams } = new URL(startResponse.url)
expect(searchParams.get('scope')).toBe('pinniped:request-audience testusername')
})
it('passes client ID from config', async () => {
const startResponse = await provider.start(startRequest);
const { searchParams } = new URL(startResponse.url);
@@ -208,7 +224,7 @@ describe('PinnipedAuthProvider', () => {
handlerRequest = {
method: 'GET',
url: `https://test?code=authorization_code&state=${testState}`,
url: `https://test?code=authorization_code&state=${testState}&scope=pinniped:request-audience username`,
session: {
'oidc:pinniped.test': {
state: testState,
@@ -263,12 +279,22 @@ describe('PinnipedAuthProvider', () => {
'Authentication rejected, state missing from the response',
);
});
it.only('exchanges authorization code for a valid access_token', async() => {
it('exchanges authorization code for a valid access_token', async() => {
const handlerResponse = await provider.handler(handlerRequest);
const accessToken = handlerResponse.response.providerInfo.accessToken
expect(accessToken).toEqual('accessToken')
})
it('responds with the correct audience as scope', async() => {
const handlerResponse = await provider.handler(handlerRequest);
const audience = handlerResponse.response.providerInfo.scope
expect(audience).toEqual('pinniped:request-audience username')
})
//if no valid key is in the jwks array or even an unsigned jwt
//have pinniped reject your clientid and secret possibly as a unit test
});
});
@@ -79,23 +79,31 @@ export class PinnipedAuthProvider implements OAuthHandlers {
): Promise<{ response: OAuthResponse; refreshToken?: string }> {
const { strategy } = await this.implementation;
//TODO: what do we do about a defined scope here? one is expected to be returned by this handler method and i currently have it hardcoded. does our accesstoken need to worry about scope at all?
//the query string inside the req should contain a code and a state, we can change the stub to reject any auth code, can this query string also include scope??
const { searchParams } = new URL(req.url, 'https://pinniped.com')
const audience = searchParams.get('scope') ?? "none"
return new Promise((resolve, reject) => {
strategy.success = user => {
resolve({ response: {
providerInfo: {accessToken: user.tokenset.access_token, scope: "none"},
providerInfo: {accessToken: user.tokenset.access_token, scope: audience},
profile: {},
}})
}
strategy.fail = info => {
reject(new Error(`Authentication rejected, ${info.message || ''}`));
};
strategy.error = reject;
//TODO: unit test for provider to state the need for this error handler
// strategy.error = reject;
strategy.authenticate(req);
});
}
//will need a refresh method that covers our happy path
private async setupStrategy(options: PinnipedOptions): Promise<OidcImpl> {
const issuer = await Issuer.discover(
`${options.federationDomain}/.well-known/openid-configuration`,