master
Bel LaPointe 2019-02-18 09:31:35 -07:00
commit cbf886fb7e
14 changed files with 1016 additions and 0 deletions

11
.gitignore vendored Normal file
View File

@ -0,0 +1,11 @@
lz4
rclone
rcloner
Go
cloudly
dockfile
compile.sh
*.swp
*.swo
*pycache*
enc_conf

18
main.go Normal file
View File

@ -0,0 +1,18 @@
package main
import (
"flag"
"local/s2sa/s2sa/server"
)
func main() {
flag.Parse()
server := server.New("")
if err := server.Routes(); err != nil {
panic(err)
}
if err := server.Run(); err != nil {
panic(err)
}
}

30
server/new.go Normal file
View File

@ -0,0 +1,30 @@
package server
import (
"local/s2sa/s2sa/server/router"
"local/s2sa/s2sa/services"
"local/s2sa/s2sa/storage"
)
func New(path string) *Server {
var db storage.DB
db = storage.NewMap()
if len(path) > 0 {
var err error
db, err = storage.NewBolt(path)
if err != nil {
return nil
}
}
authdb := storage.NewMap()
s := &Server{
db: services.New(db),
authdb: services.New(authdb),
router: router.New(),
addr: ":18341",
}
if err := s.authdb.Register(serverNS); err != nil {
panic(err)
}
return s
}

11
server/new_test.go Normal file
View File

@ -0,0 +1,11 @@
package server
import (
"os"
"testing"
)
func TestServerNew(t *testing.T) {
New("")
New(os.DevNull)
}

197
server/routes.go Normal file
View File

@ -0,0 +1,197 @@
package server
import (
"encoding/json"
"local/s2sa/s2sa/server/router"
"local/s2sa/s2sa/token"
"net/http"
"path"
"strings"
)
const clientsNS = "clients"
const accessorsNS = "accessors"
const wildcard = "{}"
const serverNS = "server"
func (s *Server) Routes() error {
appendWildcards := func(s string, cnt int) string {
s = strings.Trim(s, "/")
return path.Join(s, strings.Repeat("/"+wildcard, cnt))
}
paths := []struct {
base string
wildcards int
method http.HandlerFunc
}{
{
base: "admin/register",
wildcards: 1,
method: s.adminRegister,
},
{
base: "register",
wildcards: 1,
method: s.authenticate(s.registerClient),
},
{
base: "generate",
wildcards: 2,
method: s.authenticate(s.generateToken),
},
{
base: "retrieve",
wildcards: 3,
method: s.authenticate(s.retrieveToken),
},
{
base: "revoke",
wildcards: 2,
method: s.authenticate(s.revokeToken),
},
{
base: "lookup",
wildcards: 2,
method: s.authenticate(s.lookupToken),
},
{
base: "policies",
wildcards: 0,
method: s.authenticate(s.getPolicies),
},
}
for _, path := range paths {
if err := s.Add(appendWildcards(path.base, path.wildcards), path.method); err != nil {
return err
}
}
return nil
}
func (s *Server) authenticate(foo http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
recipient, accessorToken, ok := r.BasicAuth()
if !ok {
w.WriteHeader(http.StatusUnauthorized)
return
}
accessor := strings.Split(accessorToken, ":")[0]
tokenValue := strings.Split(accessorToken, ":")[1]
token, err := s.authdb.Get(recipient, accessor)
if err != nil {
w.WriteHeader(http.StatusUnauthorized)
return
}
if token.Token != tokenValue {
w.WriteHeader(http.StatusUnauthorized)
return
}
if token.To != recipient {
w.WriteHeader(http.StatusUnauthorized)
return
}
foo(w, r)
}
}
func (s *Server) adminRegister(w http.ResponseWriter, r *http.Request) {
var name string
if err := router.Params(r, &name); err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
token, err := s.authdb.New(serverNS, name)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
respondWithToken(token, w, r)
}
func (s *Server) registerClient(w http.ResponseWriter, r *http.Request) {
var name string
if err := router.Params(r, &name); err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
if err := s.db.Register(name); err != nil {
w.WriteHeader(http.StatusInternalServerError)
}
}
func (s *Server) generateToken(w http.ResponseWriter, r *http.Request) {
var name, to string
if err := router.Params(r, &name, &to); err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
token, err := s.db.New(name, to)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
respondWithToken(token, w, r)
}
func (s *Server) retrieveToken(w http.ResponseWriter, r *http.Request) {
var creator, recipient, accessor string
if err := router.Params(r, &creator, &recipient, &accessor); err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
token, err := s.db.Get(recipient, accessor)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
respondWithToken(token, w, r)
}
func respondWithToken(token token.Basic, w http.ResponseWriter, r *http.Request) {
if err := json.NewEncoder(w).Encode(token); err != nil {
w.WriteHeader(http.StatusInternalServerError)
}
}
func (s *Server) revokeToken(w http.ResponseWriter, r *http.Request) {
var name, accessor string
if err := router.Params(r, &name, &accessor); err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
if err := s.db.Revoke(name, accessor); err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
}
func (s *Server) lookupToken(w http.ResponseWriter, r *http.Request) {
var creator, recipient string
if err := router.Params(r, &creator, &recipient); err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
token, err := s.db.Lookup(creator, recipient)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
respondWithToken(token, w, r)
}
func (s *Server) getPolicies(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusInternalServerError)
}

437
server/routes_test.go Normal file
View File

@ -0,0 +1,437 @@
package server
import (
"bytes"
"encoding/json"
"errors"
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
)
type badRouter struct {
accept []string
}
func (br *badRouter) Add(p string, h http.HandlerFunc) error {
for i := range br.accept {
if br.accept[i] == p {
return nil
}
}
return errors.New("rejected path")
}
func (br *badRouter) ServeHTTP(http.ResponseWriter, *http.Request) {}
func TestServerRoutesBadRouter(t *testing.T) {
server, _, _ := mockServer()
br := badRouter{
accept: make([]string, 0),
}
server.router = &br
toAdd := []string{
"/nil",
"/admin/register/{}",
"/register/{}",
"/generate/{}/{}",
"/retrieve/{}/{}",
"/revoke/{}/{}",
"/lookup/{}/{}",
"/policies",
}
for _, path := range toAdd {
br.accept = append(br.accept, path)
if err := server.Routes(); err == nil {
t.Errorf("can add non-allowed routes")
}
}
}
func TestServerRoutes(t *testing.T) {
server, _, _ := mockServer()
if err := server.db.Register("a"); err != nil {
t.Fatalf("cannot register: %v", err)
}
token, err := server.db.New("a", "b")
if err != nil {
t.Fatalf("cannot new: %v", err)
}
paths := []string{
"retrieve/a/b/" + token.Accessor,
"revoke/a/" + token.Accessor,
"lookup/a/b",
"policies",
"admin/register/a",
"register/a",
"generate/a/b",
}
for _, p := range paths {
w := httptest.NewRecorder()
r, _ := http.NewRequest("GET", p, nil)
server.ServeHTTP(w, r)
if w.Code == 404 {
t.Errorf("not found for %v", p)
}
}
}
func TestServerAdminRegister(t *testing.T) {
cases := []struct {
name string
status int
}{
{
name: "",
status: 404,
},
{
name: "name",
status: 200,
},
}
path := "admin/register"
server, _, _ := mockServer()
for i, c := range cases {
w := httptest.NewRecorder()
r, _ := http.NewRequest("GET", path, nil)
r = addParam(r, "name", c.name)
server.ServeHTTP(w, r)
if w.Code != c.status {
t.Errorf("[%d] wrong code for %s: got %d, expected %d", i, path, w.Code, c.status)
}
body, _ := ioutil.ReadAll(w.Body)
if len(body) < 1 {
t.Errorf("[%d] empty body in admin/register response: %q", i, body)
}
t.Logf("[%d] admin/register body: %q", i, body)
}
}
func TestServerRegister(t *testing.T) {
cases := []struct {
name string
status int
}{
{
name: "",
status: 404,
},
{
name: "name",
status: 200,
},
}
path := "register"
server, _, _ := mockServer()
for i, c := range cases {
w := httptest.NewRecorder()
r, _ := http.NewRequest("GET", path, nil)
r = addParam(r, "name", c.name)
auth, err := server.authdb.New(serverNS, c.name)
if err != nil {
t.Fatalf("cannot authdb new: %v", err)
}
r.SetBasicAuth(c.name, auth.Accessor+":"+auth.Token)
server.ServeHTTP(w, r)
if w.Code != c.status {
t.Errorf("%d: wrong code for %s: got %d, expected %d", i, path, w.Code, c.status)
}
}
}
func TestServerGenerate(t *testing.T) {
cases := []struct {
name string
to string
status int
}{
{
name: "",
to: "",
status: 404,
},
{
name: "name",
to: "",
status: 404,
},
{
name: "",
to: "to",
status: 404,
},
{
name: "name",
to: "to",
status: 200,
},
}
path := "generate"
server, _, _ := mockServer()
for i, c := range cases {
w := httptest.NewRecorder()
r, _ := http.NewRequest("GET", path, nil)
r = addParam(r, "name", c.name)
r = addParam(r, "to", c.to)
auth, err := server.authdb.New(serverNS, c.name)
if err != nil {
t.Fatalf("cannot authdb new: %v", err)
}
r.SetBasicAuth(c.name, auth.Accessor+":"+auth.Token)
server.ServeHTTP(w, r)
if w.Code != c.status {
t.Errorf("%d: wrong code for %s: got %d, expected %d", i, path, w.Code, c.status)
}
var result struct {
Token string `json:"token"`
Acc string `json:"accessor"`
TTL int `json:"TTL"`
To string `json:"to"`
}
if err := json.NewDecoder(w.Body).Decode(&result); c.status == 200 && err != nil {
t.Errorf("invalid body: %v", err)
} else if c.status == 200 {
if result.To != c.to {
t.Errorf("wrong `to` in response: got %v, want %v", result.To, c.to)
}
if len(result.Token) == 0 {
t.Errorf("empty `token` in response")
}
if len(result.Acc) == 0 {
t.Errorf("empty `accessor` in response")
}
if result.TTL < 100 {
t.Errorf("short TTL in response")
}
}
}
}
func TestServerRetrieve(t *testing.T) {
server, defaultName, defaultAccessor := mockServer()
cases := []struct {
name string
acc string
status int
}{
{
name: "",
acc: "",
status: 404,
},
{
name: defaultName,
acc: "",
status: 404,
},
{
name: "",
acc: defaultAccessor,
status: 404,
},
{
name: defaultName,
acc: defaultAccessor,
status: 200,
},
{
name: "fake",
acc: "fake",
status: 400,
},
}
path := "retrieve"
for i, c := range cases {
w := httptest.NewRecorder()
r, _ := http.NewRequest("GET", path, nil)
r = addParam(r, "name", c.name)
r = addParam(r, "to", "to")
r = addParam(r, "accessor", c.acc)
auth, err := server.authdb.New(serverNS, c.name)
if err != nil {
t.Fatalf("cannot authdb new: %v", err)
}
r.SetBasicAuth(c.name, auth.Accessor+":"+auth.Token)
server.ServeHTTP(w, r)
if w.Code != c.status {
//t.Errorf("%d: wrong code for %s with %v: got %d, expected %d", i, path, c, w.Code, c.status)
t.Fatalf("%d: wrong code for %s with %v: got %d, expected %d", i, path, c, w.Code, c.status)
}
}
}
func TestServerRevoke(t *testing.T) {
cases := []struct {
name string
status int
}{
{
name: "",
status: 404,
},
{
name: "name",
status: 200,
},
}
path := "register"
server, _, _ := mockServer()
for i, c := range cases {
w := httptest.NewRecorder()
r, _ := http.NewRequest("GET", path, nil)
r = addParam(r, "name", c.name)
auth, err := server.authdb.New(serverNS, c.name)
if err != nil {
t.Fatalf("cannot authdb new: %v", err)
}
r.SetBasicAuth(c.name, auth.Accessor+":"+auth.Token)
server.ServeHTTP(w, r)
if w.Code != c.status {
t.Errorf("%d: wrong code for %s: got %d, expected %d", i, path, w.Code, c.status)
}
}
}
func TestServerLookup(t *testing.T) {
from := "from"
to := "to"
cases := []struct {
from string
to string
status int
}{
{
from: "",
to: "",
status: 404,
},
{
from: "",
to: to,
status: 404,
},
{
from: from,
to: "",
status: 404,
},
{
from: from,
to: to,
status: 200,
},
}
path := "lookup"
server, _, _ := mockServer()
server.db.Register(from)
generated, _ := server.db.New(from, to)
for i, c := range cases {
w := httptest.NewRecorder()
r, _ := http.NewRequest("GET", path, nil)
r = addParam(r, "from", c.from)
r = addParam(r, "to", c.to)
auth, err := server.authdb.New(serverNS, c.from)
if err != nil {
t.Fatalf("cannot authdb new: %v", err)
}
r.SetBasicAuth(c.from, auth.Accessor+":"+auth.Token)
server.ServeHTTP(w, r)
if w.Code != c.status {
t.Errorf("%d: wrong code for %s: got %d, expected %d", i, path, w.Code, c.status)
}
if w.Code != http.StatusOK {
continue
}
b, err := ioutil.ReadAll(w.Body)
if err != nil {
t.Fatalf("%d: cannot read body: %v", i, err)
}
if !bytes.Contains(b, []byte(generated.Accessor)) {
t.Errorf("%d: response didn't contain accessor: got %s, want %s", i, b, generated.Accessor)
}
}
}
func echoHTTP(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(r.URL.Path))
}
func addParam(r *http.Request, key, value string) *http.Request {
r.URL.Path += "/" + value
r.Header.Set(key, value)
return r
}
func TestServerAuthenticate(t *testing.T) {
server, _, _ := mockServer()
name := "name"
server.authdb.Register(serverNS)
token, err := server.authdb.New(serverNS, name)
if err != nil {
t.Fatalf("cannot authdb new: %v", err)
}
nilHandle := func(http.ResponseWriter, *http.Request) {}
authFunc := server.authenticate(nilHandle)
cases := []struct {
name string
token string
accessor string
code int
}{
{
name: "bad",
token: token.Token,
accessor: token.Accessor,
code: http.StatusUnauthorized,
},
{
name: name,
token: token.Token,
accessor: "bad",
code: http.StatusUnauthorized,
},
{
name: name,
token: "bad",
accessor: token.Accessor,
code: http.StatusUnauthorized,
},
{
name: name,
token: token.Token,
accessor: token.Accessor,
code: http.StatusOK,
},
}
for i, c := range cases {
w := httptest.NewRecorder()
r, _ := http.NewRequest("GET", "/any", nil)
r.SetBasicAuth(c.name, c.accessor+":"+c.token)
authFunc(w, r)
if w.Code != c.code {
t.Errorf("[case %d] failed auth: got %v, wanted %v", i, w.Code, c.code)
}
}
}

51
server/server.go Normal file
View File

@ -0,0 +1,51 @@
package server
import (
"fmt"
"local/s2sa/s2sa/logg"
"local/s2sa/s2sa/server/router"
"local/s2sa/s2sa/token"
"net/http"
"strings"
)
type Router interface {
Add(string, http.HandlerFunc) error
ServeHTTP(http.ResponseWriter, *http.Request)
}
type TokenDatabase interface {
Register(string) error
New(string, string) (token.Basic, error)
Get(string, string) (token.Basic, error)
Revoke(string, string) error
Lookup(string, string) (token.Basic, error)
}
type Messenger interface {
Produce(string, interface{}) error
}
type Server struct {
router Router
db TokenDatabase
authdb TokenDatabase
messenger Messenger
addr string
}
func (s *Server) Add(path string, handler http.HandlerFunc) error {
logg.Logf("Adding path %v...\n", path)
path = strings.Replace(path, wildcard, router.Wildcard, -1)
return s.router.Add(path, handler)
}
func (s *Server) Run() error {
logg.Logf("Listening on %v...\n", s.addr)
return http.ListenAndServe(s.addr, s)
}
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Printf("REQ: %s\n", r.URL.Path)
s.router.ServeHTTP(w, r)
}

87
server/server_test.go Normal file
View File

