From 97f5ac7e1a49c564a41289d0409523c81521d3b3 Mon Sep 17 00:00:00 2001 From: Tom Wiesing Date: Sat, 7 Jan 2023 13:14:43 +0100 Subject: [PATCH] Update grant page --- internal/dis/component/control/admin/admin.go | 13 ++ .../dis/component/control/admin/grants.go | 152 ++++++++++++++++++ .../component/control/admin/html/grants.html | 135 ++++++++++++++++ .../control/admin/html/instance.html | 51 +----- .../component/control/admin/html/users.html | 2 +- .../dis/component/control/admin/instance.go | 3 - internal/dis/component/control/admin/users.go | 4 +- pkg/httpx/form.go | 3 + pkg/httpx/html_minify.go | 2 +- pkg/httpx/html_minify_off.go | 2 +- 10 files changed, 313 insertions(+), 54 deletions(-) create mode 100644 internal/dis/component/control/admin/grants.go create mode 100644 internal/dis/component/control/admin/html/grants.html diff --git a/internal/dis/component/control/admin/admin.go b/internal/dis/component/control/admin/admin.go index 7c4a2fc..2b81977 100644 --- a/internal/dis/component/control/admin/admin.go +++ b/internal/dis/component/control/admin/admin.go @@ -6,6 +6,7 @@ import ( "github.com/FAU-CDI/wisski-distillery/internal/dis/component" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth" + "github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth/policy" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/control/static/custom" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/exporter" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/exporter/logger" @@ -28,6 +29,8 @@ type Admin struct { Auth *auth.Auth + Policy *policy.Policy + Custom *custom.Custom } @@ -108,6 +111,16 @@ func (admin *Admin) HandleRoute(ctx context.Context, route string) (handler http Template: admin.Dependencies.Custom.Template(instanceTemplate), }) + // add a router for the grants pages + router.Handler(http.MethodGet, route+"grants/:slug", httpx.HTMLHandler[grantsContext]{ + Handler: admin.getGrants, + Template: admin.Dependencies.Custom.Template(grantsTemplate), + }) + router.Handler(http.MethodPost, route+"grants/", httpx.HTMLHandler[grantsContext]{ + Handler: admin.postGrants, + Template: admin.Dependencies.Custom.Template(grantsTemplate), + }) + // add a router for the login page router.Handler(http.MethodPost, route+"login", admin.loginHandler(ctx)) diff --git a/internal/dis/component/control/admin/grants.go b/internal/dis/component/control/admin/grants.go new file mode 100644 index 0000000..3e12c57 --- /dev/null +++ b/internal/dis/component/control/admin/grants.go @@ -0,0 +1,152 @@ +package admin + +import ( + _ "embed" + "fmt" + "net/http" + + "github.com/FAU-CDI/wisski-distillery/internal/dis/component/control/static" + "github.com/FAU-CDI/wisski-distillery/internal/dis/component/control/static/custom" + "github.com/FAU-CDI/wisski-distillery/internal/dis/component/instances" + "github.com/FAU-CDI/wisski-distillery/internal/models" + "github.com/FAU-CDI/wisski-distillery/internal/wisski" + "github.com/FAU-CDI/wisski-distillery/pkg/httpx" + "github.com/gorilla/mux" + "golang.org/x/exp/maps" + "golang.org/x/exp/slices" +) + +//go:embed "html/grants.html" +var grantsStr string +var grantsTemplate = static.AssetsAdmin.MustParseShared( + "grants.html", + grantsStr, +) + +type grantsContext struct { + custom.BaseContext + + Error string + + instance *wisski.WissKI + Instance models.Instance // current instance + + Grants []models.Grant // grants that exist for the user + Usernames []string // unuused distillery usernames + Drupals []string // unusued drupal usernames +} + +func (gc *grantsContext) use(r *http.Request, slug string, admin *Admin) (err error) { + admin.Dependencies.Custom.Update(gc, r) + + // find the instance itself + gc.instance, err = admin.Dependencies.Instances.WissKI(r.Context(), slug) + if err == instances.ErrWissKINotFound { + return httpx.ErrNotFound + } + if err != nil { + return err + } + gc.Instance = gc.instance.Instance + + return nil +} + +func (gc *grantsContext) useGrants(r *http.Request, admin *Admin) (err error) { + gc.Grants, err = admin.Dependencies.Policy.Instance(r.Context(), gc.Instance.Slug) + if err != nil { + return err + } + + users, err := admin.Dependencies.Auth.Users(r.Context()) + if err != nil { + return err + } + + // create a namemap of users, but not those already taken + userNameMap := make(map[string]struct{}, len(users)) + for _, user := range users { + userNameMap[user.User.User] = struct{}{} + } + for _, grant := range gc.Grants { + delete(userNameMap, grant.User) + } + + // setup the usernames + gc.Usernames = maps.Keys(userNameMap) + slices.Sort(gc.Usernames) + + // get the drupal usernames + drupals, err := gc.instance.Users().All(r.Context(), nil) + if err != nil { + return err + } + + // and convert them to strings only + gc.Drupals = make([]string, len(drupals)) + for i, drupal := range drupals { + gc.Drupals[i] = string(drupal.Name) + } + slices.Sort(gc.Drupals) + + return nil +} + +func (admin *Admin) getGrants(r *http.Request) (gc grantsContext, err error) { + if err := gc.use(r, mux.Vars(r)["slug"], admin); err != nil { + return gc, err + } + + if err := gc.useGrants(r, admin); err != nil { + return gc, err + } + + return gc, nil +} + +func (admin *Admin) postGrants(r *http.Request) (gc grantsContext, err error) { + // parse the form + if err := r.ParseForm(); err != nil { + return gc, err + } + + // read out the form values + var ( + slug = r.PostFormValue("slug") + delete = r.PostFormValue("action") == "delete" + distilleryUser = r.PostFormValue("distillery-user") + drupalUser = r.PostFormValue("drupal-user") + adminRole = r.PostFormValue("admin") == httpx.CheckboxChecked + ) + + // set the common fields + if err := gc.use(r, slug, admin); err != nil { + return gc, err + } + + if delete { + // delete the user grant + err := admin.Dependencies.Policy.Remove(r.Context(), distilleryUser, slug) + if err != nil { + return gc, err + } + } else { + // update the grant + err := admin.Dependencies.Policy.Set(r.Context(), models.Grant{ + User: distilleryUser, + Slug: slug, + + DrupalUsername: drupalUser, + DrupalAdminRole: adminRole, + }) + if err != nil { + gc.Error = fmt.Sprintf("Unable to update grant for user %s: %s", distilleryUser, err.Error()) + } + } + + // fetch the grants for the instance + if err := gc.useGrants(r, admin); err != nil { + return gc, err + } + return gc, nil +} diff --git a/internal/dis/component/control/admin/html/grants.html b/internal/dis/component/control/admin/html/grants.html new file mode 100644 index 0000000..6150ea1 --- /dev/null +++ b/internal/dis/component/control/admin/html/grants.html @@ -0,0 +1,135 @@ +{{ template "_base.html" . }} +{{ define "title" }}Distillery Admin - {{ .Instance.Slug }} - Grants{{ end }} + +{{ define "header"}} +

