Add ability to set multiple redirect URIs in OAuth application UI (#25072)

OAuth applications can already have multiple redirect URIs if
created/edited over API.

This change allows for setting multiple redirect URIs through the UI as
a comma-separated list (e. g.
`https://example.org/redirect,https://redirect.example.org`)

<details>
<summary>Screenshots</summary>

![Bildschirmfoto vom 2023-06-04
17-14-40](https://github.com/go-gitea/gitea/assets/47871822/2206dc32-e7e4-4953-9ecb-e098890b3f54)
![Bildschirmfoto vom 2023-06-04
17-14-50](https://github.com/go-gitea/gitea/assets/47871822/cd97c73c-9310-44ee-a83a-b927a1ef94da)

</details>

Closes #25068
pull/25074/head^2
Denys Konovalov 1 year ago committed by GitHub
parent 7d192cb674
commit ca35dec18b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 8
      models/auth/oauth2.go
  2. 19
      modules/util/truncate.go
  3. 2
      options/locale/locale_en-US.ini
  4. 5
      routers/web/user/setting/oauth2_common.go
  5. 2
      services/forms/user_form.go
  6. 4
      templates/user/settings/applications_oauth2_edit_form.tmpl
  7. 4
      templates/user/settings/applications_oauth2_list.tmpl

@ -51,14 +51,6 @@ func (app *OAuth2Application) TableName() string {
return "oauth2_application" return "oauth2_application"
} }
// PrimaryRedirectURI returns the first redirect uri or an empty string if empty
func (app *OAuth2Application) PrimaryRedirectURI() string {
if len(app.RedirectURIs) == 0 {
return ""
}
return app.RedirectURIs[0]
}
// ContainsRedirectURI checks if redirectURI is allowed for app // ContainsRedirectURI checks if redirectURI is allowed for app
func (app *OAuth2Application) ContainsRedirectURI(redirectURI string) bool { func (app *OAuth2Application) ContainsRedirectURI(redirectURI string) bool {
if !app.ConfidentialClient { if !app.ConfidentialClient {

@ -3,7 +3,10 @@
package util package util
import "unicode/utf8" import (
"strings"
"unicode/utf8"
)
// in UTF8 "…" is 3 bytes so doesn't really gain us anything... // in UTF8 "…" is 3 bytes so doesn't really gain us anything...
const ( const (
@ -35,3 +38,17 @@ func SplitStringAtByteN(input string, n int) (left, right string) {
return input[:end] + utf8Ellipsis, utf8Ellipsis + input[end:] return input[:end] + utf8Ellipsis, utf8Ellipsis + input[end:]
} }
// SplitTrimSpace splits the string at given separator and trims leading and trailing space
func SplitTrimSpace(input, sep string) []string {
// replace CRLF with LF
input = strings.ReplaceAll(input, "\r\n", "\n")
var stringList []string
for _, s := range strings.Split(input, sep) {
// trim leading and trailing space
stringList = append(stringList, strings.TrimSpace(s))
}
return stringList
}

@ -826,7 +826,7 @@ create_oauth2_application_success = You've successfully created a new OAuth2 app
update_oauth2_application_success = You've successfully updated the OAuth2 application. update_oauth2_application_success = You've successfully updated the OAuth2 application.
oauth2_application_name = Application Name oauth2_application_name = Application Name
oauth2_confidential_client = Confidential Client. Select for apps that keep the secret confidential, such as web apps. Do not select for native apps including desktop and mobile apps. oauth2_confidential_client = Confidential Client. Select for apps that keep the secret confidential, such as web apps. Do not select for native apps including desktop and mobile apps.
oauth2_redirect_uri = Redirect URI oauth2_redirect_uris = Redirect URIs. Please use a new line for every URI.
save_application = Save save_application = Save
oauth2_client_id = Client ID oauth2_client_id = Client ID
oauth2_client_secret = Client Secret oauth2_client_secret = Client Secret

@ -10,6 +10,7 @@ import (
"code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/services/forms" "code.gitea.io/gitea/services/forms"
) )
@ -40,7 +41,7 @@ func (oa *OAuth2CommonHandlers) AddApp(ctx *context.Context) {
// TODO validate redirect URI // TODO validate redirect URI
app, err := auth.CreateOAuth2Application(ctx, auth.CreateOAuth2ApplicationOptions{ app, err := auth.CreateOAuth2Application(ctx, auth.CreateOAuth2ApplicationOptions{
Name: form.Name, Name: form.Name,
RedirectURIs: []string{form.RedirectURI}, RedirectURIs: util.SplitTrimSpace(form.RedirectURIs, "\n"),
UserID: oa.OwnerID, UserID: oa.OwnerID,
ConfidentialClient: form.ConfidentialClient, ConfidentialClient: form.ConfidentialClient,
}) })
@ -93,7 +94,7 @@ func (oa *OAuth2CommonHandlers) EditSave(ctx *context.Context) {
if ctx.Data["App"], err = auth.UpdateOAuth2Application(auth.UpdateOAuth2ApplicationOptions{ if ctx.Data["App"], err = auth.UpdateOAuth2Application(auth.UpdateOAuth2ApplicationOptions{
ID: ctx.ParamsInt64("id"), ID: ctx.ParamsInt64("id"),
Name: form.Name, Name: form.Name,
RedirectURIs: []string{form.RedirectURI}, RedirectURIs: util.SplitTrimSpace(form.RedirectURIs, "\n"),
UserID: oa.OwnerID, UserID: oa.OwnerID,
ConfidentialClient: form.ConfidentialClient, ConfidentialClient: form.ConfidentialClient,
}); err != nil { }); err != nil {

@ -398,7 +398,7 @@ func (f *NewAccessTokenForm) GetScope() (auth_model.AccessTokenScope, error) {
// EditOAuth2ApplicationForm form for editing oauth2 applications // EditOAuth2ApplicationForm form for editing oauth2 applications
type EditOAuth2ApplicationForm struct { type EditOAuth2ApplicationForm struct {
Name string `binding:"Required;MaxSize(255)" form:"application_name"` Name string `binding:"Required;MaxSize(255)" form:"application_name"`
RedirectURI string `binding:"Required" form:"redirect_uri"` RedirectURIs string `binding:"Required" form:"redirect_uris"`
ConfidentialClient bool `form:"confidential_client"` ConfidentialClient bool `form:"confidential_client"`
} }

@ -39,8 +39,8 @@
<input id="application-name" value="{{.App.Name}}" name="application_name" required maxlength="255"> <input id="application-name" value="{{.App.Name}}" name="application_name" required maxlength="255">
</div> </div>
<div class="field {{if .Err_RedirectURI}}error{{end}}"> <div class="field {{if .Err_RedirectURI}}error{{end}}">
<label for="redirect-uri">{{.locale.Tr "settings.oauth2_redirect_uri"}}</label> <label for="redirect-uris">{{.locale.Tr "settings.oauth2_redirect_uris"}}</label>
<input type="url" name="redirect_uri" value="{{.App.PrimaryRedirectURI}}" id="redirect-uri" required> <textarea name="redirect_uris" id="redirect-uris" required>{{StringUtils.Join .App.RedirectURIs "\n"}}</textarea>
</div> </div>
<div class="field ui checkbox {{if .Err_ConfidentialClient}}error{{end}}"> <div class="field ui checkbox {{if .Err_ConfidentialClient}}error{{end}}">
<label>{{.locale.Tr "settings.oauth2_confidential_client"}}</label> <label>{{.locale.Tr "settings.oauth2_confidential_client"}}</label>

@ -34,8 +34,8 @@
<input id="application-name" name="application_name" value="{{.application_name}}" required maxlength="255"> <input id="application-name" name="application_name" value="{{.application_name}}" required maxlength="255">
</div> </div>
<div class="field {{if .Err_RedirectURI}}error{{end}}"> <div class="field {{if .Err_RedirectURI}}error{{end}}">
<label for="redirect-uri">{{.locale.Tr "settings.oauth2_redirect_uri"}}</label> <label for="redirect-uris">{{.locale.Tr "settings.oauth2_redirect_uris"}}</label>
<input type="url" name="redirect_uri" id="redirect-uri"> <textarea name="redirect_uris" id="redirect-uris"></textarea>
</div> </div>
<div class="field ui checkbox {{if .Err_ConfidentialClient}}error{{end}}"> <div class="field ui checkbox {{if .Err_ConfidentialClient}}error{{end}}">
<label>{{.locale.Tr "settings.oauth2_confidential_client"}}</label> <label>{{.locale.Tr "settings.oauth2_confidential_client"}}</label>

Loading…
Cancel
Save