@ -0,0 +1,87 @@
package server
import (
"fmt"
"io/ioutil"
"local/s2sa/s2sa/logg"
"local/s2sa/s2sa/server/router"
"local/s2sa/s2sa/services"
"local/s2sa/s2sa/storage"
"net/http"
"net/http/httptest"
"os"
"strings"
"testing"
)
func TestServerStart(t *testing.T) {
server, _, _ := mockServer()
checked := false
if err := server.Add("/hello/world", func(_ http.ResponseWriter, _ *http.Request) {
checked = true
}); err != nil {
t.Fatalf("cannot add route: %v", err)
}
go func() {
if err := server.Run(); err != nil {
t.Fatalf("err running server: %v", err)
}
}()
if resp, err := http.Get("http://localhost" + server.addr + "/hello/world"); err != nil {
t.Errorf("failed to get: %v", err)
} else if resp.StatusCode != 200 {
t.Errorf("wrong status: %v", resp.StatusCode)
} else if !checked {
t.Errorf("didnt hit handler")
}
}
func mockServer() (*Server, string, string) {
f, _ := os.Open(os.DevNull)
logg.ConfigFile(f)
portServer := httptest.NewServer(nil)
port := strings.Split(portServer.URL, ":")[2]
portServer.Close()
s := &Server{
router: router.New(),
db: services.New(storage.NewMap()),
authdb: services.New(storage.NewMap()),
addr: ":" + port,
}
s.authdb.Register(serverNS)
if err := s.Routes(); err != nil {
panic(fmt.Sprintf("cannot initiate server routes; %v", err))
}
defaultName := "name"
if err := s.db.Register(defaultName); err != nil {
panic(fmt.Sprintf("cannot register: %v", err))
}
token, err := s.db.New("name", "to")
if err != nil {
panic(fmt.Sprintf("cannot generate: %v", err))
}
defaultAccessor := token.Accessor
return s, defaultName, defaultAccessor
}
func TestServerAdd(t *testing.T) {
server, _, _ := mockServer()
path := "/hello/world"
if err := server.Add(path, echoHTTP); err != nil {
t.Fatalf("cannot add path: %v", err)
}
w := httptest.NewRecorder()
r, _ := http.NewRequest("GET", path, nil)
server.ServeHTTP(w, r)
b, err := ioutil.ReadAll(w.Body)
if err != nil {
t.Fatalf("cannot read body: %v", err)
}
if string(b) != path {
t.Errorf("cannot hit endpoint: %s", b)
}
}

