package store_test import ( "testing" "atlas9.dev/c/core" "atlas9.dev/c/core/assert" "atlas9.dev/c/core/dbi" "atlas9.dev/c/core/iam" "atlas9.dev/c/demo/lib" "atlas9.dev/c/demo/store" ) func testAccessStore(t *testing.T, setup func(t *testing.T) ( s *store.SqliteAccessStore, newTenants func(*testing.T, int) []core.ID, newUsers func(*testing.T, int) []core.ID, addMember func(*testing.T, core.ID, core.ID), addRole func(*testing.T, core.ID, string, []iam.Cap), addUserGrant func(*testing.T, core.ID, core.ID, string), addGroup func(*testing.T, core.ID, core.Path), addGroupMember func(*testing.T, core.ID, core.Path, core.ID), addGroupGrant func(*testing.T, core.ID, core.Path, string), )) { t.Run("User grant", func(t *testing.T) { st, newTenants, newUsers, addMember, addRole, addUserGrant, _, _, _ := setup(t) ctx := t.Context() tenant := newTenants(t, 1)[0] user := newUsers(t, 1)[0] addMember(t, tenant, user) addRole(t, tenant, "viewer", []iam.Cap{"read"}) addUserGrant(t, tenant, user, "viewer") var access lib.Access err := st.Load(ctx, user, &access) assert.Ok(t, err) assert.Eq(t, access.Check("read", tenant, "") == nil, true) assert.Eq(t, access.Check("write", tenant, "") == nil, false) }) t.Run("Group grant", func(t *testing.T) { st, newTenants, newUsers, addMember, addRole, _, addGroup, addGroupMember, addGroupGrant := setup(t) ctx := t.Context() tenant := newTenants(t, 1)[0] user := newUsers(t, 1)[0] addMember(t, tenant, user) addRole(t, tenant, "editor", []iam.Cap{"read", "write"}) addGroup(t, tenant, "eng") addGroupMember(t, tenant, "eng", user) addGroupGrant(t, tenant, "eng", "editor") var access lib.Access err := st.Load(ctx, user, &access) assert.Ok(t, err) assert.Eq(t, access.Check("read", tenant, "") == nil, true) assert.Eq(t, access.Check("write", tenant, "") == nil, true) }) t.Run("Multiple grants combine caps", func(t *testing.T) { st, newTenants, newUsers, addMember, addRole, addUserGrant, addGroup, addGroupMember, addGroupGrant := setup(t) ctx := t.Context() tenant := newTenants(t, 1)[0] user := newUsers(t, 1)[0] addMember(t, tenant, user) addRole(t, tenant, "reader", []iam.Cap{"read"}) addRole(t, tenant, "writer", []iam.Cap{"write"}) addUserGrant(t, tenant, user, "reader") addGroup(t, tenant, "eng") addGroupMember(t, tenant, "eng", user) addGroupGrant(t, tenant, "eng", "writer") var access lib.Access err := st.Load(ctx, user, &access) assert.Ok(t, err) assert.Eq(t, access.Check("read", tenant, "") == nil, true) assert.Eq(t, access.Check("write", tenant, "") == nil, true) }) t.Run("Grants from other tenants do not cross over", func(t *testing.T) { st, newTenants, newUsers, addMember, addRole, addUserGrant, _, _, _ := setup(t) ctx := t.Context() tenants := newTenants(t, 2) user := newUsers(t, 1)[0] addMember(t, tenants[0], user) addRole(t, tenants[0], "viewer", []iam.Cap{"read"}) addUserGrant(t, tenants[0], user, "viewer") var access lib.Access err := st.Load(ctx, user, &access) assert.Ok(t, err) assert.Eq(t, access.Check("read", tenants[0], "") == nil, true) assert.Eq(t, access.Check("read", tenants[1], "") == nil, false) }) } func TestAccessStore(t *testing.T) { testAccessStore(t, func(t *testing.T) ( *store.SqliteAccessStore, func(*testing.T, int) []core.ID, func(*testing.T, int) []core.ID, func(*testing.T, core.ID, core.ID), func(*testing.T, core.ID, string, []iam.Cap), func(*testing.T, core.ID, core.ID, string), func(*testing.T, core.ID, core.Path), func(*testing.T, core.ID, core.Path, core.ID), func(*testing.T, core.ID, core.Path, string), ) { t.Helper() db := setupTestDB(t) tx, err := db.Begin() assert.Ok(t, err) t.Cleanup(func() { tx.Rollback() }) w := dbi.WrapTx(tx) guard := lib.ContextAccessGuard{} ctx := lib.PutAccess(t.Context(), lib.AdminAccess()) tenantStore := store.NewSqliteTenantStore(w, guard) userStore := store.NewSqliteUserStore(w, guard) memberStore := store.NewSqliteTenantMemberStore(w, guard) roleStore := store.NewSqliteRoleStore(w, guard) grantStore := store.NewSqliteGrantStore(w, guard) groupStore := store.NewSqliteGroupStore(w, guard) newTenants := func(t *testing.T, n int) []core.ID { t.Helper() ids := generateIDs(n) for _, id := range ids { err := tenantStore.Create(ctx, &iam.Tenant{ID: id, Name: "T"}) assert.Ok(t, err) } return ids } newUsers := func(t *testing.T, n int) []core.ID { t.Helper() ids := generateIDs(n) for _, id := range ids { err := userStore.Save(ctx, &iam.User{ID: id, Email: id.String() + "@test.com"}) assert.Ok(t, err) } return ids } addMember := func(t *testing.T, tenant, user core.ID) { t.Helper() assert.Ok(t, memberStore.Create(ctx, iam.TenantMember{Tenant: tenant, UserID: user})) } addRole := func(t *testing.T, tenant core.ID, slug string, caps []iam.Cap) { t.Helper() assert.Ok(t, roleStore.Save(ctx, &iam.Role{Slug: slug, Tenant: tenant, Name: slug, Caps: caps})) } addUserGrant := func(t *testing.T, tenant, user core.ID, role string) { t.Helper() assert.Ok(t, grantStore.Add(ctx, iam.Grant{ Tenant: tenant, Type: "user", Principal: user.String(), Role: role, })) } addGroup := func(t *testing.T, tenant core.ID, path core.Path) { t.Helper() assert.Ok(t, groupStore.Save(ctx, &iam.Group{Tenant: tenant, Path: path, Name: string(path)})) } addGroupMember := func(t *testing.T, tenant core.ID, group core.Path, user core.ID) { t.Helper() assert.Ok(t, groupStore.AddMember(ctx, tenant, group, user)) } addGroupGrant := func(t *testing.T, tenant core.ID, group core.Path, role string) { t.Helper() assert.Ok(t, grantStore.Add(ctx, iam.Grant{ Tenant: tenant, Type: "group", Principal: string(group), Role: role, })) } return store.NewSqliteAccessStore(w), newTenants, newUsers, addMember, addRole, addUserGrant, addGroup, addGroupMember, addGroupGrant }) }