This commit is contained in:
parent
d93337852d
commit
dbcfc5c461
@ -7,6 +7,11 @@ import (
|
|||||||
|
|
||||||
// authorizeCapability 统一把 HTTP 请求上下文映射到能力授权器。
|
// authorizeCapability 统一把 HTTP 请求上下文映射到能力授权器。
|
||||||
func authorizeCapability(ctx *context.Context, capability access.Capability, targetRole access.Role, ownResource bool) error {
|
func authorizeCapability(ctx *context.Context, capability access.Capability, targetRole access.Role, ownResource bool) error {
|
||||||
|
return authorizeCapabilityWithNewRole(ctx, capability, targetRole, "", ownResource)
|
||||||
|
}
|
||||||
|
|
||||||
|
// authorizeCapabilityWithNewRole 在角色变更时同时把目标新角色传给统一授权器。
|
||||||
|
func authorizeCapabilityWithNewRole(ctx *context.Context, capability access.Capability, targetRole, newRole access.Role, ownResource bool) error {
|
||||||
if ctx == nil {
|
if ctx == nil {
|
||||||
return &access.Denial{Code: access.CodeAccessDenied}
|
return &access.Denial{Code: access.CodeAccessDenied}
|
||||||
}
|
}
|
||||||
@ -18,6 +23,7 @@ func authorizeCapability(ctx *context.Context, capability access.Capability, tar
|
|||||||
RecentAuth: ctx.RecentAuth,
|
RecentAuth: ctx.RecentAuth,
|
||||||
RecoveryCodeConfigured: ctx.RecoveryCodeConfigured,
|
RecoveryCodeConfigured: ctx.RecoveryCodeConfigured,
|
||||||
TargetRecoveryConfigured: ctx.TargetRecoveryConfigured,
|
TargetRecoveryConfigured: ctx.TargetRecoveryConfigured,
|
||||||
|
NewRole: newRole,
|
||||||
PluginInstanceAuthorized: ctx.PluginInstanceAuthorized,
|
PluginInstanceAuthorized: ctx.PluginInstanceAuthorized,
|
||||||
AuditScopeAuthorized: ctx.AuditScopeAuthorized,
|
AuditScopeAuthorized: ctx.AuditScopeAuthorized,
|
||||||
DeploymentChannel: ctx.DeploymentChannel,
|
DeploymentChannel: ctx.DeploymentChannel,
|
||||||
|
|||||||
@ -583,7 +583,7 @@ func recoveryBrowserContext(req *http.Request, w http.ResponseWriter, create boo
|
|||||||
value := hex.EncodeToString(raw[:])
|
value := hex.EncodeToString(raw[:])
|
||||||
secure := req.TLS != nil || !config.DevelopmentMode
|
secure := req.TLS != nil || !config.DevelopmentMode
|
||||||
http.SetCookie(w, &http.Cookie{
|
http.SetCookie(w, &http.Cookie{
|
||||||
Name: recoveryContextCookieName, Value: value, Path: "/api/security/recovery",
|
Name: recoveryContextCookieName, Value: value, Path: "/api/",
|
||||||
HttpOnly: true, Secure: secure, SameSite: http.SameSiteStrictMode, MaxAge: 15 * 60,
|
HttpOnly: true, Secure: secure, SameSite: http.SameSiteStrictMode, MaxAge: 15 * 60,
|
||||||
})
|
})
|
||||||
return value, true
|
return value, true
|
||||||
@ -595,7 +595,7 @@ func clearRecoveryBrowserContext(w http.ResponseWriter, req *http.Request) {
|
|||||||
}
|
}
|
||||||
secure := req != nil && (req.TLS != nil || !config.DevelopmentMode)
|
secure := req != nil && (req.TLS != nil || !config.DevelopmentMode)
|
||||||
http.SetCookie(w, &http.Cookie{
|
http.SetCookie(w, &http.Cookie{
|
||||||
Name: recoveryContextCookieName, Value: "", Path: "/api/security/recovery",
|
Name: recoveryContextCookieName, Value: "", Path: "/api/",
|
||||||
HttpOnly: true, Secure: secure, SameSite: http.SameSiteStrictMode, MaxAge: -1,
|
HttpOnly: true, Secure: secure, SameSite: http.SameSiteStrictMode, MaxAge: -1,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -73,7 +73,7 @@ func TestRecoveryBrowserContextCookieProperties(t *testing.T) {
|
|||||||
t.Fatal("recovery context was not created")
|
t.Fatal("recovery context was not created")
|
||||||
}
|
}
|
||||||
cookie := recorder.Result().Cookies()[0]
|
cookie := recorder.Result().Cookies()[0]
|
||||||
if cookie.Name != recoveryContextCookieName || !cookie.HttpOnly || cookie.SameSite != http.SameSiteStrictMode || cookie.Path != "/api/security/recovery" || cookie.Secure {
|
if cookie.Name != recoveryContextCookieName || !cookie.HttpOnly || cookie.SameSite != http.SameSiteStrictMode || cookie.Path != "/api/" || cookie.Secure {
|
||||||
t.Fatalf("unexpected recovery context cookie: %+v", cookie)
|
t.Fatalf("unexpected recovery context cookie: %+v", cookie)
|
||||||
}
|
}
|
||||||
request.AddCookie(cookie)
|
request.AddCookie(cookie)
|
||||||
|
|||||||
@ -147,15 +147,15 @@ func Info(ctx *context.Context, w http.ResponseWriter, req *http.Request) {
|
|||||||
domains := array.Difference(mailDomains.Accepted, []string{mailDomains.Primary})
|
domains := array.Difference(mailDomains.Accepted, []string{mailDomains.Primary})
|
||||||
domains = append([]string{mailDomains.Primary}, domains...)
|
domains = append([]string{mailDomains.Primary}, domains...)
|
||||||
result := map[string]any{
|
result := map[string]any{
|
||||||
"account": ctx.UserAccount,
|
"account": ctx.UserAccount,
|
||||||
"name": ctx.UserName,
|
"name": ctx.UserName,
|
||||||
"role": ctx.EffectiveRole(),
|
"role": ctx.EffectiveRole(),
|
||||||
"capabilities": access.Capabilities(ctx.EffectiveRole()),
|
"capabilities": access.Capabilities(ctx.EffectiveRole()),
|
||||||
"is_admin": ctx.IsAdmin,
|
"is_admin": ctx.IsAdmin,
|
||||||
"must_change_password": ctx.MustChangePassword,
|
"must_change_password": ctx.MustChangePassword,
|
||||||
"showSecuritySetupPrompt": ctx.SecuritySetupAcknowledged == 0,
|
"showSecuritySetupPrompt": ctx.SecuritySetupAcknowledged == 0,
|
||||||
"webAuthnEnabled": webAuthnRuntime() != nil,
|
"webAuthnEnabled": webAuthnRuntime() != nil,
|
||||||
"domains": domains,
|
"domains": domains,
|
||||||
}
|
}
|
||||||
if ctx.EffectiveRole() != access.RoleSuperAdmin {
|
if ctx.EffectiveRole() != access.RoleSuperAdmin {
|
||||||
result["recoveryCodesConfigured"] = ctx.RecoveryCodeConfigured
|
result["recoveryCodesConfigured"] = ctx.RecoveryCodeConfigured
|
||||||
@ -232,7 +232,7 @@ func EditUser(ctx *context.Context, w http.ResponseWriter, req *http.Request) {
|
|||||||
}
|
}
|
||||||
ctx.TargetRecoveryConfigured = configured
|
ctx.TargetRecoveryConfigured = configured
|
||||||
}
|
}
|
||||||
if err := authorizeCapability(ctx, access.CapabilityRoleManage, targetRole, false); err != nil {
|
if err := authorizeCapabilityWithNewRole(ctx, access.CapabilityRoleManage, targetRole, newRole, false); err != nil {
|
||||||
response.NewErrorResponse(response.NoAccessPrivileges, "角色变更需要近期认证和恢复凭据", "").FPrint(w)
|
response.NewErrorResponse(response.NoAccessPrivileges, "角色变更需要近期认证和恢复凭据", "").FPrint(w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@ -27,11 +27,14 @@ func getList(ctx *context.Context, tagInfo dto.SearchTag, keyword string, pop3Li
|
|||||||
|
|
||||||
totalSQL, totalParams := genSQL(ctx, true, tagInfo, keyword, pop3List, offset, limit)
|
totalSQL, totalParams := genSQL(ctx, true, tagInfo, keyword, pop3List, offset, limit)
|
||||||
|
|
||||||
if _, err := db.Instance.SQL(totalSQL, totalParams...).Get(&total); err != nil {
|
var countResult struct {
|
||||||
|
Total int64 `xorm:"total"`
|
||||||
|
}
|
||||||
|
if _, err := db.Instance.SQL(totalSQL, totalParams...).Get(&countResult); err != nil {
|
||||||
return nil, 0, fmt.Errorf("query email count: %w", err)
|
return nil, 0, fmt.Errorf("query email count: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return emailList, total, nil
|
return emailList, countResult.Total, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func genSQL(ctx *context.Context, count bool, tagInfo dto.SearchTag, keyword string, pop3List bool, offset, limit int) (string, []any) {
|
func genSQL(ctx *context.Context, count bool, tagInfo dto.SearchTag, keyword string, pop3List bool, offset, limit int) (string, []any) {
|
||||||
@ -39,7 +42,7 @@ func genSQL(ctx *context.Context, count bool, tagInfo dto.SearchTag, keyword str
|
|||||||
sql := "select "
|
sql := "select "
|
||||||
|
|
||||||
if count {
|
if count {
|
||||||
sql += `count(1) from email e left join user_email ue on e.id=ue.email_id where ue.user_id = ? `
|
sql += `count(1) AS total from email e left join user_email ue on e.id=ue.email_id where ue.user_id = ? `
|
||||||
} else if pop3List {
|
} else if pop3List {
|
||||||
sql += `e.id,e.size from email e left join user_email ue on e.id=ue.email_id where ue.user_id = ? `
|
sql += `e.id,e.size from email e left join user_email ue on e.id=ue.email_id where ue.user_id = ? `
|
||||||
} else {
|
} else {
|
||||||
@ -94,10 +97,14 @@ func genSQL(ctx *context.Context, count bool, tagInfo dto.SearchTag, keyword str
|
|||||||
sqlParams = append(sqlParams, "%"+keyword+"%", "%"+keyword+"%")
|
sqlParams = append(sqlParams, "%"+keyword+"%", "%"+keyword+"%")
|
||||||
}
|
}
|
||||||
|
|
||||||
if limit == 0 {
|
if !count && limit == 0 {
|
||||||
limit = 10
|
limit = 10
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if count {
|
||||||
|
return sql, sqlParams
|
||||||
|
}
|
||||||
|
|
||||||
sql += " order by e.id desc"
|
sql += " order by e.id desc"
|
||||||
|
|
||||||
if limit < 10000 {
|
if limit < 10000 {
|
||||||
|
|||||||
@ -326,6 +326,13 @@ func (s *Service) CompleteWithContext(ticketID, newPassword, browserContext stri
|
|||||||
return ErrRecoveryDenied
|
return ErrRecoveryDenied
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if item.Method == "webauthn" && item.CredentialID > 0 {
|
||||||
|
var credential models.WebAuthnCredential
|
||||||
|
found, lookupErr := s.engine.Where("id = ? AND user_id = ? AND revoked_at IS NULL", item.CredentialID, item.UserID).Get(&credential)
|
||||||
|
if lookupErr != nil || !found {
|
||||||
|
return ErrRecoveryDenied
|
||||||
|
}
|
||||||
|
}
|
||||||
version := user.SessionVersion + 1
|
version := user.SessionVersion + 1
|
||||||
if version <= 0 {
|
if version <= 0 {
|
||||||
version = 1
|
version = 1
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user