From 69ef36b4d9ab84ff1aaed561a085f4e75f57856a Mon Sep 17 00:00:00 2001 From: Nicola Murino Date: Sat, 25 Jan 2025 21:49:01 +0100 Subject: [PATCH] httpd: add a setting to disable login methods, deprecate the previous one the previous enabled login methods setting is hard to extend in a backward compatible way Signed-off-by: Nicola Murino --- internal/config/config.go | 47 ++++++----- internal/config/config_test.go | 5 ++ internal/httpd/httpd.go | 82 ++++++++++++++---- internal/httpd/httpd_test.go | 17 ++-- internal/httpd/internal_test.go | 143 ++++++++++++++++++++++++++++++++ internal/httpd/oidc_test.go | 2 +- internal/httpd/server.go | 108 +++++++++++++----------- sftpgo.json | 1 + 8 files changed, 314 insertions(+), 91 deletions(-) diff --git a/internal/config/config.go b/internal/config/config.go index 39f39777..34feb21b 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -99,26 +99,27 @@ var ( DisableWWWAuthHeader: false, } defaultHTTPDBinding = httpd.Binding{ - Address: "", - Port: 8080, - EnableWebAdmin: true, - EnableWebClient: true, - EnableRESTAPI: true, - EnabledLoginMethods: 0, - EnableHTTPS: false, - CertificateFile: "", - CertificateKeyFile: "", - MinTLSVersion: 12, - ClientAuthType: 0, - TLSCipherSuites: nil, - Protocols: nil, - ProxyMode: 0, - ProxyAllowed: nil, - ClientIPProxyHeader: "", - ClientIPHeaderDepth: 0, - HideLoginURL: 0, - RenderOpenAPI: true, - Languages: []string{"en"}, + Address: "", + Port: 8080, + EnableWebAdmin: true, + EnableWebClient: true, + EnableRESTAPI: true, + EnabledLoginMethods: 0, + DisabledLoginMethods: 0, + EnableHTTPS: false, + CertificateFile: "", + CertificateKeyFile: "", + MinTLSVersion: 12, + ClientAuthType: 0, + TLSCipherSuites: nil, + Protocols: nil, + ProxyMode: 0, + ProxyAllowed: nil, + ClientIPProxyHeader: "", + ClientIPHeaderDepth: 0, + HideLoginURL: 0, + RenderOpenAPI: true, + Languages: []string{"en"}, OIDC: httpd.OIDC{ ClientID: "", ClientSecret: "", @@ -1868,6 +1869,12 @@ func getHTTPDBindingFromEnv(idx int) { //nolint:gocyclo isSet = true } + disabledLoginMethods, ok := lookupIntFromEnv(fmt.Sprintf("SFTPGO_HTTPD__BINDINGS__%v__DISABLED_LOGIN_METHODS", idx), 32) + if ok { + binding.DisabledLoginMethods = int(disabledLoginMethods) + isSet = true + } + renderOpenAPI, ok := lookupBoolFromEnv(fmt.Sprintf("SFTPGO_HTTPD__BINDINGS__%v__RENDER_OPENAPI", idx)) if ok { binding.RenderOpenAPI = renderOpenAPI diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 5cad8732..83d98687 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -1192,6 +1192,7 @@ func TestHTTPDBindingsFromEnv(t *testing.T) { os.Setenv("SFTPGO_HTTPD__BINDINGS__2__ENABLE_WEB_CLIENT", "0") os.Setenv("SFTPGO_HTTPD__BINDINGS__2__ENABLE_REST_API", "0") os.Setenv("SFTPGO_HTTPD__BINDINGS__2__ENABLED_LOGIN_METHODS", "3") + os.Setenv("SFTPGO_HTTPD__BINDINGS__2__DISABLED_LOGIN_METHODS", "12") os.Setenv("SFTPGO_HTTPD__BINDINGS__2__RENDER_OPENAPI", "0") os.Setenv("SFTPGO_HTTPD__BINDINGS__2__LANGUAGES", "en,es") os.Setenv("SFTPGO_HTTPD__BINDINGS__2__ENABLE_HTTPS", "1 ") @@ -1263,6 +1264,7 @@ func TestHTTPDBindingsFromEnv(t *testing.T) { os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__ENABLE_WEB_CLIENT") os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__ENABLE_REST_API") os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__ENABLED_LOGIN_METHODS") + os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__DISABLED_LOGIN_METHODS") os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__RENDER_OPENAPI") os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__LANGUAGES") os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__CLIENT_AUTH_TYPE") @@ -1327,6 +1329,7 @@ func TestHTTPDBindingsFromEnv(t *testing.T) { require.True(t, bindings[0].EnableWebClient) require.True(t, bindings[0].EnableRESTAPI) require.Equal(t, 0, bindings[0].EnabledLoginMethods) + require.Equal(t, 0, bindings[0].DisabledLoginMethods) require.True(t, bindings[0].RenderOpenAPI) require.Len(t, bindings[0].Languages, 1) assert.Contains(t, bindings[0].Languages, "en") @@ -1348,6 +1351,7 @@ func TestHTTPDBindingsFromEnv(t *testing.T) { require.True(t, bindings[1].EnableWebClient) require.True(t, bindings[1].EnableRESTAPI) require.Equal(t, 0, bindings[1].EnabledLoginMethods) + require.Equal(t, 0, bindings[1].DisabledLoginMethods) require.True(t, bindings[1].RenderOpenAPI) require.Len(t, bindings[1].Languages, 1) assert.Contains(t, bindings[1].Languages, "en") @@ -1370,6 +1374,7 @@ func TestHTTPDBindingsFromEnv(t *testing.T) { require.False(t, bindings[2].EnableWebClient) require.False(t, bindings[2].EnableRESTAPI) require.Equal(t, 3, bindings[2].EnabledLoginMethods) + require.Equal(t, 12, bindings[2].DisabledLoginMethods) require.False(t, bindings[2].RenderOpenAPI) require.Len(t, bindings[2].Languages, 2) assert.Contains(t, bindings[2].Languages, "en") diff --git a/internal/httpd/httpd.go b/internal/httpd/httpd.go index 8763c845..deba2e38 100644 --- a/internal/httpd/httpd.go +++ b/internal/httpd/httpd.go @@ -416,7 +416,7 @@ type SecurityConf struct { PermissionsPolicy string `json:"permissions_policy" mapstructure:"permissions_policy"` // CrossOriginOpenerPolicy allows to set the Cross-Origin-Opener-Policy header value. Default is "". CrossOriginOpenerPolicy string `json:"cross_origin_opener_policy" mapstructure:"cross_origin_opener_policy"` - // CrossOriginResourcePolicy allows to set the Cross-Origin-Opener-Policy header value. Default is "". + // CrossOriginResourcePolicy allows to set the Cross-Origin-Resource-Policy header value. Default is "". CrossOriginResourcePolicy string `json:"cross_origin_resource_policy" mapstructure:"cross_origin_resource_policy"` // CrossOriginEmbedderPolicy allows to set the Cross-Origin-Embedder-Policy header value. Default is "". CrossOriginEmbedderPolicy string `json:"cross_origin_embedder_policy" mapstructure:"cross_origin_embedder_policy"` @@ -565,7 +565,21 @@ type Binding struct { // // You can combine the values. For example 3 means that you can only login using OIDC on // both WebClient and WebAdmin UI. + // Deprecated because it is not extensible, use DisabledLoginMethods EnabledLoginMethods int `json:"enabled_login_methods" mapstructure:"enabled_login_methods"` + // Defines the login methods disabled for the WebAdmin and WebClient UIs: + // + // - 1 means OIDC for the WebAdmin UI + // - 2 means OIDC for the WebClient UI + // - 4 means login form for the WebAdmin UI + // - 8 means login form for the WebClient UI + // - 16 means basic auth for admin REST API + // - 32 means basic auth for user REST API + // - 64 means API key auth for admins + // - 128 means API key auth for users + // You can combine the values. For example 12 means that you can only login using OIDC on + // both WebClient and WebAdmin UI. + DisabledLoginMethods int `json:"disabled_login_methods" mapstructure:"disabled_login_methods"` // you also need to provide a certificate for enabling HTTPS EnableHTTPS bool `json:"enable_https" mapstructure:"enable_https"` // Certificate and matching private key for this specific binding, if empty the global @@ -687,45 +701,76 @@ func (b *Binding) IsValid() bool { func (b *Binding) isWebAdminOIDCLoginDisabled() bool { if b.EnableWebAdmin { - if b.EnabledLoginMethods == 0 { - return false - } - return b.EnabledLoginMethods&1 == 0 + return b.DisabledLoginMethods&1 != 0 } return false } func (b *Binding) isWebClientOIDCLoginDisabled() bool { if b.EnableWebClient { - if b.EnabledLoginMethods == 0 { - return false - } - return b.EnabledLoginMethods&2 == 0 + return b.DisabledLoginMethods&2 != 0 } return false } func (b *Binding) isWebAdminLoginFormDisabled() bool { if b.EnableWebAdmin { - if b.EnabledLoginMethods == 0 { - return false - } - return b.EnabledLoginMethods&4 == 0 + return b.DisabledLoginMethods&4 != 0 } return false } func (b *Binding) isWebClientLoginFormDisabled() bool { if b.EnableWebClient { - if b.EnabledLoginMethods == 0 { - return false - } - return b.EnabledLoginMethods&8 == 0 + return b.DisabledLoginMethods&8 != 0 } return false } +func (b *Binding) isAdminTokenEndpointDisabled() bool { + return b.DisabledLoginMethods&16 != 0 +} + +func (b *Binding) isUserTokenEndpointDisabled() bool { + return b.DisabledLoginMethods&32 != 0 +} + +func (b *Binding) isAdminAPIKeyAuthDisabled() bool { + return b.DisabledLoginMethods&64 != 0 +} + +func (b *Binding) isUserAPIKeyAuthDisabled() bool { + return b.DisabledLoginMethods&128 != 0 +} + +func (b *Binding) hasLoginForAPI() bool { + return !b.isAdminTokenEndpointDisabled() || !b.isUserTokenEndpointDisabled() || + !b.isAdminAPIKeyAuthDisabled() || !b.isUserAPIKeyAuthDisabled() +} + +// convertLoginMethods checks if the deprecated EnabledLoginMethods is set and +// convert the value to DisabledLoginMethods. +func (b *Binding) convertLoginMethods() { + if b.DisabledLoginMethods > 0 || b.EnabledLoginMethods == 0 { + // DisabledLoginMethods already in use or EnabledLoginMethods not set. + return + } + if b.EnabledLoginMethods&1 == 0 { + b.DisabledLoginMethods++ + } + if b.EnabledLoginMethods&2 == 0 { + b.DisabledLoginMethods += 2 + } + if b.EnabledLoginMethods&4 == 0 { + b.DisabledLoginMethods += 4 + } + if b.EnabledLoginMethods&8 == 0 { + b.DisabledLoginMethods += 8 + } +} + func (b *Binding) checkLoginMethods() error { + b.convertLoginMethods() if b.isWebAdminLoginFormDisabled() && b.isWebAdminOIDCLoginDisabled() { return errors.New("no login method available for WebAdmin UI") } @@ -742,6 +787,9 @@ func (b *Binding) checkLoginMethods() error { return errors.New("no login method available for WebClient UI") } } + if b.EnableRESTAPI && !b.hasLoginForAPI() { + return errors.New("no login method available for REST API") + } return nil } diff --git a/internal/httpd/httpd_test.go b/internal/httpd/httpd_test.go index e77e86e5..cca108cb 100644 --- a/internal/httpd/httpd_test.go +++ b/internal/httpd/httpd_test.go @@ -578,27 +578,28 @@ func TestInitialization(t *testing.T) { httpdConf.Bindings[0].OIDC = httpd.OIDC{} httpdConf.Bindings[0].EnableWebClient = true httpdConf.Bindings[0].EnableWebAdmin = true - httpdConf.Bindings[0].EnabledLoginMethods = 1 + httpdConf.Bindings[0].EnableRESTAPI = true + httpdConf.Bindings[0].DisabledLoginMethods = 14 err = httpdConf.Initialize(configDir, isShared) if assert.Error(t, err) { assert.Contains(t, err.Error(), "no login method available for WebAdmin UI") } - httpdConf.Bindings[0].EnabledLoginMethods = 2 + httpdConf.Bindings[0].DisabledLoginMethods = 13 err = httpdConf.Initialize(configDir, isShared) if assert.Error(t, err) { assert.Contains(t, err.Error(), "no login method available for WebAdmin UI") } - httpdConf.Bindings[0].EnabledLoginMethods = 6 + httpdConf.Bindings[0].DisabledLoginMethods = 9 err = httpdConf.Initialize(configDir, isShared) if assert.Error(t, err) { assert.Contains(t, err.Error(), "no login method available for WebClient UI") } - httpdConf.Bindings[0].EnabledLoginMethods = 4 + httpdConf.Bindings[0].DisabledLoginMethods = 11 err = httpdConf.Initialize(configDir, isShared) if assert.Error(t, err) { assert.Contains(t, err.Error(), "no login method available for WebClient UI") } - httpdConf.Bindings[0].EnabledLoginMethods = 3 + httpdConf.Bindings[0].DisabledLoginMethods = 12 err = httpdConf.Initialize(configDir, isShared) if assert.Error(t, err) { assert.Contains(t, err.Error(), "no login method available for WebAdmin UI") @@ -608,6 +609,12 @@ func TestInitialization(t *testing.T) { if assert.Error(t, err) { assert.Contains(t, err.Error(), "no login method available for WebClient UI") } + httpdConf.Bindings[0].EnableWebClient = false + httpdConf.Bindings[0].DisabledLoginMethods = 240 + err = httpdConf.Initialize(configDir, isShared) + if assert.Error(t, err) { + assert.Contains(t, err.Error(), "no login method available for REST API") + } err = dataprovider.Close() assert.NoError(t, err) err = httpdConf.Initialize(configDir, isShared) diff --git a/internal/httpd/internal_test.go b/internal/httpd/internal_test.go index e73dffc1..831f6bb0 100644 --- a/internal/httpd/internal_test.go +++ b/internal/httpd/internal_test.go @@ -3899,6 +3899,116 @@ func TestHTTPSRedirect(t *testing.T) { assert.NoError(t, err) } +func TestDisabledAdminLoginMethods(t *testing.T) { + server := httpdServer{ + binding: Binding{ + Address: "", + Port: 8080, + EnableWebAdmin: true, + EnableWebClient: true, + EnableRESTAPI: true, + DisabledLoginMethods: 20, + }, + enableWebAdmin: true, + enableWebClient: true, + enableRESTAPI: true, + } + server.initializeRouter() + testServer := httptest.NewServer(server.router) + defer testServer.Close() + + rr := httptest.NewRecorder() + req, err := http.NewRequest(http.MethodGet, tokenPath, nil) + require.NoError(t, err) + testServer.Config.Handler.ServeHTTP(rr, req) + assert.Equal(t, http.StatusNotFound, rr.Code) + + rr = httptest.NewRecorder() + req, err = http.NewRequest(http.MethodPost, path.Join(adminPath, defaultAdminUsername, "forgot-password"), nil) + require.NoError(t, err) + testServer.Config.Handler.ServeHTTP(rr, req) + assert.Equal(t, http.StatusNotFound, rr.Code) + + rr = httptest.NewRecorder() + req, err = http.NewRequest(http.MethodPost, path.Join(adminPath, defaultAdminUsername, "reset-password"), nil) + require.NoError(t, err) + testServer.Config.Handler.ServeHTTP(rr, req) + assert.Equal(t, http.StatusNotFound, rr.Code) + + rr = httptest.NewRecorder() + req, err = http.NewRequest(http.MethodPost, webAdminLoginPath, nil) + require.NoError(t, err) + testServer.Config.Handler.ServeHTTP(rr, req) + assert.Equal(t, http.StatusMethodNotAllowed, rr.Code) + + rr = httptest.NewRecorder() + req, err = http.NewRequest(http.MethodPost, webAdminResetPwdPath, nil) + require.NoError(t, err) + testServer.Config.Handler.ServeHTTP(rr, req) + assert.Equal(t, http.StatusNotFound, rr.Code) + + rr = httptest.NewRecorder() + req, err = http.NewRequest(http.MethodPost, webAdminForgotPwdPath, nil) + require.NoError(t, err) + testServer.Config.Handler.ServeHTTP(rr, req) + assert.Equal(t, http.StatusNotFound, rr.Code) +} + +func TestDisabledUserLoginMethods(t *testing.T) { + server := httpdServer{ + binding: Binding{ + Address: "", + Port: 8080, + EnableWebAdmin: true, + EnableWebClient: true, + EnableRESTAPI: true, + DisabledLoginMethods: 40, + }, + enableWebAdmin: true, + enableWebClient: true, + enableRESTAPI: true, + } + server.initializeRouter() + testServer := httptest.NewServer(server.router) + defer testServer.Close() + + rr := httptest.NewRecorder() + req, err := http.NewRequest(http.MethodGet, userTokenPath, nil) + require.NoError(t, err) + testServer.Config.Handler.ServeHTTP(rr, req) + assert.Equal(t, http.StatusNotFound, rr.Code) + + rr = httptest.NewRecorder() + req, err = http.NewRequest(http.MethodPost, userPath+"/user/forgot-password", nil) + require.NoError(t, err) + testServer.Config.Handler.ServeHTTP(rr, req) + assert.Equal(t, http.StatusNotFound, rr.Code) + + rr = httptest.NewRecorder() + req, err = http.NewRequest(http.MethodPost, userPath+"/user/reset-password", nil) + require.NoError(t, err) + testServer.Config.Handler.ServeHTTP(rr, req) + assert.Equal(t, http.StatusNotFound, rr.Code) + + rr = httptest.NewRecorder() + req, err = http.NewRequest(http.MethodPost, webClientLoginPath, nil) + require.NoError(t, err) + testServer.Config.Handler.ServeHTTP(rr, req) + assert.Equal(t, http.StatusMethodNotAllowed, rr.Code) + + rr = httptest.NewRecorder() + req, err = http.NewRequest(http.MethodPost, webClientResetPwdPath, nil) + require.NoError(t, err) + testServer.Config.Handler.ServeHTTP(rr, req) + assert.Equal(t, http.StatusNotFound, rr.Code) + + rr = httptest.NewRecorder() + req, err = http.NewRequest(http.MethodPost, webClientForgotPwdPath, nil) + require.NoError(t, err) + testServer.Config.Handler.ServeHTTP(rr, req) + assert.Equal(t, http.StatusNotFound, rr.Code) +} + func TestGetLogEventString(t *testing.T) { assert.Equal(t, "Login failed", getLogEventString(notifier.LogEventTypeLoginFailed)) assert.Equal(t, "Login with non-existent user", getLogEventString(notifier.LogEventTypeLoginNoUser)) @@ -4066,6 +4176,39 @@ func TestI18NErrors(t *testing.T) { assert.Equal(t, `{"a":"b"}`, errI18n.Args()) } +func TestConvertEnabledLoginMethods(t *testing.T) { + b := Binding{ + EnabledLoginMethods: 0, + DisabledLoginMethods: 1, + } + b.convertLoginMethods() + assert.Equal(t, 1, b.DisabledLoginMethods) + b.DisabledLoginMethods = 0 + b.EnabledLoginMethods = 1 + b.convertLoginMethods() + assert.Equal(t, 14, b.DisabledLoginMethods) + b.DisabledLoginMethods = 0 + b.EnabledLoginMethods = 2 + b.convertLoginMethods() + assert.Equal(t, 13, b.DisabledLoginMethods) + b.DisabledLoginMethods = 0 + b.EnabledLoginMethods = 3 + b.convertLoginMethods() + assert.Equal(t, 12, b.DisabledLoginMethods) + b.DisabledLoginMethods = 0 + b.EnabledLoginMethods = 4 + b.convertLoginMethods() + assert.Equal(t, 11, b.DisabledLoginMethods) + b.DisabledLoginMethods = 0 + b.EnabledLoginMethods = 7 + b.convertLoginMethods() + assert.Equal(t, 8, b.DisabledLoginMethods) + b.DisabledLoginMethods = 0 + b.EnabledLoginMethods = 15 + b.convertLoginMethods() + assert.Equal(t, 0, b.DisabledLoginMethods) +} + func getCSRFTokenFromBody(body io.Reader) (string, error) { doc, err := html.Parse(body) if err != nil { diff --git a/internal/httpd/oidc_test.go b/internal/httpd/oidc_test.go index 9a098b36..aff160f6 100644 --- a/internal/httpd/oidc_test.go +++ b/internal/httpd/oidc_test.go @@ -1548,7 +1548,7 @@ func TestOIDCWithLoginFormsDisabled(t *testing.T) { server := getTestOIDCServer() server.binding.OIDC.ImplicitRoles = true - server.binding.EnabledLoginMethods = 3 + server.binding.DisabledLoginMethods = 12 server.binding.EnableWebAdmin = true server.binding.EnableWebClient = true err := server.binding.OIDC.initialize() diff --git a/internal/httpd/server.go b/internal/httpd/server.go index a5446695..17fb830f 100644 --- a/internal/httpd/server.go +++ b/internal/httpd/server.go @@ -1298,23 +1298,55 @@ func (s *httpdServer) initializeRouter() { } } - if s.enableRESTAPI { - // share API available to external users - s.router.Get(sharesPath+"/{id}", s.downloadFromShare) //nolint:goconst - 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.setupRESTAPIRoutes() - s.router.Get(tokenPath, s.getToken) - s.router.Post(adminPath+"/{username}/forgot-password", forgotAdminPassword) - s.router.Post(adminPath+"/{username}/reset-password", resetAdminPassword) - s.router.Post(userPath+"/{username}/forgot-password", forgotUserPassword) - s.router.Post(userPath+"/{username}/reset-password", resetUserPassword) + if s.enableWebAdmin || s.enableWebClient { + s.router.Group(func(router chi.Router) { + router.Use(cleanCacheControlMiddleware) + router.Use(compressor.Handler) + serveStaticDir(router, webStaticFilesPath, s.staticFilesPath, true) + }) + if s.binding.OIDC.isEnabled() { + s.router.Get(webOIDCRedirectPath, s.handleOIDCRedirect) + } + if s.enableWebClient { + s.router.Get(webRootPath, func(w http.ResponseWriter, r *http.Request) { + r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) + s.redirectToWebPath(w, r, webClientLoginPath) + }) + s.router.Get(webBasePath, func(w http.ResponseWriter, r *http.Request) { + r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) + s.redirectToWebPath(w, r, webClientLoginPath) + }) + } else { + s.router.Get(webRootPath, func(w http.ResponseWriter, r *http.Request) { + r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) + s.redirectToWebPath(w, r, webAdminLoginPath) + }) + s.router.Get(webBasePath, func(w http.ResponseWriter, r *http.Request) { + r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) + s.redirectToWebPath(w, r, webAdminLoginPath) + }) + } + } + + s.setupWebClientRoutes() + s.setupWebAdminRoutes() +} + +func (s *httpdServer) setupRESTAPIRoutes() { + if s.enableRESTAPI { + if !s.binding.isAdminTokenEndpointDisabled() { + s.router.Get(tokenPath, s.getToken) + s.router.Post(adminPath+"/{username}/forgot-password", forgotAdminPassword) + s.router.Post(adminPath+"/{username}/reset-password", resetAdminPassword) + } s.router.Group(func(router chi.Router) { router.Use(checkNodeToken(s.tokenAuth)) - router.Use(checkAPIKeyAuth(s.tokenAuth, dataprovider.APIKeyScopeAdmin)) + if !s.binding.isAdminAPIKeyAuthDisabled() { + router.Use(checkAPIKeyAuth(s.tokenAuth, dataprovider.APIKeyScopeAdmin)) + } router.Use(jwtauth.Verify(s.tokenAuth, jwtauth.TokenFromHeader)) router.Use(jwtAuthenticatorAPI) @@ -1429,10 +1461,23 @@ func (s *httpdServer) initializeRouter() { }) }) - s.router.Get(userTokenPath, s.getUserToken) + // share API available to external users + 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) + + if !s.binding.isUserTokenEndpointDisabled() { + s.router.Get(userTokenPath, s.getUserToken) + s.router.Post(userPath+"/{username}/forgot-password", forgotUserPassword) + s.router.Post(userPath+"/{username}/reset-password", resetUserPassword) + } s.router.Group(func(router chi.Router) { - router.Use(checkAPIKeyAuth(s.tokenAuth, dataprovider.APIKeyScopeUser)) + if !s.binding.isUserAPIKeyAuthDisabled() { + router.Use(checkAPIKeyAuth(s.tokenAuth, dataprovider.APIKeyScopeUser)) + } router.Use(jwtauth.Verify(s.tokenAuth, jwtauth.TokenFromHeader)) router.Use(jwtAuthenticatorAPIUser) @@ -1498,39 +1543,6 @@ func (s *httpdServer) initializeRouter() { }) } } - - if s.enableWebAdmin || s.enableWebClient { - s.router.Group(func(router chi.Router) { - router.Use(cleanCacheControlMiddleware) - router.Use(compressor.Handler) - serveStaticDir(router, webStaticFilesPath, s.staticFilesPath, true) - }) - if s.binding.OIDC.isEnabled() { - s.router.Get(webOIDCRedirectPath, s.handleOIDCRedirect) - } - if s.enableWebClient { - s.router.Get(webRootPath, func(w http.ResponseWriter, r *http.Request) { - r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) - s.redirectToWebPath(w, r, webClientLoginPath) - }) - s.router.Get(webBasePath, func(w http.ResponseWriter, r *http.Request) { - r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) - s.redirectToWebPath(w, r, webClientLoginPath) - }) - } else { - s.router.Get(webRootPath, func(w http.ResponseWriter, r *http.Request) { - r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) - s.redirectToWebPath(w, r, webAdminLoginPath) - }) - s.router.Get(webBasePath, func(w http.ResponseWriter, r *http.Request) { - r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) - s.redirectToWebPath(w, r, webAdminLoginPath) - }) - } - } - - s.setupWebClientRoutes() - s.setupWebAdminRoutes() } func (s *httpdServer) setupWebClientRoutes() { diff --git a/sftpgo.json b/sftpgo.json index 591087eb..20361311 100644 --- a/sftpgo.json +++ b/sftpgo.json @@ -272,6 +272,7 @@ "enable_web_client": true, "enable_rest_api": true, "enabled_login_methods": 0, + "disabled_login_methods": 0, "enable_https": false, "certificate_file": "", "certificate_key_file": "",