allow to set a default expiration for newly created users
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
This commit is contained in:
parent
33bfd61a0c
commit
048591553a
11 changed files with 177 additions and 36 deletions
2
go.mod
2
go.mod
|
@ -52,7 +52,7 @@ require (
|
|||
github.com/rs/cors v1.8.3-0.20220619195839-da52b0701de5
|
||||
github.com/rs/xid v1.4.0
|
||||
github.com/rs/zerolog v1.28.0
|
||||
github.com/sftpgo/sdk v0.1.2
|
||||
github.com/sftpgo/sdk v0.1.3-0.20221105153737-bae9afc6b356
|
||||
github.com/shirou/gopsutil/v3 v3.22.10
|
||||
github.com/spf13/afero v1.9.2
|
||||
github.com/spf13/cobra v1.6.1
|
||||
|
|
4
go.sum
4
go.sum
|
@ -1457,8 +1457,8 @@ github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg
|
|||
github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo=
|
||||
github.com/seccomp/libseccomp-golang v0.9.2-0.20210429002308-3879420cc921/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg=
|
||||
github.com/secsy/goftp v0.0.0-20200609142545-aa2de14babf4 h1:PT+ElG/UUFMfqy5HrxJxNzj3QBOf7dZwupeVC+mG1Lo=
|
||||
github.com/sftpgo/sdk v0.1.2 h1:j4V63RuVcYfJAOWV0zRUofa1PlQvKU2ujly0lB7quVA=
|
||||
github.com/sftpgo/sdk v0.1.2/go.mod h1:PTp1TfXa+95wHw9yuZu7BA3vmzLqbRkz3gBmMNnwFQg=
|
||||
github.com/sftpgo/sdk v0.1.3-0.20221105153737-bae9afc6b356 h1:VwFpy5W/pP0X+082xKU2yu4OAwuk8Qqa8j2ofImJ1bM=
|
||||
github.com/sftpgo/sdk v0.1.3-0.20221105153737-bae9afc6b356/go.mod h1:Giy5vj7Gmju0nGlmBNd28DwPo0G0o1nr9XkE+vu3i+o=
|
||||
github.com/shirou/gopsutil/v3 v3.22.10 h1:4KMHdfBRYXGF9skjDWiL4RA2N+E8dRdodU/bOZpPoVg=
|
||||
github.com/shirou/gopsutil/v3 v3.22.10/go.mod h1:QNza6r4YQoydyCfo6rH0blGfKahgibh4dQmV5xdFkQk=
|
||||
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
|
||||
|
|
|
@ -123,6 +123,9 @@ type AdminPreferences struct {
|
|||
//
|
||||
// The settings can be combined
|
||||
HideUserPageSections int `json:"hide_user_page_sections,omitempty"`
|
||||
// Defines the default expiration for newly created users as number of days.
|
||||
// 0 means no expiration
|
||||
DefaultUsersExpiration int `json:"default_users_expiration,omitempty"`
|
||||
}
|
||||
|
||||
// HideGroups returns true if the groups section should be hidden
|
||||
|
@ -365,7 +368,7 @@ func (a *Admin) validate() error {
|
|||
return err
|
||||
}
|
||||
if config.NamingRules&1 == 0 && !usernameRegex.MatchString(a.Username) {
|
||||
return util.NewValidationError(fmt.Sprintf("username %#v is not valid, the following characters are allowed: a-zA-Z0-9-_.~", a.Username))
|
||||
return util.NewValidationError(fmt.Sprintf("username %q is not valid, the following characters are allowed: a-zA-Z0-9-_.~", a.Username))
|
||||
}
|
||||
if err := a.hashPassword(); err != nil {
|
||||
return err
|
||||
|
@ -574,7 +577,8 @@ func (a *Admin) getACopy() Admin {
|
|||
})
|
||||
}
|
||||
filters.Preferences = AdminPreferences{
|
||||
HideUserPageSections: a.Filters.Preferences.HideUserPageSections,
|
||||
HideUserPageSections: a.Filters.Preferences.HideUserPageSections,
|
||||
DefaultUsersExpiration: a.Filters.Preferences.DefaultUsersExpiration,
|
||||
}
|
||||
groups := make([]AdminGroupMapping, 0, len(a.Groups))
|
||||
for _, g := range a.Groups {
|
||||
|
|
|
@ -19,6 +19,7 @@ import (
|
|||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/go-chi/render"
|
||||
"github.com/sftpgo/sdk"
|
||||
|
@ -75,7 +76,15 @@ func addUser(w http.ResponseWriter, r *http.Request) {
|
|||
sendAPIResponse(w, r, err, "Invalid token claims", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
admin, err := dataprovider.AdminExists(claims.Username)
|
||||
if err != nil {
|
||||
sendAPIResponse(w, r, err, "", getRespStatus(err))
|
||||
return
|
||||
}
|
||||
var user dataprovider.User
|
||||
if admin.Filters.Preferences.DefaultUsersExpiration > 0 {
|
||||
user.ExpirationDate = util.GetTimeAsMsSinceEpoch(time.Now().Add(24 * time.Hour * time.Duration(admin.Filters.Preferences.DefaultUsersExpiration)))
|
||||
}
|
||||
err = render.DecodeJSON(r.Body, &user)
|
||||
if err != nil {
|
||||
sendAPIResponse(w, r, err, "", http.StatusBadRequest)
|
||||
|
|
|
@ -2722,6 +2722,7 @@ func TestBasicAdminHandling(t *testing.T) {
|
|||
|
||||
admin.Username = altAdminUsername
|
||||
admin.Filters.Preferences.HideUserPageSections = 1 + 4 + 8
|
||||
admin.Filters.Preferences.DefaultUsersExpiration = 30
|
||||
admin, _, err = httpdtest.AddAdmin(admin, http.StatusCreated)
|
||||
assert.NoError(t, err)
|
||||
|
||||
|
@ -2972,6 +2973,74 @@ func TestAdminPasswordHashing(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestDefaultUsersExpiration(t *testing.T) {
|
||||
a := getTestAdmin()
|
||||
a.Username = altAdminUsername
|
||||
a.Password = altAdminPassword
|
||||
a.Filters.Preferences.DefaultUsersExpiration = 30
|
||||
admin, _, err := httpdtest.AddAdmin(a, http.StatusCreated)
|
||||
assert.NoError(t, err)
|
||||
|
||||
token, _, err := httpdtest.GetToken(altAdminUsername, altAdminPassword)
|
||||
assert.NoError(t, err)
|
||||
httpdtest.SetJWTToken(token)
|
||||
|
||||
_, _, err = httpdtest.AddUser(getTestUser(), http.StatusCreated)
|
||||
assert.Error(t, err)
|
||||
|
||||
user, _, err := httpdtest.GetUserByUsername(defaultUsername, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
assert.Greater(t, user.ExpirationDate, int64(0))
|
||||
|
||||
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
|
||||
u := getTestUser()
|
||||
u.ExpirationDate = util.GetTimeAsMsSinceEpoch(time.Now().Add(1 * time.Minute))
|
||||
|
||||
_, _, err = httpdtest.AddUser(u, http.StatusCreated)
|
||||
assert.NoError(t, err)
|
||||
|
||||
user, _, err = httpdtest.GetUserByUsername(defaultUsername, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, u.ExpirationDate, user.ExpirationDate)
|
||||
|
||||
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
|
||||
httpdtest.SetJWTToken("")
|
||||
_, _, err = httpdtest.AddUser(getTestUser(), http.StatusCreated)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// render the user template page
|
||||
webToken, err := getJWTWebTokenFromTestServer(altAdminUsername, altAdminPassword)
|
||||
assert.NoError(t, err)
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, webTemplateUser, nil)
|
||||
assert.NoError(t, err)
|
||||
setJWTCookieForReq(req, webToken)
|
||||
rr := executeRequest(req)
|
||||
checkResponseCode(t, http.StatusOK, rr)
|
||||
|
||||
req, err = http.NewRequest(http.MethodGet, webTemplateUser+fmt.Sprintf("?from=%s", user.Username), nil)
|
||||
assert.NoError(t, err)
|
||||
setJWTCookieForReq(req, webToken)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusOK, rr)
|
||||
|
||||
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = httpdtest.RemoveAdmin(admin, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
|
||||
httpdtest.SetJWTToken(token)
|
||||
_, _, err = httpdtest.AddUser(u, http.StatusNotFound)
|
||||
assert.NoError(t, err)
|
||||
|
||||
httpdtest.SetJWTToken("")
|
||||
}
|
||||
|
||||
func TestAdminInvalidCredentials(t *testing.T) {
|
||||
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%v%v", httpBaseURL, tokenPath), nil)
|
||||
assert.NoError(t, err)
|
||||
|
@ -6139,6 +6208,11 @@ func TestProviderErrors(t *testing.T) {
|
|||
setJWTCookieForReq(req, testServerToken)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusInternalServerError, rr)
|
||||
req, err = http.NewRequest(http.MethodGet, webTemplateUser, nil)
|
||||
assert.NoError(t, err)
|
||||
setJWTCookieForReq(req, testServerToken)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusInternalServerError, rr)
|
||||
req, err = http.NewRequest(http.MethodGet, webGroupsPath+"?qlimit=a", nil)
|
||||
assert.NoError(t, err)
|
||||
setJWTCookieForReq(req, testServerToken)
|
||||
|
@ -16420,6 +16494,7 @@ func TestWebAdminBasicMock(t *testing.T) {
|
|||
form.Add("user_page_hidden_sections", "5")
|
||||
form.Add("user_page_hidden_sections", "6")
|
||||
form.Add("user_page_hidden_sections", "7")
|
||||
form.Set("default_users_expiration", "10")
|
||||
req, _ := http.NewRequest(http.MethodPost, webAdminPath, bytes.NewBuffer([]byte(form.Encode())))
|
||||
req.RemoteAddr = defaultRemoteAddr
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
|
@ -16438,6 +16513,16 @@ func TestWebAdminBasicMock(t *testing.T) {
|
|||
checkResponseCode(t, http.StatusOK, rr)
|
||||
|
||||
form.Set("status", "1")
|
||||
form.Set("default_users_expiration", "a")
|
||||
req, _ = http.NewRequest(http.MethodPost, webAdminPath, bytes.NewBuffer([]byte(form.Encode())))
|
||||
req.RemoteAddr = defaultRemoteAddr
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
setJWTCookieForReq(req, token)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusOK, rr)
|
||||
assert.Contains(t, rr.Body.String(), "invalid default users expiration")
|
||||
|
||||
form.Set("default_users_expiration", "10")
|
||||
req, _ = http.NewRequest(http.MethodPost, webAdminPath, bytes.NewBuffer([]byte(form.Encode())))
|
||||
req.RemoteAddr = defaultRemoteAddr
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
|
@ -16488,6 +16573,7 @@ func TestWebAdminBasicMock(t *testing.T) {
|
|||
secretPayload := admin.Filters.TOTPConfig.Secret.GetPayload()
|
||||
assert.NotEmpty(t, secretPayload)
|
||||
assert.Equal(t, 1+2+4+8+16+32+64, admin.Filters.Preferences.HideUserPageSections)
|
||||
assert.Equal(t, 10, admin.Filters.Preferences.DefaultUsersExpiration)
|
||||
|
||||
adminTOTPConfig = dataprovider.AdminTOTPConfig{
|
||||
Enabled: true,
|
||||
|
@ -16610,6 +16696,12 @@ func TestWebAdminBasicMock(t *testing.T) {
|
|||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusNotFound, rr)
|
||||
|
||||
req, _ = http.NewRequest(http.MethodGet, webUserPath, nil)
|
||||
req.RemoteAddr = defaultRemoteAddr
|
||||
setJWTCookieForReq(req, altToken)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusOK, rr)
|
||||
|
||||
req, _ = http.NewRequest(http.MethodDelete, path.Join(webAdminPath, altAdminUsername), nil)
|
||||
req.RemoteAddr = defaultRemoteAddr
|
||||
setJWTCookieForReq(req, token)
|
||||
|
@ -16617,6 +16709,13 @@ func TestWebAdminBasicMock(t *testing.T) {
|
|||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusOK, rr)
|
||||
|
||||
req, _ = http.NewRequest(http.MethodGet, webUserPath, nil)
|
||||
req.RemoteAddr = defaultRemoteAddr
|
||||
setJWTCookieForReq(req, altToken)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusInternalServerError, rr)
|
||||
assert.Contains(t, rr.Body.String(), "unable to get the admin")
|
||||
|
||||
_, err = httpdtest.RemoveAdmin(admin, http.StatusNotFound)
|
||||
assert.NoError(t, err)
|
||||
|
||||
|
|
|
@ -758,6 +758,7 @@ func TestUpdateWebAdminInvalidClaims(t *testing.T) {
|
|||
form := make(url.Values)
|
||||
form.Set(csrfFormToken, createCSRFToken(""))
|
||||
form.Set("status", "1")
|
||||
form.Set("default_users_expiration", "30")
|
||||
req, _ := http.NewRequest(http.MethodPost, path.Join(webAdminPath, "admin"), bytes.NewBuffer([]byte(form.Encode())))
|
||||
rctx := chi.NewRouteContext()
|
||||
rctx.URLParams.Add("username", "admin")
|
||||
|
|
|
@ -792,7 +792,7 @@ func (s *httpdServer) renderAddUpdateAdminPage(w http.ResponseWriter, r *http.Re
|
|||
}
|
||||
|
||||
func (s *httpdServer) renderUserPage(w http.ResponseWriter, r *http.Request, user *dataprovider.User,
|
||||
mode userPageMode, error string,
|
||||
mode userPageMode, error string, admin *dataprovider.Admin,
|
||||
) {
|
||||
folders, err := s.getWebVirtualFolders(w, r, defaultQueryLimit, true)
|
||||
if err != nil {
|
||||
|
@ -825,12 +825,7 @@ func (s *httpdServer) renderUserPage(w http.ResponseWriter, r *http.Request, use
|
|||
}
|
||||
user.FsConfig.RedactedSecret = redactedSecret
|
||||
basePage := s.getBasePageData(title, currentURL, r)
|
||||
if (mode == userPageModeAdd || mode == userPageModeTemplate) && len(user.Groups) == 0 {
|
||||
admin, err := dataprovider.AdminExists(basePage.LoggedAdmin.Username)
|
||||
if err != nil {
|
||||
s.renderInternalServerErrorPage(w, r, err)
|
||||
return
|
||||
}
|
||||
if (mode == userPageModeAdd || mode == userPageModeTemplate) && len(user.Groups) == 0 && admin != nil {
|
||||
for _, group := range admin.Groups {
|
||||
user.Groups = append(user.Groups, sdk.GroupMapping{
|
||||
Name: group.Name,
|
||||
|
@ -1587,6 +1582,14 @@ func getAdminFromPostFields(r *http.Request) (dataprovider.Admin, error) {
|
|||
admin.AdditionalInfo = r.Form.Get("additional_info")
|
||||
admin.Description = r.Form.Get("description")
|
||||
admin.Filters.Preferences.HideUserPageSections = getAdminHiddenUserPageSections(r)
|
||||
admin.Filters.Preferences.DefaultUsersExpiration = 0
|
||||
if val := r.Form.Get("default_users_expiration"); val != "" {
|
||||
defaultUsersExpiration, err := strconv.ParseInt(r.Form.Get("default_users_expiration"), 10, 64)
|
||||
if err != nil {
|
||||
return admin, fmt.Errorf("invalid default users expiration: %w", err)
|
||||
}
|
||||
admin.Filters.Preferences.DefaultUsersExpiration = int(defaultUsersExpiration)
|
||||
}
|
||||
for k := range r.Form {
|
||||
if strings.HasPrefix(k, "group") {
|
||||
groupName := strings.TrimSpace(r.Form.Get(k))
|
||||
|
@ -2646,6 +2649,12 @@ func (s *httpdServer) handleWebTemplateFolderPost(w http.ResponseWriter, r *http
|
|||
|
||||
func (s *httpdServer) handleWebTemplateUserGet(w http.ResponseWriter, r *http.Request) {
|
||||
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
|
||||
tokenAdmin := getAdminFromToken(r)
|
||||
admin, err := dataprovider.AdminExists(tokenAdmin.Username)
|
||||
if err != nil {
|
||||
s.renderInternalServerErrorPage(w, r, fmt.Errorf("unable to get the admin %q: %w", tokenAdmin.Username, err))
|
||||
return
|
||||
}
|
||||
if r.URL.Query().Get("from") != "" {
|
||||
username := r.URL.Query().Get("from")
|
||||
user, err := dataprovider.UserExists(username)
|
||||
|
@ -2654,7 +2663,10 @@ func (s *httpdServer) handleWebTemplateUserGet(w http.ResponseWriter, r *http.Re
|
|||
user.PublicKeys = nil
|
||||
user.Email = ""
|
||||
user.Description = ""
|
||||
s.renderUserPage(w, r, &user, userPageModeTemplate, "")
|
||||
if user.ExpirationDate == 0 && admin.Filters.Preferences.DefaultUsersExpiration > 0 {
|
||||
user.ExpirationDate = util.GetTimeAsMsSinceEpoch(time.Now().Add(24 * time.Hour * time.Duration(admin.Filters.Preferences.DefaultUsersExpiration)))
|
||||
}
|
||||
s.renderUserPage(w, r, &user, userPageModeTemplate, "", &admin)
|
||||
} else if _, ok := err.(*util.RecordNotFoundError); ok {
|
||||
s.renderNotFoundPage(w, r, err)
|
||||
} else {
|
||||
|
@ -2667,7 +2679,10 @@ func (s *httpdServer) handleWebTemplateUserGet(w http.ResponseWriter, r *http.Re
|
|||
"/": {dataprovider.PermAny},
|
||||
},
|
||||
}}
|
||||
s.renderUserPage(w, r, &user, userPageModeTemplate, "")
|
||||
if admin.Filters.Preferences.DefaultUsersExpiration > 0 {
|
||||
user.ExpirationDate = util.GetTimeAsMsSinceEpoch(time.Now().Add(24 * time.Hour * time.Duration(admin.Filters.Preferences.DefaultUsersExpiration)))
|
||||
}
|
||||
s.renderUserPage(w, r, &user, userPageModeTemplate, "", &admin)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2729,13 +2744,22 @@ func (s *httpdServer) handleWebTemplateUserPost(w http.ResponseWriter, r *http.R
|
|||
|
||||
func (s *httpdServer) handleWebAddUserGet(w http.ResponseWriter, r *http.Request) {
|
||||
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
|
||||
tokenAdmin := getAdminFromToken(r)
|
||||
admin, err := dataprovider.AdminExists(tokenAdmin.Username)
|
||||
if err != nil {
|
||||
s.renderInternalServerErrorPage(w, r, fmt.Errorf("unable to get the admin %q: %w", tokenAdmin.Username, err))
|
||||
return
|
||||
}
|
||||
user := dataprovider.User{BaseUser: sdk.BaseUser{
|
||||
Status: 1,
|
||||
Permissions: map[string][]string{
|
||||
"/": {dataprovider.PermAny},
|
||||
}},
|
||||
}
|
||||
s.renderUserPage(w, r, &user, userPageModeAdd, "")
|
||||
if admin.Filters.Preferences.DefaultUsersExpiration > 0 {
|
||||
user.ExpirationDate = util.GetTimeAsMsSinceEpoch(time.Now().Add(24 * time.Hour * time.Duration(admin.Filters.Preferences.DefaultUsersExpiration)))
|
||||
}
|
||||
s.renderUserPage(w, r, &user, userPageModeAdd, "", &admin)
|
||||
}
|
||||
|
||||
func (s *httpdServer) handleWebUpdateUserGet(w http.ResponseWriter, r *http.Request) {
|
||||
|
@ -2743,7 +2767,7 @@ func (s *httpdServer) handleWebUpdateUserGet(w http.ResponseWriter, r *http.Requ
|
|||
username := getURLParam(r, "username")
|
||||
user, err := dataprovider.UserExists(username)
|
||||
if err == nil {
|
||||
s.renderUserPage(w, r, &user, userPageModeUpdate, "")
|
||||
s.renderUserPage(w, r, &user, userPageModeUpdate, "", nil)
|
||||
} else if _, ok := err.(*util.RecordNotFoundError); ok {
|
||||
s.renderNotFoundPage(w, r, err)
|
||||
} else {
|
||||
|
@ -2760,7 +2784,7 @@ func (s *httpdServer) handleWebAddUserPost(w http.ResponseWriter, r *http.Reques
|
|||
}
|
||||
user, err := getUserFromPostFields(r)
|
||||
if err != nil {
|
||||
s.renderUserPage(w, r, &user, userPageModeAdd, err.Error())
|
||||
s.renderUserPage(w, r, &user, userPageModeAdd, err.Error(), nil)
|
||||
return
|
||||
}
|
||||
ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr)
|
||||
|
@ -2775,7 +2799,7 @@ func (s *httpdServer) handleWebAddUserPost(w http.ResponseWriter, r *http.Reques
|
|||
})
|
||||
err = dataprovider.AddUser(&user, claims.Username, ipAddr)
|
||||
if err != nil {
|
||||
s.renderUserPage(w, r, &user, userPageModeAdd, err.Error())
|
||||
s.renderUserPage(w, r, &user, userPageModeAdd, err.Error(), nil)
|
||||
return
|
||||
}
|
||||
http.Redirect(w, r, webUsersPath, http.StatusSeeOther)
|
||||
|
@ -2799,7 +2823,7 @@ func (s *httpdServer) handleWebUpdateUserPost(w http.ResponseWriter, r *http.Req
|
|||
}
|
||||
updatedUser, err := getUserFromPostFields(r)
|
||||
if err != nil {
|
||||
s.renderUserPage(w, r, &user, userPageModeUpdate, err.Error())
|
||||
s.renderUserPage(w, r, &user, userPageModeUpdate, err.Error(), nil)
|
||||
return
|
||||
}
|
||||
ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr)
|
||||
|
@ -2828,7 +2852,7 @@ func (s *httpdServer) handleWebUpdateUserPost(w http.ResponseWriter, r *http.Req
|
|||
|
||||
err = dataprovider.UpdateUser(&updatedUser, claims.Username, ipAddr)
|
||||
if err != nil {
|
||||
s.renderUserPage(w, r, &updatedUser, userPageModeUpdate, err.Error())
|
||||
s.renderUserPage(w, r, &updatedUser, userPageModeUpdate, err.Error(), nil)
|
||||
return
|
||||
}
|
||||
if r.Form.Get("disconnect") != "" {
|
||||
|
|
|
@ -1645,6 +1645,9 @@ func compareAdminFilters(expected, actual dataprovider.AdminFilters) error {
|
|||
if expected.Preferences.HideUserPageSections != actual.Preferences.HideUserPageSections {
|
||||
return errors.New("hide user page sections mismatch")
|
||||
}
|
||||
if expected.Preferences.DefaultUsersExpiration != actual.Preferences.DefaultUsersExpiration {
|
||||
return errors.New("default users expiration mismatch")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -2545,22 +2545,6 @@ paths:
|
|||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Admin'
|
||||
examples:
|
||||
example-1:
|
||||
value:
|
||||
id: 1
|
||||
status: 0
|
||||
username: string
|
||||
description: string
|
||||
password: pa$$word
|
||||
email: user@example.com
|
||||
permissions:
|
||||
- '*'
|
||||
filters:
|
||||
allow_list:
|
||||
- 192.0.2.0/24
|
||||
- '2001:db8::/32'
|
||||
additional_info: string
|
||||
responses:
|
||||
'201':
|
||||
description: successful operation
|
||||
|
@ -5233,6 +5217,9 @@ components:
|
|||
hide_user_page_sections:
|
||||
type: integer
|
||||
description: 'Allow to hide some sections from the user page. These are not security settings and are not enforced server side in any way. They are only intended to simplify the user page in the WebAdmin UI. 1 means hide groups section, 2 means hide filesystem section, "users_base_dir" must be set in the config file otherwise this setting is ignored, 4 means hide virtual folders section, 8 means hide profile section, 16 means hide ACLs section, 32 means hide disk and bandwidth quota limits section, 64 means hide advanced settings section. The settings can be combined'
|
||||
default_users_expiration:
|
||||
type: integer
|
||||
description: 'Defines the default expiration for newly created users as number of days. 0 means no expiration'
|
||||
AdminFilters:
|
||||
type: object
|
||||
properties:
|
||||
|
|
|
@ -182,6 +182,16 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="idDefaultUsersExpiration" class="col-sm-2 col-form-label">Default users expiration</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="number" class="form-control" id="idDefaultUsersExpiration" name="default_users_expiration"
|
||||
value="{{.Admin.Filters.Preferences.DefaultUsersExpiration}}" min="0" aria-describedby="defaultUsersExpirationHelpBlock">
|
||||
<small id="defaultUsersExpirationHelpBlock" class="form-text text-muted">
|
||||
Default expiration for newly created users as number of days
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -1057,6 +1057,10 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||
showClear: false,
|
||||
showClose: true,
|
||||
showToday: false
|
||||
},
|
||||
widgetPositioning: {
|
||||
horizontal: 'auto',
|
||||
vertical: 'bottom'
|
||||
}
|
||||
});
|
||||
|
||||
|
|
Loading…
Reference in a new issue