Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
forgefriends
Forgefriends
Commits
323cdf84
Verified
Commit
323cdf84
authored
Nov 09, 2021
by
Loïc Dachary
Browse files
activitypub: signing http client
Signed-off-by:
Loïc Dachary
<
loic@dachary.org
>
parent
7a3ac62f
Changes
4
Hide whitespace changes
Inline
Side-by-side
modules/activitypub/client.go
0 → 100644
View file @
323cdf84
// Copyright 2021 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
=
"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
}
type
Client
struct
{
clock
pub
.
Clock
client
*
http
.
Client
algs
[]
httpsig
.
Algorithm
digestAlg
httpsig
.
DigestAlgorithm
getHeaders
[]
string
postHeaders
[]
string
priv
*
rsa
.
PrivateKey
pubId
string
}
func
NewClient
(
user
*
user_model
.
User
,
pubId
string
)
(
c
*
Client
,
err
error
)
{
if
err
=
containsRequiredHttpHeaders
(
http
.
MethodGet
,
setting
.
Federation
.
GetHeaders
);
err
!=
nil
{
return
}
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
}
func
(
c
*
Client
)
Post
(
b
[]
byte
,
to
string
)
(
resp
*
http
.
Response
,
err
error
)
{
byteCopy
:=
make
([]
byte
,
len
(
b
))
copy
(
byteCopy
,
b
)
buf
:=
bytes
.
NewBuffer
(
byteCopy
)
var
req
*
http
.
Request
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
)
if
err
!=
nil
{
return
}
resp
,
err
=
c
.
client
.
Do
(
req
)
return
}
modules/activitypub/client_test.go
0 → 100644
View file @
323cdf84
// Copyright 2021 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"
"io/ioutil"
"net/http"
"net/http/httptest"
"regexp"
"testing"
_
"code.gitea.io/gitea/models"
// https://discourse.gitea.io/t/testfixtures-could-not-clean-table-access-no-such-table-access/4137/4
"code.gitea.io/gitea/models/unittest"
user_model
"code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/setting"
"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
:=
ioutil
.
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
))
}
modules/activitypub/main_test.go
0 → 100644
View file @
323cdf84
// Copyright 2021 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
(
"path/filepath"
"testing"
"code.gitea.io/gitea/models/unittest"
)
func
TestMain
(
m
*
testing
.
M
)
{
unittest
.
MainTest
(
m
,
filepath
.
Join
(
".."
,
".."
))
}
modules/setting/federation.go
View file @
323cdf84
...
...
@@ -9,9 +9,17 @@ import "code.gitea.io/gitea/modules/log"
// Federation settings
var
(
Federation
=
struct
{
Enabled
bool
Enabled
bool
Algorithms
[]
string
DigestAlgorithm
string
GetHeaders
[]
string
PostHeaders
[]
string
}{
Enabled
:
true
,
Enabled
:
true
,
Algorithms
:
[]
string
{
"rsa-sha256"
,
"rsa-sha512"
},
DigestAlgorithm
:
"SHA-256"
,
GetHeaders
:
[]
string
{
"(request-target)"
,
"Date"
},
PostHeaders
:
[]
string
{
"(request-target)"
,
"Date"
,
"Digest"
},
}
)
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment