REST API: add support for API key authentication

This commit is contained in:
Nicola Murino
2021-08-17 18:08:32 +02:00
parent 05c62b9f40
commit fe953d6b38
41 changed files with 3620 additions and 274 deletions

View File

@@ -54,7 +54,7 @@ const (
templateStatus = "status.html"
templateLogin = "login.html"
templateDefender = "defender.html"
templateChangePwd = "changepwd.html"
templateCredentials = "credentials.html"
templateMaintenance = "maintenance.html"
templateSetup = "adminsetup.html"
pageUsersTitle = "Users"
@@ -62,7 +62,7 @@ const (
pageConnectionsTitle = "Connections"
pageStatusTitle = "Status"
pageFoldersTitle = "Folders"
pageChangePwdTitle = "Change password"
pageCredentialsTitle = "Manage credentials"
pageMaintenanceTitle = "Maintenance"
pageDefenderTitle = "Defender"
pageSetupTitle = "Create first admin user"
@@ -88,7 +88,7 @@ type basePage struct {
FolderTemplateURL string
DefenderURL string
LogoutURL string
ChangeAdminPwdURL string
CredentialsURL string
FolderQuotaScanURL string
StatusURL string
MaintenanceURL string
@@ -153,9 +153,13 @@ type adminPage struct {
IsAdd bool
}
type changePwdPage struct {
type credentialsPage struct {
basePage
Error string
Error string
AllowAPIKeyAuth bool
ChangePwdURL string
ManageAPIKeyURL string
APIKeyError string
}
type maintenancePage struct {
@@ -213,9 +217,9 @@ func loadAdminTemplates(templatesPath string) {
filepath.Join(templatesPath, templateAdminDir, templateBase),
filepath.Join(templatesPath, templateAdminDir, templateAdmin),
}
changePwdPaths := []string{
credentialsPaths := []string{
filepath.Join(templatesPath, templateAdminDir, templateBase),
filepath.Join(templatesPath, templateAdminDir, templateChangePwd),
filepath.Join(templatesPath, templateAdminDir, templateCredentials),
}
connectionsPaths := []string{
filepath.Join(templatesPath, templateAdminDir, templateBase),
@@ -266,7 +270,7 @@ func loadAdminTemplates(templatesPath string) {
folderTmpl := util.LoadTemplate(rootTpl, folderPath...)
statusTmpl := util.LoadTemplate(rootTpl, statusPath...)
loginTmpl := util.LoadTemplate(rootTpl, loginPath...)
changePwdTmpl := util.LoadTemplate(rootTpl, changePwdPaths...)
credentialsTmpl := util.LoadTemplate(rootTpl, credentialsPaths...)
maintenanceTmpl := util.LoadTemplate(rootTpl, maintenancePath...)
defenderTmpl := util.LoadTemplate(rootTpl, defenderPath...)
setupTmpl := util.LoadTemplate(rootTpl, setupPath...)
@@ -281,7 +285,7 @@ func loadAdminTemplates(templatesPath string) {
adminTemplates[templateFolder] = folderTmpl
adminTemplates[templateStatus] = statusTmpl
adminTemplates[templateLogin] = loginTmpl
adminTemplates[templateChangePwd] = changePwdTmpl
adminTemplates[templateCredentials] = credentialsTmpl
adminTemplates[templateMaintenance] = maintenanceTmpl
adminTemplates[templateDefender] = defenderTmpl
adminTemplates[templateSetup] = setupTmpl
@@ -305,7 +309,7 @@ func getBasePageData(title, currentURL string, r *http.Request) basePage {
FolderTemplateURL: webTemplateFolder,
DefenderURL: webDefenderPath,
LogoutURL: webLogoutPath,
ChangeAdminPwdURL: webChangeAdminPwdPath,
CredentialsURL: webAdminCredentialsPath,
QuotaScanURL: webQuotaScanPath,
ConnectionsURL: webConnectionsPath,
StatusURL: webStatusPath,
@@ -366,13 +370,22 @@ func renderNotFoundPage(w http.ResponseWriter, r *http.Request, err error) {
renderMessagePage(w, r, page404Title, page404Body, http.StatusNotFound, err, "")
}
func renderChangePwdPage(w http.ResponseWriter, r *http.Request, error string) {
data := changePwdPage{
basePage: getBasePageData(pageChangePwdTitle, webChangeAdminPwdPath, r),
Error: error,
func renderCredentialsPage(w http.ResponseWriter, r *http.Request, pwdError, apiKeyError string) {
data := credentialsPage{
basePage: getBasePageData(pageCredentialsTitle, webAdminCredentialsPath, r),
ChangePwdURL: webChangeAdminPwdPath,
ManageAPIKeyURL: webChangeAdminAPIKeyAccessPath,
Error: pwdError,
APIKeyError: apiKeyError,
}
admin, err := dataprovider.AdminExists(data.LoggedAdmin.Username)
if err != nil {
renderInternalServerErrorPage(w, r, err)
return
}
data.AllowAPIKeyAuth = admin.Filters.AllowAPIKeyAuth
renderAdminTemplate(w, templateChangePwd, data)
renderAdminTemplate(w, templateCredentials, data)
}
func renderMaintenancePage(w http.ResponseWriter, r *http.Request, error string) {
@@ -658,6 +671,7 @@ func getFiltersFromUserPostFields(r *http.Request) sdk.UserFilters {
filters.Hooks.CheckPasswordDisabled = true
}
filters.DisableFsChecks = len(r.Form.Get("disable_fs_checks")) > 0
filters.AllowAPIKeyAuth = len(r.Form.Get("allow_api_key_auth")) > 0
return filters
}
@@ -820,6 +834,7 @@ func getAdminFromPostFields(r *http.Request) (dataprovider.Admin, error) {
admin.Email = r.Form.Get("email")
admin.Status = status
admin.Filters.AllowList = getSliceFromDelimitedValues(r.Form.Get("allowed_ip"), ",")
admin.Filters.AllowAPIKeyAuth = len(r.Form.Get("allow_api_key_auth")) > 0
admin.AdditionalInfo = r.Form.Get("additional_info")
admin.Description = r.Form.Get("description")
return admin, nil
@@ -1018,15 +1033,16 @@ func getUserFromPostFields(r *http.Request) (dataprovider.User, error) {
return user, err
}
func handleWebAdminChangePwd(w http.ResponseWriter, r *http.Request) {
renderChangePwdPage(w, r, "")
func handleWebAdminCredentials(w http.ResponseWriter, r *http.Request) {
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
renderCredentialsPage(w, r, "", "")
}
func handleWebAdminChangePwdPost(w http.ResponseWriter, r *http.Request) {
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
err := r.ParseForm()
if err != nil {
renderChangePwdPage(w, r, err.Error())
renderCredentialsPage(w, r, err.Error(), "")
return
}
if err := verifyCSRFToken(r.Form.Get(csrfFormToken)); err != nil {
@@ -1036,13 +1052,45 @@ func handleWebAdminChangePwdPost(w http.ResponseWriter, r *http.Request) {
err = doChangeAdminPassword(r, r.Form.Get("current_password"), r.Form.Get("new_password1"),
r.Form.Get("new_password2"))
if err != nil {
renderChangePwdPage(w, r, err.Error())
renderCredentialsPage(w, r, err.Error(), "")
return
}
handleWebLogout(w, r)
}
func handleWebAdminManageAPIKeyPost(w http.ResponseWriter, r *http.Request) {
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
err := r.ParseForm()
if err != nil {
renderCredentialsPage(w, r, err.Error(), "")
return
}
if err := verifyCSRFToken(r.Form.Get(csrfFormToken)); err != nil {
renderForbiddenPage(w, r, err.Error())
return
}
claims, err := getTokenClaims(r)
if err != nil || claims.Username == "" {
renderCredentialsPage(w, r, "", "Invalid token claims")
return
}
admin, err := dataprovider.AdminExists(claims.Username)
if err != nil {
renderCredentialsPage(w, r, "", err.Error())
return
}
admin.Filters.AllowAPIKeyAuth = len(r.Form.Get("allow_api_key_auth")) > 0
err = dataprovider.UpdateAdmin(&admin)
if err != nil {
renderCredentialsPage(w, r, "", err.Error())
return
}
renderMessagePage(w, r, "API key authentication updated", "", http.StatusOK, nil,
"Your API key access permission has been successfully updated")
}
func handleWebLogout(w http.ResponseWriter, r *http.Request) {
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
c := jwtTokenClaims{}
c.removeCookie(w, r, webBaseAdminPath)
@@ -1050,10 +1098,12 @@ func handleWebLogout(w http.ResponseWriter, r *http.Request) {
}
func handleWebMaintenance(w http.ResponseWriter, r *http.Request) {
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
renderMaintenancePage(w, r, "")
}
func handleWebRestore(w http.ResponseWriter, r *http.Request) {
r.Body = http.MaxBytesReader(w, r.Body, MaxRestoreSize)
err := r.ParseMultipartForm(MaxRestoreSize)
if err != nil {
renderMaintenancePage(w, r, err.Error())
@@ -1098,6 +1148,7 @@ func handleWebRestore(w http.ResponseWriter, r *http.Request) {
}
func handleGetWebAdmins(w http.ResponseWriter, r *http.Request) {
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
limit := defaultQueryLimit
if _, ok := r.URL.Query()["qlimit"]; ok {
var err error
@@ -1126,6 +1177,7 @@ func handleGetWebAdmins(w http.ResponseWriter, r *http.Request) {
}
func handleWebAdminSetupGet(w http.ResponseWriter, r *http.Request) {
r.Body = http.MaxBytesReader(w, r.Body, maxLoginBodySize)
if dataprovider.HasAdmin() {
http.Redirect(w, r, webLoginPath, http.StatusFound)
return
@@ -1134,11 +1186,13 @@ func handleWebAdminSetupGet(w http.ResponseWriter, r *http.Request) {
}
func 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)
}
func 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 {
@@ -1220,6 +1274,7 @@ func handleWebUpdateAdminPost(w http.ResponseWriter, r *http.Request) {
}
func handleWebDefenderPage(w http.ResponseWriter, r *http.Request) {
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
data := defenderHostsPage{
basePage: getBasePageData(pageDefenderTitle, webDefenderPath, r),
DefenderHostsURL: webDefenderHostsPath,
@@ -1229,6 +1284,7 @@ func handleWebDefenderPage(w http.ResponseWriter, r *http.Request) {
}
func handleGetWebUsers(w http.ResponseWriter, r *http.Request) {
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
limit := defaultQueryLimit
if _, ok := r.URL.Query()["qlimit"]; ok {
var err error
@@ -1257,6 +1313,7 @@ func handleGetWebUsers(w http.ResponseWriter, r *http.Request) {
}
func 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)
@@ -1319,6 +1376,7 @@ func handleWebTemplateFolderPost(w http.ResponseWriter, r *http.Request) {
}
func 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")
user, err := dataprovider.UserExists(username)
@@ -1376,6 +1434,7 @@ func handleWebTemplateUserPost(w http.ResponseWriter, r *http.Request) {
}
func handleWebAddUserGet(w http.ResponseWriter, r *http.Request) {
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
if r.URL.Query().Get("clone-from") != "" {
username := r.URL.Query().Get("clone-from")
user, err := dataprovider.UserExists(username)
@@ -1397,6 +1456,7 @@ func handleWebAddUserGet(w http.ResponseWriter, r *http.Request) {
}
func 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 {
@@ -1469,6 +1529,7 @@ func handleWebUpdateUserPost(w http.ResponseWriter, r *http.Request) {
}
func handleWebGetStatus(w http.ResponseWriter, r *http.Request) {
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
data := statusPage{
basePage: getBasePageData(pageStatusTitle, webStatusPath, r),
Status: getServicesStatus(),
@@ -1477,6 +1538,7 @@ func handleWebGetStatus(w http.ResponseWriter, r *http.Request) {
}
func 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),
@@ -1486,6 +1548,7 @@ func handleWebGetConnections(w http.ResponseWriter, r *http.Request) {
}
func handleWebAddFolderGet(w http.ResponseWriter, r *http.Request) {
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
renderFolderPage(w, r, vfs.BaseVirtualFolder{}, folderPageModeAdd, "")
}
@@ -1520,6 +1583,7 @@ func handleWebAddFolderPost(w http.ResponseWriter, r *http.Request) {
}
func 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 {
@@ -1594,6 +1658,7 @@ func getWebVirtualFolders(w http.ResponseWriter, r *http.Request, limit int) ([]
}
func handleWebGetFolders(w http.ResponseWriter, r *http.Request) {
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
limit := defaultQueryLimit
if _, ok := r.URL.Query()["qlimit"]; ok {
var err error