From dace86196c4f72490fa819dccde2748be480efa3 Mon Sep 17 00:00:00 2001 From: Juan Antonio Osorio Date: Fri, 2 Feb 2024 11:16:11 +0200 Subject: [PATCH] Get project information for users from OpenFGA (#2259) * Get project information for users from OpenFGA This uses OpenFGA as the authoritative location for user/project relationships. * Output user-friendly error messages if default project cannot be determined. --- cmd/server/app/migrate_up.go | 46 ---------- .../000016_remove_user_project.down.sql | 20 +++++ .../000016_remove_user_project.up.sql | 15 ++++ database/mock/store.go | 45 ---------- database/query/user_projects.sql | 10 --- database/query/users.sql | 8 -- internal/authz/authz.go | 51 +++++++++++ internal/authz/interface.go | 3 + internal/authz/mock/noop_authz.go | 5 ++ internal/authz/mock/simple_authz.go | 7 +- internal/controlplane/handlers_authz.go | 36 ++++++-- internal/controlplane/handlers_authz_test.go | 33 ++++--- internal/controlplane/handlers_user.go | 24 +++--- internal/controlplane/handlers_user_test.go | 2 - internal/db/models.go | 6 -- internal/db/querier.go | 3 - internal/db/user_projects.sql.go | 86 ------------------- internal/db/users.sql.go | 44 ---------- 18 files changed, 159 insertions(+), 285 deletions(-) create mode 100644 database/migrations/000016_remove_user_project.down.sql create mode 100644 database/migrations/000016_remove_user_project.up.sql delete mode 100644 database/query/user_projects.sql delete mode 100644 internal/db/user_projects.sql.go diff --git a/cmd/server/app/migrate_up.go b/cmd/server/app/migrate_up.go index 900cf9911e..bd0a309961 100644 --- a/cmd/server/app/migrate_up.go +++ b/cmd/server/app/migrate_up.go @@ -31,7 +31,6 @@ import ( "github.com/stacklok/minder/internal/authz" serverconfig "github.com/stacklok/minder/internal/config/server" - "github.com/stacklok/minder/internal/db" "github.com/stacklok/minder/internal/logger" ) @@ -127,55 +126,10 @@ var upCmd = &cobra.Command{ return fmt.Errorf("error preparing authz client: %w", err) } - store := db.NewStore(dbConn) - if err := migratePermsToFGA(ctx, store, authzw, cmd); err != nil { - return fmt.Errorf("error while migrating permissions to FGA: %w", err) - } - return nil }, } -func migratePermsToFGA(ctx context.Context, store db.Store, authzw authz.Client, cmd *cobra.Command) error { - cmd.Println("Migrating permissions to FGA...") - - var i int32 = 0 - for { - userList, err := store.ListUsers(ctx, db.ListUsersParams{Limit: 100, Offset: i}) - if err != nil { - return fmt.Errorf("error while listing users: %w", err) - } - i = i + 100 - cmd.Printf("Found %d users to migrate\n", len(userList)) - if len(userList) == 0 { - break - } - - for _, user := range userList { - projs, err := store.GetUserProjects(ctx, user.ID) - if err != nil { - cmd.Printf("Skipping user %d since getting user projects yielded error: %s\n", - user.ID, err) - continue - } - - for _, proj := range projs { - cmd.Printf("Migrating user to FGA for project %s\n", proj.ProjectID) - if err := authzw.Write( - ctx, user.IdentitySubject, authz.AuthzRoleAdmin, proj.ProjectID, - ); err != nil { - cmd.Printf("Error while writing permission for user %d: %s\n", user.ID, err) - continue - } - } - } - } - - cmd.Println("Done migrating permissions to FGA") - - return nil -} - func init() { migrateCmd.AddCommand(upCmd) } diff --git a/database/migrations/000016_remove_user_project.down.sql b/database/migrations/000016_remove_user_project.down.sql new file mode 100644 index 0000000000..4762dc43d2 --- /dev/null +++ b/database/migrations/000016_remove_user_project.down.sql @@ -0,0 +1,20 @@ +-- Copyright 2024 Stacklok, Inc +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. + +-- user/projects +CREATE TABLE user_projects ( + id SERIAL PRIMARY KEY, + user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, + project_id UUID NOT NULL REFERENCES projects(id) ON DELETE CASCADE +); \ No newline at end of file diff --git a/database/migrations/000016_remove_user_project.up.sql b/database/migrations/000016_remove_user_project.up.sql new file mode 100644 index 0000000000..440149bca4 --- /dev/null +++ b/database/migrations/000016_remove_user_project.up.sql @@ -0,0 +1,15 @@ +-- Copyright 2024 Stacklok, Inc +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. + +DROP TABLE IF EXISTS user_projects; \ No newline at end of file diff --git a/database/mock/store.go b/database/mock/store.go index 08f58f81c6..83dda02a59 100644 --- a/database/mock/store.go +++ b/database/mock/store.go @@ -38,21 +38,6 @@ func (m *MockStore) EXPECT() *MockStoreMockRecorder { return m.recorder } -// AddUserProject mocks base method. -func (m *MockStore) AddUserProject(arg0 context.Context, arg1 db.AddUserProjectParams) (db.UserProject, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "AddUserProject", arg0, arg1) - ret0, _ := ret[0].(db.UserProject) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// AddUserProject indicates an expected call of AddUserProject. -func (mr *MockStoreMockRecorder) AddUserProject(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddUserProject", reflect.TypeOf((*MockStore)(nil).AddUserProject), arg0, arg1) -} - // BeginTransaction mocks base method. func (m *MockStore) BeginTransaction() (*sql.Tx, error) { m.ctrl.T.Helper() @@ -1293,21 +1278,6 @@ func (mr *MockStoreMockRecorder) GetUserBySubject(arg0, arg1 interface{}) *gomoc return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUserBySubject", reflect.TypeOf((*MockStore)(nil).GetUserBySubject), arg0, arg1) } -// GetUserProjects mocks base method. -func (m *MockStore) GetUserProjects(arg0 context.Context, arg1 int32) ([]db.GetUserProjectsRow, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetUserProjects", arg0, arg1) - ret0, _ := ret[0].([]db.GetUserProjectsRow) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetUserProjects indicates an expected call of GetUserProjects. -func (mr *MockStoreMockRecorder) GetUserProjects(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUserProjects", reflect.TypeOf((*MockStore)(nil).GetUserProjects), arg0, arg1) -} - // GlobalListProviders mocks base method. func (m *MockStore) GlobalListProviders(arg0 context.Context) ([]db.Provider, error) { m.ctrl.T.Helper() @@ -1563,21 +1533,6 @@ func (mr *MockStoreMockRecorder) ListUsersByOrganization(arg0, arg1 interface{}) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListUsersByOrganization", reflect.TypeOf((*MockStore)(nil).ListUsersByOrganization), arg0, arg1) } -// ListUsersByProject mocks base method. -func (m *MockStore) ListUsersByProject(arg0 context.Context, arg1 db.ListUsersByProjectParams) ([]db.User, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ListUsersByProject", arg0, arg1) - ret0, _ := ret[0].([]db.User) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ListUsersByProject indicates an expected call of ListUsersByProject. -func (mr *MockStoreMockRecorder) ListUsersByProject(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListUsersByProject", reflect.TypeOf((*MockStore)(nil).ListUsersByProject), arg0, arg1) -} - // LockIfThresholdNotExceeded mocks base method. func (m *MockStore) LockIfThresholdNotExceeded(arg0 context.Context, arg1 db.LockIfThresholdNotExceededParams) (db.EntityExecutionLock, error) { m.ctrl.T.Helper() diff --git a/database/query/user_projects.sql b/database/query/user_projects.sql deleted file mode 100644 index 3341d879a9..0000000000 --- a/database/query/user_projects.sql +++ /dev/null @@ -1,10 +0,0 @@ --- name: AddUserProject :one -INSERT INTO user_projects ( - user_id, - project_id - ) VALUES ( - $1, $2 -) RETURNING *; - --- name: GetUserProjects :many -SELECT * FROM projects INNER JOIN user_projects ON projects.id = user_projects.project_id WHERE user_projects.user_id = $1; \ No newline at end of file diff --git a/database/query/users.sql b/database/query/users.sql index 5d06c56da5..2161542c0a 100644 --- a/database/query/users.sql +++ b/database/query/users.sql @@ -23,13 +23,5 @@ ORDER BY id LIMIT $2 OFFSET $3; --- name: ListUsersByProject :many -SELECT users.* FROM users -JOIN user_projects ON users.id = user_projects.user_id -WHERE user_projects.project_id = $1 -ORDER BY users.id -LIMIT $2 -OFFSET $3; - -- name: CountUsers :one SELECT COUNT(*) FROM users; diff --git a/internal/authz/authz.go b/internal/authz/authz.go index acacc0efd1..14459aa3d6 100644 --- a/internal/authz/authz.go +++ b/internal/authz/authz.go @@ -377,6 +377,53 @@ func (a *ClientWrapper) AssignmentsToProject(ctx context.Context, project uuid.U return assignments, nil } +// ProjectsForUser lists the projects that the given user has access to +func (a *ClientWrapper) ProjectsForUser(ctx context.Context, sub string) ([]uuid.UUID, error) { + u := getUserForTuple(sub) + + var pagesize int32 = 50 + var contTok *string = nil + + projs := map[string]any{} + projectObj := "project:" + + for { + resp, err := a.cli.Read(ctx).Options(fgaclient.ClientReadOptions{ + PageSize: &pagesize, + ContinuationToken: contTok, + }).Body(fgaclient.ClientReadRequest{ + User: &u, + Object: &projectObj, + }).Execute() + if err != nil { + return nil, fmt.Errorf("unable to read authorization tuples: %w", err) + } + + for _, t := range resp.GetTuples() { + k := t.GetKey() + + projs[k.GetObject()] = struct{}{} + } + + if resp.GetContinuationToken() == "" { + break + } + + contTok = &resp.ContinuationToken + } + + out := []uuid.UUID{} + for proj := range projs { + u, err := uuid.Parse(getProjectFromTuple(proj)) + if err != nil { + continue + } + out = append(out, u) + } + + return out, nil +} + func getUserForTuple(user string) string { return "user:" + user } @@ -388,3 +435,7 @@ func getProjectForTuple(project uuid.UUID) string { func getUserFromTuple(user string) string { return strings.TrimPrefix(user, "user:") } + +func getProjectFromTuple(project string) string { + return strings.TrimPrefix(project, "project:") +} diff --git a/internal/authz/interface.go b/internal/authz/interface.go index c2cfad2824..694dfa08ad 100644 --- a/internal/authz/interface.go +++ b/internal/authz/interface.go @@ -98,6 +98,9 @@ type Client interface { // AssignmentsToProject outputs the existing role assignments for a given project. AssignmentsToProject(ctx context.Context, project uuid.UUID) ([]*minderv1.RoleAssignment, error) + // ProjectsForUser outputs the projects a user has access to. + ProjectsForUser(ctx context.Context, sub string) ([]uuid.UUID, error) + // PrepareForRun allows for any preflight configurations to be done before // the server is started. PrepareForRun(ctx context.Context) error diff --git a/internal/authz/mock/noop_authz.go b/internal/authz/mock/noop_authz.go index 0e3b45a4cb..45803b3de0 100644 --- a/internal/authz/mock/noop_authz.go +++ b/internal/authz/mock/noop_authz.go @@ -64,6 +64,11 @@ func (_ *NoopClient) AssignmentsToProject(_ context.Context, _ uuid.UUID) ([]*mi return nil, nil } +// ProjectsForUser implements authz.Client +func (_ *NoopClient) ProjectsForUser(_ context.Context, _ string) ([]uuid.UUID, error) { + return nil, nil +} + // PrepareForRun implements authz.Client func (_ *NoopClient) PrepareForRun(_ context.Context) error { return nil diff --git a/internal/authz/mock/simple_authz.go b/internal/authz/mock/simple_authz.go index 865871d962..74ea607585 100644 --- a/internal/authz/mock/simple_authz.go +++ b/internal/authz/mock/simple_authz.go @@ -64,7 +64,12 @@ func (_ *SimpleClient) DeleteUser(_ context.Context, _ string) error { // AssignmentsToProject implements authz.Client func (_ *SimpleClient) AssignmentsToProject(_ context.Context, _ uuid.UUID) ([]*minderv1.RoleAssignment, error) { - return nil, nil + return []*minderv1.RoleAssignment{}, nil +} + +// ProjectsForUser implements authz.Client +func (n *SimpleClient) ProjectsForUser(_ context.Context, _ string) ([]uuid.UUID, error) { + return n.Allowed, nil } // PrepareForRun implements authz.Client diff --git a/internal/controlplane/handlers_authz.go b/internal/controlplane/handlers_authz.go index c6fe6df1e0..b97774edf6 100644 --- a/internal/controlplane/handlers_authz.go +++ b/internal/controlplane/handlers_authz.go @@ -67,7 +67,7 @@ func EntityContextProjectInterceptor(ctx context.Context, req interface{}, info server := info.Server.(*Server) - ctx, err := populateEntityContext(ctx, server.store, request) + ctx, err := populateEntityContext(ctx, server.store, server.authzClient, request) if err != nil { return nil, err } @@ -112,12 +112,17 @@ func ProjectAuthorizationInterceptor(ctx context.Context, req interface{}, info // populateEntityContext populates the project in the entity context, by looking at the proto context or // fetching the default project -func populateEntityContext(ctx context.Context, store db.Store, in HasProtoContext) (context.Context, error) { +func populateEntityContext( + ctx context.Context, + store db.Store, + authzClient authz.Client, + in HasProtoContext, +) (context.Context, error) { if in.GetContext() == nil { return ctx, fmt.Errorf("context cannot be nil") } - projectID, err := getProjectFromRequestOrDefault(ctx, store, in) + projectID, err := getProjectFromRequestOrDefault(ctx, store, authzClient, in) if err != nil { return ctx, err } @@ -137,7 +142,12 @@ func populateEntityContext(ctx context.Context, store db.Store, in HasProtoConte return engine.WithEntityContext(ctx, entityCtx), nil } -func getProjectFromRequestOrDefault(ctx context.Context, store db.Store, in HasProtoContext) (uuid.UUID, error) { +func getProjectFromRequestOrDefault( + ctx context.Context, + store db.Store, + authzClient authz.Client, + in HasProtoContext, +) (uuid.UUID, error) { // Prefer the context message from the protobuf if in.GetContext().GetProject() != "" { requestedProject := in.GetContext().GetProject() @@ -152,17 +162,25 @@ func getProjectFromRequestOrDefault(ctx context.Context, store db.Store, in HasP userInfo, err := store.GetUserBySubject(ctx, subject) if err != nil { - return uuid.UUID{}, status.Errorf(codes.NotFound, "user not found") + // Note that we're revealing that the user is not registered in minder + // since the caller has a valid token (this is checked in earlier middleware). + // Therefore, we assume it's safe output that the user is not found. + return uuid.UUID{}, util.UserVisibleError(codes.NotFound, "user not found") } - projects, err := store.GetUserProjects(ctx, userInfo.ID) + projects, err := authzClient.ProjectsForUser(ctx, userInfo.IdentitySubject) if err != nil { - return uuid.UUID{}, status.Errorf(codes.NotFound, "cannot find projects for user") + return uuid.UUID{}, status.Errorf(codes.Internal, "cannot find projects for user") + } + + if len(projects) == 0 { + return uuid.UUID{}, util.UserVisibleError(codes.PermissionDenied, "User has no role grants in projects") } if len(projects) != 1 { - return uuid.UUID{}, status.Errorf(codes.InvalidArgument, "cannot get default project") + return uuid.UUID{}, util.UserVisibleError(codes.PermissionDenied, "Cannot determine default project. Please specify one.") } - return projects[0].ID, nil + + return projects[0], nil } // Permissions API diff --git a/internal/controlplane/handlers_authz_test.go b/internal/controlplane/handlers_authz_test.go index f9e5b235ef..3f0f9f38da 100644 --- a/internal/controlplane/handlers_authz_test.go +++ b/internal/controlplane/handlers_authz_test.go @@ -21,6 +21,7 @@ import ( "github.com/golang/mock/gomock" "github.com/google/uuid" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" @@ -64,8 +65,9 @@ func TestEntityContextProjectInterceptor(t *testing.T) { name string req any resource minder.TargetResource - buildStubs func(store *mockdb.MockStore) + buildStubs func(t *testing.T, store *mockdb.MockStore) rpcErr error + defaultProject bool expectedContext engine.EntityContext // Only if non-error }{ { @@ -102,18 +104,15 @@ func TestEntityContextProjectInterceptor(t *testing.T) { req: &request{ Context: &minder.Context{}, }, - resource: minder.TargetResource_TARGET_RESOURCE_PROJECT, - buildStubs: func(store *mockdb.MockStore) { + resource: minder.TargetResource_TARGET_RESOURCE_PROJECT, + defaultProject: true, + buildStubs: func(t *testing.T, store *mockdb.MockStore) { + t.Helper() store.EXPECT(). GetUserBySubject(gomock.Any(), subject). Return(db.User{ ID: 1, }, nil) - store.EXPECT(). - GetUserProjects(gomock.Any(), gomock.Any()). - Return([]db.GetUserProjectsRow{{ - ID: defaultProjectID, - }}, nil) }, expectedContext: engine.EntityContext{ // Uses the default project id @@ -164,11 +163,21 @@ func TestEntityContextProjectInterceptor(t *testing.T) { mockStore := mockdb.NewMockStore(ctrl) if tc.buildStubs != nil { - tc.buildStubs(mockStore) + tc.buildStubs(t, mockStore) } ctx := auth.WithUserSubjectContext(withRpcOptions(context.Background(), rpcOptions), subject) + + authzClient := &mock.SimpleClient{} + + if tc.defaultProject { + authzClient.Allowed = []uuid.UUID{defaultProjectID} + } else { + authzClient.Allowed = []uuid.UUID{projectID} + } + server := Server{ - store: mockStore, + store: mockStore, + authzClient: authzClient, } reply, err := EntityContextProjectInterceptor(ctx, tc.req, &grpc.UnaryServerInfo{ Server: &server, @@ -177,10 +186,8 @@ func TestEntityContextProjectInterceptor(t *testing.T) { assert.Equal(t, tc.rpcErr, err) return } - if err != nil { - t.Fatalf("unexpected error: %v", err) - } + require.NoError(t, err, "expected no error") assert.Equal(t, tc.expectedContext, reply.(replyType).Context) }) } diff --git a/internal/controlplane/handlers_user.go b/internal/controlplane/handlers_user.go index 5871140823..64f2a9738e 100644 --- a/internal/controlplane/handlers_user.go +++ b/internal/controlplane/handlers_user.go @@ -101,11 +101,6 @@ func (s *Server) CreateUser(ctx context.Context, return nil, status.Errorf(codes.Internal, "failed to create user: %s", err) } - _, err = qtx.AddUserProject(ctx, db.AddUserProjectParams{UserID: user.ID, ProjectID: userProject}) - if err != nil { - return nil, status.Errorf(codes.Internal, "failed to add user to project: %s", err) - } - err = s.store.Commit(tx) if err != nil { return nil, status.Errorf(codes.Internal, "failed to commit transaction: %s", err) @@ -189,20 +184,25 @@ func (s *Server) DeleteUser(ctx context.Context, return &pb.DeleteUserResponse{}, nil } -func getUserDependencies(ctx context.Context, store db.Store, user db.User) ([]*pb.Project, error) { +func (s *Server) getUserDependencies(ctx context.Context, user db.User) ([]*pb.Project, error) { // get all the projects associated with that user - projects, err := store.GetUserProjects(ctx, user.ID) + projects, err := s.authzClient.ProjectsForUser(ctx, user.IdentitySubject) if err != nil { return nil, err } var projectsPB []*pb.Project for _, proj := range projects { + pinfo, err := s.store.GetProjectByID(ctx, proj) + if err != nil { + return nil, err + } + projectsPB = append(projectsPB, &pb.Project{ - ProjectId: proj.ID.String(), - Name: proj.Name, - CreatedAt: timestamppb.New(proj.CreatedAt), - UpdatedAt: timestamppb.New(proj.UpdatedAt), + ProjectId: proj.String(), + Name: pinfo.Name, + CreatedAt: timestamppb.New(pinfo.CreatedAt), + UpdatedAt: timestamppb.New(pinfo.UpdatedAt), }) } @@ -240,7 +240,7 @@ func (s *Server) GetUser(ctx context.Context, _ *pb.GetUserRequest) (*pb.GetUser UpdatedAt: timestamppb.New(user.UpdatedAt), } - projects, err := getUserDependencies(ctx, s.store, user) + projects, err := s.getUserDependencies(ctx, user) if err != nil { return nil, status.Errorf(codes.Unknown, "failed to get user dependencies: %s", err) } diff --git a/internal/controlplane/handlers_user_test.go b/internal/controlplane/handlers_user_test.go index 1f34055757..c51f3bafd7 100644 --- a/internal/controlplane/handlers_user_test.go +++ b/internal/controlplane/handlers_user_test.go @@ -91,7 +91,6 @@ func TestCreateUserDBMock(t *testing.T) { CreateUser(gomock.Any(), db.CreateUserParams{OrganizationID: orgID, IdentitySubject: "subject1"}). Return(returnedUser, nil) - store.EXPECT().AddUserProject(gomock.Any(), db.AddUserProjectParams{UserID: 1, ProjectID: projectID}) store.EXPECT().Commit(gomock.Any()) store.EXPECT().Rollback(gomock.Any()) tokenResult, _ := openid.NewBuilder().GivenName("Foo").FamilyName("Bar").Email("test@stacklok.com").Subject("subject1").Build() @@ -185,7 +184,6 @@ func TestCreateUser_gRPC(t *testing.T) { UpdatedAt: time.Now(), }, nil). Times(1) - store.EXPECT().AddUserProject(gomock.Any(), db.AddUserProjectParams{UserID: 1, ProjectID: projectID}) store.EXPECT().Commit(gomock.Any()) store.EXPECT().Rollback(gomock.Any()) jwt.EXPECT().ParseAndValidate(gomock.Any()).Return(openid.New(), nil) diff --git a/internal/db/models.go b/internal/db/models.go index 169a9e8133..839e0cc7eb 100644 --- a/internal/db/models.go +++ b/internal/db/models.go @@ -493,9 +493,3 @@ type User struct { CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } - -type UserProject struct { - ID int32 `json:"id"` - UserID int32 `json:"user_id"` - ProjectID uuid.UUID `json:"project_id"` -} diff --git a/internal/db/querier.go b/internal/db/querier.go index da78332a14..0232f76bd9 100644 --- a/internal/db/querier.go +++ b/internal/db/querier.go @@ -12,7 +12,6 @@ import ( ) type Querier interface { - AddUserProject(ctx context.Context, arg AddUserProjectParams) (UserProject, error) CountProfilesByEntityType(ctx context.Context) ([]CountProfilesByEntityTypeRow, error) CountProfilesByName(ctx context.Context, name string) (int64, error) CountRepositories(ctx context.Context) (int64, error) @@ -96,7 +95,6 @@ type Querier interface { GetSessionStateByProjectID(ctx context.Context, projectID uuid.UUID) (SessionStore, error) GetUserByID(ctx context.Context, id int32) (User, error) GetUserBySubject(ctx context.Context, identitySubject string) (User, error) - GetUserProjects(ctx context.Context, userID int32) ([]GetUserProjectsRow, error) GlobalListProviders(ctx context.Context) ([]Provider, error) ListAllRepositories(ctx context.Context, provider string) ([]Repository, error) ListArtifactVersionsByArtifactID(ctx context.Context, arg ListArtifactVersionsByArtifactIDParams) ([]ArtifactVersion, error) @@ -117,7 +115,6 @@ type Querier interface { ListRuleTypesByProviderAndProject(ctx context.Context, arg ListRuleTypesByProviderAndProjectParams) ([]RuleType, error) ListUsers(ctx context.Context, arg ListUsersParams) ([]User, error) ListUsersByOrganization(ctx context.Context, arg ListUsersByOrganizationParams) ([]User, error) - ListUsersByProject(ctx context.Context, arg ListUsersByProjectParams) ([]User, error) // LockIfThresholdNotExceeded is used to lock an entity for execution. It will // attempt to insert or update the entity_execution_lock table only if the // last_lock_time is older than the threshold. If the lock is successful, it diff --git a/internal/db/user_projects.sql.go b/internal/db/user_projects.sql.go deleted file mode 100644 index f774def6d9..0000000000 --- a/internal/db/user_projects.sql.go +++ /dev/null @@ -1,86 +0,0 @@ -// Code generated by sqlc. DO NOT EDIT. -// versions: -// sqlc v1.25.0 -// source: user_projects.sql - -package db - -import ( - "context" - "encoding/json" - "time" - - "github.com/google/uuid" -) - -const addUserProject = `-- name: AddUserProject :one -INSERT INTO user_projects ( - user_id, - project_id - ) VALUES ( - $1, $2 -) RETURNING id, user_id, project_id -` - -type AddUserProjectParams struct { - UserID int32 `json:"user_id"` - ProjectID uuid.UUID `json:"project_id"` -} - -func (q *Queries) AddUserProject(ctx context.Context, arg AddUserProjectParams) (UserProject, error) { - row := q.db.QueryRowContext(ctx, addUserProject, arg.UserID, arg.ProjectID) - var i UserProject - err := row.Scan(&i.ID, &i.UserID, &i.ProjectID) - return i, err -} - -const getUserProjects = `-- name: GetUserProjects :many -SELECT projects.id, name, is_organization, metadata, parent_id, created_at, updated_at, user_projects.id, user_id, project_id FROM projects INNER JOIN user_projects ON projects.id = user_projects.project_id WHERE user_projects.user_id = $1 -` - -type GetUserProjectsRow struct { - ID uuid.UUID `json:"id"` - Name string `json:"name"` - IsOrganization bool `json:"is_organization"` - Metadata json.RawMessage `json:"metadata"` - ParentID uuid.NullUUID `json:"parent_id"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` - ID_2 int32 `json:"id_2"` - UserID int32 `json:"user_id"` - ProjectID uuid.UUID `json:"project_id"` -} - -func (q *Queries) GetUserProjects(ctx context.Context, userID int32) ([]GetUserProjectsRow, error) { - rows, err := q.db.QueryContext(ctx, getUserProjects, userID) - if err != nil { - return nil, err - } - defer rows.Close() - items := []GetUserProjectsRow{} - for rows.Next() { - var i GetUserProjectsRow - if err := rows.Scan( - &i.ID, - &i.Name, - &i.IsOrganization, - &i.Metadata, - &i.ParentID, - &i.CreatedAt, - &i.UpdatedAt, - &i.ID_2, - &i.UserID, - &i.ProjectID, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} diff --git a/internal/db/users.sql.go b/internal/db/users.sql.go index 8d9a81dfbd..befa2a569f 100644 --- a/internal/db/users.sql.go +++ b/internal/db/users.sql.go @@ -170,47 +170,3 @@ func (q *Queries) ListUsersByOrganization(ctx context.Context, arg ListUsersByOr } return items, nil } - -const listUsersByProject = `-- name: ListUsersByProject :many -SELECT users.id, users.organization_id, users.identity_subject, users.created_at, users.updated_at FROM users -JOIN user_projects ON users.id = user_projects.user_id -WHERE user_projects.project_id = $1 -ORDER BY users.id -LIMIT $2 -OFFSET $3 -` - -type ListUsersByProjectParams struct { - ProjectID uuid.UUID `json:"project_id"` - Limit int32 `json:"limit"` - Offset int32 `json:"offset"` -} - -func (q *Queries) ListUsersByProject(ctx context.Context, arg ListUsersByProjectParams) ([]User, error) { - rows, err := q.db.QueryContext(ctx, listUsersByProject, arg.ProjectID, arg.Limit, arg.Offset) - if err != nil { - return nil, err - } - defer rows.Close() - items := []User{} - for rows.Next() { - var i User - if err := rows.Scan( - &i.ID, - &i.OrganizationID, - &i.IdentitySubject, - &i.CreatedAt, - &i.UpdatedAt, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -}