feat: query webfinger
This commit is contained in:
parent
d630513dc6
commit
6da9e15102
3 changed files with 196 additions and 17 deletions
|
@ -17,6 +17,7 @@ import (
|
||||||
"code.gitea.io/gitea/modules/sitemap"
|
"code.gitea.io/gitea/modules/sitemap"
|
||||||
"code.gitea.io/gitea/modules/structs"
|
"code.gitea.io/gitea/modules/structs"
|
||||||
"code.gitea.io/gitea/services/context"
|
"code.gitea.io/gitea/services/context"
|
||||||
|
"code.gitea.io/gitea/services/forgefed"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -99,6 +100,31 @@ func RenderUserSearch(ctx *context.Context, opts *user_model.SearchUserOptions,
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(opts.Keyword) > 0 && forgefed.IsFingerable(opts.Keyword) {
|
||||||
|
webfingerRes, err := forgefed.WebFingerLookup(opts.Keyword)
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("SearchUsers", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
person, err := forgefed.GetActor(webfingerRes.GetActorLink().Href)
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("SearchUsers", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = forgefed.SavePerson(ctx, person)
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("SearchUsers", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// users, count, err = user_model.SearchUsers(ctx, opts)
|
||||||
|
// if err != nil {
|
||||||
|
// ctx.ServerError("SearchUsers", err)
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
if isSitemap {
|
if isSitemap {
|
||||||
m := sitemap.NewSitemap()
|
m := sitemap.NewSitemap()
|
||||||
for _, item := range users {
|
for _, item := range users {
|
||||||
|
|
|
@ -13,25 +13,11 @@ import (
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/services/context"
|
"code.gitea.io/gitea/services/context"
|
||||||
|
"code.gitea.io/gitea/services/forgefed"
|
||||||
)
|
)
|
||||||
|
|
||||||
// https://datatracker.ietf.org/doc/html/draft-ietf-appsawg-webfinger-14#section-4.4
|
// 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
|
// WebfingerQuery returns information about a resource
|
||||||
// https://datatracker.ietf.org/doc/html/rfc7565
|
// https://datatracker.ietf.org/doc/html/rfc7565
|
||||||
func WebfingerQuery(ctx *context.Context) {
|
func WebfingerQuery(ctx *context.Context) {
|
||||||
|
@ -104,7 +90,7 @@ func WebfingerQuery(ctx *context.Context) {
|
||||||
aliases = append(aliases, fmt.Sprintf("mailto:%s", u.Email))
|
aliases = append(aliases, fmt.Sprintf("mailto:%s", u.Email))
|
||||||
}
|
}
|
||||||
|
|
||||||
links := []*webfingerLink{
|
links := []*forgefed.WebfingerLink{
|
||||||
{
|
{
|
||||||
Rel: "http://webfinger.net/rel/profile-page",
|
Rel: "http://webfinger.net/rel/profile-page",
|
||||||
Type: "text/html",
|
Type: "text/html",
|
||||||
|
@ -127,7 +113,7 @@ func WebfingerQuery(ctx *context.Context) {
|
||||||
|
|
||||||
ctx.Resp.Header().Add("Content-Type", "application/jrd+json")
|
ctx.Resp.Header().Add("Content-Type", "application/jrd+json")
|
||||||
ctx.Resp.Header().Add("Access-Control-Allow-Origin", "*")
|
ctx.Resp.Header().Add("Access-Control-Allow-Origin", "*")
|
||||||
ctx.JSON(http.StatusOK, &webfingerJRD{
|
ctx.JSON(http.StatusOK, &forgefed.WebfingerJRD{
|
||||||
Subject: fmt.Sprintf("acct:%s@%s", url.QueryEscape(u.Name), appURL.Host),
|
Subject: fmt.Sprintf("acct:%s@%s", url.QueryEscape(u.Name), appURL.Host),
|
||||||
Aliases: aliases,
|
Aliases: aliases,
|
||||||
Links: links,
|
Links: links,
|
||||||
|
|
167
services/forgefed/webfinger.go
Normal file
167
services/forgefed/webfinger.go
Normal file
|
@ -0,0 +1,167 @@
|
||||||
|
package forgefed
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w WebfingerJRD) GetAvatar() *WebfingerLink {
|
||||||
|
for _, link := range w.Links {
|
||||||
|
if link.Rel == "http://webfinger.net/rel/avatar" {
|
||||||
|
return link
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w WebfingerJRD) GetProfilePage() *WebfingerLink {
|
||||||
|
for _, link := range w.Links {
|
||||||
|
if link.Rel == "http://webfinger.net/rel/profile-page" && link.Type == "text/html" {
|
||||||
|
return link
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w WebfingerJRD) GetActorLink() *WebfingerLink {
|
||||||
|
for _, link := range w.Links {
|
||||||
|
if link.Rel == "self" && link.Type == "application/activity+json" {
|
||||||
|
return link
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetHostnameFromResource(resource string) (string, error) {
|
||||||
|
r := resource
|
||||||
|
if strings.HasPrefix(resource, "@") {
|
||||||
|
resource, _ = strings.CutPrefix(resource, "@")
|
||||||
|
}
|
||||||
|
actor, err := url.Parse(resource)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
var hostname string
|
||||||
|
switch actor.Scheme {
|
||||||
|
case "":
|
||||||
|
i := strings.Split(resource, "@")
|
||||||
|
if len(i) != 2 {
|
||||||
|
log.Error("Invalid webfinger query " + r)
|
||||||
|
return "", errors.New("Invalid webfinger query " + r)
|
||||||
|
}
|
||||||
|
hostname = i[1]
|
||||||
|
case "mailto":
|
||||||
|
i := strings.Split(resource, "@")
|
||||||
|
if len(i) != 2 {
|
||||||
|
log.Error("Invalid webfinger query " + r)
|
||||||
|
return "", errors.New("Invalid webfinger query " + r)
|
||||||
|
}
|
||||||
|
hostname = i[1]
|
||||||
|
case "https":
|
||||||
|
hostname = actor.Host
|
||||||
|
default:
|
||||||
|
log.Error("Invalid webfinger query " + r)
|
||||||
|
return "", errors.New("Invalid webfinger query" + r)
|
||||||
|
|
||||||
|
}
|
||||||
|
return hostname, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get Actor object by performing webfinger lookup
|
||||||
|
func WebFingerLookup(q string) (*WebfingerJRD, error) {
|
||||||
|
if strings.HasPrefix(q, "@") {
|
||||||
|
q, _ = strings.CutPrefix(q, "@")
|
||||||
|
}
|
||||||
|
actor, err := url.Parse(q)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var res string
|
||||||
|
switch actor.Scheme {
|
||||||
|
case "":
|
||||||
|
res = fmt.Sprintf("acct:%s", q)
|
||||||
|
case "mailto":
|
||||||
|
res = q
|
||||||
|
case "https":
|
||||||
|
res = q
|
||||||
|
default:
|
||||||
|
return nil, errors.New("Invalid webfinger query")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
hostname, err := GetHostnameFromResource(q)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
link := fmt.Sprintf("https://%s/.well-known/webfinger?resource=%s", hostname, res)
|
||||||
|
|
||||||
|
r, err := http.Get(link)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer r.Body.Close()
|
||||||
|
|
||||||
|
webfingerResponse := new(WebfingerJRD)
|
||||||
|
err = json.NewDecoder(r.Body).Decode(webfingerResponse)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return webfingerResponse, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsFingerable(resource string) bool {
|
||||||
|
if strings.HasPrefix(resource, "@") {
|
||||||
|
resource, _ = strings.CutPrefix(resource, "@")
|
||||||
|
}
|
||||||
|
actor, err := url.Parse(resource)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
switch actor.Scheme {
|
||||||
|
case "":
|
||||||
|
i := strings.Split(resource, "@")
|
||||||
|
if len(i) == 2 {
|
||||||
|
_ = i[1] // TODO: do len check before referencing element #2
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
case "mailto":
|
||||||
|
i := strings.Split(resource, "@")
|
||||||
|
if len(i) == 2 {
|
||||||
|
_ = i[1]
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
case "https":
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue