From 93b9c1617e4d1088c53a36df986bf344696af71c Mon Sep 17 00:00:00 2001 From: Nicola Murino Date: Sat, 19 Mar 2022 21:44:27 +0100 Subject: [PATCH] web UI: allow to load custom css Signed-off-by: Nicola Murino --- config/config.go | 66 ++- config/config_test.go | 11 + dataprovider/dataprovider.go | 6 +- docs/full-configuration.md | 2 + go.mod | 2 +- go.sum | 4 +- httpd/api_shares.go | 24 +- httpd/httpd.go | 23 +- httpd/internal_test.go | 87 ++-- httpd/middleware.go | 34 +- httpd/oidc.go | 2 +- httpd/server.go | 569 +++++++++++++------------- httpd/web.go | 4 + httpd/webadmin.go | 367 +++++++++-------- httpd/webclient.go | 268 ++++++------ pkgs/build.sh | 2 +- sftpgo.json | 3 +- templates/common/forgot-password.html | 4 + templates/common/reset-password.html | 4 + templates/webadmin/base.html | 4 + templates/webadmin/baselogin.html | 4 + templates/webclient/base.html | 4 + templates/webclient/baselogin.html | 4 + templates/webclient/viewpdf.html | 4 + 24 files changed, 825 insertions(+), 677 deletions(-) diff --git a/config/config.go b/config/config.go index 07241346..69090065 100644 --- a/config/config.go +++ b/config/config.go @@ -108,6 +108,7 @@ var ( CrossOriginOpenerPolicy: "", ExpectCTHeader: "", }, + ExtraCSS: []httpd.CustomCSS{}, } defaultRateLimiter = common.RateLimiterConfig{ Average: 0, @@ -1268,6 +1269,25 @@ func getHTTPDOIDCFromEnv(idx int) (httpd.OIDC, bool) { return result, isSet } +func getHTTPDExtraCSSFromEnv(idx int) []httpd.CustomCSS { + var css []httpd.CustomCSS + + for subIdx := 0; subIdx < 10; subIdx++ { + var customCSS httpd.CustomCSS + + path, ok := os.LookupEnv(fmt.Sprintf("SFTPGO_HTTPD__BINDINGS__%v__EXTRA_CSS__%v__PATH", idx, subIdx)) + if ok { + customCSS.Path = path + } + + if path != "" { + css = append(css, customCSS) + } + } + + return css +} + func getHTTPDWebClientIntegrationsFromEnv(idx int) []httpd.WebClientIntegration { var integrations []httpd.WebClientIntegration @@ -1306,6 +1326,36 @@ func getDefaultHTTPBinding(idx int) httpd.Binding { return binding } +func getHTTPDNestedObjectsFromEnv(idx int, binding *httpd.Binding) bool { + isSet := false + + webClientIntegrations := getHTTPDWebClientIntegrationsFromEnv(idx) + if len(webClientIntegrations) > 0 { + binding.WebClientIntegrations = webClientIntegrations + isSet = true + } + + oidc, ok := getHTTPDOIDCFromEnv(idx) + if ok { + binding.OIDC = oidc + isSet = true + } + + securityConf, ok := getHTTPDSecurityConfFromEnv(idx) + if ok { + binding.Security = securityConf + isSet = true + } + + extraCSS := getHTTPDExtraCSSFromEnv(idx) + if len(extraCSS) > 0 { + binding.ExtraCSS = extraCSS + isSet = true + } + + return isSet +} + func getHTTPDBindingFromEnv(idx int) { binding := getDefaultHTTPBinding(idx) isSet := false @@ -1340,12 +1390,6 @@ func getHTTPDBindingFromEnv(idx int) { isSet = true } - webClientIntegrations := getHTTPDWebClientIntegrationsFromEnv(idx) - if len(webClientIntegrations) > 0 { - binding.WebClientIntegrations = webClientIntegrations - isSet = true - } - enableHTTPS, ok := lookupBoolFromEnv(fmt.Sprintf("SFTPGO_HTTPD__BINDINGS__%v__ENABLE_HTTPS", idx)) if ok { binding.EnableHTTPS = enableHTTPS @@ -1382,15 +1426,7 @@ func getHTTPDBindingFromEnv(idx int) { isSet = true } - oidc, ok := getHTTPDOIDCFromEnv(idx) - if ok { - binding.OIDC = oidc - isSet = true - } - - securityConf, ok := getHTTPDSecurityConfFromEnv(idx) - if ok { - binding.Security = securityConf + if getHTTPDNestedObjectsFromEnv(idx, &binding) { isSet = true } diff --git a/config/config_test.go b/config/config_test.go index 88d33f07..7f60d715 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -828,6 +828,7 @@ func TestHTTPDBindingsFromEnv(t *testing.T) { os.Setenv("SFTPGO_HTTPD__BINDINGS__1__PORT", "8000") os.Setenv("SFTPGO_HTTPD__BINDINGS__1__ENABLE_HTTPS", "0") os.Setenv("SFTPGO_HTTPD__BINDINGS__1__HIDE_LOGIN_URL", " 1") + os.Setenv("SFTPGO_HTTPD__BINDINGS__1__EXTRA_CSS__0__PATH", "") os.Setenv("SFTPGO_HTTPD__BINDINGS__2__ADDRESS", "127.0.1.1") os.Setenv("SFTPGO_HTTPD__BINDINGS__2__PORT", "9000") os.Setenv("SFTPGO_HTTPD__BINDINGS__2__ENABLE_WEB_ADMIN", "0") @@ -863,6 +864,8 @@ func TestHTTPDBindingsFromEnv(t *testing.T) { os.Setenv("SFTPGO_HTTPD__BINDINGS__2__SECURITY__PERMISSIONS_POLICY", "fullscreen=(), geolocation=()") os.Setenv("SFTPGO_HTTPD__BINDINGS__2__SECURITY__CROSS_ORIGIN_OPENER_POLICY", "same-origin") os.Setenv("SFTPGO_HTTPD__BINDINGS__2__SECURITY__EXPECT_CT_HEADER", `max-age=86400, enforce, report-uri="https://foo.example/report"`) + os.Setenv("SFTPGO_HTTPD__BINDINGS__2__EXTRA_CSS__0__PATH", "path1") + os.Setenv("SFTPGO_HTTPD__BINDINGS__2__EXTRA_CSS__1__PATH", "path2") t.Cleanup(func() { os.Unsetenv("SFTPGO_HTTPD__BINDINGS__0__ADDRESS") os.Unsetenv("SFTPGO_HTTPD__BINDINGS__0__PORT") @@ -871,6 +874,7 @@ func TestHTTPDBindingsFromEnv(t *testing.T) { os.Unsetenv("SFTPGO_HTTPD__BINDINGS__1__PORT") os.Unsetenv("SFTPGO_HTTPD__BINDINGS__1__ENABLE_HTTPS") os.Unsetenv("SFTPGO_HTTPD__BINDINGS__1__HIDE_LOGIN_URL") + os.Unsetenv("SFTPGO_HTTPD__BINDINGS__1__EXTRA_CSS__0__PATH") os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__ADDRESS") os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__PORT") os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__ENABLE_HTTPS") @@ -906,6 +910,8 @@ func TestHTTPDBindingsFromEnv(t *testing.T) { os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__SECURITY__PERMISSIONS_POLICY") os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__SECURITY__CROSS_ORIGIN_OPENER_POLICY") os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__SECURITY__EXPECT_CT_HEADER") + os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__EXTRA_CSS__0__PATH") + os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__EXTRA_CSS__1__PATH") }) configDir := ".." @@ -929,6 +935,7 @@ func TestHTTPDBindingsFromEnv(t *testing.T) { require.Equal(t, "127.0.0.1", bindings[1].Address) require.False(t, bindings[1].EnableHTTPS) require.Equal(t, 12, bindings[0].MinTLSVersion) + require.Len(t, bindings[0].ExtraCSS, 0) require.True(t, bindings[1].EnableWebAdmin) require.True(t, bindings[1].EnableWebClient) require.True(t, bindings[1].RenderOpenAPI) @@ -936,6 +943,7 @@ func TestHTTPDBindingsFromEnv(t *testing.T) { require.Equal(t, 1, bindings[1].HideLoginURL) require.Empty(t, bindings[1].OIDC.ClientID) require.False(t, bindings[1].Security.Enabled) + require.Len(t, bindings[1].ExtraCSS, 0) require.Equal(t, 9000, bindings[2].Port) require.Equal(t, "127.0.1.1", bindings[2].Address) require.True(t, bindings[2].EnableHTTPS) @@ -978,6 +986,9 @@ func TestHTTPDBindingsFromEnv(t *testing.T) { require.Equal(t, "fullscreen=(), geolocation=()", bindings[2].Security.PermissionsPolicy) require.Equal(t, "same-origin", bindings[2].Security.CrossOriginOpenerPolicy) require.Equal(t, `max-age=86400, enforce, report-uri="https://foo.example/report"`, bindings[2].Security.ExpectCTHeader) + require.Len(t, bindings[2].ExtraCSS, 2) + require.Equal(t, "path1", bindings[2].ExtraCSS[0].Path) + require.Equal(t, "path2", bindings[2].ExtraCSS[1].Path) } func TestHTTPClientCertificatesFromEnv(t *testing.T) { diff --git a/dataprovider/dataprovider.go b/dataprovider/dataprovider.go index 3474fe92..97c3a673 100644 --- a/dataprovider/dataprovider.go +++ b/dataprovider/dataprovider.go @@ -2619,7 +2619,7 @@ func sendKeyboardAuthHTTPReq(url string, request *plugin.KeyboardAuthRequest) (* } func doBuiltinKeyboardInteractiveAuth(user *User, client ssh.KeyboardInteractiveChallenge, ip, protocol string) (int, error) { - answers, err := client(user.Username, "", []string{"Password: "}, []bool{false}) + answers, err := client("", "", []string{"Password: "}, []bool{false}) if err != nil { return 0, err } @@ -2639,7 +2639,7 @@ func doBuiltinKeyboardInteractiveAuth(user *User, client ssh.KeyboardInteractive user.Username, protocol, err) return 0, err } - answers, err = client(user.Username, "", []string{"Authentication code: "}, []bool{false}) + answers, err = client("", "", []string{"Authentication code: "}, []bool{false}) if err != nil { return 0, err } @@ -2742,7 +2742,7 @@ func getKeyboardInteractiveAnswers(client ssh.KeyboardInteractiveChallenge, resp user *User, ip, protocol string, ) ([]string, error) { questions := response.Questions - answers, err := client(user.Username, response.Instruction, questions, response.Echos) + answers, err := client("", response.Instruction, questions, response.Echos) if err != nil { providerLog(logger.LevelInfo, "error getting interactive auth client response: %v", err) return answers, err diff --git a/docs/full-configuration.md b/docs/full-configuration.md index 0398b40e..6747c506 100644 --- a/docs/full-configuration.md +++ b/docs/full-configuration.md @@ -263,6 +263,8 @@ The configuration file contains the following sections: - `permissions_policy`, string. Allows to set the `Permissions-Policy` header value. Default: blank. - `cross_origin_opener_policy`, string. Allows to set the `Cross-Origin-Opener-Policy` header value. Default: blank. - `expect_ct_header`, string. Allows to set the `Expect-CT` header value. Default: blank. + - `extra_css`, list of structs. Defines additional CSS files. Each struct has the following fields: + - `path`, string. Path to the CSS file relative to `static_files_path`. For example, if you create a directory named `extra_css` inside the static dir and put the `my.css` file in it, you must set `/extra_css/my.css` as path. - `templates_path`, string. Path to the HTML web templates. This can be an absolute path or a path relative to the config dir - `static_files_path`, string. Path to the static files for the web interface. This can be an absolute path or a path relative to the config dir. If both `templates_path` and `static_files_path` are empty the built-in web interface will be disabled - `openapi_path`, string. Path to the directory that contains the OpenAPI schema and the default renderer. This can be an absolute path or a path relative to the config dir. If empty the OpenAPI schema and the renderer will not be served regardless of the `render_openapi` directive diff --git a/go.mod b/go.mod index ad6cf8c8..6d385fe2 100644 --- a/go.mod +++ b/go.mod @@ -64,7 +64,7 @@ require ( golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd golang.org/x/net v0.0.0-20220225172249-27dd8689420f golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a - golang.org/x/sys v0.0.0-20220318055525-2edf467146b5 + golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8 golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 google.golang.org/api v0.73.0 gopkg.in/natefinch/lumberjack.v2 v2.0.0 diff --git a/go.sum b/go.sum index 0274e992..3066e05c 100644 --- a/go.sum +++ b/go.sum @@ -907,8 +907,8 @@ golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220318055525-2edf467146b5 h1:saXMvIOKvRFwbOMicHXr0B1uwoxq9dGmLe5ExMES6c4= -golang.org/x/sys v0.0.0-20220318055525-2edf467146b5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8 h1:OH54vjqzRWmbJ62fjuhxy7AxFFgoHN0/DPc/UrL8cAs= +golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= diff --git a/httpd/api_shares.go b/httpd/api_shares.go index 83dc78bf..7b8b26bb 100644 --- a/httpd/api_shares.go +++ b/httpd/api_shares.go @@ -151,9 +151,9 @@ func deleteShare(w http.ResponseWriter, r *http.Request) { sendAPIResponse(w, r, err, "Share deleted", http.StatusOK) } -func readBrowsableShareContents(w http.ResponseWriter, r *http.Request) { +func (s *httpdServer) readBrowsableShareContents(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) - share, connection, err := checkPublicShare(w, r, dataprovider.ShareScopeRead, false) + share, connection, err := s.checkPublicShare(w, r, dataprovider.ShareScopeRead, false) if err != nil { return } @@ -178,9 +178,9 @@ func readBrowsableShareContents(w http.ResponseWriter, r *http.Request) { renderAPIDirContents(w, r, contents, true) } -func downloadBrowsableSharedFile(w http.ResponseWriter, r *http.Request) { +func (s *httpdServer) downloadBrowsableSharedFile(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) - share, connection, err := checkPublicShare(w, r, dataprovider.ShareScopeRead, false) + share, connection, err := s.checkPublicShare(w, r, dataprovider.ShareScopeRead, false) if err != nil { return } @@ -224,9 +224,9 @@ func downloadBrowsableSharedFile(w http.ResponseWriter, r *http.Request) { } } -func downloadFromShare(w http.ResponseWriter, r *http.Request) { +func (s *httpdServer) downloadFromShare(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) - share, connection, err := checkPublicShare(w, r, dataprovider.ShareScopeRead, false) + share, connection, err := s.checkPublicShare(w, r, dataprovider.ShareScopeRead, false) if err != nil { return } @@ -273,12 +273,12 @@ func downloadFromShare(w http.ResponseWriter, r *http.Request) { } } -func uploadFileToShare(w http.ResponseWriter, r *http.Request) { +func (s *httpdServer) uploadFileToShare(w http.ResponseWriter, r *http.Request) { if maxUploadFileSize > 0 { r.Body = http.MaxBytesReader(w, r.Body, maxUploadFileSize) } name := getURLParam(r, "name") - share, connection, err := checkPublicShare(w, r, dataprovider.ShareScopeWrite, false) + share, connection, err := s.checkPublicShare(w, r, dataprovider.ShareScopeWrite, false) if err != nil { return } @@ -296,11 +296,11 @@ func uploadFileToShare(w http.ResponseWriter, r *http.Request) { } } -func uploadFilesToShare(w http.ResponseWriter, r *http.Request) { +func (s *httpdServer) uploadFilesToShare(w http.ResponseWriter, r *http.Request) { if maxUploadFileSize > 0 { r.Body = http.MaxBytesReader(w, r.Body, maxUploadFileSize) } - share, connection, err := checkPublicShare(w, r, dataprovider.ShareScopeWrite, false) + share, connection, err := s.checkPublicShare(w, r, dataprovider.ShareScopeWrite, false) if err != nil { return } @@ -346,12 +346,12 @@ func uploadFilesToShare(w http.ResponseWriter, r *http.Request) { } } -func checkPublicShare(w http.ResponseWriter, r *http.Request, shareShope dataprovider.ShareScope, +func (s *httpdServer) checkPublicShare(w http.ResponseWriter, r *http.Request, shareShope dataprovider.ShareScope, isWebClient bool, ) (dataprovider.Share, *Connection, error) { renderError := func(err error, message string, statusCode int) { if isWebClient { - renderClientMessagePage(w, r, "Unable to access the share", message, statusCode, err, "") + s.renderClientMessagePage(w, r, "Unable to access the share", message, statusCode, err, "") } else { sendAPIResponse(w, r, err, message, statusCode) } diff --git a/httpd/httpd.go b/httpd/httpd.go index 4ef6e6c7..1df3f809 100644 --- a/httpd/httpd.go +++ b/httpd/httpd.go @@ -298,6 +298,14 @@ func (s *SecurityConf) getHTTPSProxyHeaders() map[string]string { return headers } +// CustomCSS defines the configuration for custom CSS +type CustomCSS struct { + // Path to the CSS file relative to "static_files_path". + // For example, if you create a directory named "extra_css" inside the static dir + // and put the "my.css" file in it, you must set "/extra_css/my.css" as path. + Path string `json:"path" mapstructure:"path"` +} + // WebClientIntegration defines the configuration for an external Web Client integration type WebClientIntegration struct { // Files with these extensions can be sent to the configured URL @@ -354,7 +362,9 @@ type Binding struct { // Defining an OIDC configuration the web admin and web client UI will use OpenID to authenticate users. OIDC OIDC `json:"oidc" mapstructure:"oidc"` // Security defines security headers to add to HTTP responses and allows to restrict allowed hosts - Security SecurityConf `json:"security" mapstructure:"security"` + Security SecurityConf `json:"security" mapstructure:"security"` + // Additional CSS + ExtraCSS []CustomCSS `json:"extra_css" mapstructure:"extra_css"` allowHeadersFrom []func(net.IP) bool } @@ -368,6 +378,16 @@ func (b *Binding) checkWebClientIntegrations() { b.WebClientIntegrations = integrations } +func (b *Binding) checkExtraCSS() { + var extraCSS []CustomCSS + for _, css := range b.ExtraCSS { + extraCSS = append(extraCSS, CustomCSS{ + Path: path.Join("/", css.Path), + }) + } + b.ExtraCSS = extraCSS +} + func (b *Binding) parseAllowedProxy() error { allowedFuncs, err := util.ParseAllowedIPAndRanges(b.ProxyAllowed) if err != nil { @@ -606,6 +626,7 @@ func (c *Conf) Initialize(configDir string) error { return err } binding.checkWebClientIntegrations() + binding.checkExtraCSS() binding.Security.updateProxyHeaders() go func(b Binding) { diff --git a/httpd/internal_test.go b/httpd/internal_test.go index 96ceaffa..671c05e9 100644 --- a/httpd/internal_test.go +++ b/httpd/internal_test.go @@ -301,6 +301,23 @@ func TestShouldBind(t *testing.T) { } } +func TestExtraCSSValidation(t *testing.T) { + b := Binding{ + ExtraCSS: []CustomCSS{ + { + Path: "path1", + }, + { + Path: "../path2", + }, + }, + } + b.checkExtraCSS() + require.Len(t, b.ExtraCSS, 2) + assert.Equal(t, "/path1", b.ExtraCSS[0].Path) + assert.Equal(t, "/path2", b.ExtraCSS[1].Path) +} + func TestRedactedConf(t *testing.T) { c := Conf{ SigningPassphrase: "passphrase", @@ -356,6 +373,8 @@ func TestGCSWebInvalidFormFile(t *testing.T) { } func TestInvalidToken(t *testing.T) { + server := httpdServer{} + server.initializeRouter() admin := dataprovider.Admin{ Username: "admin", } @@ -510,27 +529,27 @@ func TestInvalidToken(t *testing.T) { assert.Contains(t, rr.Body.String(), "Invalid token claims") rr = httptest.NewRecorder() - handleWebRestore(rr, req) + server.handleWebRestore(rr, req) assert.Equal(t, http.StatusBadRequest, rr.Code) assert.Contains(t, rr.Body.String(), "invalid token claims") rr = httptest.NewRecorder() - handleWebAddUserPost(rr, req) + server.handleWebAddUserPost(rr, req) assert.Equal(t, http.StatusBadRequest, rr.Code) assert.Contains(t, rr.Body.String(), "invalid token claims") rr = httptest.NewRecorder() - handleWebUpdateUserPost(rr, req) + server.handleWebUpdateUserPost(rr, req) assert.Equal(t, http.StatusBadRequest, rr.Code) assert.Contains(t, rr.Body.String(), "invalid token claims") rr = httptest.NewRecorder() - handleWebTemplateFolderPost(rr, req) + server.handleWebTemplateFolderPost(rr, req) assert.Equal(t, http.StatusBadRequest, rr.Code) assert.Contains(t, rr.Body.String(), "invalid token claims") rr = httptest.NewRecorder() - handleWebTemplateUserPost(rr, req) + server.handleWebTemplateUserPost(rr, req) assert.Equal(t, http.StatusBadRequest, rr.Code) assert.Contains(t, rr.Body.String(), "invalid token claims") @@ -545,7 +564,7 @@ func TestInvalidToken(t *testing.T) { assert.Contains(t, rr.Body.String(), "Invalid token claims") rr = httptest.NewRecorder() - handleWebUpdateFolderPost(rr, req) + server.handleWebUpdateFolderPost(rr, req) assert.Equal(t, http.StatusBadRequest, rr.Code) assert.Contains(t, rr.Body.String(), "invalid token claims") @@ -575,12 +594,10 @@ func TestInvalidToken(t *testing.T) { assert.Contains(t, rr.Body.String(), "Invalid token claims") rr = httptest.NewRecorder() - handleWebAddAdminPost(rr, req) + server.handleWebAddAdminPost(rr, req) assert.Equal(t, http.StatusBadRequest, rr.Code) assert.Contains(t, rr.Body.String(), "invalid token claims") - server := httpdServer{} - server.initializeRouter() rr = httptest.NewRecorder() server.handleWebClientTwoFactorRecoveryPost(rr, req) assert.Equal(t, http.StatusNotFound, rr.Code) @@ -624,7 +641,7 @@ func TestUpdateWebAdminInvalidClaims(t *testing.T) { req = req.WithContext(context.WithValue(req.Context(), chi.RouteCtxKey, rctx)) req.Header.Set("Content-Type", "application/x-www-form-urlencoded") req.Header.Set("Cookie", fmt.Sprintf("jwt=%v", token["access_token"])) - handleWebUpdateAdminPost(rr, req) + server.handleWebUpdateAdminPost(rr, req) assert.Equal(t, http.StatusOK, rr.Code) assert.Contains(t, rr.Body.String(), "Invalid token claims") } @@ -796,13 +813,13 @@ func TestCreateTokenError(t *testing.T) { req, _ = http.NewRequest(http.MethodPost, webClientProfilePath+"?a=a%C3%AO%GB", bytes.NewBuffer([]byte(form.Encode()))) req.Header.Set("Content-Type", "application/x-www-form-urlencoded") rr = httptest.NewRecorder() - handleWebClientProfilePost(rr, req) + server.handleWebClientProfilePost(rr, req) assert.Equal(t, http.StatusInternalServerError, rr.Code, rr.Body.String()) req, _ = http.NewRequest(http.MethodPost, webAdminProfilePath+"?a=a%C3%AO%GB", bytes.NewBuffer([]byte(form.Encode()))) req.Header.Set("Content-Type", "application/x-www-form-urlencoded") rr = httptest.NewRecorder() - handleWebAdminProfilePost(rr, req) + server.handleWebAdminProfilePost(rr, req) assert.Equal(t, http.StatusInternalServerError, rr.Code, rr.Body.String()) req, _ = http.NewRequest(http.MethodPost, webAdminTwoFactorPath+"?a=a%C3%AO%GC", bytes.NewBuffer([]byte(form.Encode()))) @@ -836,14 +853,14 @@ func TestCreateTokenError(t *testing.T) { req, _ = http.NewRequest(http.MethodPost, webAdminForgotPwdPath+"?a=a%C3%A1%GD", bytes.NewBuffer([]byte(form.Encode()))) req.Header.Set("Content-Type", "application/x-www-form-urlencoded") rr = httptest.NewRecorder() - handleWebAdminForgotPwdPost(rr, req) + server.handleWebAdminForgotPwdPost(rr, req) assert.Equal(t, http.StatusOK, rr.Code, rr.Body.String()) assert.Contains(t, rr.Body.String(), "invalid URL escape") req, _ = http.NewRequest(http.MethodPost, webClientForgotPwdPath+"?a=a%C2%A1%GD", bytes.NewBuffer([]byte(form.Encode()))) req.Header.Set("Content-Type", "application/x-www-form-urlencoded") rr = httptest.NewRecorder() - handleWebClientForgotPwdPost(rr, req) + server.handleWebClientForgotPwdPost(rr, req) assert.Equal(t, http.StatusOK, rr.Code, rr.Body.String()) assert.Contains(t, rr.Body.String(), "invalid URL escape") @@ -939,13 +956,17 @@ func TestJWTTokenValidation(t *testing.T) { token, _, err := tokenAuth.Encode(claims) assert.NoError(t, err) - r := GetHTTPRouter(Binding{ - Address: "", - Port: 8080, - EnableWebAdmin: true, - EnableWebClient: true, - RenderOpenAPI: true, - }) + server := httpdServer{ + binding: Binding{ + Address: "", + Port: 8080, + EnableWebAdmin: true, + EnableWebClient: true, + RenderOpenAPI: true, + }, + } + server.initializeRouter() + r := server.router fn := jwtAuthenticatorAPI(r) rr := httptest.NewRecorder() req, _ := http.NewRequest(http.MethodGet, userPath, nil) @@ -970,7 +991,7 @@ func TestJWTTokenValidation(t *testing.T) { assert.Equal(t, webClientLoginPath, rr.Header().Get("Location")) errTest := errors.New("test error") - permFn := checkPerm(dataprovider.PermAdminAny) + permFn := server.checkPerm(dataprovider.PermAdminAny) fn = permFn(r) rr = httptest.NewRecorder() req, _ = http.NewRequest(http.MethodGet, userPath, nil) @@ -978,7 +999,7 @@ func TestJWTTokenValidation(t *testing.T) { fn.ServeHTTP(rr, req.WithContext(ctx)) assert.Equal(t, http.StatusBadRequest, rr.Code) - permFn = checkPerm(dataprovider.PermAdminAny) + permFn = server.checkPerm(dataprovider.PermAdminAny) fn = permFn(r) rr = httptest.NewRecorder() req, _ = http.NewRequest(http.MethodGet, webUserPath, nil) @@ -987,7 +1008,7 @@ func TestJWTTokenValidation(t *testing.T) { fn.ServeHTTP(rr, req.WithContext(ctx)) assert.Equal(t, http.StatusBadRequest, rr.Code) - permClientFn := checkHTTPUserPerm(sdk.WebClientPubKeyChangeDisabled) + permClientFn := server.checkHTTPUserPerm(sdk.WebClientPubKeyChangeDisabled) fn = permClientFn(r) rr = httptest.NewRecorder() req, _ = http.NewRequest(http.MethodPost, webClientProfilePath, nil) @@ -1003,7 +1024,7 @@ func TestJWTTokenValidation(t *testing.T) { fn.ServeHTTP(rr, req.WithContext(ctx)) assert.Equal(t, http.StatusBadRequest, rr.Code) - fn = checkSecondFactorRequirement(r) + fn = server.checkSecondFactorRequirement(r) rr = httptest.NewRecorder() req, _ = http.NewRequest(http.MethodPost, webClientProfilePath, nil) req.RequestURI = webClientProfilePath @@ -1989,42 +2010,42 @@ func TestWebUserInvalidClaims(t *testing.T) { rr = httptest.NewRecorder() req, _ = http.NewRequest(http.MethodGet, webClientDownloadZipPath, nil) req.Header.Set("Cookie", fmt.Sprintf("jwt=%v", token["access_token"])) - handleWebClientDownloadZip(rr, req) + server.handleWebClientDownloadZip(rr, req) assert.Equal(t, http.StatusForbidden, rr.Code) assert.Contains(t, rr.Body.String(), "Invalid token claims") rr = httptest.NewRecorder() req, _ = http.NewRequest(http.MethodGet, webClientEditFilePath, nil) req.Header.Set("Cookie", fmt.Sprintf("jwt=%v", token["access_token"])) - handleClientEditFile(rr, req) + server.handleClientEditFile(rr, req) assert.Equal(t, http.StatusForbidden, rr.Code) assert.Contains(t, rr.Body.String(), "Invalid token claims") rr = httptest.NewRecorder() req, _ = http.NewRequest(http.MethodGet, webClientSharePath, nil) req.Header.Set("Cookie", fmt.Sprintf("jwt=%v", token["access_token"])) - handleClientUpdateShareGet(rr, req) + server.handleClientUpdateShareGet(rr, req) assert.Equal(t, http.StatusForbidden, rr.Code) assert.Contains(t, rr.Body.String(), "Invalid token claims") rr = httptest.NewRecorder() req, _ = http.NewRequest(http.MethodPost, webClientSharePath, nil) req.Header.Set("Cookie", fmt.Sprintf("jwt=%v", token["access_token"])) - handleClientAddSharePost(rr, req) + server.handleClientAddSharePost(rr, req) assert.Equal(t, http.StatusForbidden, rr.Code) assert.Contains(t, rr.Body.String(), "Invalid token claims") rr = httptest.NewRecorder() req, _ = http.NewRequest(http.MethodPost, webClientSharePath+"/id", nil) req.Header.Set("Cookie", fmt.Sprintf("jwt=%v", token["access_token"])) - handleClientUpdateSharePost(rr, req) + server.handleClientUpdateSharePost(rr, req) assert.Equal(t, http.StatusForbidden, rr.Code) assert.Contains(t, rr.Body.String(), "Invalid token claims") rr = httptest.NewRecorder() req, _ = http.NewRequest(http.MethodGet, webClientSharesPath, nil) req.Header.Set("Cookie", fmt.Sprintf("jwt=%v", token["access_token"])) - handleClientGetShares(rr, req) + server.handleClientGetShares(rr, req) assert.Equal(t, http.StatusForbidden, rr.Code) assert.Contains(t, rr.Body.String(), "Invalid token claims") } @@ -2053,7 +2074,7 @@ func TestInvalidClaims(t *testing.T) { req, _ := http.NewRequest(http.MethodPost, webClientProfilePath, bytes.NewBuffer([]byte(form.Encode()))) req.Header.Set("Content-Type", "application/x-www-form-urlencoded") req.Header.Set("Cookie", fmt.Sprintf("jwt=%v", token["access_token"])) - handleWebClientProfilePost(rr, req) + server.handleWebClientProfilePost(rr, req) assert.Equal(t, http.StatusForbidden, rr.Code) admin := dataprovider.Admin{ @@ -2073,7 +2094,7 @@ func TestInvalidClaims(t *testing.T) { req, _ = http.NewRequest(http.MethodPost, webAdminProfilePath, bytes.NewBuffer([]byte(form.Encode()))) req.Header.Set("Content-Type", "application/x-www-form-urlencoded") req.Header.Set("Cookie", fmt.Sprintf("jwt=%v", token["access_token"])) - handleWebAdminProfilePost(rr, req) + server.handleWebAdminProfilePost(rr, req) assert.Equal(t, http.StatusForbidden, rr.Code) } diff --git a/httpd/middleware.go b/httpd/middleware.go index d6b024eb..e011e757 100644 --- a/httpd/middleware.go +++ b/httpd/middleware.go @@ -87,13 +87,13 @@ func validateJWTToken(w http.ResponseWriter, r *http.Request, audience tokenAudi return nil } -func validateJWTPartialToken(w http.ResponseWriter, r *http.Request, audience tokenAudience) error { +func (s *httpdServer) validateJWTPartialToken(w http.ResponseWriter, r *http.Request, audience tokenAudience) error { token, _, err := jwtauth.FromContext(r.Context()) var notFoundFunc func(w http.ResponseWriter, r *http.Request, err error) if audience == tokenAudienceWebAdminPartial { - notFoundFunc = renderNotFoundPage + notFoundFunc = s.renderNotFoundPage } else { - notFoundFunc = renderClientNotFoundPage + notFoundFunc = s.renderClientNotFoundPage } if err != nil || token == nil || jwt.Validate(token) != nil { notFoundFunc(w, r, nil) @@ -112,10 +112,10 @@ func validateJWTPartialToken(w http.ResponseWriter, r *http.Request, audience to return nil } -func jwtAuthenticatorPartial(audience tokenAudience) func(next http.Handler) http.Handler { +func (s *httpdServer) jwtAuthenticatorPartial(audience tokenAudience) func(next http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if err := validateJWTPartialToken(w, r, audience); err != nil { + if err := s.validateJWTPartialToken(w, r, audience); err != nil { return } @@ -169,13 +169,13 @@ func jwtAuthenticatorWebClient(next http.Handler) http.Handler { }) } -func checkHTTPUserPerm(perm string) func(next http.Handler) http.Handler { +func (s *httpdServer) checkHTTPUserPerm(perm string) func(next http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { _, claims, err := jwtauth.FromContext(r.Context()) if err != nil { if isWebRequest(r) { - renderClientBadRequestPage(w, r, err) + s.renderClientBadRequestPage(w, r, err) } else { sendAPIResponse(w, r, err, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) } @@ -186,7 +186,7 @@ func checkHTTPUserPerm(perm string) func(next http.Handler) http.Handler { // for web client perms are negated and not granted if tokenClaims.hasPerm(perm) { if isWebRequest(r) { - renderClientForbiddenPage(w, r, "You don't have permission for this action") + s.renderClientForbiddenPage(w, r, "You don't have permission for this action") } else { sendAPIResponse(w, r, nil, http.StatusText(http.StatusForbidden), http.StatusForbidden) } @@ -198,12 +198,12 @@ func checkHTTPUserPerm(perm string) func(next http.Handler) http.Handler { } } -func checkSecondFactorRequirement(next http.Handler) http.Handler { +func (s *httpdServer) checkSecondFactorRequirement(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { _, claims, err := jwtauth.FromContext(r.Context()) if err != nil { if isWebRequest(r) { - renderClientBadRequestPage(w, r, err) + s.renderClientBadRequestPage(w, r, err) } else { sendAPIResponse(w, r, err, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) } @@ -215,7 +215,7 @@ func checkSecondFactorRequirement(next http.Handler) http.Handler { message := fmt.Sprintf("Two-factor authentication requirements not met, please configure two-factor authentication for the following protocols: %v", strings.Join(tokenClaims.RequiredTwoFactorProtocols, ", ")) if isWebRequest(r) { - renderClientForbiddenPage(w, r, message) + s.renderClientForbiddenPage(w, r, message) } else { sendAPIResponse(w, r, nil, message, http.StatusForbidden) } @@ -226,13 +226,13 @@ func checkSecondFactorRequirement(next http.Handler) http.Handler { }) } -func requireBuiltinLogin(next http.Handler) http.Handler { +func (s *httpdServer) requireBuiltinLogin(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if isLoggedInWithOIDC(r) { if isWebClientRequest(r) { - renderClientForbiddenPage(w, r, "This feature is not available if you are logged in with OpenID") + s.renderClientForbiddenPage(w, r, "This feature is not available if you are logged in with OpenID") } else { - renderForbiddenPage(w, r, "This feature is not available if you are logged in with OpenID") + s.renderForbiddenPage(w, r, "This feature is not available if you are logged in with OpenID") } return } @@ -240,13 +240,13 @@ func requireBuiltinLogin(next http.Handler) http.Handler { }) } -func checkPerm(perm string) func(next http.Handler) http.Handler { +func (s *httpdServer) checkPerm(perm string) func(next http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { _, claims, err := jwtauth.FromContext(r.Context()) if err != nil { if isWebRequest(r) { - renderBadRequestPage(w, r, err) + s.renderBadRequestPage(w, r, err) } else { sendAPIResponse(w, r, err, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) } @@ -257,7 +257,7 @@ func checkPerm(perm string) func(next http.Handler) http.Handler { if !tokenClaims.hasPerm(perm) { if isWebRequest(r) { - renderForbiddenPage(w, r, "You don't have permission for this action") + s.renderForbiddenPage(w, r, "You don't have permission for this action") } else { sendAPIResponse(w, r, nil, http.StatusText(http.StatusForbidden), http.StatusForbidden) } diff --git a/httpd/oidc.go b/httpd/oidc.go index 9be4c2ef..99e09850 100644 --- a/httpd/oidc.go +++ b/httpd/oidc.go @@ -534,7 +534,7 @@ func (s *httpdServer) handleOIDCRedirect(w http.ResponseWriter, r *http.Request) authReq, err := oidcMgr.getPendingAuth(state) if err != nil { logger.Debug(logSender, "", "oidc authentication state did not match") - renderClientMessagePage(w, r, "Invalid authentication request", "Authentication state did not match", + s.renderClientMessagePage(w, r, "Invalid authentication request", "Authentication state did not match", http.StatusBadRequest, nil, "") return } diff --git a/httpd/server.go b/httpd/server.go index 7e41382d..35c93f65 100644 --- a/httpd/server.go +++ b/httpd/server.go @@ -140,6 +140,7 @@ func (s *httpdServer) renderClientLoginPage(w http.ResponseWriter, error string) Error: error, CSRFToken: createCSRFToken(), StaticURL: webStaticFilesPath, + ExtraCSS: s.binding.ExtraCSS, } if s.binding.showAdminLoginURL() { data.AltLoginURL = webAdminLoginPath @@ -166,17 +167,17 @@ func (s *httpdServer) handleWebClientChangePwdPost(w http.ResponseWriter, r *htt r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) err := r.ParseForm() if err != nil { - renderClientChangePasswordPage(w, r, err.Error()) + s.renderClientChangePasswordPage(w, r, err.Error()) return } if err := verifyCSRFToken(r.Form.Get(csrfFormToken)); err != nil { - renderClientForbiddenPage(w, r, err.Error()) + s.renderClientForbiddenPage(w, r, err.Error()) return } err = doChangeUserPassword(r, r.Form.Get("current_password"), r.Form.Get("new_password1"), r.Form.Get("new_password2")) if err != nil { - renderClientChangePasswordPage(w, r, err.Error()) + s.renderClientChangePasswordPage(w, r, err.Error()) return } s.handleWebClientLogout(w, r) @@ -248,25 +249,25 @@ func (s *httpdServer) handleWebClientPasswordResetPost(w http.ResponseWriter, r r.Body = http.MaxBytesReader(w, r.Body, maxLoginBodySize) err := r.ParseForm() if err != nil { - renderClientResetPwdPage(w, err.Error()) + s.renderClientResetPwdPage(w, err.Error()) return } if err := verifyCSRFToken(r.Form.Get(csrfFormToken)); err != nil { - renderClientForbiddenPage(w, r, err.Error()) + s.renderClientForbiddenPage(w, r, err.Error()) return } _, user, err := handleResetPassword(r, r.Form.Get("code"), r.Form.Get("password"), false) if err != nil { if e, ok := err.(*util.ValidationError); ok { - renderClientResetPwdPage(w, e.GetErrorString()) + s.renderClientResetPwdPage(w, e.GetErrorString()) return } - renderClientResetPwdPage(w, err.Error()) + s.renderClientResetPwdPage(w, err.Error()) return } connectionID := fmt.Sprintf("%v_%v", getProtocolFromRequest(r), xid.New().String()) if err := checkHTTPClientUser(user, r, connectionID); err != nil { - renderClientResetPwdPage(w, fmt.Sprintf("Password reset successfully but unable to login: %v", err.Error())) + s.renderClientResetPwdPage(w, fmt.Sprintf("Password reset successfully but unable to login: %v", err.Error())) return } @@ -274,210 +275,210 @@ func (s *httpdServer) handleWebClientPasswordResetPost(w http.ResponseWriter, r err = user.CheckFsRoot(connectionID) if err != nil { logger.Warn(logSender, connectionID, "unable to check fs root: %v", err) - renderClientResetPwdPage(w, fmt.Sprintf("Password reset successfully but unable to login: %v", err.Error())) + s.renderClientResetPwdPage(w, fmt.Sprintf("Password reset successfully but unable to login: %v", err.Error())) return } ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr) - s.loginUser(w, r, user, connectionID, ipAddr, false, renderClientResetPwdPage) + s.loginUser(w, r, user, connectionID, ipAddr, false, s.renderClientResetPwdPage) } func (s *httpdServer) handleWebClientTwoFactorRecoveryPost(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, maxLoginBodySize) claims, err := getTokenClaims(r) if err != nil { - renderNotFoundPage(w, r, nil) + s.renderNotFoundPage(w, r, nil) return } if err := r.ParseForm(); err != nil { - renderClientTwoFactorRecoveryPage(w, err.Error()) + s.renderClientTwoFactorRecoveryPage(w, err.Error()) return } username := claims.Username recoveryCode := r.Form.Get("recovery_code") if username == "" || recoveryCode == "" { - renderClientTwoFactorRecoveryPage(w, "Invalid credentials") + s.renderClientTwoFactorRecoveryPage(w, "Invalid credentials") return } if err := verifyCSRFToken(r.Form.Get(csrfFormToken)); err != nil { - renderClientTwoFactorRecoveryPage(w, err.Error()) + s.renderClientTwoFactorRecoveryPage(w, err.Error()) return } user, err := dataprovider.UserExists(username) if err != nil { - renderClientTwoFactorRecoveryPage(w, "Invalid credentials") + s.renderClientTwoFactorRecoveryPage(w, "Invalid credentials") return } if !user.Filters.TOTPConfig.Enabled || !util.IsStringInSlice(common.ProtocolHTTP, user.Filters.TOTPConfig.Protocols) { - renderClientTwoFactorPage(w, "Two factory authentication is not enabled") + s.renderClientTwoFactorPage(w, "Two factory authentication is not enabled") return } for idx, code := range user.Filters.RecoveryCodes { if err := code.Secret.Decrypt(); err != nil { - renderClientInternalServerErrorPage(w, r, fmt.Errorf("unable to decrypt recovery code: %w", err)) + s.renderClientInternalServerErrorPage(w, r, fmt.Errorf("unable to decrypt recovery code: %w", err)) return } if code.Secret.GetPayload() == recoveryCode { if code.Used { - renderClientTwoFactorRecoveryPage(w, "This recovery code was already used") + s.renderClientTwoFactorRecoveryPage(w, "This recovery code was already used") return } user.Filters.RecoveryCodes[idx].Used = true err = dataprovider.UpdateUser(&user, dataprovider.ActionExecutorSelf, util.GetIPFromRemoteAddress(r.RemoteAddr)) if err != nil { logger.Warn(logSender, "", "unable to set the recovery code %#v as used: %v", recoveryCode, err) - renderClientInternalServerErrorPage(w, r, errors.New("unable to set the recovery code as used")) + s.renderClientInternalServerErrorPage(w, r, errors.New("unable to set the recovery code as used")) return } connectionID := fmt.Sprintf("%v_%v", getProtocolFromRequest(r), xid.New().String()) s.loginUser(w, r, &user, connectionID, util.GetIPFromRemoteAddress(r.RemoteAddr), true, - renderClientTwoFactorRecoveryPage) + s.renderClientTwoFactorRecoveryPage) return } } - renderClientTwoFactorRecoveryPage(w, "Invalid recovery code") + s.renderClientTwoFactorRecoveryPage(w, "Invalid recovery code") } func (s *httpdServer) handleWebClientTwoFactorPost(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, maxLoginBodySize) claims, err := getTokenClaims(r) if err != nil { - renderNotFoundPage(w, r, nil) + s.renderNotFoundPage(w, r, nil) return } if err := r.ParseForm(); err != nil { - renderClientTwoFactorPage(w, err.Error()) + s.renderClientTwoFactorPage(w, err.Error()) return } username := claims.Username passcode := r.Form.Get("passcode") if username == "" || passcode == "" { - renderClientTwoFactorPage(w, "Invalid credentials") + s.renderClientTwoFactorPage(w, "Invalid credentials") return } if err := verifyCSRFToken(r.Form.Get(csrfFormToken)); err != nil { - renderClientTwoFactorPage(w, err.Error()) + s.renderClientTwoFactorPage(w, err.Error()) return } user, err := dataprovider.UserExists(username) if err != nil { - renderClientTwoFactorPage(w, "Invalid credentials") + s.renderClientTwoFactorPage(w, "Invalid credentials") return } if !user.Filters.TOTPConfig.Enabled || !util.IsStringInSlice(common.ProtocolHTTP, user.Filters.TOTPConfig.Protocols) { - renderClientTwoFactorPage(w, "Two factory authentication is not enabled") + s.renderClientTwoFactorPage(w, "Two factory authentication is not enabled") return } err = user.Filters.TOTPConfig.Secret.Decrypt() if err != nil { - renderClientInternalServerErrorPage(w, r, err) + s.renderClientInternalServerErrorPage(w, r, err) return } match, err := mfa.ValidateTOTPPasscode(user.Filters.TOTPConfig.ConfigName, passcode, user.Filters.TOTPConfig.Secret.GetPayload()) if !match || err != nil { - renderClientTwoFactorPage(w, "Invalid authentication code") + s.renderClientTwoFactorPage(w, "Invalid authentication code") return } connectionID := fmt.Sprintf("%v_%v", getProtocolFromRequest(r), xid.New().String()) - s.loginUser(w, r, &user, connectionID, util.GetIPFromRemoteAddress(r.RemoteAddr), true, renderClientTwoFactorPage) + s.loginUser(w, r, &user, connectionID, util.GetIPFromRemoteAddress(r.RemoteAddr), true, s.renderClientTwoFactorPage) } func (s *httpdServer) handleWebAdminTwoFactorRecoveryPost(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, maxLoginBodySize) claims, err := getTokenClaims(r) if err != nil { - renderNotFoundPage(w, r, nil) + s.renderNotFoundPage(w, r, nil) return } if err := r.ParseForm(); err != nil { - renderTwoFactorRecoveryPage(w, err.Error()) + s.renderTwoFactorRecoveryPage(w, err.Error()) return } username := claims.Username recoveryCode := r.Form.Get("recovery_code") if username == "" || recoveryCode == "" { - renderTwoFactorRecoveryPage(w, "Invalid credentials") + s.renderTwoFactorRecoveryPage(w, "Invalid credentials") return } if err := verifyCSRFToken(r.Form.Get(csrfFormToken)); err != nil { - renderTwoFactorRecoveryPage(w, err.Error()) + s.renderTwoFactorRecoveryPage(w, err.Error()) return } admin, err := dataprovider.AdminExists(username) if err != nil { - renderTwoFactorRecoveryPage(w, "Invalid credentials") + s.renderTwoFactorRecoveryPage(w, "Invalid credentials") return } if !admin.Filters.TOTPConfig.Enabled { - renderTwoFactorRecoveryPage(w, "Two factory authentication is not enabled") + s.renderTwoFactorRecoveryPage(w, "Two factory authentication is not enabled") return } for idx, code := range admin.Filters.RecoveryCodes { if err := code.Secret.Decrypt(); err != nil { - renderInternalServerErrorPage(w, r, fmt.Errorf("unable to decrypt recovery code: %w", err)) + s.renderInternalServerErrorPage(w, r, fmt.Errorf("unable to decrypt recovery code: %w", err)) return } if code.Secret.GetPayload() == recoveryCode { if code.Used { - renderTwoFactorRecoveryPage(w, "This recovery code was already used") + s.renderTwoFactorRecoveryPage(w, "This recovery code was already used") return } admin.Filters.RecoveryCodes[idx].Used = true err = dataprovider.UpdateAdmin(&admin, dataprovider.ActionExecutorSelf, util.GetIPFromRemoteAddress(r.RemoteAddr)) if err != nil { logger.Warn(logSender, "", "unable to set the recovery code %#v as used: %v", recoveryCode, err) - renderInternalServerErrorPage(w, r, errors.New("unable to set the recovery code as used")) + s.renderInternalServerErrorPage(w, r, errors.New("unable to set the recovery code as used")) return } - s.loginAdmin(w, r, &admin, true, renderTwoFactorRecoveryPage) + s.loginAdmin(w, r, &admin, true, s.renderTwoFactorRecoveryPage) return } } - renderTwoFactorRecoveryPage(w, "Invalid recovery code") + s.renderTwoFactorRecoveryPage(w, "Invalid recovery code") } func (s *httpdServer) handleWebAdminTwoFactorPost(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, maxLoginBodySize) claims, err := getTokenClaims(r) if err != nil { - renderNotFoundPage(w, r, nil) + s.renderNotFoundPage(w, r, nil) return } if err := r.ParseForm(); err != nil { - renderTwoFactorPage(w, err.Error()) + s.renderTwoFactorPage(w, err.Error()) return } username := claims.Username passcode := r.Form.Get("passcode") if username == "" || passcode == "" { - renderTwoFactorPage(w, "Invalid credentials") + s.renderTwoFactorPage(w, "Invalid credentials") return } if err := verifyCSRFToken(r.Form.Get(csrfFormToken)); err != nil { - renderTwoFactorPage(w, err.Error()) + s.renderTwoFactorPage(w, err.Error()) return } admin, err := dataprovider.AdminExists(username) if err != nil { - renderTwoFactorPage(w, "Invalid credentials") + s.renderTwoFactorPage(w, "Invalid credentials") return } if !admin.Filters.TOTPConfig.Enabled { - renderTwoFactorPage(w, "Two factory authentication is not enabled") + s.renderTwoFactorPage(w, "Two factory authentication is not enabled") return } err = admin.Filters.TOTPConfig.Secret.Decrypt() if err != nil { - renderInternalServerErrorPage(w, r, err) + s.renderInternalServerErrorPage(w, r, err) return } match, err := mfa.ValidateTOTPPasscode(admin.Filters.TOTPConfig.ConfigName, passcode, admin.Filters.TOTPConfig.Secret.GetPayload()) if !match || err != nil { - renderTwoFactorPage(w, "Invalid authentication code") + s.renderTwoFactorPage(w, "Invalid authentication code") return } - s.loginAdmin(w, r, &admin, true, renderTwoFactorPage) + s.loginAdmin(w, r, &admin, true, s.renderTwoFactorPage) } func (s *httpdServer) handleWebAdminLoginPost(w http.ResponseWriter, r *http.Request) { @@ -511,6 +512,7 @@ func (s *httpdServer) renderAdminLoginPage(w http.ResponseWriter, error string) Error: error, CSRFToken: createCSRFToken(), StaticURL: webStaticFilesPath, + ExtraCSS: s.binding.ExtraCSS, } if s.binding.showClientLoginURL() { data.AltLoginURL = webClientLoginPath @@ -546,17 +548,17 @@ func (s *httpdServer) handleWebAdminChangePwdPost(w http.ResponseWriter, r *http r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) err := r.ParseForm() if err != nil { - renderChangePasswordPage(w, r, err.Error()) + s.renderChangePasswordPage(w, r, err.Error()) return } if err := verifyCSRFToken(r.Form.Get(csrfFormToken)); err != nil { - renderForbiddenPage(w, r, err.Error()) + s.renderForbiddenPage(w, r, err.Error()) return } err = doChangeAdminPassword(r, r.Form.Get("current_password"), r.Form.Get("new_password1"), r.Form.Get("new_password2")) if err != nil { - renderChangePasswordPage(w, r, err.Error()) + s.renderChangePasswordPage(w, r, err.Error()) return } s.handleWebAdminLogout(w, r) @@ -566,39 +568,39 @@ func (s *httpdServer) handleWebAdminPasswordResetPost(w http.ResponseWriter, r * r.Body = http.MaxBytesReader(w, r.Body, maxLoginBodySize) err := r.ParseForm() if err != nil { - renderResetPwdPage(w, err.Error()) + s.renderResetPwdPage(w, err.Error()) return } if err := verifyCSRFToken(r.Form.Get(csrfFormToken)); err != nil { - renderForbiddenPage(w, r, err.Error()) + s.renderForbiddenPage(w, r, err.Error()) return } admin, _, err := handleResetPassword(r, r.Form.Get("code"), r.Form.Get("password"), true) if err != nil { if e, ok := err.(*util.ValidationError); ok { - renderResetPwdPage(w, e.GetErrorString()) + s.renderResetPwdPage(w, e.GetErrorString()) return } - renderResetPwdPage(w, err.Error()) + s.renderResetPwdPage(w, err.Error()) return } - s.loginAdmin(w, r, admin, false, renderResetPwdPage) + s.loginAdmin(w, r, admin, false, s.renderResetPwdPage) } func (s *httpdServer) handleWebAdminSetupPost(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, maxLoginBodySize) if dataprovider.HasAdmin() { - renderBadRequestPage(w, r, errors.New("an admin user already exists")) + s.renderBadRequestPage(w, r, errors.New("an admin user already exists")) return } err := r.ParseForm() if err != nil { - renderAdminSetupPage(w, r, "", err.Error()) + s.renderAdminSetupPage(w, r, "", err.Error()) return } if err := verifyCSRFToken(r.Form.Get(csrfFormToken)); err != nil { - renderForbiddenPage(w, r, err.Error()) + s.renderForbiddenPage(w, r, err.Error()) return } username := r.Form.Get("username") @@ -606,19 +608,19 @@ func (s *httpdServer) handleWebAdminSetupPost(w http.ResponseWriter, r *http.Req confirmPassword := r.Form.Get("confirm_password") installCode := r.Form.Get("install_code") if installationCode != "" && installCode != installationCode { - renderAdminSetupPage(w, r, username, fmt.Sprintf("%v mismatch", installationCodeHint)) + s.renderAdminSetupPage(w, r, username, fmt.Sprintf("%v mismatch", installationCodeHint)) return } if username == "" { - renderAdminSetupPage(w, r, username, "Please set a username") + s.renderAdminSetupPage(w, r, username, "Please set a username") return } if password == "" { - renderAdminSetupPage(w, r, username, "Please set a password") + s.renderAdminSetupPage(w, r, username, "Please set a password") return } if password != confirmPassword { - renderAdminSetupPage(w, r, username, "Passwords mismatch") + s.renderAdminSetupPage(w, r, username, "Passwords mismatch") return } admin := dataprovider.Admin{ @@ -629,7 +631,7 @@ func (s *httpdServer) handleWebAdminSetupPost(w http.ResponseWriter, r *http.Req } err = dataprovider.AddAdmin(&admin, username, util.GetIPFromRemoteAddress(r.RemoteAddr)) if err != nil { - renderAdminSetupPage(w, r, username, err.Error()) + s.renderAdminSetupPage(w, r, username, err.Error()) return } s.loginAdmin(w, r, &admin, false, nil) @@ -691,7 +693,7 @@ func (s *httpdServer) loginAdmin( if err != nil { logger.Warn(logSender, "", "unable to set admin login cookie %v", err) if errorFunc == nil { - renderAdminSetupPage(w, r, admin.Username, err.Error()) + s.renderAdminSetupPage(w, r, admin.Username, err.Error()) return } errorFunc(w, err.Error()) @@ -1007,11 +1009,11 @@ func (s *httpdServer) sendTooManyRequestResponse(w http.ResponseWriter, r *http. if (s.enableWebAdmin || s.enableWebClient) && isWebRequest(r) { r = s.updateContextFromCookie(r) if s.enableWebClient && (isWebClientRequest(r) || !s.enableWebAdmin) { - renderClientMessagePage(w, r, http.StatusText(http.StatusTooManyRequests), "Rate limit exceeded", + s.renderClientMessagePage(w, r, http.StatusText(http.StatusTooManyRequests), "Rate limit exceeded", http.StatusTooManyRequests, err, "") return } - renderMessagePage(w, r, http.StatusText(http.StatusTooManyRequests), "Rate limit exceeded", http.StatusTooManyRequests, + s.renderMessagePage(w, r, http.StatusText(http.StatusTooManyRequests), "Rate limit exceeded", http.StatusTooManyRequests, err, "") return } @@ -1022,10 +1024,10 @@ func (s *httpdServer) sendForbiddenResponse(w http.ResponseWriter, r *http.Reque if (s.enableWebAdmin || s.enableWebClient) && isWebRequest(r) { r = s.updateContextFromCookie(r) if s.enableWebClient && (isWebClientRequest(r) || !s.enableWebAdmin) { - renderClientForbiddenPage(w, r, message) + s.renderClientForbiddenPage(w, r, message) return } - renderForbiddenPage(w, r, message) + s.renderForbiddenPage(w, r, message) return } sendAPIResponse(w, r, errors.New(message), message, http.StatusForbidden) @@ -1109,10 +1111,10 @@ func (s *httpdServer) initializeRouter() { if (s.enableWebAdmin || s.enableWebClient) && isWebRequest(r) { r = s.updateContextFromCookie(r) if s.enableWebClient && (isWebClientRequest(r) || !s.enableWebAdmin) { - renderClientNotFoundPage(w, r, nil) + s.renderClientNotFoundPage(w, r, nil) return } - renderNotFoundPage(w, r, nil) + s.renderNotFoundPage(w, r, nil) return } sendAPIResponse(w, r, nil, http.StatusText(http.StatusNotFound), http.StatusNotFound) @@ -1123,11 +1125,11 @@ func (s *httpdServer) initializeRouter() { }) // share API exposed to external users - s.router.Get(sharesPath+"/{id}", downloadFromShare) - s.router.Post(sharesPath+"/{id}", uploadFilesToShare) - s.router.Post(sharesPath+"/{id}/{name}", uploadFileToShare) - s.router.With(compressor.Handler).Get(sharesPath+"/{id}/dirs", readBrowsableShareContents) - s.router.Get(sharesPath+"/{id}/files", downloadBrowsableSharedFile) + s.router.Get(sharesPath+"/{id}", s.downloadFromShare) + s.router.Post(sharesPath+"/{id}", s.uploadFilesToShare) + s.router.Post(sharesPath+"/{id}/{name}", s.uploadFileToShare) + s.router.With(compressor.Handler).Get(sharesPath+"/{id}/dirs", s.readBrowsableShareContents) + s.router.Get(sharesPath+"/{id}/files", s.downloadBrowsableSharedFile) s.router.Get(tokenPath, s.getToken) s.router.Post(adminPath+"/{username}/forgot-password", forgotAdminPassword) @@ -1159,81 +1161,81 @@ func (s *httpdServer) initializeRouter() { router.With(forbidAPIKeyAuthentication).Get(admin2FARecoveryCodesPath, getRecoveryCodes) router.With(forbidAPIKeyAuthentication).Post(admin2FARecoveryCodesPath, generateRecoveryCodes) - router.With(checkPerm(dataprovider.PermAdminViewServerStatus)). + router.With(s.checkPerm(dataprovider.PermAdminViewServerStatus)). Get(serverStatusPath, func(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) render.JSON(w, r, getServicesStatus()) }) - router.With(checkPerm(dataprovider.PermAdminViewConnections)). + router.With(s.checkPerm(dataprovider.PermAdminViewConnections)). Get(activeConnectionsPath, func(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) render.JSON(w, r, common.Connections.GetStats()) }) - router.With(checkPerm(dataprovider.PermAdminCloseConnections)). + router.With(s.checkPerm(dataprovider.PermAdminCloseConnections)). Delete(activeConnectionsPath+"/{connectionID}", handleCloseConnection) - router.With(checkPerm(dataprovider.PermAdminQuotaScans)).Get(quotaScanPath, getUsersQuotaScans) - router.With(checkPerm(dataprovider.PermAdminQuotaScans)).Get(quotasBasePath+"/users/scans", getUsersQuotaScans) - router.With(checkPerm(dataprovider.PermAdminQuotaScans)).Post(quotaScanPath, startUserQuotaScanCompat) - router.With(checkPerm(dataprovider.PermAdminQuotaScans)).Post(quotasBasePath+"/users/{username}/scan", startUserQuotaScan) - router.With(checkPerm(dataprovider.PermAdminQuotaScans)).Get(quotaScanVFolderPath, getFoldersQuotaScans) - router.With(checkPerm(dataprovider.PermAdminQuotaScans)).Get(quotasBasePath+"/folders/scans", getFoldersQuotaScans) - router.With(checkPerm(dataprovider.PermAdminQuotaScans)).Post(quotaScanVFolderPath, startFolderQuotaScanCompat) - router.With(checkPerm(dataprovider.PermAdminQuotaScans)).Post(quotasBasePath+"/folders/{name}/scan", startFolderQuotaScan) - router.With(checkPerm(dataprovider.PermAdminViewUsers)).Get(userPath, getUsers) - router.With(checkPerm(dataprovider.PermAdminAddUsers)).Post(userPath, addUser) - router.With(checkPerm(dataprovider.PermAdminViewUsers)).Get(userPath+"/{username}", getUserByUsername) - router.With(checkPerm(dataprovider.PermAdminChangeUsers)).Put(userPath+"/{username}", updateUser) - router.With(checkPerm(dataprovider.PermAdminDeleteUsers)).Delete(userPath+"/{username}", deleteUser) - router.With(checkPerm(dataprovider.PermAdminChangeUsers)).Put(userPath+"/{username}/2fa/disable", disableUser2FA) - router.With(checkPerm(dataprovider.PermAdminViewUsers)).Get(folderPath, getFolders) - router.With(checkPerm(dataprovider.PermAdminViewUsers)).Get(folderPath+"/{name}", getFolderByName) - router.With(checkPerm(dataprovider.PermAdminAddUsers)).Post(folderPath, addFolder) - router.With(checkPerm(dataprovider.PermAdminChangeUsers)).Put(folderPath+"/{name}", updateFolder) - router.With(checkPerm(dataprovider.PermAdminDeleteUsers)).Delete(folderPath+"/{name}", deleteFolder) - router.With(checkPerm(dataprovider.PermAdminManageSystem)).Get(dumpDataPath, dumpData) - router.With(checkPerm(dataprovider.PermAdminManageSystem)).Get(loadDataPath, loadData) - router.With(checkPerm(dataprovider.PermAdminManageSystem)).Post(loadDataPath, loadDataFromRequest) - router.With(checkPerm(dataprovider.PermAdminChangeUsers)).Put(updateUsedQuotaPath, updateUserQuotaUsageCompat) - router.With(checkPerm(dataprovider.PermAdminChangeUsers)).Put(quotasBasePath+"/users/{username}/usage", + router.With(s.checkPerm(dataprovider.PermAdminQuotaScans)).Get(quotaScanPath, getUsersQuotaScans) + router.With(s.checkPerm(dataprovider.PermAdminQuotaScans)).Get(quotasBasePath+"/users/scans", getUsersQuotaScans) + router.With(s.checkPerm(dataprovider.PermAdminQuotaScans)).Post(quotaScanPath, startUserQuotaScanCompat) + router.With(s.checkPerm(dataprovider.PermAdminQuotaScans)).Post(quotasBasePath+"/users/{username}/scan", startUserQuotaScan) + router.With(s.checkPerm(dataprovider.PermAdminQuotaScans)).Get(quotaScanVFolderPath, getFoldersQuotaScans) + router.With(s.checkPerm(dataprovider.PermAdminQuotaScans)).Get(quotasBasePath+"/folders/scans", getFoldersQuotaScans) + router.With(s.checkPerm(dataprovider.PermAdminQuotaScans)).Post(quotaScanVFolderPath, startFolderQuotaScanCompat) + router.With(s.checkPerm(dataprovider.PermAdminQuotaScans)).Post(quotasBasePath+"/folders/{name}/scan", startFolderQuotaScan) + router.With(s.checkPerm(dataprovider.PermAdminViewUsers)).Get(userPath, getUsers) + router.With(s.checkPerm(dataprovider.PermAdminAddUsers)).Post(userPath, addUser) + router.With(s.checkPerm(dataprovider.PermAdminViewUsers)).Get(userPath+"/{username}", getUserByUsername) + router.With(s.checkPerm(dataprovider.PermAdminChangeUsers)).Put(userPath+"/{username}", updateUser) + router.With(s.checkPerm(dataprovider.PermAdminDeleteUsers)).Delete(userPath+"/{username}", deleteUser) + router.With(s.checkPerm(dataprovider.PermAdminChangeUsers)).Put(userPath+"/{username}/2fa/disable", disableUser2FA) + router.With(s.checkPerm(dataprovider.PermAdminViewUsers)).Get(folderPath, getFolders) + router.With(s.checkPerm(dataprovider.PermAdminViewUsers)).Get(folderPath+"/{name}", getFolderByName) + router.With(s.checkPerm(dataprovider.PermAdminAddUsers)).Post(folderPath, addFolder) + router.With(s.checkPerm(dataprovider.PermAdminChangeUsers)).Put(folderPath+"/{name}", updateFolder) + router.With(s.checkPerm(dataprovider.PermAdminDeleteUsers)).Delete(folderPath+"/{name}", deleteFolder) + router.With(s.checkPerm(dataprovider.PermAdminManageSystem)).Get(dumpDataPath, dumpData) + router.With(s.checkPerm(dataprovider.PermAdminManageSystem)).Get(loadDataPath, loadData) + router.With(s.checkPerm(dataprovider.PermAdminManageSystem)).Post(loadDataPath, loadDataFromRequest) + router.With(s.checkPerm(dataprovider.PermAdminChangeUsers)).Put(updateUsedQuotaPath, updateUserQuotaUsageCompat) + router.With(s.checkPerm(dataprovider.PermAdminChangeUsers)).Put(quotasBasePath+"/users/{username}/usage", updateUserQuotaUsage) - router.With(checkPerm(dataprovider.PermAdminChangeUsers)).Put(quotasBasePath+"/users/{username}/transfer-usage", + router.With(s.checkPerm(dataprovider.PermAdminChangeUsers)).Put(quotasBasePath+"/users/{username}/transfer-usage", updateUserTransferQuotaUsage) - router.With(checkPerm(dataprovider.PermAdminChangeUsers)).Put(updateFolderUsedQuotaPath, updateFolderQuotaUsageCompat) - router.With(checkPerm(dataprovider.PermAdminChangeUsers)).Put(quotasBasePath+"/folders/{name}/usage", + router.With(s.checkPerm(dataprovider.PermAdminChangeUsers)).Put(updateFolderUsedQuotaPath, updateFolderQuotaUsageCompat) + router.With(s.checkPerm(dataprovider.PermAdminChangeUsers)).Put(quotasBasePath+"/folders/{name}/usage", updateFolderQuotaUsage) - router.With(checkPerm(dataprovider.PermAdminViewDefender)).Get(defenderHosts, getDefenderHosts) - router.With(checkPerm(dataprovider.PermAdminViewDefender)).Get(defenderHosts+"/{id}", getDefenderHostByID) - router.With(checkPerm(dataprovider.PermAdminManageDefender)).Delete(defenderHosts+"/{id}", deleteDefenderHostByID) - router.With(checkPerm(dataprovider.PermAdminViewDefender)).Get(defenderBanTime, getBanTime) - router.With(checkPerm(dataprovider.PermAdminViewDefender)).Get(defenderScore, getScore) - router.With(checkPerm(dataprovider.PermAdminManageDefender)).Post(defenderUnban, unban) - router.With(checkPerm(dataprovider.PermAdminManageAdmins)).Get(adminPath, getAdmins) - router.With(checkPerm(dataprovider.PermAdminManageAdmins)).Post(adminPath, addAdmin) - router.With(checkPerm(dataprovider.PermAdminManageAdmins)).Get(adminPath+"/{username}", getAdminByUsername) - router.With(checkPerm(dataprovider.PermAdminManageAdmins)).Put(adminPath+"/{username}", updateAdmin) - router.With(checkPerm(dataprovider.PermAdminManageAdmins)).Delete(adminPath+"/{username}", deleteAdmin) - router.With(checkPerm(dataprovider.PermAdminManageAdmins)).Put(adminPath+"/{username}/2fa/disable", disableAdmin2FA) - router.With(checkPerm(dataprovider.PermAdminRetentionChecks)).Get(retentionChecksPath, getRetentionChecks) - router.With(checkPerm(dataprovider.PermAdminRetentionChecks)).Post(retentionBasePath+"/{username}/check", + router.With(s.checkPerm(dataprovider.PermAdminViewDefender)).Get(defenderHosts, getDefenderHosts) + router.With(s.checkPerm(dataprovider.PermAdminViewDefender)).Get(defenderHosts+"/{id}", getDefenderHostByID) + router.With(s.checkPerm(dataprovider.PermAdminManageDefender)).Delete(defenderHosts+"/{id}", deleteDefenderHostByID) + router.With(s.checkPerm(dataprovider.PermAdminViewDefender)).Get(defenderBanTime, getBanTime) + router.With(s.checkPerm(dataprovider.PermAdminViewDefender)).Get(defenderScore, getScore) + router.With(s.checkPerm(dataprovider.PermAdminManageDefender)).Post(defenderUnban, unban) + router.With(s.checkPerm(dataprovider.PermAdminManageAdmins)).Get(adminPath, getAdmins) + router.With(s.checkPerm(dataprovider.PermAdminManageAdmins)).Post(adminPath, addAdmin) + router.With(s.checkPerm(dataprovider.PermAdminManageAdmins)).Get(adminPath+"/{username}", getAdminByUsername) + router.With(s.checkPerm(dataprovider.PermAdminManageAdmins)).Put(adminPath+"/{username}", updateAdmin) + router.With(s.checkPerm(dataprovider.PermAdminManageAdmins)).Delete(adminPath+"/{username}", deleteAdmin) + router.With(s.checkPerm(dataprovider.PermAdminManageAdmins)).Put(adminPath+"/{username}/2fa/disable", disableAdmin2FA) + router.With(s.checkPerm(dataprovider.PermAdminRetentionChecks)).Get(retentionChecksPath, getRetentionChecks) + router.With(s.checkPerm(dataprovider.PermAdminRetentionChecks)).Post(retentionBasePath+"/{username}/check", startRetentionCheck) - router.With(checkPerm(dataprovider.PermAdminMetadataChecks)).Get(metadataChecksPath, getMetadataChecks) - router.With(checkPerm(dataprovider.PermAdminMetadataChecks)).Post(metadataBasePath+"/{username}/check", + router.With(s.checkPerm(dataprovider.PermAdminMetadataChecks)).Get(metadataChecksPath, getMetadataChecks) + router.With(s.checkPerm(dataprovider.PermAdminMetadataChecks)).Post(metadataBasePath+"/{username}/check", startMetadataCheck) - router.With(checkPerm(dataprovider.PermAdminViewEvents), compressor.Handler). + router.With(s.checkPerm(dataprovider.PermAdminViewEvents), compressor.Handler). Get(fsEventsPath, searchFsEvents) - router.With(checkPerm(dataprovider.PermAdminViewEvents), compressor.Handler). + router.With(s.checkPerm(dataprovider.PermAdminViewEvents), compressor.Handler). Get(providerEventsPath, searchProviderEvents) - router.With(forbidAPIKeyAuthentication, checkPerm(dataprovider.PermAdminManageAPIKeys)). + router.With(forbidAPIKeyAuthentication, s.checkPerm(dataprovider.PermAdminManageAPIKeys)). Get(apiKeysPath, getAPIKeys) - router.With(forbidAPIKeyAuthentication, checkPerm(dataprovider.PermAdminManageAPIKeys)). + router.With(forbidAPIKeyAuthentication, s.checkPerm(dataprovider.PermAdminManageAPIKeys)). Post(apiKeysPath, addAPIKey) - router.With(forbidAPIKeyAuthentication, checkPerm(dataprovider.PermAdminManageAPIKeys)). + router.With(forbidAPIKeyAuthentication, s.checkPerm(dataprovider.PermAdminManageAPIKeys)). Get(apiKeysPath+"/{id}", getAPIKeyByID) - router.With(forbidAPIKeyAuthentication, checkPerm(dataprovider.PermAdminManageAPIKeys)). + router.With(forbidAPIKeyAuthentication, s.checkPerm(dataprovider.PermAdminManageAPIKeys)). Put(apiKeysPath+"/{id}", updateAPIKey) - router.With(forbidAPIKeyAuthentication, checkPerm(dataprovider.PermAdminManageAPIKeys)). + router.With(forbidAPIKeyAuthentication, s.checkPerm(dataprovider.PermAdminManageAPIKeys)). Delete(apiKeysPath+"/{id}", deleteAPIKey) }) @@ -1245,60 +1247,60 @@ func (s *httpdServer) initializeRouter() { router.Use(jwtAuthenticatorAPIUser) router.With(forbidAPIKeyAuthentication).Get(userLogoutPath, s.logout) - router.With(forbidAPIKeyAuthentication, checkSecondFactorRequirement, - checkHTTPUserPerm(sdk.WebClientPasswordChangeDisabled)).Put(userPwdPath, changeUserPassword) - router.With(forbidAPIKeyAuthentication, checkSecondFactorRequirement, - checkHTTPUserPerm(sdk.WebClientPubKeyChangeDisabled)).Get(userPublicKeysPath, getUserPublicKeys) - router.With(forbidAPIKeyAuthentication, checkSecondFactorRequirement, - checkHTTPUserPerm(sdk.WebClientPubKeyChangeDisabled)).Put(userPublicKeysPath, setUserPublicKeys) + router.With(forbidAPIKeyAuthentication, s.checkSecondFactorRequirement, + s.checkHTTPUserPerm(sdk.WebClientPasswordChangeDisabled)).Put(userPwdPath, changeUserPassword) + router.With(forbidAPIKeyAuthentication, s.checkSecondFactorRequirement, + s.checkHTTPUserPerm(sdk.WebClientPubKeyChangeDisabled)).Get(userPublicKeysPath, getUserPublicKeys) + router.With(forbidAPIKeyAuthentication, s.checkSecondFactorRequirement, + s.checkHTTPUserPerm(sdk.WebClientPubKeyChangeDisabled)).Put(userPublicKeysPath, setUserPublicKeys) router.With(forbidAPIKeyAuthentication).Get(userProfilePath, getUserProfile) - router.With(forbidAPIKeyAuthentication, checkSecondFactorRequirement).Put(userProfilePath, updateUserProfile) + router.With(forbidAPIKeyAuthentication, s.checkSecondFactorRequirement).Put(userProfilePath, updateUserProfile) // user TOTP APIs - router.With(forbidAPIKeyAuthentication, checkHTTPUserPerm(sdk.WebClientMFADisabled)). + router.With(forbidAPIKeyAuthentication, s.checkHTTPUserPerm(sdk.WebClientMFADisabled)). Get(userTOTPConfigsPath, getTOTPConfigs) - router.With(forbidAPIKeyAuthentication, checkHTTPUserPerm(sdk.WebClientMFADisabled)). + router.With(forbidAPIKeyAuthentication, s.checkHTTPUserPerm(sdk.WebClientMFADisabled)). Post(userTOTPGeneratePath, generateTOTPSecret) - router.With(forbidAPIKeyAuthentication, checkHTTPUserPerm(sdk.WebClientMFADisabled)). + router.With(forbidAPIKeyAuthentication, s.checkHTTPUserPerm(sdk.WebClientMFADisabled)). Post(userTOTPValidatePath, validateTOTPPasscode) - router.With(forbidAPIKeyAuthentication, checkHTTPUserPerm(sdk.WebClientMFADisabled)). + router.With(forbidAPIKeyAuthentication, s.checkHTTPUserPerm(sdk.WebClientMFADisabled)). Post(userTOTPSavePath, saveTOTPConfig) - router.With(forbidAPIKeyAuthentication, checkHTTPUserPerm(sdk.WebClientMFADisabled)). + router.With(forbidAPIKeyAuthentication, s.checkHTTPUserPerm(sdk.WebClientMFADisabled)). Get(user2FARecoveryCodesPath, getRecoveryCodes) - router.With(forbidAPIKeyAuthentication, checkHTTPUserPerm(sdk.WebClientMFADisabled)). + router.With(forbidAPIKeyAuthentication, s.checkHTTPUserPerm(sdk.WebClientMFADisabled)). Post(user2FARecoveryCodesPath, generateRecoveryCodes) // compatibility layer to remove in v2.3 - router.With(checkSecondFactorRequirement, compressor.Handler).Get(userFolderPath, readUserFolder) - router.With(checkSecondFactorRequirement).Get(userFilePath, getUserFile) + router.With(s.checkSecondFactorRequirement, compressor.Handler).Get(userFolderPath, readUserFolder) + router.With(s.checkSecondFactorRequirement).Get(userFilePath, getUserFile) - router.With(checkSecondFactorRequirement, compressor.Handler).Get(userDirsPath, readUserFolder) - router.With(checkSecondFactorRequirement, checkHTTPUserPerm(sdk.WebClientWriteDisabled)). + router.With(s.checkSecondFactorRequirement, compressor.Handler).Get(userDirsPath, readUserFolder) + router.With(s.checkSecondFactorRequirement, s.checkHTTPUserPerm(sdk.WebClientWriteDisabled)). Post(userDirsPath, createUserDir) - router.With(checkSecondFactorRequirement, checkHTTPUserPerm(sdk.WebClientWriteDisabled)). + router.With(s.checkSecondFactorRequirement, s.checkHTTPUserPerm(sdk.WebClientWriteDisabled)). Patch(userDirsPath, renameUserDir) - router.With(checkSecondFactorRequirement, checkHTTPUserPerm(sdk.WebClientWriteDisabled)). + router.With(s.checkSecondFactorRequirement, s.checkHTTPUserPerm(sdk.WebClientWriteDisabled)). Delete(userDirsPath, deleteUserDir) - router.With(checkSecondFactorRequirement).Get(userFilesPath, getUserFile) - router.With(checkSecondFactorRequirement, checkHTTPUserPerm(sdk.WebClientWriteDisabled)). + router.With(s.checkSecondFactorRequirement).Get(userFilesPath, getUserFile) + router.With(s.checkSecondFactorRequirement, s.checkHTTPUserPerm(sdk.WebClientWriteDisabled)). Post(userFilesPath, uploadUserFiles) - router.With(checkSecondFactorRequirement, checkHTTPUserPerm(sdk.WebClientWriteDisabled)). + router.With(s.checkSecondFactorRequirement, s.checkHTTPUserPerm(sdk.WebClientWriteDisabled)). Patch(userFilesPath, renameUserFile) - router.With(checkSecondFactorRequirement, checkHTTPUserPerm(sdk.WebClientWriteDisabled)). + router.With(s.checkSecondFactorRequirement, s.checkHTTPUserPerm(sdk.WebClientWriteDisabled)). Delete(userFilesPath, deleteUserFile) - router.With(checkSecondFactorRequirement).Post(userStreamZipPath, getUserFilesAsZipStream) - router.With(checkSecondFactorRequirement, checkHTTPUserPerm(sdk.WebClientSharesDisabled)). + router.With(s.checkSecondFactorRequirement).Post(userStreamZipPath, getUserFilesAsZipStream) + router.With(s.checkSecondFactorRequirement, s.checkHTTPUserPerm(sdk.WebClientSharesDisabled)). Get(userSharesPath, getShares) - router.With(checkSecondFactorRequirement, checkHTTPUserPerm(sdk.WebClientSharesDisabled)). + router.With(s.checkSecondFactorRequirement, s.checkHTTPUserPerm(sdk.WebClientSharesDisabled)). Post(userSharesPath, addShare) - router.With(checkSecondFactorRequirement, checkHTTPUserPerm(sdk.WebClientSharesDisabled)). + router.With(s.checkSecondFactorRequirement, s.checkHTTPUserPerm(sdk.WebClientSharesDisabled)). Get(userSharesPath+"/{id}", getShareByID) - router.With(checkSecondFactorRequirement, checkHTTPUserPerm(sdk.WebClientSharesDisabled)). + router.With(s.checkSecondFactorRequirement, s.checkHTTPUserPerm(sdk.WebClientSharesDisabled)). Put(userSharesPath+"/{id}", updateShare) - router.With(checkSecondFactorRequirement, checkHTTPUserPerm(sdk.WebClientSharesDisabled)). + router.With(s.checkSecondFactorRequirement, s.checkHTTPUserPerm(sdk.WebClientSharesDisabled)). Delete(userSharesPath+"/{id}", deleteShare) - router.With(checkSecondFactorRequirement, checkHTTPUserPerm(sdk.WebClientWriteDisabled)). + router.With(s.checkSecondFactorRequirement, s.checkHTTPUserPerm(sdk.WebClientWriteDisabled)). Post(userUploadFilePath, uploadUserFile) - router.With(checkSecondFactorRequirement, checkHTTPUserPerm(sdk.WebClientWriteDisabled)). + router.With(s.checkSecondFactorRequirement, s.checkHTTPUserPerm(sdk.WebClientWriteDisabled)). Patch(userFilesDirsMetadataPath, setFileDirMetadata) }) @@ -1353,29 +1355,29 @@ func (s *httpdServer) setupWebClientRoutes() { s.router.Get(webClientOIDCLoginPath, s.handleWebClientOIDCLogin) } s.router.Post(webClientLoginPath, s.handleWebClientLoginPost) - s.router.Get(webClientForgotPwdPath, handleWebClientForgotPwd) - s.router.Post(webClientForgotPwdPath, handleWebClientForgotPwdPost) - s.router.Get(webClientResetPwdPath, handleWebClientPasswordReset) + s.router.Get(webClientForgotPwdPath, s.handleWebClientForgotPwd) + s.router.Post(webClientForgotPwdPath, s.handleWebClientForgotPwdPost) + s.router.Get(webClientResetPwdPath, s.handleWebClientPasswordReset) s.router.Post(webClientResetPwdPath, s.handleWebClientPasswordResetPost) s.router.With(jwtauth.Verify(s.tokenAuth, jwtauth.TokenFromCookie), - jwtAuthenticatorPartial(tokenAudienceWebClientPartial)). - Get(webClientTwoFactorPath, handleWebClientTwoFactor) + s.jwtAuthenticatorPartial(tokenAudienceWebClientPartial)). + Get(webClientTwoFactorPath, s.handleWebClientTwoFactor) s.router.With(jwtauth.Verify(s.tokenAuth, jwtauth.TokenFromCookie), - jwtAuthenticatorPartial(tokenAudienceWebClientPartial)). + s.jwtAuthenticatorPartial(tokenAudienceWebClientPartial)). Post(webClientTwoFactorPath, s.handleWebClientTwoFactorPost) s.router.With(jwtauth.Verify(s.tokenAuth, jwtauth.TokenFromCookie), - jwtAuthenticatorPartial(tokenAudienceWebClientPartial)). - Get(webClientTwoFactorRecoveryPath, handleWebClientTwoFactorRecovery) + s.jwtAuthenticatorPartial(tokenAudienceWebClientPartial)). + Get(webClientTwoFactorRecoveryPath, s.handleWebClientTwoFactorRecovery) s.router.With(jwtauth.Verify(s.tokenAuth, jwtauth.TokenFromCookie), - jwtAuthenticatorPartial(tokenAudienceWebClientPartial)). + s.jwtAuthenticatorPartial(tokenAudienceWebClientPartial)). Post(webClientTwoFactorRecoveryPath, s.handleWebClientTwoFactorRecoveryPost) // share API exposed to external users - s.router.Get(webClientPubSharesPath+"/{id}", downloadFromShare) - s.router.Get(webClientPubSharesPath+"/{id}/browse", handleShareGetFiles) - s.router.Get(webClientPubSharesPath+"/{id}/upload", handleClientUploadToShare) - s.router.With(compressor.Handler).Get(webClientPubSharesPath+"/{id}/dirs", handleShareGetDirContents) - s.router.Post(webClientPubSharesPath+"/{id}", uploadFilesToShare) - s.router.Post(webClientPubSharesPath+"/{id}/{name}", uploadFileToShare) + s.router.Get(webClientPubSharesPath+"/{id}", s.downloadFromShare) + s.router.Get(webClientPubSharesPath+"/{id}/browse", s.handleShareGetFiles) + s.router.Get(webClientPubSharesPath+"/{id}/upload", s.handleClientUploadToShare) + s.router.With(compressor.Handler).Get(webClientPubSharesPath+"/{id}/dirs", s.handleShareGetDirContents) + s.router.Post(webClientPubSharesPath+"/{id}", s.uploadFilesToShare) + s.router.Post(webClientPubSharesPath+"/{id}/{name}", s.uploadFileToShare) s.router.Group(func(router chi.Router) { if s.binding.OIDC.isEnabled() { @@ -1385,57 +1387,57 @@ func (s *httpdServer) setupWebClientRoutes() { router.Use(jwtAuthenticatorWebClient) router.Get(webClientLogoutPath, s.handleWebClientLogout) - router.With(checkSecondFactorRequirement, s.refreshCookie).Get(webClientFilesPath, s.handleClientGetFiles) - router.With(checkSecondFactorRequirement, s.refreshCookie).Get(webClientViewPDFPath, handleClientViewPDF) - router.With(checkSecondFactorRequirement, s.refreshCookie, verifyCSRFHeader).Get(webClientFilePath, getUserFile) - router.With(checkSecondFactorRequirement, checkHTTPUserPerm(sdk.WebClientWriteDisabled), verifyCSRFHeader). + router.With(s.checkSecondFactorRequirement, s.refreshCookie).Get(webClientFilesPath, s.handleClientGetFiles) + router.With(s.checkSecondFactorRequirement, s.refreshCookie).Get(webClientViewPDFPath, s.handleClientViewPDF) + router.With(s.checkSecondFactorRequirement, s.refreshCookie, verifyCSRFHeader).Get(webClientFilePath, getUserFile) + router.With(s.checkSecondFactorRequirement, s.checkHTTPUserPerm(sdk.WebClientWriteDisabled), verifyCSRFHeader). Post(webClientFilePath, uploadUserFile) - router.With(checkSecondFactorRequirement, s.refreshCookie).Get(webClientEditFilePath, handleClientEditFile) - router.With(checkSecondFactorRequirement, checkHTTPUserPerm(sdk.WebClientWriteDisabled), verifyCSRFHeader). + router.With(s.checkSecondFactorRequirement, s.refreshCookie).Get(webClientEditFilePath, s.handleClientEditFile) + router.With(s.checkSecondFactorRequirement, s.checkHTTPUserPerm(sdk.WebClientWriteDisabled), verifyCSRFHeader). Patch(webClientFilesPath, renameUserFile) - router.With(checkSecondFactorRequirement, checkHTTPUserPerm(sdk.WebClientWriteDisabled), verifyCSRFHeader). + router.With(s.checkSecondFactorRequirement, s.checkHTTPUserPerm(sdk.WebClientWriteDisabled), verifyCSRFHeader). Delete(webClientFilesPath, deleteUserFile) - router.With(checkSecondFactorRequirement, compressor.Handler, s.refreshCookie). + router.With(s.checkSecondFactorRequirement, compressor.Handler, s.refreshCookie). Get(webClientDirsPath, s.handleClientGetDirContents) - router.With(checkSecondFactorRequirement, checkHTTPUserPerm(sdk.WebClientWriteDisabled), verifyCSRFHeader). + router.With(s.checkSecondFactorRequirement, s.checkHTTPUserPerm(sdk.WebClientWriteDisabled), verifyCSRFHeader). Post(webClientDirsPath, createUserDir) - router.With(checkSecondFactorRequirement, checkHTTPUserPerm(sdk.WebClientWriteDisabled), verifyCSRFHeader). + router.With(s.checkSecondFactorRequirement, s.checkHTTPUserPerm(sdk.WebClientWriteDisabled), verifyCSRFHeader). Patch(webClientDirsPath, renameUserDir) - router.With(checkSecondFactorRequirement, checkHTTPUserPerm(sdk.WebClientWriteDisabled), verifyCSRFHeader). + router.With(s.checkSecondFactorRequirement, s.checkHTTPUserPerm(sdk.WebClientWriteDisabled), verifyCSRFHeader). Delete(webClientDirsPath, deleteUserDir) - router.With(checkSecondFactorRequirement, s.refreshCookie). - Get(webClientDownloadZipPath, handleWebClientDownloadZip) - router.With(checkSecondFactorRequirement, s.refreshCookie, requireBuiltinLogin). - Get(webClientProfilePath, handleClientGetProfile) - router.With(checkSecondFactorRequirement, requireBuiltinLogin). - Post(webClientProfilePath, handleWebClientProfilePost) - router.With(checkSecondFactorRequirement, checkHTTPUserPerm(sdk.WebClientPasswordChangeDisabled)). - Get(webChangeClientPwdPath, handleWebClientChangePwd) - router.With(checkSecondFactorRequirement, checkHTTPUserPerm(sdk.WebClientPasswordChangeDisabled)). + router.With(s.checkSecondFactorRequirement, s.refreshCookie). + Get(webClientDownloadZipPath, s.handleWebClientDownloadZip) + router.With(s.checkSecondFactorRequirement, s.refreshCookie, s.requireBuiltinLogin). + Get(webClientProfilePath, s.handleClientGetProfile) + router.With(s.checkSecondFactorRequirement, s.requireBuiltinLogin). + Post(webClientProfilePath, s.handleWebClientProfilePost) + router.With(s.checkSecondFactorRequirement, s.checkHTTPUserPerm(sdk.WebClientPasswordChangeDisabled)). + Get(webChangeClientPwdPath, s.handleWebClientChangePwd) + router.With(s.checkSecondFactorRequirement, s.checkHTTPUserPerm(sdk.WebClientPasswordChangeDisabled)). Post(webChangeClientPwdPath, s.handleWebClientChangePwdPost) - router.With(checkHTTPUserPerm(sdk.WebClientMFADisabled), s.refreshCookie). - Get(webClientMFAPath, handleWebClientMFA) - router.With(checkHTTPUserPerm(sdk.WebClientMFADisabled), verifyCSRFHeader). + router.With(s.checkHTTPUserPerm(sdk.WebClientMFADisabled), s.refreshCookie). + Get(webClientMFAPath, s.handleWebClientMFA) + router.With(s.checkHTTPUserPerm(sdk.WebClientMFADisabled), verifyCSRFHeader). Post(webClientTOTPGeneratePath, generateTOTPSecret) - router.With(checkHTTPUserPerm(sdk.WebClientMFADisabled), verifyCSRFHeader). + router.With(s.checkHTTPUserPerm(sdk.WebClientMFADisabled), verifyCSRFHeader). Post(webClientTOTPValidatePath, validateTOTPPasscode) - router.With(checkHTTPUserPerm(sdk.WebClientMFADisabled), verifyCSRFHeader). + router.With(s.checkHTTPUserPerm(sdk.WebClientMFADisabled), verifyCSRFHeader). Post(webClientTOTPSavePath, saveTOTPConfig) - router.With(checkHTTPUserPerm(sdk.WebClientMFADisabled), verifyCSRFHeader, s.refreshCookie). + router.With(s.checkHTTPUserPerm(sdk.WebClientMFADisabled), verifyCSRFHeader, s.refreshCookie). Get(webClientRecoveryCodesPath, getRecoveryCodes) - router.With(checkHTTPUserPerm(sdk.WebClientMFADisabled), verifyCSRFHeader). + router.With(s.checkHTTPUserPerm(sdk.WebClientMFADisabled), verifyCSRFHeader). Post(webClientRecoveryCodesPath, generateRecoveryCodes) - router.With(checkSecondFactorRequirement, checkHTTPUserPerm(sdk.WebClientSharesDisabled), s.refreshCookie). - Get(webClientSharesPath, handleClientGetShares) - router.With(checkSecondFactorRequirement, checkHTTPUserPerm(sdk.WebClientSharesDisabled), s.refreshCookie). - Get(webClientSharePath, handleClientAddShareGet) - router.With(checkSecondFactorRequirement, checkHTTPUserPerm(sdk.WebClientSharesDisabled)). - Post(webClientSharePath, handleClientAddSharePost) - router.With(checkSecondFactorRequirement, checkHTTPUserPerm(sdk.WebClientSharesDisabled), s.refreshCookie). - Get(webClientSharePath+"/{id}", handleClientUpdateShareGet) - router.With(checkSecondFactorRequirement, checkHTTPUserPerm(sdk.WebClientSharesDisabled)). - Post(webClientSharePath+"/{id}", handleClientUpdateSharePost) - router.With(checkSecondFactorRequirement, checkHTTPUserPerm(sdk.WebClientSharesDisabled), verifyCSRFHeader). + router.With(s.checkSecondFactorRequirement, s.checkHTTPUserPerm(sdk.WebClientSharesDisabled), s.refreshCookie). + Get(webClientSharesPath, s.handleClientGetShares) + router.With(s.checkSecondFactorRequirement, s.checkHTTPUserPerm(sdk.WebClientSharesDisabled), s.refreshCookie). + Get(webClientSharePath, s.handleClientAddShareGet) + router.With(s.checkSecondFactorRequirement, s.checkHTTPUserPerm(sdk.WebClientSharesDisabled)). + Post(webClientSharePath, s.handleClientAddSharePost) + router.With(s.checkSecondFactorRequirement, s.checkHTTPUserPerm(sdk.WebClientSharesDisabled), s.refreshCookie). + Get(webClientSharePath+"/{id}", s.handleClientUpdateShareGet) + router.With(s.checkSecondFactorRequirement, s.checkHTTPUserPerm(sdk.WebClientSharesDisabled)). + Post(webClientSharePath+"/{id}", s.handleClientUpdateSharePost) + router.With(s.checkSecondFactorRequirement, s.checkHTTPUserPerm(sdk.WebClientSharesDisabled), verifyCSRFHeader). Delete(webClientSharePath+"/{id}", deleteShare) }) } @@ -1452,23 +1454,23 @@ func (s *httpdServer) setupWebAdminRoutes() { s.router.Get(webAdminOIDCLoginPath, s.handleWebAdminOIDCLogin) } s.router.Post(webAdminLoginPath, s.handleWebAdminLoginPost) - s.router.Get(webAdminSetupPath, handleWebAdminSetupGet) + s.router.Get(webAdminSetupPath, s.handleWebAdminSetupGet) s.router.Post(webAdminSetupPath, s.handleWebAdminSetupPost) - s.router.Get(webAdminForgotPwdPath, handleWebAdminForgotPwd) - s.router.Post(webAdminForgotPwdPath, handleWebAdminForgotPwdPost) - s.router.Get(webAdminResetPwdPath, handleWebAdminPasswordReset) + s.router.Get(webAdminForgotPwdPath, s.handleWebAdminForgotPwd) + s.router.Post(webAdminForgotPwdPath, s.handleWebAdminForgotPwdPost) + s.router.Get(webAdminResetPwdPath, s.handleWebAdminPasswordReset) s.router.Post(webAdminResetPwdPath, s.handleWebAdminPasswordResetPost) s.router.With(jwtauth.Verify(s.tokenAuth, jwtauth.TokenFromCookie), - jwtAuthenticatorPartial(tokenAudienceWebAdminPartial)). - Get(webAdminTwoFactorPath, handleWebAdminTwoFactor) + s.jwtAuthenticatorPartial(tokenAudienceWebAdminPartial)). + Get(webAdminTwoFactorPath, s.handleWebAdminTwoFactor) s.router.With(jwtauth.Verify(s.tokenAuth, jwtauth.TokenFromCookie), - jwtAuthenticatorPartial(tokenAudienceWebAdminPartial)). + s.jwtAuthenticatorPartial(tokenAudienceWebAdminPartial)). Post(webAdminTwoFactorPath, s.handleWebAdminTwoFactorPost) s.router.With(jwtauth.Verify(s.tokenAuth, jwtauth.TokenFromCookie), - jwtAuthenticatorPartial(tokenAudienceWebAdminPartial)). - Get(webAdminTwoFactorRecoveryPath, handleWebAdminTwoFactorRecovery) + s.jwtAuthenticatorPartial(tokenAudienceWebAdminPartial)). + Get(webAdminTwoFactorRecoveryPath, s.handleWebAdminTwoFactorRecovery) s.router.With(jwtauth.Verify(s.tokenAuth, jwtauth.TokenFromCookie), - jwtAuthenticatorPartial(tokenAudienceWebAdminPartial)). + s.jwtAuthenticatorPartial(tokenAudienceWebAdminPartial)). Post(webAdminTwoFactorRecoveryPath, s.handleWebAdminTwoFactorRecoveryPost) s.router.Group(func(router chi.Router) { @@ -1479,72 +1481,73 @@ func (s *httpdServer) setupWebAdminRoutes() { router.Use(jwtAuthenticatorWebAdmin) router.Get(webLogoutPath, s.handleWebAdminLogout) - router.With(s.refreshCookie, requireBuiltinLogin).Get(webAdminProfilePath, handleWebAdminProfile) - router.With(requireBuiltinLogin).Post(webAdminProfilePath, handleWebAdminProfilePost) - router.With(s.refreshCookie, requireBuiltinLogin).Get(webChangeAdminPwdPath, handleWebAdminChangePwd) - router.With(requireBuiltinLogin).Post(webChangeAdminPwdPath, s.handleWebAdminChangePwdPost) + router.With(s.refreshCookie, s.requireBuiltinLogin).Get(webAdminProfilePath, s.handleWebAdminProfile) + router.With(s.requireBuiltinLogin).Post(webAdminProfilePath, s.handleWebAdminProfilePost) + router.With(s.refreshCookie, s.requireBuiltinLogin).Get(webChangeAdminPwdPath, s.handleWebAdminChangePwd) + router.With(s.requireBuiltinLogin).Post(webChangeAdminPwdPath, s.handleWebAdminChangePwdPost) - router.With(s.refreshCookie, requireBuiltinLogin).Get(webAdminMFAPath, handleWebAdminMFA) - router.With(verifyCSRFHeader, requireBuiltinLogin).Post(webAdminTOTPGeneratePath, generateTOTPSecret) - router.With(verifyCSRFHeader, requireBuiltinLogin).Post(webAdminTOTPValidatePath, validateTOTPPasscode) - router.With(verifyCSRFHeader, requireBuiltinLogin).Post(webAdminTOTPSavePath, saveTOTPConfig) - router.With(verifyCSRFHeader, requireBuiltinLogin, s.refreshCookie).Get(webAdminRecoveryCodesPath, getRecoveryCodes) - router.With(verifyCSRFHeader, requireBuiltinLogin).Post(webAdminRecoveryCodesPath, generateRecoveryCodes) + router.With(s.refreshCookie, s.requireBuiltinLogin).Get(webAdminMFAPath, s.handleWebAdminMFA) + router.With(verifyCSRFHeader, s.requireBuiltinLogin).Post(webAdminTOTPGeneratePath, generateTOTPSecret) + router.With(verifyCSRFHeader, s.requireBuiltinLogin).Post(webAdminTOTPValidatePath, validateTOTPPasscode) + router.With(verifyCSRFHeader, s.requireBuiltinLogin).Post(webAdminTOTPSavePath, saveTOTPConfig) + router.With(verifyCSRFHeader, s.requireBuiltinLogin, s.refreshCookie).Get(webAdminRecoveryCodesPath, getRecoveryCodes) + router.With(verifyCSRFHeader, s.requireBuiltinLogin).Post(webAdminRecoveryCodesPath, generateRecoveryCodes) - router.With(checkPerm(dataprovider.PermAdminViewUsers), s.refreshCookie). - Get(webUsersPath, handleGetWebUsers) - router.With(checkPerm(dataprovider.PermAdminAddUsers), s.refreshCookie). - Get(webUserPath, handleWebAddUserGet) - router.With(checkPerm(dataprovider.PermAdminChangeUsers), s.refreshCookie). - Get(webUserPath+"/{username}", handleWebUpdateUserGet) - router.With(checkPerm(dataprovider.PermAdminAddUsers)).Post(webUserPath, handleWebAddUserPost) - router.With(checkPerm(dataprovider.PermAdminChangeUsers)).Post(webUserPath+"/{username}", handleWebUpdateUserPost) - router.With(checkPerm(dataprovider.PermAdminViewConnections), s.refreshCookie). - Get(webConnectionsPath, handleWebGetConnections) - router.With(checkPerm(dataprovider.PermAdminViewUsers), s.refreshCookie). - Get(webFoldersPath, handleWebGetFolders) - router.With(checkPerm(dataprovider.PermAdminAddUsers), s.refreshCookie). - Get(webFolderPath, handleWebAddFolderGet) - router.With(checkPerm(dataprovider.PermAdminAddUsers)).Post(webFolderPath, handleWebAddFolderPost) - router.With(checkPerm(dataprovider.PermAdminViewServerStatus), s.refreshCookie). - Get(webStatusPath, handleWebGetStatus) - router.With(checkPerm(dataprovider.PermAdminManageAdmins), s.refreshCookie). - Get(webAdminsPath, handleGetWebAdmins) - router.With(checkPerm(dataprovider.PermAdminManageAdmins), s.refreshCookie). - Get(webAdminPath, handleWebAddAdminGet) - router.With(checkPerm(dataprovider.PermAdminManageAdmins), s.refreshCookie). - Get(webAdminPath+"/{username}", handleWebUpdateAdminGet) - router.With(checkPerm(dataprovider.PermAdminManageAdmins)).Post(webAdminPath, handleWebAddAdminPost) - router.With(checkPerm(dataprovider.PermAdminManageAdmins)).Post(webAdminPath+"/{username}", - handleWebUpdateAdminPost) - router.With(checkPerm(dataprovider.PermAdminManageAdmins), verifyCSRFHeader). + router.With(s.checkPerm(dataprovider.PermAdminViewUsers), s.refreshCookie). + Get(webUsersPath, s.handleGetWebUsers) + router.With(s.checkPerm(dataprovider.PermAdminAddUsers), s.refreshCookie). + Get(webUserPath, s.handleWebAddUserGet) + router.With(s.checkPerm(dataprovider.PermAdminChangeUsers), s.refreshCookie). + Get(webUserPath+"/{username}", s.handleWebUpdateUserGet) + router.With(s.checkPerm(dataprovider.PermAdminAddUsers)).Post(webUserPath, s.handleWebAddUserPost) + router.With(s.checkPerm(dataprovider.PermAdminChangeUsers)).Post(webUserPath+"/{username}", + s.handleWebUpdateUserPost) + router.With(s.checkPerm(dataprovider.PermAdminViewConnections), s.refreshCookie). + Get(webConnectionsPath, s.handleWebGetConnections) + router.With(s.checkPerm(dataprovider.PermAdminViewUsers), s.refreshCookie). + Get(webFoldersPath, s.handleWebGetFolders) + router.With(s.checkPerm(dataprovider.PermAdminAddUsers), s.refreshCookie). + Get(webFolderPath, s.handleWebAddFolderGet) + router.With(s.checkPerm(dataprovider.PermAdminAddUsers)).Post(webFolderPath, s.handleWebAddFolderPost) + router.With(s.checkPerm(dataprovider.PermAdminViewServerStatus), s.refreshCookie). + Get(webStatusPath, s.handleWebGetStatus) + router.With(s.checkPerm(dataprovider.PermAdminManageAdmins), s.refreshCookie). + Get(webAdminsPath, s.handleGetWebAdmins) + router.With(s.checkPerm(dataprovider.PermAdminManageAdmins), s.refreshCookie). + Get(webAdminPath, s.handleWebAddAdminGet) + router.With(s.checkPerm(dataprovider.PermAdminManageAdmins), s.refreshCookie). + Get(webAdminPath+"/{username}", s.handleWebUpdateAdminGet) + router.With(s.checkPerm(dataprovider.PermAdminManageAdmins)).Post(webAdminPath, s.handleWebAddAdminPost) + router.With(s.checkPerm(dataprovider.PermAdminManageAdmins)).Post(webAdminPath+"/{username}", + s.handleWebUpdateAdminPost) + router.With(s.checkPerm(dataprovider.PermAdminManageAdmins), verifyCSRFHeader). Delete(webAdminPath+"/{username}", deleteAdmin) - router.With(checkPerm(dataprovider.PermAdminCloseConnections), verifyCSRFHeader). + router.With(s.checkPerm(dataprovider.PermAdminCloseConnections), verifyCSRFHeader). Delete(webConnectionsPath+"/{connectionID}", handleCloseConnection) - router.With(checkPerm(dataprovider.PermAdminChangeUsers), s.refreshCookie). - Get(webFolderPath+"/{name}", handleWebUpdateFolderGet) - router.With(checkPerm(dataprovider.PermAdminChangeUsers)).Post(webFolderPath+"/{name}", - handleWebUpdateFolderPost) - router.With(checkPerm(dataprovider.PermAdminDeleteUsers), verifyCSRFHeader). + router.With(s.checkPerm(dataprovider.PermAdminChangeUsers), s.refreshCookie). + Get(webFolderPath+"/{name}", s.handleWebUpdateFolderGet) + router.With(s.checkPerm(dataprovider.PermAdminChangeUsers)).Post(webFolderPath+"/{name}", + s.handleWebUpdateFolderPost) + router.With(s.checkPerm(dataprovider.PermAdminDeleteUsers), verifyCSRFHeader). Delete(webFolderPath+"/{name}", deleteFolder) - router.With(checkPerm(dataprovider.PermAdminQuotaScans), verifyCSRFHeader). + router.With(s.checkPerm(dataprovider.PermAdminQuotaScans), verifyCSRFHeader). Post(webScanVFolderPath+"/{name}", startFolderQuotaScan) - router.With(checkPerm(dataprovider.PermAdminDeleteUsers), verifyCSRFHeader). + router.With(s.checkPerm(dataprovider.PermAdminDeleteUsers), verifyCSRFHeader). Delete(webUserPath+"/{username}", deleteUser) - router.With(checkPerm(dataprovider.PermAdminQuotaScans), verifyCSRFHeader). + router.With(s.checkPerm(dataprovider.PermAdminQuotaScans), verifyCSRFHeader). Post(webQuotaScanPath+"/{username}", startUserQuotaScan) - router.With(checkPerm(dataprovider.PermAdminManageSystem)).Get(webMaintenancePath, handleWebMaintenance) - router.With(checkPerm(dataprovider.PermAdminManageSystem)).Get(webBackupPath, dumpData) - router.With(checkPerm(dataprovider.PermAdminManageSystem)).Post(webRestorePath, handleWebRestore) - router.With(checkPerm(dataprovider.PermAdminManageSystem), s.refreshCookie). - Get(webTemplateUser, handleWebTemplateUserGet) - router.With(checkPerm(dataprovider.PermAdminManageSystem)).Post(webTemplateUser, handleWebTemplateUserPost) - router.With(checkPerm(dataprovider.PermAdminManageSystem), s.refreshCookie). - Get(webTemplateFolder, handleWebTemplateFolderGet) - router.With(checkPerm(dataprovider.PermAdminManageSystem)).Post(webTemplateFolder, handleWebTemplateFolderPost) - router.With(checkPerm(dataprovider.PermAdminViewDefender)).Get(webDefenderPath, handleWebDefenderPage) - router.With(checkPerm(dataprovider.PermAdminViewDefender)).Get(webDefenderHostsPath, getDefenderHosts) - router.With(checkPerm(dataprovider.PermAdminManageDefender)).Delete(webDefenderHostsPath+"/{id}", + router.With(s.checkPerm(dataprovider.PermAdminManageSystem)).Get(webMaintenancePath, s.handleWebMaintenance) + router.With(s.checkPerm(dataprovider.PermAdminManageSystem)).Get(webBackupPath, dumpData) + router.With(s.checkPerm(dataprovider.PermAdminManageSystem)).Post(webRestorePath, s.handleWebRestore) + router.With(s.checkPerm(dataprovider.PermAdminManageSystem), s.refreshCookie). + Get(webTemplateUser, s.handleWebTemplateUserGet) + router.With(s.checkPerm(dataprovider.PermAdminManageSystem)).Post(webTemplateUser, s.handleWebTemplateUserPost) + router.With(s.checkPerm(dataprovider.PermAdminManageSystem), s.refreshCookie). + Get(webTemplateFolder, s.handleWebTemplateFolderGet) + router.With(s.checkPerm(dataprovider.PermAdminManageSystem)).Post(webTemplateFolder, s.handleWebTemplateFolderPost) + router.With(s.checkPerm(dataprovider.PermAdminViewDefender)).Get(webDefenderPath, s.handleWebDefenderPage) + router.With(s.checkPerm(dataprovider.PermAdminViewDefender)).Get(webDefenderHostsPath, getDefenderHosts) + router.With(s.checkPerm(dataprovider.PermAdminManageDefender)).Delete(webDefenderHostsPath+"/{id}", deleteDefenderHostByID) }) } diff --git a/httpd/web.go b/httpd/web.go index 2b819b11..99329181 100644 --- a/httpd/web.go +++ b/httpd/web.go @@ -32,6 +32,7 @@ type loginPage struct { AltLoginURL string ForgotPwdURL string OpenIDLoginURL string + ExtraCSS []CustomCSS } type twoFactorPage struct { @@ -41,6 +42,7 @@ type twoFactorPage struct { CSRFToken string StaticURL string RecoveryURL string + ExtraCSS []CustomCSS } type forgotPwdPage struct { @@ -49,6 +51,7 @@ type forgotPwdPage struct { CSRFToken string StaticURL string Title string + ExtraCSS []CustomCSS } type resetPwdPage struct { @@ -57,6 +60,7 @@ type resetPwdPage struct { CSRFToken string StaticURL string Title string + ExtraCSS []CustomCSS } func getSliceFromDelimitedValues(values, delimiter string) []string { diff --git a/httpd/webadmin.go b/httpd/webadmin.go index 494991f2..7af3d283 100644 --- a/httpd/webadmin.go +++ b/httpd/webadmin.go @@ -117,6 +117,7 @@ type basePage struct { HasDefender bool HasExternalLogin bool LoggedAdmin *dataprovider.Admin + ExtraCSS []CustomCSS } type usersPage struct { @@ -372,7 +373,7 @@ func loadAdminTemplates(templatesPath string) { adminTemplates[templateResetPassword] = resetPwdTmpl } -func getBasePageData(title, currentURL string, r *http.Request) basePage { +func (s *httpdServer) getBasePageData(title, currentURL string, r *http.Request) basePage { var csrfToken string if currentURL != "" { csrfToken = createCSRFToken() @@ -411,6 +412,7 @@ func getBasePageData(title, currentURL string, r *http.Request) basePage { HasDefender: common.Config.DefenderConfig.Enabled, HasExternalLogin: isLoggedInWithOIDC(r), CSRFToken: csrfToken, + ExtraCSS: s.binding.ExtraCSS, } } @@ -421,7 +423,9 @@ func renderAdminTemplate(w http.ResponseWriter, tmplName string, data interface{ } } -func renderMessagePage(w http.ResponseWriter, r *http.Request, title, body string, statusCode int, err error, message string) { +func (s *httpdServer) renderMessagePage(w http.ResponseWriter, r *http.Request, title, body string, statusCode int, + err error, message string, +) { var errorString string if body != "" { errorString = body + " " @@ -430,7 +434,7 @@ func renderMessagePage(w http.ResponseWriter, r *http.Request, title, body strin errorString += err.Error() } data := messagePage{ - basePage: getBasePageData(title, "", r), + basePage: s.getBasePageData(title, "", r), Error: errorString, Success: message, } @@ -438,45 +442,47 @@ func renderMessagePage(w http.ResponseWriter, r *http.Request, title, body strin renderAdminTemplate(w, templateMessage, data) } -func renderInternalServerErrorPage(w http.ResponseWriter, r *http.Request, err error) { - renderMessagePage(w, r, page500Title, page500Body, http.StatusInternalServerError, err, "") +func (s *httpdServer) renderInternalServerErrorPage(w http.ResponseWriter, r *http.Request, err error) { + s.renderMessagePage(w, r, page500Title, page500Body, http.StatusInternalServerError, err, "") } -func renderBadRequestPage(w http.ResponseWriter, r *http.Request, err error) { - renderMessagePage(w, r, page400Title, "", http.StatusBadRequest, err, "") +func (s *httpdServer) renderBadRequestPage(w http.ResponseWriter, r *http.Request, err error) { + s.renderMessagePage(w, r, page400Title, "", http.StatusBadRequest, err, "") } -func renderForbiddenPage(w http.ResponseWriter, r *http.Request, body string) { - renderMessagePage(w, r, page403Title, "", http.StatusForbidden, nil, body) +func (s *httpdServer) renderForbiddenPage(w http.ResponseWriter, r *http.Request, body string) { + s.renderMessagePage(w, r, page403Title, "", http.StatusForbidden, nil, body) } -func renderNotFoundPage(w http.ResponseWriter, r *http.Request, err error) { - renderMessagePage(w, r, page404Title, page404Body, http.StatusNotFound, err, "") +func (s *httpdServer) renderNotFoundPage(w http.ResponseWriter, r *http.Request, err error) { + s.renderMessagePage(w, r, page404Title, page404Body, http.StatusNotFound, err, "") } -func renderForgotPwdPage(w http.ResponseWriter, error string) { +func (s *httpdServer) renderForgotPwdPage(w http.ResponseWriter, error string) { data := forgotPwdPage{ CurrentURL: webAdminForgotPwdPath, Error: error, CSRFToken: createCSRFToken(), StaticURL: webStaticFilesPath, Title: pageForgotPwdTitle, + ExtraCSS: s.binding.ExtraCSS, } renderAdminTemplate(w, templateForgotPassword, data) } -func renderResetPwdPage(w http.ResponseWriter, error string) { +func (s *httpdServer) renderResetPwdPage(w http.ResponseWriter, error string) { data := resetPwdPage{ CurrentURL: webAdminResetPwdPath, Error: error, CSRFToken: createCSRFToken(), StaticURL: webStaticFilesPath, Title: pageResetPwdTitle, + ExtraCSS: s.binding.ExtraCSS, } renderAdminTemplate(w, templateResetPassword, data) } -func renderTwoFactorPage(w http.ResponseWriter, error string) { +func (s *httpdServer) renderTwoFactorPage(w http.ResponseWriter, error string) { data := twoFactorPage{ CurrentURL: webAdminTwoFactorPath, Version: version.Get().Version, @@ -484,24 +490,26 @@ func renderTwoFactorPage(w http.ResponseWriter, error string) { CSRFToken: createCSRFToken(), StaticURL: webStaticFilesPath, RecoveryURL: webAdminTwoFactorRecoveryPath, + ExtraCSS: s.binding.ExtraCSS, } renderAdminTemplate(w, templateTwoFactor, data) } -func renderTwoFactorRecoveryPage(w http.ResponseWriter, error string) { +func (s *httpdServer) renderTwoFactorRecoveryPage(w http.ResponseWriter, error string) { data := twoFactorPage{ CurrentURL: webAdminTwoFactorRecoveryPath, Version: version.Get().Version, Error: error, CSRFToken: createCSRFToken(), StaticURL: webStaticFilesPath, + ExtraCSS: s.binding.ExtraCSS, } renderAdminTemplate(w, templateTwoFactorRecovery, data) } -func renderMFAPage(w http.ResponseWriter, r *http.Request) { +func (s *httpdServer) renderMFAPage(w http.ResponseWriter, r *http.Request) { data := mfaPage{ - basePage: getBasePageData(pageMFATitle, webAdminMFAPath, r), + basePage: s.getBasePageData(pageMFATitle, webAdminMFAPath, r), TOTPConfigs: mfa.GetAvailableTOTPConfigNames(), GenerateTOTPURL: webAdminTOTPGeneratePath, ValidateTOTPURL: webAdminTOTPValidatePath, @@ -510,21 +518,21 @@ func renderMFAPage(w http.ResponseWriter, r *http.Request) { } admin, err := dataprovider.AdminExists(data.LoggedAdmin.Username) if err != nil { - renderInternalServerErrorPage(w, r, err) + s.renderInternalServerErrorPage(w, r, err) return } data.TOTPConfig = admin.Filters.TOTPConfig renderAdminTemplate(w, templateMFA, data) } -func renderProfilePage(w http.ResponseWriter, r *http.Request, error string) { +func (s *httpdServer) renderProfilePage(w http.ResponseWriter, r *http.Request, error string) { data := profilePage{ - basePage: getBasePageData(pageProfileTitle, webAdminProfilePath, r), + basePage: s.getBasePageData(pageProfileTitle, webAdminProfilePath, r), Error: error, } admin, err := dataprovider.AdminExists(data.LoggedAdmin.Username) if err != nil { - renderInternalServerErrorPage(w, r, err) + s.renderInternalServerErrorPage(w, r, err) return } data.AllowAPIKeyAuth = admin.Filters.AllowAPIKeyAuth @@ -534,18 +542,18 @@ func renderProfilePage(w http.ResponseWriter, r *http.Request, error string) { renderAdminTemplate(w, templateProfile, data) } -func renderChangePasswordPage(w http.ResponseWriter, r *http.Request, error string) { +func (s *httpdServer) renderChangePasswordPage(w http.ResponseWriter, r *http.Request, error string) { data := changePasswordPage{ - basePage: getBasePageData(pageChangePwdTitle, webChangeAdminPwdPath, r), + basePage: s.getBasePageData(pageChangePwdTitle, webChangeAdminPwdPath, r), Error: error, } renderAdminTemplate(w, templateChangePwd, data) } -func renderMaintenancePage(w http.ResponseWriter, r *http.Request, error string) { +func (s *httpdServer) renderMaintenancePage(w http.ResponseWriter, r *http.Request, error string) { data := maintenancePage{ - basePage: getBasePageData(pageMaintenanceTitle, webMaintenancePath, r), + basePage: s.getBasePageData(pageMaintenanceTitle, webMaintenancePath, r), BackupPath: webBackupPath, RestorePath: webRestorePath, Error: error, @@ -554,9 +562,9 @@ func renderMaintenancePage(w http.ResponseWriter, r *http.Request, error string) renderAdminTemplate(w, templateMaintenance, data) } -func renderAdminSetupPage(w http.ResponseWriter, r *http.Request, username, error string) { +func (s *httpdServer) renderAdminSetupPage(w http.ResponseWriter, r *http.Request, username, error string) { data := setupPage{ - basePage: getBasePageData(pageSetupTitle, webAdminSetupPath, r), + basePage: s.getBasePageData(pageSetupTitle, webAdminSetupPath, r), Username: username, HasInstallationCode: installationCode != "", InstallationCodeHint: installationCodeHint, @@ -566,7 +574,7 @@ func renderAdminSetupPage(w http.ResponseWriter, r *http.Request, username, erro renderAdminTemplate(w, templateSetup, data) } -func renderAddUpdateAdminPage(w http.ResponseWriter, r *http.Request, admin *dataprovider.Admin, +func (s *httpdServer) renderAddUpdateAdminPage(w http.ResponseWriter, r *http.Request, admin *dataprovider.Admin, error string, isAdd bool) { currentURL := webAdminPath title := "Add a new admin" @@ -575,7 +583,7 @@ func renderAddUpdateAdminPage(w http.ResponseWriter, r *http.Request, admin *dat title = "Update admin" } data := adminPage{ - basePage: getBasePageData(title, currentURL, r), + basePage: s.getBasePageData(title, currentURL, r), Admin: admin, Error: error, IsAdd: isAdd, @@ -584,8 +592,10 @@ func renderAddUpdateAdminPage(w http.ResponseWriter, r *http.Request, admin *dat renderAdminTemplate(w, templateAdmin, data) } -func renderUserPage(w http.ResponseWriter, r *http.Request, user *dataprovider.User, mode userPageMode, error string) { - folders, err := getWebVirtualFolders(w, r, defaultQueryLimit) +func (s *httpdServer) renderUserPage(w http.ResponseWriter, r *http.Request, user *dataprovider.User, + mode userPageMode, error string, +) { + folders, err := s.getWebVirtualFolders(w, r, defaultQueryLimit) if err != nil { return } @@ -607,7 +617,7 @@ func renderUserPage(w http.ResponseWriter, r *http.Request, user *dataprovider.U } user.FsConfig.RedactedSecret = redactedSecret data := userPage{ - basePage: getBasePageData(title, currentURL, r), + basePage: s.getBasePageData(title, currentURL, r), Mode: mode, Error: error, User: user, @@ -629,7 +639,7 @@ func renderUserPage(w http.ResponseWriter, r *http.Request, user *dataprovider.U renderAdminTemplate(w, templateUser, data) } -func renderFolderPage(w http.ResponseWriter, r *http.Request, folder vfs.BaseVirtualFolder, mode folderPageMode, error string) { +func (s *httpdServer) renderFolderPage(w http.ResponseWriter, r *http.Request, folder vfs.BaseVirtualFolder, mode folderPageMode, error string) { var title, currentURL string switch mode { case folderPageModeAdd: @@ -646,7 +656,7 @@ func renderFolderPage(w http.ResponseWriter, r *http.Request, folder vfs.BaseVir folder.FsConfig.SetEmptySecretsIfNil() data := folderPage{ - basePage: getBasePageData(title, currentURL, r), + basePage: s.getBasePageData(title, currentURL, r), Error: error, Folder: folder, Mode: mode, @@ -1360,92 +1370,91 @@ func getUserFromPostFields(r *http.Request) (dataprovider.User, error) { return user, err } -func handleWebAdminForgotPwd(w http.ResponseWriter, r *http.Request) { +func (s *httpdServer) handleWebAdminForgotPwd(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) if !smtp.IsEnabled() { - renderNotFoundPage(w, r, errors.New("this page does not exist")) + s.renderNotFoundPage(w, r, errors.New("this page does not exist")) return } - renderForgotPwdPage(w, "") + s.renderForgotPwdPage(w, "") } -func handleWebAdminForgotPwdPost(w http.ResponseWriter, r *http.Request) { +func (s *httpdServer) handleWebAdminForgotPwdPost(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) err := r.ParseForm() if err != nil { - renderForgotPwdPage(w, err.Error()) + s.renderForgotPwdPage(w, err.Error()) return } if err := verifyCSRFToken(r.Form.Get(csrfFormToken)); err != nil { - renderForbiddenPage(w, r, err.Error()) + s.renderForbiddenPage(w, r, err.Error()) return } - username := r.Form.Get("username") - err = handleForgotPassword(r, username, true) + err = handleForgotPassword(r, r.Form.Get("username"), true) if err != nil { if e, ok := err.(*util.ValidationError); ok { - renderForgotPwdPage(w, e.GetErrorString()) + s.renderForgotPwdPage(w, e.GetErrorString()) return } - renderForgotPwdPage(w, err.Error()) + s.renderForgotPwdPage(w, err.Error()) return } http.Redirect(w, r, webAdminResetPwdPath, http.StatusFound) } -func handleWebAdminPasswordReset(w http.ResponseWriter, r *http.Request) { +func (s *httpdServer) handleWebAdminPasswordReset(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, maxLoginBodySize) if !smtp.IsEnabled() { - renderNotFoundPage(w, r, errors.New("this page does not exist")) + s.renderNotFoundPage(w, r, errors.New("this page does not exist")) return } - renderResetPwdPage(w, "") + s.renderResetPwdPage(w, "") } -func handleWebAdminTwoFactor(w http.ResponseWriter, r *http.Request) { +func (s *httpdServer) handleWebAdminTwoFactor(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) - renderTwoFactorPage(w, "") + s.renderTwoFactorPage(w, "") } -func handleWebAdminTwoFactorRecovery(w http.ResponseWriter, r *http.Request) { +func (s *httpdServer) handleWebAdminTwoFactorRecovery(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) - renderTwoFactorRecoveryPage(w, "") + s.renderTwoFactorRecoveryPage(w, "") } -func handleWebAdminMFA(w http.ResponseWriter, r *http.Request) { +func (s *httpdServer) handleWebAdminMFA(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) - renderMFAPage(w, r) + s.renderMFAPage(w, r) } -func handleWebAdminProfile(w http.ResponseWriter, r *http.Request) { +func (s *httpdServer) handleWebAdminProfile(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) - renderProfilePage(w, r, "") + s.renderProfilePage(w, r, "") } -func handleWebAdminChangePwd(w http.ResponseWriter, r *http.Request) { +func (s *httpdServer) handleWebAdminChangePwd(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) - renderChangePasswordPage(w, r, "") + s.renderChangePasswordPage(w, r, "") } -func handleWebAdminProfilePost(w http.ResponseWriter, r *http.Request) { +func (s *httpdServer) handleWebAdminProfilePost(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) err := r.ParseForm() if err != nil { - renderProfilePage(w, r, err.Error()) + s.renderProfilePage(w, r, err.Error()) return } if err := verifyCSRFToken(r.Form.Get(csrfFormToken)); err != nil { - renderForbiddenPage(w, r, err.Error()) + s.renderForbiddenPage(w, r, err.Error()) return } claims, err := getTokenClaims(r) if err != nil || claims.Username == "" { - renderProfilePage(w, r, "Invalid token claims") + s.renderProfilePage(w, r, "Invalid token claims") return } admin, err := dataprovider.AdminExists(claims.Username) if err != nil { - renderProfilePage(w, r, err.Error()) + s.renderProfilePage(w, r, err.Error()) return } admin.Filters.AllowAPIKeyAuth = len(r.Form.Get("allow_api_key_auth")) > 0 @@ -1453,48 +1462,48 @@ func handleWebAdminProfilePost(w http.ResponseWriter, r *http.Request) { admin.Description = r.Form.Get("description") err = dataprovider.UpdateAdmin(&admin, dataprovider.ActionExecutorSelf, util.GetIPFromRemoteAddress(r.RemoteAddr)) if err != nil { - renderProfilePage(w, r, err.Error()) + s.renderProfilePage(w, r, err.Error()) return } - renderMessagePage(w, r, "Profile updated", "", http.StatusOK, nil, + s.renderMessagePage(w, r, "Profile updated", "", http.StatusOK, nil, "Your profile has been successfully updated") } -func handleWebMaintenance(w http.ResponseWriter, r *http.Request) { +func (s *httpdServer) handleWebMaintenance(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) - renderMaintenancePage(w, r, "") + s.renderMaintenancePage(w, r, "") } -func handleWebRestore(w http.ResponseWriter, r *http.Request) { +func (s *httpdServer) handleWebRestore(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, MaxRestoreSize) claims, err := getTokenClaims(r) if err != nil || claims.Username == "" { - renderBadRequestPage(w, r, errors.New("invalid token claims")) + s.renderBadRequestPage(w, r, errors.New("invalid token claims")) return } err = r.ParseMultipartForm(MaxRestoreSize) if err != nil { - renderMaintenancePage(w, r, err.Error()) + s.renderMaintenancePage(w, r, err.Error()) return } defer r.MultipartForm.RemoveAll() //nolint:errcheck if err := verifyCSRFToken(r.Form.Get(csrfFormToken)); err != nil { - renderForbiddenPage(w, r, err.Error()) + s.renderForbiddenPage(w, r, err.Error()) return } restoreMode, err := strconv.Atoi(r.Form.Get("mode")) if err != nil { - renderMaintenancePage(w, r, err.Error()) + s.renderMaintenancePage(w, r, err.Error()) return } scanQuota, err := strconv.Atoi(r.Form.Get("quota")) if err != nil { - renderMaintenancePage(w, r, err.Error()) + s.renderMaintenancePage(w, r, err.Error()) return } backupFile, _, err := r.FormFile("backup_file") if err != nil { - renderMaintenancePage(w, r, err.Error()) + s.renderMaintenancePage(w, r, err.Error()) return } defer backupFile.Close() @@ -1504,19 +1513,19 @@ func handleWebRestore(w http.ResponseWriter, r *http.Request) { if len(backupContent) == 0 { err = errors.New("backup file size must be greater than 0") } - renderMaintenancePage(w, r, err.Error()) + s.renderMaintenancePage(w, r, err.Error()) return } if err := restoreBackup(backupContent, "", scanQuota, restoreMode, claims.Username, util.GetIPFromRemoteAddress(r.RemoteAddr)); err != nil { - renderMaintenancePage(w, r, err.Error()) + s.renderMaintenancePage(w, r, err.Error()) return } - renderMessagePage(w, r, "Data restored", "", http.StatusOK, nil, "Your backup was successfully restored") + s.renderMessagePage(w, r, "Data restored", "", http.StatusOK, nil, "Your backup was successfully restored") } -func handleGetWebAdmins(w http.ResponseWriter, r *http.Request) { +func (s *httpdServer) handleGetWebAdmins(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) limit := defaultQueryLimit if _, ok := r.URL.Query()["qlimit"]; ok { @@ -1530,7 +1539,7 @@ func handleGetWebAdmins(w http.ResponseWriter, r *http.Request) { for { a, err := dataprovider.GetAdmins(limit, len(admins), dataprovider.OrderASC) if err != nil { - renderInternalServerErrorPage(w, r, err) + s.renderInternalServerErrorPage(w, r, err) return } admins = append(admins, a...) @@ -1539,84 +1548,84 @@ func handleGetWebAdmins(w http.ResponseWriter, r *http.Request) { } } data := adminsPage{ - basePage: getBasePageData(pageAdminsTitle, webAdminsPath, r), + basePage: s.getBasePageData(pageAdminsTitle, webAdminsPath, r), Admins: admins, } renderAdminTemplate(w, templateAdmins, data) } -func handleWebAdminSetupGet(w http.ResponseWriter, r *http.Request) { +func (s *httpdServer) handleWebAdminSetupGet(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, maxLoginBodySize) if dataprovider.HasAdmin() { http.Redirect(w, r, webAdminLoginPath, http.StatusFound) return } - renderAdminSetupPage(w, r, "", "") + s.renderAdminSetupPage(w, r, "", "") } -func handleWebAddAdminGet(w http.ResponseWriter, r *http.Request) { +func (s *httpdServer) handleWebAddAdminGet(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) admin := &dataprovider.Admin{Status: 1} - renderAddUpdateAdminPage(w, r, admin, "", true) + s.renderAddUpdateAdminPage(w, r, admin, "", true) } -func handleWebUpdateAdminGet(w http.ResponseWriter, r *http.Request) { +func (s *httpdServer) handleWebUpdateAdminGet(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) username := getURLParam(r, "username") admin, err := dataprovider.AdminExists(username) if err == nil { - renderAddUpdateAdminPage(w, r, &admin, "", false) + s.renderAddUpdateAdminPage(w, r, &admin, "", false) } else if _, ok := err.(*util.RecordNotFoundError); ok { - renderNotFoundPage(w, r, err) + s.renderNotFoundPage(w, r, err) } else { - renderInternalServerErrorPage(w, r, err) + s.renderInternalServerErrorPage(w, r, err) } } -func handleWebAddAdminPost(w http.ResponseWriter, r *http.Request) { +func (s *httpdServer) handleWebAddAdminPost(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) claims, err := getTokenClaims(r) if err != nil || claims.Username == "" { - renderBadRequestPage(w, r, errors.New("invalid token claims")) + s.renderBadRequestPage(w, r, errors.New("invalid token claims")) return } admin, err := getAdminFromPostFields(r) if err != nil { - renderAddUpdateAdminPage(w, r, &admin, err.Error(), true) + s.renderAddUpdateAdminPage(w, r, &admin, err.Error(), true) return } if err := verifyCSRFToken(r.Form.Get(csrfFormToken)); err != nil { - renderForbiddenPage(w, r, err.Error()) + s.renderForbiddenPage(w, r, err.Error()) return } err = dataprovider.AddAdmin(&admin, claims.Username, util.GetIPFromRemoteAddress(r.Method)) if err != nil { - renderAddUpdateAdminPage(w, r, &admin, err.Error(), true) + s.renderAddUpdateAdminPage(w, r, &admin, err.Error(), true) return } http.Redirect(w, r, webAdminsPath, http.StatusSeeOther) } -func handleWebUpdateAdminPost(w http.ResponseWriter, r *http.Request) { +func (s *httpdServer) handleWebUpdateAdminPost(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) username := getURLParam(r, "username") admin, err := dataprovider.AdminExists(username) if _, ok := err.(*util.RecordNotFoundError); ok { - renderNotFoundPage(w, r, err) + s.renderNotFoundPage(w, r, err) return } else if err != nil { - renderInternalServerErrorPage(w, r, err) + s.renderInternalServerErrorPage(w, r, err) return } updatedAdmin, err := getAdminFromPostFields(r) if err != nil { - renderAddUpdateAdminPage(w, r, &updatedAdmin, err.Error(), false) + s.renderAddUpdateAdminPage(w, r, &updatedAdmin, err.Error(), false) return } if err := verifyCSRFToken(r.Form.Get(csrfFormToken)); err != nil { - renderForbiddenPage(w, r, err.Error()) + s.renderForbiddenPage(w, r, err.Error()) return } updatedAdmin.ID = admin.ID @@ -1628,52 +1637,54 @@ func handleWebUpdateAdminPost(w http.ResponseWriter, r *http.Request) { updatedAdmin.Filters.RecoveryCodes = admin.Filters.RecoveryCodes claims, err := getTokenClaims(r) if err != nil || claims.Username == "" { - renderAddUpdateAdminPage(w, r, &updatedAdmin, "Invalid token claims", false) + s.renderAddUpdateAdminPage(w, r, &updatedAdmin, "Invalid token claims", false) return } if username == claims.Username { if claims.isCriticalPermRemoved(updatedAdmin.Permissions) { - renderAddUpdateAdminPage(w, r, &updatedAdmin, "You cannot remove these permissions to yourself", false) + s.renderAddUpdateAdminPage(w, r, &updatedAdmin, "You cannot remove these permissions to yourself", false) return } if updatedAdmin.Status == 0 { - renderAddUpdateAdminPage(w, r, &updatedAdmin, "You cannot disable yourself", false) + s.renderAddUpdateAdminPage(w, r, &updatedAdmin, "You cannot disable yourself", false) return } } err = dataprovider.UpdateAdmin(&updatedAdmin, claims.Username, util.GetIPFromRemoteAddress(r.RemoteAddr)) if err != nil { - renderAddUpdateAdminPage(w, r, &admin, err.Error(), false) + s.renderAddUpdateAdminPage(w, r, &admin, err.Error(), false) return } http.Redirect(w, r, webAdminsPath, http.StatusSeeOther) } -func handleWebDefenderPage(w http.ResponseWriter, r *http.Request) { +func (s *httpdServer) handleWebDefenderPage(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) data := defenderHostsPage{ - basePage: getBasePageData(pageDefenderTitle, webDefenderPath, r), + basePage: s.getBasePageData(pageDefenderTitle, webDefenderPath, r), DefenderHostsURL: webDefenderHostsPath, } renderAdminTemplate(w, templateDefender, data) } -func handleGetWebUsers(w http.ResponseWriter, r *http.Request) { +func (s *httpdServer) handleGetWebUsers(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) - limit := defaultQueryLimit + var limit int if _, ok := r.URL.Query()["qlimit"]; ok { var err error limit, err = strconv.Atoi(r.URL.Query().Get("qlimit")) if err != nil { limit = defaultQueryLimit } + } else { + limit = defaultQueryLimit } users := make([]dataprovider.User, 0, limit) for { u, err := dataprovider.GetUsers(limit, len(users), dataprovider.OrderASC) if err != nil { - renderInternalServerErrorPage(w, r, err) + s.renderInternalServerErrorPage(w, r, err) return } users = append(users, u...) @@ -1682,48 +1693,48 @@ func handleGetWebUsers(w http.ResponseWriter, r *http.Request) { } } data := usersPage{ - basePage: getBasePageData(pageUsersTitle, webUsersPath, r), + basePage: s.getBasePageData(pageUsersTitle, webUsersPath, r), Users: users, } renderAdminTemplate(w, templateUsers, data) } -func handleWebTemplateFolderGet(w http.ResponseWriter, r *http.Request) { +func (s *httpdServer) handleWebTemplateFolderGet(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) if r.URL.Query().Get("from") != "" { name := r.URL.Query().Get("from") folder, err := dataprovider.GetFolderByName(name) if err == nil { folder.FsConfig.SetEmptySecrets() - renderFolderPage(w, r, folder, folderPageModeTemplate, "") + s.renderFolderPage(w, r, folder, folderPageModeTemplate, "") } else if _, ok := err.(*util.RecordNotFoundError); ok { - renderNotFoundPage(w, r, err) + s.renderNotFoundPage(w, r, err) } else { - renderInternalServerErrorPage(w, r, err) + s.renderInternalServerErrorPage(w, r, err) } } else { folder := vfs.BaseVirtualFolder{} - renderFolderPage(w, r, folder, folderPageModeTemplate, "") + s.renderFolderPage(w, r, folder, folderPageModeTemplate, "") } } -func handleWebTemplateFolderPost(w http.ResponseWriter, r *http.Request) { +func (s *httpdServer) handleWebTemplateFolderPost(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) claims, err := getTokenClaims(r) if err != nil || claims.Username == "" { - renderBadRequestPage(w, r, errors.New("invalid token claims")) + s.renderBadRequestPage(w, r, errors.New("invalid token claims")) return } templateFolder := vfs.BaseVirtualFolder{} err = r.ParseMultipartForm(maxRequestSize) if err != nil { - renderMessagePage(w, r, "Error parsing folders fields", "", http.StatusBadRequest, err, "") + s.renderMessagePage(w, r, "Error parsing folders fields", "", http.StatusBadRequest, err, "") return } defer r.MultipartForm.RemoveAll() //nolint:errcheck if err := verifyCSRFToken(r.Form.Get(csrfFormToken)); err != nil { - renderForbiddenPage(w, r, err.Error()) + s.renderForbiddenPage(w, r, err.Error()) return } @@ -1731,7 +1742,7 @@ func handleWebTemplateFolderPost(w http.ResponseWriter, r *http.Request) { templateFolder.Description = r.Form.Get("description") fsConfig, err := getFsConfigFromPostFields(r) if err != nil { - renderMessagePage(w, r, "Error parsing folders fields", "", http.StatusBadRequest, err, "") + s.renderMessagePage(w, r, "Error parsing folders fields", "", http.StatusBadRequest, err, "") return } templateFolder.FsConfig = fsConfig @@ -1743,7 +1754,7 @@ func handleWebTemplateFolderPost(w http.ResponseWriter, r *http.Request) { for _, tmpl := range foldersFields { f := getFolderFromTemplate(templateFolder, tmpl) if err := dataprovider.ValidateFolder(&f); err != nil { - renderMessagePage(w, r, "Folder validation error", fmt.Sprintf("Error validating folder %#v", f.Name), + s.renderMessagePage(w, r, "Folder validation error", fmt.Sprintf("Error validating folder %#v", f.Name), http.StatusBadRequest, err, "") return } @@ -1751,7 +1762,7 @@ func handleWebTemplateFolderPost(w http.ResponseWriter, r *http.Request) { } if len(dump.Folders) == 0 { - renderMessagePage(w, r, "No folders defined", "No valid folders defined, unable to complete the requested action", + s.renderMessagePage(w, r, "No folders defined", "No valid folders defined, unable to complete the requested action", http.StatusBadRequest, nil, "") return } @@ -1762,14 +1773,14 @@ func handleWebTemplateFolderPost(w http.ResponseWriter, r *http.Request) { return } if err = RestoreFolders(dump.Folders, "", 1, 0, claims.Username, util.GetIPFromRemoteAddress(r.RemoteAddr)); err != nil { - renderMessagePage(w, r, "Unable to save folders", "Cannot save the defined folders:", + s.renderMessagePage(w, r, "Unable to save folders", "Cannot save the defined folders:", getRespStatus(err), err, "") return } http.Redirect(w, r, webFoldersPath, http.StatusSeeOther) } -func handleWebTemplateUserGet(w http.ResponseWriter, r *http.Request) { +func (s *httpdServer) handleWebTemplateUserGet(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) if r.URL.Query().Get("from") != "" { username := r.URL.Query().Get("from") @@ -1779,11 +1790,11 @@ func handleWebTemplateUserGet(w http.ResponseWriter, r *http.Request) { user.PublicKeys = nil user.Email = "" user.Description = "" - renderUserPage(w, r, &user, userPageModeTemplate, "") + s.renderUserPage(w, r, &user, userPageModeTemplate, "") } else if _, ok := err.(*util.RecordNotFoundError); ok { - renderNotFoundPage(w, r, err) + s.renderNotFoundPage(w, r, err) } else { - renderInternalServerErrorPage(w, r, err) + s.renderInternalServerErrorPage(w, r, err) } } else { user := dataprovider.User{BaseUser: sdk.BaseUser{ @@ -1792,24 +1803,24 @@ func handleWebTemplateUserGet(w http.ResponseWriter, r *http.Request) { "/": {dataprovider.PermAny}, }, }} - renderUserPage(w, r, &user, userPageModeTemplate, "") + s.renderUserPage(w, r, &user, userPageModeTemplate, "") } } -func handleWebTemplateUserPost(w http.ResponseWriter, r *http.Request) { +func (s *httpdServer) handleWebTemplateUserPost(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) claims, err := getTokenClaims(r) if err != nil || claims.Username == "" { - renderBadRequestPage(w, r, errors.New("invalid token claims")) + s.renderBadRequestPage(w, r, errors.New("invalid token claims")) return } templateUser, err := getUserFromPostFields(r) if err != nil { - renderMessagePage(w, r, "Error parsing user fields", "", http.StatusBadRequest, err, "") + s.renderMessagePage(w, r, "Error parsing user fields", "", http.StatusBadRequest, err, "") return } if err := verifyCSRFToken(r.Form.Get(csrfFormToken)); err != nil { - renderForbiddenPage(w, r, err.Error()) + s.renderForbiddenPage(w, r, err.Error()) return } @@ -1820,7 +1831,7 @@ func handleWebTemplateUserPost(w http.ResponseWriter, r *http.Request) { for _, tmpl := range userTmplFields { u := getUserFromTemplate(templateUser, tmpl) if err := dataprovider.ValidateUser(&u); err != nil { - renderMessagePage(w, r, "User validation error", fmt.Sprintf("Error validating user %#v", u.Username), + s.renderMessagePage(w, r, "User validation error", fmt.Sprintf("Error validating user %#v", u.Username), http.StatusBadRequest, err, "") return } @@ -1833,7 +1844,7 @@ func handleWebTemplateUserPost(w http.ResponseWriter, r *http.Request) { } if len(dump.Users) == 0 { - renderMessagePage(w, r, "No users defined", "No valid users defined, unable to complete the requested action", + s.renderMessagePage(w, r, "No users defined", "No valid users defined, unable to complete the requested action", http.StatusBadRequest, nil, "") return } @@ -1844,14 +1855,14 @@ func handleWebTemplateUserPost(w http.ResponseWriter, r *http.Request) { return } if err = RestoreUsers(dump.Users, "", 1, 0, claims.Username, util.GetIPFromRemoteAddress(r.RemoteAddr)); err != nil { - renderMessagePage(w, r, "Unable to save users", "Cannot save the defined users:", + s.renderMessagePage(w, r, "Unable to save users", "Cannot save the defined users:", getRespStatus(err), err, "") return } http.Redirect(w, r, webUsersPath, http.StatusSeeOther) } -func handleWebAddUserGet(w http.ResponseWriter, r *http.Request) { +func (s *httpdServer) handleWebAddUserGet(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) user := dataprovider.User{BaseUser: sdk.BaseUser{ Status: 1, @@ -1859,69 +1870,69 @@ func handleWebAddUserGet(w http.ResponseWriter, r *http.Request) { "/": {dataprovider.PermAny}, }, }} - renderUserPage(w, r, &user, userPageModeAdd, "") + s.renderUserPage(w, r, &user, userPageModeAdd, "") } -func handleWebUpdateUserGet(w http.ResponseWriter, r *http.Request) { +func (s *httpdServer) handleWebUpdateUserGet(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) username := getURLParam(r, "username") user, err := dataprovider.UserExists(username) if err == nil { - renderUserPage(w, r, &user, userPageModeUpdate, "") + s.renderUserPage(w, r, &user, userPageModeUpdate, "") } else if _, ok := err.(*util.RecordNotFoundError); ok { - renderNotFoundPage(w, r, err) + s.renderNotFoundPage(w, r, err) } else { - renderInternalServerErrorPage(w, r, err) + s.renderInternalServerErrorPage(w, r, err) } } -func handleWebAddUserPost(w http.ResponseWriter, r *http.Request) { +func (s *httpdServer) handleWebAddUserPost(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) claims, err := getTokenClaims(r) if err != nil || claims.Username == "" { - renderBadRequestPage(w, r, errors.New("invalid token claims")) + s.renderBadRequestPage(w, r, errors.New("invalid token claims")) return } user, err := getUserFromPostFields(r) if err != nil { - renderUserPage(w, r, &user, userPageModeAdd, err.Error()) + s.renderUserPage(w, r, &user, userPageModeAdd, err.Error()) return } if err := verifyCSRFToken(r.Form.Get(csrfFormToken)); err != nil { - renderForbiddenPage(w, r, err.Error()) + s.renderForbiddenPage(w, r, err.Error()) return } err = dataprovider.AddUser(&user, claims.Username, util.GetIPFromRemoteAddress(r.RemoteAddr)) if err == nil { http.Redirect(w, r, webUsersPath, http.StatusSeeOther) } else { - renderUserPage(w, r, &user, userPageModeAdd, err.Error()) + s.renderUserPage(w, r, &user, userPageModeAdd, err.Error()) } } -func handleWebUpdateUserPost(w http.ResponseWriter, r *http.Request) { +func (s *httpdServer) handleWebUpdateUserPost(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) claims, err := getTokenClaims(r) if err != nil || claims.Username == "" { - renderBadRequestPage(w, r, errors.New("invalid token claims")) + s.renderBadRequestPage(w, r, errors.New("invalid token claims")) return } username := getURLParam(r, "username") user, err := dataprovider.UserExists(username) if _, ok := err.(*util.RecordNotFoundError); ok { - renderNotFoundPage(w, r, err) + s.renderNotFoundPage(w, r, err) return } else if err != nil { - renderInternalServerErrorPage(w, r, err) + s.renderInternalServerErrorPage(w, r, err) return } updatedUser, err := getUserFromPostFields(r) if err != nil { - renderUserPage(w, r, &user, userPageModeUpdate, err.Error()) + s.renderUserPage(w, r, &user, userPageModeUpdate, err.Error()) return } if err := verifyCSRFToken(r.Form.Get(csrfFormToken)); err != nil { - renderForbiddenPage(w, r, err.Error()) + s.renderForbiddenPage(w, r, err.Error()) return } updatedUser.ID = user.ID @@ -1943,46 +1954,46 @@ func handleWebUpdateUserPost(w http.ResponseWriter, r *http.Request) { } http.Redirect(w, r, webUsersPath, http.StatusSeeOther) } else { - renderUserPage(w, r, &user, userPageModeUpdate, err.Error()) + s.renderUserPage(w, r, &user, userPageModeUpdate, err.Error()) } } -func handleWebGetStatus(w http.ResponseWriter, r *http.Request) { +func (s *httpdServer) handleWebGetStatus(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) data := statusPage{ - basePage: getBasePageData(pageStatusTitle, webStatusPath, r), + basePage: s.getBasePageData(pageStatusTitle, webStatusPath, r), Status: getServicesStatus(), } renderAdminTemplate(w, templateStatus, data) } -func handleWebGetConnections(w http.ResponseWriter, r *http.Request) { +func (s *httpdServer) handleWebGetConnections(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) connectionStats := common.Connections.GetStats() data := connectionsPage{ - basePage: getBasePageData(pageConnectionsTitle, webConnectionsPath, r), + basePage: s.getBasePageData(pageConnectionsTitle, webConnectionsPath, r), Connections: connectionStats, } renderAdminTemplate(w, templateConnections, data) } -func handleWebAddFolderGet(w http.ResponseWriter, r *http.Request) { +func (s *httpdServer) handleWebAddFolderGet(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) - renderFolderPage(w, r, vfs.BaseVirtualFolder{}, folderPageModeAdd, "") + s.renderFolderPage(w, r, vfs.BaseVirtualFolder{}, folderPageModeAdd, "") } -func handleWebAddFolderPost(w http.ResponseWriter, r *http.Request) { +func (s *httpdServer) handleWebAddFolderPost(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) folder := vfs.BaseVirtualFolder{} err := r.ParseMultipartForm(maxRequestSize) if err != nil { - renderFolderPage(w, r, folder, folderPageModeAdd, err.Error()) + s.renderFolderPage(w, r, folder, folderPageModeAdd, err.Error()) return } defer r.MultipartForm.RemoveAll() //nolint:errcheck if err := verifyCSRFToken(r.Form.Get(csrfFormToken)); err != nil { - renderForbiddenPage(w, r, err.Error()) + s.renderForbiddenPage(w, r, err.Error()) return } folder.MappedPath = r.Form.Get("mapped_path") @@ -1990,7 +2001,7 @@ func handleWebAddFolderPost(w http.ResponseWriter, r *http.Request) { folder.Description = r.Form.Get("description") fsConfig, err := getFsConfigFromPostFields(r) if err != nil { - renderFolderPage(w, r, folder, folderPageModeAdd, err.Error()) + s.renderFolderPage(w, r, folder, folderPageModeAdd, err.Error()) return } folder.FsConfig = fsConfig @@ -1999,54 +2010,54 @@ func handleWebAddFolderPost(w http.ResponseWriter, r *http.Request) { if err == nil { http.Redirect(w, r, webFoldersPath, http.StatusSeeOther) } else { - renderFolderPage(w, r, folder, folderPageModeAdd, err.Error()) + s.renderFolderPage(w, r, folder, folderPageModeAdd, err.Error()) } } -func handleWebUpdateFolderGet(w http.ResponseWriter, r *http.Request) { +func (s *httpdServer) handleWebUpdateFolderGet(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) name := getURLParam(r, "name") folder, err := dataprovider.GetFolderByName(name) if err == nil { - renderFolderPage(w, r, folder, folderPageModeUpdate, "") + s.renderFolderPage(w, r, folder, folderPageModeUpdate, "") } else if _, ok := err.(*util.RecordNotFoundError); ok { - renderNotFoundPage(w, r, err) + s.renderNotFoundPage(w, r, err) } else { - renderInternalServerErrorPage(w, r, err) + s.renderInternalServerErrorPage(w, r, err) } } -func handleWebUpdateFolderPost(w http.ResponseWriter, r *http.Request) { +func (s *httpdServer) handleWebUpdateFolderPost(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) claims, err := getTokenClaims(r) if err != nil || claims.Username == "" { - renderBadRequestPage(w, r, errors.New("invalid token claims")) + s.renderBadRequestPage(w, r, errors.New("invalid token claims")) return } name := getURLParam(r, "name") folder, err := dataprovider.GetFolderByName(name) if _, ok := err.(*util.RecordNotFoundError); ok { - renderNotFoundPage(w, r, err) + s.renderNotFoundPage(w, r, err) return } else if err != nil { - renderInternalServerErrorPage(w, r, err) + s.renderInternalServerErrorPage(w, r, err) return } err = r.ParseMultipartForm(maxRequestSize) if err != nil { - renderFolderPage(w, r, folder, folderPageModeUpdate, err.Error()) + s.renderFolderPage(w, r, folder, folderPageModeUpdate, err.Error()) return } defer r.MultipartForm.RemoveAll() //nolint:errcheck if err := verifyCSRFToken(r.Form.Get(csrfFormToken)); err != nil { - renderForbiddenPage(w, r, err.Error()) + s.renderForbiddenPage(w, r, err.Error()) return } fsConfig, err := getFsConfigFromPostFields(r) if err != nil { - renderFolderPage(w, r, folder, folderPageModeUpdate, err.Error()) + s.renderFolderPage(w, r, folder, folderPageModeUpdate, err.Error()) return } updatedFolder := &vfs.BaseVirtualFolder{ @@ -2063,18 +2074,18 @@ func handleWebUpdateFolderPost(w http.ResponseWriter, r *http.Request) { err = dataprovider.UpdateFolder(updatedFolder, folder.Users, claims.Username, util.GetIPFromRemoteAddress(r.RemoteAddr)) if err != nil { - renderFolderPage(w, r, folder, folderPageModeUpdate, err.Error()) + s.renderFolderPage(w, r, folder, folderPageModeUpdate, err.Error()) return } http.Redirect(w, r, webFoldersPath, http.StatusSeeOther) } -func getWebVirtualFolders(w http.ResponseWriter, r *http.Request, limit int) ([]vfs.BaseVirtualFolder, error) { +func (s *httpdServer) getWebVirtualFolders(w http.ResponseWriter, r *http.Request, limit int) ([]vfs.BaseVirtualFolder, error) { folders := make([]vfs.BaseVirtualFolder, 0, limit) for { f, err := dataprovider.GetFolders(limit, len(folders), dataprovider.OrderASC) if err != nil { - renderInternalServerErrorPage(w, r, err) + s.renderInternalServerErrorPage(w, r, err) return folders, err } folders = append(folders, f...) @@ -2085,7 +2096,7 @@ func getWebVirtualFolders(w http.ResponseWriter, r *http.Request, limit int) ([] return folders, nil } -func handleWebGetFolders(w http.ResponseWriter, r *http.Request) { +func (s *httpdServer) handleWebGetFolders(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) limit := defaultQueryLimit if _, ok := r.URL.Query()["qlimit"]; ok { @@ -2095,13 +2106,13 @@ func handleWebGetFolders(w http.ResponseWriter, r *http.Request) { limit = defaultQueryLimit } } - folders, err := getWebVirtualFolders(w, r, limit) + folders, err := s.getWebVirtualFolders(w, r, limit) if err != nil { return } data := foldersPage{ - basePage: getBasePageData(pageFoldersTitle, webFoldersPath, r), + basePage: s.getBasePageData(pageFoldersTitle, webFoldersPath, r), Folders: folders, } renderAdminTemplate(w, templateFolders, data) diff --git a/httpd/webclient.go b/httpd/webclient.go index 33ae727b..c0637dba 100644 --- a/httpd/webclient.go +++ b/httpd/webclient.go @@ -98,6 +98,7 @@ type baseClientPage struct { CSRFToken string HasExternalLogin bool LoggedUser *dataprovider.User + ExtraCSS []CustomCSS } type dirMapping struct { @@ -109,6 +110,7 @@ type viewPDFPage struct { Title string URL string StaticURL string + ExtraCSS []CustomCSS } type editFilePage struct { @@ -309,7 +311,7 @@ func loadClientTemplates(templatesPath string) { clientTemplates[templateUploadToShare] = shareUploadTmpl } -func getBaseClientPageData(title, currentURL string, r *http.Request) baseClientPage { +func (s *httpdServer) getBaseClientPageData(title, currentURL string, r *http.Request) baseClientPage { var csrfToken string if currentURL != "" { csrfToken = createCSRFToken() @@ -335,27 +337,30 @@ func getBaseClientPageData(title, currentURL string, r *http.Request) baseClient CSRFToken: csrfToken, HasExternalLogin: isLoggedInWithOIDC(r), LoggedUser: getUserFromToken(r), + ExtraCSS: s.binding.ExtraCSS, } } -func renderClientForgotPwdPage(w http.ResponseWriter, error string) { +func (s *httpdServer) renderClientForgotPwdPage(w http.ResponseWriter, error string) { data := forgotPwdPage{ CurrentURL: webClientForgotPwdPath, Error: error, CSRFToken: createCSRFToken(), StaticURL: webStaticFilesPath, Title: pageClientForgotPwdTitle, + ExtraCSS: s.binding.ExtraCSS, } renderClientTemplate(w, templateForgotPassword, data) } -func renderClientResetPwdPage(w http.ResponseWriter, error string) { +func (s *httpdServer) renderClientResetPwdPage(w http.ResponseWriter, error string) { data := resetPwdPage{ CurrentURL: webClientResetPwdPath, Error: error, CSRFToken: createCSRFToken(), StaticURL: webStaticFilesPath, Title: pageClientResetPwdTitle, + ExtraCSS: s.binding.ExtraCSS, } renderClientTemplate(w, templateResetPassword, data) } @@ -367,7 +372,7 @@ func renderClientTemplate(w http.ResponseWriter, tmplName string, data interface } } -func renderClientMessagePage(w http.ResponseWriter, r *http.Request, title, body string, statusCode int, err error, message string) { +func (s *httpdServer) renderClientMessagePage(w http.ResponseWriter, r *http.Request, title, body string, statusCode int, err error, message string) { var errorString string if body != "" { errorString = body + " " @@ -376,7 +381,7 @@ func renderClientMessagePage(w http.ResponseWriter, r *http.Request, title, body errorString += err.Error() } data := clientMessagePage{ - baseClientPage: getBaseClientPageData(title, "", r), + baseClientPage: s.getBaseClientPageData(title, "", r), Error: errorString, Success: message, } @@ -384,23 +389,23 @@ func renderClientMessagePage(w http.ResponseWriter, r *http.Request, title, body renderClientTemplate(w, templateClientMessage, data) } -func renderClientInternalServerErrorPage(w http.ResponseWriter, r *http.Request, err error) { - renderClientMessagePage(w, r, page500Title, page500Body, http.StatusInternalServerError, err, "") +func (s *httpdServer) renderClientInternalServerErrorPage(w http.ResponseWriter, r *http.Request, err error) { + s.renderClientMessagePage(w, r, page500Title, page500Body, http.StatusInternalServerError, err, "") } -func renderClientBadRequestPage(w http.ResponseWriter, r *http.Request, err error) { - renderClientMessagePage(w, r, page400Title, "", http.StatusBadRequest, err, "") +func (s *httpdServer) renderClientBadRequestPage(w http.ResponseWriter, r *http.Request, err error) { + s.renderClientMessagePage(w, r, page400Title, "", http.StatusBadRequest, err, "") } -func renderClientForbiddenPage(w http.ResponseWriter, r *http.Request, body string) { - renderClientMessagePage(w, r, page403Title, "", http.StatusForbidden, nil, body) +func (s *httpdServer) renderClientForbiddenPage(w http.ResponseWriter, r *http.Request, body string) { + s.renderClientMessagePage(w, r, page403Title, "", http.StatusForbidden, nil, body) } -func renderClientNotFoundPage(w http.ResponseWriter, r *http.Request, err error) { - renderClientMessagePage(w, r, page404Title, page404Body, http.StatusNotFound, err, "") +func (s *httpdServer) renderClientNotFoundPage(w http.ResponseWriter, r *http.Request, err error) { + s.renderClientMessagePage(w, r, page404Title, page404Body, http.StatusNotFound, err, "") } -func renderClientTwoFactorPage(w http.ResponseWriter, error string) { +func (s *httpdServer) renderClientTwoFactorPage(w http.ResponseWriter, error string) { data := twoFactorPage{ CurrentURL: webClientTwoFactorPath, Version: version.Get().Version, @@ -408,24 +413,26 @@ func renderClientTwoFactorPage(w http.ResponseWriter, error string) { CSRFToken: createCSRFToken(), StaticURL: webStaticFilesPath, RecoveryURL: webClientTwoFactorRecoveryPath, + ExtraCSS: s.binding.ExtraCSS, } renderClientTemplate(w, templateTwoFactor, data) } -func renderClientTwoFactorRecoveryPage(w http.ResponseWriter, error string) { +func (s *httpdServer) renderClientTwoFactorRecoveryPage(w http.ResponseWriter, error string) { data := twoFactorPage{ CurrentURL: webClientTwoFactorRecoveryPath, Version: version.Get().Version, Error: error, CSRFToken: createCSRFToken(), StaticURL: webStaticFilesPath, + ExtraCSS: s.binding.ExtraCSS, } renderClientTemplate(w, templateTwoFactorRecovery, data) } -func renderClientMFAPage(w http.ResponseWriter, r *http.Request) { +func (s *httpdServer) renderClientMFAPage(w http.ResponseWriter, r *http.Request) { data := clientMFAPage{ - baseClientPage: getBaseClientPageData(pageMFATitle, webClientMFAPath, r), + baseClientPage: s.getBaseClientPageData(pageMFATitle, webClientMFAPath, r), TOTPConfigs: mfa.GetAvailableTOTPConfigNames(), GenerateTOTPURL: webClientTOTPGeneratePath, ValidateTOTPURL: webClientTOTPValidatePath, @@ -435,16 +442,16 @@ func renderClientMFAPage(w http.ResponseWriter, r *http.Request) { } user, err := dataprovider.UserExists(data.LoggedUser.Username) if err != nil { - renderInternalServerErrorPage(w, r, err) + s.renderInternalServerErrorPage(w, r, err) return } data.TOTPConfig = user.Filters.TOTPConfig renderClientTemplate(w, templateClientMFA, data) } -func renderEditFilePage(w http.ResponseWriter, r *http.Request, fileName, fileData string, readOnly bool) { +func (s *httpdServer) renderEditFilePage(w http.ResponseWriter, r *http.Request, fileName, fileData string, readOnly bool) { data := editFilePage{ - baseClientPage: getBaseClientPageData(pageClientEditFileTitle, webClientEditFilePath, r), + baseClientPage: s.getBaseClientPageData(pageClientEditFileTitle, webClientEditFilePath, r), Path: fileName, Name: path.Base(fileName), CurrentDir: path.Dir(fileName), @@ -456,7 +463,7 @@ func renderEditFilePage(w http.ResponseWriter, r *http.Request, fileName, fileDa renderClientTemplate(w, templateClientEditFile, data) } -func renderAddUpdateSharePage(w http.ResponseWriter, r *http.Request, share *dataprovider.Share, +func (s *httpdServer) renderAddUpdateSharePage(w http.ResponseWriter, r *http.Request, share *dataprovider.Share, error string, isAdd bool) { currentURL := webClientSharePath title := "Add a new share" @@ -465,7 +472,7 @@ func renderAddUpdateSharePage(w http.ResponseWriter, r *http.Request, share *dat title = "Update share" } data := clientSharePage{ - baseClientPage: getBaseClientPageData(title, currentURL, r), + baseClientPage: s.getBaseClientPageData(title, currentURL, r), Share: share, Error: error, IsAdd: isAdd, @@ -495,10 +502,12 @@ func getDirMapping(dirName, baseWebPath string) []dirMapping { return paths } -func renderSharedFilesPage(w http.ResponseWriter, r *http.Request, dirName, error string, share dataprovider.Share) { +func (s *httpdServer) renderSharedFilesPage(w http.ResponseWriter, r *http.Request, dirName, error string, + share dataprovider.Share, +) { currentURL := path.Join(webClientPubSharesPath, share.ShareID, "browse") data := shareFilesPage{ - baseClientPage: getBaseClientPageData(pageExtShareTitle, currentURL, r), + baseClientPage: s.getBaseClientPageData(pageExtShareTitle, currentURL, r), CurrentDir: url.QueryEscape(dirName), DirsURL: path.Join(webClientPubSharesPath, share.ShareID, "dirs"), FilesURL: currentURL, @@ -509,21 +518,21 @@ func renderSharedFilesPage(w http.ResponseWriter, r *http.Request, dirName, erro renderClientTemplate(w, templateShareFiles, data) } -func renderUploadToSharePage(w http.ResponseWriter, r *http.Request, share dataprovider.Share) { +func (s *httpdServer) renderUploadToSharePage(w http.ResponseWriter, r *http.Request, share dataprovider.Share) { currentURL := path.Join(webClientPubSharesPath, share.ShareID, "upload") data := shareUploadPage{ - baseClientPage: getBaseClientPageData(pageUploadToShareTitle, currentURL, r), + baseClientPage: s.getBaseClientPageData(pageUploadToShareTitle, currentURL, r), Share: &share, UploadBasePath: path.Join(webClientPubSharesPath, share.ShareID), } renderClientTemplate(w, templateUploadToShare, data) } -func renderFilesPage(w http.ResponseWriter, r *http.Request, dirName, error string, user dataprovider.User, +func (s *httpdServer) renderFilesPage(w http.ResponseWriter, r *http.Request, dirName, error string, user dataprovider.User, hasIntegrations bool, ) { data := filesPage{ - baseClientPage: getBaseClientPageData(pageClientFilesTitle, webClientFilesPath, r), + baseClientPage: s.getBaseClientPageData(pageClientFilesTitle, webClientFilesPath, r), Error: error, CurrentDir: url.QueryEscape(dirName), DownloadURL: webClientDownloadZipPath, @@ -542,14 +551,14 @@ func renderFilesPage(w http.ResponseWriter, r *http.Request, dirName, error stri renderClientTemplate(w, templateClientFiles, data) } -func renderClientProfilePage(w http.ResponseWriter, r *http.Request, error string) { +func (s *httpdServer) renderClientProfilePage(w http.ResponseWriter, r *http.Request, error string) { data := clientProfilePage{ - baseClientPage: getBaseClientPageData(pageClientProfileTitle, webClientProfilePath, r), + baseClientPage: s.getBaseClientPageData(pageClientProfileTitle, webClientProfilePath, r), Error: error, } user, err := dataprovider.UserExists(data.LoggedUser.Username) if err != nil { - renderClientInternalServerErrorPage(w, r, err) + s.renderClientInternalServerErrorPage(w, r, err) return } data.PublicKeys = user.PublicKeys @@ -560,26 +569,26 @@ func renderClientProfilePage(w http.ResponseWriter, r *http.Request, error strin renderClientTemplate(w, templateClientProfile, data) } -func renderClientChangePasswordPage(w http.ResponseWriter, r *http.Request, error string) { +func (s *httpdServer) renderClientChangePasswordPage(w http.ResponseWriter, r *http.Request, error string) { data := changeClientPasswordPage{ - baseClientPage: getBaseClientPageData(pageClientChangePwdTitle, webChangeClientPwdPath, r), + baseClientPage: s.getBaseClientPageData(pageClientChangePwdTitle, webChangeClientPwdPath, r), Error: error, } renderClientTemplate(w, templateClientChangePwd, data) } -func handleWebClientDownloadZip(w http.ResponseWriter, r *http.Request) { +func (s *httpdServer) handleWebClientDownloadZip(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) claims, err := getTokenClaims(r) if err != nil || claims.Username == "" { - renderClientMessagePage(w, r, "Invalid token claims", "", http.StatusForbidden, nil, "") + s.renderClientMessagePage(w, r, "Invalid token claims", "", http.StatusForbidden, nil, "") return } user, err := dataprovider.UserExists(claims.Username) if err != nil { - renderClientMessagePage(w, r, "Unable to retrieve your user", "", getRespStatus(err), nil, "") + s.renderClientMessagePage(w, r, "Unable to retrieve your user", "", getRespStatus(err), nil, "") return } @@ -587,7 +596,7 @@ func handleWebClientDownloadZip(w http.ResponseWriter, r *http.Request) { protocol := getProtocolFromRequest(r) connectionID := fmt.Sprintf("%v_%v", protocol, connID) if err := checkHTTPClientUser(&user, r, connectionID); err != nil { - renderClientForbiddenPage(w, r, err.Error()) + s.renderClientForbiddenPage(w, r, err.Error()) return } connection := &Connection{ @@ -603,7 +612,7 @@ func handleWebClientDownloadZip(w http.ResponseWriter, r *http.Request) { var filesList []string err = json.Unmarshal([]byte(files), &filesList) if err != nil { - renderClientMessagePage(w, r, "Unable to get files list", "", http.StatusInternalServerError, err, "") + s.renderClientMessagePage(w, r, "Unable to get files list", "", http.StatusInternalServerError, err, "") return } @@ -611,19 +620,19 @@ func handleWebClientDownloadZip(w http.ResponseWriter, r *http.Request) { renderCompressedFiles(w, connection, name, filesList, nil) } -func handleShareGetDirContents(w http.ResponseWriter, r *http.Request) { +func (s *httpdServer) handleShareGetDirContents(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) - share, connection, err := checkPublicShare(w, r, dataprovider.ShareScopeRead, true) + share, connection, err := s.checkPublicShare(w, r, dataprovider.ShareScopeRead, true) if err != nil { return } if err := validateBrowsableShare(share, connection); err != nil { - renderClientMessagePage(w, r, "Unable to validate share", "", getRespStatus(err), err, "") + s.renderClientMessagePage(w, r, "Unable to validate share", "", getRespStatus(err), err, "") return } name, err := getBrowsableSharedPath(share, r) if err != nil { - renderClientMessagePage(w, r, "Invalid share path", "", getRespStatus(err), err, "") + s.renderClientMessagePage(w, r, "Invalid share path", "", getRespStatus(err), err, "") return } common.Connections.Add(connection) @@ -657,28 +666,28 @@ func handleShareGetDirContents(w http.ResponseWriter, r *http.Request) { render.JSON(w, r, results) } -func handleClientUploadToShare(w http.ResponseWriter, r *http.Request) { +func (s *httpdServer) handleClientUploadToShare(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) - share, _, err := checkPublicShare(w, r, dataprovider.ShareScopeWrite, true) + share, _, err := s.checkPublicShare(w, r, dataprovider.ShareScopeWrite, true) if err != nil { return } - renderUploadToSharePage(w, r, share) + s.renderUploadToSharePage(w, r, share) } -func handleShareGetFiles(w http.ResponseWriter, r *http.Request) { +func (s *httpdServer) handleShareGetFiles(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) - share, connection, err := checkPublicShare(w, r, dataprovider.ShareScopeRead, true) + share, connection, err := s.checkPublicShare(w, r, dataprovider.ShareScopeRead, true) if err != nil { return } if err := validateBrowsableShare(share, connection); err != nil { - renderClientMessagePage(w, r, "Unable to validate share", "", getRespStatus(err), err, "") + s.renderClientMessagePage(w, r, "Unable to validate share", "", getRespStatus(err), err, "") return } name, err := getBrowsableSharedPath(share, r) if err != nil { - renderClientMessagePage(w, r, "Invalid share path", "", getRespStatus(err), err, "") + s.renderClientMessagePage(w, r, "Invalid share path", "", getRespStatus(err), err, "") return } @@ -692,11 +701,11 @@ func handleShareGetFiles(w http.ResponseWriter, r *http.Request) { info, err = connection.Stat(name, 1) } if err != nil { - renderSharedFilesPage(w, r, path.Dir(share.GetRelativePath(name)), err.Error(), share) + s.renderSharedFilesPage(w, r, path.Dir(share.GetRelativePath(name)), err.Error(), share) return } if info.IsDir() { - renderSharedFilesPage(w, r, share.GetRelativePath(name), "", share) + s.renderSharedFilesPage(w, r, share.GetRelativePath(name), "", share) return } inline := r.URL.Query().Get("inline") != "" @@ -704,7 +713,7 @@ func handleShareGetFiles(w http.ResponseWriter, r *http.Request) { if status, err := downloadFile(w, r, connection, name, info, inline, &share); err != nil { dataprovider.UpdateShareLastUse(&share, -1) //nolint:errcheck if status > 0 { - renderSharedFilesPage(w, r, path.Dir(share.GetRelativePath(name)), err.Error(), share) + s.renderSharedFilesPage(w, r, path.Dir(share.GetRelativePath(name)), err.Error(), share) } } } @@ -787,13 +796,13 @@ func (s *httpdServer) handleClientGetFiles(w http.ResponseWriter, r *http.Reques r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) claims, err := getTokenClaims(r) if err != nil || claims.Username == "" { - renderClientForbiddenPage(w, r, "Invalid token claims") + s.renderClientForbiddenPage(w, r, "Invalid token claims") return } user, err := dataprovider.UserExists(claims.Username) if err != nil { - renderClientMessagePage(w, r, "Unable to retrieve your user", "", getRespStatus(err), nil, "") + s.renderClientMessagePage(w, r, "Unable to retrieve your user", "", getRespStatus(err), nil, "") return } @@ -801,7 +810,7 @@ func (s *httpdServer) handleClientGetFiles(w http.ResponseWriter, r *http.Reques protocol := getProtocolFromRequest(r) connectionID := fmt.Sprintf("%v_%v", protocol, connID) if err := checkHTTPClientUser(&user, r, connectionID); err != nil { - renderClientForbiddenPage(w, r, err.Error()) + s.renderClientForbiddenPage(w, r, err.Error()) return } connection := &Connection{ @@ -820,37 +829,37 @@ func (s *httpdServer) handleClientGetFiles(w http.ResponseWriter, r *http.Reques info, err = connection.Stat(name, 0) } if err != nil { - renderFilesPage(w, r, path.Dir(name), fmt.Sprintf("unable to stat file %#v: %v", name, err), + s.renderFilesPage(w, r, path.Dir(name), fmt.Sprintf("unable to stat file %#v: %v", name, err), user, len(s.binding.WebClientIntegrations) > 0) return } if info.IsDir() { - renderFilesPage(w, r, name, "", user, len(s.binding.WebClientIntegrations) > 0) + s.renderFilesPage(w, r, name, "", user, len(s.binding.WebClientIntegrations) > 0) return } inline := r.URL.Query().Get("inline") != "" if status, err := downloadFile(w, r, connection, name, info, inline, nil); err != nil && status != 0 { if status > 0 { if status == http.StatusRequestedRangeNotSatisfiable { - renderClientMessagePage(w, r, http.StatusText(status), "", status, err, "") + s.renderClientMessagePage(w, r, http.StatusText(status), "", status, err, "") return } - renderFilesPage(w, r, path.Dir(name), err.Error(), user, len(s.binding.WebClientIntegrations) > 0) + s.renderFilesPage(w, r, path.Dir(name), err.Error(), user, len(s.binding.WebClientIntegrations) > 0) } } } -func handleClientEditFile(w http.ResponseWriter, r *http.Request) { +func (s *httpdServer) handleClientEditFile(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) claims, err := getTokenClaims(r) if err != nil || claims.Username == "" { - renderClientForbiddenPage(w, r, "Invalid token claims") + s.renderClientForbiddenPage(w, r, "Invalid token claims") return } user, err := dataprovider.UserExists(claims.Username) if err != nil { - renderClientMessagePage(w, r, "Unable to retrieve your user", "", getRespStatus(err), nil, "") + s.renderClientMessagePage(w, r, "Unable to retrieve your user", "", getRespStatus(err), nil, "") return } @@ -858,7 +867,7 @@ func handleClientEditFile(w http.ResponseWriter, r *http.Request) { protocol := getProtocolFromRequest(r) connectionID := fmt.Sprintf("%v_%v", protocol, connID) if err := checkHTTPClientUser(&user, r, connectionID); err != nil { - renderClientForbiddenPage(w, r, err.Error()) + s.renderClientForbiddenPage(w, r, err.Error()) return } connection := &Connection{ @@ -872,24 +881,24 @@ func handleClientEditFile(w http.ResponseWriter, r *http.Request) { name := connection.User.GetCleanedPath(r.URL.Query().Get("path")) info, err := connection.Stat(name, 0) if err != nil { - renderClientMessagePage(w, r, fmt.Sprintf("Unable to stat file %#v", name), "", + s.renderClientMessagePage(w, r, fmt.Sprintf("Unable to stat file %#v", name), "", getRespStatus(err), nil, "") return } if info.IsDir() { - renderClientMessagePage(w, r, fmt.Sprintf("The path %#v does not point to a file", name), "", + s.renderClientMessagePage(w, r, fmt.Sprintf("The path %#v does not point to a file", name), "", http.StatusBadRequest, nil, "") return } if info.Size() > httpdMaxEditFileSize { - renderClientMessagePage(w, r, fmt.Sprintf("The file size %v for %#v exceeds the maximum allowed size", + s.renderClientMessagePage(w, r, fmt.Sprintf("The file size %v for %#v exceeds the maximum allowed size", util.ByteCountIEC(info.Size()), name), "", http.StatusBadRequest, nil, "") return } reader, err := connection.getFileReader(name, 0, r.Method) if err != nil { - renderClientMessagePage(w, r, fmt.Sprintf("Unable to get a reader for the file %#v", name), "", + s.renderClientMessagePage(w, r, fmt.Sprintf("Unable to get a reader for the file %#v", name), "", getRespStatus(err), nil, "") return } @@ -898,15 +907,15 @@ func handleClientEditFile(w http.ResponseWriter, r *http.Request) { var b bytes.Buffer _, err = io.Copy(&b, reader) if err != nil { - renderClientMessagePage(w, r, fmt.Sprintf("Unable to read the file %#v", name), "", http.StatusInternalServerError, + s.renderClientMessagePage(w, r, fmt.Sprintf("Unable to read the file %#v", name), "", http.StatusInternalServerError, nil, "") return } - renderEditFilePage(w, r, name, b.String(), util.IsStringInSlice(sdk.WebClientWriteDisabled, user.Filters.WebClient)) + s.renderEditFilePage(w, r, name, b.String(), util.IsStringInSlice(sdk.WebClientWriteDisabled, user.Filters.WebClient)) } -func handleClientAddShareGet(w http.ResponseWriter, r *http.Request) { +func (s *httpdServer) handleClientAddShareGet(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) share := &dataprovider.Share{Scope: dataprovider.ShareScopeRead} dirName := "/" @@ -919,7 +928,7 @@ func handleClientAddShareGet(w http.ResponseWriter, r *http.Request) { var filesList []string err := json.Unmarshal([]byte(files), &filesList) if err != nil { - renderClientMessagePage(w, r, "Invalid share list", "", http.StatusBadRequest, err, "") + s.renderClientMessagePage(w, r, "Invalid share list", "", http.StatusBadRequest, err, "") return } for _, f := range filesList { @@ -929,42 +938,42 @@ func handleClientAddShareGet(w http.ResponseWriter, r *http.Request) { } } - renderAddUpdateSharePage(w, r, share, "", true) + s.renderAddUpdateSharePage(w, r, share, "", true) } -func handleClientUpdateShareGet(w http.ResponseWriter, r *http.Request) { +func (s *httpdServer) handleClientUpdateShareGet(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) claims, err := getTokenClaims(r) if err != nil || claims.Username == "" { - renderClientForbiddenPage(w, r, "Invalid token claims") + s.renderClientForbiddenPage(w, r, "Invalid token claims") return } shareID := getURLParam(r, "id") share, err := dataprovider.ShareExists(shareID, claims.Username) if err == nil { share.HideConfidentialData() - renderAddUpdateSharePage(w, r, &share, "", false) + s.renderAddUpdateSharePage(w, r, &share, "", false) } else if _, ok := err.(*util.RecordNotFoundError); ok { - renderClientNotFoundPage(w, r, err) + s.renderClientNotFoundPage(w, r, err) } else { - renderClientInternalServerErrorPage(w, r, err) + s.renderClientInternalServerErrorPage(w, r, err) } } -func handleClientAddSharePost(w http.ResponseWriter, r *http.Request) { +func (s *httpdServer) handleClientAddSharePost(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) claims, err := getTokenClaims(r) if err != nil || claims.Username == "" { - renderClientForbiddenPage(w, r, "Invalid token claims") + s.renderClientForbiddenPage(w, r, "Invalid token claims") return } share, err := getShareFromPostFields(r) if err != nil { - renderAddUpdateSharePage(w, r, share, err.Error(), true) + s.renderAddUpdateSharePage(w, r, share, err.Error(), true) return } if err := verifyCSRFToken(r.Form.Get(csrfFormToken)); err != nil { - renderClientForbiddenPage(w, r, err.Error()) + s.renderClientForbiddenPage(w, r, err.Error()) return } share.ID = 0 @@ -973,7 +982,7 @@ func handleClientAddSharePost(w http.ResponseWriter, r *http.Request) { share.Username = claims.Username if share.Password == "" { if util.IsStringInSlice(sdk.WebClientShareNoPasswordDisabled, claims.Permissions) { - renderClientForbiddenPage(w, r, "You are not authorized to share files/folders without a password") + s.renderClientForbiddenPage(w, r, "You are not authorized to share files/folders without a password") return } } @@ -981,33 +990,33 @@ func handleClientAddSharePost(w http.ResponseWriter, r *http.Request) { if err == nil { http.Redirect(w, r, webClientSharesPath, http.StatusSeeOther) } else { - renderAddUpdateSharePage(w, r, share, err.Error(), true) + s.renderAddUpdateSharePage(w, r, share, err.Error(), true) } } -func handleClientUpdateSharePost(w http.ResponseWriter, r *http.Request) { +func (s *httpdServer) handleClientUpdateSharePost(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) claims, err := getTokenClaims(r) if err != nil || claims.Username == "" { - renderClientForbiddenPage(w, r, "Invalid token claims") + s.renderClientForbiddenPage(w, r, "Invalid token claims") return } shareID := getURLParam(r, "id") share, err := dataprovider.ShareExists(shareID, claims.Username) if _, ok := err.(*util.RecordNotFoundError); ok { - renderClientNotFoundPage(w, r, err) + s.renderClientNotFoundPage(w, r, err) return } else if err != nil { - renderClientInternalServerErrorPage(w, r, err) + s.renderClientInternalServerErrorPage(w, r, err) return } updatedShare, err := getShareFromPostFields(r) if err != nil { - renderAddUpdateSharePage(w, r, updatedShare, err.Error(), false) + s.renderAddUpdateSharePage(w, r, updatedShare, err.Error(), false) return } if err := verifyCSRFToken(r.Form.Get(csrfFormToken)); err != nil { - renderClientForbiddenPage(w, r, err.Error()) + s.renderClientForbiddenPage(w, r, err.Error()) return } updatedShare.ShareID = shareID @@ -1017,7 +1026,7 @@ func handleClientUpdateSharePost(w http.ResponseWriter, r *http.Request) { } if updatedShare.Password == "" { if util.IsStringInSlice(sdk.WebClientShareNoPasswordDisabled, claims.Permissions) { - renderClientForbiddenPage(w, r, "You are not authorized to share files/folders without a password") + s.renderClientForbiddenPage(w, r, "You are not authorized to share files/folders without a password") return } } @@ -1025,15 +1034,15 @@ func handleClientUpdateSharePost(w http.ResponseWriter, r *http.Request) { if err == nil { http.Redirect(w, r, webClientSharesPath, http.StatusSeeOther) } else { - renderAddUpdateSharePage(w, r, updatedShare, err.Error(), false) + s.renderAddUpdateSharePage(w, r, updatedShare, err.Error(), false) } } -func handleClientGetShares(w http.ResponseWriter, r *http.Request) { +func (s *httpdServer) handleClientGetShares(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) claims, err := getTokenClaims(r) if err != nil || claims.Username == "" { - renderClientForbiddenPage(w, r, "Invalid token claims") + s.renderClientForbiddenPage(w, r, "Invalid token claims") return } limit := defaultQueryLimit @@ -1046,57 +1055,57 @@ func handleClientGetShares(w http.ResponseWriter, r *http.Request) { } shares := make([]dataprovider.Share, 0, limit) for { - s, err := dataprovider.GetShares(limit, len(shares), dataprovider.OrderASC, claims.Username) + sh, err := dataprovider.GetShares(limit, len(shares), dataprovider.OrderASC, claims.Username) if err != nil { - renderInternalServerErrorPage(w, r, err) + s.renderInternalServerErrorPage(w, r, err) return } - shares = append(shares, s...) - if len(s) < limit { + shares = append(shares, sh...) + if len(sh) < limit { break } } data := clientSharesPage{ - baseClientPage: getBaseClientPageData(pageClientSharesTitle, webClientSharesPath, r), + baseClientPage: s.getBaseClientPageData(pageClientSharesTitle, webClientSharesPath, r), Shares: shares, BasePublicSharesURL: webClientPubSharesPath, } renderClientTemplate(w, templateClientShares, data) } -func handleClientGetProfile(w http.ResponseWriter, r *http.Request) { +func (s *httpdServer) handleClientGetProfile(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) - renderClientProfilePage(w, r, "") + s.renderClientProfilePage(w, r, "") } -func handleWebClientChangePwd(w http.ResponseWriter, r *http.Request) { +func (s *httpdServer) handleWebClientChangePwd(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) - renderClientChangePasswordPage(w, r, "") + s.renderClientChangePasswordPage(w, r, "") } -func handleWebClientProfilePost(w http.ResponseWriter, r *http.Request) { +func (s *httpdServer) handleWebClientProfilePost(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) err := r.ParseForm() if err != nil { - renderClientProfilePage(w, r, err.Error()) + s.renderClientProfilePage(w, r, err.Error()) return } if err := verifyCSRFToken(r.Form.Get(csrfFormToken)); err != nil { - renderClientForbiddenPage(w, r, err.Error()) + s.renderClientForbiddenPage(w, r, err.Error()) return } claims, err := getTokenClaims(r) if err != nil || claims.Username == "" { - renderClientForbiddenPage(w, r, "Invalid token claims") + s.renderClientForbiddenPage(w, r, "Invalid token claims") return } user, err := dataprovider.UserExists(claims.Username) if err != nil { - renderClientProfilePage(w, r, err.Error()) + s.renderClientProfilePage(w, r, err.Error()) return } if !user.CanManagePublicKeys() && !user.CanChangeAPIKeyAuth() && !user.CanChangeInfo() { - renderClientForbiddenPage(w, r, "You are not allowed to change anything") + s.renderClientForbiddenPage(w, r, "You are not allowed to change anything") return } if user.CanManagePublicKeys() { @@ -1111,26 +1120,26 @@ func handleWebClientProfilePost(w http.ResponseWriter, r *http.Request) { } err = dataprovider.UpdateUser(&user, dataprovider.ActionExecutorSelf, util.GetIPFromRemoteAddress(r.RemoteAddr)) if err != nil { - renderClientProfilePage(w, r, err.Error()) + s.renderClientProfilePage(w, r, err.Error()) return } - renderClientMessagePage(w, r, "Profile updated", "", http.StatusOK, nil, + s.renderClientMessagePage(w, r, "Profile updated", "", http.StatusOK, nil, "Your profile has been successfully updated") } -func handleWebClientMFA(w http.ResponseWriter, r *http.Request) { +func (s *httpdServer) handleWebClientMFA(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) - renderClientMFAPage(w, r) + s.renderClientMFAPage(w, r) } -func handleWebClientTwoFactor(w http.ResponseWriter, r *http.Request) { +func (s *httpdServer) handleWebClientTwoFactor(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) - renderClientTwoFactorPage(w, "") + s.renderClientTwoFactorPage(w, "") } -func handleWebClientTwoFactorRecovery(w http.ResponseWriter, r *http.Request) { +func (s *httpdServer) handleWebClientTwoFactorRecovery(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) - renderClientTwoFactorRecoveryPage(w, "") + s.renderClientTwoFactorRecoveryPage(w, "") } func getShareFromPostFields(r *http.Request) (*dataprovider.Share, error) { @@ -1166,53 +1175,53 @@ func getShareFromPostFields(r *http.Request) (*dataprovider.Share, error) { return share, nil } -func handleWebClientForgotPwd(w http.ResponseWriter, r *http.Request) { +func (s *httpdServer) handleWebClientForgotPwd(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) if !smtp.IsEnabled() { - renderClientNotFoundPage(w, r, errors.New("this page does not exist")) + s.renderClientNotFoundPage(w, r, errors.New("this page does not exist")) return } - renderClientForgotPwdPage(w, "") + s.renderClientForgotPwdPage(w, "") } -func handleWebClientForgotPwdPost(w http.ResponseWriter, r *http.Request) { +func (s *httpdServer) handleWebClientForgotPwdPost(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) err := r.ParseForm() if err != nil { - renderClientForgotPwdPage(w, err.Error()) + s.renderClientForgotPwdPage(w, err.Error()) return } if err := verifyCSRFToken(r.Form.Get(csrfFormToken)); err != nil { - renderClientForbiddenPage(w, r, err.Error()) + s.renderClientForbiddenPage(w, r, err.Error()) return } username := r.Form.Get("username") err = handleForgotPassword(r, username, false) if err != nil { if e, ok := err.(*util.ValidationError); ok { - renderClientForgotPwdPage(w, e.GetErrorString()) + s.renderClientForgotPwdPage(w, e.GetErrorString()) return } - renderClientForgotPwdPage(w, err.Error()) + s.renderClientForgotPwdPage(w, err.Error()) return } http.Redirect(w, r, webClientResetPwdPath, http.StatusFound) } -func handleWebClientPasswordReset(w http.ResponseWriter, r *http.Request) { +func (s *httpdServer) handleWebClientPasswordReset(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, maxLoginBodySize) if !smtp.IsEnabled() { - renderClientNotFoundPage(w, r, errors.New("this page does not exist")) + s.renderClientNotFoundPage(w, r, errors.New("this page does not exist")) return } - renderClientResetPwdPage(w, "") + s.renderClientResetPwdPage(w, "") } -func handleClientViewPDF(w http.ResponseWriter, r *http.Request) { +func (s *httpdServer) handleClientViewPDF(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, maxLoginBodySize) name := r.URL.Query().Get("path") if name == "" { - renderClientBadRequestPage(w, r, errors.New("no file specified")) + s.renderClientBadRequestPage(w, r, errors.New("no file specified")) return } name = util.CleanPath(name) @@ -1220,6 +1229,7 @@ func handleClientViewPDF(w http.ResponseWriter, r *http.Request) { Title: path.Base(name), URL: fmt.Sprintf("%v?path=%v&inline=1", webClientFilesPath, url.QueryEscape(name)), StaticURL: webStaticFilesPath, + ExtraCSS: s.binding.ExtraCSS, } renderClientTemplate(w, templateClientViewPDF, data) } diff --git a/pkgs/build.sh b/pkgs/build.sh index 135e879a..2e54993d 100755 --- a/pkgs/build.sh +++ b/pkgs/build.sh @@ -1,6 +1,6 @@ #!/bin/bash -NFPM_VERSION=2.14.0 +NFPM_VERSION=2.15.0 NFPM_ARCH=${NFPM_ARCH:-amd64} if [ -z ${SFTPGO_VERSION} ] then diff --git a/sftpgo.json b/sftpgo.json index c168460a..1b3303d7 100644 --- a/sftpgo.json +++ b/sftpgo.json @@ -250,7 +250,8 @@ "permissions_policy": "", "cross_origin_opener_policy": "", "expect_ct_header": "" - } + }, + "extra_css": [] } ], "templates_path": "templates", diff --git a/templates/common/forgot-password.html b/templates/common/forgot-password.html index 4ef96f23..9d69a64b 100644 --- a/templates/common/forgot-password.html +++ b/templates/common/forgot-password.html @@ -70,6 +70,10 @@ } + {{range .ExtraCSS}} + + {{end}} + diff --git a/templates/common/reset-password.html b/templates/common/reset-password.html index 8b04ac32..ef117950 100644 --- a/templates/common/reset-password.html +++ b/templates/common/reset-password.html @@ -70,6 +70,10 @@ } + {{range .ExtraCSS}} + + {{end}} + diff --git a/templates/webadmin/base.html b/templates/webadmin/base.html index cc2a6193..61a8db0a 100644 --- a/templates/webadmin/base.html +++ b/templates/webadmin/base.html @@ -53,6 +53,10 @@ {{block "extra_css" .}}{{end}} + {{range .ExtraCSS}} + + {{end}} + diff --git a/templates/webadmin/baselogin.html b/templates/webadmin/baselogin.html index a5d57d4f..5d8e684b 100644 --- a/templates/webadmin/baselogin.html +++ b/templates/webadmin/baselogin.html @@ -71,6 +71,10 @@ } + {{range .ExtraCSS}} + + {{end}} + diff --git a/templates/webclient/base.html b/templates/webclient/base.html index 799a9536..b824b8d0 100644 --- a/templates/webclient/base.html +++ b/templates/webclient/base.html @@ -53,6 +53,10 @@ {{block "extra_css" .}}{{end}} + {{range .ExtraCSS}} + + {{end}} + diff --git a/templates/webclient/baselogin.html b/templates/webclient/baselogin.html index f51345c4..28e949c4 100644 --- a/templates/webclient/baselogin.html +++ b/templates/webclient/baselogin.html @@ -71,6 +71,10 @@ } + {{range .ExtraCSS}} + + {{end}} + diff --git a/templates/webclient/viewpdf.html b/templates/webclient/viewpdf.html index 907ff4af..6f91e95d 100644 --- a/templates/webclient/viewpdf.html +++ b/templates/webclient/viewpdf.html @@ -4,6 +4,10 @@ {{.Title}} + + {{range .ExtraCSS}} + + {{end}}