Skip to content

Commit

Permalink
Replace legacy permission commands.
Browse files Browse the repository at this point in the history
  • Loading branch information
lnsp committed Jan 25, 2024
1 parent 6b37a1f commit 6316d31
Show file tree
Hide file tree
Showing 2 changed files with 185 additions and 57 deletions.
153 changes: 108 additions & 45 deletions cmd/projects.go
Original file line number Diff line number Diff line change
@@ -1,97 +1,160 @@
package cmd

import (
"errors"
"fmt"
"os"
"strings"
"text/tabwriter"
"valar/cli/pkg/api"
"valar/cli/pkg/config"

"github.com/spf13/cobra"
)

var (
authCmd = &cobra.Command{
Use: "auth",
Short: "Manage project permissions",
authListCmd = &cobra.Command{
Use: "list [path]",
Short: "List permissions for a path prefix",
Args: cobra.MaximumNArgs(1),
Run: runAndHandle(func(cmd *cobra.Command, args []string) error {
client, err := globalConfiguration.APIClient()
if err != nil {
return err
}
var project string
cfg := &config.ServiceConfig{}
if err := cfg.ReadFromFile(functionConfiguration); err != nil {
// Use default project
project = globalConfiguration.Project()
} else {
project = cfg.Project
if err := cfg.ReadFromFile(functionConfiguration); errors.Is(err, os.ErrNotExist) {
cfg.Project = globalConfiguration.Project()
} else if err != nil {
return err
}
pms, err := client.ListPermissions(project)
namespace, prefix := "service", cfg.Project
if len(args) == 1 {
subargs := strings.SplitN(args[0], ":", 2)
if len(subargs) != 2 {
return fmt.Errorf("expect path to be in the form namespace:prefix")
}
namespace, prefix = subargs[0], subargs[1]
}
permissions, err := client.ListPermissions(cfg.Project, namespace, prefix)
if err != nil {
return err
}
tw := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0)
fmt.Fprintln(tw, "USER\tACTIONS")
for user, actions := range pms {
fmt.Fprintf(tw, "%s\t%s\n", user, strings.Join(actions, ", "))
fmt.Fprintln(tw, "PATH\tUSER\tACTION\tSTATE")
for _, pm := range permissions {
fmt.Fprintf(tw, "%s\t%s\t%s\t%s\n", pm.Path, pm.User, pm.Action, pm.State)
}
tw.Flush()
return nil
}),
}
authUser, authAction string
authAllowCmd = &cobra.Command{
Use: "allow",
Short: "Allow a user to perform a specific action",
Args: cobra.ExactArgs(0),

authAllowCmd = &cobra.Command{
Use: "allow path user action",
Short: "Modify permissions for a path and user",
Args: cobra.ExactArgs(3),
Run: runAndHandle(authModifyWithState("allow")),
}
authForbidCmd = &cobra.Command{
Use: "forbid path user action",
Short: "Forbid a specific action for a path and user",
Args: cobra.ExactArgs(3),
Run: runAndHandle(authModifyWithState("forbid")),
}
authClearCmd = &cobra.Command{
Use: "clear path user action",
Short: "Remove the permission for a path and user",
Args: cobra.ExactArgs(3),
Run: runAndHandle(authModifyWithState("unset")),
}
authCheckCmd = &cobra.Command{
Use: "check path user action",
Short: "Check if a user can perform an action",
Args: cobra.ExactArgs(3),
Run: runAndHandle(func(cmd *cobra.Command, args []string) error {
client, err := globalConfiguration.APIClient()
if err != nil {
return err
}
var project string
cfg := &config.ServiceConfig{}
if err := cfg.ReadFromFile(functionConfiguration); err != nil {
project = globalConfiguration.Project()
} else {
project = cfg.Project
if err := cfg.ReadFromFile(functionConfiguration); errors.Is(err, os.ErrNotExist) {
cfg.Project = globalConfiguration.Project()
} else if err != nil {
return err
}
if err := client.ModifyPermission(project, authUser, authAction, false); err != nil {
path, err := api.PermissionPathFromString(args[0])
if err != nil {
return err
}
return nil
}),
}
authForbidCmd = &cobra.Command{
Use: "forbid",
Short: "Forbid a user to perform a specific action",
Args: cobra.ExactArgs(0),
Run: runAndHandle(func(cmd *cobra.Command, args []string) error {
client, err := globalConfiguration.APIClient()
user, err := api.PermissionUserFromString(args[1])
if err != nil {
return err
}
var project string
cfg := &config.ServiceConfig{}
if err := cfg.ReadFromFile(functionConfiguration); err != nil {
project = globalConfiguration.Project()
} else {
project = cfg.Project
permission := api.Permission{
Path: path,
User: user,
Action: args[2],
}
if err := client.ModifyPermission(project, authUser, authAction, true); err != nil {
allowed, err := client.CheckPermission(cfg.Project, permission)
if err != nil {
return err
}
if allowed {
fmt.Println("allowed")
} else {
fmt.Println("forbidden")
}
return nil
}),
}

authCmd = &cobra.Command{
Use: "auth",
Short: "Manage user and service permissions",
}
)

func authModifyWithState(state string) func(cmd *cobra.Command, args []string) error {
return func(cmd *cobra.Command, args []string) error {
client, err := globalConfiguration.APIClient()
if err != nil {
return err
}
cfg := &config.ServiceConfig{}
if err := cfg.ReadFromFile(functionConfiguration); errors.Is(err, os.ErrNotExist) {
cfg.Project = globalConfiguration.Project()
} else if err != nil {
return err
}
path, err := api.PermissionPathFromString(args[0])
if err != nil {
return err
}
user, err := api.PermissionUserFromString(args[1])
if err != nil {
return err
}
permission := api.Permission{
Path: path,
User: user,
Action: args[2],
State: state,
}
modified, err := client.ModifyPermission(cfg.Project, permission)
if err != nil {
return err
}
if modified {
fmt.Println("modified")
} else {
fmt.Println("unchanged")
}
return nil
}
}

func initProjectsCmd() {
authCmd.AddCommand(authAllowCmd, authForbidCmd)
authForbidCmd.Flags().StringVarP(&authAction, "action", "a", "invoke", "Action to be modified")
authForbidCmd.Flags().StringVarP(&authUser, "user", "u", "anonymous", "User to be modified")
authAllowCmd.Flags().StringVarP(&authAction, "action", "a", "invoke", "Action to be modified")
authAllowCmd.Flags().StringVarP(&authUser, "user", "u", "anonymous", "User to be modified")
authCmd.AddCommand(authListCmd, authAllowCmd, authForbidCmd, authClearCmd, authCheckCmd)
rootCmd.AddCommand(authCmd)
}
89 changes: 77 additions & 12 deletions pkg/api/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"net/http"
"net/url"
"strconv"
"strings"
"time"
)

Expand Down Expand Up @@ -225,10 +226,13 @@ func (client *Client) ListBuilds(project, service, id string) ([]Build, error) {
}

// ListPermissions retrieves all permissions set for a specific project.
func (client *Client) ListPermissions(project string) (PermissionSet, error) {
func (client *Client) ListPermissions(project, namespace, prefix string) ([]Permission, error) {
params := url.Values{}
params.Add("namespace", namespace)
params.Add("prefix", prefix)
var (
set = make(PermissionSet)
path = fmt.Sprintf("/projects/%s/auth", project)
set = []Permission{}
path = fmt.Sprintf("/projects/%s/permissions?%s", project, params.Encode())
)
if err := client.request(http.MethodGet, path, &set, nil); err != nil {
return nil, err
Expand All @@ -237,19 +241,35 @@ func (client *Client) ListPermissions(project string) (PermissionSet, error) {
}

// ModifyPermission modifies the permission set of a project.
func (client *Client) ModifyPermission(project, user, action string, forbid bool) error {
func (client *Client) ModifyPermission(project string, permission Permission) (bool, error) {
var (
resp struct{}
path = fmt.Sprintf("/projects/%s/auth/%s/%s", project, user, action)
resp struct {
Modified bool `json:"modified"`
}
path = fmt.Sprintf("/projects/%s/permissions", project)
method = http.MethodPost
)
if forbid {
method = http.MethodDelete
body, _ := json.Marshal(&permission)
if err := client.request(method, path, &resp, bytes.NewReader(body)); err != nil {
return false, err
}
if err := client.request(method, path, &resp, nil); err != nil {
return err
return resp.Modified, nil
}

// CheckPermission checks if a user has a specific permission.
func (client *Client) CheckPermission(project string, permission Permission) (bool, error) {
var (
resp struct {
Allowed bool `json:"allowed"`
}
path = fmt.Sprintf("/projects/%s/permissions?mode=check", project)
method = http.MethodPost
)
body, _ := json.Marshal(&permission)
if err := client.request(method, path, &resp, bytes.NewReader(body)); err != nil {
return false, err
}
return nil
return resp.Allowed, nil
}

// ListDeployments shows all deployments of a specific service.
Expand Down Expand Up @@ -419,7 +439,52 @@ type KVPair struct {
Secret bool `json:"secret"`
}

type PermissionSet map[string][]string
type PermissionPath struct {
Namespace string `json:"namespace"`
Items []string `json:"items"`
}

func (pp PermissionPath) String() string {
return fmt.Sprintf("%s:%s", pp.Namespace, strings.Join(pp.Items, "/"))
}

func PermissionPathFromString(str string) (PermissionPath, error) {
path := PermissionPath{}
parts := strings.SplitN(str, ":", 2)
if len(parts) != 2 {
return path, fmt.Errorf("invalid path: %s", str)
}
path.Namespace = parts[0]
path.Items = strings.Split(parts[1], "/")
return path, nil
}

type PermissionUser struct {
Type string `json:"type"`
Identifier []string `json:"identifier"`
}

func (pp PermissionUser) String() string {
return fmt.Sprintf("%s:%s", pp.Type, strings.Join(pp.Identifier, "/"))
}

func PermissionUserFromString(str string) (PermissionUser, error) {
user := PermissionUser{}
parts := strings.SplitN(str, ":", 2)
if len(parts) != 2 {
return user, fmt.Errorf("invalid user: %s", str)
}
user.Type = parts[0]
user.Identifier = strings.Split(parts[1], "/")
return user, nil
}

type Permission struct {
Path PermissionPath `json:"path"`
User PermissionUser `json:"user"`
Action string `json:"action"`
State string `json:"state"`
}

type UserInfo struct {
Name string `json:"name"`
Expand Down

0 comments on commit 6316d31

Please sign in to comment.