Storage to uuids
This commit is contained in:
194
server/auth.go
Normal file
194
server/auth.go
Normal file
@@ -0,0 +1,194 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"local/dndex/config"
|
||||
"local/dndex/storage"
|
||||
"local/dndex/storage/entity"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
func Auth(g storage.RateLimitedGraph, w http.ResponseWriter, r *http.Request) error {
|
||||
if !config.New().Auth {
|
||||
return nil
|
||||
}
|
||||
if err := auth(g, w, r); err != nil {
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{"error": "error when authorizing: " + err.Error()})
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func auth(g storage.RateLimitedGraph, w http.ResponseWriter, r *http.Request) error {
|
||||
if isPublic(g, r) {
|
||||
return nil
|
||||
}
|
||||
if !hasAuth(r) {
|
||||
return requestAuth(g, w, r)
|
||||
}
|
||||
return checkAuth(g, w, r)
|
||||
}
|
||||
|
||||
func isPublic(g storage.RateLimitedGraph, r *http.Request) bool {
|
||||
namespace, err := getAuthNamespace(r)
|
||||
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
ones, err := g.List(r.Context(), namespace, UserKey)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if len(ones) == 0 {
|
||||
return false
|
||||
}
|
||||
return ones[0].Title == ""
|
||||
}
|
||||
|
||||
func hasAuth(r *http.Request) bool {
|
||||
_, err := r.Cookie(AuthKey)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func checkAuth(g storage.RateLimitedGraph, w http.ResponseWriter, r *http.Request) error {
|
||||
namespace, err := getAuthNamespace(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
token, _ := r.Cookie(AuthKey)
|
||||
results, err := g.List(r.Context(), namespace, token.Value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(results) != 1 {
|
||||
return requestAuth(g, w, r)
|
||||
}
|
||||
modified := time.Unix(0, results[0].Modified)
|
||||
if time.Since(modified) > config.New().AuthLifetime {
|
||||
return requestAuth(g, w, r)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func requestAuth(g storage.RateLimitedGraph, w http.ResponseWriter, r *http.Request) error {
|
||||
namespace, err := getAuthNamespace(r)
|
||||
if err != nil {
|
||||
http.Error(w, `{"error": "namespace required"}`, http.StatusBadRequest)
|
||||
return err
|
||||
}
|
||||
|
||||
ones, err := g.List(r.Context(), namespace, UserKey)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return err
|
||||
}
|
||||
if len(ones) != 1 {
|
||||
http.NotFound(w, r)
|
||||
return errors.New("namespace not established")
|
||||
}
|
||||
userKey := ones[0]
|
||||
|
||||
id := uuid.New().String()
|
||||
token := entity.One{
|
||||
ID: id,
|
||||
Name: id,
|
||||
Title: namespace,
|
||||
}
|
||||
if err := g.Insert(r.Context(), namespace, token); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return err
|
||||
}
|
||||
|
||||
encodedToken, err := aesEnc(userKey.Title, token.Name)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return err
|
||||
}
|
||||
http.SetCookie(w, &http.Cookie{Name: NewAuthKey, Value: encodedToken})
|
||||
|
||||
http.Redirect(w, r, r.URL.String(), http.StatusSeeOther)
|
||||
return errors.New("auth requested")
|
||||
}
|
||||
|
||||
func aesEnc(key, payload string) (string, error) {
|
||||
if len(key) == 0 {
|
||||
return "", errors.New("key required")
|
||||
}
|
||||
key = strings.Repeat(key, 32)[:32]
|
||||
|
||||
block, err := aes.NewCipher([]byte(key))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
gcm, err := cipher.NewGCM(block)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
nonce := make([]byte, gcm.NonceSize())
|
||||
if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
|
||||
return "", err
|
||||
}
|
||||
b := gcm.Seal(nonce, nonce, []byte(payload), nil)
|
||||
|
||||
return base64.StdEncoding.EncodeToString(b), nil
|
||||
}
|
||||
|
||||
func aesDec(key, payload string) (string, error) {
|
||||
if len(key) == 0 {
|
||||
return "", errors.New("key required")
|
||||
}
|
||||
key = strings.Repeat(key, 32)[:32]
|
||||
|
||||
ciphertext, err := base64.StdEncoding.DecodeString(payload)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
block, err := aes.NewCipher([]byte(key))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
gcm, err := cipher.NewGCM(block)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(ciphertext) < gcm.NonceSize() {
|
||||
return "", errors.New("short ciphertext")
|
||||
}
|
||||
b, err := gcm.Open(nil, ciphertext[:gcm.NonceSize()], ciphertext[gcm.NonceSize():], nil)
|
||||
return string(b), err
|
||||
}
|
||||
|
||||
func getAuthNamespace(r *http.Request) (string, error) {
|
||||
namespace, err := getNamespace(r)
|
||||
return strings.Join([]string{namespace, AuthKey}, "."), err
|
||||
}
|
||||
|
||||
func getNamespace(r *http.Request) (string, error) {
|
||||
if strings.HasPrefix(r.URL.Path, config.New().FilePrefix) {
|
||||
path := strings.TrimPrefix(r.URL.Path, config.New().FilePrefix+"/")
|
||||
if path == r.URL.Path {
|
||||
return "", errors.New("no namespace on files")
|
||||
}
|
||||
path = strings.Split(path, "/")[0]
|
||||
if path == "" {
|
||||
return "", errors.New("empty namespace on files")
|
||||
}
|
||||
return path, nil
|
||||
}
|
||||
namespace := r.URL.Query().Get("namespace")
|
||||
if len(namespace) == 0 {
|
||||
return "", errors.New("no namespace found")
|
||||
}
|
||||
return namespace, nil
|
||||
}
|
||||
184
server/auth_test.go
Normal file
184
server/auth_test.go
Normal file
@@ -0,0 +1,184 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"local/dndex/storage/entity"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
func TestAuth(t *testing.T) {
|
||||
os.Args = os.Args[:1]
|
||||
|
||||
rest, clean := testREST(t)
|
||||
defer clean()
|
||||
|
||||
handler := rest.router
|
||||
g := rest.g
|
||||
|
||||
os.Setenv("AUTH", "true")
|
||||
defer os.Setenv("AUTH", "false")
|
||||
|
||||
if err := g.Insert(context.TODO(), "col."+AuthKey, entity.One{ID: UserKey, Name: UserKey, Title: "password"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
t.Run("auth: no namespace", func(t *testing.T) {
|
||||
r := httptest.NewRequest(http.MethodGet, "/who", nil)
|
||||
w := httptest.NewRecorder()
|
||||
handler.ServeHTTP(w, r)
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Fatalf("%d: %s", w.Code, w.Body.Bytes())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("auth: bad provided", func(t *testing.T) {
|
||||
r := httptest.NewRequest(http.MethodGet, "/who?namespace=col", nil)
|
||||
r.Header.Set("Cookie", fmt.Sprintf("%s=not-a-real-token", AuthKey))
|
||||
w := httptest.NewRecorder()
|
||||
handler.ServeHTTP(w, r)
|
||||
if w.Code != http.StatusSeeOther {
|
||||
t.Fatalf("%d: %s", w.Code, w.Body.Bytes())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("auth: expired provided", func(t *testing.T) {
|
||||
os.Setenv("AUTHLIFETIME", "1ms")
|
||||
defer os.Setenv("AUTHLIFETIME", "1h")
|
||||
one := entity.One{ID: uuid.New().String(), Name: uuid.New().String(), Title: "title"}
|
||||
if err := g.Insert(context.TODO(), "col", one); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
time.Sleep(time.Millisecond * 50)
|
||||
r := httptest.NewRequest(http.MethodGet, "/who?namespace=col", nil)
|
||||
r.Header.Set("Cookie", fmt.Sprintf("%s=%s", AuthKey, one.ID))
|
||||
w := httptest.NewRecorder()
|
||||
handler.ServeHTTP(w, r)
|
||||
if w.Code != http.StatusSeeOther {
|
||||
t.Fatalf("%d: %s", w.Code, w.Body.Bytes())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("auth: none provided: who", func(t *testing.T) {
|
||||
r := httptest.NewRequest(http.MethodGet, "/who?namespace=col", nil)
|
||||
w := httptest.NewRecorder()
|
||||
handler.ServeHTTP(w, r)
|
||||
if w.Code != http.StatusSeeOther {
|
||||
t.Fatalf("%d: %s", w.Code, w.Body.Bytes())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("auth: none provided: files", func(t *testing.T) {
|
||||
r := httptest.NewRequest(http.MethodGet, "/__files__/col/myfile", nil)
|
||||
w := httptest.NewRecorder()
|
||||
handler.ServeHTTP(w, r)
|
||||
if w.Code != http.StatusSeeOther {
|
||||
t.Fatalf("%d: %s", w.Code, w.Body.Bytes())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("auth: provided", func(t *testing.T) {
|
||||
os.Setenv("AUTHLIFETIME", "1h")
|
||||
one := entity.One{ID: uuid.New().String(), Name: uuid.New().String(), Title: "title"}
|
||||
if err := g.Insert(context.TODO(), "col."+AuthKey, one); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
r := httptest.NewRequest(http.MethodTrace, "/who?namespace=col", nil)
|
||||
r.Header.Set("Cookie", fmt.Sprintf("%s=%s", AuthKey, one.Name))
|
||||
w := httptest.NewRecorder()
|
||||
handler.ServeHTTP(w, r)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("%d: %s", w.Code, w.Body.Bytes())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("auth: request unknown namespace", func(t *testing.T) {
|
||||
os.Setenv("AUTHLIFETIME", "1h")
|
||||
r := httptest.NewRequest(http.MethodTrace, "/who?namespace=not-col", nil)
|
||||
w := httptest.NewRecorder()
|
||||
handler.ServeHTTP(w, r)
|
||||
if w.Code != http.StatusNotFound {
|
||||
t.Fatalf("%d: %s", w.Code, w.Body.Bytes())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("auth: request", func(t *testing.T) {
|
||||
os.Setenv("AUTHLIFETIME", "1h")
|
||||
r := httptest.NewRequest(http.MethodTrace, "/who?namespace=col", nil)
|
||||
w := httptest.NewRecorder()
|
||||
handler.ServeHTTP(w, r)
|
||||
if w.Code != http.StatusSeeOther {
|
||||
t.Fatalf("%d: %s", w.Code, w.Body.Bytes())
|
||||
}
|
||||
|
||||
rawtoken := getCookie(NewAuthKey, w.Header())
|
||||
if rawtoken == "" {
|
||||
t.Fatal(w.Header())
|
||||
}
|
||||
token, err := aesDec("password", rawtoken)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
r = httptest.NewRequest(http.MethodTrace, "/who?namespace=col", nil)
|
||||
w = httptest.NewRecorder()
|
||||
r.Header.Set("Cookie", fmt.Sprintf("%s=%s", AuthKey, token))
|
||||
handler.ServeHTTP(w, r)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("%d: %s", w.Code, w.Body.Bytes())
|
||||
}
|
||||
|
||||
r = httptest.NewRequest(http.MethodTrace, "/__files__/col/myfile", nil)
|
||||
w = httptest.NewRecorder()
|
||||
r.Header.Set("Cookie", fmt.Sprintf("%s=%s", AuthKey, token))
|
||||
handler.ServeHTTP(w, r)
|
||||
if w.Code != http.StatusNotFound {
|
||||
t.Fatalf("%d: %s", w.Code, w.Body.Bytes())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestAES(t *testing.T) {
|
||||
for _, plaintext := range []string{"", "payload!", "a really long payload here"} {
|
||||
key := "password"
|
||||
|
||||
enc, err := aesEnc(key, plaintext)
|
||||
if err != nil {
|
||||
t.Fatal("cannot enc:", err)
|
||||
}
|
||||
if enc == plaintext {
|
||||
t.Fatal(enc)
|
||||
}
|
||||
|
||||
dec, err := aesDec(key, enc)
|
||||
if err != nil {
|
||||
t.Fatal("cannot dec:", err)
|
||||
}
|
||||
if dec != plaintext {
|
||||
t.Fatalf("want decrypted %q, got %q", plaintext, dec)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getCookie(key string, header http.Header) string {
|
||||
cookies, _ := header["Set-Cookie"]
|
||||
if len(cookies) == 0 {
|
||||
cookies, _ = header["Cookie"]
|
||||
}
|
||||
for i := range cookies {
|
||||
value := strings.Split(cookies[i], ";")[0]
|
||||
k := value[:strings.Index(value, "=")]
|
||||
v := value[strings.Index(value, "=")+1:]
|
||||
if k == key {
|
||||
return v
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
11
server/const.go
Normal file
11
server/const.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package server
|
||||
|
||||
const (
|
||||
AuthKey = "DnDex-Auth"
|
||||
UserKey = "DnDex-User"
|
||||
)
|
||||
|
||||
var (
|
||||
NewAuthKey = "New-" + AuthKey
|
||||
GitCommit string
|
||||
)
|
||||
47
server/middleware.go
Normal file
47
server/middleware.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"io"
|
||||
"local/dndex/config"
|
||||
"local/gziphttp"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (rest *REST) delay(foo http.HandlerFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
select {
|
||||
case <-time.After(config.New().Delay):
|
||||
foo(w, r)
|
||||
case <-r.Context().Done():
|
||||
http.Error(w, r.Context().Err().Error(), 499)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (rest *REST) defend(foo http.HandlerFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if gziphttp.Can(r) {
|
||||
gz := gziphttp.New(w)
|
||||
defer gz.Close()
|
||||
w = gz
|
||||
}
|
||||
r.Body = struct {
|
||||
io.Reader
|
||||
io.Closer
|
||||
}{
|
||||
Reader: io.LimitReader(r.Body, config.New().MaxFileSize),
|
||||
Closer: r.Body,
|
||||
}
|
||||
foo(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
func (rest *REST) auth(foo http.HandlerFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if err := Auth(rest.g, w, r); err != nil {
|
||||
return
|
||||
}
|
||||
foo(w, r)
|
||||
}
|
||||
}
|
||||
16
server/response.go
Normal file
16
server/response.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func (rest *REST) respMap(w http.ResponseWriter, key string, value interface{}) {
|
||||
rest.resp(w, map[string]interface{}{key: value})
|
||||
}
|
||||
|
||||
func (rest *REST) resp(w http.ResponseWriter, body interface{}) {
|
||||
enc := json.NewEncoder(w)
|
||||
enc.SetIndent("", " ")
|
||||
enc.Encode(body)
|
||||
}
|
||||
77
server/rest.go
Normal file
77
server/rest.go
Normal file
@@ -0,0 +1,77 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"local/dndex/config"
|
||||
"local/dndex/storage"
|
||||
"local/router"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type REST struct {
|
||||
port int
|
||||
router *router.Router
|
||||
g storage.RateLimitedGraph
|
||||
}
|
||||
|
||||
type RESTScope struct {
|
||||
entity scope
|
||||
user scope
|
||||
}
|
||||
type scope struct {
|
||||
name string
|
||||
id string
|
||||
}
|
||||
|
||||
func Listen(g storage.RateLimitedGraph) error {
|
||||
rest, err := NewREST(g)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return http.ListenAndServe(fmt.Sprintf(":%d", rest.port), rest.router)
|
||||
}
|
||||
|
||||
func NewREST(g storage.RateLimitedGraph) (*REST, error) {
|
||||
rest := &REST{
|
||||
g: g,
|
||||
port: config.New().Port,
|
||||
router: router.New(),
|
||||
}
|
||||
|
||||
param := router.Wildcard
|
||||
params := router.Wildcard + router.Wildcard
|
||||
_, _ = param, params
|
||||
|
||||
paths := map[string]http.HandlerFunc{
|
||||
fmt.Sprintf("version"): rest.version,
|
||||
fmt.Sprintf("files/%s/%s", config.New().FilePrefix, params): rest.files,
|
||||
fmt.Sprintf("users"): rest.users,
|
||||
fmt.Sprintf("entities/%s", params): rest.entities,
|
||||
}
|
||||
|
||||
for path, foo := range paths {
|
||||
bar := foo
|
||||
bar = rest.auth(bar)
|
||||
bar = rest.defend(bar)
|
||||
bar = rest.delay(bar)
|
||||
if err := rest.router.Add(path, bar); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return rest, nil
|
||||
}
|
||||
|
||||
func (rest *REST) scope(r *http.Request) RESTScope {
|
||||
value, _ := r.Context().Value(AuthKey).(RESTScope)
|
||||
return value
|
||||
}
|
||||
|
||||
func (rest *REST) files(w http.ResponseWriter, _ *http.Request) {
|
||||
}
|
||||
|
||||
func (rest *REST) users(w http.ResponseWriter, _ *http.Request) {
|
||||
}
|
||||
|
||||
func (rest *REST) entities(w http.ResponseWriter, _ *http.Request) {
|
||||
}
|
||||
217
server/rest_test.go
Normal file
217
server/rest_test.go
Normal file
@@ -0,0 +1,217 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"local/dndex/config"
|
||||
"local/dndex/storage"
|
||||
"local/dndex/storage/entity"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
var (
|
||||
testNamespaceName = "col-name-" + uuid.New().String()[:10]
|
||||
testNamespaceID = "col-id-" + uuid.New().String()[:10]
|
||||
testEntityName = "ent-name-" + uuid.New().String()[:10]
|
||||
testEntityID = "ent-id-" + uuid.New().String()[:10]
|
||||
testFilename = "filename-" + uuid.New().String()[:10]
|
||||
testContent = "content-" + uuid.New().String()[:10]
|
||||
)
|
||||
|
||||
func TestRESTRouter(t *testing.T) {
|
||||
rest, clean := testREST(t)
|
||||
defer clean()
|
||||
cases := map[string]struct {
|
||||
method string
|
||||
is404 bool
|
||||
}{
|
||||
"/version": {
|
||||
method: http.MethodGet,
|
||||
},
|
||||
fmt.Sprintf(`%s`, config.New().FilePrefix): {
|
||||
method: http.MethodGet,
|
||||
is404: true,
|
||||
},
|
||||
fmt.Sprintf(`%s/`, config.New().FilePrefix): {
|
||||
method: http.MethodGet,
|
||||
is404: true,
|
||||
},
|
||||
fmt.Sprintf(`%s/%s`, config.New().FilePrefix, testFilename): {
|
||||
method: http.MethodGet,
|
||||
},
|
||||
fmt.Sprintf(`%s/fake.fake`, config.New().FilePrefix): {
|
||||
method: http.MethodGet,
|
||||
is404: true,
|
||||
},
|
||||
fmt.Sprintf("/users/%s", testNamespaceID): {
|
||||
method: http.MethodGet,
|
||||
is404: true,
|
||||
},
|
||||
fmt.Sprintf("/users/%s", testNamespaceID): {
|
||||
method: http.MethodPost,
|
||||
is404: true,
|
||||
},
|
||||
"/users/": {
|
||||
method: http.MethodGet,
|
||||
is404: true,
|
||||
},
|
||||
"/users": {
|
||||
method: http.MethodPost,
|
||||
},
|
||||
"/users?": {
|
||||
method: http.MethodGet,
|
||||
},
|
||||
"/entities": {
|
||||
method: http.MethodGet,
|
||||
},
|
||||
"/entities/": {
|
||||
method: http.MethodGet,
|
||||
},
|
||||
fmt.Sprintf("/entities/fake-%s", testEntityID): {
|
||||
method: http.MethodGet,
|
||||
is404: true,
|
||||
},
|
||||
fmt.Sprintf("/entities/%s", testEntityID): {
|
||||
method: http.MethodGet,
|
||||
},
|
||||
fmt.Sprintf("/entities/%s", testEntityID): {
|
||||
method: http.MethodPatch,
|
||||
},
|
||||
fmt.Sprintf("/entities/%s", testEntityID): {
|
||||
method: http.MethodPost,
|
||||
},
|
||||
fmt.Sprintf("/entities/%s", testEntityID): {
|
||||
method: http.MethodPut,
|
||||
},
|
||||
fmt.Sprintf("/entities/%s/connections/uuid", testEntityID): {
|
||||
method: http.MethodPatch,
|
||||
},
|
||||
fmt.Sprintf("/entities/%s/connections/uuid", testEntityID): {
|
||||
method: http.MethodPut,
|
||||
},
|
||||
fmt.Sprintf("/entities/%s/connections/uuid", testEntityID): {
|
||||
method: http.MethodPost,
|
||||
is404: true,
|
||||
},
|
||||
fmt.Sprintf("/entities/%s/connections/", testEntityID): {
|
||||
method: http.MethodPost,
|
||||
is404: true,
|
||||
},
|
||||
fmt.Sprintf("/entities/%s/connections/", testEntityID): {
|
||||
method: http.MethodPut,
|
||||
is404: true,
|
||||
},
|
||||
fmt.Sprintf("/entities/%s/connections/", testEntityID): {
|
||||
method: http.MethodPatch,
|
||||
is404: true,
|
||||
},
|
||||
fmt.Sprintf("/entities/%s/connections", testEntityID): {
|
||||
method: http.MethodPost,
|
||||
is404: true,
|
||||
},
|
||||
fmt.Sprintf("/entities/%s/connections", testEntityID): {
|
||||
method: http.MethodPut,
|
||||
},
|
||||
fmt.Sprintf("/entities/%s/connections", testEntityID): {
|
||||
method: http.MethodPatch,
|
||||
},
|
||||
fmt.Sprintf("/entities/%s/connections/uuid", testEntityID): {
|
||||
method: http.MethodDelete,
|
||||
},
|
||||
fmt.Sprintf("/entities/%s/connections/", testEntityID): {
|
||||
method: http.MethodDelete,
|
||||
is404: true,
|
||||
},
|
||||
fmt.Sprintf("/entities/%s/connections", testEntityID): {
|
||||
method: http.MethodDelete,
|
||||
},
|
||||
fmt.Sprintf("/entities/%s/", testEntityID): {
|
||||
method: http.MethodDelete,
|
||||
is404: true,
|
||||
},
|
||||
fmt.Sprintf("/entities/%s", testEntityID): {
|
||||
method: http.MethodDelete,
|
||||
},
|
||||
}
|
||||
|
||||
for name, d := range cases {
|
||||
c := d
|
||||
path := name
|
||||
t.Run(name, func(t *testing.T) {
|
||||
r := httptest.NewRequest(c.method, path, strings.NewReader(``))
|
||||
w := httptest.NewRecorder()
|
||||
rest.router.ServeHTTP(w, r)
|
||||
if (w.Code == http.StatusNotFound) != c.is404 {
|
||||
t.Fatalf("want 404==%v, got %d: %+v: %s", c.is404, w.Code, c, w.Body.Bytes())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func testREST(t *testing.T) (*REST, func()) {
|
||||
d, err := ioutil.TempDir(os.TempDir(), "tempdir.*")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := ioutil.WriteFile(path.Join(d, testFilename), []byte(testContent), os.ModePerm); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
os.Setenv("FILEROOT", d)
|
||||
os.Setenv("DRIVER_TYPE", "map")
|
||||
os.Setenv("AUTH", "false")
|
||||
os.Args = os.Args[:1]
|
||||
os.Args = os.Args[:1]
|
||||
rest, err := NewREST(storage.NewRateLimitedGraph(""))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ctx, can := context.WithTimeout(context.Background(), time.Second*5)
|
||||
defer can()
|
||||
one := randomOne()
|
||||
one.Name = testEntityName
|
||||
one.ID = testEntityID
|
||||
if err := rest.g.Insert(ctx, testNamespaceID, one); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := rest.g.Insert(ctx, testNamespaceID+"."+AuthKey, entity.One{
|
||||
Name: testNamespaceName,
|
||||
ID: testNamespaceID,
|
||||
Title: "title",
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := rest.g.Insert(ctx, testNamespaceID+"."+AuthKey, entity.One{
|
||||
Name: UserKey,
|
||||
ID: UserKey,
|
||||
Title: "",
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return rest, func() {
|
||||
os.RemoveAll(d)
|
||||
}
|
||||
}
|
||||
|
||||
func randomOne() entity.One {
|
||||
return entity.One{
|
||||
ID: "iddd-" + uuid.New().String()[:5],
|
||||
Name: "name-" + uuid.New().String()[:5],
|
||||
Type: "type-" + uuid.New().String()[:5],
|
||||
Title: "titl-" + uuid.New().String()[:5],
|
||||
Text: "text-" + uuid.New().String()[:5],
|
||||
Modified: time.Now().UnixNano(),
|
||||
Connections: map[string]entity.One{},
|
||||
Attachments: map[string]string{
|
||||
uuid.New().String()[:5]: uuid.New().String()[:5],
|
||||
},
|
||||
}
|
||||
}
|
||||
12
server/version.go
Normal file
12
server/version.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func (rest *REST) version(w http.ResponseWriter, _ *http.Request) {
|
||||
enc := json.NewEncoder(w)
|
||||
enc.SetIndent("", " ")
|
||||
enc.Encode(map[string]string{"version": GitCommit})
|
||||
}
|
||||
Reference in New Issue
Block a user