package server_test
import (
"bytes"
"context"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
nethttp "net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/keys-pub/keys"
"github.com/keys-pub/keys-ext/http/server"
"github.com/keys-pub/keys/dstore"
"github.com/keys-pub/keys/encoding"
"github.com/keys-pub/keys/http"
"github.com/keys-pub/keys/tsutil"
"github.com/keys-pub/keys/user"
"github.com/stretchr/testify/require"
)
type testServer struct {
Server *server.Server
Handler http.Handler
Addr string
}
func testFire(t *testing.T, clock tsutil.Clock) server.Fire {
fi := dstore.NewMem()
fi.SetClock(clock)
return fi
}
func TestFireCreatedAt(t *testing.T) {
clock := tsutil.NewTestClock()
fi := testFire(t, clock)
err := fi.Set(context.TODO(), "/test/a", dstore.Data([]byte{0x01}))
require.NoError(t, err)
doc, err := fi.Get(context.TODO(), "/test/a")
require.NoError(t, err)
require.NotNil(t, doc)
ftime := doc.CreatedAt.Format(http.TimeFormat)
require.Equal(t, "Fri, 13 Feb 2009 23:31:30 GMT", ftime)
ftime = doc.CreatedAt.Format(tsutil.RFC3339Milli)
require.Equal(t, "2009-02-13T23:31:30.001Z", ftime)
}
type env struct {
clock tsutil.Clock
fi server.Fire
client http.Client
logLevel server.LogLevel
}
func newEnv(t *testing.T) *env {
clock := tsutil.NewTestClock()
fi := testFire(t, clock)
return newEnvWithFire(t, fi, clock)
}
func newEnvWithFire(t *testing.T, fi server.Fire, clock tsutil.Clock) *env {
client := http.NewClient()
return &env{
clock: clock,
fi: fi,
client: client,
logLevel: server.NoLevel,
}
}
func newTestServer(t *testing.T, env *env) *testServer {
rds := server.NewRedisTest(env.clock)
srv := server.New(env.fi, rds, env.client, env.clock, server.NewLogger(env.logLevel))
tasks := server.NewTestTasks(srv)
srv.SetTasks(tasks)
srv.SetInternalAuth(encoding.MustEncode(keys.RandBytes(32), encoding.Base62))
_ = srv.SetInternalKey("6a169a699f7683c04d127504a12ace3b326e8b56a61a9b315cf6b42e20d6a44a")
srv.SetClock(env.clock)
handler := server.NewHandler(srv)
return &testServer{
Server: srv,
Handler: handler,
}
}
func (s *testServer) Serve(req *http.Request) (int, nethttp.Header, string) {
rr := httptest.NewRecorder()
s.Handler.ServeHTTP(rr, req)
return rr.Code, rr.Header(), rr.Body.String()
}
func testSeed(b byte) *[32]byte {
return keys.Bytes32(bytes.Repeat([]byte{b}, 32))
}
type testKeys struct {
alice *keys.EdX25519Key
bob *keys.EdX25519Key
channel *keys.EdX25519Key
channel2 *keys.EdX25519Key
frank *keys.EdX25519Key
}
func testKeysSeeded() testKeys {
alice := keys.NewEdX25519KeyFromSeed(testSeed(0x01))
bob := keys.NewEdX25519KeyFromSeed(testSeed(0x02))
channel := keys.NewEdX25519KeyFromSeed(testSeed(0xef))
channel2 := keys.NewEdX25519KeyFromSeed(testSeed(0xf0))
frank := keys.NewEdX25519KeyFromSeed(testSeed(0x06))
return testKeys{
alice: alice,
bob: bob,
channel: channel,
channel2: channel2,
frank: frank,
}
}
func testKeysRandom() testKeys {
alice := keys.GenerateEdX25519Key()
bob := keys.GenerateEdX25519Key()
channel := keys.GenerateEdX25519Key()
channel2 := keys.GenerateEdX25519Key()
frank := keys.GenerateEdX25519Key()
return testKeys{
alice: alice,
bob: bob,
channel: channel,
channel2: channel2,
frank: frank,
}
}
func userMock(t *testing.T, key *keys.EdX25519Key, name string, service string, client http.Client, clock tsutil.Clock) *keys.Statement {
url := ""
api := ""
id := hex.EncodeToString(sha256.New().Sum([]byte(service + "/" + name))[:8])
switch service {
case "github":
url = fmt.Sprintf("https://gist.github.com/%s/"+id, name)
api = "https://api.github.com/gists/" + id
default:
t.Fatal("unsupported service in test")
}
sc := keys.NewSigchain(key.ID())
usr, err := user.New(key.ID(), service, name, url, sc.LastSeq()+1)
require.NoError(t, err)
st, err := user.NewSigchainStatement(sc, usr, key, clock.Now())
require.NoError(t, err)
msg, err := usr.Sign(key)
require.NoError(t, err)
client.SetProxy(api, func(ctx context.Context, req *http.Request, headers []http.Header) http.ProxyResponse {
return http.ProxyResponse{Body: []byte(githubMock(name, "1", msg))}
})
return st
}
func githubMock(name string, id string, msg string) string {
msg = strings.ReplaceAll(msg, "\n", "")
return `{
"id": "` + id + `",
"files": {
"gistfile1.txt": {
"content": "` + msg + `"
}
},
"owner": {
"login": "` + name + `"
}
}`
}
func testJSONMarshal(t *testing.T, i interface{}) []byte {
b, err := json.Marshal(i)
require.NoError(t, err)
return b
}
func testJSONUnmarshal(t *testing.T, b []byte, v interface{}) {
err := json.Unmarshal(b, v)
require.NoError(t, err)
}
func TestInternalAuth(t *testing.T) {
env := newEnv(t)
srv := newTestServer(t, env)
alice := keys.NewEdX25519KeyFromSeed(keys.Bytes32(bytes.Repeat([]byte{0x01}, 32)))
req, err := http.NewRequest("POST", "/task/check/"+alice.ID().String(), nil)
require.NoError(t, err)
code, _, body := srv.Serve(req)
require.Equal(t, http.StatusForbidden, code)
require.Equal(t, `{"error":{"code":403,"message":"auth failed"}}`, body)
srv.Server.SetInternalAuth("testtoken")
req, err = http.NewRequest("POST", "/task/check/"+alice.ID().String(), nil)
require.NoError(t, err)
req.Header.Set("Authorization", "testtoken")
code, _, body = srv.Serve(req)
require.Equal(t, http.StatusOK, code)
require.Equal(t, "", body)
}