Commit 1fef9c2b by Mads

Some of tonights work. Seems like there's a bug with where nonces are generating…

Some of tonights work. Seems like there's a bug with where nonces are generating empty arrays. Test case has been added for this and will fix ASAP
parent 0f650ad0
Pipeline #20288479 failed with stages
in 31 seconds
......@@ -3,6 +3,34 @@ that [Ben Johnson](https://medium.com/@benbjohnson) coined in 2016. Whenever som
with that architecture, it is to be marked with a `FIXME(Name):` message
in the comments.
## Logging, errors and panics
Vee uses the [yalp](https://gitlab.com/MadsRC/yalp) logging library, and
adheres to the ideas presented within said library.
Generally, using the Print and Printf methods of yalp should be used sparringly
and only be used to provide useful information in the context of operating vee.
Such information could be access logging and logging errors.
The Debug and Debugf methods is to be used only for information relevant to
debug errors in the context of developing and/or operating vee. Debug and
Debugf will thus cause a lot of output.
### Errors
Once an error occurs, consider the following:
1. Can it be ignored? If so, log it with Debug or Debugf.
2. Let the error "bubble upwards" by returning it.
3. If returning the error doesn't make sense, call panic(err)
### Panic
Panics can be used, but should be used sparringly. By handling the error, we're
able to provide a context specific response, such as 405 Method Not Allowed in
HTTP requests, wheres panics, in the context of HTTP requests, should result in
a "generic" 500 Internal Server Error. It should be up to the adapter (Our HTTP
module is one such, as it "adapts" our code to be available over HTTP) how to
handle panics. Usually a panic will be handled by logging it and crashing the
application.
## Testing
Vee uses the following guidelines for testing:
......@@ -12,7 +40,7 @@ Vee uses the following guidelines for testing:
4. `_internal_test.go` is also for Unit tests belonging to the same package as the code being tested.
5. `_test.go` is prefered over `_internal_test.go` to allow us to change the internals as we like. See [this](https://medium.com/@matryer/5-simple-tips-and-tricks-for-writing-unit-tests-in-golang-619653f90742) post by Mat Ryer.
6. `_integration_test.go` is for Integration testing. To lighten the development workload, these are not run as often, as Unit testing covers _most_ of our needs.
7. Add an integration build constraint to the top of integration tests and call tests with "sudo -E /usr/local/go/bin/go test -v -cover -tags=integration ./..." to run integration tests
7. Add an integration build constraint to the top of integration tests and call tests with `sudo -E /usr/local/go/bin/go test -v -cover -tags=integration ./...` to run integration tests
### Acceptance vs Integration
......
......@@ -5,6 +5,6 @@ First thing to notice is that the project aims to use the Git Flow workflow
for working with Git. You can read more about it [here](http://nvie.com/posts/a-successful-git-branching-model/).
You would most likely also like to read about the way the code is organized,
which you canfind [here](CODE_ORGANIZATION.md).
which you can find [here](CODE_ORGANIZATION.md).
Please feel free send a pull request!
......@@ -3,6 +3,7 @@ package crypt_test
import (
"bufio"
"bytes"
"fmt"
"testing"
vee "gitlab.com/MadsRC/vee/app"
......@@ -11,6 +12,7 @@ import (
func TestCrypt(t *testing.T) {
var config vee.Config
var emptyNonce [32]byte
var nonce [32]byte
var key [32]byte
cs := crypt.CryptService{
......@@ -21,6 +23,10 @@ func TestCrypt(t *testing.T) {
t.Fatalf("Error creating nonce. Err: %s\n", err)
}
if nonce == emptyNonce {
t.Fatalf("Nonce should not be empty")
}
key, err = cs.Key(nonce)
if err != nil {
t.Fatalf("Error creating key. Err: %s\n", err)
......@@ -36,6 +42,9 @@ func TestCrypt(t *testing.T) {
if err != nil {
t.Fatalf("Error encrypting. Err: %s\n", err)
}
fmt.Printf("cipher: %+v\n", cipher)
fmt.Printf("key: %+v\n", key)
fmt.Printf("nonce: %+v\n", nonce)
if cipher.String() == plain.String() {
t.Fatal("Expected cipher text to be different from plain text")
}
......
package http
import (
"encoding/json"
"fmt"
"io"
"mime/multipart"
......@@ -8,32 +9,10 @@ import (
"strconv"
"strings"
"github.com/google/uuid"
vee "gitlab.com/MadsRC/vee/app"
)
func internalServerError(w http.ResponseWriter, r *http.Request) {
http.Error(w, "Internal server Error", http.StatusInternalServerError)
return
}
func badRequestError(w http.ResponseWriter, r *http.Request, msg string) {
http.Error(w, msg, http.StatusBadRequest)
return
}
// ReceiveFileHandler handles the multipart/form-data request received from a
// client.
//
// Do note that the Content-Type should be checked before passing control of
// the request to ReceiveFileHandler. If ReceiveFileHandler is given a
// request which isn't a multipart/form-data, a HTTP status code of 500 is
// returned
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")
// and tries to extract the size parameter. If returned, non-error, value is -1
// no size parameter has been found.
......@@ -65,7 +44,7 @@ func validateFilePart(part *multipart.Part) error {
partSize, err := extractSize(cd)
if err != nil {
if strings.Contains(err.Error(), "invalid syntax") {
err = fmt.Errorf("Part %s size parameter has bad syntax\"", part.FormName())
err = fmt.Errorf("Part %s size parameter has bad syntax", part.FormName())
}
return err
}
......@@ -110,52 +89,129 @@ func handleFilePart(h *ReceiveFileHandler, part *multipart.Part) (*vee.File, err
_, err = h.Crypt.Encrypt(pWriter, part, recvFile.Key)
if err != nil {
//internalServerError(w, r)
// DO SOMETHING HERE
return
panic(err)
}
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.
// isn't possible to accidently save unencrypted data. However, we'd
// need the "part" variable available inside the SaveFile function,
// which it isn't.
err = h.FileService.SaveFile(recvFile)
return recvFile, err
}
type partError struct {
FormName string `json:"name,omitempty"` //Might be omitted as the formname
//might not be available at the time the error occurs (Such as with a missing
//content-disposition.
Error string `json:"error"`
}
type partSuccess struct {
FormName string `json:"formName"`
ID string `json:"id"`
}
type response struct {
Errors []partError `json:"errors,omitempty"`
Success []partSuccess `json:"success,omitempty"`
}
// ReceiveFileHandler handles the multipart/form-data request received from a
// client.
//
// Do note that the Content-Type should be checked before passing control of
// the request to ReceiveFileHandler. If ReceiveFileHandler is given a
// request which isn't a multipart/form-data a panic occurs.
type ReceiveFileHandler struct {
FileService vee.FileService
Config vee.Config
Log vee.LogService
Crypt vee.CryptService
}
func (h *ReceiveFileHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
reader, err := r.MultipartReader()
if err != nil {
h.Log.Print(err)
internalServerError(w, r)
return
panic(err) // panic, as content-type and method (multipart/form-data POST)
// should have been checked prior to calling this function.
}
var files []*vee.File
var partErrors []error
var partErrors []partError
var partSuccesses []partSuccess
for {
part, err := reader.NextPart()
if err == io.EOF {
break
}
if err != nil {
h.Log.Print(err)
internalServerError(w, r)
return
panic(err)
}
file, err := handleFilePart(h, part)
if err != nil {
partErrors = append(partErrors, err)
partErrors = append(partErrors, partError{FormName: part.FormName(), Error: err.Error()})
continue
}
files = append(files, file)
partSuccesses = append(partSuccesses, partSuccess{FormName: part.FormName(), ID: file.Name.String()})
}
// w.Header().Set("location", fmt.Sprintf("%s", recvFile.Name.String()))
fmt.Println(files)
if len(partErrors) > 0 {
fmt.Println("kage")
if len(partSuccesses) == 0 {
w.WriteHeader(http.StatusBadRequest)
return
} else {
w.WriteHeader(http.StatusCreated)
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(response{Errors: partErrors, Success: partSuccesses})
}
// FetchFileHandler takes care of retrieving a given file from storage,
// decrypting it and sending it back to the client.
type FetchFileHandler struct {
FileService vee.FileService
Config vee.Config
Log vee.LogService
Crypt vee.CryptService
}
func (h *FetchFileHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
paths := strings.Split(r.URL.Path, "/")
file := h.FileService.NewFile()
var err error
file.Name, err = uuid.Parse(paths[2])
if err != nil {
if strings.HasPrefix(err.Error(), "invalid UUID length") {
http.Error(w, "Not Found", http.StatusNotFound)
return
}
panic(err)
}
fmt.Println("fisk")
w.WriteHeader(http.StatusCreated)
err = h.FileService.GetFile(file)
if err != nil {
if err == vee.ErrExpiredObject {
http.Error(w, err.Error(), http.StatusGone)
return
}
panic(err)
}
fmt.Printf("%+v\n", file)
pReader, pWriter := io.Pipe()
go func() {
defer pWriter.Close()
_, err = h.Crypt.Decrypt(pWriter, file.Content, file.Key)
if err != nil {
panic(err)
}
return
}()
io.Copy(w, pReader)
}
......@@ -3,12 +3,12 @@ package http_test
import (
"bytes"
"crypto/rand"
"errors"
"io"
"mime/multipart"
"net/http"
"net/http/httptest"
"net/textproto"
"strings"
"testing"
"github.com/google/uuid"
......@@ -20,7 +20,6 @@ import (
var fs mock.FileService
var logService mock.LogService
var crypt mock.CryptService
var h veeHTTP.ReceiveFileHandler
func resetMocks() {
//Mock FileService.NewFile() call.
......@@ -33,6 +32,11 @@ func resetMocks() {
}
fs.SaveFileInvoked = false
fs.GetFileFn = func(f *vee.File) error {
return nil
}
fs.GetFileInvoked = false
//Mock LogService.Print() call.
logService.PrintFn = func(v ...interface{}) {
return
......@@ -91,33 +95,51 @@ func createMultipartBody(size int64, cd string) (*bytes.Buffer, string, error) {
return body, writer.FormDataContentType(), nil
}
func TestHandler(t *testing.T) {
func TestReceiveFile(t *testing.T) {
var h veeHTTP.ReceiveFileHandler
// Inject our mock
h.FileService = &fs
h.Log = &logService
h.Crypt = &crypt
resetMocks()
t.Run("Content-TypeNotMultipartFormData", func(t *testing.T) {
//Invoke the handler
w := httptest.NewRecorder()
r, _ := http.NewRequest("POST", "/file", nil)
h.ServeHTTP(w, r)
if w.Code != http.StatusInternalServerError {
t.Fatalf("Expected response code to be %d, was %d\n", http.StatusInternalServerError, w.Code)
t.Run("Panics", func(t *testing.T) {
tests := []struct {
method string
body io.Reader
}{
{"POST", nil},
{"GET", nil},
{"DELETE", nil},
{"PUT", nil},
{"OPTIONS", nil},
{"POST", strings.NewReader("fisk")},
{"GET", strings.NewReader("fisk")},
{"DELETE", strings.NewReader("fisk")},
{"PUT", strings.NewReader("fisk")},
{"OPTIONS", strings.NewReader("fisk")},
}
for _, test := range tests {
w := httptest.NewRecorder()
r, _ := http.NewRequest(test.method, "/file", test.body)
defer func() {
if r := recover(); r == nil {
t.Errorf("The code did not panic - test: %+v", test)
}
}()
h.ServeHTTP(w, r)
}
})
resetMocks()
t.Run("Content-Disposition and size present", func(t *testing.T) {
resetMocks()
t.Run("Requests", func(t *testing.T) {
tables := []struct {
x string
y int64
z int
contentDisposition string
size int64
expectedResponse int
}{
{"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=\"1048576\"", 10, http.StatusCreated},
{"form-data; filename=\"randomdata.bin\"; name=\"file\"; size=24000", 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},
......@@ -125,9 +147,8 @@ func TestHandler(t *testing.T) {
}
for i, table := range tables {
//Invoke the handler
w := httptest.NewRecorder()
body, ct, err := createMultipartBody(table.y, table.x)
body, ct, err := createMultipartBody(table.size, table.contentDisposition)
if err != nil {
t.Fatalf("Failed creating multipart: %v\n", err)
}
......@@ -138,63 +159,48 @@ func TestHandler(t *testing.T) {
r.Header.Set("Content-Type", ct)
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)
if w.Code != table.expectedResponse {
t.Fatalf("[%d] Expected response code to be %d, was %d\n", i, table.expectedResponse, 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.z {
t.Fatalf("Expected response code to be %d, was %d\n", table.z, w.Code)
}
})
}
}
func TestFetchFile(t *testing.T) {
var h veeHTTP.FetchFileHandler
// Inject our mock
h.FileService = &fs
h.Log = &logService
h.Crypt = &crypt
crypt.KeyFn = func(n [32]byte) ([32]byte, error) {
return [32]byte{}, errors.New("This key is an error")
expiredFile := func() {
fs.GetFileFn = func(f *vee.File) error {
return vee.ErrExpiredObject
}
}
//Invoke the handler
w = httptest.NewRecorder()
tables = []struct {
x string
y int64
z int
resetMocks()
t.Run("Panics", func(t *testing.T) {
tests := []struct {
method string
path string
expectedCode int
preFunc func()
}{
{"form-data; filename=\"randomdata.bin\"; name=\"file\"; size=\"10\"", 10, http.StatusBadRequest},
{"GET", "/file/123avbfs", 404, nil},
{"GET", "/file/2ed03414-afc8-49c3-bd44-521aa6d46647", 410, expiredFile},
}
for _, table := range tables {
body, ct, _ := createMultipartBody(table.y, table.x)
r, _ := http.NewRequest("POST", "/file", body)
r.Header.Set("Content-Type", ct)
for i, test := range tests {
resetMocks()
if test.preFunc != nil {
test.preFunc()
}
w := httptest.NewRecorder()
r, _ := http.NewRequest(test.method, test.path, nil)
h.ServeHTTP(w, r)
if w.Code != table.z {
t.Fatalf("Expected response code to be %d, was %d\n", table.z, w.Code)
if w.Code != test.expectedCode {
t.Errorf("[%d] Expected response code to be %d, was %d\n", i, test.expectedCode, w.Code)
}
}
})
}
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