Skip to content

Commit 1b6c0aa

Browse files
authored
feat(controlplane): scope invitation system (#553)
Signed-off-by: Miguel Martinez Trivino <[email protected]>
1 parent aaabbc6 commit 1b6c0aa

File tree

13 files changed

+167
-124
lines changed

13 files changed

+167
-124
lines changed

app/cli/cmd/organization_invitation_create.go

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,13 @@ import (
2323
)
2424

2525
func newOrganizationInvitationCreateCmd() *cobra.Command {
26-
var receiverEmail, organizationID string
26+
var receiverEmail string
2727
cmd := &cobra.Command{
2828
Use: "create",
2929
Short: "Invite a User to an Organization",
3030
RunE: func(cmd *cobra.Command, args []string) error {
3131
res, err := action.NewOrgInvitationCreate(actionOpts).Run(
32-
context.Background(), organizationID, receiverEmail)
32+
context.Background(), receiverEmail)
3333
if err != nil {
3434
return err
3535
}
@@ -42,9 +42,5 @@ func newOrganizationInvitationCreateCmd() *cobra.Command {
4242
err := cmd.MarkFlagRequired("receiver")
4343
cobra.CheckErr(err)
4444

45-
cmd.Flags().StringVar(&organizationID, "organization", "", "ID of the organization to invite the user to")
46-
err = cmd.MarkFlagRequired("organization")
47-
cobra.CheckErr(err)
48-
4945
return cmd
5046
}

app/cli/cmd/organization_invitation_list_sent.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,10 @@ func orgInvitationTableOutput(items []*action.OrgInvitationItem) error {
5050
}
5151

5252
t := newTableWriter()
53-
t.AppendHeader(table.Row{"ID", "Org Name", "Receiver Email", "Status", "Created At"})
53+
t.AppendHeader(table.Row{"ID", "Receiver Email", "Status", "Created At"})
5454

5555
for _, i := range items {
56-
t.AppendRow(table.Row{i.ID, i.Organization.Name, i.ReceiverEmail, i.Status, i.CreatedAt.Format(time.RFC822)})
56+
t.AppendRow(table.Row{i.ID, i.ReceiverEmail, i.Status, i.CreatedAt.Format(time.RFC822)})
5757
t.AppendSeparator()
5858
}
5959

app/cli/internal/action/org_invitation_create.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//
2-
// Copyright 2023 The Chainloop Authors.
2+
// Copyright 2024 The Chainloop Authors.
33
//
44
// Licensed under the Apache License, Version 2.0 (the "License");
55
// you may not use this file except in compliance with the License.
@@ -29,11 +29,10 @@ func NewOrgInvitationCreate(cfg *ActionsOpts) *OrgInvitationCreate {
2929
return &OrgInvitationCreate{cfg}
3030
}
3131

32-
func (action *OrgInvitationCreate) Run(ctx context.Context, organization, receiver string) (*OrgInvitationItem, error) {
32+
func (action *OrgInvitationCreate) Run(ctx context.Context, receiver string) (*OrgInvitationItem, error) {
3333
client := pb.NewOrgInvitationServiceClient(action.cfg.CPConnection)
3434
resp, err := client.Create(ctx, &pb.OrgInvitationServiceCreateRequest{
35-
OrganizationId: organization,
36-
ReceiverEmail: receiver,
35+
ReceiverEmail: receiver,
3736
})
3837

3938
if err != nil {

app/controlplane/api/controlplane/v1/org_invitation.pb.go

Lines changed: 81 additions & 77 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/controlplane/api/controlplane/v1/org_invitation.pb.validate.go

Lines changed: 1 addition & 19 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/controlplane/api/controlplane/v1/org_invitation.proto

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@ syntax = "proto3";
1717

1818
package controlplane.v1;
1919

20+
import "controlplane/v1/response_messages.proto";
2021
import "google/protobuf/timestamp.proto";
2122
import "validate/validate.proto";
22-
import "controlplane/v1/response_messages.proto";
2323

2424
option go_package = "github.com/chainloop-dev/chainloop/app/controlplane/api/controlplane/v1;v1";
2525

@@ -33,7 +33,8 @@ service OrgInvitationService {
3333
}
3434

3535
message OrgInvitationServiceCreateRequest {
36-
string organization_id = 1 [(validate.rules).string.uuid = true];
36+
// organization is deprecated and not used anymore
37+
string organization_id = 1 [deprecated = true];
3738
string receiver_email = 2 [(validate.rules).string.email = true];
3839
}
3940

app/controlplane/api/gen/frontend/controlplane/v1/org_invitation.ts

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/controlplane/internal/biz/orginvitation.go

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//
2-
// Copyright 2023 The Chainloop Authors.
2+
// Copyright 2024 The Chainloop Authors.
33
//
44
// Licensed under the Apache License, Version 2.0 (the "License");
55
// you may not use this file except in compliance with the License.
@@ -48,7 +48,7 @@ type OrgInvitationRepo interface {
4848
PendingInvitation(ctx context.Context, orgID uuid.UUID, receiverEmail string) (*OrgInvitation, error)
4949
PendingInvitations(ctx context.Context, receiverEmail string) ([]*OrgInvitation, error)
5050
SoftDelete(ctx context.Context, id uuid.UUID) error
51-
ListBySender(ctx context.Context, sender uuid.UUID) ([]*OrgInvitation, error)
51+
ListBySenderAndOrg(ctx context.Context, sender, org uuid.UUID) ([]*OrgInvitation, error)
5252
ChangeStatus(ctx context.Context, ID uuid.UUID, status OrgInvitationStatus) error
5353
}
5454

@@ -87,14 +87,15 @@ func (uc *OrgInvitationUseCase) Create(ctx context.Context, orgID, senderID, rec
8787
return nil, NewErrValidationStr("sender and receiver emails cannot be the same")
8888
}
8989

90-
// 3 - Check if the user has permissions to invite to the org
90+
// 3 - Check that the user is a member of the given org
91+
// NOTE: this check is not necessary, as the user is already a member of the org
9192
if membership, err := uc.mRepo.FindByOrgAndUser(ctx, orgUUID, senderUUID); err != nil {
9293
return nil, fmt.Errorf("failed to find memberships: %w", err)
9394
} else if membership == nil {
9495
return nil, NewErrNotFound("user does not have permission to invite to this org")
9596
}
9697

97-
// 4 - The receiver does not exist in the org already
98+
// 4 - The receiver does exist in the org already
9899
memberships, err := uc.mRepo.FindByOrg(ctx, orgUUID)
99100
if err != nil {
100101
return nil, fmt.Errorf("error finding memberships for user %s: %w", senderUUID.String(), err)
@@ -116,7 +117,7 @@ func (uc *OrgInvitationUseCase) Create(ctx context.Context, orgID, senderID, rec
116117
return nil, NewErrValidationStr("invitation already exists for this user and org")
117118
}
118119

119-
// 5 - Create the invitation
120+
// 6 - Create the invitation
120121
invitation, err := uc.repo.Create(ctx, orgUUID, senderUUID, receiverEmail)
121122
if err != nil {
122123
return nil, fmt.Errorf("error creating invitation: %w", err)
@@ -125,13 +126,18 @@ func (uc *OrgInvitationUseCase) Create(ctx context.Context, orgID, senderID, rec
125126
return invitation, nil
126127
}
127128

128-
func (uc *OrgInvitationUseCase) ListBySender(ctx context.Context, senderID string) ([]*OrgInvitation, error) {
129+
func (uc *OrgInvitationUseCase) ListBySenderAndOrg(ctx context.Context, senderID, orgID string) ([]*OrgInvitation, error) {
129130
senderUUID, err := uuid.Parse(senderID)
130131
if err != nil {
131132
return nil, NewErrInvalidUUID(err)
132133
}
133134

134-
return uc.repo.ListBySender(ctx, senderUUID)
135+
orgUUID, err := uuid.Parse(orgID)
136+
if err != nil {
137+
return nil, NewErrInvalidUUID(err)
138+
}
139+
140+
return uc.repo.ListBySenderAndOrg(ctx, senderUUID, orgUUID)
135141
}
136142

137143
// Revoke an invitation by ID only if the user is the one who created it

app/controlplane/internal/biz/orginvitation_integration_test.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,46 @@ import (
3030

3131
const receiverEmail = "[email protected]"
3232

33+
func (s *OrgInvitationIntegrationTestSuite) TestList() {
34+
ctx := context.Background()
35+
inviteOrg1A, err := s.OrgInvitation.Create(ctx, s.org1.ID, s.user.ID, receiverEmail)
36+
s.NoError(err)
37+
inviteOrg1B, err := s.OrgInvitation.Create(ctx, s.org1.ID, s.user.ID, "[email protected]")
38+
s.NoError(err)
39+
inviteOrg2A, err := s.OrgInvitation.Create(ctx, s.org2.ID, s.user.ID, receiverEmail)
40+
s.NoError(err)
41+
42+
testCases := []struct {
43+
name string
44+
orgID string
45+
expected []*biz.OrgInvitation
46+
}{
47+
{
48+
name: "org1",
49+
orgID: s.org1.ID,
50+
expected: []*biz.OrgInvitation{inviteOrg1A, inviteOrg1B},
51+
},
52+
{
53+
name: "org2",
54+
orgID: s.org2.ID,
55+
expected: []*biz.OrgInvitation{inviteOrg2A},
56+
},
57+
{
58+
name: "org3",
59+
orgID: s.org3.ID,
60+
expected: []*biz.OrgInvitation{},
61+
},
62+
}
63+
64+
for _, tc := range testCases {
65+
s.T().Run(tc.name, func(t *testing.T) {
66+
invites, err := s.OrgInvitation.ListBySenderAndOrg(ctx, s.user.ID, tc.orgID)
67+
s.NoError(err)
68+
s.Equal(tc.expected, invites)
69+
})
70+
}
71+
}
72+
3373
func (s *OrgInvitationIntegrationTestSuite) TestCreate() {
3474
s.T().Run("invalid org ID", func(t *testing.T) {
3575
invite, err := s.OrgInvitation.Create(context.Background(), "deadbeef", s.user.ID, receiverEmail)

app/controlplane/internal/data/ent/migrate/migrations/20240229202352.sql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
ALTER TABLE "memberships" ADD COLUMN "role" character varying;
33

44
-- Set the existing memberships to the role "role:admin" to not to break compatibility
5-
UPDATE "memberships" SET "role" = 'role:admin';
5+
UPDATE "memberships" SET "role" = 'role:org:admin';
66

77
-- enable the not null constraint
88
ALTER TABLE "memberships" ALTER COLUMN "role" SET NOT NULL;

0 commit comments

Comments
 (0)