From 7ae9303c9910f7d17427fa6ba071767ddf6df51b Mon Sep 17 00:00:00 2001 From: Nicola Murino Date: Thu, 22 Sep 2022 17:27:00 +0200 Subject: [PATCH] allow to disable REST API Fixes #987 Signed-off-by: Nicola Murino --- docs/full-configuration.md | 1 + docs/rest-api.md | 5 + internal/config/config.go | 7 + internal/config/config_test.go | 5 + internal/httpd/httpd.go | 5 + internal/httpd/internal_test.go | 34 +++ internal/httpd/server.go | 358 ++++++++++++++++---------------- sftpgo.json | 1 + 8 files changed, 239 insertions(+), 177 deletions(-) diff --git a/docs/full-configuration.md b/docs/full-configuration.md index 03d1a24c..26630e4f 100644 --- a/docs/full-configuration.md +++ b/docs/full-configuration.md @@ -260,6 +260,7 @@ The configuration file contains the following sections: - `address`, string. Leave blank to listen on all available network interfaces. On *NIX you can specify an absolute path to listen on a Unix-domain socket Default: blank. - `enable_web_admin`, boolean. Set to `false` to disable the built-in web admin for this binding. You also need to define `templates_path` and `static_files_path` to use the built-in web admin interface. Default `true`. - `enable_web_client`, boolean. Set to `false` to disable the built-in web client for this binding. You also need to define `templates_path` and `static_files_path` to use the built-in web client interface. Default `true`. + - `enable_rest_api`, boolean. Set to `false` to disable REST API. Default `true`. - `enabled_login_methods`, integer. Defines the login methods available for the WebAdmin and WebClient UIs. `0` means any configured method: username/password login form and OIDC, if enabled. `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. You can combine the values. For example `3` means that you can only login using OIDC on both WebClient and WebAdmin UI. Default: `0`. - `enable_https`, boolean. Set to `true` and provide both a certificate and a key file to enable HTTPS connection for this binding. Default `false`. - `certificate_file`, string. Binding specific TLS certificate. This can be an absolute path or a path relative to the config dir. diff --git a/docs/rest-api.md b/docs/rest-api.md index da7810ce..d5f473e0 100644 --- a/docs/rest-api.md +++ b/docs/rest-api.md @@ -20,6 +20,8 @@ If you define multiple bindings, each binding will sign JWT tokens with a differ If, instead, you want to use a persistent signing key for JWT tokens, you can define a signing passphrase via configuration file or environment variable. +REST API can be disabled within the `httpd` configuration via the `enable_rest_api` key. + You can create other administrator and assign them the following permissions: - add users @@ -35,8 +37,11 @@ You can create other administrator and assign them the following permissions: - manage API keys - manage system - manage admins +- manage groups - manage data retention +- manage metadata - view events +- manage event rules You can also restrict administrator access based on the source IP address. If you are running SFTPGo behind a reverse proxy you need to allow both the proxy IP address and the real client IP. diff --git a/internal/config/config.go b/internal/config/config.go index e73fcb6f..e5a12ae7 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -100,6 +100,7 @@ var ( Port: 8080, EnableWebAdmin: true, EnableWebClient: true, + EnableRESTAPI: true, EnabledLoginMethods: 0, EnableHTTPS: false, CertificateFile: "", @@ -1685,6 +1686,12 @@ func getHTTPDBindingFromEnv(idx int) { //nolint:gocyclo isSet = true } + enableRESTAPI, ok := lookupBoolFromEnv(fmt.Sprintf("SFTPGO_HTTPD__BINDINGS__%v__ENABLE_REST_API", idx)) + if ok { + binding.EnableRESTAPI = enableRESTAPI + isSet = true + } + enabledLoginMethods, ok := lookupIntFromEnv(fmt.Sprintf("SFTPGO_HTTPD__BINDINGS__%v__ENABLED_LOGIN_METHODS", idx)) if ok { binding.EnabledLoginMethods = int(enabledLoginMethods) diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 85660681..ea0cdc99 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -1019,6 +1019,7 @@ func TestHTTPDBindingsFromEnv(t *testing.T) { os.Setenv("SFTPGO_HTTPD__BINDINGS__2__PORT", "9000") os.Setenv("SFTPGO_HTTPD__BINDINGS__2__ENABLE_WEB_ADMIN", "0") 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__RENDER_OPENAPI", "0") os.Setenv("SFTPGO_HTTPD__BINDINGS__2__ENABLE_HTTPS", "1 ") @@ -1088,6 +1089,7 @@ func TestHTTPDBindingsFromEnv(t *testing.T) { os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__MIN_TLS_VERSION") os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__ENABLE_WEB_ADMIN") 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__RENDER_OPENAPI") os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__CLIENT_AUTH_TYPE") @@ -1149,6 +1151,7 @@ func TestHTTPDBindingsFromEnv(t *testing.T) { require.Equal(t, 12, bindings[0].MinTLSVersion) require.True(t, bindings[0].EnableWebAdmin) require.True(t, bindings[0].EnableWebClient) + require.True(t, bindings[0].EnableRESTAPI) require.Equal(t, 0, bindings[0].EnabledLoginMethods) require.True(t, bindings[0].RenderOpenAPI) require.Len(t, bindings[0].TLSCipherSuites, 1) @@ -1165,6 +1168,7 @@ func TestHTTPDBindingsFromEnv(t *testing.T) { require.Equal(t, 12, bindings[0].MinTLSVersion) require.True(t, bindings[1].EnableWebAdmin) require.True(t, bindings[1].EnableWebClient) + require.True(t, bindings[1].EnableRESTAPI) require.Equal(t, 0, bindings[1].EnabledLoginMethods) require.True(t, bindings[1].RenderOpenAPI) require.Nil(t, bindings[1].TLSCipherSuites) @@ -1182,6 +1186,7 @@ func TestHTTPDBindingsFromEnv(t *testing.T) { require.Equal(t, 13, bindings[2].MinTLSVersion) require.False(t, bindings[2].EnableWebAdmin) require.False(t, bindings[2].EnableWebClient) + require.False(t, bindings[2].EnableRESTAPI) require.Equal(t, 3, bindings[2].EnabledLoginMethods) require.False(t, bindings[2].RenderOpenAPI) require.Equal(t, 1, bindings[2].ClientAuthType) diff --git a/internal/httpd/httpd.go b/internal/httpd/httpd.go index fc2509d3..27d4fc35 100644 --- a/internal/httpd/httpd.go +++ b/internal/httpd/httpd.go @@ -410,6 +410,8 @@ type Binding struct { // Enable the built-in client interface. // You have to define TemplatesPath and StaticFilesPath for this to work EnableWebClient bool `json:"enable_web_client" mapstructure:"enable_web_client"` + // Enable REST API + EnableRESTAPI bool `json:"enable_rest_api" mapstructure:"enable_rest_api"` // Defines the login methods available for the WebAdmin and WebClient UIs: // // - 0 means any configured method: username/password login form and OIDC, if enabled @@ -522,6 +524,9 @@ func (b *Binding) GetAddress() string { // IsValid returns true if the binding is valid func (b *Binding) IsValid() bool { + if !b.EnableRESTAPI && !b.EnableWebAdmin && !b.EnableWebClient { + return false + } if b.Port > 0 { return true } diff --git a/internal/httpd/internal_test.go b/internal/httpd/internal_test.go index 8ebe9f28..9052820d 100644 --- a/internal/httpd/internal_test.go +++ b/internal/httpd/internal_test.go @@ -309,6 +309,8 @@ func TestShouldBind(t *testing.T) { }, }, } + require.False(t, c.ShouldBind()) + c.Bindings[0].EnableRESTAPI = true require.True(t, c.ShouldBind()) c.Bindings[0].Port = 0 @@ -833,6 +835,7 @@ func TestCSRFToken(t *testing.T) { Port: 8080, EnableWebAdmin: true, EnableWebClient: true, + EnableRESTAPI: true, RenderOpenAPI: true, }) fn := verifyCSRFHeader(r) @@ -1080,6 +1083,7 @@ func TestAPIKeyAuthForbidden(t *testing.T) { Port: 8080, EnableWebAdmin: true, EnableWebClient: true, + EnableRESTAPI: true, RenderOpenAPI: true, }) fn := forbidAPIKeyAuthentication(r) @@ -1104,6 +1108,7 @@ func TestJWTTokenValidation(t *testing.T) { Port: 8080, EnableWebAdmin: true, EnableWebClient: true, + EnableRESTAPI: true, RenderOpenAPI: true, }, } @@ -1648,6 +1653,7 @@ func TestProxyHeaders(t *testing.T) { Port: 8080, EnableWebAdmin: true, EnableWebClient: false, + EnableRESTAPI: true, ProxyAllowed: []string{testIP, "10.8.0.0/30"}, ClientIPProxyHeader: "x-forwarded-for", } @@ -1739,6 +1745,7 @@ func TestRecoverer(t *testing.T) { Port: 8080, EnableWebAdmin: true, EnableWebClient: false, + EnableRESTAPI: true, } server := newHttpdServer(b, "../static", "", CorsConfig{}, "../openapi") server.initializeRouter() @@ -1859,6 +1866,7 @@ func TestWebAdminRedirect(t *testing.T) { Port: 8080, EnableWebAdmin: true, EnableWebClient: false, + EnableRESTAPI: true, } server := newHttpdServer(b, "../static", "", CorsConfig{}, "../openapi") server.initializeRouter() @@ -2323,16 +2331,19 @@ func TestLoginLinks(t *testing.T) { b := Binding{ EnableWebAdmin: true, EnableWebClient: false, + EnableRESTAPI: true, } assert.False(t, b.showClientLoginURL()) b = Binding{ EnableWebAdmin: false, EnableWebClient: true, + EnableRESTAPI: true, } assert.False(t, b.showAdminLoginURL()) b = Binding{ EnableWebAdmin: true, EnableWebClient: true, + EnableRESTAPI: true, } assert.True(t, b.showAdminLoginURL()) assert.True(t, b.showClientLoginURL()) @@ -2489,6 +2500,7 @@ func TestSecureMiddlewareIntegration(t *testing.T) { }, enableWebAdmin: true, enableWebClient: true, + enableRESTAPI: true, } server.binding.Security.updateProxyHeaders() err := server.binding.parseAllowedProxy() @@ -2560,6 +2572,27 @@ func TestGetCompressedFileName(t *testing.T) { require.Equal(t, fmt.Sprintf("%s-file1.zip", username), res) } +func TestRESTAPIDisabled(t *testing.T) { + server := httpdServer{ + enableWebAdmin: true, + enableWebClient: true, + enableRESTAPI: false, + } + server.initializeRouter() + assert.False(t, server.enableRESTAPI) + rr := httptest.NewRecorder() + r, err := http.NewRequest(http.MethodGet, healthzPath, nil) + assert.NoError(t, err) + server.router.ServeHTTP(rr, r) + assert.Equal(t, http.StatusOK, rr.Code) + + rr = httptest.NewRecorder() + r, err = http.NewRequest(http.MethodGet, tokenPath, nil) + assert.NoError(t, err) + server.router.ServeHTTP(rr, r) + assert.Equal(t, http.StatusNotFound, rr.Code) +} + func TestWebAdminSetupWithInstallCode(t *testing.T) { installationCode = "1234" // delete all the admins @@ -2580,6 +2613,7 @@ func TestWebAdminSetupWithInstallCode(t *testing.T) { server := httpdServer{ enableWebAdmin: true, enableWebClient: true, + enableRESTAPI: true, } server.initializeRouter() diff --git a/internal/httpd/server.go b/internal/httpd/server.go index 648412a8..f4d889d5 100644 --- a/internal/httpd/server.go +++ b/internal/httpd/server.go @@ -57,6 +57,7 @@ type httpdServer struct { openAPIPath string enableWebAdmin bool enableWebClient bool + enableRESTAPI bool renderOpenAPI bool isShared int router *chi.Mux @@ -77,6 +78,7 @@ func newHttpdServer(b Binding, staticFilesPath, signingPassphrase string, cors C openAPIPath: openAPIPath, enableWebAdmin: b.EnableWebAdmin, enableWebClient: b.EnableWebClient, + enableRESTAPI: b.EnableRESTAPI, renderOpenAPI: b.RenderOpenAPI, signingPassphrase: signingPassphrase, cors: cors, @@ -1178,187 +1180,189 @@ func (s *httpdServer) initializeRouter() { render.PlainText(w, r, "User-agent: *\nDisallow: /") }) - // share API exposed 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.enableRESTAPI { + // share API exposed 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) - 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) + 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) - s.router.Group(func(router chi.Router) { - router.Use(checkAPIKeyAuth(s.tokenAuth, dataprovider.APIKeyScopeAdmin)) - router.Use(jwtauth.Verify(s.tokenAuth, jwtauth.TokenFromHeader)) - router.Use(jwtAuthenticatorAPI) - - router.Get(versionPath, func(w http.ResponseWriter, r *http.Request) { - r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) - render.JSON(w, r, version.Get()) - }) - - router.With(forbidAPIKeyAuthentication).Get(logoutPath, s.logout) - router.With(forbidAPIKeyAuthentication).Get(adminProfilePath, getAdminProfile) - router.With(forbidAPIKeyAuthentication).Put(adminProfilePath, updateAdminProfile) - router.With(forbidAPIKeyAuthentication).Put(adminPwdPath, changeAdminPassword) - // admin TOTP APIs - router.With(forbidAPIKeyAuthentication).Get(adminTOTPConfigsPath, getTOTPConfigs) - router.With(forbidAPIKeyAuthentication).Post(adminTOTPGeneratePath, generateTOTPSecret) - router.With(forbidAPIKeyAuthentication).Post(adminTOTPValidatePath, validateTOTPPasscode) - router.With(forbidAPIKeyAuthentication).Post(adminTOTPSavePath, saveTOTPConfig) - router.With(forbidAPIKeyAuthentication).Get(admin2FARecoveryCodesPath, getRecoveryCodes) - router.With(forbidAPIKeyAuthentication).Post(admin2FARecoveryCodesPath, generateRecoveryCodes) - - 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(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(s.checkPerm(dataprovider.PermAdminCloseConnections)). - Delete(activeConnectionsPath+"/{connectionID}", handleCloseConnection) - router.With(s.checkPerm(dataprovider.PermAdminQuotaScans)).Get(quotasBasePath+"/users/scans", getUsersQuotaScans) - router.With(s.checkPerm(dataprovider.PermAdminQuotaScans)).Post(quotasBasePath+"/users/{username}/scan", startUserQuotaScan) - router.With(s.checkPerm(dataprovider.PermAdminQuotaScans)).Get(quotasBasePath+"/folders/scans", getFoldersQuotaScans) - 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.PermAdminManageGroups)).Get(groupPath, getGroups) - router.With(s.checkPerm(dataprovider.PermAdminManageGroups)).Get(groupPath+"/{name}", getGroupByName) - router.With(s.checkPerm(dataprovider.PermAdminManageGroups)).Post(groupPath, addGroup) - router.With(s.checkPerm(dataprovider.PermAdminManageGroups)).Put(groupPath+"/{name}", updateGroup) - router.With(s.checkPerm(dataprovider.PermAdminManageGroups)).Delete(groupPath+"/{name}", deleteGroup) - 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(quotasBasePath+"/users/{username}/usage", - updateUserQuotaUsage) - router.With(s.checkPerm(dataprovider.PermAdminChangeUsers)).Put(quotasBasePath+"/users/{username}/transfer-usage", - updateUserTransferQuotaUsage) - router.With(s.checkPerm(dataprovider.PermAdminChangeUsers)).Put(quotasBasePath+"/folders/{name}/usage", - updateFolderQuotaUsage) - 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.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(s.checkPerm(dataprovider.PermAdminMetadataChecks)).Get(metadataChecksPath, getMetadataChecks) - router.With(s.checkPerm(dataprovider.PermAdminMetadataChecks)).Post(metadataBasePath+"/{username}/check", - startMetadataCheck) - router.With(s.checkPerm(dataprovider.PermAdminViewEvents), compressor.Handler). - Get(fsEventsPath, searchFsEvents) - router.With(s.checkPerm(dataprovider.PermAdminViewEvents), compressor.Handler). - Get(providerEventsPath, searchProviderEvents) - router.With(forbidAPIKeyAuthentication, s.checkPerm(dataprovider.PermAdminManageAPIKeys)). - Get(apiKeysPath, getAPIKeys) - router.With(forbidAPIKeyAuthentication, s.checkPerm(dataprovider.PermAdminManageAPIKeys)). - Post(apiKeysPath, addAPIKey) - router.With(forbidAPIKeyAuthentication, s.checkPerm(dataprovider.PermAdminManageAPIKeys)). - Get(apiKeysPath+"/{id}", getAPIKeyByID) - router.With(forbidAPIKeyAuthentication, s.checkPerm(dataprovider.PermAdminManageAPIKeys)). - Put(apiKeysPath+"/{id}", updateAPIKey) - router.With(forbidAPIKeyAuthentication, s.checkPerm(dataprovider.PermAdminManageAPIKeys)). - Delete(apiKeysPath+"/{id}", deleteAPIKey) - router.With(s.checkPerm(dataprovider.PermAdminManageEventRules)).Get(eventActionsPath, getEventActions) - router.With(s.checkPerm(dataprovider.PermAdminManageEventRules)).Get(eventActionsPath+"/{name}", getEventActionByName) - router.With(s.checkPerm(dataprovider.PermAdminManageEventRules)).Post(eventActionsPath, addEventAction) - router.With(s.checkPerm(dataprovider.PermAdminManageEventRules)).Put(eventActionsPath+"/{name}", updateEventAction) - router.With(s.checkPerm(dataprovider.PermAdminManageEventRules)).Delete(eventActionsPath+"/{name}", deleteEventAction) - router.With(s.checkPerm(dataprovider.PermAdminManageEventRules)).Get(eventRulesPath, getEventRules) - router.With(s.checkPerm(dataprovider.PermAdminManageEventRules)).Get(eventRulesPath+"/{name}", getEventRuleByName) - router.With(s.checkPerm(dataprovider.PermAdminManageEventRules)).Post(eventRulesPath, addEventRule) - router.With(s.checkPerm(dataprovider.PermAdminManageEventRules)).Put(eventRulesPath+"/{name}", updateEventRule) - router.With(s.checkPerm(dataprovider.PermAdminManageEventRules)).Delete(eventRulesPath+"/{name}", deleteEventRule) - }) - - s.router.Get(userTokenPath, s.getUserToken) - - s.router.Group(func(router chi.Router) { - router.Use(checkAPIKeyAuth(s.tokenAuth, dataprovider.APIKeyScopeUser)) - router.Use(jwtauth.Verify(s.tokenAuth, jwtauth.TokenFromHeader)) - router.Use(jwtAuthenticatorAPIUser) - - router.With(forbidAPIKeyAuthentication).Get(userLogoutPath, s.logout) - router.With(forbidAPIKeyAuthentication, s.checkSecondFactorRequirement, - s.checkHTTPUserPerm(sdk.WebClientPasswordChangeDisabled)).Put(userPwdPath, changeUserPassword) - router.With(forbidAPIKeyAuthentication).Get(userProfilePath, getUserProfile) - router.With(forbidAPIKeyAuthentication, s.checkSecondFactorRequirement).Put(userProfilePath, updateUserProfile) - // user TOTP APIs - router.With(forbidAPIKeyAuthentication, s.checkHTTPUserPerm(sdk.WebClientMFADisabled)). - Get(userTOTPConfigsPath, getTOTPConfigs) - router.With(forbidAPIKeyAuthentication, s.checkHTTPUserPerm(sdk.WebClientMFADisabled)). - Post(userTOTPGeneratePath, generateTOTPSecret) - router.With(forbidAPIKeyAuthentication, s.checkHTTPUserPerm(sdk.WebClientMFADisabled)). - Post(userTOTPValidatePath, validateTOTPPasscode) - router.With(forbidAPIKeyAuthentication, s.checkHTTPUserPerm(sdk.WebClientMFADisabled)). - Post(userTOTPSavePath, saveTOTPConfig) - router.With(forbidAPIKeyAuthentication, s.checkHTTPUserPerm(sdk.WebClientMFADisabled)). - Get(user2FARecoveryCodesPath, getRecoveryCodes) - router.With(forbidAPIKeyAuthentication, s.checkHTTPUserPerm(sdk.WebClientMFADisabled)). - Post(user2FARecoveryCodesPath, generateRecoveryCodes) - - router.With(s.checkSecondFactorRequirement, compressor.Handler).Get(userDirsPath, readUserFolder) - router.With(s.checkSecondFactorRequirement, s.checkHTTPUserPerm(sdk.WebClientWriteDisabled)). - Post(userDirsPath, createUserDir) - router.With(s.checkSecondFactorRequirement, s.checkHTTPUserPerm(sdk.WebClientWriteDisabled)). - Patch(userDirsPath, renameUserDir) - router.With(s.checkSecondFactorRequirement, s.checkHTTPUserPerm(sdk.WebClientWriteDisabled)). - Delete(userDirsPath, deleteUserDir) - router.With(s.checkSecondFactorRequirement).Get(userFilesPath, getUserFile) - router.With(s.checkSecondFactorRequirement, s.checkHTTPUserPerm(sdk.WebClientWriteDisabled)). - Post(userFilesPath, uploadUserFiles) - router.With(s.checkSecondFactorRequirement, s.checkHTTPUserPerm(sdk.WebClientWriteDisabled)). - Patch(userFilesPath, renameUserFile) - router.With(s.checkSecondFactorRequirement, s.checkHTTPUserPerm(sdk.WebClientWriteDisabled)). - Delete(userFilesPath, deleteUserFile) - router.With(s.checkSecondFactorRequirement).Post(userStreamZipPath, getUserFilesAsZipStream) - router.With(s.checkSecondFactorRequirement, s.checkHTTPUserPerm(sdk.WebClientSharesDisabled)). - Get(userSharesPath, getShares) - router.With(s.checkSecondFactorRequirement, s.checkHTTPUserPerm(sdk.WebClientSharesDisabled)). - Post(userSharesPath, addShare) - router.With(s.checkSecondFactorRequirement, s.checkHTTPUserPerm(sdk.WebClientSharesDisabled)). - Get(userSharesPath+"/{id}", getShareByID) - router.With(s.checkSecondFactorRequirement, s.checkHTTPUserPerm(sdk.WebClientSharesDisabled)). - Put(userSharesPath+"/{id}", updateShare) - router.With(s.checkSecondFactorRequirement, s.checkHTTPUserPerm(sdk.WebClientSharesDisabled)). - Delete(userSharesPath+"/{id}", deleteShare) - router.With(s.checkSecondFactorRequirement, s.checkHTTPUserPerm(sdk.WebClientWriteDisabled)). - Post(userUploadFilePath, uploadUserFile) - router.With(s.checkSecondFactorRequirement, s.checkHTTPUserPerm(sdk.WebClientWriteDisabled)). - Patch(userFilesDirsMetadataPath, setFileDirMetadata) - }) - - if s.renderOpenAPI { s.router.Group(func(router chi.Router) { - router.Use(compressor.Handler) - serveStaticDir(router, webOpenAPIPath, s.openAPIPath) + router.Use(checkAPIKeyAuth(s.tokenAuth, dataprovider.APIKeyScopeAdmin)) + router.Use(jwtauth.Verify(s.tokenAuth, jwtauth.TokenFromHeader)) + router.Use(jwtAuthenticatorAPI) + + router.Get(versionPath, func(w http.ResponseWriter, r *http.Request) { + r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) + render.JSON(w, r, version.Get()) + }) + + router.With(forbidAPIKeyAuthentication).Get(logoutPath, s.logout) + router.With(forbidAPIKeyAuthentication).Get(adminProfilePath, getAdminProfile) + router.With(forbidAPIKeyAuthentication).Put(adminProfilePath, updateAdminProfile) + router.With(forbidAPIKeyAuthentication).Put(adminPwdPath, changeAdminPassword) + // admin TOTP APIs + router.With(forbidAPIKeyAuthentication).Get(adminTOTPConfigsPath, getTOTPConfigs) + router.With(forbidAPIKeyAuthentication).Post(adminTOTPGeneratePath, generateTOTPSecret) + router.With(forbidAPIKeyAuthentication).Post(adminTOTPValidatePath, validateTOTPPasscode) + router.With(forbidAPIKeyAuthentication).Post(adminTOTPSavePath, saveTOTPConfig) + router.With(forbidAPIKeyAuthentication).Get(admin2FARecoveryCodesPath, getRecoveryCodes) + router.With(forbidAPIKeyAuthentication).Post(admin2FARecoveryCodesPath, generateRecoveryCodes) + + 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(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(s.checkPerm(dataprovider.PermAdminCloseConnections)). + Delete(activeConnectionsPath+"/{connectionID}", handleCloseConnection) + router.With(s.checkPerm(dataprovider.PermAdminQuotaScans)).Get(quotasBasePath+"/users/scans", getUsersQuotaScans) + router.With(s.checkPerm(dataprovider.PermAdminQuotaScans)).Post(quotasBasePath+"/users/{username}/scan", startUserQuotaScan) + router.With(s.checkPerm(dataprovider.PermAdminQuotaScans)).Get(quotasBasePath+"/folders/scans", getFoldersQuotaScans) + 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.PermAdminManageGroups)).Get(groupPath, getGroups) + router.With(s.checkPerm(dataprovider.PermAdminManageGroups)).Get(groupPath+"/{name}", getGroupByName) + router.With(s.checkPerm(dataprovider.PermAdminManageGroups)).Post(groupPath, addGroup) + router.With(s.checkPerm(dataprovider.PermAdminManageGroups)).Put(groupPath+"/{name}", updateGroup) + router.With(s.checkPerm(dataprovider.PermAdminManageGroups)).Delete(groupPath+"/{name}", deleteGroup) + 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(quotasBasePath+"/users/{username}/usage", + updateUserQuotaUsage) + router.With(s.checkPerm(dataprovider.PermAdminChangeUsers)).Put(quotasBasePath+"/users/{username}/transfer-usage", + updateUserTransferQuotaUsage) + router.With(s.checkPerm(dataprovider.PermAdminChangeUsers)).Put(quotasBasePath+"/folders/{name}/usage", + updateFolderQuotaUsage) + 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.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(s.checkPerm(dataprovider.PermAdminMetadataChecks)).Get(metadataChecksPath, getMetadataChecks) + router.With(s.checkPerm(dataprovider.PermAdminMetadataChecks)).Post(metadataBasePath+"/{username}/check", + startMetadataCheck) + router.With(s.checkPerm(dataprovider.PermAdminViewEvents), compressor.Handler). + Get(fsEventsPath, searchFsEvents) + router.With(s.checkPerm(dataprovider.PermAdminViewEvents), compressor.Handler). + Get(providerEventsPath, searchProviderEvents) + router.With(forbidAPIKeyAuthentication, s.checkPerm(dataprovider.PermAdminManageAPIKeys)). + Get(apiKeysPath, getAPIKeys) + router.With(forbidAPIKeyAuthentication, s.checkPerm(dataprovider.PermAdminManageAPIKeys)). + Post(apiKeysPath, addAPIKey) + router.With(forbidAPIKeyAuthentication, s.checkPerm(dataprovider.PermAdminManageAPIKeys)). + Get(apiKeysPath+"/{id}", getAPIKeyByID) + router.With(forbidAPIKeyAuthentication, s.checkPerm(dataprovider.PermAdminManageAPIKeys)). + Put(apiKeysPath+"/{id}", updateAPIKey) + router.With(forbidAPIKeyAuthentication, s.checkPerm(dataprovider.PermAdminManageAPIKeys)). + Delete(apiKeysPath+"/{id}", deleteAPIKey) + router.With(s.checkPerm(dataprovider.PermAdminManageEventRules)).Get(eventActionsPath, getEventActions) + router.With(s.checkPerm(dataprovider.PermAdminManageEventRules)).Get(eventActionsPath+"/{name}", getEventActionByName) + router.With(s.checkPerm(dataprovider.PermAdminManageEventRules)).Post(eventActionsPath, addEventAction) + router.With(s.checkPerm(dataprovider.PermAdminManageEventRules)).Put(eventActionsPath+"/{name}", updateEventAction) + router.With(s.checkPerm(dataprovider.PermAdminManageEventRules)).Delete(eventActionsPath+"/{name}", deleteEventAction) + router.With(s.checkPerm(dataprovider.PermAdminManageEventRules)).Get(eventRulesPath, getEventRules) + router.With(s.checkPerm(dataprovider.PermAdminManageEventRules)).Get(eventRulesPath+"/{name}", getEventRuleByName) + router.With(s.checkPerm(dataprovider.PermAdminManageEventRules)).Post(eventRulesPath, addEventRule) + router.With(s.checkPerm(dataprovider.PermAdminManageEventRules)).Put(eventRulesPath+"/{name}", updateEventRule) + router.With(s.checkPerm(dataprovider.PermAdminManageEventRules)).Delete(eventRulesPath+"/{name}", deleteEventRule) }) + + s.router.Get(userTokenPath, s.getUserToken) + + s.router.Group(func(router chi.Router) { + router.Use(checkAPIKeyAuth(s.tokenAuth, dataprovider.APIKeyScopeUser)) + router.Use(jwtauth.Verify(s.tokenAuth, jwtauth.TokenFromHeader)) + router.Use(jwtAuthenticatorAPIUser) + + router.With(forbidAPIKeyAuthentication).Get(userLogoutPath, s.logout) + router.With(forbidAPIKeyAuthentication, s.checkSecondFactorRequirement, + s.checkHTTPUserPerm(sdk.WebClientPasswordChangeDisabled)).Put(userPwdPath, changeUserPassword) + router.With(forbidAPIKeyAuthentication).Get(userProfilePath, getUserProfile) + router.With(forbidAPIKeyAuthentication, s.checkSecondFactorRequirement).Put(userProfilePath, updateUserProfile) + // user TOTP APIs + router.With(forbidAPIKeyAuthentication, s.checkHTTPUserPerm(sdk.WebClientMFADisabled)). + Get(userTOTPConfigsPath, getTOTPConfigs) + router.With(forbidAPIKeyAuthentication, s.checkHTTPUserPerm(sdk.WebClientMFADisabled)). + Post(userTOTPGeneratePath, generateTOTPSecret) + router.With(forbidAPIKeyAuthentication, s.checkHTTPUserPerm(sdk.WebClientMFADisabled)). + Post(userTOTPValidatePath, validateTOTPPasscode) + router.With(forbidAPIKeyAuthentication, s.checkHTTPUserPerm(sdk.WebClientMFADisabled)). + Post(userTOTPSavePath, saveTOTPConfig) + router.With(forbidAPIKeyAuthentication, s.checkHTTPUserPerm(sdk.WebClientMFADisabled)). + Get(user2FARecoveryCodesPath, getRecoveryCodes) + router.With(forbidAPIKeyAuthentication, s.checkHTTPUserPerm(sdk.WebClientMFADisabled)). + Post(user2FARecoveryCodesPath, generateRecoveryCodes) + + router.With(s.checkSecondFactorRequirement, compressor.Handler).Get(userDirsPath, readUserFolder) + router.With(s.checkSecondFactorRequirement, s.checkHTTPUserPerm(sdk.WebClientWriteDisabled)). + Post(userDirsPath, createUserDir) + router.With(s.checkSecondFactorRequirement, s.checkHTTPUserPerm(sdk.WebClientWriteDisabled)). + Patch(userDirsPath, renameUserDir) + router.With(s.checkSecondFactorRequirement, s.checkHTTPUserPerm(sdk.WebClientWriteDisabled)). + Delete(userDirsPath, deleteUserDir) + router.With(s.checkSecondFactorRequirement).Get(userFilesPath, getUserFile) + router.With(s.checkSecondFactorRequirement, s.checkHTTPUserPerm(sdk.WebClientWriteDisabled)). + Post(userFilesPath, uploadUserFiles) + router.With(s.checkSecondFactorRequirement, s.checkHTTPUserPerm(sdk.WebClientWriteDisabled)). + Patch(userFilesPath, renameUserFile) + router.With(s.checkSecondFactorRequirement, s.checkHTTPUserPerm(sdk.WebClientWriteDisabled)). + Delete(userFilesPath, deleteUserFile) + router.With(s.checkSecondFactorRequirement).Post(userStreamZipPath, getUserFilesAsZipStream) + router.With(s.checkSecondFactorRequirement, s.checkHTTPUserPerm(sdk.WebClientSharesDisabled)). + Get(userSharesPath, getShares) + router.With(s.checkSecondFactorRequirement, s.checkHTTPUserPerm(sdk.WebClientSharesDisabled)). + Post(userSharesPath, addShare) + router.With(s.checkSecondFactorRequirement, s.checkHTTPUserPerm(sdk.WebClientSharesDisabled)). + Get(userSharesPath+"/{id}", getShareByID) + router.With(s.checkSecondFactorRequirement, s.checkHTTPUserPerm(sdk.WebClientSharesDisabled)). + Put(userSharesPath+"/{id}", updateShare) + router.With(s.checkSecondFactorRequirement, s.checkHTTPUserPerm(sdk.WebClientSharesDisabled)). + Delete(userSharesPath+"/{id}", deleteShare) + router.With(s.checkSecondFactorRequirement, s.checkHTTPUserPerm(sdk.WebClientWriteDisabled)). + Post(userUploadFilePath, uploadUserFile) + router.With(s.checkSecondFactorRequirement, s.checkHTTPUserPerm(sdk.WebClientWriteDisabled)). + Patch(userFilesDirsMetadataPath, setFileDirMetadata) + }) + + if s.renderOpenAPI { + s.router.Group(func(router chi.Router) { + router.Use(compressor.Handler) + serveStaticDir(router, webOpenAPIPath, s.openAPIPath) + }) + } } if s.enableWebAdmin || s.enableWebClient { diff --git a/sftpgo.json b/sftpgo.json index 2daab3f4..63c16637 100644 --- a/sftpgo.json +++ b/sftpgo.json @@ -245,6 +245,7 @@ "address": "", "enable_web_admin": true, "enable_web_client": true, + "enable_rest_api": true, "enabled_login_methods": 0, "enable_https": false, "certificate_file": "",