fix: correctly delete sessions created before 2.42 (#7050)

* fix: correctly delete sessions created before 2.42

* fix test

* fix linting

* fixes requested from review
This commit is contained in:
Livio Spring 2023-12-09 10:59:51 +02:00 committed by GitHub
parent aa3c352ae7
commit 831bb88ec4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 112 additions and 8 deletions

View File

@ -0,0 +1,41 @@
package command
import (
"github.com/zitadel/zitadel/internal/eventstore"
)
// resourceOwnerModel can be used to retrieve the resourceOwner of an aggregate
// by checking the first event it.
type resourceOwnerModel struct {
instanceID string
aggregateType eventstore.AggregateType
aggregateID string
resourceOwner string
}
func NewResourceOwnerModel(instanceID string, aggregateType eventstore.AggregateType, aggregateID string) *resourceOwnerModel {
return &resourceOwnerModel{
instanceID: instanceID,
aggregateType: aggregateType,
aggregateID: aggregateID,
}
}
func (r *resourceOwnerModel) Reduce() error {
return nil
}
func (r *resourceOwnerModel) AppendEvents(events ...eventstore.Event) {
if len(events) == 1 {
r.resourceOwner = events[0].Aggregate().ResourceOwner
}
}
func (r *resourceOwnerModel) Query() *eventstore.SearchQueryBuilder {
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
InstanceID(r.instanceID).
AddQuery().
AggregateTypes(r.aggregateType).
AggregateIDs(r.aggregateID).
Builder().
Limit(1)
}

View File

@ -400,7 +400,27 @@ func (c *Commands) checkSessionTerminationPermission(ctx context.Context, model
if model.UserID != "" && model.UserID == authz.GetCtxData(ctx).UserID {
return nil
}
return c.checkPermission(ctx, domain.PermissionSessionDelete, model.UserResourceOwner, model.UserID)
userResourceOwner, err := c.sessionUserResourceOwner(ctx, model)
if err != nil {
return err
}
return c.checkPermission(ctx, domain.PermissionSessionDelete, userResourceOwner, model.UserID)
}
// sessionUserResourceOwner will return the resourceOwner of the session form the [SessionWriteModel] or by additionally calling the eventstore,
// because before 2.42.0, the resourceOwner of a session used to be the organisation of the creator.
// Further the (checked) users organisation id was not stored.
// To be able to check the permission, we need to get the user's resourceOwner in this case.
func (c *Commands) sessionUserResourceOwner(ctx context.Context, model *SessionWriteModel) (string, error) {
if model.UserID == "" || model.UserResourceOwner != "" {
return model.UserResourceOwner, nil
}
r := NewResourceOwnerModel(authz.GetInstance(ctx).InstanceID(), user.AggregateType, model.UserID)
err := c.eventstore.FilterToQueryReducer(ctx, r)
if err != nil {
return "", err
}
return r.resourceOwner, nil
}
func sessionTokenCreator(idGenerator id.Generator, sessionAlg crypto.EncryptionAlgorithm) func(sessionID string) (id string, token string, err error) {

View File

@ -62,8 +62,7 @@ type SessionWriteModel struct {
func NewSessionWriteModel(sessionID string, instanceID string) *SessionWriteModel {
return &SessionWriteModel{
WriteModel: eventstore.WriteModel{
AggregateID: sessionID,
ResourceOwner: instanceID,
AggregateID: sessionID,
},
Metadata: make(map[string][]byte),
aggregate: &session.NewAggregate(sessionID, instanceID).Aggregate,

View File

@ -487,11 +487,9 @@ func TestCommands_updateSession(t *testing.T) {
},
res{
want: &SessionChanged{
ObjectDetails: &domain.ObjectDetails{
ResourceOwner: "instance1",
},
ID: "sessionID",
NewToken: "",
ObjectDetails: &domain.ObjectDetails{},
ID: "sessionID",
NewToken: "",
},
},
},
@ -1251,6 +1249,52 @@ func TestCommands_TerminateSession(t *testing.T) {
},
},
},
{
"terminate session owned by org with permission",
fields{
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
session.NewAddedEvent(context.Background(),
&session.NewAggregate("sessionID", "instance1").Aggregate,
&domain.UserAgent{
FingerprintID: gu.Ptr("fp1"),
IP: net.ParseIP("1.2.3.4"),
Description: gu.Ptr("firefox"),
Header: http.Header{"foo": []string{"bar"}},
},
),
),
eventFromEventPusher(
session.NewUserCheckedEvent(context.Background(), &session.NewAggregate("sessionID", "org2").Aggregate,
"userID", "", testNow),
),
eventFromEventPusher(
session.NewTokenSetEvent(context.Background(), &session.NewAggregate("sessionID", "org2").Aggregate,
"tokenID"),
),
),
expectFilter(
user.NewHumanAddedEvent(context.Background(), &user.NewAggregate("userID", "org1").Aggregate,
"username", "firstname", "lastname", "nickname", "displayname", language.English, domain.GenderUnspecified, "email", false),
),
expectPush(
session.NewTerminateEvent(authz.NewMockContext("instance1", "org1", "admin1"), &session.NewAggregate("sessionID", "instance1").Aggregate),
),
),
checkPermission: newMockPermissionCheckAllowed(),
},
args{
ctx: authz.NewMockContext("instance1", "org1", "admin1"),
sessionID: "sessionID",
sessionToken: "",
},
res{
want: &domain.ObjectDetails{
ResourceOwner: "instance1",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {