Add 'dis_grant' command
This commit is contained in:
parent
6bf6d3a8f5
commit
8b55fd74f9
10 changed files with 161 additions and 18 deletions
96
cmd/dis_grant.go
Normal file
96
cmd/dis_grant.go
Normal file
|
|
@ -0,0 +1,96 @@
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
wisski_distillery "github.com/FAU-CDI/wisski-distillery"
|
||||||
|
"github.com/FAU-CDI/wisski-distillery/internal/cli"
|
||||||
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/instances"
|
||||||
|
"github.com/FAU-CDI/wisski-distillery/internal/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DisGrant is the 'dis_grant' command
|
||||||
|
var DisGrant wisski_distillery.Command = disGrant{}
|
||||||
|
|
||||||
|
type disGrant struct {
|
||||||
|
AddUser bool `short:"a" long:"add" description:"add or update a user to a given wisski"`
|
||||||
|
RemoveUser bool `short:"r" long:"remove" description:"remove a user from a given wisski"`
|
||||||
|
|
||||||
|
DrupalAdmin bool `short:"A" long:"admin" description:"grant user the admin role"`
|
||||||
|
|
||||||
|
Positionals struct {
|
||||||
|
User string `positional-arg-name:"USER" required:"1-1" description:"distillery username"`
|
||||||
|
Slug string `positional-arg-name:"SLUG" required:"1-1" description:"WissKI instance"`
|
||||||
|
DrupalUser string `positional-arg-name:"DRUPAL" description:"drupal username"`
|
||||||
|
} `positional-args:"true"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (disGrant) Description() wisski_distillery.Description {
|
||||||
|
return wisski_distillery.Description{
|
||||||
|
Requirements: cli.Requirements{
|
||||||
|
NeedsDistillery: true,
|
||||||
|
},
|
||||||
|
Command: "dis_grant",
|
||||||
|
Description: "grant distillery users access to specific WissKIs",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dg disGrant) AfterParse() error {
|
||||||
|
var counter int
|
||||||
|
for _, action := range []bool{
|
||||||
|
dg.AddUser,
|
||||||
|
dg.RemoveUser,
|
||||||
|
} {
|
||||||
|
if action {
|
||||||
|
counter++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if counter != 1 {
|
||||||
|
return errNoActionSelected
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dg disGrant) Run(context wisski_distillery.Context) error {
|
||||||
|
switch {
|
||||||
|
case dg.AddUser:
|
||||||
|
return dg.runAddUser(context)
|
||||||
|
case dg.RemoveUser:
|
||||||
|
return dg.runRemoveUser(context)
|
||||||
|
}
|
||||||
|
panic("never reached")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dg disGrant) checkHasSlug(context wisski_distillery.Context) error {
|
||||||
|
has, err := context.Environment.Instances().Has(context.Context, dg.Positionals.Slug)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !has {
|
||||||
|
return instances.ErrWissKINotFound
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dg disGrant) runAddUser(context wisski_distillery.Context) error {
|
||||||
|
if err := dg.checkHasSlug(context); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
policy := context.Environment.Policy()
|
||||||
|
return policy.Set(context.Context, models.Grant{
|
||||||
|
User: dg.Positionals.User,
|
||||||
|
Slug: dg.Positionals.Slug,
|
||||||
|
DrupalUsername: dg.Positionals.DrupalUser,
|
||||||
|
DrupalAdminRole: dg.DrupalAdmin,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dg disGrant) runRemoveUser(context wisski_distillery.Context) error {
|
||||||
|
if err := dg.checkHasSlug(context); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
policy := context.Environment.Policy()
|
||||||
|
return policy.Remove(context.Context, dg.Positionals.User, dg.Positionals.Slug)
|
||||||
|
}
|
||||||
|
|
@ -101,5 +101,10 @@ func (i info) Run(context wisski_distillery.Context) error {
|
||||||
context.Printf("- %v\n", user)
|
context.Printf("- %v\n", user)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
context.Printf("Grants: (count %d)\n", len(info.Grants))
|
||||||
|
for _, grant := range info.Grants {
|
||||||
|
context.Printf("- %v\n", grant)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,7 @@ func init() {
|
||||||
|
|
||||||
// distillery auth
|
// distillery auth
|
||||||
wdcli.Register(cmd.DisUser)
|
wdcli.Register(cmd.DisUser)
|
||||||
|
wdcli.Register(cmd.DisGrant)
|
||||||
|
|
||||||
// backup & cron
|
// backup & cron
|
||||||
wdcli.Register(cmd.Snapshot)
|
wdcli.Register(cmd.Snapshot)
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,24 @@ func (g grantDeny) Denied() string {
|
||||||
return string(g)
|
return string(g)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AllPermissions returns a new permission that checks if all the given permissions are set
|
||||||
|
func AllPermissions(clauses ...Permission) Permission {
|
||||||
|
return func(user *AuthUser, r *http.Request) (ok Grant, err error) {
|
||||||
|
for _, clause := range clauses {
|
||||||
|
perm, err := clause.Permit(user, r)
|
||||||
|
if err != nil {
|
||||||
|
return perm, err
|
||||||
|
}
|
||||||
|
if !perm.Granted() {
|
||||||
|
return perm, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// everything was fine
|
||||||
|
return grantAllow{}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var errPermissionPanic = errors.New("permission: panic()")
|
var errPermissionPanic = errors.New("permission: panic()")
|
||||||
|
|
||||||
// Permit checks if the given user has this permission.
|
// Permit checks if the given user has this permission.
|
||||||
|
|
|
||||||
|
|
@ -15,10 +15,21 @@ var (
|
||||||
|
|
||||||
// Set sets a specific grant, overwriting a previous grant (if any)
|
// Set sets a specific grant, overwriting a previous grant (if any)
|
||||||
func (policy *Policy) Set(ctx context.Context, grant models.Grant) error {
|
func (policy *Policy) Set(ctx context.Context, grant models.Grant) error {
|
||||||
if grant.User == "" || grant.Slug == "" || grant.DrupalUsername == "" {
|
if grant.DrupalUsername == "" {
|
||||||
|
grant.DrupalUsername = grant.User
|
||||||
|
}
|
||||||
|
if grant.User == "" || grant.Slug == "" {
|
||||||
return ErrInvalid
|
return ErrInvalid
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check that the referenced user exists!
|
||||||
|
{
|
||||||
|
_, err := policy.Dependencies.Auth.User(ctx, grant.User)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// get the table
|
// get the table
|
||||||
table, err := policy.table(ctx)
|
table, err := policy.table(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -27,8 +38,7 @@ func (policy *Policy) Set(ctx context.Context, grant models.Grant) error {
|
||||||
|
|
||||||
// and create or update the given user / slug combination
|
// and create or update the given user / slug combination
|
||||||
return table.Clauses(clause.OnConflict{
|
return table.Clauses(clause.OnConflict{
|
||||||
Columns: []clause.Column{{Name: "user"}, {Name: "slug"}},
|
UpdateAll: true,
|
||||||
DoUpdates: clause.AssignmentColumns([]string{"drupal_user", "admin"}),
|
|
||||||
}).Create(&grant).Error
|
}).Create(&grant).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
|
"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/sql"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/sql"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/models"
|
"github.com/FAU-CDI/wisski-distillery/internal/models"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
|
|
@ -13,7 +14,8 @@ type Policy struct {
|
||||||
component.Base
|
component.Base
|
||||||
|
|
||||||
Dependencies struct {
|
Dependencies struct {
|
||||||
SQL *sql.SQL
|
SQL *sql.SQL
|
||||||
|
Auth *auth.Auth
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -74,5 +74,10 @@ func (auth *Auth) Protect(handler http.Handler, perm Permission) http.Handler {
|
||||||
|
|
||||||
// Admin represents a permission that checks if a user is an administrator and has totp enabled.
|
// Admin represents a permission that checks if a user is an administrator and has totp enabled.
|
||||||
var Admin Permission = func(user *AuthUser, r *http.Request) (ok Grant, err error) {
|
var Admin Permission = func(user *AuthUser, r *http.Request) (ok Grant, err error) {
|
||||||
return Bool2Grant(user != nil && user.IsAdmin() && user.IsTOTPEnabled(), "user needs to have admin permissions and TOTP enabled"), nil
|
return Bool2Grant(user != nil && user.IsAdmin() && user.IsTOTPEnabled(), "user needs to have admin permissions and passcode enabled"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// User represents a permission that checks if a user has totp enabled.
|
||||||
|
var User Permission = func(user *AuthUser, r *http.Request) (ok Grant, err error) {
|
||||||
|
return Bool2Grant(user != nil && user.IsEnabled() && user.IsTOTPEnabled(), "user needs to have passcode enabled"), nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,23 +13,26 @@
|
||||||
{{ define "content" }}
|
{{ define "content" }}
|
||||||
<div class="pure-u-1">
|
<div class="pure-u-1">
|
||||||
<p>
|
<p>
|
||||||
{{ if .User.IsAdmin }}
|
<ul>
|
||||||
You are an administrator.
|
{{ if .User.IsAdmin }}
|
||||||
{{ else }}
|
<li>Role: <b>Administrator</b></li>
|
||||||
You are a regular user.
|
{{ else }}
|
||||||
{{ end }}
|
<li>Role: <b>Regular User</b></li>
|
||||||
{{ if .User.IsTOTPEnabled }}
|
{{ end }}
|
||||||
You have TOTP enabled.
|
|
||||||
{{ else }}
|
{{ if .User.IsTOTPEnabled }}
|
||||||
You do not have TOTP enabled.
|
<li>Passcode Enabled: <b>true</b></li>
|
||||||
{{ end }}
|
{{ else }}
|
||||||
|
<li>Passcode Enabled: <b>false</b> <small>(some actions are disabled)</small></li>
|
||||||
|
{{ end }}
|
||||||
|
</ul>
|
||||||
</p>
|
</p>
|
||||||
<div class="pure-button-group" role="group" role="Actions">
|
<div class="pure-button-group" role="group" role="Actions">
|
||||||
<a class="pure-button" href="/user/password/">Change Password</a>
|
<a class="pure-button" href="/user/password/">Change Password</a>
|
||||||
{{ if .User.IsTOTPEnabled }}
|
{{ if .User.IsTOTPEnabled }}
|
||||||
<a class="pure-button" href="/user/totp/disable/">Disable TOTP</a>
|
<a class="pure-button" href="/user/totp/disable/">Disable Passcode (TOTP)</a>
|
||||||
{{ else }}
|
{{ else }}
|
||||||
<a class="pure-button" href="/user/totp/enable/">Enable TOTP</a>
|
<a class="pure-button" href="/user/totp/enable/">Enable Passcode (TOTP)</a>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
<hr />
|
<hr />
|
||||||
|
|
|
||||||
|
|
@ -103,6 +103,9 @@ func (dis *Distillery) Provisionable() []component.Provisionable {
|
||||||
func (dis *Distillery) Info() *admin.Admin {
|
func (dis *Distillery) Info() *admin.Admin {
|
||||||
return export[*admin.Admin](dis)
|
return export[*admin.Admin](dis)
|
||||||
}
|
}
|
||||||
|
func (dis *Distillery) Policy() *policy.Policy {
|
||||||
|
return export[*policy.Policy](dis)
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// All components
|
// All components
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,2 @@
|
||||||
// Package status provides status information
|
// Package status provides dagta structutres for distillery information
|
||||||
package status
|
package status
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue