diff --git a/internal/configuration/setup/Setup.go b/internal/configuration/setup/Setup.go
index 92b3e52..9d1256a 100644
--- a/internal/configuration/setup/Setup.go
+++ b/internal/configuration/setup/Setup.go
@@ -447,8 +447,12 @@ func parseCloudSettings(formObjects *[]jsonFormObject) (*cloudconfig.CloudConfig
}
func getCloudConfig(formObjects *[]jsonFormObject) (*cloudconfig.CloudConfig, error) {
- var err error
awsConfig := cloudconfig.CloudConfig{}
+ proxyDownload, err := getFormValueString(formObjects, "storage_sel_proxy")
+ if err != nil {
+ return nil, err
+ }
+ awsConfig.Aws.ProxyDownload = proxyDownload == "proxy"
awsConfig.Aws.Bucket, err = getFormValueString(formObjects, "s3_bucket")
if err != nil {
return nil, err
diff --git a/internal/configuration/setup/Setup_test.go b/internal/configuration/setup/Setup_test.go
index dd20f29..9eb1f7a 100644
--- a/internal/configuration/setup/Setup_test.go
+++ b/internal/configuration/setup/Setup_test.go
@@ -5,6 +5,7 @@ import (
"context"
"encoding/json"
"errors"
+ "fmt"
"github.com/forceu/gokapi/internal/configuration"
"github.com/forceu/gokapi/internal/configuration/cloudconfig"
"github.com/forceu/gokapi/internal/configuration/database"
@@ -35,6 +36,10 @@ func TestMain(m *testing.M) {
func TestDebugNotSet(t *testing.T) {
test.IsEqualBool(t, debugDisableAuth, false)
+ if debugDisableAuth {
+ fmt.Println("Debug mode is still on! Exiting test")
+ os.Exit(1)
+ }
}
func TestInputToJson(t *testing.T) {
@@ -345,6 +350,7 @@ func TestIntegration(t *testing.T) {
test.IsEqualString(t, cconfig.Aws.KeyId, "testapi")
test.IsEqualString(t, cconfig.Aws.KeySecret, "testsecret")
test.IsEqualString(t, cconfig.Aws.Endpoint, "testendpoint")
+ test.IsEqualBool(t, cconfig.Aws.ProxyDownload, true)
}
test.FileExists(t, "test/cloudconfig.yml")
@@ -418,7 +424,7 @@ func TestIntegration(t *testing.T) {
test.IsEqualString(t, settings.ServerUrl, "http://127.0.0.1:53842/")
test.IsEqualString(t, settings.RedirectUrl, "https://test.com")
test.IsEqualBool(t, settings.PicturesAlwaysLocal, false)
- _, ok = cloudconfig.Load()
+ cconfig, ok = cloudconfig.Load()
if os.Getenv("GOKAPI_AWS_BUCKET") == "" {
test.IsEqualBool(t, ok, false)
}
@@ -445,6 +451,7 @@ func TestIntegration(t *testing.T) {
waitForServer(t, false)
test.IsEqualBool(t, settings.PicturesAlwaysLocal, true)
+ test.IsEqualBool(t, cconfig.Aws.ProxyDownload, false)
test.IsEqualString(t, settings.Authentication.OAuthProvider, "provider")
test.IsEqualString(t, settings.Authentication.OAuthClientId, "id")
test.IsEqualString(t, settings.Authentication.OAuthClientSecret, "secret")
@@ -481,6 +488,7 @@ type setupValues struct {
AuthHeaderUsers setupEntry `form:"auth_header_users"`
StorageSelection setupEntry `form:"storage_sel"`
PicturesAlwaysLocal setupEntry `form:"storage_sel_image"`
+ ProxyDownloads setupEntry `form:"storage_sel_proxy"`
S3Bucket setupEntry `form:"s3_bucket"`
S3Region setupEntry `form:"s3_region"`
S3ApiKey setupEntry `form:"s3_api"`
@@ -591,6 +599,7 @@ func createInputInternalAuth() setupValues {
values.AuthUsername.Value = "admin"
values.AuthPassword.Value = "adminadmin"
values.StorageSelection.Value = "cloud"
+ values.ProxyDownloads.Value = "proxy"
values.S3Bucket.Value = "testbucket"
values.S3Region.Value = "testregion"
values.S3ApiKey.Value = "testapi"
@@ -643,6 +652,7 @@ func createInputOAuth() setupValues {
values.OAuthAuthorisedGroups.Value = "group1; group2"
values.StorageSelection.Value = "local"
values.PicturesAlwaysLocal.Value = "local"
+ values.ProxyDownloads.Value = "default"
return values
}
diff --git a/internal/configuration/setup/templates/setup.tmpl b/internal/configuration/setup/templates/setup.tmpl
index 7fb0f4b..7532af5 100644
--- a/internal/configuration/setup/templates/setup.tmpl
+++ b/internal/configuration/setup/templates/setup.tmpl
@@ -344,7 +344,7 @@
If you configured Gokapi to use a password during startup, please enter it now in the console before clicking on continue.
- Continue
+ Continue
@@ -676,7 +687,9 @@ function TestAWS(button, isManual) {
wizard.el.find(".wizard-success .im-done").click(function() {
- window.location.href = $("#url").val()+"admin";
+ document.getElementById("bt_finishsetup").disabled=true;
+ document.getElementById("bt_finishsetup").text="Please wait";
+ setTimeout(function(){ window.location.href = $("#url").val()+"admin";}, 1500);
});
@@ -735,10 +748,20 @@ function TestAWS(button, isManual) {
document.getElementById("auth_header_users").value = "{{ .HeaderUsers }}";
break;
}
+
{{ if .CloudSettings.Aws.Bucket }}
document.getElementById("storage_sel").value = "cloud";
storageSelectionChanged(1);
+
+ {{ if .CloudSettings.Aws.ProxyDownload }}
+ document.getElementById("storage_sel_proxy").value = "proxy";
+ {{ end }}
+
+ {{ if .Settings.PicturesAlwaysLocal }}
+ document.getElementById("storage_sel_image").value = "local";
+ {{ end }}
+
{{ if not .S3EnvProvided }}
document.getElementById("s3_bucket").value = "{{ .CloudSettings.Aws.Bucket }}";
document.getElementById("s3_region").value = "{{ .CloudSettings.Aws.Region }}";
@@ -886,10 +909,15 @@ function TestAWS(button, isManual) {
function storageSelectionChanged(index) {
let card = wizard.cards["s3credentials"];
- if (index==0)
+ let storageOptDiv = document.getElementById("cloudstorage_options");
+ if (index==0) {
card.disable(true);
- else if (index==1)
+ storageOptDiv.style.visibility="hidden";
+ } else {
card.enable();
+ storageOptDiv.style.visibility="visible";
+ }
+
}
$(function() {
diff --git a/internal/storage/FileServing.go b/internal/storage/FileServing.go
index 9bf584e..c8d8562 100644
--- a/internal/storage/FileServing.go
+++ b/internal/storage/FileServing.go
@@ -534,11 +534,14 @@ func ServeFile(file models.File, w http.ResponseWriter, r *http.Request, forceDo
logging.AddDownload(&file, r, configuration.Get().SaveIp)
if !file.IsLocalStorage() {
- // We are not setting a download complete status as there is no reliable way to
+ // If non-blocking, we are not setting a download complete status as there is no reliable way to
// confirm that the file has been completely downloaded. It expires automatically after 24 hours.
- downloadstatus.SetDownload(file)
- err := aws.ServeFile(w, r, file, forceDownload)
+ statusId := downloadstatus.SetDownload(file)
+ isBlocking, err := aws.ServeFile(w, r, file, forceDownload)
helper.Check(err)
+ if isBlocking {
+ downloadstatus.SetComplete(statusId)
+ }
return
}
fileData, size := getFileHandler(file, configuration.Get().DataDir)
diff --git a/internal/storage/filesystem/s3filesystem/aws/Aws.go b/internal/storage/filesystem/s3filesystem/aws/Aws.go
index f724d00..c223591 100644
--- a/internal/storage/filesystem/s3filesystem/aws/Aws.go
+++ b/internal/storage/filesystem/s3filesystem/aws/Aws.go
@@ -119,17 +119,16 @@ func Download(writer io.WriterAt, file models.File) (int64, error) {
return size, nil
}
-// ServeFile either redirects the user to a pre-signed download url (default) or downloads the file and serves it as a proxy (if request
-func ServeFile(w http.ResponseWriter, r *http.Request, file models.File, forceDownload bool) error {
+// ServeFile either redirects the user to a pre-signed download url (default) or downloads the file and serves it as a proxy (depending
+// on configuration). Returns true if blocking operation (in order to set download status) or false if non-blocking.
+func ServeFile(w http.ResponseWriter, r *http.Request, file models.File, forceDownload bool) (bool, error) {
if !awsConfig.ProxyDownload {
- return RedirectToDownload(w, r, file, forceDownload)
+ return false, redirectToDownload(w, r, file, forceDownload)
}
- return ProxyDownload(w, file, forceDownload)
+ return true, proxyDownload(w, file, forceDownload)
}
-// RedirectToDownload creates a presigned link that is valid for 15 seconds and redirects the
-// client to this url
-func RedirectToDownload(w http.ResponseWriter, r *http.Request, file models.File, forceDownload bool) error {
+func getPresignedUrl(file models.File, forceDownload bool) (string, error) {
sess := createSession()
s3svc := s3.New(sess)
@@ -146,7 +145,13 @@ func RedirectToDownload(w http.ResponseWriter, r *http.Request, file models.File
ResponseContentType: aws.String(file.ContentType),
})
- url, err := req.Presign(15 * time.Second)
+ return req.Presign(15 * time.Second)
+}
+
+// redirectToDownload creates a presigned link that is valid for 15 seconds and redirects the
+// client to this url
+func redirectToDownload(w http.ResponseWriter, r *http.Request, file models.File, forceDownload bool) error {
+ url, err := getPresignedUrl(file, forceDownload)
if err != nil {
return err
}
@@ -155,28 +160,13 @@ func RedirectToDownload(w http.ResponseWriter, r *http.Request, file models.File
return nil
}
-// ProxyDownload streams the file from S3 as a proxy
-func ProxyDownload(w http.ResponseWriter, file models.File, forceDownload bool) error {
- sess := createSession()
- s3svc := s3.New(sess)
-
- contentDisposition := "inline; filename=\"" + file.Name + "\""
- if forceDownload {
- contentDisposition = "Attachment; filename=\"" + file.Name + "\""
- }
-
- req, _ := s3svc.GetObjectRequest(&s3.GetObjectInput{
- Bucket: aws.String(file.AwsBucket),
- Key: aws.String(file.SHA1),
- ResponseContentDisposition: aws.String(contentDisposition),
- ResponseCacheControl: aws.String("no-store"),
- ResponseContentType: aws.String(file.ContentType),
- })
-
- url, err := req.Presign(15 * time.Second)
+// proxyDownload streams the file from S3 as a proxy, by downloading a presigned url
+func proxyDownload(w http.ResponseWriter, file models.File, forceDownload bool) error {
+ url, err := getPresignedUrl(file, forceDownload)
if err != nil {
return err
}
+
resp, err := http.Get(url)
if err != nil {
return err
@@ -214,7 +204,8 @@ func fileExists(bucket, filename string) (bool, int64, error) {
})
if err != nil {
- aerr, ok := err.(awserr.Error)
+ var aerr awserr.Error
+ ok := errors.As(err, &aerr)
if ok {
if aerr.Code() == "NotFound" {
return false, 0, nil
@@ -260,7 +251,8 @@ func IsCorsCorrectlySet(bucket, gokapiUrl string) (bool, error) {
result, err := svc.GetBucketCorsWithContext(ctx, input)
if err != nil {
- aerr, ok := err.(awserr.Error)
+ var aerr awserr.Error
+ ok := errors.As(err, &aerr)
if ok && aerr.Code() == "NoSuchCorsConfiguration" {
return false, nil
}
diff --git a/internal/storage/filesystem/s3filesystem/aws/Aws_mock.go b/internal/storage/filesystem/s3filesystem/aws/Aws_mock.go
index 1d6d080..cc225f3 100644
--- a/internal/storage/filesystem/s3filesystem/aws/Aws_mock.go
+++ b/internal/storage/filesystem/s3filesystem/aws/Aws_mock.go
@@ -127,9 +127,11 @@ func isUploaded(file models.File) bool {
return false
}
-// ServeFile either redirects the user to a pre-signed download url (default) or downloads the file and serves it as a proxy (if request
-func ServeFile(w http.ResponseWriter, r *http.Request, file models.File, forceDownload bool) error {
- return RedirectToDownload(w, r, file, forceDownload)
+// ServeFile either redirects the user to a pre-signed download url (default) or downloads the file and serves it as a proxy (depending
+// on configuration). Returns true if blocking operation (in order to set download status) or false if non-blocking.
+func ServeFile(w http.ResponseWriter, r *http.Request, file models.File, forceDownload bool) (bool, error) {
+ // TODO implement proxy as well
+ return false, RedirectToDownload(w, r, file, forceDownload)
}
// RedirectToDownload creates a presigned link that is valid for 15 seconds and redirects the
diff --git a/internal/storage/filesystem/s3filesystem/aws/Aws_slim.go b/internal/storage/filesystem/s3filesystem/aws/Aws_slim.go
index 63d3c0a..d97b0a7 100644
--- a/internal/storage/filesystem/s3filesystem/aws/Aws_slim.go
+++ b/internal/storage/filesystem/s3filesystem/aws/Aws_slim.go
@@ -57,9 +57,10 @@ func RedirectToDownload(w http.ResponseWriter, r *http.Request, file models.File
return errors.New(errorString)
}
-// ServeFile either redirects the user to a pre-signed download url (default) or downloads the file and serves it as a proxy (if request
-func ServeFile(w http.ResponseWriter, r *http.Request, file models.File, forceDownload bool) error {
- return errors.New(errorString)
+// ServeFile either redirects the user to a pre-signed download url (default) or downloads the file and serves it as a proxy (depending
+// on configuration). Returns true if blocking operation (in order to set download status) or false if non-blocking.
+func ServeFile(w http.ResponseWriter, r *http.Request, file models.File, forceDownload bool) (bool, error) {
+ return false, errors.New(errorString)
}
// FileExists returns true if the object is stored in S3
diff --git a/internal/storage/filesystem/s3filesystem/aws/Aws_test.go b/internal/storage/filesystem/s3filesystem/aws/Aws_test.go
index 23c8897..78f7faf 100644
--- a/internal/storage/filesystem/s3filesystem/aws/Aws_test.go
+++ b/internal/storage/filesystem/s3filesystem/aws/Aws_test.go
@@ -8,16 +8,22 @@ import (
"github.com/forceu/gokapi/internal/test"
"github.com/johannesboyne/gofakes3"
"github.com/johannesboyne/gofakes3/backend/s3mem"
+ "io"
+ "net/http"
"net/http/httptest"
"os"
+ "strings"
"testing"
)
var testFile, invalidFile, invalidBucket, invalidAll models.File
+var isRealAwsServer bool
+
func TestMain(m *testing.M) {
testFile.AwsBucket = "gokapi-test"
testFile.SHA1 = "testfile"
+ testFile.Name = "Testfile.jpg"
invalidFile.AwsBucket = "gokapi-test"
invalidFile.SHA1 = "invalid"
invalidBucket.AwsBucket = "invalid"
@@ -28,6 +34,8 @@ func TestMain(m *testing.M) {
ts := startMockServer()
os.Setenv("GOKAPI_AWS_ENDPOINT", ts.URL)
defer ts.Close()
+ } else {
+ isRealAwsServer = true
}
exitVal := m.Run()
os.Exit(exitVal)
@@ -87,21 +95,62 @@ func TestDownloadFromAws(t *testing.T) {
os.Remove("test")
}
-func TestRedirectToDownload(t *testing.T) {
+func TestServeFile(t *testing.T) {
+ awsConfig.ProxyDownload = false
+ testServing(t, true, false)
+ testServing(t, true, true)
+
+ awsConfig.ProxyDownload = true
+ testServing(t, false, false)
+ testServing(t, false, true)
+ awsConfig.ProxyDownload = false
+}
+
+func testServing(t *testing.T, expectRedirect, forceDownload bool) {
w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "/download", nil)
- err := RedirectToDownload(w, r, testFile, false)
- test.IsNil(t, err)
- test.ResponseBodyContains(t, w, "