Commit 0f650ad0 authored by Mads's avatar Mads

Tonights stuff

parent ea6f3ab9
Pipeline #20048160 failed with stages
in 44 seconds
......@@ -14,13 +14,13 @@ type CryptService struct {
Config vee.Config
}
func (cs *CryptService) Nonce(n []byte) (int, error) {
return io.ReadFull(rand.Reader, n)
func (cs *CryptService) Nonce(n [32]byte) (int, error) {
return io.ReadFull(rand.Reader, n[:])
}
func (cs *CryptService) Key(nonce []byte) ([32]byte, error) {
func (cs *CryptService) Key(nonce [32]byte) ([32]byte, error) {
var key [32]byte
kdf := hkdf.New(sha256.New, cs.Config.MasterKey, nonce, nil)
kdf := hkdf.New(sha256.New, cs.Config.MasterKey, nonce[:], nil)
_, err := io.ReadFull(kdf, key[:])
return key, err
}
......
......@@ -11,7 +11,7 @@ import (
func TestCrypt(t *testing.T) {
var config vee.Config
var nonce []byte
var nonce [32]byte
var key [32]byte
cs := crypt.CryptService{
Config: config,
......
......@@ -31,6 +31,7 @@ type ReceiveFileHandler struct {
FileService vee.FileService
Config vee.Config
Log vee.LogService
Crypt vee.CryptService
}
// extractSize takes a string, as returned by Header.Get("content-disposition")
......@@ -74,6 +75,54 @@ func validateFilePart(part *multipart.Part) error {
return nil
}
// createFile is a convenience function to create a new file, with
// a new random nonce and a derived key already generated.
//FIXME: This could potentially be made the default way that
// fileservice.NewFile() works, possible configureable with arguments.
func createFile(h *ReceiveFileHandler) (*vee.File, error) {
recvFile := h.FileService.NewFile()
_, err := h.Crypt.Nonce(recvFile.Nonce)
if err != nil {
return nil, err
}
recvFile.Key, err = h.Crypt.Key(recvFile.Nonce)
if err != nil {
return nil, err
}
return recvFile, nil
}
// handleFilePart takes a multipart.Part and validates, encrypts
// and saves the file.
//
// Returned error should be checked before accessing the returned
// file.
func handleFilePart(h *ReceiveFileHandler, part *multipart.Part) (*vee.File, error) {
err := validateFilePart(part)
if err != nil {
return nil, err
}
recvFile, err := createFile(h)
pReader, pWriter := io.Pipe()
go func() {
defer pWriter.Close()
_, err = h.Crypt.Encrypt(pWriter, part, recvFile.Key)
if err != nil {
//internalServerError(w, r)
// DO SOMETHING HERE
return
}
}()
recvFile.Content = pReader
// TODO: Shouldn't SaveFile take care of encryption, so that it isn't
// isn't possible to accidently save unencrypted data.
err = h.FileService.SaveFile(recvFile)
return recvFile, err
}
func (h *ReceiveFileHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
reader, err := r.MultipartReader()
if err != nil {
......@@ -81,6 +130,8 @@ func (h *ReceiveFileHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
internalServerError(w, r)
return
}
var files []*vee.File
var partErrors []error
for {
part, err := reader.NextPart()
if err == io.EOF {
......@@ -91,11 +142,20 @@ func (h *ReceiveFileHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
internalServerError(w, r)
return
}
err = validateFilePart(part)
file, err := handleFilePart(h, part)
if err != nil {
h.Log.Debug(err)
badRequestError(w, r, err.Error())
return
partErrors = append(partErrors, err)
}
files = append(files, file)
}
// w.Header().Set("location", fmt.Sprintf("%s", recvFile.Name.String()))
fmt.Println(files)
if len(partErrors) > 0 {
fmt.Println("kage")
w.WriteHeader(http.StatusBadRequest)
return
}
fmt.Println("fisk")
w.WriteHeader(http.StatusCreated)
}
......@@ -3,6 +3,7 @@ package http_test
import (
"bytes"
"crypto/rand"
"errors"
"io"
"mime/multipart"
"net/http"
......@@ -10,10 +11,57 @@ import (
"net/textproto"
"testing"
"github.com/google/uuid"
vee "gitlab.com/MadsRC/vee/app"
veeHTTP "gitlab.com/MadsRC/vee/app/http"
"gitlab.com/MadsRC/vee/app/mock"
)
var fs mock.FileService
var logService mock.LogService
var crypt mock.CryptService
var h veeHTTP.ReceiveFileHandler
func resetMocks() {
//Mock FileService.NewFile() call.
fs.NewFileFn = func() *vee.File {
return &vee.File{Name: uuid.New()}
}
fs.NewFileInvoked = false
fs.SaveFileFn = func(f *vee.File) error {
return nil
}
fs.SaveFileInvoked = false
//Mock LogService.Print() call.
logService.PrintFn = func(v ...interface{}) {
return
}
logService.PrintInvoked = false
logService.PrintfFn = func(format string, v ...interface{}) {
return
}
logService.PrintfInvoked = false
//Mock LogService.Debug() call.
logService.DebugFn = func(v ...interface{}) {
return
}
logService.DebugInvoked = false
crypt.NonceFn = func(n [32]byte) (int, error) {
return io.ReadFull(rand.Reader, n[:])
}
crypt.NonceInvoked = false
crypt.KeyFn = func(n [32]byte) ([32]byte, error) {
var key [32]byte
_, err := io.ReadFull(rand.Reader, key[:])
return key, err
}
crypt.KeyInvoked = false
}
func createMultipartBody(size int64, cd string) (*bytes.Buffer, string, error) {
data := make([]byte, size)
rand.Read(data)
......@@ -45,21 +93,11 @@ func createMultipartBody(size int64, cd string) (*bytes.Buffer, string, error) {
func TestHandler(t *testing.T) {
// Inject our mock
var fs mock.FileService
var logService mock.LogService
var h veeHTTP.ReceiveFileHandler
h.FileService = &fs
h.Log = &logService
h.Crypt = &crypt
//Mock LogService.Print() call.
logService.PrintFn = func(v ...interface{}) {
return
}
//Mock LogService.Debug() call.
logService.DebugFn = func(v ...interface{}) {
return
}
resetMocks()
t.Run("Content-TypeNotMultipartFormData", func(t *testing.T) {
//Invoke the handler
w := httptest.NewRecorder()
......@@ -70,24 +108,26 @@ func TestHandler(t *testing.T) {
t.Fatalf("Expected response code to be %d, was %d\n", http.StatusInternalServerError, w.Code)
}
})
resetMocks()
t.Run("Content-Disposition and size present", func(t *testing.T) {
//Invoke the handler
w := httptest.NewRecorder()
tables := []struct {
x string
y int
y int64
z int
}{
{"form-data; filename=\"randomdata.bin\"; name=\"file\"; size=\"10\"", http.StatusOK},
{"form-data; filename=\"randomdata.bin\"; name=\"file\"; size=10", http.StatusOK},
{"form-data; filename=\"randomdata.bin\"; name=\"file\"; size=kat", http.StatusBadRequest},
{"form-data; filename=\"randomdata.bin\"; name=\"file\"; size=\"kat\"", http.StatusBadRequest},
{"form-data; filename=\"randomdata.bin\"; name=\"file\"", http.StatusBadRequest},
{"", http.StatusBadRequest},
{"form-data; filename=\"randomdata.bin\"; name=\"file\"; size=\"10\"", 10, http.StatusCreated},
{"form-data; filename=\"randomdata.bin\"; name=\"file\"; size=10", 10, http.StatusCreated},
{"form-data; filename=\"randomdata.bin\"; name=\"file\"; size=kat", 10, http.StatusBadRequest},
{"form-data; filename=\"randomdata.bin\"; name=\"file\"; size=\"kat\"", 10, http.StatusBadRequest},
{"form-data; filename=\"randomdata.bin\"; name=\"file\"", 10, http.StatusBadRequest},
{"", 10, http.StatusBadRequest},
}
for _, table := range tables {
body, ct, err := createMultipartBody(10, table.x)
for i, table := range tables {
//Invoke the handler
w := httptest.NewRecorder()
body, ct, err := createMultipartBody(table.y, table.x)
if err != nil {
t.Fatalf("Failed creating multipart: %v\n", err)
}
......@@ -96,14 +136,65 @@ func TestHandler(t *testing.T) {
t.Fatalf("Failed creating multipart: %v\n", err)
}
r.Header.Set("Content-Type", ct)
if err != nil {
t.Fatalf("Failed creating multipart: %v\n", err)
h.ServeHTTP(w, r)
if w.Code != table.z {
t.Fatalf("[%d] Expected response code to be %d, was %d\n", i, table.z, w.Code)
}
}
resetMocks()
crypt.NonceFn = func(n [32]byte) (int, error) {
return 0, errors.New("This nonce is an error")
}
//Invoke the handler
w := httptest.NewRecorder()
tables = []struct {
x string
y int64
z int
}{
{"form-data; filename=\"randomdata.bin\"; name=\"file\"; size=\"10\"", 10, http.StatusBadRequest},
}
for _, table := range tables {
body, ct, _ := createMultipartBody(table.y, table.x)
r, _ := http.NewRequest("POST", "/file", body)
r.Header.Set("Content-Type", ct)
h.ServeHTTP(w, r)
if w.Code != table.y {
t.Fatalf("Expected response code to be %d, was %d\n", table.y, w.Code)
if w.Code != table.z {
t.Fatalf("Expected response code to be %d, was %d\n", table.z, w.Code)
}
}
crypt.KeyFn = func(n [32]byte) ([32]byte, error) {
return [32]byte{}, errors.New("This key is an error")
}
//Invoke the handler
w = httptest.NewRecorder()
tables = []struct {
x string
y int64
z int
}{
{"form-data; filename=\"randomdata.bin\"; name=\"file\"; size=\"10\"", 10, http.StatusBadRequest},
}
for _, table := range tables {
body, ct, _ := createMultipartBody(table.y, table.x)
r, _ := http.NewRequest("POST", "/file", body)
r.Header.Set("Content-Type", ct)
h.ServeHTTP(w, r)
if w.Code != table.z {
t.Fatalf("Expected response code to be %d, was %d\n", table.z, w.Code)
}
}
})
}
......@@ -19,7 +19,7 @@ type FileService struct {
// NewFile invokes the mock implementation and marks the function as invoked
func (s *FileService) NewFile() *vee.File {
s.NewFileInvoked = true
return s.NewFile()
return s.NewFileFn()
}
// GetFile invokes the mock implementation and marks the function as invoked
......@@ -135,3 +135,40 @@ func (s *LogService) Debugf(format string, v ...interface{}) {
s.DebugfInvoked = true
s.DebugfFn(format, v)
}
//CryptService represents a mock implementation of vee.CryptService
type CryptService struct {
NonceFn func(n [32]byte) (int, error)
NonceInvoked bool
KeyFn func(n [32]byte) ([32]byte, error)
KeyInvoked bool
EncryptFn func(dst io.Writer, src io.Reader, key [32]byte) (n int64, err error)
EncryptInvoked bool
DecryptFn func(dst io.Writer, src io.Reader, key [32]byte) (n int64, err error)
DecryptInvoked bool
Config *vee.Config
}
// Nonce invokes the mock implementation and marks the function as invoked
func (s *CryptService) Nonce(n [32]byte) (int, error) {
s.NonceInvoked = true
return s.NonceFn(n)
}
// Key invokes the mock implementation and marks the function as invoked
func (s *CryptService) Key(n [32]byte) ([32]byte, error) {
s.KeyInvoked = true
return s.KeyFn(n)
}
// Encrypt invokes the mock implementation and marks the function as invoked
func (s *CryptService) Encrypt(dst io.Writer, src io.Reader, key [32]byte) (n int64, err error) {
s.EncryptInvoked = true
return s.EncryptFn(dst, src, key)
}
// Decrypt invokes the mock implementation and marks the function as invoked
func (s *CryptService) Decrypt(dst io.Writer, src io.Reader, key [32]byte) (n int64, err error) {
s.DecryptInvoked = true
return s.DecryptFn(dst, src, key)
}
......@@ -65,8 +65,8 @@ type LogService interface {
// CryptService is the domain representation of the cryptographic service
// available throughout the application.
type CryptService interface {
Nonce(n []byte) (int, error)
Key(n []byte) ([32]byte, error)
Encrypt(dst io.Writer, src io.Reader, config Config) (n int64, err error)
Decrypt(dst io.Writer, src io.Reader, config Config) (n int64, err error)
Nonce(n [32]byte) (int, error)
Key(n [32]byte) ([32]byte, error)
Encrypt(dst io.Writer, src io.Reader, key [32]byte) (n int64, err error)
Decrypt(dst io.Writer, src io.Reader, key [32]byte) (n int64, err error)
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment