From b279687623865eaed0d43053f7f07a4a79271e66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9E=D0=BB=D0=B5=D0=B3=20=D0=91=D0=BE=D1=80=D0=BE=D0=B4?= =?UTF-8?q?=D0=B8=D0=BD?= Date: Thu, 12 Feb 2026 12:00:17 +0200 Subject: [PATCH] working commit --- app/descr/account.go | 43 +++---- app/descr/grant.go | 36 ++++++ app/handler/authmw.go | 28 ++--- app/handler/grant.go | 101 +++++++++++++++ app/handler/service.go | 26 ++++ app/maindb/account.go | 14 +-- app/maindb/file.go | 22 +--- app/maindb/file_test.go | 8 +- app/maindb/grant.go | 56 +++++++-- app/maindb/grant_test.go | 60 +++++++++ app/maindb/schema.go | 14 ++- app/operator/account.go | 52 ++++---- app/operator/file.go | 13 +- app/operator/grant.go | 182 +++++++++++++++++++++++++++ {test => attic/test}/account_test.go | 9 ++ {test => attic/test}/file_test.go | 10 +- pkg/client/client.go | 66 ---------- pkg/client/filelife_test.go | 10 +- pkg/client/imagelife_test.go | 2 +- pkg/client/service.go | 79 ++++++++++++ 20 files changed, 639 insertions(+), 192 deletions(-) create mode 100644 app/descr/grant.go create mode 100644 app/handler/grant.go create mode 100644 app/maindb/grant_test.go create mode 100644 app/operator/grant.go rename {test => attic/test}/account_test.go (98%) rename {test => attic/test}/file_test.go (98%) create mode 100644 pkg/client/service.go diff --git a/app/descr/account.go b/app/descr/account.go index 00df3b3..8cbbc00 100644 --- a/app/descr/account.go +++ b/app/descr/account.go @@ -10,38 +10,23 @@ package descr -const ( - GrantModifyUsers = "modifyUsers" - GrantModifyDatabase = "modifyDatabase" - GrantModifyArtefact = "modifyArtefact" - GrantModifyDummy = "modifyDummy" -) - type Account struct { - ID string `json:"id" db:"id"` - Username string `json:"username" db:"username"` - Passhash string `json:"passhash" db:"passhash"` - Disabled bool `json:"disabled" db:"disabled"` - CreatedAt string `json:"createdAt" db:"created_at"` - UpdatedAt string `json:"updatedAt,omitempty" db:"updated_at"` -} - -type Grant struct { ID string `json:"id" db:"id"` - AccountID string `json:"accountID" db:"account_id"` - Operation string `json:"operation" db:"operation"` + Username string `json:"username" db:"username"` + Passhash string `json:"passhash" db:"passhash"` + Disabled bool `json:"disabled" db:"disabled"` 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"` } -type GrantShortDescr struct { - Operation string `json:"operation"` - CreatedAt string `json:"createdAt"` -} - -type AccountShortDescr struct { - Username string `json:"username"` - Disabled bool `json:"disabled"` - CreatedAt string `json:"createdAt"` - UpdatedAt string `json:"updatedAt,omitempty"` - Grants []GrantShortDescr `json:"grants"` +type AccountShort struct { + 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"` } diff --git a/app/descr/grant.go b/app/descr/grant.go new file mode 100644 index 0000000..9e9d4d1 --- /dev/null +++ b/app/descr/grant.go @@ -0,0 +1,36 @@ +/* + * 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 descr + +type Grant struct { + ID string `json:"id" db:"id"` + AccountID string `json:"accountID" db:"account_id"` + Operation string `json:"operation" db:"operation"` + 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"` +} + +type GrantShort struct { + Operation string `json:"operation" db:"operation"` + 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 ( + operFileinfo = "opFileinfo" + operPutFile = "opPutFile" +) diff --git a/app/handler/authmw.go b/app/handler/authmw.go index 11e33b3..68f10b1 100644 --- a/app/handler/authmw.go +++ b/app/handler/authmw.go @@ -1,10 +1,6 @@ package handler import ( - //"encoding/base64" - //"fmt" - //"strings" - "mstore/app/router" "mstore/pkg/auxhttp" ) @@ -18,12 +14,14 @@ func (hand *Handler) AuthMiddleware(next router.Handler) router.Handler { var handlerFunc router.HandlerFunc handlerFunc = func(rctx *router.Context) { - authSuccessful, authError := hand.CheckAccess(rctx) - if authSuccessful && authError == nil { + hand.logg.Debugf("Call authorization middleware") + success, username, err := hand.CheckAccess(rctx) + if success && err == nil { rctx.SetBool(authTag, true) + rctx.SetString(userTag, username) } - if authError != nil { - hand.logg.Errorf("Authorization middleware error: %v", authError) + if err != nil { + hand.logg.Errorf("Authorization middleware error: %v", err) } next.ServeHTTP(rctx) @@ -31,21 +29,23 @@ func (hand *Handler) AuthMiddleware(next router.Handler) router.Handler { return handlerFunc } -func (hand *Handler) CheckAccess(rctx *router.Context) (bool, error) { +func (hand *Handler) CheckAccess(rctx *router.Context) (bool, string, error) { var err error - var res bool + var success bool + var username string + var password string authHeader := rctx.GetHeader("Authorization") if authHeader != "" { hand.logg.Debugf("Authorization header is %s", authHeader) - username, password, err := auxhttp.ParseBasicAuth(authHeader) + username, password, err = auxhttp.ParseBasicAuth(authHeader) if err != nil { - return res, err + return success, username, err } hand.logg.Debugf("Authorization username is %s:%s", username, password) } - res = true + success = true - return res, err + return success, username, err } diff --git a/app/handler/grant.go b/app/handler/grant.go new file mode 100644 index 0000000..0cefefb --- /dev/null +++ b/app/handler/grant.go @@ -0,0 +1,101 @@ +package handler + +import ( + "mstore/app/operator" + "mstore/app/router" +) + +// POST /v3/grant/create 200 200 +func (hand *Handler) CreateGrant(rctx *router.Context) { + var err error + + params := &operator.CreateGrantParams{} + err = rctx.BindJSON(params) + if err != nil { + hand.SendError(rctx, err) + return + } + res, err := hand.oper.CreateGrant(rctx.Ctx, params) + if err != nil { + hand.logg.Errorf("CreateGrant error: %v", err) + hand.SendError(rctx, err) + return + } + hand.SendResult(rctx, res) +} + +// POST /v3/grant/get 200 200 +func (hand *Handler) GetGrant(rctx *router.Context) { + var err error + + params := &operator.GetGrantParams{} + err = rctx.BindJSON(params) + if err != nil { + hand.SendError(rctx, err) + return + } + res, err := hand.oper.GetGrant(rctx.Ctx, params) + if err != nil { + hand.logg.Errorf("CreateGrant error: %v", err) + hand.SendError(rctx, err) + return + } + hand.SendResult(rctx, res) +} + +// POST /v3/grants/list 200 200 +func (hand *Handler) ListGrants(rctx *router.Context) { + var err error + + params := &operator.ListGrantsParams{} + err = rctx.BindJSON(params) + if err != nil { + hand.SendError(rctx, err) + return + } + res, err := hand.oper.ListGrants(rctx.Ctx, params) + if err != nil { + hand.logg.Errorf("ListGrants error: %v", err) + hand.SendError(rctx, err) + return + } + hand.SendResult(rctx, res) +} + +// POST /v3/grant/get 200 200 +func (hand *Handler) UpdateGrant(rctx *router.Context) { + var err error + + params := &operator.UpdateGrantParams{} + err = rctx.BindJSON(params) + if err != nil { + hand.SendError(rctx, err) + return + } + res, err := hand.oper.UpdateGrant(rctx.Ctx, params) + if err != nil { + hand.logg.Errorf("UpdateGrant error: %v", err) + hand.SendError(rctx, err) + return + } + hand.SendResult(rctx, res) +} + +// POST /v3/grant/delete 200 200 +func (hand *Handler) DeleteGrant(rctx *router.Context) { + var err error + + params := &operator.DeleteGrantParams{} + err = rctx.BindJSON(params) + if err != nil { + hand.SendError(rctx, err) + return + } + res, err := hand.oper.DeleteGrant(rctx.Ctx, params) + if err != nil { + hand.logg.Errorf("DeleteGrant error: %v", err) + hand.SendError(rctx, err) + return + } + hand.SendResult(rctx, res) +} diff --git a/app/handler/service.go b/app/handler/service.go index 5cbf222..963f7e8 100644 --- a/app/handler/service.go +++ b/app/handler/service.go @@ -15,7 +15,33 @@ import ( ) func (hand *Handler) SendHello(rctx *router.Context) { + + authSuccess, _ := rctx.Bools[authTag] + authUser, _ := rctx.Strings[userTag] + + hand.logg.Debugf("%s:%v", authTag, authSuccess) + hand.logg.Debugf("%s:%v", userTag, authUser) + + if authSuccess { + + } + params := &operator.SendHelloParams{} res, _ := hand.oper.SendHello(params) hand.SendResult(rctx, res) } + +/* +func checkGrant(who, subject, operation string) (bool, error) { + + return true, error +} + +type Grant struct { + AccountID `xxxx-xxxx` + Subject `file` + Operation `putFile` + + Path "foo/bare" +} +*/ diff --git a/app/maindb/account.go b/app/maindb/account.go index 4e1796a..b70cbaf 100644 --- a/app/maindb/account.go +++ b/app/maindb/account.go @@ -9,10 +9,10 @@ import ( func (db *Database) InsertAccount(ctx context.Context, account *descr.Account) error { var err error - request := `INSERT INTO accounts(id, username, passhash, disabled, created_at, updated_at) - VALUES ($1, $2, $3, $4, $5, $6)` - _, err = db.db.Exec(request, account.ID, account.Username, account.Passhash, - account.Disabled, account.CreatedAt, account.UpdatedAt) + request := `INSERT INTO accounts(id, username, passhash, disabled, created_at, updated_at, created_by, updated_by) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8)` + _, err = db.db.Exec(request, account.ID, account.Username, account.Passhash, account.Disabled, + account.CreatedAt, account.UpdatedAt, account.CreatedBy, account.UpdatedBy) if err != nil { return err } @@ -21,8 +21,8 @@ func (db *Database) InsertAccount(ctx context.Context, account *descr.Account) e func (db *Database) UpdateAccountByID(ctx context.Context, accountID string, account *descr.Account) error { var err error - request := `UPDATE accounts SET username = $1, passhash = $2, disabled = $3, updated_at = $4 WHERE id = $6` - _, err = db.db.Exec(request, account.Username, account.Passhash, account.Disabled, account.UpdatedAt, accountID) + request := `UPDATE accounts SET username = $1, passhash = $2, disabled = $3, updated_at = $4, updated_by = $5 WHERE id = $6` + _, err = db.db.Exec(request, account.Username, account.Passhash, account.Disabled, account.UpdatedAt, account.UpdatedBy, accountID) if err != nil { return err } @@ -31,7 +31,7 @@ func (db *Database) UpdateAccountByID(ctx context.Context, accountID string, acc func (db *Database) ReducedListAccounts(ctx context.Context) ([]descr.Account, error) { var err error - request := `SELECT id, username, disabled, created_at, updated_at FROM accounts` + request := `SELECT id, username, disabled, created_at, updated_at, created_by, updated_by FROM accounts` res := make([]descr.Account, 0) err = db.db.Select(&res, request) if err != nil { diff --git a/app/maindb/file.go b/app/maindb/file.go index 159908b..e3a25b0 100644 --- a/app/maindb/file.go +++ b/app/maindb/file.go @@ -62,25 +62,7 @@ func (db *Database) ListAllFiles(ctx context.Context) ([]descr.File, error) { return res, err } -func (db *Database) GetFileByID(ctx context.Context, fileID int64) (bool, *descr.File, error) { - var err error - var res *descr.File - var exists bool - request := `SELECT * FROM files WHERE id = $1 LiMIT 1` - dbRes := make([]descr.File, 0) - err = db.db.Select(&dbRes, request, fileID) - if err != nil { - return exists, res, err - } - if len(dbRes) == 0 { - return exists, res, err - } - exists = true - res = &dbRes[0] - return exists, res, err -} - -func (db *Database) GetFileByCollection(ctx context.Context, collection, name string) (bool, *descr.File, error) { +func (db *Database) GetFileByCollectionName(ctx context.Context, collection, name string) (bool, *descr.File, error) { var err error var res *descr.File var exists bool @@ -99,7 +81,7 @@ func (db *Database) GetFileByCollection(ctx context.Context, collection, name st return exists, res, err } -func (db *Database) DeleteFileByCollection(ctx context.Context, collection, name string) error { +func (db *Database) DeleteFileByCollectionName(ctx context.Context, collection, name string) error { var err error request := `DELETE FROM files WHERE collection = $1 AND name = $2` _, err = db.db.Exec(request, collection, name) diff --git a/app/maindb/file_test.go b/app/maindb/file_test.go index c34e262..fa99e2b 100644 --- a/app/maindb/file_test.go +++ b/app/maindb/file_test.go @@ -10,7 +10,9 @@ package maindb import ( + "context" "testing" + "time" "mstore/app/descr" "mstore/pkg/auxtool" @@ -45,11 +47,11 @@ func TestFile(t *testing.T) { CreatedBy: creator, UpdatedBy: creator, } - - err = db.InsertFile(newFile) + ctx, _ := context.WithTimeout(context.Background(), 1*time.Second) + err = db.InsertFile(ctx, newFile) require.NoError(t, err) - files, err := db.ListFilesByCollection(collection) + files, err := db.ListFilesByCollection(ctx, collection) require.NoError(t, err) require.Equal(t, len(files), 1) require.Equal(t, files[0].ID, id) diff --git a/app/maindb/grant.go b/app/maindb/grant.go index 0c1401a..481cf56 100644 --- a/app/maindb/grant.go +++ b/app/maindb/grant.go @@ -8,9 +8,20 @@ import ( func (db *Database) InsertGrant(ctx context.Context, grant *descr.Grant) error { var err error - request := `INSERT INTO grants(id, account_id, operation, created_at) - VALUES ($1, $2, $3, $4)` - _, err = db.db.Exec(request, grant.ID, grant.AccountID, grant.Operation, grant.CreatedAt) + request := `INSERT INTO grants(id, account_id, operation, pattern, created_at, updated_at, created_by, updated_by) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8)` + _, err = db.db.Exec(request, grant.ID, grant.AccountID, grant.Operation, grant.Pattern, + grant.CreatedAt, grant.UpdatedAt, grant.CreatedBy, grant.UpdatedBy) + if err != nil { + return err + } + return err +} + +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` + _, err = db.db.Exec(request, grant.Pattern, grant.UpdatedAt, grant.UpdatedBy, grantID) if err != nil { return err } @@ -39,12 +50,12 @@ func (db *Database) ListGrants(ctx context.Context) ([]descr.Grant, error) { return res, err } -func (db *Database) GetGrant(ctx context.Context, accountID, operation string) (bool, *descr.Grant, error) { +func (db *Database) GetGrantByID(ctx context.Context, id string) (bool, *descr.Grant, error) { var err error res := &descr.Grant{} - request := `SELECT * FROM grants WHERE account_id = $1 AND operation = $2 LIMIT 1` + request := `SELECT * FROM grants WHERE id = $1 LIMIT 1` dbRes := make([]descr.Grant, 0) - err = db.db.Select(&dbRes, request, accountID, operation) + err = db.db.Select(&dbRes, request, id) if err != nil { return false, res, err } @@ -56,10 +67,37 @@ func (db *Database) GetGrant(ctx context.Context, accountID, operation string) ( return true, res, err } -func (db *Database) DeleteGrantByAccountID(ctx context.Context, grantID, operation string) error { +func (db *Database) GetGrantByAccoundIDOperationPattern(ctx context.Context, accountID, operation, pattern string) (bool, *descr.Grant, error) { var err error - request := `DELETE FROM grants WHERE account_id = $1 AND operation = $2` - _, err = db.db.Exec(request, grantID, operation) + res := &descr.Grant{} + request := `SELECT * FROM grants WHERE account_id = $1 AND operation = $2 AND pattern = $3 LIMIT 1` + dbRes := make([]descr.Grant, 0) + err = db.db.Select(&dbRes, request, accountID, operation, pattern) + if err != nil { + return false, res, err + } + if len(dbRes) == 0 { + return false, res, err + + } + res = &dbRes[0] + return true, res, err +} + +func (db *Database) DeleteGrantByAccountIDOperationPattern(ctx context.Context, accountID, operation, pattern string) error { + var err error + request := `DELETE FROM grants WHERE account_id = $1 AND operation = $2 AND pattern = $3` + _, err = db.db.Exec(request, accountID, operation, pattern) + if err != nil { + return err + } + return err +} + +func (db *Database) DeleteGrantByID(ctx context.Context, grantID string) error { + var err error + request := `DELETE FROM grants WHERE id = $1` + _, err = db.db.Exec(request, grantID) if err != nil { return err } diff --git a/app/maindb/grant_test.go b/app/maindb/grant_test.go new file mode 100644 index 0000000..e0e9b1c --- /dev/null +++ b/app/maindb/grant_test.go @@ -0,0 +1,60 @@ +/* + * 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 maindb + +import ( + "context" + "testing" + "time" + + "mstore/app/descr" + "mstore/pkg/auxtool" + "mstore/pkg/auxuuid" + + "github.com/stretchr/testify/require" +) + +func TestGrant(t *testing.T) { + var err error + + dbDir := t.TempDir() + db := NewDatabase(dbDir) + + err = db.OpenDatabase() + require.NoError(t, err) + + err = db.InitDatabase() + require.NoError(t, err) + + id := auxuuid.NewUUID() + accountID := auxuuid.NewUUID() + timenow := auxtool.TimeNow() + creator := "some" + newGrant := &descr.Grant{ + ID: id, + AccountID: accountID, + Operation: "opFoo", + Pattern: `*`, + CreatedAt: timenow, + UpdatedAt: timenow, + CreatedBy: creator, + UpdatedBy: creator, + } + ctx, _ := context.WithTimeout(context.Background(), 1*time.Second) + + err = db.InsertGrant(ctx, newGrant) + require.NoError(t, err) + + files, err := db.ListGrantsByAccountID(ctx, accountID) + require.NoError(t, err) + require.Equal(t, len(files), 1) + require.Equal(t, files[0].ID, id) + require.Equal(t, files[0].AccountID, accountID) +} diff --git a/app/maindb/schema.go b/app/maindb/schema.go index d68cfec..858ad0e 100644 --- a/app/maindb/schema.go +++ b/app/maindb/schema.go @@ -63,6 +63,8 @@ const schema = ` passhash TEXT NOT NULL, created_at TEXT NOT NULL, updated_at TEXT NOT NULL, + created_by TEXT NOT NULL, + updated_by TEXT NOT NULL, disabled BOOL ); CREATE UNIQUE INDEX IF NOT EXISTS accounts_index01 @@ -75,11 +77,19 @@ const schema = ` id TEXT NOT NULL, account_id INT NOT NULL, operation TEXT NOT NULL, - created_at TEXT NOT NULL + pattern TEXT NOT NULL, + created_at TEXT NOT NULL, + updated_at TEXT NOT NULL, + created_by TEXT NOT NULL, + updated_by TEXT NOT NULL ); + CREATE UNIQUE INDEX IF NOT EXISTS grants_index00 + ON grants(id); CREATE INDEX IF NOT EXISTS grants_index01 ON grants(account_id); - CREATE UNIQUE INDEX IF NOT EXISTS grants_index02 + CREATE INDEX IF NOT EXISTS grants_index02 ON grants(account_id, operation); + CREATE UNIQUE INDEX IF NOT EXISTS grants_index03 + ON grants(account_id, operation, pattern); ` diff --git a/app/operator/account.go b/app/operator/account.go index 36b4ceb..f42c2d4 100644 --- a/app/operator/account.go +++ b/app/operator/account.go @@ -10,13 +10,11 @@ import ( "mstore/pkg/auxuuid" ) -func (oper *Operator) ValidateAcount(ctx context.Context, username, password string) (bool, string, error) { +func (oper *Operator) ValidatePassword(ctx context.Context, username, password string) (bool, string, error) { var err error var accountID string valid := false - //oper.WaitRestoring() - accountExists, accountDescr, err := oper.mdb.GetAccountByUsername(ctx, username) if !accountExists { err := fmt.Errorf("Account not exists") @@ -32,8 +30,8 @@ func (oper *Operator) ValidateAcount(ctx context.Context, username, password str } type CreateAccountParams struct { - Username string - Password string + Username string `json:"username"` + Password string `json:"password"` } type CreateAccountResult struct { AccountID string `json:"accountId"` @@ -80,11 +78,11 @@ func (oper *Operator) CreateAccount(ctx context.Context, params *CreateAccountPa } type UpdateAccountParams struct { - Username string - AccountID string - NewUsername string - NewPassword string - Disabled bool + Username string `json:"username"` + AccountID string `json:"accountId"` + NewUsername string `json:"newUsername"` + NewPassword string `json:"newPassword"` + Disabled bool `json:"disabled"` } type UpdateAccountResult struct{} @@ -137,8 +135,8 @@ func (oper *Operator) UpdateAccount(ctx context.Context, params *UpdateAccountPa } type DeleteAccountParams struct { - Username string - AccountID string + Username string `json:"username"` + AccountID string `json:"accountId"` } type DeleteAccountResult struct{} @@ -182,13 +180,13 @@ func (oper *Operator) DeleteAccount(ctx context.Context, params *DeleteAccountPa type ListAccountsParams struct{} type ListAccountsResult struct { - Accounts []descr.AccountShortDescr `json:"accounts"` + Accounts []descr.AccountShort `json:"accounts"` } func (oper *Operator) ListAccounts(ctx context.Context, params *ListAccountsParams) (*ListAccountsResult, error) { var err error res := &ListAccountsResult{ - Accounts: make([]descr.AccountShortDescr, 0), + Accounts: make([]descr.AccountShort, 0), } accountDescrs, err := oper.mdb.ReducedListAccounts(ctx) @@ -196,35 +194,35 @@ func (oper *Operator) ListAccounts(ctx context.Context, params *ListAccountsPara return res, err } for _, accountDescr := range accountDescrs { - accountShortDescr := descr.AccountShortDescr{ + accountShort := descr.AccountShort{ Username: accountDescr.Username, Disabled: accountDescr.Disabled, CreatedAt: accountDescr.CreatedAt, UpdatedAt: accountDescr.UpdatedAt, - Grants: make([]descr.GrantShortDescr, 0), + Grants: make([]descr.GrantShort, 0), } grantDescrs, err := oper.mdb.ListGrantsByAccountID(ctx, accountDescr.ID) if err != nil { return res, err } for _, grantDescrs := range grantDescrs { - grantShortDescrs := descr.GrantShortDescr{ + grantShorts := descr.GrantShort{ Operation: grantDescrs.Operation, CreatedAt: grantDescrs.CreatedAt, } - accountShortDescr.Grants = append(accountShortDescr.Grants, grantShortDescrs) + accountShort.Grants = append(accountShort.Grants, grantShorts) } - res.Accounts = append(res.Accounts, accountShortDescr) + res.Accounts = append(res.Accounts, accountShort) } return res, err } type GetAccountParams struct { - Username string - AccountID string + Username string `json:"username"` + AccountID string `json:"accountId"` } type GetAccountResult struct { - Account *descr.AccountShortDescr `json:"account"` + Account *descr.AccountShort `json:"account"` } func (oper *Operator) GetAccount(ctx context.Context, params *GetAccountParams) (*GetAccountResult, error) { @@ -253,25 +251,25 @@ func (oper *Operator) GetAccount(ctx context.Context, params *GetAccountParams) return res, err } } - accountShortDescr := &descr.AccountShortDescr{ + accountShort := &descr.AccountShort{ Username: accountDescr.Username, Disabled: accountDescr.Disabled, CreatedAt: accountDescr.CreatedAt, UpdatedAt: accountDescr.UpdatedAt, - Grants: make([]descr.GrantShortDescr, 0), + Grants: make([]descr.GrantShort, 0), } grantDescrs, err := oper.mdb.ListGrantsByAccountID(ctx, accountDescr.ID) if err != nil { return res, err } for _, grantDescrs := range grantDescrs { - grantShortDescrs := descr.GrantShortDescr{ + grantShorts := descr.GrantShort{ Operation: grantDescrs.Operation, CreatedAt: grantDescrs.CreatedAt, } - accountShortDescr.Grants = append(accountShortDescr.Grants, grantShortDescrs) + accountShort.Grants = append(accountShort.Grants, grantShorts) } - res.Account = accountShortDescr + res.Account = accountShort return res, err } diff --git a/app/operator/file.go b/app/operator/file.go index 0a52f82..d02ee2f 100644 --- a/app/operator/file.go +++ b/app/operator/file.go @@ -56,7 +56,7 @@ func (oper *Operator) FileInfo(ctx context.Context, param *FileInfoParams) (int, filename := path.Base(xfilepath) collection := path.Dir(xfilepath) - exist, fileDescr, err := oper.mdb.GetFileByCollection(ctx, collection, filename) + exist, fileDescr, err := oper.mdb.GetFileByCollectionName(ctx, collection, filename) if err != nil { code := http.StatusInternalServerError return code, res, err @@ -86,6 +86,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) { var err error res := &PutFileResult{} @@ -120,7 +121,7 @@ func (oper *Operator) PutFile(ctx context.Context, param *PutFileParams) (int, * return code, res, err } - descrExists, fileDescr, err := oper.mdb.GetFileByCollection(ctx, collection, filename) + descrExists, fileDescr, err := oper.mdb.GetFileByCollectionName(ctx, collection, filename) if err != nil { code := http.StatusInternalServerError return code, res, err @@ -189,7 +190,7 @@ func (oper *Operator) GetFile(ctx context.Context, param *GetFileParams) (int, * filename := path.Base(xfilepath) collection := path.Dir(xfilepath) - descrExists, fileDescr, err := oper.mdb.GetFileByCollection(ctx, collection, filename) + descrExists, fileDescr, err := oper.mdb.GetFileByCollectionName(ctx, collection, filename) if err != nil { code := http.StatusInternalServerError return code, res, err @@ -232,13 +233,13 @@ func (oper *Operator) DeleteFile(ctx context.Context, param *DeleteFileParams) ( filename := path.Base(xfilepath) collection := path.Dir(xfilepath) - exist, _, err := oper.mdb.GetFileByCollection(ctx, collection, filename) + exist, _, err := oper.mdb.GetFileByCollectionName(ctx, collection, filename) if err != nil { code = http.StatusInternalServerError return code, res, err } if exist { - err = oper.mdb.DeleteFileByCollection(ctx, collection, filename) + err = oper.mdb.DeleteFileByCollectionName(ctx, collection, filename) if err != nil { code = http.StatusInternalServerError return code, res, err @@ -258,12 +259,14 @@ type ListFilesParams struct { } type ListFilesResult struct { Files []descr.File `json:"files,omitempty"` + //Catalogs []string `json:"files,omitempty"` } func (oper *Operator) ListFiles(ctx context.Context, param *ListFilesParams) (int, *ListFilesResult, error) { var err error res := &ListFilesResult{ Files: make([]descr.File, 0), + //Catalogs: make([]string, 0) } // TODO: convert file path to a unified and secure state diff --git a/app/operator/grant.go b/app/operator/grant.go new file mode 100644 index 0000000..d54ae9f --- /dev/null +++ b/app/operator/grant.go @@ -0,0 +1,182 @@ +package operator + +import ( + "context" + "fmt" + + "mstore/app/descr" + "mstore/pkg/auxtool" + "mstore/pkg/auxuuid" +) + +type CreateGrantParams struct { + AccountID string `json:"accountID"` + Operation string `json:"operation"` + Pattern string `json:"pattern"` +} +type CreateGrantResult struct { + GrantID string `json:"grantId"` +} + +func (oper *Operator) CreateGrant(ctx context.Context, params *CreateGrantParams) (*CreateGrantResult, error) { + var err error + res := &CreateGrantResult{} + + if params.AccountID == "" { + err := fmt.Errorf("Empty accountId parameters") + return res, err + } + if params.Operation == "" { + err := fmt.Errorf("Empty operation parameter") + return res, err + } + if params.Pattern == "" { + err := fmt.Errorf("Empty pattern parameter") + return res, err + } + + grantExists, _, err := oper.mdb.GetGrantByAccoundIDOperationPattern(ctx, params.AccountID, params.Operation, params.Pattern) + if err != nil { + return res, err + } + if grantExists { + err := fmt.Errorf("Grant with this parapeters already exists") + return res, err + } + now := auxtool.TimeNow() + grantDescr := &descr.Grant{ + ID: auxuuid.NewUUID(), + AccountID: params.AccountID, + Operation: params.Operation, + Pattern: params.Pattern, + CreatedAt: now, + UpdatedAt: now, + } + err = oper.mdb.InsertGrant(ctx, grantDescr) + if err != nil { + return res, err + } + res.GrantID = grantDescr.ID + return res, err +} + +type UpdateGrantParams struct { + GrantID string + NewPattern string +} +type UpdateGrantResult struct{} + +func (oper *Operator) UpdateGrant(ctx context.Context, params *UpdateGrantParams) (*UpdateGrantResult, error) { + var err error + res := &UpdateGrantResult{} + + if params.GrantID == "" { + err := fmt.Errorf("Empty grantId parameter") + return res, err + } + var grantDescr *descr.Grant + var grantExists bool + + grantExists, grantDescr, err = oper.mdb.GetGrantByID(ctx, params.GrantID) + if err != nil { + return res, err + } + if !grantExists { + err := fmt.Errorf("Grant with ID %s dont exists", params.GrantID) + return res, err + } + now := auxtool.TimeNow() + if params.NewPattern != "" { + grantDescr.UpdatedAt = now + grantDescr.Pattern = params.NewPattern + } + err = oper.mdb.UpdateGrantByID(ctx, grantDescr.ID, grantDescr) + if err != nil { + return res, err + } + return res, err +} + +type DeleteGrantParams struct { + GrantID string `json:"grantId"` +} +type DeleteGrantResult struct{} + +func (oper *Operator) DeleteGrant(ctx context.Context, params *DeleteGrantParams) (*DeleteGrantResult, error) { + var err error + res := &DeleteGrantResult{} + + if params.GrantID == "" { + err := fmt.Errorf("Empty grantId parameter") + return res, err + } + + var grantDescr *descr.Grant + var grantExists bool + + grantExists, grantDescr, err = oper.mdb.GetGrantByID(ctx, params.GrantID) + if err != nil { + return res, err + } + if !grantExists { + err := fmt.Errorf("Grant with ID %s dont exists", params.GrantID) + return res, err + } + err = oper.mdb.DeleteGrantByID(ctx, grantDescr.ID) + if err != nil { + return res, err + } + return res, err +} + +type ListGrantsParams struct { + AccountID string `json:"accountId"` +} +type ListGrantsResult struct { + Grants []descr.Grant `json:"grants"` +} + +func (oper *Operator) ListGrants(ctx context.Context, params *ListGrantsParams) (*ListGrantsResult, error) { + var err error + res := &ListGrantsResult{ + Grants: make([]descr.Grant, 0), + } + + grantDescrs, err := oper.mdb.ListGrantsByAccountID(ctx, params.AccountID) + if err != nil { + return res, err + } + res.Grants = grantDescrs + return res, err +} + +type GetGrantParams struct { + GrantID string `json:"grantId"` +} +type GetGrantResult struct { + Grant *descr.Grant `json:"grant"` +} + +func (oper *Operator) GetGrant(ctx context.Context, params *GetGrantParams) (*GetGrantResult, error) { + var err error + res := &GetGrantResult{} + + if params.GrantID == "" { + err := fmt.Errorf("Empty grantId parameter") + return res, err + } + + var grantDescr *descr.Grant + var grantExists bool + grantExists, grantDescr, err = oper.mdb.GetGrantByID(ctx, params.GrantID) + if err != nil { + return res, err + } + if !grantExists { + err := fmt.Errorf("Grant with ID %s dont exists", params.GrantID) + return res, err + } + + res.Grant = grantDescr + return res, err +} diff --git a/test/account_test.go b/attic/test/account_test.go similarity index 98% rename from test/account_test.go rename to attic/test/account_test.go index 43e71e3..15da075 100644 --- a/test/account_test.go +++ b/attic/test/account_test.go @@ -25,6 +25,7 @@ import ( "mstore/app/operator" "mstore/app/router" "mstore/app/server" + "mstore/pkg/auxhttp" ) func TestAccountOperations(t *testing.T) { @@ -56,6 +57,8 @@ func TestAccountOperations(t *testing.T) { err = srv.Build() require.NoError(t, err) } + username := "testuser" + password := "testpass" { fmt.Printf("=== ServiceHello ===\n") reqPath := "/service/hello" @@ -68,6 +71,9 @@ func TestAccountOperations(t *testing.T) { request, err := http.NewRequest("GET", reqPath, nil) require.NoError(t, err) + basic := auxhttp.EncodeBasicAuth(username, password) + request.Header.Add("Authorization", basic) + recorder := httptest.NewRecorder() rout.ServeHTTP(recorder, request) require.Equal(t, http.StatusOK, recorder.Code) @@ -79,6 +85,9 @@ func TestAccountOperations(t *testing.T) { fmt.Printf("Response body: %s\n", string(bodyBytes)) } + + return + var accountID string var accountName = "testname1" { diff --git a/test/file_test.go b/attic/test/file_test.go similarity index 98% rename from test/file_test.go rename to attic/test/file_test.go index 6ed0dc5..937dff5 100644 --- a/test/file_test.go +++ b/attic/test/file_test.go @@ -36,7 +36,7 @@ func xxxTestFileOperations(t *testing.T) { var srvport int64 = 10240 + rand.Int63n(1024) srvdir := t.TempDir() - filename := `bare.bin?abc=12` + filename := `bare.bin` { err = srv.Configure() @@ -44,9 +44,10 @@ func xxxTestFileOperations(t *testing.T) { err = srv.Configure() require.NoError(t, err) - var tmpdir bool - tmpdir = true - if tmpdir { + + useTmpdir := false + + if useTmpdir { srv.SetDatadir(srvdir) srv.SetLogdir(srvdir) srv.SetRundir(srvdir) @@ -169,6 +170,7 @@ func xxxTestFileOperations(t *testing.T) { fmt.Printf("Response body: %s\n", string(bodyBytes)) } + return { fmt.Printf("=== DeleteFile ===\n") reqPath := filepath.Join(`/v3/api/file`, filename) diff --git a/pkg/client/client.go b/pkg/client/client.go index 1ec7e7a..7acb207 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -9,74 +9,8 @@ */ package client -import ( - "context" - "crypto/tls" - "fmt" - "net/http" - "net/url" - "time" -) - type Client struct{} func NewClient() *Client { return &Client{} } - -func convertServiceURI(ref string) (string, error) { - var err error - var res string - const serviceAPI = "/v3/api/service/" - const serviceScheme = "https" - uri, err := url.Parse(ref) - if err != nil { - return res, err - } - uri.Path, err = url.JoinPath(serviceAPI, uri.Path) - if err != nil { - return res, err - } - uri.Scheme = serviceScheme - res = uri.String() - return res, err -} - -func (cli *Client) ServiceHello(ctx context.Context, serviceuri string, timeout time.Duration) (bool, error) { - var res bool - var err error - ctx, _ = context.WithTimeout(ctx, timeout) - - serviceuri, _, _, err = repackServiceURI(serviceuri) - fmt.Printf("%s\n", serviceuri) - if err != nil { - return res, err - } - serviceuri, err = convertServiceURI(serviceuri) - fmt.Printf("%s\n", serviceuri) - - if err != nil { - return res, err - } - req, err := http.NewRequestWithContext(ctx, http.MethodGet, serviceuri, nil) - if err != nil { - return res, err - } - transport := &http.Transport{ - TLSClientConfig: &tls.Config{ - InsecureSkipVerify: true, - }, - } - client := &http.Client{ - Transport: transport, - } - resp, err := client.Do(req) - if err != nil { - return res, err - } - defer resp.Body.Close() - if resp.StatusCode == http.StatusOK { - res = true - } - return res, err -} diff --git a/pkg/client/filelife_test.go b/pkg/client/filelife_test.go index e36d248..89d9112 100644 --- a/pkg/client/filelife_test.go +++ b/pkg/client/filelife_test.go @@ -27,7 +27,7 @@ import ( func TestFileLife(t *testing.T) { var srvport int64 = 10250 srvdir := t.TempDir() - srvaddr := fmt.Sprintf("127.0.0.1:%d", srvport) + srvaddr := fmt.Sprintf("testuser:testpass@127.0.0.1:%d", srvport) srv, err := server.NewServer() require.NoError(t, err) @@ -73,6 +73,9 @@ func TestFileLife(t *testing.T) { require.NoError(t, err) require.True(t, helloRes) } + + return + filesize := 32 { // PutFile @@ -139,10 +142,7 @@ func TestFileLife(t *testing.T) { ctx := context.Background() ctx, _ = context.WithTimeout(ctx, 1*time.Second) - tmpdir := t.TempDir() - tmpfile := filepath.Join(tmpdir, "foo.bin") - - err = cli.DeleteFile(ctx, srvaddr+"/foo.bin", tmpfile) + err = cli.DeleteFile(ctx, srvaddr+"/foo.bin") require.NoError(t, err) } { diff --git a/pkg/client/imagelife_test.go b/pkg/client/imagelife_test.go index 3cef5a1..a4420f7 100644 --- a/pkg/client/imagelife_test.go +++ b/pkg/client/imagelife_test.go @@ -22,7 +22,7 @@ import ( "sigs.k8s.io/yaml" ) -func TestImageLife(t *testing.T) { +func xxxTestImageLife(t *testing.T) { var srvport int64 = 10250 srvdir := t.TempDir() srvaddr := fmt.Sprintf("127.0.0.1:%d", srvport) diff --git a/pkg/client/service.go b/pkg/client/service.go new file mode 100644 index 0000000..3cae199 --- /dev/null +++ b/pkg/client/service.go @@ -0,0 +1,79 @@ +/* + * 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 client + +import ( + "context" + "crypto/tls" + "net/http" + "net/url" + "time" + + "mstore/pkg/auxhttp" +) + +func convertServiceURI(ref string) (string, error) { + var err error + var res string + const serviceAPI = "/v3/api/service/" + const serviceScheme = "https" + uri, err := url.Parse(ref) + if err != nil { + return res, err + } + uri.Path, err = url.JoinPath(serviceAPI, uri.Path) + if err != nil { + return res, err + } + uri.Scheme = serviceScheme + res = uri.String() + return res, err +} + +func (cli *Client) ServiceHello(ctx context.Context, serviceuri string, timeout time.Duration) (bool, error) { + var res bool + var err error + ctx, _ = context.WithTimeout(ctx, timeout) + + serviceuri, username, password, err := repackServiceURI(serviceuri) + if err != nil { + return res, err + } + serviceuri, err = convertServiceURI(serviceuri) + + if err != nil { + return res, err + } + req, err := http.NewRequestWithContext(ctx, http.MethodGet, serviceuri, nil) + if err != nil { + return res, err + } + if username != "" && password != "" { + basic := auxhttp.EncodeBasicAuth(username, password) + req.Header.Add("Authorization", basic) + } + transport := &http.Transport{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: true, + }, + } + client := &http.Client{ + Transport: transport, + } + resp, err := client.Do(req) + if err != nil { + return res, err + } + defer resp.Body.Close() + if resp.StatusCode == http.StatusOK { + res = true + } + return res, err +}