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.
+
+
+
+
+ Distillery Users must exist in order to grant them access to a specific instance
+
+
+ Drupal Users will be automatically created if they do not exist.
+
+
+ If Admin is checked and a user logs in, they will automatically be given the admin role.
+ For security reasons, an admin role is never automatically removed.
+
+
+
+ {{ block "form/message" . }}
+ {{ $E := .Error }}
+ {{ if not (eq $E "") }}
+
+ {{ end }}
+ {{ end }}
+
+
+
+ {{ range $unused, $user := .Usernames }}
+
+ {{ end }}
+
+
+ {{ range $unused, $drupal := .Drupals }}
+
+ {{ end }}
+
+{{ 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
{{ end }}
@@ -180,7 +181,8 @@
@@ -265,47 +267,6 @@
-
-
(Distillery) Users
-
-
-
-
-
-
-
-
-
- Distillery User
-
-
- WissKI User
-
-
- Admin
-
-
-
-
- {{ range $index, $grant := .Info.Grants }}
-
-
- {{ $grant.User }}
-
-
- {{ $grant.DrupalUsername }}
-
-
- {{ $grant.DrupalAdminRole }}
-
-
- {{ end }}
-
-
-
-
-
-
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 @@
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