diff --git a/app/descr/account.go b/app/descr/account.go index db4d59f..09f0d58 100644 --- a/app/descr/account.go +++ b/app/descr/account.go @@ -22,12 +22,12 @@ type Account struct { } type AccountShort struct { - ID string `json:"id"` - Username string `json:"username"` - Disabled bool `json:"disabled"` - CreatedAt string `json:"createdAt"` - UpdatedAt string `json:"updatedAt"` - CreatedBy string `json:"createdBy"` - UpdatedBy string `json:"updatedBy"` - Grants []GrantShort `json:"grants"` + ID string `json:"id"` + Username string `json:"username"` + Disabled bool `json:"disabled"` + CreatedAt string `json:"createdAt"` + UpdatedAt string `json:"updatedAt"` + CreatedBy string `json:"createdBy"` + UpdatedBy string `json:"updatedBy"` + Grants []Grant `json:"grants"` } diff --git a/app/descr/grant.go b/app/descr/grant.go index 1bf2bd0..d10dc5a 100644 --- a/app/descr/grant.go +++ b/app/descr/grant.go @@ -31,15 +31,6 @@ type Grant struct { UpdatedBy string `json:"updatedBy" db:"updated_by"` } -type GrantShort struct { - Right string `json:"right" db:"right"` - Pattern string `json:"pattern" db:"pattern"` - CreatedAt string `json:"createdAt" db:"created_at"` - UpdatedAt string `json:"updatedAt" db:"updated_at"` - CreatedBy string `json:"createdBy" db:"created_by"` - UpdatedBy string `json:"updatedBy" db:"updated_by"` -} - const ( // Accounts RightReadAccounts = "readAccounts" // GetAccount, ListAccounts diff --git a/app/handler/file.go b/app/handler/file.go index 6eaf1e4..126693c 100644 --- a/app/handler/file.go +++ b/app/handler/file.go @@ -41,7 +41,7 @@ func (hand *Handler) FileInfo(rctx *router.Context) { } // Execution of the operation ctx := rctx.GetContext() - code, res, err := hand.oper.FileInfo(ctx, params) + code, res, err := hand.oper.FileInfo(ctx, operatorID, params) if err != nil { hand.logg.Errorf("FileInfo error: %v", err) rctx.SetStatus(code) @@ -53,6 +53,11 @@ func (hand *Handler) FileInfo(rctx *router.Context) { rctx.SetHeader("Content-Size", res.ContentSize) rctx.SetHeader("Content-Digest", res.ContentDigest) + rctx.SetHeader("Content-CreatedAt", res.ContentCreatedAt) + rctx.SetHeader("Content-CreatedBy", res.ContentCreatedBy) + rctx.SetHeader("Content-UpdatedAt", res.ContentUpdatedAt) + rctx.SetHeader("Content-UpdatedBy", res.ContentUpdatedBy) + rctx.SetHeader("Content-Length", zeroContentLength) rctx.SetStatus(code) } @@ -83,7 +88,7 @@ func (hand *Handler) PutFile(rctx *router.Context) { } // Execution of the operation ctx := rctx.GetContext() - code, _, err := hand.oper.PutFile(ctx, params) + code, _, err := hand.oper.PutFile(ctx, operatorID, params) if err != nil { hand.logg.Errorf("PutFile error: %v", err) rctx.SetStatus(code) @@ -113,7 +118,7 @@ func (hand *Handler) GetFile(rctx *router.Context) { } // Execution of the operation ctx := rctx.GetContext() - code, res, err := hand.oper.GetFile(ctx, params) + code, res, err := hand.oper.GetFile(ctx, operatorID, params) if err != nil { hand.logg.Errorf("PutFile error: %v", err) rctx.SetStatus(code) @@ -124,6 +129,12 @@ func (hand *Handler) GetFile(rctx *router.Context) { rctx.SetHeader("Content-Size", res.ContentSize) rctx.SetHeader("Content-Digest", res.ContentDigest) rctx.SetHeader("Content-Length", res.ContentSize) + + rctx.SetHeader("Content-CreatedAt", res.ContentCreatedAt) + rctx.SetHeader("Content-CreatedBy", res.ContentCreatedBy) + rctx.SetHeader("Content-UpdatedAt", res.ContentUpdatedAt) + rctx.SetHeader("Content-UpdatedBy", res.ContentUpdatedBy) + rctx.SetStatus(code) if res.Source != nil { @@ -157,7 +168,7 @@ func (hand *Handler) DeleteFile(rctx *router.Context) { } // Execution of the operation ctx := rctx.GetContext() - code, _, err := hand.oper.DeleteFile(ctx, params) + code, _, err := hand.oper.DeleteFile(ctx, operatorID, params) if err != nil { hand.logg.Errorf("GetFile error: %v", err) } @@ -189,7 +200,7 @@ func (hand *Handler) ListFiles(rctx *router.Context) { } // Execution of the operation ctx := rctx.GetContext() - code, res, err := hand.oper.ListFiles(ctx, params) + code, res, err := hand.oper.ListFiles(ctx, operatorID, params) if err != nil { hand.logg.Errorf("ListFiles error: %v", err) rctx.SetStatus(code) diff --git a/app/handler/grant.go b/app/handler/grant.go index 44c8ac4..8e595fc 100644 --- a/app/handler/grant.go +++ b/app/handler/grant.go @@ -41,7 +41,7 @@ func (hand *Handler) CreateGrant(rctx *router.Context) { return } // Execution of the operation - res, err := hand.oper.CreateGrant(rctx.Ctx, params) + res, err := hand.oper.CreateGrant(rctx.Ctx, operatorID, params) if err != nil { hand.logg.Errorf("CreateGrant error: %v", err) hand.SendError(rctx, err) @@ -74,7 +74,7 @@ func (hand *Handler) GetGrant(rctx *router.Context) { return } // Execution of the operation - res, err := hand.oper.GetGrant(rctx.Ctx, params) + res, err := hand.oper.GetGrant(rctx.Ctx, operatorID, params) if err != nil { hand.logg.Errorf("CreateGrant error: %v", err) hand.SendError(rctx, err) @@ -107,7 +107,7 @@ func (hand *Handler) ListGrants(rctx *router.Context) { return } // Execution of the operation - res, err := hand.oper.ListGrants(rctx.Ctx, params) + res, err := hand.oper.ListGrants(rctx.Ctx, operatorID, params) if err != nil { hand.logg.Errorf("ListGrants error: %v", err) hand.SendError(rctx, err) @@ -140,7 +140,7 @@ func (hand *Handler) UpdateGrant(rctx *router.Context) { return } // Execution of the operation - res, err := hand.oper.UpdateGrant(rctx.Ctx, params) + res, err := hand.oper.UpdateGrant(rctx.Ctx, operatorID, params) if err != nil { hand.logg.Errorf("UpdateGrant error: %v", err) hand.SendError(rctx, err) @@ -173,7 +173,7 @@ func (hand *Handler) DeleteGrant(rctx *router.Context) { return } // Execution of the operation - res, err := hand.oper.DeleteGrant(rctx.Ctx, params) + res, err := hand.oper.DeleteGrant(rctx.Ctx, operatorID, params) if err != nil { hand.logg.Errorf("DeleteGrant error: %v", err) hand.SendError(rctx, err) diff --git a/app/maindb/grant.go b/app/maindb/grant.go index 44d24fe..86317cc 100644 --- a/app/maindb/grant.go +++ b/app/maindb/grant.go @@ -29,7 +29,7 @@ func (db *Database) InsertGrant(ctx context.Context, grant *descr.Grant) error { func (db *Database) UpdateGrantByID(ctx context.Context, grantID string, grant *descr.Grant) error { var err error - request := `UPDATE accounts SET pattern = $1, updated_at = $2, updated_by = $3 WHERE id = $4` + request := `UPDATE grants SET pattern = $1, updated_at = $2, updated_by = $3 WHERE id = $4` _, err = db.db.Exec(request, grant.Pattern, grant.UpdatedAt, grant.UpdatedBy, grantID) if err != nil { return err diff --git a/app/operator/account.go b/app/operator/account.go index 9dbbbbe..8f90fa0 100644 --- a/app/operator/account.go +++ b/app/operator/account.go @@ -60,6 +60,7 @@ func (oper *Operator) CreateAccount(ctx context.Context, operatorID string, para return res, err } +// GetAccount type GetAccountParams struct { Username string `json:"username"` AccountID string `json:"accountId"` @@ -114,14 +115,14 @@ func (oper *Operator) GetAccount(ctx context.Context, params *GetAccountParams) CreatedBy: accountDescr.CreatedBy, UpdatedBy: accountDescr.UpdatedBy, Disabled: accountDescr.Disabled, - Grants: make([]descr.GrantShort, 0), + Grants: make([]descr.Grant, 0), } grantDescrs, err := oper.mdb.ListGrantsByAccountID(ctx, accountDescr.ID) if err != nil { return res, err } for _, grantDescrs := range grantDescrs { - grantShorts := descr.GrantShort{ + grantShorts := descr.Grant{ Right: grantDescrs.Right, Pattern: grantDescrs.Pattern, CreatedAt: grantDescrs.CreatedAt, @@ -282,14 +283,14 @@ func (oper *Operator) ListAccounts(ctx context.Context, params *ListAccountsPara UpdatedAt: accountDescr.UpdatedAt, CreatedBy: accountDescr.CreatedBy, UpdatedBy: accountDescr.UpdatedBy, - Grants: make([]descr.GrantShort, 0), + Grants: make([]descr.Grant, 0), } grantDescrs, err := oper.mdb.ListGrantsByAccountID(ctx, accountDescr.ID) if err != nil { return res, err } for _, grantDescrs := range grantDescrs { - grantShorts := descr.GrantShort{ + grantShorts := descr.Grant{ Right: grantDescrs.Right, Pattern: grantDescrs.Pattern, CreatedAt: grantDescrs.CreatedAt, diff --git a/app/operator/file.go b/app/operator/file.go index d02ee2f..959d35b 100644 --- a/app/operator/file.go +++ b/app/operator/file.go @@ -35,6 +35,11 @@ type FileInfoResult struct { ContentType string ContentSize string ContentDigest string + + ContentCreatedAt string + ContentCreatedBy string + ContentUpdatedAt string + ContentUpdatedBy string } func cleanFilepath(filename string) (string, error) { @@ -42,7 +47,7 @@ func cleanFilepath(filename string) (string, error) { return filepath.Clean(filename), nil } -func (oper *Operator) FileInfo(ctx context.Context, param *FileInfoParams) (int, *FileInfoResult, error) { +func (oper *Operator) FileInfo(ctx context.Context, operID string, param *FileInfoParams) (int, *FileInfoResult, error) { var err error code := http.StatusOK res := &FileInfoResult{} @@ -71,6 +76,11 @@ func (oper *Operator) FileInfo(ctx context.Context, param *FileInfoParams) (int, ContentSize: strconv.FormatInt(fileDescr.Size, 10), ContentType: fileDescr.Type, ContentDigest: fileDescr.Checksum, + + ContentCreatedAt: fileDescr.CreatedAt, + ContentCreatedBy: fileDescr.CreatedBy, + ContentUpdatedAt: fileDescr.UpdatedAt, + ContentUpdatedBy: fileDescr.UpdatedBy, } return code, res, err } @@ -87,7 +97,7 @@ type PutFileResult struct{} const defaultContentType = "application/octet-stream" // TODO: checking catalog and file names conflict -func (oper *Operator) PutFile(ctx context.Context, param *PutFileParams) (int, *PutFileResult, error) { +func (oper *Operator) PutFile(ctx context.Context, operID string, param *PutFileParams) (int, *PutFileResult, error) { var err error res := &PutFileResult{} @@ -133,6 +143,7 @@ func (oper *Operator) PutFile(ctx context.Context, param *PutFileParams) (int, * fileDescr.Checksum = checksum fileDescr.UpdatedAt = now fileDescr.Type = contentType + fileDescr.UpdatedBy = operID err = oper.mdb.UpdateFileByID(ctx, fileDescr.ID, fileDescr) if err != nil { code := http.StatusInternalServerError @@ -148,6 +159,8 @@ func (oper *Operator) PutFile(ctx context.Context, param *PutFileParams) (int, * Checksum: checksum, CreatedAt: now, UpdatedAt: now, + CreatedBy: operID, + UpdatedBy: operID, } err = oper.mdb.InsertFile(ctx, fileDescr) if err != nil { @@ -174,9 +187,14 @@ type GetFileResult struct { ContentSize string ContentDigest string Source io.ReadCloser + + ContentCreatedAt string + ContentCreatedBy string + ContentUpdatedAt string + ContentUpdatedBy string } -func (oper *Operator) GetFile(ctx context.Context, param *GetFileParams) (int, *GetFileResult, error) { +func (oper *Operator) GetFile(ctx context.Context, operID string, param *GetFileParams) (int, *GetFileResult, error) { var err error res := &GetFileResult{} @@ -209,6 +227,11 @@ func (oper *Operator) GetFile(ctx context.Context, param *GetFileParams) (int, * ContentType: fileDescr.Type, ContentDigest: fileDescr.Checksum, Source: reader, + + ContentCreatedAt: fileDescr.CreatedAt, + ContentCreatedBy: fileDescr.CreatedBy, + ContentUpdatedAt: fileDescr.UpdatedAt, + ContentUpdatedBy: fileDescr.UpdatedBy, } code := http.StatusOK return code, res, err @@ -220,7 +243,7 @@ type DeleteFileParams struct { } type DeleteFileResult struct{} -func (oper *Operator) DeleteFile(ctx context.Context, param *DeleteFileParams) (int, *DeleteFileResult, error) { +func (oper *Operator) DeleteFile(ctx context.Context, operID string, param *DeleteFileParams) (int, *DeleteFileResult, error) { var err error res := &DeleteFileResult{} code := http.StatusOK @@ -262,7 +285,7 @@ type ListFilesResult struct { //Catalogs []string `json:"files,omitempty"` } -func (oper *Operator) ListFiles(ctx context.Context, param *ListFilesParams) (int, *ListFilesResult, error) { +func (oper *Operator) ListFiles(ctx context.Context, operID string, param *ListFilesParams) (int, *ListFilesResult, error) { var err error res := &ListFilesResult{ Files: make([]descr.File, 0), diff --git a/app/operator/grant.go b/app/operator/grant.go index a419049..7acaa4d 100644 --- a/app/operator/grant.go +++ b/app/operator/grant.go @@ -9,6 +9,7 @@ import ( "mstore/pkg/auxuuid" ) +// CreateGrant type CreateGrantParams struct { AccountID string `json:"accountID"` Right string `json:"operation"` @@ -18,7 +19,7 @@ type CreateGrantResult struct { GrantID string `json:"grantId"` } -func (oper *Operator) CreateGrant(ctx context.Context, params *CreateGrantParams) (*CreateGrantResult, error) { +func (oper *Operator) CreateGrant(ctx context.Context, operID string, params *CreateGrantParams) (*CreateGrantResult, error) { var err error res := &CreateGrantResult{} @@ -40,9 +41,10 @@ func (oper *Operator) CreateGrant(ctx context.Context, params *CreateGrantParams return res, err } if grantExists { - err := fmt.Errorf("Grant with this parapeters already exists") + err := fmt.Errorf("Grant with this right already exists") return res, err } + oper.logg.Debugf("Call CreateGrant") now := auxtool.TimeNow() grantDescr := &descr.Grant{ ID: auxuuid.NewUUID(), @@ -51,6 +53,8 @@ func (oper *Operator) CreateGrant(ctx context.Context, params *CreateGrantParams Pattern: params.Pattern, CreatedAt: now, UpdatedAt: now, + CreatedBy: operID, + UpdatedBy: operID, } err = oper.mdb.InsertGrant(ctx, grantDescr) if err != nil { @@ -60,16 +64,21 @@ func (oper *Operator) CreateGrant(ctx context.Context, params *CreateGrantParams return res, err } +// UpdateGrant type UpdateGrantParams struct { GrantID string NewPattern string } type UpdateGrantResult struct{} -func (oper *Operator) UpdateGrant(ctx context.Context, params *UpdateGrantParams) (*UpdateGrantResult, error) { +func (oper *Operator) UpdateGrant(ctx context.Context, operID string, params *UpdateGrantParams) (*UpdateGrantResult, error) { var err error res := &UpdateGrantResult{} + if params.NewPattern == "" { + err := fmt.Errorf("Empty newPattern parameter") + return res, err + } if params.GrantID == "" { err := fmt.Errorf("Empty grantId parameter") return res, err @@ -88,6 +97,7 @@ func (oper *Operator) UpdateGrant(ctx context.Context, params *UpdateGrantParams now := auxtool.TimeNow() if params.NewPattern != "" { grantDescr.UpdatedAt = now + grantDescr.UpdatedBy = operID grantDescr.Pattern = params.NewPattern } err = oper.mdb.UpdateGrantByID(ctx, grantDescr.ID, grantDescr) @@ -97,12 +107,13 @@ func (oper *Operator) UpdateGrant(ctx context.Context, params *UpdateGrantParams return res, err } +// DeleteGrant type DeleteGrantParams struct { GrantID string `json:"grantId"` } type DeleteGrantResult struct{} -func (oper *Operator) DeleteGrant(ctx context.Context, params *DeleteGrantParams) (*DeleteGrantResult, error) { +func (oper *Operator) DeleteGrant(ctx context.Context, operID string, params *DeleteGrantParams) (*DeleteGrantResult, error) { var err error res := &DeleteGrantResult{} @@ -129,20 +140,47 @@ func (oper *Operator) DeleteGrant(ctx context.Context, params *DeleteGrantParams return res, err } +// ListGrants type ListGrantsParams struct { - AccountID string `json:"accountId"` + Username string + AccountID string } type ListGrantsResult struct { Grants []descr.Grant `json:"grants"` } -func (oper *Operator) ListGrants(ctx context.Context, params *ListGrantsParams) (*ListGrantsResult, error) { +func (oper *Operator) ListGrants(ctx context.Context, operID string, params *ListGrantsParams) (*ListGrantsResult, error) { var err error res := &ListGrantsResult{ Grants: make([]descr.Grant, 0), } - - grantDescrs, err := oper.mdb.ListGrantsByAccountID(ctx, params.AccountID) + var accountDescr *descr.Account + var accountExists bool + switch { + case params.AccountID != "": + accountExists, accountDescr, err = oper.mdb.GetAccountByID(ctx, params.AccountID) + if err != nil { + return res, err + } + if !accountExists { + err := fmt.Errorf("Account with ID %s dont exists", params.AccountID) + return res, err + } + case params.Username != "": + accountExists, accountDescr, err = oper.mdb.GetAccountByUsername(ctx, params.Username) + if err != nil { + return res, err + } + if !accountExists { + err := fmt.Errorf("Account with name %s dont exists", params.Username) + return res, err + } + default: + err := fmt.Errorf("Empty username and accountId parameter") + return res, err + } + accountID := accountDescr.ID + grantDescrs, err := oper.mdb.ListGrantsByAccountID(ctx, accountID) if err != nil { return res, err } @@ -150,6 +188,7 @@ func (oper *Operator) ListGrants(ctx context.Context, params *ListGrantsParams) return res, err } +// Get Grants type GetGrantParams struct { GrantID string `json:"grantId"` } @@ -157,7 +196,7 @@ type GetGrantResult struct { Grant *descr.Grant `json:"grant"` } -func (oper *Operator) GetGrant(ctx context.Context, params *GetGrantParams) (*GetGrantResult, error) { +func (oper *Operator) GetGrant(ctx context.Context, operID string, params *GetGrantParams) (*GetGrantResult, error) { var err error res := &GetGrantResult{} diff --git a/cmd/mstorectl/accountcmd.go b/cmd/mstorectl/accountcmd.go index 76ddf7a..34bb04b 100644 --- a/cmd/mstorectl/accountcmd.go +++ b/cmd/mstorectl/accountcmd.go @@ -73,7 +73,7 @@ func (util *AccountUtil) CreateAccountCmds() *cobra.Command { // UpdateAccount var updateAccountCmd = &cobra.Command{ Use: "info", - Short: "Show file information", + Short: "Update account parameters", Run: util.UpdateAccount, } updateAccountCmd.Flags().StringVarP(&util.updateAccountParams.Username, "username", "u", "", "Username") @@ -93,7 +93,7 @@ func (util *AccountUtil) CreateAccountCmds() *cobra.Command { // DeleteAccount var deleteAccountCmd = &cobra.Command{ Use: "delete", - Short: "Delete file in storage", + Short: "Delete account", Run: util.DeleteAccount, } deleteAccountCmd.Flags().StringVarP(&util.deleteAccountParams.Username, "username", "u", "", "Username") @@ -121,7 +121,7 @@ func (util *AccountUtil) CreateAccountsCmds() *cobra.Command { // ListAccounts var listAccountsCmd = &cobra.Command{ Use: "list", - Short: "list user accounts", + Short: "list accounts", Run: util.ListAccounts, } listAccountsCmd.Flags().StringVarP(&util.listAccountsParams.Username, "username", "u", "", "Username") diff --git a/cmd/mstorectl/grantcmd.go b/cmd/mstorectl/grantcmd.go new file mode 100644 index 0000000..f8c0962 --- /dev/null +++ b/cmd/mstorectl/grantcmd.go @@ -0,0 +1,332 @@ +/* + * Copyright 2026 Oleg Borodin + * + * This work is published and licensed under a Creative Commons + * Attribution-NonCommercial-NoDerivatives 4.0 International License. + * + * Distribution of this work is permitted, but commercial use and + * modifications are strictly prohibited. + */ +package main + +import ( + "context" + "regexp" + "strings" + "time" + + "github.com/spf13/cobra" + + "mstore/app/descr" + "mstore/pkg/client" +) + +func (util *GrantUtil) CreateGrantCmds() *cobra.Command { + var subCmd = &cobra.Command{ + Use: "account", + Short: "Grant operation", + } + const defaultTimeout uint64 = 10 + + // CreateGrant + var createGrantCmd = &cobra.Command{ + Use: "create", + Short: "Create grant", + Run: util.CreateGrant, + } + createGrantCmd.Flags().StringVarP(&util.createGrantParams.Username, "username", "u", "", "Username") + createGrantCmd.Flags().StringVarP(&util.createGrantParams.Password, "password", "p", "", "Password") + createGrantCmd.Flags().StringVarP(&util.createGrantParams.Hostname, "host", "x", defaultHostname, "Hostname") + createGrantCmd.Flags().Uint64VarP(&util.createGrantParams.Timeout, "timeout", "t", defaultTimeout, "Operation timeout") + + createGrantCmd.Flags().StringVarP(&util.createGrantParams.Right, "newuser", "U", "", "New account username") + createGrantCmd.Flags().StringVarP(&util.createGrantParams.Pattern, "newpass", "P", "", "New account password") + createGrantCmd.MarkFlagRequired("host") + createGrantCmd.MarkFlagsRequiredTogether("newuser", "newpass") + createGrantCmd.MarkFlagsRequiredTogether("username", "password") + + subCmd.AddCommand(createGrantCmd) + + // GetGrant + var getGrantCmd = &cobra.Command{ + Use: "get", + Short: "Get detail grant info", + Run: util.GetGrant, + } + getGrantCmd.Flags().StringVarP(&util.getGrantParams.Hostname, "host", "x", defaultHostname, "Hostname") + getGrantCmd.Flags().StringVarP(&util.getGrantParams.Username, "username", "u", "", "Username") + getGrantCmd.Flags().StringVarP(&util.getGrantParams.Password, "password", "p", "", "Password") + getGrantCmd.Flags().Uint64VarP(&util.getGrantParams.Timeout, "timeout", "t", defaultTimeout, "Operation timeout") + getGrantCmd.Flags().StringVarP(&util.getGrantParams.GrantID, "id", "I", "", "Grant ID or name") + getGrantCmd.Flags().StringVarP(&util.getGrantParams.GrantID, "name", "n", "", "Grant ID or name") + + getGrantCmd.MarkFlagRequired("host") + getGrantCmd.MarkFlagsOneRequired("id", "name") + getGrantCmd.MarkFlagsRequiredTogether("username", "password") + + subCmd.AddCommand(getGrantCmd) + + // UpdateGrant + var updateGrantCmd = &cobra.Command{ + Use: "update", + Short: "Update grant parameters", + Run: util.UpdateGrant, + } + updateGrantCmd.Flags().StringVarP(&util.updateGrantParams.Username, "username", "u", "", "Username") + updateGrantCmd.Flags().StringVarP(&util.updateGrantParams.Password, "password", "p", "", "Password") + updateGrantCmd.Flags().StringVarP(&util.updateGrantParams.Hostname, "host", "x", "", "File path") + updateGrantCmd.Flags().Uint64VarP(&util.updateGrantParams.Timeout, "timeout", "t", defaultTimeout, "Operation timeout") + + updateGrantCmd.Flags().StringVarP(&util.updateGrantParams.GrantID, "id", "I", "", "Grant ID or username") + updateGrantCmd.Flags().StringVarP(&util.updateGrantParams.GrantID, "name", "n", "", "Grant ID or username") + updateGrantCmd.Flags().StringVarP(&util.updateGrantParams.Right, "newuser", "U", "", "New username") + updateGrantCmd.Flags().StringVarP(&util.updateGrantParams.Pattern, "pass", "P", "", "New password") + updateGrantCmd.MarkFlagRequired("host") + updateGrantCmd.MarkFlagsOneRequired("id", "name") + + subCmd.AddCommand(updateGrantCmd) + + // DeleteGrant + var deleteGrantCmd = &cobra.Command{ + Use: "delete", + Short: "Delete grant", + Run: util.DeleteGrant, + } + deleteGrantCmd.Flags().StringVarP(&util.deleteGrantParams.Username, "username", "u", "", "Username") + deleteGrantCmd.Flags().StringVarP(&util.deleteGrantParams.Password, "password", "p", "", "Password") + deleteGrantCmd.Flags().StringVarP(&util.deleteGrantParams.Hostname, "host", "x", defaultHostname, "Hostname") + deleteGrantCmd.Flags().Uint64VarP(&util.deleteGrantParams.Timeout, "timeout", "t", defaultTimeout, "Operation timeout") + + deleteGrantCmd.Flags().StringVarP(&util.deleteGrantParams.GrantID, "id", "I", "", "Grant ID") + deleteGrantCmd.MarkFlagRequired("host") + deleteGrantCmd.MarkFlagsRequiredTogether("username", "password") + + subCmd.AddCommand(deleteGrantCmd) + + return subCmd +} + +func (util *GrantUtil) CreateGrantsCmds() *cobra.Command { + var subCmd = &cobra.Command{ + Use: "grans", + Short: "Grants operation", + } + const defaultTimeout uint64 = 10 + // ListGrants + var listGrantsCmd = &cobra.Command{ + Use: "list", + Short: "list user grants", + Run: util.ListGrants, + } + listGrantsCmd.Flags().StringVarP(&util.listGrantsParams.Username, "username", "u", "", "Username") + listGrantsCmd.Flags().StringVarP(&util.listGrantsParams.Password, "password", "p", "", "Password") + listGrantsCmd.Flags().StringVarP(&util.listGrantsParams.Hostname, "host", "x", defaultHostname, "Hostname") + listGrantsCmd.Flags().Uint64VarP(&util.listGrantsParams.Timeout, "timeout", "t", defaultTimeout, "Operation timeout") + listGrantsCmd.Flags().BoolVarP(&util.listGrantsParams.Detail, "detail", "d", false, "Show detail information") + listGrantsCmd.Flags().StringVarP(&util.listGrantsParams.AccountID, "id", "I", "", "User account ID") + listGrantsCmd.Flags().StringVarP(&util.listGrantsParams.AccountID, "name", "n", "", "User account ID") + + listGrantsCmd.MarkFlagRequired("host") + listGrantsCmd.MarkFlagsOneRequired("id", "name") + + subCmd.AddCommand(listGrantsCmd) + return subCmd +} + +type GrantUtil struct { + createGrantParams CreateGrantParams + updateGrantParams UpdateGrantParams + getGrantParams GetGrantParams + deleteGrantParams DeleteGrantParams + listGrantsParams ListGrantsParams +} + +// CreateGrant +type CreateGrantParams struct { + Username string + Password string + Hostname string + AccountID string + Right string + Pattern string + Timeout uint64 +} +type CreateGrantResult struct { + GrantID string `json:"grantId"` +} + +func (util *GrantUtil) CreateGrant(cmd *cobra.Command, args []string) { + res, err := util.createGrant(&util.createGrantParams) + printResponse(res, err) +} + +func (util *GrantUtil) createGrant(params *CreateGrantParams) (*CreateGrantResult, error) { + var err error + res := &CreateGrantResult{} + params.Hostname, err = packUserinfo(params.Hostname, params.Right, params.Pattern) + if err != nil { + return res, err + } + timeout := time.Duration(params.Timeout) * time.Second + ctx, _ := context.WithTimeout(context.Background(), timeout) + accountID, err := client.NewClient().CreateGrant(ctx, params.Hostname, params.AccountID, params.Right, params.Pattern) + if err != nil { + return res, err + } + res.GrantID = accountID + return res, err +} + +// UpdateGrant +type UpdateGrantParams struct { + Hostname string + Username string + Password string + Timeout uint64 + GrantID string + Right string + Pattern string +} +type UpdateGrantResult struct{} + +func (util *GrantUtil) UpdateGrant(cmd *cobra.Command, args []string) { + res, err := util.updateGrant(&util.updateGrantParams) + printResponse(res, err) +} + +func (util *GrantUtil) updateGrant(params *UpdateGrantParams) (*UpdateGrantResult, error) { + var err error + res := &UpdateGrantResult{} + params.Hostname, err = packUserinfo(params.Hostname, params.Username, params.Password) + if err != nil { + return res, err + } + timeout := time.Duration(params.Timeout) * time.Second + ctx, _ := context.WithTimeout(context.Background(), timeout) + id := strings.ToLower(params.GrantID) + err = client.NewClient().UpdateGrant(ctx, params.Hostname, id, params.Pattern) + if err != nil { + return res, err + } + return res, err +} + +// GetGrant +type GetGrantParams struct { + Hostname string + Username string + Password string + Timeout uint64 + GrantID string +} + +type GetGrantResult struct { + Grant *descr.Grant `json:"grant,omitempty"` +} + +func (util *GrantUtil) GetGrant(cmd *cobra.Command, args []string) { + res, err := util.getGrant(&util.getGrantParams) + printResponse(res, err) +} + +func (util *GrantUtil) getGrant(params *GetGrantParams) (*GetGrantResult, error) { + var err error + res := &GetGrantResult{} + params.Hostname, err = packUserinfo(params.Hostname, params.Username, params.Password) + if err != nil { + return res, err + } + timeout := time.Duration(params.Timeout) * time.Second + ctx, _ := context.WithTimeout(context.Background(), timeout) + opRes := &descr.Grant{} + + id := strings.ToLower(params.GrantID) + opRes, err = client.NewClient().GetGrant(ctx, params.Hostname, id) + if err != nil { + return res, err + } + res.Grant = opRes + return res, err +} + +// DeleteGrant +type DeleteGrantParams struct { + Hostname string + Username string + Password string + GrantID string + Timeout uint64 +} + +type DeleteGrantResult struct{} + +func (util *GrantUtil) DeleteGrant(cmd *cobra.Command, args []string) { + res, err := util.deleteGrant(&util.deleteGrantParams) + printResponse(res, err) +} +func (util *GrantUtil) deleteGrant(params *DeleteGrantParams) (*DeleteGrantResult, error) { + var err error + res := &DeleteGrantResult{} + params.Hostname, err = packUserinfo(params.Hostname, params.Username, params.Password) + if err != nil { + return res, err + } + timeout := time.Duration(params.Timeout) * time.Second + ctx, _ := context.WithTimeout(context.Background(), timeout) + id := strings.ToLower(params.GrantID) + err = client.NewClient().DeleteGrant(ctx, params.Hostname, id) + if err != nil { + return res, err + } + return res, err +} + +// ListGrants +type ListGrantsParams struct { + Hostname string + Username string + Password string + Timeout uint64 + Detail bool + AccountID string +} + +type ListGrantsResult struct { + Grants []descr.Grant `json:"grants,omitempty"` + Rights []string `json:"rights,omitempty"` +} + +func (util *GrantUtil) ListGrants(cmd *cobra.Command, args []string) { + res, err := util.listGrants(&util.listGrantsParams) + printResponse(res, err) +} +func (util *GrantUtil) listGrants(params *ListGrantsParams) (*ListGrantsResult, error) { + var err error + res := &ListGrantsResult{} + params.Hostname, err = packUserinfo(params.Hostname, params.Username, params.Password) + if err != nil { + return res, err + } + timeout := time.Duration(params.Timeout) * time.Second + ctx, _ := context.WithTimeout(context.Background(), timeout) + grants := make([]descr.Grant, 0) + re := regexp.MustCompile(uuidRegex) + id := strings.ToLower(params.AccountID) + if re.MatchString(id) { + grants, err = client.NewClient().ListGrantsByAccountID(ctx, params.Hostname, params.AccountID) + } else { + grants, err = client.NewClient().ListGrantsByUsername(ctx, params.Hostname, params.AccountID) + } + if err != nil { + return res, err + } + if params.Detail { + res.Grants = grants + } else { + res.Rights = make([]string, 0) + for _, item := range grants { + res.Rights = append(res.Rights, item.Right) + } + } + return res, err +} diff --git a/pkg/client/grant.go b/pkg/client/grant.go index bca007c..5afebc0 100644 --- a/pkg/client/grant.go +++ b/pkg/client/grant.go @@ -14,16 +14,18 @@ import ( "encoding/json" "fmt" + "mstore/app/descr" "mstore/app/handler" "mstore/app/operator" ) -func (cli *Client) CreateGrant(ctx context.Context, hosturi, accountID, right, pattern string) error { +func (cli *Client) CreateGrant(ctx context.Context, hosturi, accountID, right, pattern string) (string, error) { var err error + var res string apiuri, err := setApiPath(hosturi, "/v3/api/grant/create") if err != nil { - return err + return res, err } operParams := operator.CreateGrantParams{ AccountID: accountID, @@ -32,54 +34,57 @@ func (cli *Client) CreateGrant(ctx context.Context, hosturi, accountID, right, p } paramsJson, err := json.Marshal(operParams) if err != nil { - return err + return res, err } respBytes, err := doHTTPCall(ctx, apiuri, paramsJson) if err != nil { - return err + return res, err } operRes := handler.NewResponse[operator.CreateGrantResult]() err = json.Unmarshal(respBytes, operRes) if err != nil { - return err + return res, err } - if !operRes.Error { + if operRes.Error { err = fmt.Errorf("%s", operRes.Message) - return err + return res, err } - return err + res = operRes.Result.GrantID + return res, err } -func (cli *Client) GetGrant(ctx context.Context, hosturi, id, username string) error { +func (cli *Client) GetGrant(ctx context.Context, hosturi, id string) (*descr.Grant, error) { var err error + res := &descr.Grant{} apipath, err := setApiPath(hosturi, "/v3/api/grant/get") if err != nil { - return err + return res, err } operParams := operator.GetGrantParams{ GrantID: id, } paramsJson, err := json.Marshal(operParams) if err != nil { - return err + return res, err } respBytes, err := doHTTPCall(ctx, apipath, paramsJson) if err != nil { - return err + return res, err } operRes := handler.NewResponse[operator.GetGrantResult]() err = json.Unmarshal(respBytes, operRes) if err != nil { - return err + return res, err } - if !operRes.Error { + if operRes.Error { err = fmt.Errorf("%s", operRes.Message) - return err + return res, err } - return err + res = operRes.Result.Grant + return res, err } func (cli *Client) UpdateGrant(ctx context.Context, hosturi, grantID, newPattern string) error { @@ -106,7 +111,7 @@ func (cli *Client) UpdateGrant(ctx context.Context, hosturi, grantID, newPattern if err != nil { return err } - if !operRes.Error { + if operRes.Error { err = fmt.Errorf("%s", operRes.Message) return err } @@ -137,9 +142,75 @@ func (cli *Client) DeleteGrant(ctx context.Context, hosturi, grantID string) err if err != nil { return err } - if !operRes.Error { + if operRes.Error { err = fmt.Errorf("%s", operRes.Message) return err } return err } + +func (cli *Client) ListGrantsByAccountID(ctx context.Context, hosturi, accountID string) ([]descr.Grant, error) { + var err error + res := make([]descr.Grant, 0) + + apipath, err := setApiPath(hosturi, "/v3/api/grant/delete") + if err != nil { + return res, err + } + operParams := operator.ListGrantsParams{ + AccountID: accountID, + } + paramsJson, err := json.Marshal(operParams) + if err != nil { + return res, err + } + respBytes, err := doHTTPCall(ctx, apipath, paramsJson) + if err != nil { + return res, err + } + + operRes := handler.NewResponse[operator.ListGrantsResult]() + err = json.Unmarshal(respBytes, operRes) + if err != nil { + return res, err + } + if operRes.Error { + err = fmt.Errorf("%s", operRes.Message) + return res, err + } + res = operRes.Result.Grants + return res, err +} + +func (cli *Client) ListGrantsByUsername(ctx context.Context, hosturi, username string) ([]descr.Grant, error) { + var err error + res := make([]descr.Grant, 0) + + apipath, err := setApiPath(hosturi, "/v3/api/grant/delete") + if err != nil { + return res, err + } + operParams := operator.ListGrantsParams{ + Username: username, + } + paramsJson, err := json.Marshal(operParams) + if err != nil { + return res, err + } + respBytes, err := doHTTPCall(ctx, apipath, paramsJson) + if err != nil { + return res, err + } + + operRes := handler.NewResponse[operator.ListGrantsResult]() + err = json.Unmarshal(respBytes, operRes) + if err != nil { + return res, err + } + if operRes.Error { + err = fmt.Errorf("%s", operRes.Message) + return res, err + } + res = operRes.Result.Grants + return res, err +} diff --git a/test/account_test.go b/test/account_test.go index fae147e..6807d44 100644 --- a/test/account_test.go +++ b/test/account_test.go @@ -16,6 +16,7 @@ import ( "testing" "time" + "mstore/app/descr" "mstore/app/server" "mstore/pkg/client" @@ -87,6 +88,90 @@ func TestAccountLife(t *testing.T) { accountID, err = cli.CreateAccount(ctx, srvaddr, username, password) require.NoError(t, err) } + var grantID string + { + // CreateGrant + fmt.Printf("=== CreateGrant ===\n") + cli := client.NewClient() + ctx := context.Background() + ctx, _ = context.WithTimeout(ctx, 1*time.Second) + + grantID, err = cli.CreateGrant(ctx, srvaddr, accountID, descr.RightReadAccounts, "*") + require.NoError(t, err) + require.Equal(t, len(grantID), 36) + } + { + // UpdateGrant + fmt.Printf("=== UpdateGrant ===\n") + cli := client.NewClient() + ctx := context.Background() + ctx, _ = context.WithTimeout(ctx, 1*time.Second) + + err := cli.UpdateGrant(ctx, srvaddr, grantID, "**") + require.NoError(t, err) + } + { + // CreateGrant + fmt.Printf("=== CreateGrant ===\n") + cli := client.NewClient() + ctx := context.Background() + ctx, _ = context.WithTimeout(ctx, 1*time.Second) + + grantID, err = cli.CreateGrant(ctx, srvaddr, accountID, descr.RightWriteAccounts, "*") + require.NoError(t, err) + require.Equal(t, len(grantID), 36) + fmt.Printf("grantID: %s\n", grantID) + } + { + // GetGrant + fmt.Printf("=== GetGrant ===\n") + cli := client.NewClient() + ctx := context.Background() + ctx, _ = context.WithTimeout(ctx, 1*time.Second) + + grantDescr, err := cli.GetGrant(ctx, srvaddr, grantID) + require.NoError(t, err) + require.Equal(t, len(grantID), 36) + fmt.Printf("grantID: %s\n", grantID) + grantYAML, err := yaml.Marshal(grantDescr) + require.NoError(t, err) + fmt.Printf("account:\n%s\n", string(grantYAML)) + } + { + // UpdateGrant + fmt.Printf("=== UpdateGrant ===\n") + cli := client.NewClient() + ctx := context.Background() + ctx, _ = context.WithTimeout(ctx, 1*time.Second) + + err := cli.UpdateGrant(ctx, srvaddr, grantID, "**") + require.NoError(t, err) + } + { + // GetGrant + fmt.Printf("=== GetGrant ===\n") + cli := client.NewClient() + ctx := context.Background() + ctx, _ = context.WithTimeout(ctx, 1*time.Second) + + grantDescr, err := cli.GetGrant(ctx, srvaddr, grantID) + require.NoError(t, err) + require.Equal(t, len(grantID), 36) + fmt.Printf("grantID: %s\n", grantID) + grantYAML, err := yaml.Marshal(grantDescr) + require.NoError(t, err) + fmt.Printf("account:\n%s\n", string(grantYAML)) + } + { + // DeleteGrant + fmt.Printf("=== DeleteGrant ===\n") + cli := client.NewClient() + ctx := context.Background() + ctx, _ = context.WithTimeout(ctx, 1*time.Second) + + err := cli.DeleteGrant(ctx, srvaddr, grantID) + require.NoError(t, err) + } { // GetAccount fmt.Printf("=== GetAccount ===\n") @@ -97,6 +182,7 @@ func TestAccountLife(t *testing.T) { accountDescr, err := cli.GetAccountByID(ctx, srvaddr, accountID) require.NoError(t, err) accountYAML, err := yaml.Marshal(accountDescr) + require.NoError(t, err) fmt.Printf("account:\n%s\n", string(accountYAML)) } { @@ -109,7 +195,11 @@ func TestAccountLife(t *testing.T) { accountDescrs, err := cli.ListAccounts(ctx, srvaddr+"/") require.NoError(t, err) require.NotZero(t, len(accountDescrs)) - accountsYAML, err := yaml.Marshal(accountDescrs) + nameList := make([]string, 0) + for _, item := range accountDescrs { + nameList = append(nameList, item.Username) + } + accountsYAML, err := yaml.Marshal(nameList) fmt.Printf("accounts:\n%s\n", string(accountsYAML)) } diff --git a/test/image_test.go b/test/image_test.go index 8a6ee82..e4c8ea8 100644 --- a/test/image_test.go +++ b/test/image_test.go @@ -23,7 +23,7 @@ import ( "sigs.k8s.io/yaml" ) -func xxxTestImageLife(t *testing.T) { +func TestImageLife(t *testing.T) { var srvport int64 = 10250 srvdir := t.TempDir() srvaddr := fmt.Sprintf("127.0.0.1:%d", srvport) diff --git a/pkg/client/test-oci.img b/test/test-oci.img similarity index 100% rename from pkg/client/test-oci.img rename to test/test-oci.img