diff --git a/NEWS.md b/NEWS.md index 5fedda3..287d2ea 100644 --- a/NEWS.md +++ b/NEWS.md @@ -2,6 +2,18 @@ This file contains signficant news items for the distillery. +# User And Instance Management (2023-01-07) +- the concept of distillery user accounts has been added + - their accounts have a password as well as TOTP + - users can manage their own account details + - administrators can reset user passwords, and disable TOTP +- distillery accounts can be linked to multiple drupal accounts + - users can sign into the account without entering further passwords + - users must have two-factor-authentication enabled to use this functionality +- administrators have access to the distillery admin panel + - the functionality to manage distillery accounts has been added + - the functionality to link distillery and drupal accounts has been added + # Automatic Password Checking (2022-11-25) - Implemented automatic password checking diff --git a/internal/dis/component/auth/next/next.go b/internal/dis/component/auth/next/next.go new file mode 100644 index 0000000..515a877 --- /dev/null +++ b/internal/dis/component/auth/next/next.go @@ -0,0 +1,109 @@ +package next + +import ( + "context" + "net/http" + "net/url" + + "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/instances" + "github.com/FAU-CDI/wisski-distillery/internal/wisski" + "github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/php/users" + "github.com/FAU-CDI/wisski-distillery/pkg/httpx" +) + +type Next struct { + component.Base + Dependencies struct { + Auth *auth.Auth + Policy *policy.Policy + Instances *instances.Instances + } +} + +var ( + _ component.Routeable = (*Next)(nil) +) + +func (next *Next) Routes() component.Routes { + return component.Routes{ + Paths: []string{"/next/"}, + Decorator: next.Dependencies.Auth.Require(auth.User), + } +} + +// Next returns a url that will forward authorized users to the given slug and path +func (next *Next) Next(context context.Context, slug, path string) (string, error) { + wisski, err := next.Dependencies.Instances.WissKI(context, slug) + if err != nil { + return "", err + } + + target := wisski.URL() + target.Path = path + return "/next/?next=" + url.PathEscape(target.String()), nil + +} + +func (next *Next) getInstance(r *http.Request) (wisski *wisski.WissKI, path string, err error) { + // extract the instance + url, err := url.Parse(r.URL.Query().Get("next")) + if err != nil { + return nil, "", httpx.ErrBadRequest + } + + // find the slug + slug, ok := next.Config.SlugFromHost(url.Host) + if slug == "" || !ok { + return nil, "", httpx.ErrBadRequest + } + + // fetch the instance from the database + wisski, err = next.Dependencies.Instances.WissKI(r.Context(), slug) + if err != nil { + return nil, "", err + } + + // return the wisski and the relative path + return wisski, url.Path, nil +} + +func (next *Next) HandleRoute(ctx context.Context, path string) (http.Handler, error) { + return httpx.RedirectHandler(func(r *http.Request) (string, int, error) { + // get the instance and the path + instance, path, err := next.getInstance(r) + if err != nil { + return "", 0, httpx.ErrForbidden + } + + // get the user + user, err := next.Dependencies.Auth.UserOf(r) + if err != nil { + return "", 0, err + } + + // check if they have a grant + grant, err := next.Dependencies.Policy.Has(r.Context(), user.User.User, instance.Slug) + if err == policy.ErrNoAccess { + return "", 0, httpx.ErrForbidden + } + if err != nil { + return "", 0, err + } + + // perform the login + dest, err := instance.Users().LoginWithOpt(r.Context(), nil, grant.DrupalUsername, users.LoginOptions{ + Destination: path, + CreateIfMissing: true, + GrantAdminRole: grant.DrupalAdminRole, + }) + if err != nil { + return "", 0, err + } + + // and redirect + return dest.String(), http.StatusSeeOther, nil + }), nil +} diff --git a/internal/dis/component/auth/panel/panel.go b/internal/dis/component/auth/panel/panel.go index ba52d4a..5411795 100644 --- a/internal/dis/component/auth/panel/panel.go +++ b/internal/dis/component/auth/panel/panel.go @@ -6,7 +6,10 @@ 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/next" + "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/instances" "github.com/FAU-CDI/wisski-distillery/internal/models" "github.com/FAU-CDI/wisski-distillery/pkg/httpx" "github.com/julienschmidt/httprouter" @@ -15,8 +18,11 @@ import ( type UserPanel struct { component.Base Dependencies struct { - Auth *auth.Auth - Custom *custom.Custom + Auth *auth.Auth + Custom *custom.Custom + Policy *policy.Policy + Instances *instances.Instances + Next *next.Next } } diff --git a/internal/dis/component/auth/panel/templates/user.html b/internal/dis/component/auth/panel/templates/user.html index 6ec9a43..d2e71fd 100644 --- a/internal/dis/component/auth/panel/templates/user.html +++ b/internal/dis/component/auth/panel/templates/user.html @@ -55,8 +55,49 @@ {{ end }}
+ This is a page of WissKIs you have access to. + Click on the button containing the name to login. +
+| + WissKI Slug + | ++ Drupal Username + | ++ Admin + | +
|---|---|---|
| + + {{ $grant.Slug }} + + | ++ {{ $grant.DrupalUsername }} + | ++ {{ $grant.DrupalAdminRole }} + | +