feat: allow session deletion without session token (#6889)

* fix: add resource owner of user and change the one of session to instance

* use user resource owner from session projection

* fix session permission check

* integration tests and fixes

* update api docs
This commit is contained in:
Livio Spring
2023-11-16 08:35:50 +02:00
committed by GitHub
parent 0948a0b9ae
commit 2e8c3b5a53
18 changed files with 448 additions and 301 deletions

View File

@@ -204,7 +204,7 @@ func TestCommands_CreateSession(t *testing.T) {
},
},
args{
ctx: authz.NewMockContext("", "org1", ""),
ctx: authz.NewMockContext("instance1", "", ""),
userAgent: &domain.UserAgent{
FingerprintID: gu.Ptr("fp1"),
IP: net.ParseIP("1.2.3.4"),
@@ -231,7 +231,7 @@ func TestCommands_CreateSession(t *testing.T) {
},
},
args{
ctx: authz.NewMockContext("", "org1", ""),
ctx: authz.NewMockContext("instance1", "", ""),
userAgent: &domain.UserAgent{
FingerprintID: gu.Ptr("fp1"),
IP: net.ParseIP("1.2.3.4"),
@@ -244,7 +244,7 @@ func TestCommands_CreateSession(t *testing.T) {
expectFilter(),
expectPush(
session.NewAddedEvent(context.Background(),
&session.NewAggregate("sessionID", "org1").Aggregate,
&session.NewAggregate("sessionID", "instance1").Aggregate,
&domain.UserAgent{
FingerprintID: gu.Ptr("fp1"),
IP: net.ParseIP("1.2.3.4"),
@@ -252,15 +252,15 @@ func TestCommands_CreateSession(t *testing.T) {
Header: http.Header{"foo": []string{"bar"}},
},
),
session.NewLifetimeSetEvent(context.Background(), &session.NewAggregate("sessionID", "org1").Aggregate, 10*time.Minute),
session.NewTokenSetEvent(context.Background(), &session.NewAggregate("sessionID", "org1").Aggregate,
session.NewLifetimeSetEvent(context.Background(), &session.NewAggregate("sessionID", "instance1").Aggregate, 10*time.Minute),
session.NewTokenSetEvent(context.Background(), &session.NewAggregate("sessionID", "instance1").Aggregate,
"tokenID",
),
),
},
res{
want: &SessionChanged{
ObjectDetails: &domain.ObjectDetails{ResourceOwner: "org1"},
ObjectDetails: &domain.ObjectDetails{ResourceOwner: "instance1"},
ID: "sessionID",
NewToken: "token",
},
@@ -326,7 +326,7 @@ func TestCommands_UpdateSession(t *testing.T) {
expectFilter(
eventFromEventPusher(
session.NewAddedEvent(context.Background(),
&session.NewAggregate("sessionID", "org1").Aggregate,
&session.NewAggregate("sessionID", "instance1").Aggregate,
&domain.UserAgent{
FingerprintID: gu.Ptr("fp1"),
IP: net.ParseIP("1.2.3.4"),
@@ -335,13 +335,11 @@ func TestCommands_UpdateSession(t *testing.T) {
},
)),
eventFromEventPusher(
session.NewTokenSetEvent(context.Background(), &session.NewAggregate("sessionID", "org1").Aggregate,
session.NewTokenSetEvent(context.Background(), &session.NewAggregate("sessionID", "instance1").Aggregate,
"tokenID")),
),
),
tokenVerifier: func(ctx context.Context, sessionToken, sessionID, tokenID string) (err error) {
return caos_errs.ThrowPermissionDenied(nil, "COMMAND-sGr42", "Errors.Session.Token.Invalid")
},
tokenVerifier: newMockTokenVerifierInvalid(),
},
args{
ctx: context.Background(),
@@ -359,7 +357,7 @@ func TestCommands_UpdateSession(t *testing.T) {
expectFilter(
eventFromEventPusher(
session.NewAddedEvent(context.Background(),
&session.NewAggregate("sessionID", "org1").Aggregate,
&session.NewAggregate("sessionID", "instance1").Aggregate,
&domain.UserAgent{
FingerprintID: gu.Ptr("fp1"),
IP: net.ParseIP("1.2.3.4"),
@@ -368,7 +366,7 @@ func TestCommands_UpdateSession(t *testing.T) {
},
)),
eventFromEventPusher(
session.NewTokenSetEvent(context.Background(), &session.NewAggregate("sessionID", "org1").Aggregate,
session.NewTokenSetEvent(context.Background(), &session.NewAggregate("sessionID", "instance1").Aggregate,
"tokenID")),
),
),
@@ -384,7 +382,7 @@ func TestCommands_UpdateSession(t *testing.T) {
res{
want: &SessionChanged{
ObjectDetails: &domain.ObjectDetails{
ResourceOwner: "org1",
ResourceOwner: "instance1",
},
ID: "sessionID",
NewToken: "",
@@ -463,7 +461,7 @@ func TestCommands_updateSession(t *testing.T) {
args{
ctx: context.Background(),
checks: &SessionCommands{
sessionWriteModel: NewSessionWriteModel("sessionID", "org1"),
sessionWriteModel: NewSessionWriteModel("sessionID", "instance1"),
sessionCommands: []SessionCommand{
func(ctx context.Context, cmd *SessionCommands) error {
return caos_errs.ThrowInternal(nil, "id", "check failed")
@@ -481,16 +479,16 @@ func TestCommands_updateSession(t *testing.T) {
eventstore: eventstoreExpect(t),
},
args{
ctx: context.Background(),
ctx: authz.NewMockContext("instance1", "", ""),
checks: &SessionCommands{
sessionWriteModel: NewSessionWriteModel("sessionID", "org1"),
sessionWriteModel: NewSessionWriteModel("sessionID", "instance1"),
sessionCommands: []SessionCommand{},
},
},
res{
want: &SessionChanged{
ObjectDetails: &domain.ObjectDetails{
ResourceOwner: "org1",
ResourceOwner: "instance1",
},
ID: "sessionID",
NewToken: "",
@@ -503,9 +501,9 @@ func TestCommands_updateSession(t *testing.T) {
eventstore: eventstoreExpect(t),
},
args{
ctx: context.Background(),
ctx: authz.NewMockContext("instance1", "", ""),
checks: &SessionCommands{
sessionWriteModel: NewSessionWriteModel("sessionID", "org1"),
sessionWriteModel: NewSessionWriteModel("sessionID", "instance1"),
sessionCommands: []SessionCommand{},
eventstore: eventstoreExpect(t),
createToken: func(sessionID string) (string, string, error) {
@@ -528,19 +526,19 @@ func TestCommands_updateSession(t *testing.T) {
fields{
eventstore: eventstoreExpect(t,
expectPush(
session.NewLifetimeSetEvent(context.Background(), &session.NewAggregate("sessionID", "org1").Aggregate,
session.NewLifetimeSetEvent(context.Background(), &session.NewAggregate("sessionID", "instance1").Aggregate,
10*time.Minute,
),
session.NewTokenSetEvent(context.Background(), &session.NewAggregate("sessionID", "org1").Aggregate,
session.NewTokenSetEvent(context.Background(), &session.NewAggregate("sessionID", "instance1").Aggregate,
"tokenID",
),
),
),
},
args{
ctx: context.Background(),
ctx: authz.NewMockContext("instance1", "", ""),
checks: &SessionCommands{
sessionWriteModel: NewSessionWriteModel("sessionID", "org1"),
sessionWriteModel: NewSessionWriteModel("sessionID", "instance1"),
sessionCommands: []SessionCommand{},
eventstore: eventstoreExpect(t),
createToken: func(sessionID string) (string, string, error) {
@@ -557,7 +555,7 @@ func TestCommands_updateSession(t *testing.T) {
res{
want: &SessionChanged{
ObjectDetails: &domain.ObjectDetails{
ResourceOwner: "org1",
ResourceOwner: "instance1",
},
ID: "sessionID",
NewToken: "token",
@@ -569,27 +567,27 @@ func TestCommands_updateSession(t *testing.T) {
fields{
eventstore: eventstoreExpect(t,
expectPush(
session.NewUserCheckedEvent(context.Background(), &session.NewAggregate("sessionID", "org1").Aggregate,
"userID", testNow,
session.NewUserCheckedEvent(context.Background(), &session.NewAggregate("sessionID", "instance1").Aggregate,
"userID", "org1", testNow,
),
session.NewPasswordCheckedEvent(context.Background(), &session.NewAggregate("sessionID", "org1").Aggregate,
session.NewPasswordCheckedEvent(context.Background(), &session.NewAggregate("sessionID", "instance1").Aggregate,
testNow,
),
session.NewMetadataSetEvent(context.Background(), &session.NewAggregate("sessionID", "org1").Aggregate,
session.NewMetadataSetEvent(context.Background(), &session.NewAggregate("sessionID", "instance1").Aggregate,
map[string][]byte{"key": []byte("value")},
),
session.NewTokenSetEvent(context.Background(), &session.NewAggregate("sessionID", "org1").Aggregate,
session.NewTokenSetEvent(context.Background(), &session.NewAggregate("sessionID", "instance1").Aggregate,
"tokenID",
),
),
),
},
args{
ctx: context.Background(),
ctx: authz.NewMockContext("instance1", "", ""),
checks: &SessionCommands{
sessionWriteModel: NewSessionWriteModel("sessionID", "org1"),
sessionWriteModel: NewSessionWriteModel("sessionID", "instance1"),
sessionCommands: []SessionCommand{
CheckUser("userID"),
CheckUser("userID", "org1"),
CheckPassword("password"),
},
eventstore: eventstoreExpect(t,
@@ -621,7 +619,7 @@ func TestCommands_updateSession(t *testing.T) {
res{
want: &SessionChanged{
ObjectDetails: &domain.ObjectDetails{
ResourceOwner: "org1",
ResourceOwner: "instance1",
},
ID: "sessionID",
NewToken: "token",
@@ -634,11 +632,11 @@ func TestCommands_updateSession(t *testing.T) {
eventstore: eventstoreExpect(t),
},
args{
ctx: context.Background(),
ctx: authz.NewMockContext("instance1", "", ""),
checks: &SessionCommands{
sessionWriteModel: NewSessionWriteModel("sessionID", "org1"),
sessionWriteModel: NewSessionWriteModel("sessionID", "instance1"),
sessionCommands: []SessionCommand{
CheckUser("userID"),
CheckUser("userID", "org1"),
CheckIntent("intent", "aW50ZW50"),
},
eventstore: eventstoreExpect(t,
@@ -673,11 +671,11 @@ func TestCommands_updateSession(t *testing.T) {
eventstore: eventstoreExpect(t),
},
args{
ctx: context.Background(),
ctx: authz.NewMockContext("instance1", "", ""),
checks: &SessionCommands{
sessionWriteModel: NewSessionWriteModel("sessionID", "org1"),
sessionWriteModel: NewSessionWriteModel("sessionID", "instance1"),
sessionCommands: []SessionCommand{
CheckUser("userID"),
CheckUser("userID", "org1"),
CheckIntent("intent", "aW50ZW50"),
},
eventstore: eventstoreExpect(t,
@@ -722,11 +720,11 @@ func TestCommands_updateSession(t *testing.T) {
eventstore: eventstoreExpect(t),
},
args{
ctx: context.Background(),
ctx: authz.NewMockContext("instance1", "", ""),
checks: &SessionCommands{
sessionWriteModel: NewSessionWriteModel("sessionID", "org1"),
sessionWriteModel: NewSessionWriteModel("sessionID", "instance1"),
sessionCommands: []SessionCommand{
CheckUser("userID"),
CheckUser("userID", "org1"),
CheckIntent("intent2", "aW50ZW50"),
},
eventstore: eventstoreExpect(t),
@@ -753,23 +751,23 @@ func TestCommands_updateSession(t *testing.T) {
fields{
eventstore: eventstoreExpect(t,
expectPush(
session.NewUserCheckedEvent(context.Background(), &session.NewAggregate("sessionID", "org1").Aggregate,
"userID", testNow),
session.NewIntentCheckedEvent(context.Background(), &session.NewAggregate("sessionID", "org1").Aggregate,
session.NewUserCheckedEvent(context.Background(), &session.NewAggregate("sessionID", "instance1").Aggregate,
"userID", "org1", testNow),
session.NewIntentCheckedEvent(context.Background(), &session.NewAggregate("sessionID", "instance1").Aggregate,
testNow),
session.NewMetadataSetEvent(context.Background(), &session.NewAggregate("sessionID", "org1").Aggregate,
session.NewMetadataSetEvent(context.Background(), &session.NewAggregate("sessionID", "instance1").Aggregate,
map[string][]byte{"key": []byte("value")}),
session.NewTokenSetEvent(context.Background(), &session.NewAggregate("sessionID", "org1").Aggregate,
session.NewTokenSetEvent(context.Background(), &session.NewAggregate("sessionID", "instance1").Aggregate,
"tokenID"),
),
),
},
args{
ctx: context.Background(),
ctx: authz.NewMockContext("instance1", "", ""),
checks: &SessionCommands{
sessionWriteModel: NewSessionWriteModel("sessionID", "org1"),
sessionWriteModel: NewSessionWriteModel("sessionID", "instance1"),
sessionCommands: []SessionCommand{
CheckUser("userID"),
CheckUser("userID", "org1"),
CheckIntent("intent", "aW50ZW50"),
},
eventstore: eventstoreExpect(t,
@@ -807,7 +805,7 @@ func TestCommands_updateSession(t *testing.T) {
res{
want: &SessionChanged{
ObjectDetails: &domain.ObjectDetails{
ResourceOwner: "org1",
ResourceOwner: "instance1",
},
ID: "sessionID",
NewToken: "token",
@@ -828,13 +826,13 @@ func TestCommands_updateSession(t *testing.T) {
}
func TestCheckTOTP(t *testing.T) {
ctx := authz.NewMockContext("", "org1", "user1")
ctx := authz.NewMockContext("instance1", "org1", "user1")
cryptoAlg := crypto.CreateMockEncryptionAlg(gomock.NewController(t))
key, secret, err := domain.NewTOTPKey("example.com", "user1", cryptoAlg)
require.NoError(t, err)
sessAgg := &session.NewAggregate("session1", "org1").Aggregate
sessAgg := &session.NewAggregate("session1", "instance1").Aggregate
userAgg := &user.NewAggregate("user1", "org1").Aggregate
code, err := totp.GenerateCode(key.Secret(), testNow)
@@ -961,8 +959,9 @@ func TestCheckTOTP(t *testing.T) {
func TestCommands_TerminateSession(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
tokenVerifier func(ctx context.Context, sessionToken, sessionID, tokenID string) (err error)
eventstore func(t *testing.T) *eventstore.Eventstore
tokenVerifier func(ctx context.Context, sessionToken, sessionID, tokenID string) (err error)
checkPermission domain.PermissionCheck
}
type args struct {
ctx context.Context
@@ -982,7 +981,7 @@ func TestCommands_TerminateSession(t *testing.T) {
{
"eventstore failed",
fields{
eventstore: eventstoreExpect(t,
eventstore: expectEventstore(
expectFilterError(caos_errs.ThrowInternal(nil, "id", "filter failed")),
),
},
@@ -996,11 +995,11 @@ func TestCommands_TerminateSession(t *testing.T) {
{
"invalid session token",
fields{
eventstore: eventstoreExpect(t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
session.NewAddedEvent(context.Background(),
&session.NewAggregate("sessionID", "org1").Aggregate,
&session.NewAggregate("sessionID", "instance1").Aggregate,
&domain.UserAgent{
FingerprintID: gu.Ptr("fp1"),
IP: net.ParseIP("1.2.3.4"),
@@ -1009,13 +1008,11 @@ func TestCommands_TerminateSession(t *testing.T) {
},
)),
eventFromEventPusher(
session.NewTokenSetEvent(context.Background(), &session.NewAggregate("sessionID", "org1").Aggregate,
session.NewTokenSetEvent(context.Background(), &session.NewAggregate("sessionID", "instance1").Aggregate,
"tokenID")),
),
),
tokenVerifier: func(ctx context.Context, sessionToken, sessionID, tokenID string) (err error) {
return caos_errs.ThrowPermissionDenied(nil, "COMMAND-sGr42", "Errors.Session.Token.Invalid")
},
tokenVerifier: newMockTokenVerifierInvalid(),
},
args{
ctx: context.Background(),
@@ -1027,13 +1024,13 @@ func TestCommands_TerminateSession(t *testing.T) {
},
},
{
"not active",
"missing permission",
fields{
eventstore: eventstoreExpect(t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
session.NewAddedEvent(context.Background(),
&session.NewAggregate("sessionID", "org1").Aggregate,
&session.NewAggregate("sessionID", "instance1").Aggregate,
&domain.UserAgent{
FingerprintID: gu.Ptr("fp1"),
IP: net.ParseIP("1.2.3.4"),
@@ -1042,10 +1039,41 @@ func TestCommands_TerminateSession(t *testing.T) {
},
)),
eventFromEventPusher(
session.NewTokenSetEvent(context.Background(), &session.NewAggregate("sessionID", "org1").Aggregate,
session.NewTokenSetEvent(context.Background(), &session.NewAggregate("sessionID", "instance1").Aggregate,
"tokenID")),
),
),
checkPermission: newMockPermissionCheckNotAllowed(),
},
args{
ctx: context.Background(),
sessionID: "sessionID",
sessionToken: "",
},
res{
err: caos_errs.ThrowPermissionDenied(nil, "AUTHZ-HKJD33", "Errors.PermissionDenied"),
},
},
{
"not active",
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.NewTokenSetEvent(context.Background(), &session.NewAggregate("sessionID", "instance1").Aggregate,
"tokenID")),
eventFromEventPusher(
session.NewTerminateEvent(context.Background(), &session.NewAggregate("sessionID", "org1").Aggregate)),
session.NewTerminateEvent(context.Background(), &session.NewAggregate("sessionID", "instance1").Aggregate)),
),
),
tokenVerifier: func(ctx context.Context, sessionToken, sessionID, tokenID string) (err error) {
@@ -1059,18 +1087,18 @@ func TestCommands_TerminateSession(t *testing.T) {
},
res{
want: &domain.ObjectDetails{
ResourceOwner: "org1",
ResourceOwner: "instance1",
},
},
},
{
"push failed",
fields{
eventstore: eventstoreExpect(t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
session.NewAddedEvent(context.Background(),
&session.NewAggregate("sessionID", "org1").Aggregate,
&session.NewAggregate("sessionID", "instance1").Aggregate,
&domain.UserAgent{
FingerprintID: gu.Ptr("fp1"),
IP: net.ParseIP("1.2.3.4"),
@@ -1079,13 +1107,13 @@ func TestCommands_TerminateSession(t *testing.T) {
},
)),
eventFromEventPusher(
session.NewTokenSetEvent(context.Background(), &session.NewAggregate("sessionID", "org1").Aggregate,
session.NewTokenSetEvent(context.Background(), &session.NewAggregate("sessionID", "instance1").Aggregate,
"tokenID"),
),
),
expectPushFailed(
caos_errs.ThrowInternal(nil, "id", "pushed failed"),
session.NewTerminateEvent(context.Background(), &session.NewAggregate("sessionID", "org1").Aggregate),
session.NewTerminateEvent(context.Background(), &session.NewAggregate("sessionID", "instance1").Aggregate),
),
),
tokenVerifier: func(ctx context.Context, sessionToken, sessionID, tokenID string) (err error) {
@@ -1102,13 +1130,13 @@ func TestCommands_TerminateSession(t *testing.T) {
},
},
{
"terminate",
"terminate with token",
fields{
eventstore: eventstoreExpect(t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
session.NewAddedEvent(context.Background(),
&session.NewAggregate("sessionID", "org1").Aggregate,
&session.NewAggregate("sessionID", "instance1").Aggregate,
&domain.UserAgent{
FingerprintID: gu.Ptr("fp1"),
IP: net.ParseIP("1.2.3.4"),
@@ -1117,12 +1145,12 @@ func TestCommands_TerminateSession(t *testing.T) {
},
)),
eventFromEventPusher(
session.NewTokenSetEvent(context.Background(), &session.NewAggregate("sessionID", "org1").Aggregate,
session.NewTokenSetEvent(context.Background(), &session.NewAggregate("sessionID", "instance1").Aggregate,
"tokenID"),
),
),
expectPush(
session.NewTerminateEvent(context.Background(), &session.NewAggregate("sessionID", "org1").Aggregate),
session.NewTerminateEvent(context.Background(), &session.NewAggregate("sessionID", "instance1").Aggregate),
),
),
tokenVerifier: func(ctx context.Context, sessionToken, sessionID, tokenID string) (err error) {
@@ -1136,7 +1164,90 @@ func TestCommands_TerminateSession(t *testing.T) {
},
res{
want: &domain.ObjectDetails{
ResourceOwner: "org1",
ResourceOwner: "instance1",
},
},
},
{
"terminate own session",
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", "instance1").Aggregate,
"user1", "org1", testNow),
),
eventFromEventPusher(
session.NewTokenSetEvent(context.Background(), &session.NewAggregate("sessionID", "instance1").Aggregate,
"tokenID"),
),
),
expectPush(
session.NewTerminateEvent(authz.NewMockContext("instance1", "org1", "user1"), &session.NewAggregate("sessionID", "instance1").Aggregate),
),
),
},
args{
ctx: authz.NewMockContext("instance1", "org1", "user1"),
sessionID: "sessionID",
sessionToken: "",
},
res{
want: &domain.ObjectDetails{
ResourceOwner: "instance1",
},
},
},
{
"terminate 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", "instance1").Aggregate,
"userID", "org1", testNow),
),
eventFromEventPusher(
session.NewTokenSetEvent(context.Background(), &session.NewAggregate("sessionID", "instance1").Aggregate,
"tokenID"),
),
),
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",
},
},
},
@@ -1144,8 +1255,9 @@ func TestCommands_TerminateSession(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &Commands{
eventstore: tt.fields.eventstore,
eventstore: tt.fields.eventstore(t),
sessionTokenVerifier: tt.fields.tokenVerifier,
checkPermission: tt.fields.checkPermission,
}
got, err := c.TerminateSession(tt.args.ctx, tt.args.sessionID, tt.args.sessionToken)
require.ErrorIs(t, err, tt.res.err)