9
storage/db.go Normal file
View File

@ -0,0 +1,9 @@
package storage
import "local/rproxy3/storage/packable"
type DB interface {
Get(string, string, packable.Packable) error
Set(string, string, packable.Packable) error
Close() error
}

46
storage/db_test.go Normal file
View File

@ -0,0 +1,46 @@
package storage
import (
"local/rproxy3/storage/packable"
"os"
"testing"
)
func TestDB(t *testing.T) {
dbPath := "./fake_db_path"
defer os.Remove(dbPath)
cases := []func() DB{
func() DB {
return DB(NewMap())
},
}
raw := "hello world"
for _, c := range cases {
os.Remove(dbPath)
s := packable.NewString(raw)
db := c()
if err := db.Set("ns", "key", s); err != nil {
t.Errorf("cannot save: %v", err)
}
r := packable.NewString("")
if err := db.Get("ns", "key", r); err != nil {
t.Errorf("cannot load: %v", err)
}
if s.String() != r.String() {
t.Errorf("set/get values not equal: %v vs %v", s, r)
}
if s.String() != raw {
t.Errorf("set/get values not equal to raw: %v vs %v", s, raw)
}
if err := db.Close(); err != nil {
t.Errorf("cannot close bolt: %v", err)
}
}
}

64
storage/map.go Normal file
View File

@ -0,0 +1,64 @@
package storage
import (
"errors"
"fmt"
"local/rproxy3/storage/packable"
)
type Map map[string]map[string][]byte
func NewMap() Map {
m := make(map[string]map[string][]byte)
n := Map(m)
return n
}
func (m Map) String() string {
s := ""
for k, v := range m {
if k == "clients" {
if s != "" {
s += ",\n"
}
s += fmt.Sprintf("[%s]:[%v]", k, v)
} else {
for k1, _ := range v {
if s != "" {
s += ",\n"
}
str := packable.NewString("")
m.Get(k, k1, str)
s += fmt.Sprintf("[%s:%s]:[%v]", k, k1, str)
}
}
}
return s
}
func (m Map) Close() error {
m = nil
return nil
}
func (m Map) Get(ns, key string, value packable.Packable) error {
if _, ok := m[ns]; !ok {
m[ns] = make(map[string][]byte)
}
if _, ok := m[ns][key]; !ok {
return errors.New("not found")
}
return value.Decode(m[ns][key])
}
func (m Map) Set(ns, key string, value packable.Packable) error {
if _, ok := m[ns]; !ok {
m[ns] = make(map[string][]byte)
}
b, err := value.Encode()
if err != nil {
return err
}
m[ns][key] = b
return nil
}

View File

@ -0,0 +1,26 @@
package packable
type Packable interface {
Encode() ([]byte, error)
Decode([]byte) error
}
type String string
func (s *String) String() string {
return string(*s)
}
func (s *String) Encode() ([]byte, error) {
return []byte(*s), nil
}
func (s *String) Decode(b []byte) error {
*s = String(string(b))
return nil
}
func NewString(s string) *String {
w := String(s)
return &w
}

View File

@ -0,0 +1,23 @@
package packable
import "testing"
func TestPackableString(t *testing.T) {
raw := "hello"
s := NewString(raw)
if s.String() != raw {
t.Errorf("cannot convert string to String: %v vs %v", s, raw)
}
packed, err := s.Encode()
if err != nil {
t.Errorf("cannot encode String: %v", err)
}
x := NewString("")
if err := x.Decode(packed); err != nil {
t.Errorf("cannot decode string: %v", err)
} else if x.String() != raw {
t.Errorf("wrong decoded string: %v vs %v", x, raw)
}
}

6
vendor/vendor.json vendored Normal file
View File

@ -0,0 +1,6 @@
{
"comment": "",
"ignore": "test",
"package": [],
"rootPath": "local/rproxy3"
}