Add resource param to client credential oauth grant. (#1174)

Addresses #716
This commit is contained in:
Devin Carr 2018-09-26 16:03:40 -07:00 committed by Gregory Schier
parent a921c9c054
commit 9413481b2e
5 changed files with 76 additions and 135 deletions

View File

@ -10,6 +10,8 @@ const ACCESS_TOKEN_URL = 'https://foo.com/access_token';
const CLIENT_ID = 'client_123';
const CLIENT_SECRET = 'secret_12345456677756343';
const SCOPE = 'scope_123';
const AUDIENCE = 'https://foo.com/userinfo';
const RESOURCE = 'https://foo.com/resource';
describe('client_credentials', () => {
beforeEach(globalBeforeEach);
@ -21,7 +23,9 @@ describe('client_credentials', () => {
JSON.stringify({
access_token: 'token_123',
token_type: 'token_type',
scope: SCOPE
scope: SCOPE,
audience: AUDIENCE,
resource: RESOURCE
})
);
@ -39,7 +43,9 @@ describe('client_credentials', () => {
false,
CLIENT_ID,
CLIENT_SECRET,
SCOPE
SCOPE,
AUDIENCE,
RESOURCE
);
// Check the request to fetch the token
@ -53,7 +59,9 @@ describe('client_credentials', () => {
mimeType: 'application/x-www-form-urlencoded',
params: [
{ name: 'grant_type', value: 'client_credentials' },
{ name: 'scope', value: SCOPE }
{ name: 'scope', value: SCOPE },
{ name: 'audience', value: AUDIENCE },
{ name: 'resource', value: RESOURCE }
]
},
headers: [
@ -80,6 +88,8 @@ describe('client_credentials', () => {
expires_in: null,
token_type: 'token_type',
scope: SCOPE,
audience: AUDIENCE,
resource: RESOURCE,
error: null,
error_uri: null,
error_description: null,
@ -95,7 +105,9 @@ describe('client_credentials', () => {
JSON.stringify({
access_token: 'token_123',
token_type: 'token_type',
scope: SCOPE
scope: SCOPE,
audience: AUDIENCE,
resource: RESOURCE
})
);
@ -104,9 +116,7 @@ describe('client_credentials', () => {
bodyCompression: '',
parentId: 'req_1',
statusCode: 200,
headers: [
{ name: 'Content-Type', value: 'application/x-www-form-urlencoded' }
]
headers: [{ name: 'Content-Type', value: 'application/x-www-form-urlencoded' }]
}));
const result = await getToken(
@ -115,7 +125,9 @@ describe('client_credentials', () => {
true,
CLIENT_ID,
CLIENT_SECRET,
SCOPE
SCOPE,
AUDIENCE,
RESOURCE
);
// Check the request to fetch the token
@ -130,6 +142,8 @@ describe('client_credentials', () => {
params: [
{ name: 'grant_type', value: 'client_credentials' },
{ name: 'scope', value: SCOPE },
{ name: 'audience', value: AUDIENCE },
{ name: 'resource', value: RESOURCE },
{ name: 'client_id', value: CLIENT_ID },
{ name: 'client_secret', value: CLIENT_SECRET }
]
@ -154,6 +168,8 @@ describe('client_credentials', () => {
expires_in: null,
token_type: 'token_type',
scope: SCOPE,
audience: AUDIENCE,
resource: RESOURCE,
error: null,
error_uri: null,
error_description: null,

View File

@ -13,6 +13,7 @@ export const P_ACCESS_TOKEN = 'access_token';
export const P_CLIENT_ID = 'client_id';
export const P_CLIENT_SECRET = 'client_secret';
export const P_AUDIENCE = 'audience';
export const P_RESOURCE = 'resource';
export const P_CODE = 'code';
export const P_NONCE = 'nonce';
export const P_ERROR = 'error';

View File

@ -30,17 +30,9 @@ export default async function(
): Promise<OAuth2Token | null> {
switch (authentication.grantType) {
case GRANT_TYPE_AUTHORIZATION_CODE:
return _getOAuth2AuthorizationCodeHeader(
requestId,
authentication,
forceRefresh
);
return _getOAuth2AuthorizationCodeHeader(requestId, authentication, forceRefresh);
case GRANT_TYPE_CLIENT_CREDENTIALS:
return _getOAuth2ClientCredentialsHeader(
requestId,
authentication,
forceRefresh
);
return _getOAuth2ClientCredentialsHeader(requestId, authentication, forceRefresh);
case GRANT_TYPE_IMPLICIT:
return _getOAuth2ImplicitHeader(requestId, authentication, forceRefresh);
case GRANT_TYPE_PASSWORD:
@ -55,11 +47,7 @@ async function _getOAuth2AuthorizationCodeHeader(
authentication: RequestAuthentication,
forceRefresh: boolean
): Promise<OAuth2Token | null> {
const oAuth2Token = await _getAccessToken(
requestId,
authentication,
forceRefresh
);
const oAuth2Token = await _getAccessToken(requestId, authentication, forceRefresh);
if (oAuth2Token) {
return oAuth2Token;
@ -85,11 +73,7 @@ async function _getOAuth2ClientCredentialsHeader(
authentication: RequestAuthentication,
forceRefresh: boolean
): Promise<OAuth2Token | null> {
const oAuth2Token = await _getAccessToken(
requestId,
authentication,
forceRefresh
);
const oAuth2Token = await _getAccessToken(requestId, authentication, forceRefresh);
if (oAuth2Token) {
return oAuth2Token;
@ -102,7 +86,8 @@ async function _getOAuth2ClientCredentialsHeader(
authentication.clientId,
authentication.clientSecret,
authentication.scope,
authentication.audience
authentication.audience,
authentication.resource
);
return _updateOAuth2Token(requestId, results);
@ -113,11 +98,7 @@ async function _getOAuth2ImplicitHeader(
authentication: RequestAuthentication,
forceRefresh: boolean
): Promise<OAuth2Token | null> {
const oAuth2Token = await _getAccessToken(
requestId,
authentication,
forceRefresh
);
const oAuth2Token = await _getAccessToken(requestId, authentication, forceRefresh);
if (oAuth2Token) {
return oAuth2Token;
@ -142,11 +123,7 @@ async function _getOAuth2PasswordHeader(
authentication: RequestAuthentication,
forceRefresh: boolean
): Promise<OAuth2Token | null> {
const oAuth2Token = await _getAccessToken(
requestId,
authentication,
forceRefresh
);
const oAuth2Token = await _getAccessToken(requestId, authentication, forceRefresh);
if (oAuth2Token) {
return oAuth2Token;
@ -175,9 +152,7 @@ async function _getAccessToken(
// See if we have a token already //
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
let token: OAuth2Token | null = await models.oAuth2Token.getByParentId(
requestId
);
let token: OAuth2Token | null = await models.oAuth2Token.getByParentId(requestId);
if (!token) {
return null;
@ -222,10 +197,7 @@ async function _getAccessToken(
return _updateOAuth2Token(requestId, refreshResults);
}
async function _updateOAuth2Token(
requestId: string,
authResults: Object
): Promise<OAuth2Token> {
async function _updateOAuth2Token(requestId: string, authResults: Object): Promise<OAuth2Token> {
const oAuth2Token = await models.oAuth2Token.getOrCreateByParentId(requestId);
// Calculate expiry date

View File

@ -13,15 +13,15 @@ export default async function(
clientId: string,
clientSecret: string,
scope: string = '',
audience: string = ''
audience: string = '',
resource: string = ''
): Promise<Object> {
const params = [
{ name: c.P_GRANT_TYPE, value: c.GRANT_TYPE_CLIENT_CREDENTIALS }
];
const params = [{ name: c.P_GRANT_TYPE, value: c.GRANT_TYPE_CLIENT_CREDENTIALS }];
// Add optional params
scope && params.push({ name: c.P_SCOPE, value: scope });
audience && params.push({ name: c.P_AUDIENCE, value: audience });
resource && params.push({ name: c.P_RESOURCE, value: resource });
const headers = [
{ name: 'Content-Type', value: 'application/x-www-form-urlencoded' },
@ -70,6 +70,8 @@ export default async function(
c.P_TOKEN_TYPE,
c.P_EXPIRES_IN,
c.P_SCOPE,
c.P_AUDIENCE,
c.P_RESOURCE,
c.P_ERROR,
c.P_ERROR_URI,
c.P_ERROR_DESCRIPTION

View File

@ -71,9 +71,7 @@ class OAuth2Auth extends React.PureComponent<Props, State> {
this.setState({ showAdvanced });
}
async _handleUpdateAccessToken(
e: SyntheticEvent<HTMLInputElement>
): Promise<void> {
async _handleUpdateAccessToken(e: SyntheticEvent<HTMLInputElement>): Promise<void> {
const { oAuth2Token } = this.props;
const accessToken = e.currentTarget.value;
@ -87,9 +85,7 @@ class OAuth2Auth extends React.PureComponent<Props, State> {
}
}
async _handleUpdateRefreshToken(
e: SyntheticEvent<HTMLInputElement>
): Promise<void> {
async _handleUpdateRefreshToken(e: SyntheticEvent<HTMLInputElement>): Promise<void> {
const { oAuth2Token } = this.props;
const refreshToken = e.currentTarget.value;
@ -104,9 +100,7 @@ class OAuth2Auth extends React.PureComponent<Props, State> {
}
async _handleClearTokens(): Promise<void> {
const oAuth2Token = await models.oAuth2Token.getByParentId(
this.props.request._id
);
const oAuth2Token = await models.oAuth2Token.getByParentId(this.props.request._id);
if (oAuth2Token) {
await models.oAuth2Token.remove(oAuth2Token);
}
@ -129,9 +123,7 @@ class OAuth2Auth extends React.PureComponent<Props, State> {
const { request } = this.props;
try {
const authentication = await this.props.handleRender(
request.authentication
);
const authentication = await this.props.handleRender(request.authentication);
await getAccessToken(request._id, authentication, true);
this.setState({ loading: false });
} catch (err) {
@ -157,10 +149,7 @@ class OAuth2Auth extends React.PureComponent<Props, State> {
}
_handleChangeCredentialsInBody(e: SyntheticEvent<HTMLInputElement>): void {
this._handleChangeProperty(
'credentialsInBody',
e.currentTarget.value === 'true'
);
this._handleChangeProperty('credentialsInBody', e.currentTarget.value === 'true');
}
_handleChangeEnabled(value: boolean): void {
@ -207,6 +196,10 @@ class OAuth2Auth extends React.PureComponent<Props, State> {
this._handleChangeProperty('audience', value);
}
_handleChangeResource(value: string): void {
this._handleChangeProperty('resource', value);
}
_handleChangeGrantType(e: SyntheticEvent<HTMLInputElement>): void {
this._handleChangeProperty('grantType', e.currentTarget.value);
}
@ -248,18 +241,10 @@ class OAuth2Auth extends React.PureComponent<Props, State> {
help: string | null = null,
handleAutocomplete: Function | null = null
): React.Element<*> {
const {
handleRender,
handleGetRenderContext,
request,
nunjucksPowerUserMode
} = this.props;
const { handleRender, handleGetRenderContext, request, nunjucksPowerUserMode } = this.props;
const { authentication } = request;
const id = label.replace(/ /g, '-');
const type =
!this.props.showPasswords && property === 'password'
? 'password'
: 'text';
const type = !this.props.showPasswords && property === 'password' ? 'password' : 'text';
return (
<tr key={id}>
<td className="pad-right no-wrap valign-middle">
@ -270,12 +255,9 @@ class OAuth2Auth extends React.PureComponent<Props, State> {
</td>
<td className="wide">
<div
className={classnames(
'form-control form-control--underlined no-margin',
{
'form-control--inactive': authentication.disabled
}
)}>
className={classnames('form-control form-control--underlined no-margin', {
'form-control--inactive': authentication.disabled
})}>
<OneLineEditor
id={id}
type={type}
@ -316,12 +298,9 @@ class OAuth2Auth extends React.PureComponent<Props, State> {
</td>
<td className="wide">
<div
className={classnames(
'form-control form-control--outlined no-margin',
{
'form-control--inactive': authentication.disabled
}
)}>
className={classnames('form-control form-control--outlined no-margin', {
'form-control--inactive': authentication.disabled
})}>
<select id={id} onChange={onChange} value={value}>
{options.map(({ name, value }) => (
<option key={value} value={value}>
@ -341,11 +320,7 @@ class OAuth2Auth extends React.PureComponent<Props, State> {
let basicFields = [];
let advancedFields = [];
const clientId = this.renderInputRow(
'Client ID',
'clientId',
this._handleChangeClientId
);
const clientId = this.renderInputRow('Client ID', 'clientId', this._handleChangeClientId);
const clientSecret = this.renderInputRow(
'Client Secret',
@ -378,29 +353,13 @@ class OAuth2Auth extends React.PureComponent<Props, State> {
'redirected URL'
);
const state = this.renderInputRow(
'State',
'state',
this._handleChangeState
);
const state = this.renderInputRow('State', 'state', this._handleChangeState);
const scope = this.renderInputRow(
'Scope',
'scope',
this._handleChangeScope
);
const scope = this.renderInputRow('Scope', 'scope', this._handleChangeScope);
const username = this.renderInputRow(
'Username',
'username',
this._handleChangeUsername
);
const username = this.renderInputRow('Username', 'username', this._handleChangeUsername);
const password = this.renderInputRow(
'Password',
'password',
this._handleChangePassword
);
const password = this.renderInputRow('Password', 'password', this._handleChangePassword);
const tokenPrefix = this.renderInputRow(
'Header Prefix',
@ -428,6 +387,13 @@ class OAuth2Auth extends React.PureComponent<Props, State> {
'Indicate what resource server to access'
);
const resource = this.renderInputRow(
'Resource',
'resource',
this._handleChangeResource,
'Indicate what resource to access'
);
const credentialsInBody = this.renderSelectRow(
'Credentials',
'credentialsInBody',
@ -455,16 +421,9 @@ class OAuth2Auth extends React.PureComponent<Props, State> {
} else if (grantType === GRANT_TYPE_CLIENT_CREDENTIALS) {
basicFields = [accessTokenUrl, clientId, clientSecret, enabled];
advancedFields = [scope, credentialsInBody, tokenPrefix, audience];
advancedFields = [scope, credentialsInBody, tokenPrefix, audience, resource];
} else if (grantType === GRANT_TYPE_PASSWORD) {
basicFields = [
username,
password,
accessTokenUrl,
clientId,
clientSecret,
enabled
];
basicFields = [username, password, accessTokenUrl, clientId, clientSecret, enabled];
advancedFields = [scope, credentialsInBody, tokenPrefix];
} else if (grantType === GRANT_TYPE_IMPLICIT) {
@ -476,9 +435,7 @@ class OAuth2Auth extends React.PureComponent<Props, State> {
return { basic: basicFields, advanced: advancedFields };
}
static renderExpireAt(
token: OAuth2Token | null
): React.Element<*> | string | null {
static renderExpireAt(token: OAuth2Token | null): React.Element<*> | string | null {
if (!token || !token.accessToken) {
return null;
}
@ -509,10 +466,7 @@ class OAuth2Auth extends React.PureComponent<Props, State> {
const errorUriButton =
oAuth2Token && oAuth2Token.errorUri ? (
<Link
href={oAuth2Token.errorUri}
title={oAuth2Token.errorUri}
className="space-left icon">
<Link href={oAuth2Token.errorUri} title={oAuth2Token.errorUri} className="space-left icon">
<i className="fa fa-question-circle" />
</Link>
) : null;
@ -584,9 +538,7 @@ class OAuth2Auth extends React.PureComponent<Props, State> {
</tbody>
</table>
<div className="notice subtle margin-top text-left">
{error && (
<p className="selectable notice warning margin-bottom">{error}</p>
)}
{error && <p className="selectable notice warning margin-bottom">{error}</p>}
{this.renderError()}
<div className="form-control form-control--outlined">
<label>
@ -610,9 +562,7 @@ class OAuth2Auth extends React.PureComponent<Props, State> {
</div>
<div className="pad-top text-right">
{tok ? (
<PromptButton
className="btn btn--clicky"
onClick={this._handleClearTokens}>
<PromptButton className="btn btn--clicky" onClick={this._handleClearTokens}>
Clear
</PromptButton>
) : null}