Skip to content

User keypairs and HTTP signatures for ActivityPub federation using go-ap #19133

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 116 commits into from Jun 19, 2022
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
116 commits
Select commit Hold shift + click to select a range
f2db473
go.mod: add go-fed/{httpsig,activity/pub,activity/streams} dependency
Mar 13, 2022
4951af4
activitypub: implement /api/v1/activitypub/user/{username} (#14186)
Oct 27, 2021
678a56f
activitypub: add the public key to Person (#14186)
Oct 29, 2021
e8907c3
activitypub: go-fed conformant Clock instance
Nov 9, 2021
15c1f62
activitypub: signing http client
Nov 9, 2021
97fedf2
activitypub: implement the ReqSignature middleware
Nov 10, 2021
b342241
activitypub: hack_16834
Dec 24, 2021
2a8864f
Fix CI checks-backend errors with go mod tidy
Mar 19, 2022
b480c52
Change 2021 to 2022, properly format package imports
Mar 19, 2022
f9e33d9
Run make fmt and make generate-swagger
Mar 19, 2022
ea4129e
Use Gitea JSON library, add assert for pkp
Mar 19, 2022
456ed42
Run make fmt again, fix err var redeclaration
Mar 20, 2022
d75809a
Remove LogSQL from ActivityPub person test
Mar 20, 2022
ebef769
Assert if json.Unmarshal succeeds
Mar 20, 2022
46973f9
Cleanup, handle invalid usernames for ActivityPub person GET request
Mar 24, 2022
3ed4a71
Rename hack_16834 to user_settings
Mar 25, 2022
21c56f8
Use the httplib module instead of http for GET requests
Mar 27, 2022
373a84a
Clean up whitespace with make fmt
Mar 27, 2022
d1a53f7
Use time.RFC1123 and make the http.Client proxy-aware
Mar 28, 2022
65016b2
Check if digest algo is supported in setting module
Mar 29, 2022
1da0d49
Clean up some variable declarations
Mar 30, 2022
fdae736
Remove unneeded copy
Mar 30, 2022
7ea5e10
Use system timezone instead of setting.DefaultUILocation
Mar 30, 2022
5139b3d
Use named constant for httpsigExpirationTime
Apr 13, 2022
7931e21
Make pubKey IRI #main-key instead of /#main-key
Apr 13, 2022
702a963
Move /#main-key to #main-key in tests
Apr 14, 2022
523ca3d
Implemented Webfinger endpoint.
KN4CK3R Apr 20, 2022
0d120f8
Add visible check.
KN4CK3R Apr 22, 2022
5d61e59
Add user profile as alias.
KN4CK3R Apr 22, 2022
aa962c6
Add actor IRI and remote interaction URL to WebFinger response
Apr 22, 2022
07150b3
Merge branch 'master' into feature-activitypub
6543 May 9, 2022
55c5e93
fmt
6543 May 9, 2022
609fcc7
Merge branch 'master' into feature-activitypub
6543 May 9, 2022
364838c
Fix lint errors
May 10, 2022
501a39f
Merge remote-tracking branch 'github/feature-activitypub' into featur…
May 10, 2022
becdf5e
Use go-ap instead of go-fed
May 23, 2022
1e57f01
Merge remote-tracking branch 'github/main' into feature-activitypub
Jun 9, 2022
67e0fcd
Run go mod tidy to fix missing modules in go.mod and go.sum
Jun 9, 2022
57e6b67
make fmt
Jun 9, 2022
a8cb4a8
Convert remaining code to go-ap
Jun 9, 2022
94fbd80
Clean up go.sum
Jun 9, 2022
2f0a0b1
Merge branch 'main' into feature-activitypub
6543 Jun 10, 2022
46cab80
Fix JSON unmarshall error
Jun 10, 2022
86a3221
Fix CI errors by adding @context to Person() and making sure types match
Jun 10, 2022
d487a76
Correctly decode JSON in api_activitypub_person_test.go
Jun 10, 2022
fc58ab6
Force CI rerun
Jun 10, 2022
7428ff0
Fix TestActivityPubPersonInbox segfault
Jun 10, 2022
66b1761
Fix lint error
Jun 10, 2022
f7da251
Merge branch 'main' into feature-activitypub
6543 Jun 11, 2022
cf6aed3
Use @mariusor's suggestions for idiomatic go-ap usage
Jun 11, 2022
528c282
Correctly add inbox/outbox IRIs to person
Jun 11, 2022
7658649
Merge remote-tracking branch 'upstream/main' into feature-activitypub
Jun 11, 2022
6074222
Code cleanup
Jun 12, 2022
76f06ce
Remove another LogSQL from ActivityPub person test
Jun 12, 2022
d1f14ff
Move httpsig algos slice to an init() function
Jun 12, 2022
191919e
Merge remote-tracking branch 'upstream/main' into feature-activitypub
Jun 12, 2022
5823d81
Add actor IRI and remote interaction URL to WebFinger response
Apr 22, 2022
d91c61f
Update TestWebFinger to check for ActivityPub IRI in aliases
Jun 13, 2022
d7b81f5
make fmt
Jun 13, 2022
a5b00ec
Force CI rerun
Jun 13, 2022
b6b7fe2
WebFinger: Add CORS header and fix Href -> Template for remote intera…
Jun 13, 2022
718f35a
Merge remote-tracking branch 'upstream/main' into feature-activitypub
Jun 13, 2022
ed2a6f5
make lint-backend
Jun 13, 2022
f889793
Make sure Person endpoint has Content-Type application/activity+json …
Jun 13, 2022
3e9a69c
Use UTC instead of GMT
Jun 14, 2022
d749f8f
Rename pkey to pubKey
Jun 14, 2022
08eebff
Make sure HTTP request Date in GMT
Jun 14, 2022
2706e89
Merge branch 'main' into feature-activitypub
6543 Jun 14, 2022
900ceb2
make fmt
6543 Jun 14, 2022
3f2d8b0
dont drop err
6543 Jun 14, 2022
add8469
Merge branch 'feature-activitypub' of github.com:Ta180m/Gitea into fe…
Jun 14, 2022
e60158c
Make sure API responses always refer to username in original case
Jun 14, 2022
a4403e4
Move httpsig algs constant slice to modules/setting/federation.go
Jun 14, 2022
faf2855
Add new federation settings to app.example.ini and config-cheat-sheet
Jun 14, 2022
d06772b
Return if marshalling error
Jun 14, 2022
a312007
Make sure Person IRIs are generated correctly
Jun 14, 2022
f53e46c
If httpsig verification fails, fix Host header and try again
Jun 14, 2022
f8ad1a8
Apply suggestions from code review
6543 Jun 15, 2022
c05bad8
Merge branch 'main' into feature-activitypub
6543 Jun 15, 2022
14cfd8d
Revert "If httpsig verification fails, fix Host header and try again"
Jun 15, 2022
1da4849
Merge remote-tracking branch 'github/feature-activitypub' into featur…
Jun 15, 2022
f48115f
Go back to using ap.IRI to generate inbox and outbox IRIs
Jun 15, 2022
172c39f
use const for key values
6543 Jun 15, 2022
5840163
Update routers/web/webfinger.go
6543 Jun 15, 2022
46b344c
Merge branch 'main' into feature-activitypub
6543 Jun 15, 2022
e5ed91d
Merge remote-tracking branch 'github/feature-activitypub' into featur…
Jun 15, 2022
95aad98
Use ctx.JSON in Person response to make code cleaner
Jun 16, 2022
3fe4459
Revert "Use ctx.JSON in Person response to make code cleaner"
Jun 16, 2022
e9e8a03
Use activitypub.ActivityStreamsContentType for Person response Conten…
Jun 16, 2022
a2d5202
Limit maximum ActivityPub request and response sizes to a configurabl…
Jun 16, 2022
fb1f551
Move setting key constants to models/user/setting_keys.go
Jun 16, 2022
ad62049
Fix failing ActivityPubPerson integration test by checking the correc…
Jun 16, 2022
db13e1d
Add a warning about changing settings that can break federation
Jun 16, 2022
6336ba2
Add better comments
Jun 16, 2022
0c49fea
Don't multiply Federation.MaxSize by 1<<20 twice
Jun 16, 2022
6602fd1
Add more better comments
Jun 16, 2022
3a8b840
Fix failing ActivityPubMissingPerson test
Jun 16, 2022
2a013b8
make generate-swagger
Jun 16, 2022
c118dac
Move getting the RFC 2616 time to a separate function
Jun 16, 2022
b35490c
Merge remote-tracking branch 'upstream/main' into feature-activitypub
Jun 16, 2022
7a214dd
More code cleanup
Jun 16, 2022
7e1784f
Merge branch 'main' into feature-activitypub
6543 Jun 16, 2022
8e6f3fb
Update go-ap to fix empty liked collection and removed unneeded HTTP …
Jun 17, 2022
7446583
go mod tidy
Jun 17, 2022
47011db
Add ed25519 to httpsig algorithms
Jun 17, 2022
7bfadb4
Merge branch 'main' into feature-activitypub
6543 Jun 18, 2022
fe18cf7
Merge branch 'main' into feature-activitypub
6543 Jun 18, 2022
37d2d01
Use go-ap/jsonld to add @context and marshal JSON
Jun 18, 2022
7c10ab9
Change Gitea user agent from the default to Gitea/Version
Jun 18, 2022
0231dad
go mod tidy
Jun 18, 2022
adfb213
Merge branch 'main' into feature-activitypub
6543 Jun 18, 2022
1bd8eb6
Merge branch 'main' into feature-activitypub
6543 Jun 18, 2022
4ffb6b6
Use ctx.ServerError and remove all remote interaction code from webfi…
Jun 18, 2022
28fd3e7
Remove accidently added files
Jun 18, 2022
f602958
Use ctx.ServerError in reqsignature.go
Jun 18, 2022
a3ff170
Merge branch 'main' into feature-activitypub
6543 Jun 18, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ require (
github.com/go-chi/chi/v5 v5.0.7
github.com/go-chi/cors v1.2.0
github.com/go-enry/go-enry/v2 v2.8.0
github.com/go-fed/activity v1.0.1-0.20220119073622-b14b50eecad0
github.com/go-fed/httpsig v1.1.1-0.20201223112313-55836744818e
github.com/go-git/go-billy/v5 v5.3.1
github.com/go-git/go-git/v5 v5.4.3-0.20210630082519-b4368b2a2ca4
github.com/go-ldap/ldap/v3 v3.4.2
Expand Down
10 changes: 10 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,7 @@ github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ
github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76/go.mod h1:vYwsqCOLxGiisLwp9rITslkFNpZD5rz43tf41QFkTWY=
github.com/daaku/go.zipexe v1.0.0/go.mod h1:z8IiR6TsVLEYKwXAoE/I+8ys/sDkgTzSL0CLnGVd57E=
github.com/daaku/go.zipexe v1.0.1/go.mod h1:5xWogtqlYnfBXkSB1o9xysukNP9GTvaNkqzUZbt3Bw8=
github.com/dave/jennifer v1.3.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg=
github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
Expand Down Expand Up @@ -464,6 +465,11 @@ github.com/go-enry/go-enry/v2 v2.8.0 h1:KMW4mSG+8uUF6FaD3iPkFqyfC5tF8gRrsYImq6yh
github.com/go-enry/go-enry/v2 v2.8.0/go.mod h1:GVzIiAytiS5uT/QiuakK7TF1u4xDab87Y8V5EJRpsIQ=
github.com/go-enry/go-oniguruma v1.2.1 h1:k8aAMuJfMrqm/56SG2lV9Cfti6tC4x8673aHCcBk+eo=
github.com/go-enry/go-oniguruma v1.2.1/go.mod h1:bWDhYP+S6xZQgiRL7wlTScFYBe023B6ilRZbCAD5Hf4=
github.com/go-fed/activity v1.0.1-0.20220119073622-b14b50eecad0 h1:rV8Mp/ChJLd0ZUrS6xMwiP6ZIFpSomffrQOjf4Xyd3M=
github.com/go-fed/activity v1.0.1-0.20220119073622-b14b50eecad0/go.mod h1:v4QoPaAzjWZ8zN2VFVGL5ep9C02mst0hQYHUpQwso4Q=
github.com/go-fed/httpsig v0.1.1-0.20190914113940-c2de3672e5b5/go.mod h1:T56HUNYZUQ1AGUzhAYPugZfp36sKApVnGBgKlIY+aIE=
github.com/go-fed/httpsig v1.1.1-0.20201223112313-55836744818e h1:oRq/fiirun5HqlEWMLIcDmLpIELlG4iGbd0s8iqgPi8=
github.com/go-fed/httpsig v1.1.1-0.20201223112313-55836744818e/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM=
github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4=
github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E=
github.com/go-git/go-billy/v5 v5.2.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
Expand Down Expand Up @@ -608,6 +614,8 @@ github.com/go-swagger/go-swagger v0.29.0/go.mod h1:Z4GJzI+bHKKkGB2Ji1rawpi3/ldXX
github.com/go-swagger/scan-repo-boundary v0.0.0-20180623220736-973b3573c013 h1:l9rI6sNaZgNC0LnF3MiE+qTmyBA/tZAg1rtyrGbUMK0=
github.com/go-swagger/scan-repo-boundary v0.0.0-20180623220736-973b3573c013/go.mod h1:b65mBPzqzZWxOZGxSWrqs4GInLIn+u99Q9q7p+GKni0=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/go-test/deep v1.0.1 h1:UQhStjbkDClarlmv0am7OXXO4/GaPdCGiUiMTvi28sg=
github.com/go-test/deep v1.0.1/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/go-testfixtures/testfixtures/v3 v3.6.1 h1:n4Fv95Exp0D05G6l6CAZv22Ck1EJK0pa0TfPqE4ncSs=
github.com/go-testfixtures/testfixtures/v3 v3.6.1/go.mod h1:Bsb2MoHAfHnNsPpSwAjtOs102mqDuM+1u3nE2OCi0N0=
github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0=
Expand Down Expand Up @@ -1642,6 +1650,7 @@ go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8=
go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
gocloud.dev v0.19.0/go.mod h1:SmKwiR8YwIMMJvQBKLsC3fHNyMwXLw3PMDO+VVteJMI=
golang.org/x/crypto v0.0.0-20180501155221-613d6eafa307/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20180527072434-ab813273cd59/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
Expand Down Expand Up @@ -1838,6 +1847,7 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180525142821-c11f84a56e43/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
Expand Down
133 changes: 133 additions & 0 deletions integrations/api_activitypub_person_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package integrations

import (
"context"
"fmt"
"net/http"
"net/http/httptest"
"net/url"
"testing"

user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/activitypub"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/setting"

"github.com/go-fed/activity/pub"
"github.com/go-fed/activity/streams"
"github.com/go-fed/activity/streams/vocab"
"github.com/stretchr/testify/assert"
)

func TestActivityPubPerson(t *testing.T) {
onGiteaRun(t, func(*testing.T, *url.URL) {
setting.Federation.Enabled = true
defer func() {
setting.Federation.Enabled = false
}()

username := "user2"
req := NewRequestf(t, "GET", fmt.Sprintf("/api/v1/activitypub/user/%s", username))
resp := MakeRequest(t, req, http.StatusOK)
assert.Contains(t, resp.Body.String(), "@context")
var m map[string]interface{}
err := json.Unmarshal(resp.Body.Bytes(), &m)
assert.Equal(t, err, nil)

var person vocab.ActivityStreamsPerson
resolver, _ := streams.NewJSONResolver(func(c context.Context, p vocab.ActivityStreamsPerson) error {
person = p
return nil
})
ctx := context.Background()
err = resolver.Resolve(ctx, m)
assert.Equal(t, err, nil)
assert.Equal(t, "Person", person.GetTypeName())
assert.Equal(t, username, person.GetActivityStreamsName().Begin().GetXMLSchemaString())
keyID := person.GetJSONLDId().GetIRI().String()
assert.Regexp(t, fmt.Sprintf("activitypub/user/%s$", username), keyID)
assert.Regexp(t, fmt.Sprintf("activitypub/user/%s/outbox$", username), person.GetActivityStreamsOutbox().GetIRI().String())
assert.Regexp(t, fmt.Sprintf("activitypub/user/%s/inbox$", username), person.GetActivityStreamsInbox().GetIRI().String())

pkp := person.GetW3IDSecurityV1PublicKey()
assert.NotNil(t, pkp)
publicKeyID := keyID + "/#main-key"
var pkpFound vocab.W3IDSecurityV1PublicKey
for pkpIter := pkp.Begin(); pkpIter != pkp.End(); pkpIter = pkpIter.Next() {
if !pkpIter.IsW3IDSecurityV1PublicKey() {
continue
}
pkValue := pkpIter.Get()
var pkID *url.URL
pkID, err = pub.GetId(pkValue)
if err != nil {
return
}
assert.Equal(t, pkID.String(), publicKeyID)
if pkID.String() != publicKeyID {
continue
}
pkpFound = pkValue
break
}
assert.NotNil(t, pkpFound)

pkPemProp := pkpFound.GetW3IDSecurityV1PublicKeyPem()
assert.NotNil(t, pkPemProp)
assert.True(t, pkPemProp.IsXMLSchemaString())

pubKeyPem := pkPemProp.Get()
assert.Regexp(t, "^-----BEGIN PUBLIC KEY-----", pubKeyPem)
})
}

func TestActivityPubMissingPerson(t *testing.T) {
onGiteaRun(t, func(*testing.T, *url.URL) {
setting.Federation.Enabled = true
defer func() {
setting.Federation.Enabled = false
}()

req := NewRequestf(t, "GET", "/api/v1/activitypub/user/nonexistentuser")
resp := MakeRequest(t, req, http.StatusNotFound)
assert.Contains(t, resp.Body.String(), "GetUserByName")
})
}

func TestActivityPubPersonInbox(t *testing.T) {
srv := httptest.NewServer(c)
defer srv.Close()

onGiteaRun(t, func(*testing.T, *url.URL) {
appURL := setting.AppURL
setting.Federation.Enabled = true
setting.Database.LogSQL = true
setting.AppURL = srv.URL
defer func() {
setting.Federation.Enabled = false
setting.Database.LogSQL = false
setting.AppURL = appURL
}()
username1 := "user1"
user1, err := user_model.GetUserByName(username1)
assert.NoError(t, err)
user1url := fmt.Sprintf("%s/api/v1/activitypub/user/%s/#main-key", srv.URL, username1)
c, err := activitypub.NewClient(user1, user1url)
assert.NoError(t, err)
username2 := "user2"
user2inboxurl := fmt.Sprintf("%s/api/v1/activitypub/user/%s/inbox", srv.URL, username2)

// Signed request succeeds
resp, err := c.Post([]byte{}, user2inboxurl)
assert.NoError(t, err)
assert.Equal(t, 204, resp.StatusCode)

// Unsigned request fails
req := NewRequest(t, "POST", user2inboxurl)
MakeRequest(t, req, 500)
})
}
127 changes: 127 additions & 0 deletions modules/activitypub/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package activitypub

import (
"bytes"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"fmt"
"net/http"

user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/setting"

"github.com/go-fed/activity/pub"
"github.com/go-fed/httpsig"
)

const (
// ActivityStreamsContentType const
ActivityStreamsContentType = "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
)

func containsRequiredHTTPHeaders(method string, headers []string) error {
var hasRequestTarget, hasDate, hasDigest bool
for _, header := range headers {
hasRequestTarget = hasRequestTarget || header == httpsig.RequestTarget
hasDate = hasDate || header == "Date"
hasDigest = method == "GET" || hasDigest || header == "Digest"
}
if !hasRequestTarget {
return fmt.Errorf("missing http header for %s: %s", method, httpsig.RequestTarget)
} else if !hasDate {
return fmt.Errorf("missing http header for %s: Date", method)
} else if !hasDigest {
return fmt.Errorf("missing http header for %s: Digest", method)
}
return nil
}

// Client struct
type Client struct {
clock pub.Clock
client *http.Client
algs []httpsig.Algorithm
digestAlg httpsig.DigestAlgorithm
getHeaders []string
postHeaders []string
priv *rsa.PrivateKey
pubID string
}

// NewClient function
func NewClient(user *user_model.User, pubID string) (c *Client, err error) {
if err = containsRequiredHTTPHeaders(http.MethodGet, setting.Federation.GetHeaders); err != nil {
return
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that there is currently a PR that attempts to get rid of naked returns, I think you should convert them.
Everywhere you added them.

} else if err = containsRequiredHTTPHeaders(http.MethodPost, setting.Federation.PostHeaders); err != nil {
return
} else if !httpsig.IsSupportedDigestAlgorithm(setting.Federation.DigestAlgorithm) {
err = fmt.Errorf("unsupported digest algorithm: %s", setting.Federation.DigestAlgorithm)
return
}
algos := make([]httpsig.Algorithm, len(setting.Federation.Algorithms))
for i, algo := range setting.Federation.Algorithms {
algos[i] = httpsig.Algorithm(algo)
}
clock, err := NewClock()
if err != nil {
return
}

priv, err := GetPrivateKey(user)
if err != nil {
return
}
privPem, _ := pem.Decode([]byte(priv))
privParsed, err := x509.ParsePKCS1PrivateKey(privPem.Bytes)
if err != nil {
return
}

c = &Client{
clock: clock,
client: &http.Client{},
algs: algos,
digestAlg: httpsig.DigestAlgorithm(setting.Federation.DigestAlgorithm),
getHeaders: setting.Federation.GetHeaders,
postHeaders: setting.Federation.PostHeaders,
priv: privParsed,
pubID: pubID,
}
return
}

// NewRequest function
func (c *Client) NewRequest(b []byte, to string) (req *http.Request, err error) {
byteCopy := make([]byte, len(b))
copy(byteCopy, b)
buf := bytes.NewBuffer(byteCopy)
req, err = http.NewRequest(http.MethodPost, to, buf)
if err != nil {
return
}
req.Header.Add("Content-Type", ActivityStreamsContentType)
req.Header.Add("Accept-Charset", "utf-8")
req.Header.Add("Date", fmt.Sprintf("%s GMT", c.clock.Now().UTC().Format("Mon, 02 Jan 2006 15:04:05")))

signer, _, err := httpsig.NewSigner(c.algs, c.digestAlg, c.postHeaders, httpsig.Signature, 60)
if err != nil {
return
}
err = signer.SignRequest(c.priv, c.pubID, req, b)
return
}

// Post function
func (c *Client) Post(b []byte, to string) (resp *http.Response, err error) {
var req *http.Request
if req, err = c.NewRequest(b, to); err != nil {
return
}
resp, err = c.client.Do(req)
return
}
49 changes: 49 additions & 0 deletions modules/activitypub/client_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package activitypub

import (
"fmt"
"io"
"net/http"
"net/http/httptest"
"regexp"
"testing"

"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/setting"

_ "code.gitea.io/gitea/models" // https://discourse.gitea.io/t/testfixtures-could-not-clean-table-access-no-such-table-access/4137/4

"github.com/stretchr/testify/assert"
)

func TestActivityPubSignedPost(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}).(*user_model.User)
pubID := "https://example.com/pubID"
c, err := NewClient(user, pubID)
assert.NoError(t, err)

expected := "BODY"
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Regexp(t, regexp.MustCompile("^"+setting.Federation.DigestAlgorithm), r.Header.Get("Digest"))
assert.Contains(t, r.Header.Get("Signature"), pubID)
assert.Equal(t, r.Header.Get("Content-Type"), ActivityStreamsContentType)
body, err := io.ReadAll(r.Body)
assert.NoError(t, err)
assert.Equal(t, expected, string(body))
fmt.Fprintf(w, expected)
}))
defer srv.Close()

r, err := c.Post([]byte(expected), srv.URL)
assert.NoError(t, err)
defer r.Body.Close()
body, err := io.ReadAll(r.Body)
assert.NoError(t, err)
assert.Equal(t, expected, string(body))
}
29 changes: 29 additions & 0 deletions modules/activitypub/clock.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package activitypub

import (
"time"

"code.gitea.io/gitea/modules/setting"

"github.com/go-fed/activity/pub"
)

var _ pub.Clock = &Clock{}

// Clock struct
type Clock struct{}

// NewClock function
func NewClock() (c *Clock, err error) {
c = &Clock{}
return
}

// Now function
func (c *Clock) Now() time.Time {
return time.Now().In(setting.DefaultUILocation)
}
Loading