 73dac787a3
			
		
	
	
	73dac787a3
	
	
	
		
			
			As stated in a comment: https://codeberg.org/forgejo/forgejo/issues/8634#issuecomment-6136933 > `routers/web/webfinger.go` was left unchanged, so it still includes the trailing slash, no longer matching the issuer specified in other endpoints. > > ... > > From the [OpenID Connect Discovery specification](https://openid.net/specs/openid-connect-discovery-1_0.html#IssuerDiscovery): > > > The Issuer location MUST be returned in the WebFinger response as the value of the href member of a links array element with rel member value http://openid.net/specs/connect/1.0/issuer. > > This sounds to me like the `href` should be the issuer location exactly. > > Using Forgejo for OIDC for auth with Tailscale is one instance of this change breaking something - signing up to Tailscale with OIDC now gives an error. Unsure what happens for existing accounts. In summary, since !8028, trailing slashes have been removed from the OIDC issuer locations specified by Forgejo everywhere except in WebFinger responses at `/.well-known/webfinger`, which still includes a trailing slash and so no longer matches the issuer as specified elsewhere (such as at `/.well-known/openid-configuration`). ## Checklist The [contributor guide](https://forgejo.org/docs/next/contributor/) contains information that will be helpful to first time contributors. There also are a few [conditions for merging Pull Requests in Forgejo repositories](https://codeberg.org/forgejo/governance/src/branch/main/PullRequestsAgreement.md). You are also welcome to join the [Forgejo development chatroom](https://matrix.to/#/#forgejo-development:matrix.org). ### Tests - I added test coverage for Go changes... - [ ] in their respective `*_test.go` for unit tests. - [x] in the `tests/integration` directory if it involves interactions with a live Forgejo server. - I added test coverage for JavaScript changes... - [ ] in `web_src/js/*.test.js` if it can be unit tested. - [ ] in `tests/e2e/*.test.e2e.js` if it requires interactions with a live Forgejo server (see also the [developer guide for JavaScript testing](https://codeberg.org/forgejo/forgejo/src/branch/forgejo/tests/e2e/README.md#end-to-end-tests)). ### Documentation - [ ] I created a pull request [to the documentation](https://codeberg.org/forgejo/docs) to explain to Forgejo users how to use this change. - [ ] I did not document these changes and I do not expect someone else to do it. ### Release notes - [ ] I do not want this change to show in the release notes. - [ ] I want the title to show in the release notes with a link to this pull request. - [ ] I want the content of the `release-notes/<pull request number>.md` to be be used for the release notes instead of the title. Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/8794 Reviewed-by: Gusted <gusted@noreply.codeberg.org> Reviewed-by: Michael Kriese <michael.kriese@gmx.de> Co-authored-by: hazycora <git@hazy.gay> Co-committed-by: hazycora <git@hazy.gay>
		
			
				
	
	
		
			193 lines
		
	
	
	
		
			4.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			193 lines
		
	
	
	
		
			4.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright 2022 The Gitea Authors. All rights reserved.
 | |
| // SPDX-License-Identifier: MIT
 | |
| 
 | |
| package web
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"net/http"
 | |
| 	"net/url"
 | |
| 	"strings"
 | |
| 
 | |
| 	user_model "forgejo.org/models/user"
 | |
| 	"forgejo.org/modules/log"
 | |
| 	"forgejo.org/modules/setting"
 | |
| 	"forgejo.org/services/context"
 | |
| )
 | |
| 
 | |
| // https://datatracker.ietf.org/doc/html/draft-ietf-appsawg-webfinger-14#section-4.4
 | |
| 
 | |
| type webfingerJRD struct {
 | |
| 	Subject    string           `json:"subject,omitempty"`
 | |
| 	Aliases    []string         `json:"aliases,omitempty"`
 | |
| 	Properties map[string]any   `json:"properties,omitempty"`
 | |
| 	Links      []*webfingerLink `json:"links,omitempty"`
 | |
| }
 | |
| 
 | |
| type webfingerLink struct {
 | |
| 	Rel        string            `json:"rel,omitempty"`
 | |
| 	Type       string            `json:"type,omitempty"`
 | |
| 	Href       string            `json:"href,omitempty"`
 | |
| 	Titles     map[string]string `json:"titles,omitempty"`
 | |
| 	Properties map[string]any    `json:"properties,omitempty"`
 | |
| }
 | |
| 
 | |
| // WebfingerQuery returns information about a resource
 | |
| // https://datatracker.ietf.org/doc/html/rfc7565
 | |
