Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improvement: Add client credentials location parameter for Client Credentials OAuth flow #9730

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
82 changes: 50 additions & 32 deletions src/core/components/auth/oauth2.jsx
Expand Up @@ -26,6 +26,7 @@ export default class Oauth2 extends React.Component {
let clientId = auth && auth.get("clientId") || authConfigs.clientId || ""
let clientSecret = auth && auth.get("clientSecret") || authConfigs.clientSecret || ""
let passwordType = auth && auth.get("passwordType") || "basic"
let clientCredentialsLocation = auth && auth.get("clientCredentialsLocation") || "header"
let scopes = auth && auth.get("scopes") || authConfigs.scopes || []
if (typeof scopes === "string") {
scopes = scopes.split(authConfigs.scopeSeparator || " ")
Expand All @@ -40,7 +41,8 @@ export default class Oauth2 extends React.Component {
clientSecret: clientSecret,
username: username,
password: "",
passwordType: passwordType
passwordType: passwordType,
clientCredentialsLocation: clientCredentialsLocation
}
}

Expand Down Expand Up @@ -199,34 +201,50 @@ export default class Oauth2 extends React.Component {
<label htmlFor={ `client_id_${flow}` }>client_id:</label>
{
isAuthorized ? <code> ****** </code>
: <Col tablet={10} desktop={10}>
<InitializedInput id={`client_id_${flow}`}
type="text"
required={ flow === AUTH_FLOW_PASSWORD }
initialValue={ this.state.clientId }
data-name="clientId"
onChange={ this.onInputChange }/>
</Col>
: <Col tablet={10} desktop={10}>
<InitializedInput id={`client_id_${flow}`}
type="text"
required={ flow === AUTH_FLOW_PASSWORD }
initialValue={ this.state.clientId }
data-name="clientId"
onChange={ this.onInputChange }/>
</Col>
}
</Row>
}

{
( (flow === AUTH_FLOW_APPLICATION || flow === AUTH_FLOW_ACCESS_CODE || flow === AUTH_FLOW_PASSWORD) && <Row>
<label htmlFor={ `client_secret_${flow}` }>client_secret:</label>
{
isAuthorized ? <code> ****** </code>
: <Col tablet={10} desktop={10}>
<InitializedInput id={ `client_secret_${flow}` }
<label htmlFor={ `client_secret_${flow}` }>client_secret:</label>
{
isAuthorized ? <code> ****** </code>
: <Col tablet={10} desktop={10}>
<InitializedInput id={ `client_secret_${flow}` }
initialValue={ this.state.clientSecret }
type="password"
data-name="clientSecret"
onChange={ this.onInputChange }/>
</Col>
}
</Col>
}

</Row>
)}
</Row>
)}

{
flow !== AUTH_FLOW_APPLICATION ? null
: <Row>
<label htmlFor="client_credentials_location">Client credentials location:</label>
{
isAuthorized ? <code> { this.state.clientCredentialsLocation } </code>
: <Col tablet={10} desktop={10}>
<select id="client_credentials_location" data-name="clientCredentialsLocation" onChange={ this.onInputChange }>
<option value="header">Authorization header</option>
<option value="request-body">Request body</option>
</select>
</Col>
}
</Row>
}

{
!isAuthorized && scopes && scopes.size ? <div className="scopes">
Expand All @@ -240,22 +258,22 @@ export default class Oauth2 extends React.Component {
<Row key={ name }>
<div className="checkbox">
<Input data-value={ name }
id={`${name}-${flow}-checkbox-${this.state.name}`}
id={`${name}-${flow}-checkbox-${this.state.name}`}
disabled={ isAuthorized }
checked={ this.state.scopes.includes(name) }
type="checkbox"
onChange={ this.onScopeChange }/>
<label htmlFor={`${name}-${flow}-checkbox-${this.state.name}`}>
<span className="item"></span>
<div className="text">
<p className="name">{name}</p>
<p className="description">{description}</p>
</div>
</label>
<label htmlFor={`${name}-${flow}-checkbox-${this.state.name}`}>
<span className="item"></span>
<div className="text">
<p className="name">{name}</p>
<p className="description">{description}</p>
</div>
</label>
</div>
</Row>
)
}).toArray()
}).toArray()
}
</div> : null
}
Expand All @@ -267,11 +285,11 @@ export default class Oauth2 extends React.Component {
} )
}
<div className="auth-btn-wrapper">
{ isValid &&
( isAuthorized ? <Button className="btn modal-btn auth authorize" onClick={ this.logout } aria-label="Remove authorization">Logout</Button>
: <Button className="btn modal-btn auth authorize" onClick={ this.authorize } aria-label="Apply given OAuth2 credentials">Authorize</Button>
)
}
{ isValid &&
( isAuthorized ? <Button className="btn modal-btn auth authorize" onClick={ this.logout } aria-label="Remove authorization">Logout</Button>
: <Button className="btn modal-btn auth authorize" onClick={ this.authorize } aria-label="Apply given OAuth2 credentials">Authorize</Button>
)
}
<Button className="btn modal-btn auth btn-done" onClick={ this.close }>Close</Button>
</div>

