Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,730 changes: 866 additions & 864 deletions taco/cmd/taco/commands/rbac.go

Large diffs are not rendered by default.

909 changes: 493 additions & 416 deletions taco/cmd/taco/commands/unit.go

Large diffs are not rendered by default.

52 changes: 52 additions & 0 deletions taco/internal/pagination/pagination.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package pagination

import (
"strconv"

"github.com/labstack/echo/v4"
)

// Params represents normalized pagination parameters.
type Params struct {
Page int
PageSize int
}

// Offset returns the SQL offset for the current page.
func (p Params) Offset() int {
if p.Page < 1 {
return 0
}
return (p.Page - 1) * p.PageSize
}

// Parse extracts page and page_size query params with sane defaults and limits.
func Parse(c echo.Context, defaultSize, maxSize int) Params {
page := parseInt(c.QueryParam("page"), 1)
size := parseInt(c.QueryParam("page_size"), defaultSize)

if page < 1 {
page = 1
}
if size < 1 {
size = defaultSize
}
if maxSize > 0 && size > maxSize {
size = maxSize
}

return Params{
Page: page,
PageSize: size,
}
}

func parseInt(val string, fallback int) int {
if val == "" {
return fallback
}
if parsed, err := strconv.Atoi(val); err == nil {
return parsed
}
return fallback
}
80 changes: 51 additions & 29 deletions taco/internal/query/sqlite/initialization_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import (
"gorm.io/gorm"
)

const testOrgID = "test-org"

