Skip to content

Commit 275c8f2

Browse files
authored
improvement: oauth "scopes" improvements (#6037)
* improvement: oauth "scopes" init parameter * improvement: add "select all" and "select none" to oauth scopes popup
1 parent 4497387 commit 275c8f2

File tree

9 files changed

+62
-4
lines changed

9 files changed

+62
-4
lines changed

dev-helpers/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
realm: "your-realms",
6363
appName: "your-app-name",
6464
scopeSeparator: " ",
65+
scopes: "openid profile email phone address",
6566
additionalQueryStringParams: {},
6667
usePkceWithAuthorizationCodeGrant: false
6768
})

docker/configurator/oauth.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ const oauthBlockSchema = {
2323
type: "string",
2424
name: "scopeSeparator"
2525
},
26+
OAUTH_SCOPES: {
27+
type: "string",
28+
name: "scopes"
29+
},
2630
OAUTH_ADDITIONAL_PARAMS: {
2731
type: "object",
2832
name: "additionalQueryStringParams"
@@ -44,4 +48,4 @@ ${indent(translatorResult, 2)}
4448
}
4549

4650
return ``
47-
}
51+
}

docs/usage/oauth2.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ clientSecret | `OAUTH_CLIENT_SECRET` | **🚨 Never use this parameter in your p
88
realm | `OAUTH_REALM` |realm query parameter (for oauth1) added to `authorizationUrl` and `tokenUrl`. MUST be a string
99
appName | `OAUTH_APP_NAME` |application name, displayed in authorization popup. MUST be a string
1010
scopeSeparator | `OAUTH_SCOPE_SEPARATOR` |scope separator for passing scopes, encoded before calling, default value is a space (encoded value `%20`). MUST be a string
11+
scopes | `OAUTH_SCOPES` |string array or scope separator (i.e. space) separated string of initially selected oauth scopes, default is empty array
1112
additionalQueryStringParams | `OAUTH_ADDITIONAL_PARAMS` |Additional query parameters added to `authorizationUrl` and `tokenUrl`. MUST be an object
1213
useBasicAuthenticationWithAccessCodeGrant | _Unavailable_ |Only activated for the `accessCode` flow. During the `authorization_code` request to the `tokenUrl`, pass the [Client Password](https://tools.ietf.org/html/rfc6749#section-2.3.1) using the HTTP Basic Authentication scheme (`Authorization` header with `Basic base64encode(client_id + client_secret)`). The default is `false`
1314
usePkceWithAuthorizationCodeGrant | `OAUTH_USE_PKCE` | Only applies to `authorizatonCode` flows. [Proof Key for Code Exchange](https://tools.ietf.org/html/rfc7636) brings enhanced security for OAuth public clients. The default is `false`
@@ -22,6 +23,7 @@ ui.initOAuth({
2223
realm: "your-realms",
2324
appName: "your-app-name",
2425
scopeSeparator: " ",
26+
scopes: "openid profile",
2527
additionalQueryStringParams: {test: "hello"},
2628
usePkceWithAuthorizationCodeGrant: true
2729
})

src/core/components/auth/oauth2.jsx

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,16 @@ export default class Oauth2 extends React.Component {
2525
let clientId = auth && auth.get("clientId") || authConfigs.clientId || ""
2626
let clientSecret = auth && auth.get("clientSecret") || authConfigs.clientSecret || ""
2727
let passwordType = auth && auth.get("passwordType") || "basic"
28+
let scopes = auth && auth.get("scopes") || authConfigs.scopes || []
29+
if (typeof scopes === "string") {
30+
scopes = scopes.split(authConfigs.scopeSeparator || " ")
31+
}
2832

2933
this.state = {
3034
appName: authConfigs.appName,
3135
name: name,
3236
schema: schema,
33-
scopes: [],
37+
scopes: scopes,
3438
clientId: clientId,
3539
clientSecret: clientSecret,
3640
username: username,
@@ -77,6 +81,16 @@ export default class Oauth2 extends React.Component {
7781
this.setState(state)
7882
}
7983

84+
selectScopes =(e) => {
85+
if (e.target.dataset.all) {
86+
this.setState({
87+
scopes: Array.from((this.props.schema.get("allowedScopes") || this.props.schema.get("scopes")).keys())
88+
})
89+
} else {
90+
this.setState({ scopes: [] })
91+
}
92+
}
93+
8094
logout =(e) => {
8195
e.preventDefault()
8296
let { authActions, errActions, name } = this.props
@@ -201,14 +215,19 @@ export default class Oauth2 extends React.Component {
201215

202216
{
203217
!isAuthorized && scopes && scopes.size ? <div className="scopes">
204-
<h2>Scopes:</h2>
218+
<h2>
219+
Scopes:
220+
<a onClick={this.selectScopes} data-all={true}>select all</a>
221+
<a onClick={this.selectScopes}>select none</a>
222+
</h2>
205223
{ scopes.map((description, name) => {
206224
return (
207225
<Row key={ name }>
208226
<div className="checkbox">
209227
<Input data-value={ name }
210228
id={`${name}-${flow}-checkbox-${this.state.name}`}
211229
disabled={ isAuthorized }
230+
checked={ this.state.scopes.includes(name) }
212231
type="checkbox"
213232
onChange={ this.onScopeChange }/>
214233
<label htmlFor={`${name}-${flow}-checkbox-${this.state.name}`}>

src/style/_authorize.scss

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,15 @@
7676
font-size: 14px;
7777

7878
@include text_headline();
79+
80+
a
81+
{
82+
font-size: 12px;
83+
color: $auth-select-all-none-link-font-color;
84+
cursor: pointer;
85+
padding-left: 10px;
86+
text-decoration: underline;
87+
}
7988
}
8089
}
8190

src/style/_variables.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ $_color-options: #0d5aa7 !default;
5454
// Authorize
5555

5656
$auth-container-border-color: $gray-50 !default;
57-
57+
$auth-select-all-none-link-font-color: $color-info !default;
5858
// Buttons
5959

6060
$btn-background-color: transparent !default;

test/e2e-selenium/pages/main.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,18 @@ module.exports = {
7070
authorizationUrl: {
7171
selector: ".auth-container code"
7272
},
73+
readPetsScope: {
74+
selector: "input[data-value='read:pets']"
75+
},
76+
writePetsScope: {
77+
selector: "input[data-value='write:pets']"
78+
},
79+
selectAllScopes: {
80+
selector: ".auth-container h2 a[data-all]"
81+
},
82+
selectNoneScopes: {
83+
selector: ".auth-container h2 a:not([data-all])"
84+
},
7385
flow: {
7486
selector: ".flow code"
7587
},

test/e2e-selenium/scenarios/schemeContainer.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,16 @@ describe("Render scheme", function () {
4545
.assert.containsText("@authorizationUrl", "http://petstore.swagger.io/oauth/dialog")
4646
.assert.containsText("@flow", "implicit")
4747
.assert.value("@inputClientID", "your-client-id")
48+
schemeContainer.expect.element("@readPetsScope").to.be.selected
49+
schemeContainer.expect.element("@writePetsScope").to.not.be.selected
50+
51+
schemeContainer.click("@selectAllScopes")
52+
schemeContainer.expect.element("@readPetsScope").to.be.selected
53+
schemeContainer.expect.element("@writePetsScope").to.be.selected
54+
55+
schemeContainer.click("@selectNoneScopes")
56+
schemeContainer.expect.element("@readPetsScope").to.not.be.selected
57+
schemeContainer.expect.element("@writePetsScope").to.not.be.selected
4858

4959
client.end()
5060
})

test/e2e-selenium/static/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@
101101
realm: "your-realms",
102102
appName: "your-app-name",
103103
scopeSeparator: " ",
104+
scopes: "read:pets profile openid",
104105
additionalQueryStringParams: {}
105106
})
106107
}

0 commit comments

Comments
 (0)