Skip to content

Commit 912b127

Browse files
authored
feat(events): Record on audit logs when user membership has been changed (#2278)
Signed-off-by: Javier Rodriguez <[email protected]>
1 parent 0a2046c commit 912b127

File tree

6 files changed

+100
-12
lines changed

6 files changed

+100
-12
lines changed

app/controlplane/cmd/wire_gen.go

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"ActionType": "RoleChanged",
3+
"TargetType": "User",
4+
"TargetID": "1089bb36-e27b-428b-8009-d015c8737c54",
5+
"ActorType": "USER",
6+
"ActorID": "1089bb36-e27b-428b-8009-d015c8737c54",
7+
"ActorEmail": "[email protected]",
8+
"OrgID": "1089bb36-e27b-428b-8009-d015c8737c54",
9+
"Description": "[email protected] role changed from 'role:org:owner' to 'role:org:member'",
10+
"Info": {
11+
"user_id": "1089bb36-e27b-428b-8009-d015c8737c54",
12+
"email": "[email protected]",
13+
"sso_groups": [
14+
"group1",
15+
"group2"
16+
],
17+
"old_role": "role:org:owner",
18+
"new_role": "role:org:member"
19+
},
20+
"Digest": "sha256:80bfd69a13fe4736ad2642ec20eaecc8f002e1774089d75d3c9fcee421af4247"
21+
}

app/controlplane/pkg/auditor/events/user.go

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,10 @@ var (
3131
)
3232

3333
const (
34-
UserType auditor.TargetType = "User"
35-
UserSignedUpActionType string = "SignedUp"
36-
UserLoggedInActionType string = "LoggedIn"
34+
UserType auditor.TargetType = "User"
35+
UserSignedUpActionType string = "SignedUp"
36+
UserLoggedInActionType string = "LoggedIn"
37+
UserRoleChangedActionType string = "RoleChanged"
3738
)
3839

3940
// UserBase is the base struct for policy events
@@ -96,3 +97,25 @@ func (p *UserLoggedIn) ActionInfo() (json.RawMessage, error) {
9697

9798
return json.Marshal(&p)
9899
}
100+
101+
type UserRoleChanged struct {
102+
*UserBase
103+
OldRole string `json:"old_role,omitempty"`
104+
NewRole string `json:"new_role,omitempty"`
105+
}
106+
107+
func (p *UserRoleChanged) ActionType() string {
108+
return UserRoleChangedActionType
109+
}
110+
111+
func (p *UserRoleChanged) Description() string {
112+
return fmt.Sprintf("%s role changed from '%s' to '%s'", p.Email, p.OldRole, p.NewRole)
113+
}
114+
115+
func (p *UserRoleChanged) ActionInfo() (json.RawMessage, error) {
116+
if p.UserID == nil || p.Email == "" || p.OldRole == "" || p.NewRole == "" {
117+
return nil, errors.New("user id, email, old role and new role are required")
118+
}
119+
120+
return json.Marshal(&p)
121+
}

app/controlplane/pkg/auditor/events/user_test.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import (
2424

2525
"github.com/chainloop-dev/chainloop/app/controlplane/pkg/auditor"
2626
"github.com/chainloop-dev/chainloop/app/controlplane/pkg/auditor/events"
27+
"github.com/chainloop-dev/chainloop/app/controlplane/pkg/authz"
28+
2729
"github.com/google/uuid"
2830
"github.com/stretchr/testify/assert"
2931
"github.com/stretchr/testify/require"
@@ -62,6 +64,19 @@ func TestUserEvents(t *testing.T) {
6264
},
6365
expected: "testdata/users/user_logs_in.json",
6466
},
67+
{
68+
name: "User role changed",
69+
event: &events.UserRoleChanged{
70+
UserBase: &events.UserBase{
71+
UserID: uuidPtr(userUUID),
72+
Email: testEmail,
73+
SSOGroups: []string{"group1", "group2"},
74+
},
75+
OldRole: string(authz.RoleOwner),
76+
NewRole: string(authz.RoleOrgMember),
77+
},
78+
expected: "testdata/users/user_role_changed.json",
79+
},
6580
}
6681

6782
for _, tt := range tests {

app/controlplane/pkg/biz/membership.go

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -82,14 +82,17 @@ type MembershipsRBAC interface {
8282
}
8383

8484
type MembershipUseCase struct {
85-
repo MembershipRepo
85+
logger *log.Helper
86+
// Repositories
87+
repo MembershipRepo
88+
userRepo UserRepo
89+
// Use Cases
8690
orgUseCase *OrganizationUseCase
87-
logger *log.Helper
8891
auditor *AuditorUseCase
8992
}
9093

91-
func NewMembershipUseCase(repo MembershipRepo, orgUC *OrganizationUseCase, auditor *AuditorUseCase, logger log.Logger) *MembershipUseCase {
92-
return &MembershipUseCase{repo: repo, orgUseCase: orgUC, logger: log.NewHelper(logger), auditor: auditor}
94+
func NewMembershipUseCase(repo MembershipRepo, orgUC *OrganizationUseCase, auditor *AuditorUseCase, userRepo UserRepo, logger log.Logger) *MembershipUseCase {
95+
return &MembershipUseCase{repo: repo, orgUseCase: orgUC, logger: log.NewHelper(logger), userRepo: userRepo, auditor: auditor}
9396
}
9497

9598
// LeaveAndDeleteOrg deletes a membership (and the org i) from the database associated with the current user
@@ -197,15 +200,41 @@ func (uc *MembershipUseCase) UpdateRole(ctx context.Context, orgID, userID, memb
197200
m, err := uc.repo.FindByIDInOrg(ctx, orgUUID, membershipUUID)
198201
if err != nil {
199202
return nil, fmt.Errorf("failed to find membership: %w", err)
200-
} else if m == nil {
203+
}
204+
205+
if m == nil {
201206
return nil, NewErrNotFound("membership")
202207
}
203208

204209
if m.User.ID == userID {
205210
return nil, NewErrValidationStr("cannot update yourself")
206211
}
207212

208-
return uc.repo.SetRole(ctx, membershipUUID, role)
213+
userUUID, err := uuid.Parse(m.User.ID)
214+
if err != nil {
215+
return nil, NewErrInvalidUUID(err)
216+
}
217+
218+
user, err := uc.userRepo.FindByID(ctx, userUUID)
219+
if err != nil {
220+
return nil, fmt.Errorf("failed to find user: %w", err)
221+
}
222+
223+
updatedMembership, err := uc.repo.SetRole(ctx, membershipUUID, role)
224+
if err != nil {
225+
return nil, fmt.Errorf("failed to update membership role: %w", err)
226+
}
227+
228+
uc.auditor.Dispatch(ctx, &events.UserRoleChanged{
229+
UserBase: &events.UserBase{
230+
UserID: &userUUID,
231+
Email: user.Email,
232+
},
233+
OldRole: string(m.Role),
234+
NewRole: string(role),
235+
}, &m.OrganizationID)
236+
237+
return updatedMembership, nil
209238
}
210239

211240
type membershipCreateOpts struct {

app/controlplane/pkg/biz/testhelpers/wire_gen.go

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

0 commit comments

Comments
 (0)