From 6993a9f58b06dba1eb515fb1adf05819344dd132 Mon Sep 17 00:00:00 2001 From: Andreas Schneider Date: Wed, 28 Oct 2020 19:59:29 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20Implemented=20login=20mana?= =?UTF-8?q?gement?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- templates/base.html | 184 ++++++++++++------------- templates/my-shares.html | 67 +++++++++ templates/share-add-login.html | 15 ++ templates/shares.html | 97 ++++++------- webadmin.go | 241 ++++++++++++++++++++++++++------- 5 files changed, 421 insertions(+), 183 deletions(-) create mode 100644 templates/my-shares.html create mode 100644 templates/share-add-login.html diff --git a/templates/base.html b/templates/base.html index 3a66953..ab233a0 100644 --- a/templates/base.html +++ b/templates/base.html @@ -1,92 +1,94 @@ - - - - - ShareDAV - - {{ block "page-styles" . }}{{ end }} - - -
- {{ block "page-content" . }}{{ end }} -
- -
- + + + + + ShareDAV + + {{ block "page-styles" . }}{{ end }} + + +
+ {{ block "page-content" . }}{{ end }} +
+ +
+ \ No newline at end of file diff --git a/templates/my-shares.html b/templates/my-shares.html new file mode 100644 index 0000000..9a6d047 --- /dev/null +++ b/templates/my-shares.html @@ -0,0 +1,67 @@ +{{ define "page-styles"}} + +{{ end }} + +{{ define "page-content" }} +
+ {{ range $share := .ShareInfos }} + + {{ end }} + Create Share +
+{{ end }} \ No newline at end of file diff --git a/templates/share-add-login.html b/templates/share-add-login.html new file mode 100644 index 0000000..21b8427 --- /dev/null +++ b/templates/share-add-login.html @@ -0,0 +1,15 @@ +{{ define "page-content" }} +
+ + + + + +
+{{ end }} \ No newline at end of file diff --git a/templates/shares.html b/templates/shares.html index 88f8da9..6cb02a1 100644 --- a/templates/shares.html +++ b/templates/shares.html @@ -1,47 +1,52 @@ -{{ define "page-styles"}} - -{{ end }} - -{{ define "page-content" }} -
- {{ range $share := .ShareInfos }} - - {{ end }} - Create Share -
+{{ define "page-styles"}} + +{{ end }} + +{{ define "page-content" }} +
+ {{ range $share := .ShareInfos }} + + {{ end }} + Create Share +
{{ end }} \ No newline at end of file diff --git a/webadmin.go b/webadmin.go index 146766b..9aad1d3 100644 --- a/webadmin.go +++ b/webadmin.go @@ -42,15 +42,18 @@ import ( ) type webAdminHandler struct { - router chi.Router - tplError *template.Template - tplConfirm *template.Template - tplLogin *template.Template - tplIndex *template.Template - tplUsers *template.Template - tplShares *template.Template - tplShareAddUser *template.Template - tplCreateShare *template.Template + router chi.Router + shareStore ShareStore + tplError *template.Template + tplConfirm *template.Template + tplLogin *template.Template + tplIndex *template.Template + tplUsers *template.Template + tplShares *template.Template + tplMyShares *template.Template + tplShareAddUser *template.Template + tplShareAddLogin *template.Template + tplCreateShare *template.Template } type sessionContext struct { @@ -93,14 +96,17 @@ func newWebAdminHandler(app *app) *webAdminHandler { } h := &webAdminHandler{ - tplError: loadTemplate("error"), - tplConfirm: loadTemplate("confirm"), - tplLogin: loadSingleTemplate("login"), - tplIndex: loadTemplate("index"), - tplUsers: loadTemplate("users"), - tplShares: loadTemplate("shares"), - tplShareAddUser: loadTemplate("share-add-user"), - tplCreateShare: loadTemplate("create-share"), + shareStore: app.shareStore, + tplError: loadTemplate("error"), + tplConfirm: loadTemplate("confirm"), + tplLogin: loadSingleTemplate("login"), + tplIndex: loadTemplate("index"), + tplUsers: loadTemplate("users"), + tplShares: loadTemplate("shares"), + tplMyShares: loadTemplate("my-shares"), + tplShareAddUser: loadTemplate("share-add-user"), + tplShareAddLogin: loadTemplate("share-add-login"), + tplCreateShare: loadTemplate("create-share"), } r := chi.NewRouter() @@ -231,6 +237,54 @@ func newWebAdminHandler(app *app) *webAdminHandler { "ShareInfos": shareInfos, }) }) + ar.Get("/my-shares", func(w http.ResponseWriter, r *http.Request) { + sessionContext := h.buildSessionContext(w, r) + + shares, err := app.shareStore.FindSharesByUser(sessionContext.user.Username) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + type shareInfo struct { + Share + IsAdmin bool + Logins []Login + Users []ShareUser + } + + shareInfos := make([]shareInfo, len(shares)) + for i := range shares { + shareInfos[i].Share = shares[i].Share + + shareRole, err := app.shareStore.GetShareAccess(shares[i].Share, sessionContext.user.Username) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + shareInfos[i].IsAdmin = sessionContext.user.Role == GlobalRoleAdmin || + shareRole == ShareRoleAdmin + + if shareInfos[i].IsAdmin { + users, err := app.shareStore.GetShareUsers(shares[i].Share) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + shareInfos[i].Users = users + } + + logins, err := app.shareStore.GetShareLogins(shares[i].Share, sessionContext.user.Username) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + shareInfos[i].Logins = logins + } + sessionContext.RenderPage(h.tplMyShares, map[string]interface{}{ + "ShareInfos": shareInfos, + }) + }) ar.Route("/share-add-user", func(r chi.Router) { r.Get("/", func(w http.ResponseWriter, r *http.Request) { sessionContext := h.buildSessionContext(w, r) @@ -241,12 +295,9 @@ func newWebAdminHandler(app *app) *webAdminHandler { return } - if sessionContext.user.Role != GlobalRoleAdmin { - shareRole, err := app.shareStore.GetShareAccess(share, sessionContext.user.Username) - if err != nil || shareRole != ShareRoleAdmin { - sessionContext.Unauthorized() - return - } + if !sessionContext.IsAdmin(share) { + sessionContext.Unauthorized() + return } users, err := app.userStore.GetUsers() @@ -271,12 +322,9 @@ func newWebAdminHandler(app *app) *webAdminHandler { return } - if sessionContext.user.Role != GlobalRoleAdmin { - shareRole, err := app.shareStore.GetShareAccess(share, sessionContext.user.Username) - if err != nil || shareRole != ShareRoleAdmin { - sessionContext.Unauthorized() - return - } + if !sessionContext.IsAdmin(share) { + sessionContext.Unauthorized() + return } user, err := app.userStore.GetUser(r.FormValue("user")) @@ -300,6 +348,94 @@ func newWebAdminHandler(app *app) *webAdminHandler { ar.Post("/share-delete-user", func(w http.ResponseWriter, r *http.Request) { sessionContext := h.buildSessionContext(w, r) + returnURL := r.FormValue("source") + + share, err := app.shareStore.GetShare(r.FormValue("share")) + if err != nil { + sessionContext.RenderError(template.HTML("Internal error: "+err.Error()), "") + return + } + + if !sessionContext.IsAdmin(share) { + sessionContext.Unauthorized() + return + } + + username := r.FormValue("user") + + message := fmt.Sprintf(`You are about to delete the user "%s" from the share %s.
+This will irrevocably also remove all logins of that user.

+Are you sure you want to continue?`, username, share.Name) + if confirmStatus := sessionContext.RequestConfirmation(template.HTML(message), "share-delete-user"); confirmStatus == confirmRequested { + // We have already rendered. Nothing to do. + return + } else if confirmStatus == confirmAccepted { + err = app.shareStore.RemoveUserFromShare(share, username) + if err != nil { + sessionContext.RenderError(template.HTML("Cannot remove user from share: "+err.Error()), returnURL) + return + } + } + + http.Redirect(w, r, returnURL, http.StatusFound) + }) + ar.Route("/share-add-login", func(r chi.Router) { + r.Get("/", func(w http.ResponseWriter, r *http.Request) { + sessionContext := h.buildSessionContext(w, r) + + share, err := app.shareStore.GetShare(r.URL.Query().Get("share")) + if err != nil { + sessionContext.RenderError(template.HTML("Internal error: "+err.Error()), "") + return + } + + if !sessionContext.IsAdmin(share) { + sessionContext.Unauthorized() + return + } + + sessionContext.RenderPage(h.tplShareAddLogin, map[string]interface{}{ + "ShareId": share.UUID, + }) + }) + r.Post("/", func(w http.ResponseWriter, r *http.Request) { + sessionContext := h.buildSessionContext(w, r) + + returnURL := "share-add-login?share=" + r.FormValue("share") + + share, err := app.shareStore.GetShare(r.FormValue("share")) + if err != nil { + sessionContext.RenderError(template.HTML("Internal error: "+err.Error()), returnURL) + return + } + + if !sessionContext.IsAdmin(share) { + sessionContext.Unauthorized() + return + } + + pwHash, err := bcrypt.GenerateFromPassword([]byte(r.FormValue("password")), bcrypt.DefaultCost) + if err != nil { + sessionContext.RenderError(template.HTML("Internal error: "+err.Error()), returnURL) + return + } + + err = app.shareStore.AddLogin(share, sessionContext.user.Username, Login{ + LoginName: r.FormValue("login"), + Password: string(pwHash), + ReadOnly: r.FormValue("readonly") == "on", + }) + if err != nil { + sessionContext.RenderError(template.HTML("Cannot add login: "+err.Error()), returnURL) + return + } + + sessionContext.Redirect("my-shares") + }) + }) + ar.Post("/share-delete-login", func(w http.ResponseWriter, r *http.Request) { + sessionContext := h.buildSessionContext(w, r) + returnURL := "shares" share, err := app.shareStore.GetShare(r.FormValue("share")) @@ -308,21 +444,27 @@ func newWebAdminHandler(app *app) *webAdminHandler { return } - if sessionContext.user.Role != GlobalRoleAdmin { - shareRole, err := app.shareStore.GetShareAccess(share, sessionContext.user.Username) - if err != nil || shareRole != ShareRoleAdmin { - sessionContext.Unauthorized() + if !sessionContext.IsAdmin(share) { + sessionContext.Unauthorized() + return + } + + loginName := r.FormValue("login") + + message := fmt.Sprintf(`You are about to delete the login "%s".

+Are you sure you want to continue?`, loginName) + if confirmStatus := sessionContext.RequestConfirmation(template.HTML(message), "share-delete-login"); confirmStatus == confirmRequested { + // We have already rendered. Nothing to do. + return + } else if confirmStatus == confirmAccepted { + err = app.shareStore.RemoveLogin(share, sessionContext.user.Username, loginName) + if err != nil { + sessionContext.RenderError(template.HTML("Cannot remove login from share: "+err.Error()), returnURL) return } } - err = app.shareStore.RemoveUserFromShare(share, r.FormValue("user")) - if err != nil { - sessionContext.RenderError(template.HTML("Cannot remove user from share: "+err.Error()), returnURL) - return - } - - http.Redirect(w, r, "shares", http.StatusFound) + http.Redirect(w, r, "my-shares", http.StatusFound) }) ar.Route("/create-share", func(r chi.Router) { r.Get("/", func(w http.ResponseWriter, r *http.Request) { @@ -358,12 +500,9 @@ func newWebAdminHandler(app *app) *webAdminHandler { return } - if sessionContext.user.Role != GlobalRoleAdmin { - shareRole, err := app.shareStore.GetShareAccess(share, sessionContext.user.Username) - if err != nil || shareRole != ShareRoleAdmin { - sessionContext.Unauthorized() - return - } + if !sessionContext.IsAdmin(share) { + sessionContext.Unauthorized() + return } message := fmt.Sprintf(`You are about to delete the share %s (%s).
@@ -429,6 +568,16 @@ func (s *sessionContext) RenderError(msg template.HTML, returnURL string) { s.RenderPage(s.h.tplError, model) } +func (s *sessionContext) IsAdmin(share Share) bool { + if s.user.Role != GlobalRoleAdmin { + shareRole, err := s.h.shareStore.GetShareAccess(share, s.user.Username) + if err != nil || shareRole != ShareRoleAdmin { + return false + } + } + return true +} + func (s *sessionContext) Unauthorized() { http.Error(s.w, http.StatusText(http.StatusForbidden), http.StatusForbidden) }