Auth: Ensure clipboard is cleared on logout and privilege change #3512

Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
Michael Mayer 2023-07-14 09:14:57 +02:00
parent 781bb0b04f
commit 0e93bd8aa2
8 changed files with 59 additions and 15 deletions

View file

@ -100,7 +100,7 @@ export default class Session {
}
useSessionStorage() {
this.deleteId();
this.reset();
this.storage.setItem(this.storage_key, "true");
this.storage = window.sessionStorage;
}
@ -112,7 +112,7 @@ export default class Session {
applyId(id) {
if (!id) {
this.deleteId();
this.reset();
return false;
}
@ -146,8 +146,6 @@ export default class Session {
this.storage.removeItem("session_id");
delete Api.defaults.headers.common[SessionHeader];
this.deleteAll();
}
setResp(resp) {
@ -272,9 +270,17 @@ export default class Session {
this.storage.removeItem("user");
}
deleteAll() {
deleteClipboard() {
this.storage.removeItem("clipboard");
this.storage.removeItem("photo_clipboard");
this.storage.removeItem("album_clipboard");
}
reset() {
this.deleteId();
this.deleteData();
this.deleteUser();
this.deleteClipboard();
}
sendClientInfo() {
@ -303,16 +309,20 @@ export default class Session {
}
login(username, password, token) {
this.deleteId();
this.reset();
return Api.post("session", { username, password, token }).then((resp) => {
const reload = this.config.getLanguage() !== resp.data?.config?.settings?.ui?.language;
this.setResp(resp);
this.sendClientInfo();
this.onLogin();
return Promise.resolve(reload);
});
}
onLogin() {
this.sendClientInfo();
}
refresh() {
// Refresh session information.
if (this.config.isPublic()) {
@ -330,7 +340,7 @@ export default class Session {
return Promise.resolve();
})
.catch(() => {
this.deleteId();
this.reset();
if (!this.isLogin()) {
window.location.reload();
}
@ -354,7 +364,7 @@ export default class Session {
}
onLogout(noRedirect) {
this.deleteId();
this.reset();
if (noRedirect !== true && !this.isLogin()) {
window.location = this.config.baseUri + "/";
}

View file

@ -25,7 +25,7 @@ describe("common/session", () => {
assert.equal(session.session_id, "999900000000000000000000000000000000000000000000");
const result = session.getId();
assert.equal(result, "999900000000000000000000000000000000000000000000");
session.deleteId();
session.reset();
assert.equal(session.session_id, null);
});
@ -54,7 +54,7 @@ describe("common/session", () => {
assert.equal(session.user.DisplayName, "Max Example");
assert.equal(session.user.SuperAdmin, true);
assert.equal(session.user.Role, "admin");
session.deleteAll();
session.reset();
assert.equal(session.user.DisplayName, "");
assert.equal(session.user.SuperAdmin, false);
assert.equal(session.user.Role, "");

View file

@ -3,10 +3,12 @@ package api
import (
"net/http"
"github.com/dustin/go-humanize/english"
"github.com/gin-gonic/gin"
"github.com/photoprism/photoprism/internal/acl"
"github.com/photoprism/photoprism/internal/entity"
"github.com/photoprism/photoprism/internal/event"
"github.com/photoprism/photoprism/internal/get"
"github.com/photoprism/photoprism/internal/i18n"
"github.com/photoprism/photoprism/pkg/clean"
@ -31,8 +33,10 @@ func UpdateUser(router *gin.RouterGroup) {
return
}
// UserUID.
uid := clean.UID(c.Param("uid"))
// Find user.
m := entity.FindUserByUID(uid)
if m == nil {
@ -66,14 +70,25 @@ func UpdateUser(router *gin.RouterGroup) {
// Save model with values from form.
if err = m.SaveForm(f, isPrivileged); err != nil {
log.Error(err)
event.AuditErr([]string{ClientIP(c), "session %s", "users", m.UserName, "update", err.Error()}, s.RefID)
AbortSaveFailed(c)
return
}
// Clear the session cache, as it contains user information.
// Log event.
event.AuditInfo([]string{ClientIP(c), "session %s", "users", m.UserName, "updated"}, s.RefID)
// Delete sessions after privilege level change.
if s.User().UserUID != m.UID() && isPrivileged {
// see https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html#renew-the-session-id-after-any-privilege-level-change
event.AuditInfo([]string{ClientIP(c), "session %s", "users", m.UserName, "invalidated %s"}, s.RefID,
english.Plural(m.DeleteSessions(nil), "session", "sessions"))
}
// Clear the session cache.
s.ClearCache()
// Find and return the updated user record.
m = entity.FindUserByUID(uid)
if m == nil {

View file

@ -1,16 +1,19 @@
package entity
import (
"github.com/photoprism/photoprism/pkg/clean"
"github.com/urfave/cli"
"github.com/photoprism/photoprism/internal/form"
"github.com/photoprism/photoprism/pkg/clean"
)
// SetValuesFromCli updates the entity values from a CLI context and validates them.
func (m *User) SetValuesFromCli(ctx *cli.Context) error {
frm := form.NewUserFromCli(ctx)
// see https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html#renew-the-session-id-after-any-privilege-level-change
privilegeLevelChange := false
// Email address.
if ctx.IsSet("email") {
m.UserEmail = frm.Email()
@ -24,39 +27,55 @@ func (m *User) SetValuesFromCli(ctx *cli.Context) error {
// User role.
if ctx.IsSet("role") {
m.SetRole(frm.Role())
privilegeLevelChange = true
}
// Super-admin status.
if ctx.IsSet("superadmin") {
m.SuperAdmin = frm.SuperAdmin
privilegeLevelChange = true
}
// Disable login (Web UI)?
if ctx.IsSet("no-login") {
m.CanLogin = frm.CanLogin
privilegeLevelChange = true
}
// Allow the use of WebDAV?
if ctx.IsSet("webdav") {
m.WebDAV = frm.WebDAV
privilegeLevelChange = true
}
// Set custom attributes?
if ctx.IsSet("attr") {
m.UserAttr = frm.Attr()
privilegeLevelChange = true
}
// Originals base folder.
if ctx.IsSet("base-path") {
m.SetBasePath(frm.BasePath)
privilegeLevelChange = true
}
// Sub-folder for uploads.
if ctx.IsSet("upload-path") {
m.SetUploadPath(frm.UploadPath)
privilegeLevelChange = true
}
return m.Validate()
// Validate properties.
if err := m.Validate(); err != nil {
// Invalid.
return err
} else if privilegeLevelChange {
// Delete sessions after privilege level change.
m.DeleteSessions(nil)
}
return nil
}
// RestoreFromCli restored the account from a CLI context.