MCPcopy Create free account
hub / github.com/bytebase/bytebase / LeaveWorkspace

Method LeaveWorkspace

backend/api/v1/workspace_service.go:268–369  ·  view source on GitHub ↗

LeaveWorkspace removes the calling user from a workspace's IAM bindings, then switches to the next available workspace.

(ctx context.Context, req *connect.Request[v1pb.LeaveWorkspaceRequest])

Source from the content-addressed store, hash-verified

266// LeaveWorkspace removes the calling user from a workspace's IAM bindings,
267// then switches to the next available workspace.
268func (s *WorkspaceService) LeaveWorkspace(ctx context.Context, req *connect.Request[v1pb.LeaveWorkspaceRequest]) (*connect.Response[v1pb.LoginResponse], error) {
269 user, ok := GetUserFromContext(ctx)
270 if !ok || user == nil {
271 return nil, connect.NewError(connect.CodeUnauthenticated, errors.New("user not found"))
272 }
273
274 workspaceID, err := common.GetWorkspaceID(req.Msg.Name)
275 if err != nil {
276 return nil, connect.NewError(connect.CodeInvalidArgument, err)
277 }
278
279 // Verify the user is a member of the target workspace.
280 ws, err := s.store.FindWorkspace(ctx, &store.FindWorkspaceMessage{
281 WorkspaceID: &workspaceID,
282 Email: user.Email,
283 })
284 if err != nil {
285 return nil, connect.NewError(connect.CodeInternal, errors.Wrap(err, "failed to verify workspace membership"))
286 }
287 if ws == nil {
288 return nil, connect.NewError(connect.CodeNotFound, errors.Errorf("not a member of workspace %q", workspaceID))
289 }
290
291 memberIdentifier := common.FormatUserEmail(user.Email)
292
293 // Simulate removing the user from all direct IAM bindings and check
294 // that at least one active admin would remain. We exclude the caller
295 // from the admin list to also account for admin-via-group bindings.
296 policyMessage, err := s.store.GetWorkspaceIamPolicy(ctx, workspaceID)
297 if err != nil {
298 return nil, connect.NewError(connect.CodeInternal, errors.Wrap(err, "failed to get workspace IAM policy"))
299 }
300 simulatedPolicy := &storepb.IamPolicy{}
301 for _, binding := range policyMessage.Policy.Bindings {
302 var filtered []string
303 for _, m := range binding.Members {
304 if m != memberIdentifier {
305 filtered = append(filtered, m)
306 }
307 }
308 if len(filtered) > 0 {
309 simulatedPolicy.Bindings = append(simulatedPolicy.Bindings, &storepb.Binding{
310 Role: binding.Role,
311 Members: filtered,
312 Condition: binding.Condition,
313 })
314 }
315 }
316 admins := utils.GetUsersByRoleInIAMPolicy(ctx, s.store, workspaceID, store.WorkspaceAdminRole, !s.profile.SaaS, simulatedPolicy)
317 // Filter out the caller — they may still appear as admin via group bindings
318 // that haven't been removed yet.
319 var otherAdmins []*store.UserMessage
320 for _, admin := range admins {
321 if admin.Email != user.Email {
322 otherAdmins = append(otherAdmins, admin)
323 }
324 }
325 if !containsActiveEndUser(otherAdmins) {

Callers

nothing calls this directly

Calls 15

GetWorkspaceIDFunction · 0.92
FormatUserEmailFunction · 0.92
GetUserFromContextFunction · 0.85
containsActiveEndUserFunction · 0.85
ErrorfMethod · 0.80
GetWorkspaceIamPolicyMethod · 0.80
ReloadCacheMethod · 0.80

Tested by

no test coverage detected