+ Control > + Instance > + Grants +

+{{ end }} + +{{ define "content" }} +{{ $csrf := .CSRF }} +{{ $slug := .Instance.Slug }} +
+

Grants

+ +

+ A grant provides access for a specific distillery user to a specific WissKI instance. + Only Distillery Administrators can manage grants. +

+ + + + {{ block "form/message" . }} + {{ $E := .Error }} + {{ if not (eq $E "") }} +
+

+ {{ $E }} +

+
+ {{ end }} + {{ end }} +
+
+
+
+ + + + + + + + + + + {{ range $id, $grant := .Grants }} + + + + + + + {{ end }} + + + + + + + +
+ Distillery Username + + Drupal Username + + Roles + + Actions +
+ {{ $grant.User }} + + + + + + + + +
+
+ {{ $csrf }} + + + +
+
+ {{ $csrf }} + + + +
+
+
+ + + + + + + +
+ {{ $csrf }} + + + +
+
+
+
+
+ + {{ range $unused, $user := .Usernames }} + + + {{ range $unused, $drupal := .Drupals }} + +{{ end }} \ No newline at end of file diff --git a/internal/dis/component/control/admin/html/instance.html b/internal/dis/component/control/admin/html/instance.html index 167120a..00fea69 100644 --- a/internal/dis/component/control/admin/html/instance.html +++ b/internal/dis/component/control/admin/html/instance.html @@ -1,14 +1,15 @@ {{ template "_base.html" . }} -{{ define "title" }}Distillery Admin - {{ .Info.Slug }}{{ end }} +{{ define "title" }}Distillery Admin - {{ .Instance.Slug }}{{ end }} {{ define "header"}}

