Compare commits

..

No commits in common. "6d51145dad8b236aa24d69e92cc032f5bbe4cc13" and "dbfe957f6344e68aa84c84b16d3d98b858490b33" have entirely different histories.

7 changed files with 88 additions and 194 deletions

View File

@ -15,7 +15,7 @@ import (
var authHeaderRegex = regexp.MustCompile("^Bearer (.+)$")
func cachedCheckAuthToken(conn redis.Conn, header string, tokenEndpoint string, r *auth.TokenResponse) (bool, error) {
func (b *memoryBackend) cachedCheckAuthToken(conn redis.Conn, header string, r *auth.TokenResponse) (bool, error) {
tokens := authHeaderRegex.FindStringSubmatch(header)
if len(tokens) != 2 {
@ -33,7 +33,7 @@ func cachedCheckAuthToken(conn redis.Conn, header string, tokenEndpoint string,
return true, nil
}
authorized, err = checkAuthToken(header, tokenEndpoint, r)
authorized, err = b.checkAuthToken(header, r)
if err != nil {
return false, errors.Wrap(err, "could not check auth token")
}
@ -50,7 +50,8 @@ func cachedCheckAuthToken(conn redis.Conn, header string, tokenEndpoint string,
return authorized, nil
}
func checkAuthToken(header string, tokenEndpoint string, token *auth.TokenResponse) (bool, error) {
func (b *memoryBackend) checkAuthToken(header string, token *auth.TokenResponse) (bool, error) {
tokenEndpoint := b.TokenEndpoint
req, err := buildValidateAuthTokenRequest(tokenEndpoint, header)
if err != nil {

View File

@ -1,7 +1,6 @@
package main
import (
"expvar"
"fmt"
"io"
"log"
@ -50,22 +49,6 @@ type Feed struct {
ResubscribeAt int64 `redis:"resubscribe_at"`
}
var (
subscribeRuns *expvar.Int
resubscriptions *expvar.Int
resubscribeErrors *expvar.Int
)
func init() {
subscribeRuns = expvar.NewInt("subscribe.runs")
resubscriptions = expvar.NewInt("subscribe.calls")
resubscribeErrors = expvar.NewInt("subscribe.errors")
expvar.Publish("subscribe.runs", subscribeRuns)
expvar.Publish("subscribe.calls", resubscriptions)
expvar.Publish("subscribe.errors", resubscribeErrors)
}
func (h *hubIncomingBackend) GetSecret(id int64) string {
conn := h.pool.Get()
defer conn.Close()
@ -248,12 +231,7 @@ func (h *hubIncomingBackend) run() error {
select {
case <-ticker.C:
log.Println("Getting feeds for WebSub")
subscribeRuns.Add(1)
feeds, err := h.Feeds()
if err != nil {
}
feeds := h.GetFeeds()
for _, feed := range feeds {
log.Printf("Looking at %s\n", feed.URL)
if feed.ResubscribeAt == 0 || time.Now().After(time.Unix(feed.ResubscribeAt, 0)) {
@ -261,11 +239,9 @@ func (h *hubIncomingBackend) run() error {
feed.Callback = fmt.Sprintf("%s/incoming/%d", h.baseURL, feed.ID)
}
log.Printf("Send resubscribe for %q on %q with callback %q\n", feed.URL, feed.Hub, feed.Callback)
resubscriptions.Add(1)
err := h.Subscribe(&feed)
if err != nil {
log.Printf("Error while subscribing: %s", err)
resubscribeErrors.Add(1)
}
}
}

View File

@ -2,14 +2,15 @@ package main
import (
"bytes"
"crypto/hmac"
"crypto/sha1"
"fmt"
"io/ioutil"
"log"
"net/http"
"regexp"
"strconv"
"p83.nl/go/ekster/pkg/websub"
"strings"
)
type incomingHandler struct {
@ -84,9 +85,8 @@ func (h *incomingHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// match signature
sig := r.Header.Get("X-Hub-Signature")
if sig != "" {
if err := websub.ValidateHubSignature(sig, feedContent, []byte(secret)); err != nil {
log.Printf("could not validate signature: %+v", err)
http.Error(w, fmt.Sprintf("could not validate signature: %s", err), 400)
if err := isHubSignatureValid(sig, feedContent, secret); err != nil {
http.Error(w, fmt.Sprintf("Error in signature: %s", err), 400)
return
}
}
@ -94,9 +94,32 @@ func (h *incomingHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
ct := r.Header.Get("Content-Type")
err = h.Backend.UpdateFeed(feed, ct, bytes.NewBuffer(feedContent))
if err != nil {
http.Error(w, fmt.Sprintf("could not update feed: %s (%s)", ct, err), 400)
http.Error(w, fmt.Sprintf("Unknown format of body: %s (%s)", ct, err), 400)
return
}
return
}
func isHubSignatureValid(sig string, feedContent []byte, secret string) error {
parts := strings.Split(sig, "=")
if len(parts) != 2 {
return fmt.Errorf("signature format is not like sha1=signature")
}
if parts[0] != "sha1" {
return fmt.Errorf("signature format is not like sha1=signature")
}
// verification
mac := hmac.New(sha1.New, []byte(secret))
mac.Write(feedContent)
signature := mac.Sum(nil)
if fmt.Sprintf("%x", signature) != parts[1] {
return fmt.Errorf("signature does not match feed %s %s", signature, parts[1])
}
return nil
}

View File

@ -75,7 +75,7 @@ func (b *memoryBackend) AuthTokenAccepted(header string, r *auth.TokenResponse)
log.Printf("could not close redis connection: %v", err)
}
}()
return cachedCheckAuthToken(conn, header, b.TokenEndpoint, r)
return b.cachedCheckAuthToken(conn, header, r)
}
func (b *memoryBackend) Debug() {

View File

@ -22,86 +22,6 @@ type micropubHandler struct {
pool *redis.Pool
}
func (h *micropubHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
defer func() {
err := r.Body.Close()
if err != nil {
log.Printf("could not close request body: %v", err)
}
}()
conn := h.pool.Get()
defer func() {
err := conn.Close()
if err != nil {
log.Printf("could not close redis connection: %v", err)
}
}()
err := r.ParseForm()
if err != nil {
http.Error(w, "bad request", 400)
return
}
if r.Method == http.MethodPost {
var channel string
channel, err = getChannelFromAuthorization(r, conn)
if err != nil {
log.Println(err)
http.Error(w, "unauthorized", 401)
return
}
// no channel is found
if channel == "" {
http.Error(w, "bad request, unknown channel", 400)
return
}
item, err := parseIncomingItem(r)
if err != nil {
http.Error(w, err.Error(), 400)
return
}
item.Read = false
newID, err := generateItemID(conn, channel)
if err != nil {
http.Error(w, err.Error(), 500)
return
}
item.ID = newID
err = h.Backend.channelAddItemWithMatcher(channel, *item)
if err != nil {
log.Printf("could not add item to channel %s: %v", channel, err)
}
err = h.Backend.updateChannelUnreadCount(channel)
if err != nil {
log.Printf("could not update channel unread content %s: %v", channel, err)
}
w.Header().Set("Content-Type", "application/json")
if err = json.NewEncoder(w).Encode(map[string]string{"ok": "1"}); err != nil {
http.Error(w, "internal server error", 500)
}
return
}
http.Error(w, "Method not allowed", 405)
}
func generateItemID(conn redis.Conn, channel string) (string, error) {
id, err := redis.Int(conn.Do("INCR", fmt.Sprintf("source:%s:next_id", channel)))
if err != nil {
return "", err
}
return fmt.Sprintf("%x", sha1.Sum([]byte(fmt.Sprintf("source:%s:%d", channel, id)))), nil
}
func parseIncomingItem(r *http.Request) (*microsub.Item, error) {
var item microsub.Item
@ -139,29 +59,66 @@ func parseIncomingItem(r *http.Request) (*microsub.Item, error) {
return &item, nil
}
func getChannelFromAuthorization(r *http.Request, conn redis.Conn) (string, error) {
// backward compatible
sourceID := r.URL.Query().Get("source_id")
if sourceID != "" {
channel, err := redis.String(conn.Do("HGET", "sources", sourceID))
/*
* URLs needed:
* - / with endpoint urls
* - /micropub micropub endpoint
* - /auth auth endpoint
* - /token token endpoint
*/
func (h *micropubHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
conn := h.pool.Get()
defer conn.Close()
err := r.ParseForm()
if err != nil {
return "", errors.Wrapf(err, "could not get channel for sourceID: %s", sourceID)
http.Error(w, "bad request", 400)
return
}
return channel, nil
}
if r.Method == http.MethodPost {
sourceID := r.URL.Query().Get("source_id")
// full micropub with indieauth
authHeader := r.Header.Get("Authorization")
if strings.HasPrefix(authHeader, "Bearer ") {
token := authHeader[7:]
channel, err := redis.String(conn.Do("HGET", "token:"+token, "channel"))
sourceID = authHeader[7:]
}
channel, err := redis.String(conn.Do("HGET", "sources", sourceID))
if err != nil {
return "", errors.Wrap(err, "could not get channel for token")
channel, err = redis.String(conn.Do("HGET", "token:"+sourceID, "channel"))
if err != nil {
http.Error(w, "unauthorized", 401)
return
}
}
return channel, nil
item, err := parseIncomingItem(r)
if err != nil {
http.Error(w, err.Error(), 400)
return
}
return "", fmt.Errorf("could not get channel from authorization")
item.Read = false
id, _ := redis.Int(conn.Do("INCR", "source:"+sourceID+"next_id"))
item.ID = fmt.Sprintf("%x", sha1.Sum([]byte(fmt.Sprintf("source:%s:%d", sourceID, id))))
err = h.Backend.channelAddItemWithMatcher(channel, *item)
err = h.Backend.updateChannelUnreadCount(channel)
if err != nil {
log.Printf("could not update channel unread content %s: %v", channel, err)
}
w.Header().Set("Content-Type", "application/json")
enc := json.NewEncoder(w)
err = enc.Encode(map[string]string{
"ok": "1",
})
return
}
http.Error(w, "Method not allowed", 405)
}

View File

@ -1,41 +0,0 @@
package websub
import (
"crypto/hmac"
"crypto/sha1"
"encoding/hex"
"fmt"
"strings"
"github.com/pkg/errors"
)
// ValidateHubSignature validate a sha1 signature that could be send with the
// hub as an extra header
func ValidateHubSignature(sig string, feedContent, secret []byte) error {
parts := strings.Split(sig, "=")
if len(parts) != 2 {
return errors.New("signature format is not like sha1=signature")
}
if parts[0] != "sha1" {
return errors.New("signature format is not like sha1=signature")
}
// verification
mac := hmac.New(sha1.New, secret)
mac.Write(feedContent)
signature := mac.Sum(nil)
signature2, err := hex.DecodeString(parts[1])
if err != nil {
return errors.Wrap(err, "could not decode signature")
}
if !hmac.Equal(signature, signature2) {
return fmt.Errorf("signature does not match feed %s %s", signature, parts[1])
}
return nil
}

View File

@ -1,22 +0,0 @@
package websub
import (
"crypto/hmac"
"crypto/sha1"
"fmt"
"testing"
"github.com/stretchr/testify/assert"
)
func TestValidateHubSignature(t *testing.T) {
secret := []byte("this is a test secret")
feedContent := []byte("hello world")
mac := hmac.New(sha1.New, secret)
mac.Write(feedContent)
signature := mac.Sum(nil)
err := ValidateHubSignature(fmt.Sprintf("sha1=%x", signature), feedContent, secret)
assert.NoError(t, err, "error should be nil")
}