| func WebfingerQuery(ctx *context.Context) {
 | |
| 	appURL, _ := url.Parse(setting.AppURL)
 | |
| 
 | |
| 	resource, err := url.Parse(ctx.FormTrim("resource"))
 | |
| 	if err != nil {
 | |
| 		ctx.Error(http.StatusBadRequest)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	var u *user_model.User
 | |
| 
 | |
| 	switch resource.Scheme {
 | |
| 	case "acct":
 | |
| 		// allow only the current host
 | |
| 		parts := strings.SplitN(resource.Opaque, "@", 2)
 | |
| 		if len(parts) != 2 {
 | |
| 			ctx.Error(http.StatusBadRequest)
 | |
| 			return
 | |
| 		}
 | |
| 		if parts[1] != appURL.Host {
 | |
| 			ctx.Error(http.StatusBadRequest)
 | |
| 			return
 | |
| 		}
 | |
| 
 | |
| 		// Instance actor
 | |
| 		if parts[0] == "ghost" {
 | |
| 			aliases := []string{
 | |
| 				appURL.String() + "api/v1/activitypub/actor",
 | |
| 			}
 | |
| 
 | |
| 			links := []*webfingerLink{
 | |
| 				{
 | |
| 					Rel:  "self",
 | |
| 					Type: "application/activity+json",
 | |
| 					Href: appURL.String() + "api/v1/activitypub/actor",
 | |
| 				},
 | |
| 			}
 | |
| 
 | |
| 			ctx.Resp.Header().Add("Access-Control-Allow-Origin", "*")
 | |
| 			ctx.JSON(http.StatusOK, &webfingerJRD{
 | |
| 				Subject: fmt.Sprintf("acct:%s@%s", "ghost", appURL.Host),
 | |
| 				Aliases: aliases,
 | |
| 				Links:   links,
 | |
| 			})
 | |
| 			ctx.Resp.Header().Set("Content-Type", "application/jrd+json")
 | |
| 
 | |
| 			return
 | |
| 		}
 | |
| 
 | |
| 		u, err = user_model.GetUserByName(ctx, parts[0])
 | |
| 
 | |
| 	case "mailto":
 | |
| 		u, err = user_model.GetUserByEmail(ctx, resource.Opaque)
 | |
| 		if u != nil && u.KeepEmailPrivate {
 | |
| 			err = user_model.ErrUserNotExist{}
 | |
| 		}
 | |
| 	case "https", "http":
 | |
| 		if resource.Host != appURL.Host {
 | |
| 			ctx.Error(http.StatusBadRequest)
 | |
| 			return
 | |
| 		}
 | |
| 
 | |
| 		p := strings.Trim(resource.Path, "/")
 | |
| 		if len(p) == 0 {
 | |
| 			ctx.Error(http.StatusNotFound)
 | |
| 			return
 | |
| 		}
 | |
| 
 | |
| 		parts := strings.Split(p, "/")
 | |
| 
 | |
| 		switch len(parts) {
 | |
| 		case 1: // user
 | |
| 			u, err = user_model.GetUserByName(ctx, parts[0])
 | |
| 		case 2: // repository
 | |
| 			ctx.Error(http.StatusNotFound)
 | |
| 			return
 | |
| 
 | |
| 		case 3:
 | |
| 			switch parts[2] {
 | |
| 			case "issues":
 | |
| 				ctx.Error(http.StatusNotFound)
 | |
| 				return
 | |
| 
 | |
| 			case "pulls":
 | |
| 				ctx.Error(http.StatusNotFound)
 | |
| 				return
 | |
| 
 | |
| 			case "projects":
 | |
| 				ctx.Error(http.StatusNotFound)
 | |
| 				return
 | |
| 
 | |
| 			default:
 | |
| 				ctx.Error(http.StatusNotFound)
 | |
| 				return
 | |
| 			}
 | |
| 
 | |
| 		default:
 | |
| 			ctx.Error(http.StatusNotFound)
 | |
| 			return
 | |
| 		}
 | |
| 
 | |
| 	default:
 | |
| 		ctx.Error(http.StatusBadRequest)
 | |
| 		return
 | |
| 	}
 | |
| 	if err != nil {
 | |
| 		if user_model.IsErrUserNotExist(err) {
 | |
| 			ctx.Error(http.StatusNotFound)
 | |
| 		} else {
 | |
| 			log.Error("Error getting user: %s Error: %v", resource.Opaque, err)
 | |
| 			ctx.Error(http.StatusInternalServerError)
 | |
| 		}
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	if !user_model.IsUserVisibleToViewer(ctx, u, ctx.Doer) {
 | |
| 		ctx.Error(http.StatusNotFound)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	aliases := []string{
 | |
| 		u.HTMLURL(),
 | |
| 		appURL.String() + "api/v1/activitypub/user-id/" + fmt.Sprint(u.ID),
 | |
| 	}
 | |
| 	if !u.KeepEmailPrivate {
 | |
| 		aliases = append(aliases, fmt.Sprintf("mailto:%s", u.Email))
 | |
| 	}
 | |
| 
 | |
| 	links := []*webfingerLink{
 | |
| 		{
 | |
| 			Rel:  "http://webfinger.net/rel/profile-page",
 | |
| 			Type: "text/html",
 | |
| 			Href: u.HTMLURL(),
 | |
| 		},
 | |
| 		{
 | |
| 			Rel:  "http://webfinger.net/rel/avatar",
 | |
| 			Href: u.AvatarLink(ctx),
 | |
| 		},
 | |
| 		{
 | |
| 			Rel:  "self",
 | |
| 			Type: "application/activity+json",
 | |
| 			Href: appURL.String() + "api/v1/activitypub/user-id/" + fmt.Sprint(u.ID),
 | |
| 		},
 | |
| 		{
 | |
| 			Rel:  "http://openid.net/specs/connect/1.0/issuer",
 | |
| 			Href: strings.TrimSuffix(appURL.String(), "/"),
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	ctx.Resp.Header().Add("Access-Control-Allow-Origin", "*")
 | |
| 	ctx.JSON(http.StatusOK, &webfingerJRD{
 | |
| 		Subject: fmt.Sprintf("acct:%s@%s", url.QueryEscape(u.Name), appURL.Host),
 | |
| 		Aliases: aliases,
 | |
| 		Links:   links,
 | |
| 	})
 | |
| 	ctx.Resp.Header().Set("Content-Type", "application/jrd+json")
 | |
| }
 |