diff options
author | Daniel Baumann <daniel@debian.org> | 2024-10-18 20:33:49 +0200 |
---|---|---|
committer | Daniel Baumann <daniel@debian.org> | 2024-12-12 23:57:56 +0100 |
commit | e68b9d00a6e05b3a941f63ffb696f91e554ac5ec (patch) | |
tree | 97775d6c13b0f416af55314eb6a89ef792474615 /routers/web/webfinger.go | |
parent | Initial commit. (diff) | |
download | forgejo-e68b9d00a6e05b3a941f63ffb696f91e554ac5ec.tar.xz forgejo-e68b9d00a6e05b3a941f63ffb696f91e554ac5ec.zip |
Adding upstream version 9.0.3.
Signed-off-by: Daniel Baumann <daniel@debian.org>
Diffstat (limited to '')
-rw-r--r-- | routers/web/webfinger.go | 167 |
1 files changed, 167 insertions, 0 deletions
diff --git a/routers/web/webfinger.go b/routers/web/webfinger.go new file mode 100644 index 0000000..1f3de70 --- /dev/null +++ b/routers/web/webfinger.go @@ -0,0 +1,167 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package web + +import ( + "fmt" + "net/http" + "net/url" + "strings" + + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/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 + } + + 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: 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") +} |