Control > - Instance + Instance

- Ingredients + Grants + Ingredients

{{ end }} @@ -180,7 +181,8 @@
-

(Drupal) Users

+

Users (Drupal)

+ Manage Grants
@@ -265,47 +267,6 @@
-
-

(Distillery) Users

-
- -
-
-
- - - - - - - - - - {{ range $index, $grant := .Info.Grants }} - - - - - - {{ end }} - -
- Distillery User - - WissKI User - - Admin -
- {{ $grant.User }} - - {{ $grant.DrupalUsername }} - - {{ $grant.DrupalAdminRole }} -
-
-
-
-

WissKI Data

diff --git a/internal/dis/component/control/admin/html/users.html b/internal/dis/component/control/admin/html/users.html index 0957103..24b8d42 100644 --- a/internal/dis/component/control/admin/html/users.html +++ b/internal/dis/component/control/admin/html/users.html @@ -63,7 +63,7 @@
-   +   {{ $csrf }}
diff --git a/internal/dis/component/control/admin/instance.go b/internal/dis/component/control/admin/instance.go index 8a76eb6..ce160db 100644 --- a/internal/dis/component/control/admin/instance.go +++ b/internal/dis/component/control/admin/instance.go @@ -10,7 +10,6 @@ import ( "github.com/FAU-CDI/wisski-distillery/internal/models" "github.com/FAU-CDI/wisski-distillery/internal/status" "github.com/FAU-CDI/wisski-distillery/pkg/httpx" - "github.com/gorilla/csrf" "github.com/gorilla/mux" ) @@ -31,8 +30,6 @@ type instanceContext struct { func (admin *Admin) instance(r *http.Request) (is instanceContext, err error) { admin.Dependencies.Custom.Update(&is, r) - is.CSRF = csrf.TemplateField(r) - // find the instance itself! instance, err := admin.Dependencies.Instances.WissKI(r.Context(), mux.Vars(r)["slug"]) if err == instances.ErrWissKINotFound { diff --git a/internal/dis/component/control/admin/users.go b/internal/dis/component/control/admin/users.go index b1cdf2e..2aea17b 100644 --- a/internal/dis/component/control/admin/users.go +++ b/internal/dis/component/control/admin/users.go @@ -11,7 +11,6 @@ import ( "github.com/FAU-CDI/wisski-distillery/internal/dis/component/control/static" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/control/static/custom" "github.com/FAU-CDI/wisski-distillery/pkg/httpx" - "github.com/gorilla/csrf" "github.com/rs/zerolog" ) @@ -32,7 +31,6 @@ type userContext struct { func (admin *Admin) users(r *http.Request) (uc userContext, err error) { admin.Dependencies.Custom.Update(&uc, r) - uc.CSRF = csrf.TemplateField(r) uc.Users, err = admin.Dependencies.Auth.Users(r.Context()) return } @@ -70,7 +68,7 @@ func (admin *Admin) createUser(ctx context.Context) http.Handler { RenderTemplateContext: admin.Dependencies.Custom.RenderContext, Validate: func(r *http.Request, values map[string]string) (cu createUserResult, err error) { - cu.User, cu.Passsword, cu.Admin = values["username"], values["password"], values["admin"] == "on" + cu.User, cu.Passsword, cu.Admin = values["username"], values["password"], values["admin"] == httpx.CheckboxChecked if cu.User == "" { return cu, errCreateInvalidUsername diff --git a/pkg/httpx/form.go b/pkg/httpx/form.go index 56a6993..dc129a4 100644 --- a/pkg/httpx/form.go +++ b/pkg/httpx/form.go @@ -211,3 +211,6 @@ const ( PasswordField InputType = "password" CheckboxField InputType = "checkbox" ) + +// CheckboxChecked is the default value of a checked checkbox +const CheckboxChecked = "on" diff --git a/pkg/httpx/html_minify.go b/pkg/httpx/html_minify.go index 42809a9..02f787a 100644 --- a/pkg/httpx/html_minify.go +++ b/pkg/httpx/html_minify.go @@ -1,4 +1,4 @@ -//go:build nominify +//go:build !nominify package httpx diff --git a/pkg/httpx/html_minify_off.go b/pkg/httpx/html_minify_off.go index edd51c0..b4c57f2 100644 --- a/pkg/httpx/html_minify_off.go +++ b/pkg/httpx/html_minify_off.go @@ -1,4 +1,4 @@ -//go:build !nominify +//go:build nominify package httpx