Expand Down
111 changes: 60 additions & 51 deletions src/core/plugins/auth/actions.js
Expand Up @@ -125,14 +125,23 @@ function setClientIdAndSecret(target, clientId, clientSecret) {
}

export const authorizeApplication = ( auth ) => ( { authActions } ) => {
let { schema, scopes, name, clientId, clientSecret } = auth
let headers = {
Authorization: "Basic " + btoa(clientId + ":" + clientSecret)
}
let { schema, scopes, name, clientId, clientSecret, clientCredentialsLocation } = auth

let form = {
grant_type: "client_credentials",
scope: scopes.join(scopeSeparator)
}
let headers = {}

switch (clientCredentialsLocation) {
case "request-body":
setClientIdAndSecret(form, clientId, clientSecret)
break

case "header":
headers.Authorization = "Basic " + btoa(clientId + ":" + clientSecret)
break
}

return authActions.authorizeRequest({body: buildFormData(form), name, url: schema.get("tokenUrl"), auth, headers })
}
Expand Down Expand Up @@ -202,59 +211,59 @@ export const authorizeRequest = ( data ) => ( { fn, getConfigs, authActions, err
requestInterceptor: getConfigs().requestInterceptor,
responseInterceptor: getConfigs().responseInterceptor
})
.then(function (response) {
let token = JSON.parse(response.data)
let error = token && ( token.error || "" )
let parseError = token && ( token.parseError || "" )
.then(function (response) {
let token = JSON.parse(response.data)
let error = token && ( token.error || "" )
let parseError = token && ( token.parseError || "" )

if ( !response.ok ) {
errActions.newAuthErr( {
authId: name,
level: "error",
source: "auth",
message: response.statusText
} )
return
}

if ( error || parseError ) {
errActions.newAuthErr({
authId: name,
level: "error",
source: "auth",
message: JSON.stringify(token)
})
return
}

if ( !response.ok ) {
authActions.authorizeOauth2WithPersistOption({ auth, token})
})
.catch(e => {
let err = new Error(e)
let message = err.message
// swagger-js wraps the response (if available) into the e.response property;
// investigate to check whether there are more details on why the authorization
// request failed (according to RFC 6479).
// See also https://github.com/swagger-api/swagger-ui/issues/4048
if (e.response && e.response.data) {
const errData = e.response.data
try {
const jsonResponse = typeof errData === "string" ? JSON.parse(errData) : errData
if (jsonResponse.error)
message += `, error: ${jsonResponse.error}`
if (jsonResponse.error_description)
message += `, description: ${jsonResponse.error_description}`
} catch (jsonError) {
// Ignore
}
}
errActions.newAuthErr( {
authId: name,
level: "error",
source: "auth",
message: response.statusText
message: message
} )
return
}

if ( error || parseError ) {
errActions.newAuthErr({
authId: name,
level: "error",
source: "auth",
message: JSON.stringify(token)
})
return
}

authActions.authorizeOauth2WithPersistOption({ auth, token})
})
.catch(e => {
let err = new Error(e)
let message = err.message
// swagger-js wraps the response (if available) into the e.response property;
// investigate to check whether there are more details on why the authorization
// request failed (according to RFC 6479).
// See also https://github.com/swagger-api/swagger-ui/issues/4048
if (e.response && e.response.data) {
const errData = e.response.data
try {
const jsonResponse = typeof errData === "string" ? JSON.parse(errData) : errData
if (jsonResponse.error)
message += `, error: ${jsonResponse.error}`
if (jsonResponse.error_description)
message += `, description: ${jsonResponse.error_description}`
} catch (jsonError) {
// Ignore
}
}
errActions.newAuthErr( {
authId: name,
level: "error",
source: "auth",
message: message
} )
})
})
}

export function configureAuth(payload) {
Expand Down