5 changes: 3 additions & 2 deletions services/forms/user_form.go
Original file line number Diff line number Diff line change
Expand Up @@ -379,8 +379,9 @@ func (f *NewAccessTokenForm) Validate(req *http.Request, errs binding.Errors) bi

// EditOAuth2ApplicationForm form for editing oauth2 applications
type EditOAuth2ApplicationForm struct {
Name string `binding:"Required;MaxSize(255)" form:"application_name"`
RedirectURI string `binding:"Required" form:"redirect_uri"`
Name string `binding:"Required;MaxSize(255)" form:"application_name"`
RedirectURI string `binding:"Required" form:"redirect_uri"`
ConfidentialClient bool `form:"confidential_client"`
}

// Validate validates the fields
Expand Down
8 changes: 8 additions & 0 deletions templates/swagger/v1_json.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -14645,6 +14645,10 @@
"description": "CreateOAuth2ApplicationOptions holds options to create an oauth2 application",
"type": "object",
"properties": {
"confidential_client": {
"type": "boolean",
"x-go-name": "ConfidentialClient"
},
"name": {
"type": "string",
"x-go-name": "Name"
Expand Down Expand Up @@ -17306,6 +17310,10 @@
"type": "string",
"x-go-name": "ClientSecret"
},
"confidential_client": {
"type": "boolean",
"x-go-name": "ConfidentialClient"
},
"created": {
"type": "string",
"format": "date-time",
Expand Down
4 changes: 4 additions & 0 deletions templates/user/settings/applications_oauth2_edit_form.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@
<label for="redirect-uri">{{.locale.Tr "settings.oauth2_redirect_uri"}}</label>
<input type="url" name="redirect_uri" value="{{.App.PrimaryRedirectURI}}" id="redirect-uri">
</div>
<div class="field ui checkbox {{if .Err_ConfidentialClient}}error{{end}}">
<label>{{.locale.Tr "settings.oauth2_confidential_client"}}</label>
<input type="checkbox" name="confidential_client" {{if .App.ConfidentialClient}}checked{{end}}>
</div>
<button class="ui green button">
{{.locale.Tr "settings.save_application"}}
</button>
Expand Down
4 changes: 4 additions & 0 deletions templates/user/settings/applications_oauth2_list.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@
<label for="redirect-uri">{{.locale.Tr "settings.oauth2_redirect_uri"}}</label>
<input type="url" name="redirect_uri" id="redirect-uri">
</div>
<div class="field ui checkbox {{if .Err_ConfidentialClient}}error{{end}}">
<label>{{.locale.Tr "settings.oauth2_confidential_client"}}</label>
<input type="checkbox" name="confidential_client" checked>
</div>
<button class="ui green button">
{{.locale.Tr "settings.create_oauth2_application_button"}}
</button>
Expand Down
8 changes: 8 additions & 0 deletions tests/integration/api_oauth2_apps_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ func testAPICreateOAuth2Application(t *testing.T) {
RedirectURIs: []string{
"http://www.google.com",
},
ConfidentialClient: true,
}

req := NewRequestWithJSON(t, "POST", "/api/v1/user/applications/oauth2", &appBody)
Expand All @@ -46,6 +47,7 @@ func testAPICreateOAuth2Application(t *testing.T) {
assert.EqualValues(t, appBody.Name, createdApp.Name)
assert.Len(t, createdApp.ClientSecret, 56)
assert.Len(t, createdApp.ClientID, 36)
assert.True(t, createdApp.ConfidentialClient)
assert.NotEmpty(t, createdApp.Created)
assert.EqualValues(t, appBody.RedirectURIs[0], createdApp.RedirectURIs[0])
unittest.AssertExistsAndLoadBean(t, &auth.OAuth2Application{UID: user.ID, Name: createdApp.Name})
Expand All @@ -62,6 +64,7 @@ func testAPIListOAuth2Applications(t *testing.T) {
RedirectURIs: []string{
"http://www.google.com",
},
ConfidentialClient: true,
})

urlStr := fmt.Sprintf("/api/v1/user/applications/oauth2?token=%s", token)
Expand All @@ -74,6 +77,7 @@ func testAPIListOAuth2Applications(t *testing.T) {

assert.EqualValues(t, existApp.Name, expectedApp.Name)
assert.EqualValues(t, existApp.ClientID, expectedApp.ClientID)
assert.Equal(t, existApp.ConfidentialClient, expectedApp.ConfidentialClient)
assert.Len(t, expectedApp.ClientID, 36)
assert.Empty(t, expectedApp.ClientSecret)
assert.EqualValues(t, existApp.RedirectURIs[0], expectedApp.RedirectURIs[0])
Expand Down Expand Up @@ -112,6 +116,7 @@ func testAPIGetOAuth2Application(t *testing.T) {
RedirectURIs: []string{
"http://www.google.com",
},
ConfidentialClient: true,
})

urlStr := fmt.Sprintf("/api/v1/user/applications/oauth2/%d?token=%s", existApp.ID, token)
Expand All @@ -124,6 +129,7 @@ func testAPIGetOAuth2Application(t *testing.T) {

assert.EqualValues(t, existApp.Name, expectedApp.Name)
assert.EqualValues(t, existApp.ClientID, expectedApp.ClientID)
assert.Equal(t, existApp.ConfidentialClient, expectedApp.ConfidentialClient)
assert.Len(t, expectedApp.ClientID, 36)
assert.Empty(t, expectedApp.ClientSecret)
assert.Len(t, expectedApp.RedirectURIs, 1)
Expand All @@ -148,6 +154,7 @@ func testAPIUpdateOAuth2Application(t *testing.T) {
"http://www.google.com/",
"http://www.github.com/",
},
ConfidentialClient: true,
}

urlStr := fmt.Sprintf("/api/v1/user/applications/oauth2/%d", existApp.ID)
Expand All @@ -162,5 +169,6 @@ func testAPIUpdateOAuth2Application(t *testing.T) {
assert.Len(t, expectedApp.RedirectURIs, 2)
assert.EqualValues(t, expectedApp.RedirectURIs[0], appBody.RedirectURIs[0])
assert.EqualValues(t, expectedApp.RedirectURIs[1], appBody.RedirectURIs[1])
assert.Equal(t, expectedApp.ConfidentialClient, appBody.ConfidentialClient)
unittest.AssertExistsAndLoadBean(t, &auth.OAuth2Application{ID: expectedApp.ID, Name: expectedApp.Name})
}
11 changes: 11 additions & 0 deletions tests/integration/oauth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,17 @@ func TestAuthorizeRedirectWithExistingGrant(t *testing.T) {
assert.Equal(t, "https://example.com/xyzzy", u.String())
}

func TestAuthorizePKCERequiredForPublicClient(t *testing.T) {
defer tests.PrepareTestEnv(t)()
req := NewRequest(t, "GET", "/login/oauth/authorize?client_id=ce5a1322-42a7-11ed-b878-0242ac120002&redirect_uri=http%3A%2F%2F127.0.0.1&response_type=code&state=thestate")
ctx := loginUser(t, "user1")
resp := ctx.MakeRequest(t, req, http.StatusSeeOther)
u, err := resp.Result().Location()
assert.NoError(t, err)
assert.Equal(t, "invalid_request", u.Query().Get("error"))
assert.Equal(t, "PKCE is required for public clients", u.Query().Get("error_description"))
}

func TestAccessTokenExchange(t *testing.T) {
defer tests.PrepareTestEnv(t)()
req := NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
Expand Down