Jeffrey Smith II 625273adf9
feat: Add new replication field to better show queue sync progress (#480)
* chore: update openapi

* feat: update replication field names to match influx changes

* feat: add new field to show replication queue sync progress

* chore: update to master openapi
2023-03-10 09:13:35 -05:00

467 lines
11 KiB
Go

package auth
import (
"context"
"fmt"
"github.com/influxdata/influx-cli/v2/api"
"github.com/influxdata/influx-cli/v2/api/extras"
"github.com/influxdata/influx-cli/v2/clients"
"github.com/influxdata/influx-cli/v2/pkg/influxid"
)
type Client struct {
clients.CLI
api.AuthorizationsAPITokensApi
api.UsersApi
api.OrganizationsApi
api.ResourcesApi
}
const (
ReadAction = "read"
WriteAction = "write"
)
type token struct {
ID string `json:"id"`
Description string `json:"description"`
Token string `json:"token"`
Status string `json:"status"`
UserName string `json:"userName"`
UserID string `json:"userID"`
Permissions []string `json:"permissions"`
}
type printParams struct {
deleted bool
token *token
tokens []token
}
type ResourcePermission struct {
Name string
Read bool
Write bool
IsCloud bool
IsOss bool
}
type CreateParams struct {
clients.OrgParams
User string
Description string
ResourcePermissions []*ResourcePermission
WriteBucketIds []string
ReadBucketIds []string
OperatorPermission bool
AllAccess bool
}
func BuildResourcePermissions() []*ResourcePermission {
cloudResources := make(map[string]struct{})
ossResources := make(map[string]struct{})
for _, val := range extras.ResourceEnumCloudValues() {
cloudResources[string(val)] = struct{}{}
}
for _, val := range extras.ResourceEnumOSSValues() {
ossResources[string(val)] = struct{}{}
}
perms := make([]*ResourcePermission, 0, len(ossResources))
// First the common permissions in order
for _, val := range extras.ResourceEnumOSSValues() {
if _, ok := cloudResources[string(val)]; ok {
perms = append(perms, &ResourcePermission{
Name: string(val),
IsCloud: true,
IsOss: true,
})
}
}
// Then oss-only in order
for _, val := range extras.ResourceEnumOSSValues() {
if _, ok := cloudResources[string(val)]; !ok {
perms = append(perms, &ResourcePermission{
Name: string(val),
IsOss: true,
})
}
}
// Then cloud-only in order
for _, val := range extras.ResourceEnumCloudValues() {
if _, ok := ossResources[string(val)]; !ok {
perms = append(perms, &ResourcePermission{
Name: string(val),
IsCloud: true,
})
}
}
return perms
}
func (c Client) Create(ctx context.Context, params *CreateParams) error {
orgID, err := params.GetOrgID(ctx, c.ActiveConfig, c.OrganizationsApi)
if err != nil {
return err
}
bucketPerms := []struct {
action string
perms []string
}{
{action: ReadAction, perms: params.ReadBucketIds},
{action: WriteAction, perms: params.WriteBucketIds},
}
// Get the user ID because the command only takes a username, not ID
users, err := c.UsersApi.GetUsers(ctx).Name(params.User).Execute()
if err != nil || len(users.GetUsers()) == 0 {
return fmt.Errorf("could not find user with name %q: %w", params.User, err)
}
userID := users.GetUsers()[0].GetId()
var permissions []api.Permission
for _, bp := range bucketPerms {
for _, p := range bp.perms {
// verify the input ID
if err := influxid.Validate(p); err != nil {
return fmt.Errorf("invalid bucket ID '%s': %w (did you pass a bucket name instead of an ID?)", p, err)
}
newPerm := api.Permission{
Action: bp.action,
Resource: makePermResource("buckets", p, orgID),
}
permissions = append(permissions, newPerm)
}
}
for _, resourcePermission := range params.ResourcePermissions {
var actions []string
if resourcePermission.Read {
actions = append(actions, ReadAction)
}
if resourcePermission.Write {
actions = append(actions, WriteAction)
}
for _, action := range actions {
p := api.Permission{
Action: action,
Resource: makePermResource(resourcePermission.Name, "", orgID),
}
permissions = append(permissions, p)
}
}
if params.OperatorPermission {
if len(permissions) > 0 {
return fmt.Errorf("cannot compose other permissions with operator permissions")
}
resources, err := c.GetResources(ctx).Execute()
if err != nil {
return err
}
for _, r := range resources {
if r == string(extras.RESOURCEENUMOSS_INSTANCE) {
// We skip the instance type since it is only set on setup
continue
}
for _, action := range []string{ReadAction, WriteAction} {
permissions = append(permissions, api.Permission{
Action: action,
Resource: makePermResource(r, "", ""),
})
}
}
}
if params.AllAccess {
if len(permissions) > 0 {
return fmt.Errorf("cannot compose other permissions with all access permissions")
}
resources, err := c.GetResources(ctx).Execute()
if err != nil {
return err
}
for _, r := range resources {
if r == string(extras.RESOURCEENUMCLOUD_ORGS) {
// orgs are handled specifically - all access is for a single org, not global access to all orgs
permissions = append(permissions, api.Permission{
Action: ReadAction,
Resource: api.PermissionResource{
Type: r,
Id: &orgID,
},
})
} else if r == string(extras.RESOURCEENUMCLOUD_USERS) {
// users are handled specifically - all access is for a single user, not global to all users
for _, action := range []string{ReadAction, WriteAction} {
permissions = append(permissions, api.Permission{
Action: action,
Resource: api.PermissionResource{
Type: r,
Id: &userID,
},
})
}
} else if r == string(extras.RESOURCEENUMOSS_INSTANCE) {
// We skip the instance type since it is only set on setup
continue
} else {
for _, action := range []string{ReadAction, WriteAction} {
permissions = append(permissions, api.Permission{
Action: action,
Resource: makePermResource(r, "", orgID),
})
}
}
}
}
authReq := api.AuthorizationPostRequest{
OrgID: orgID,
UserID: &userID,
Permissions: permissions,
}
if params.Description != "" {
authReq.SetDescription(params.Description)
}
auth, err := c.PostAuthorizations(ctx).AuthorizationPostRequest(authReq).Execute()
if err != nil {
return fmt.Errorf("could not write auth with provided arguments: %w", err)
}
ps := make([]string, 0, len(auth.GetPermissions()))
for _, p := range auth.GetPermissions() {
ps = append(ps, p.String())
}
return c.printAuth(printParams{
token: &token{
ID: auth.GetId(),
Description: auth.GetDescription(),
Token: auth.GetToken(),
Status: auth.GetStatus(),
UserName: auth.GetUser(),
UserID: auth.GetUserID(),
Permissions: ps,
},
})
}
func (c Client) Remove(ctx context.Context, authID string) error {
// check if auth exists first for better error logging, and to
// acquire the auth that was deleted since the delete
// request does not return the Authorization object.
a, err := c.GetAuthorizationsID(ctx, authID).Execute()
if err != nil {
return fmt.Errorf("could not find auth with ID %q: %w", authID, err)
}
if err := c.DeleteAuthorizationsID(ctx, authID).Execute(); err != nil {
return fmt.Errorf("could not remove auth with ID %q: %w", authID, err)
}
ps := make([]string, 0, len(a.Permissions))
for _, p := range a.Permissions {
ps = append(ps, p.String())
}
return c.printAuth(printParams{
deleted: true,
token: &token{
ID: a.GetId(),
Description: a.GetDescription(),
Token: a.GetToken(),
Status: a.GetStatus(),
UserName: a.GetUser(),
UserID: a.GetUserID(),
Permissions: ps,
},
})
}
type ListParams struct {
clients.OrgParams
Id string
User string
UserID string
}
func (c Client) List(ctx context.Context, params *ListParams) error {
// If ID parameter is set, search by that over other filters
if params.Id != "" {
return c.findAuthorization(ctx, params.Id)
}
req := c.GetAuthorizations(ctx)
if params.User != "" {
req.User(params.User)
}
if params.UserID != "" {
req.UserID(params.UserID)
}
if params.OrgName != "" {
req.Org(params.OrgName)
}
if params.OrgID != "" {
req.OrgID(params.OrgID)
}
auths, err := req.Execute()
if err != nil {
return fmt.Errorf("could not find authorization with given parameters: %w", err)
}
var tokens []token
for _, a := range auths.GetAuthorizations() {
var ps []string
for _, p := range a.GetPermissions() {
ps = append(ps, p.String())
}
tokens = append(tokens, token{
ID: a.GetId(),
Description: a.GetDescription(),
Token: a.GetToken(),
Status: a.GetStatus(),
UserName: a.GetUser(),
UserID: a.GetUserID(),
Permissions: ps,
})
}
return c.printAuth(printParams{tokens: tokens})
}
func (c Client) findAuthorization(ctx context.Context, authID string) error {
a, err := c.GetAuthorizationsID(ctx, authID).Execute()
if err != nil {
return fmt.Errorf("could not find authorization with ID %q: %w", authID, err)
}
ps := make([]string, 0, len(a.GetPermissions()))
for _, p := range a.GetPermissions() {
ps = append(ps, p.String())
}
return c.printAuth(printParams{
token: &token{
ID: a.GetId(),
Description: a.GetDescription(),
Token: a.GetToken(),
Status: a.GetStatus(),
UserName: a.GetUser(),
UserID: a.GetUserID(),
Permissions: ps,
},
})
}
func (c Client) SetActive(ctx context.Context, authID string, active bool) error {
// check if auth exists first for better error logging
if _, err := c.GetAuthorizationsID(ctx, authID).Execute(); err != nil {
return fmt.Errorf("could not find auth with ID %q: %w", authID, err)
}
var status string
if active {
status = "active"
} else {
status = "inactive"
}
a, err := c.PatchAuthorizationsID(ctx, authID).
AuthorizationUpdateRequest(api.AuthorizationUpdateRequest{Status: &status}).
Execute()
if err != nil {
return fmt.Errorf("could not update status of auth with ID %q: %w", authID, err)
}
ps := make([]string, 0, len(a.GetPermissions()))
for _, p := range a.GetPermissions() {
ps = append(ps, p.String())
}
return c.printAuth(printParams{
token: &token{
ID: a.GetId(),
Description: a.GetDescription(),
Token: a.GetToken(),
Status: a.GetStatus(),
UserName: a.GetUser(),
UserID: a.GetUserID(),
Permissions: ps,
},
})
}
func (c Client) printAuth(opts printParams) error {
if c.PrintAsJSON {
var v interface{}
if opts.token != nil {
v = opts.token
} else {
v = opts.tokens
}
return c.PrintJSON(v)
}
headers := []string{
"ID",
"Description",
"Token",
"User Name",
"User ID",
"Permissions",
}
if opts.deleted {
headers = append(headers, "Deleted")
}
if opts.token != nil {
opts.tokens = append(opts.tokens, *opts.token)
}
var rows []map[string]interface{}
for _, t := range opts.tokens {
row := map[string]interface{}{
"ID": t.ID,
"Description": t.Description,
"Token": t.Token,
"User Name": t.UserName,
"User ID": t.UserID,
"Permissions": t.Permissions,
}
if opts.deleted {
row["Deleted"] = true
}
rows = append(rows, row)
}
return c.PrintTable(headers, rows...)
}
func makePermResource(permType string, id string, orgId string) api.PermissionResource {
if permType == "orgs" || permType == "users" {
orgId = ""
}
pr := api.PermissionResource{Type: permType}
if id != "" {
pr.Id = &id
}
if orgId != "" {
pr.OrgID = &orgId
}
return pr
}