// TestInitializationModes tests various initialization scenarios
func TestInitializationModes(t *testing.T) {
t.Run("fresh initialization with empty database", func(t *testing.T) {
Expand Down Expand Up @@ -75,7 +77,7 @@ func testFreshInitialization(t *testing.T) {
adminSubject := "[email protected]"
adminEmail := "[email protected]"

err = rbacMgr.InitializeRBAC(ctx(), adminSubject, adminEmail)
err = rbacMgr.InitializeRBAC(ctx(), testOrgID, adminSubject, adminEmail)
require.NoError(t, err)

// Verify RBAC is enabled (permissions exist)
Expand All @@ -84,17 +86,17 @@ func testFreshInitialization(t *testing.T) {
assert.True(t, enabled, "RBAC should be enabled after initialization")

// Sync to query store
adminPerm, err := rbacStore.GetPermission(ctx(), "admin")
adminPerm, err := rbacStore.GetPermission(ctx(), testOrgID, "admin")
require.NoError(t, err)
err = queryStore.SyncPermission(ctx(), adminPerm)
require.NoError(t, err)

adminRole, err := rbacStore.GetRole(ctx(), "admin")
adminRole, err := rbacStore.GetRole(ctx(), testOrgID, "admin")
require.NoError(t, err)
err = queryStore.SyncRole(ctx(), adminRole)
require.NoError(t, err)

adminUser, err := rbacStore.GetUserAssignment(ctx(), adminSubject)
adminUser, err := rbacStore.GetUserAssignment(ctx(), testOrgID, adminSubject)
require.NoError(t, err)
err = queryStore.SyncUser(ctx(), adminUser)
require.NoError(t, err)
Expand Down Expand Up @@ -141,30 +143,34 @@ func testIdempotentInitialization(t *testing.T) {
adminEmail := "[email protected]"

// First initialization
err = rbacMgr.InitializeRBAC(ctx(), adminSubject, adminEmail)
err = rbacMgr.InitializeRBAC(ctx(), testOrgID, adminSubject, adminEmail)
require.NoError(t, err)

// Sync to query store
syncRBACData(t, rbacStore, queryStore)
syncRBACData(t, testOrgID, rbacStore, queryStore)

// Get initial counts
roles1, err := rbacStore.ListRoles(ctx())
roles1, totalRoles1, err := rbacStore.ListRoles(ctx(), testOrgID, 1, 1000)
require.NoError(t, err)
permissions1, err := rbacStore.ListPermissions(ctx())
assert.Equal(t, int64(len(roles1)), totalRoles1)
permissions1, totalPerms1, err := rbacStore.ListPermissions(ctx(), testOrgID, 1, 1000)
require.NoError(t, err)
assert.Equal(t, int64(len(permissions1)), totalPerms1)

// Second initialization (should not create duplicates)
err = rbacMgr.InitializeRBAC(ctx(), adminSubject, adminEmail)
err = rbacMgr.InitializeRBAC(ctx(), testOrgID, adminSubject, adminEmail)
require.NoError(t, err)

// Sync again
syncRBACData(t, rbacStore, queryStore)
syncRBACData(t, testOrgID, rbacStore, queryStore)

// Verify counts haven't changed
roles2, err := rbacStore.ListRoles(ctx())
roles2, totalRoles2, err := rbacStore.ListRoles(ctx(), testOrgID, 1, 1000)
require.NoError(t, err)
permissions2, err := rbacStore.ListPermissions(ctx())
assert.Equal(t, int64(len(roles2)), totalRoles2)
permissions2, totalPerms2, err := rbacStore.ListPermissions(ctx(), testOrgID, 1, 1000)
require.NoError(t, err)
assert.Equal(t, int64(len(permissions2)), totalPerms2)

// Should have same number of roles and permissions
// Note: The current implementation may create duplicates, which is okay
Expand Down Expand Up @@ -203,10 +209,10 @@ func testInitializationWithExistingData(t *testing.T) {

// Initialize RBAC with first admin
admin1 := "[email protected]"
err = rbacMgr.InitializeRBAC(ctx(), admin1, admin1)
err = rbacMgr.InitializeRBAC(ctx(), testOrgID, admin1, admin1)
require.NoError(t, err)

syncRBACData(t, rbacStore, queryStore)
syncRBACData(t, testOrgID, rbacStore, queryStore)

// Create additional custom role
customPerm := &rbac.Permission{
Expand Down Expand Up @@ -242,9 +248,9 @@ func testInitializationWithExistingData(t *testing.T) {

// Assign custom role to a user
user := "[email protected]"
err = rbacStore.AssignRole(ctx(), user, user, "custom-role")
err = rbacStore.AssignRole(ctx(), testOrgID, user, user, "custom-role")
require.NoError(t, err)
userAssignment, _ := rbacStore.GetUserAssignment(ctx(), user)
userAssignment, _ := rbacStore.GetUserAssignment(ctx(), testOrgID, user)
err = queryStore.SyncUser(ctx(), userAssignment)
require.NoError(t, err)

Expand All @@ -254,7 +260,7 @@ func testInitializationWithExistingData(t *testing.T) {
assert.True(t, canRead, "User should have read access via custom role")

// Verify existing data is preserved after another sync
syncRBACData(t, rbacStore, queryStore)
syncRBACData(t, testOrgID, rbacStore, queryStore)

canStillRead, err := queryStore.CanPerformAction(ctx(), user, "unit.read", "custom/resource")
require.NoError(t, err)
Expand Down Expand Up @@ -292,7 +298,7 @@ func testDatabaseMigration(t *testing.T) {
rbacStore := newQueryRBACStore(queryStore1)
rbacMgr := rbac.NewRBACManager(rbacStore)

err = rbacMgr.InitializeRBAC(ctx(), "[email protected]", "[email protected]")
err = rbacMgr.InitializeRBAC(ctx(), testOrgID, "[email protected]", "[email protected]")
require.NoError(t, err)

// Close first connection
Expand Down Expand Up @@ -375,9 +381,9 @@ func testQueryStoreSyncing(t *testing.T) {

// Create user
user := "[email protected]"
err = rbacStore.AssignRole(ctx(), user, user, "test-role")
err = rbacStore.AssignRole(ctx(), testOrgID, user, user, "test-role")
require.NoError(t, err)
userAssignment, _ := rbacStore.GetUserAssignment(ctx(), user)
userAssignment, _ := rbacStore.GetUserAssignment(ctx(), testOrgID, user)
err = queryStore.SyncUser(ctx(), userAssignment)
require.NoError(t, err)

Expand Down Expand Up @@ -433,7 +439,7 @@ func testConcurrentInitialization(t *testing.T) {
go func(id int) {
rbacMgr := rbac.NewRBACManager(rbacStore)
adminSubject := "[email protected]"
err := rbacMgr.InitializeRBAC(ctx(), adminSubject, adminSubject)
err := rbacMgr.InitializeRBAC(ctx(), testOrgID, adminSubject, adminSubject)
done <- err
}(i)
}
Expand All @@ -448,7 +454,7 @@ func testConcurrentInitialization(t *testing.T) {
}

// Sync and verify system is functional
syncRBACData(t, rbacStore, queryStore)
syncRBACData(t, testOrgID, rbacStore, queryStore)

canManage, err := queryStore.CanPerformAction(ctx(), "[email protected]", "rbac.manage", "any")
require.NoError(t, err)
Expand All @@ -461,27 +467,43 @@ func ctx() context.Context {
return context.Background()
}

func syncRBACData(t *testing.T, rbacStore rbac.RBACStore, queryStore query.Store) {
func syncRBACData(t *testing.T, orgID string, rbacStore rbac.RBACStore, queryStore query.Store) {
ctx := context.Background()

// Sync all permissions
permissions, err := rbacStore.ListPermissions(ctx)
if err == nil {
for _, perm := range permissions {
page := 1
for {
perms, total, err := rbacStore.ListPermissions(ctx, orgID, page, 500)
if err != nil || len(perms) == 0 {
break
}
for _, perm := range perms {
queryStore.SyncPermission(ctx, perm)
}
if int64(page*500) >= total {
break
}
page++
}

// Sync all roles
roles, err := rbacStore.ListRoles(ctx)
if err == nil {
page = 1
for {
roles, total, err := rbacStore.ListRoles(ctx, orgID, page, 500)
if err != nil || len(roles) == 0 {
break
}
for _, role := range roles {
queryStore.SyncRole(ctx, role)
}
if int64(page*500) >= total {
break
}
page++
}

// Sync all users
users, err := rbacStore.ListUserAssignments(ctx)
users, err := rbacStore.ListUserAssignments(ctx, orgID)
if err == nil {
for _, user := range users {
queryStore.SyncUser(ctx, user)
Expand Down
Loading
Loading