feat: saml application configuration for login version (#9351)

# Which Problems Are Solved

OIDC applications can configure the used login version, which is
currently not possible for SAML applications.

# How the Problems Are Solved

Add the same functionality dependent on the feature-flag for SAML
applications.

# Additional Changes

None

# Additional Context

Closes #9267
Follow up issue for frontend changes #9354

---------

Co-authored-by: Livio Spring <livio.a@gmail.com>
This commit is contained in:
Stefan Benz
2025-02-13 17:03:05 +01:00
committed by GitHub
parent 66296db971
commit 49de5c61b2
40 changed files with 1051 additions and 240 deletions

View File

@@ -1264,10 +1264,10 @@ func TestCommandSide_RemoveOrg(t *testing.T) {
),
expectFilter(
eventFromEventPusher(
project.NewSAMLConfigAddedEvent(context.Background(), &project.NewAggregate("project1", "org1").Aggregate, "app1", "entity1", []byte{}, ""),
project.NewSAMLConfigAddedEvent(context.Background(), &project.NewAggregate("project1", "org1").Aggregate, "app1", "entity1", []byte{}, "", domain.LoginVersionUnspecified, ""),
),
eventFromEventPusher(
project.NewSAMLConfigAddedEvent(context.Background(), &project.NewAggregate("project2", "org1").Aggregate, "app2", "entity2", []byte{}, ""),
project.NewSAMLConfigAddedEvent(context.Background(), &project.NewAggregate("project2", "org1").Aggregate, "app2", "entity2", []byte{}, "", domain.LoginVersionUnspecified, ""),
),
),
expectPush(

View File

@@ -325,10 +325,10 @@ func (wm *OIDCApplicationWriteModel) NewChangedEvent(
changes = append(changes, project.ChangeBackChannelLogoutURI(backChannelLogoutURI))
}
if wm.LoginVersion != loginVersion {
changes = append(changes, project.ChangeLoginVersion(loginVersion))
changes = append(changes, project.ChangeOIDCLoginVersion(loginVersion))
}
if wm.LoginBaseURI != loginBaseURI {
changes = append(changes, project.ChangeLoginBaseURI(loginBaseURI))
changes = append(changes, project.ChangeOIDCLoginBaseURI(loginBaseURI))
}
if len(changes) == 0 {

View File

@@ -1297,8 +1297,8 @@ func newOIDCAppChangedEvent(ctx context.Context, appID, projectID, resourceOwner
project.ChangeIDTokenRoleAssertion(false),
project.ChangeIDTokenUserinfoAssertion(false),
project.ChangeClockSkew(time.Second * 2),
project.ChangeLoginVersion(domain.LoginVersion2),
project.ChangeLoginBaseURI("https://login.test.ch"),
project.ChangeOIDCLoginVersion(domain.LoginVersion2),
project.ChangeOIDCLoginBaseURI("https://login.test.ch"),
}
event, _ := project.NewOIDCConfigChangedEvent(ctx,
&project.NewAggregate(projectID, resourceOwner).Aggregate,

View File

@@ -79,6 +79,8 @@ func (c *Commands) addSAMLApplication(ctx context.Context, projectAgg *eventstor
string(entity.EntityID),
samlApp.Metadata,
samlApp.MetadataURL,
samlApp.LoginVersion,
samlApp.LoginBaseURI,
),
}, nil
}
@@ -119,7 +121,10 @@ func (c *Commands) ChangeSAMLApplication(ctx context.Context, samlApp *domain.SA
samlApp.AppID,
string(entity.EntityID),
samlApp.Metadata,
samlApp.MetadataURL)
samlApp.MetadataURL,
samlApp.LoginVersion,
samlApp.LoginBaseURI,
)
if err != nil {
return nil, err
}

View File

@@ -12,11 +12,13 @@ import (
type SAMLApplicationWriteModel struct {
eventstore.WriteModel
AppID string
AppName string
EntityID string
Metadata []byte
MetadataURL string
AppID string
AppName string
EntityID string
Metadata []byte
MetadataURL string
LoginVersion domain.LoginVersion
LoginBaseURI string
State domain.AppState
saml bool
@@ -121,6 +123,8 @@ func (wm *SAMLApplicationWriteModel) appendAddSAMLEvent(e *project.SAMLConfigAdd
wm.Metadata = e.Metadata
wm.MetadataURL = e.MetadataURL
wm.EntityID = e.EntityID
wm.LoginVersion = e.LoginVersion
wm.LoginBaseURI = e.LoginBaseURI
}
func (wm *SAMLApplicationWriteModel) appendChangeSAMLEvent(e *project.SAMLConfigChangedEvent) {
@@ -134,6 +138,12 @@ func (wm *SAMLApplicationWriteModel) appendChangeSAMLEvent(e *project.SAMLConfig
if e.EntityID != "" {
wm.EntityID = e.EntityID
}
if e.LoginVersion != nil {
wm.LoginVersion = *e.LoginVersion
}
if e.LoginBaseURI != nil {
wm.LoginBaseURI = *e.LoginBaseURI
}
}
func (wm *SAMLApplicationWriteModel) Query() *eventstore.SearchQueryBuilder {
@@ -161,6 +171,8 @@ func (wm *SAMLApplicationWriteModel) NewChangedEvent(
entityID string,
metadata []byte,
metadataURL string,
loginVersion domain.LoginVersion,
loginBaseURI string,
) (*project.SAMLConfigChangedEvent, bool, error) {
changes := make([]project.SAMLConfigChanges, 0)
var err error
@@ -173,6 +185,12 @@ func (wm *SAMLApplicationWriteModel) NewChangedEvent(
if wm.EntityID != entityID {
changes = append(changes, project.ChangeEntityID(entityID))
}
if wm.LoginVersion != loginVersion {
changes = append(changes, project.ChangeSAMLLoginVersion(loginVersion))
}
if wm.LoginBaseURI != loginBaseURI {
changes = append(changes, project.ChangeSAMLLoginBaseURI(loginBaseURI))
}
if len(changes) == 0 {
return nil, false, nil

View File

@@ -50,7 +50,7 @@ var testMetadataChangedEntityID = []byte(`<?xml version="1.0"?>
func TestCommandSide_AddSAMLApplication(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
eventstore func(t *testing.T) *eventstore.Eventstore
idGenerator id.Generator
httpClient *http.Client
}
@@ -72,9 +72,7 @@ func TestCommandSide_AddSAMLApplication(t *testing.T) {
{
name: "no aggregate id, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
eventstore: expectEventstore(),
},
args: args{
ctx: authz.WithInstanceID(context.Background(), "instanceID"),
@@ -88,8 +86,7 @@ func TestCommandSide_AddSAMLApplication(t *testing.T) {
{
name: "project not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(),
),
},
@@ -111,8 +108,7 @@ func TestCommandSide_AddSAMLApplication(t *testing.T) {
{
name: "invalid app, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
project.NewProjectAddedEvent(context.Background(),
@@ -141,8 +137,7 @@ func TestCommandSide_AddSAMLApplication(t *testing.T) {
{
name: "create saml app, metadata not parsable",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
project.NewProjectAddedEvent(context.Background(),
@@ -174,8 +169,7 @@ func TestCommandSide_AddSAMLApplication(t *testing.T) {
{
name: "create saml app, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
project.NewProjectAddedEvent(context.Background(),
@@ -196,6 +190,8 @@ func TestCommandSide_AddSAMLApplication(t *testing.T) {
"https://test.com/saml/metadata",
testMetadata,
"",
domain.LoginVersionUnspecified,
"",
),
),
),
@@ -229,11 +225,73 @@ func TestCommandSide_AddSAMLApplication(t *testing.T) {
},
},
},
{
name: "create saml app, loginversion, ok",
fields: fields{
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
project.NewProjectAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"project", true, true, true,
domain.PrivateLabelingSettingUnspecified),
),
),
expectPush(
project.NewApplicationAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"app1",
"app",
),
project.NewSAMLConfigAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"app1",
"https://test.com/saml/metadata",
testMetadata,
"",
domain.LoginVersion2,
"https://test.com/login",
),
),
),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "app1"),
},
args: args{
ctx: authz.WithInstanceID(context.Background(), "instanceID"),
samlApp: &domain.SAMLApp{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
},
AppName: "app",
EntityID: "https://test.com/saml/metadata",
Metadata: testMetadata,
MetadataURL: "",
LoginVersion: domain.LoginVersion2,
LoginBaseURI: "https://test.com/login",
},
resourceOwner: "org1",
},
res: res{
want: &domain.SAMLApp{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
ResourceOwner: "org1",
},
AppID: "app1",
AppName: "app",
EntityID: "https://test.com/saml/metadata",
Metadata: testMetadata,
MetadataURL: "",
State: domain.AppStateActive,
LoginVersion: domain.LoginVersion2,
LoginBaseURI: "https://test.com/login",
},
},
},
{
name: "create saml app metadataURL, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
project.NewProjectAddedEvent(context.Background(),
@@ -254,6 +312,8 @@ func TestCommandSide_AddSAMLApplication(t *testing.T) {
"https://test.com/saml/metadata",
testMetadata,
"http://localhost:8080/saml/metadata",
domain.LoginVersionUnspecified,
"",
),
),
),
@@ -291,8 +351,7 @@ func TestCommandSide_AddSAMLApplication(t *testing.T) {
{
name: "create saml app metadataURL, http error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
project.NewProjectAddedEvent(context.Background(),
@@ -327,7 +386,7 @@ func TestCommandSide_AddSAMLApplication(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),
idGenerator: tt.fields.idGenerator,
httpClient: tt.fields.httpClient,
}
@@ -348,7 +407,7 @@ func TestCommandSide_AddSAMLApplication(t *testing.T) {
func TestCommandSide_ChangeSAMLApplication(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
eventstore func(t *testing.T) *eventstore.Eventstore
httpClient *http.Client
}
type args struct {
@@ -369,9 +428,7 @@ func TestCommandSide_ChangeSAMLApplication(t *testing.T) {
{
name: "invalid app, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
eventstore: expectEventstore(),
},
args: args{
ctx: context.Background(),
@@ -390,9 +447,7 @@ func TestCommandSide_ChangeSAMLApplication(t *testing.T) {
{
name: "missing appid, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
eventstore: expectEventstore(),
},
args: args{
ctx: context.Background(),
@@ -412,9 +467,7 @@ func TestCommandSide_ChangeSAMLApplication(t *testing.T) {
{
name: "missing aggregateid, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
eventstore: expectEventstore(),
},
args: args{
ctx: context.Background(),
@@ -434,8 +487,7 @@ func TestCommandSide_ChangeSAMLApplication(t *testing.T) {
{
name: "app not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(),
),
},
@@ -457,8 +509,7 @@ func TestCommandSide_ChangeSAMLApplication(t *testing.T) {
{
name: "no changes, precondition error, metadataURL",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
project.NewApplicationAddedEvent(context.Background(),
@@ -474,6 +525,8 @@ func TestCommandSide_ChangeSAMLApplication(t *testing.T) {
"https://test.com/saml/metadata",
testMetadata,
"http://localhost:8080/saml/metadata",
domain.LoginVersionUnspecified,
"",
),
),
),
@@ -502,8 +555,7 @@ func TestCommandSide_ChangeSAMLApplication(t *testing.T) {
{
name: "no changes, precondition error, metadata",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
project.NewApplicationAddedEvent(context.Background(),
@@ -519,6 +571,8 @@ func TestCommandSide_ChangeSAMLApplication(t *testing.T) {
"https://test.com/saml/metadata",
testMetadata,
"",
domain.LoginVersionUnspecified,
"",
),
),
),
@@ -547,8 +601,7 @@ func TestCommandSide_ChangeSAMLApplication(t *testing.T) {
{
name: "change saml app, ok, metadataURL",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
project.NewApplicationAddedEvent(context.Background(),
@@ -564,6 +617,8 @@ func TestCommandSide_ChangeSAMLApplication(t *testing.T) {
"https://test.com/saml/metadata",
testMetadata,
"http://localhost:8080/saml/metadata",
domain.LoginVersionUnspecified,
"",
),
),
),
@@ -613,8 +668,7 @@ func TestCommandSide_ChangeSAMLApplication(t *testing.T) {
{
name: "change saml app, ok, metadata",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
project.NewApplicationAddedEvent(context.Background(),
@@ -630,6 +684,8 @@ func TestCommandSide_ChangeSAMLApplication(t *testing.T) {
"https://test.com/saml/metadata",
testMetadata,
"",
domain.LoginVersionUnspecified,
"",
),
),
),
@@ -675,13 +731,85 @@ func TestCommandSide_ChangeSAMLApplication(t *testing.T) {
State: domain.AppStateActive,
},
},
}, {
name: "change saml app, ok, loginversion",
fields: fields{
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
project.NewApplicationAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"app1",
"app",
),
),
eventFromEventPusher(
project.NewSAMLConfigAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"app1",
"https://test.com/saml/metadata",
testMetadata,
"",
domain.LoginVersionUnspecified,
"",
),
),
),
expectPush(
newSAMLAppChangedEventLoginVersion(context.Background(),
"app1",
"project1",
"org1",
"https://test.com/saml/metadata",
"https://test2.com/saml/metadata",
testMetadataChangedEntityID,
domain.LoginVersion2,
"https://test.com/login",
),
),
),
httpClient: nil,
},
args: args{
ctx: context.Background(),
samlApp: &domain.SAMLApp{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
ResourceOwner: "org1",
},
AppID: "app1",
AppName: "app",
EntityID: "https://test2.com/saml/metadata",
Metadata: testMetadataChangedEntityID,
MetadataURL: "",
LoginVersion: domain.LoginVersion2,
LoginBaseURI: "https://test.com/login",
},
resourceOwner: "org1",
},
res: res{
want: &domain.SAMLApp{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
ResourceOwner: "org1",
},
AppID: "app1",
AppName: "app",
EntityID: "https://test2.com/saml/metadata",
Metadata: testMetadataChangedEntityID,
MetadataURL: "",
State: domain.AppStateActive,
LoginVersion: domain.LoginVersion2,
LoginBaseURI: "https://test.com/login",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
eventstore: tt.fields.eventstore(t),
httpClient: tt.fields.httpClient,
}
got, err := r.ChangeSAMLApplication(tt.args.ctx, tt.args.samlApp, tt.args.resourceOwner)
@@ -726,6 +854,22 @@ func newSAMLAppChangedEventMetadataURL(ctx context.Context, appID, projectID, re
return event
}
func newSAMLAppChangedEventLoginVersion(ctx context.Context, appID, projectID, resourceOwner, oldEntityID, entityID string, metadata []byte, loginVersion domain.LoginVersion, loginURI string) *project.SAMLConfigChangedEvent {
changes := []project.SAMLConfigChanges{
project.ChangeEntityID(entityID),
project.ChangeMetadata(metadata),
project.ChangeSAMLLoginVersion(loginVersion),
project.ChangeSAMLLoginBaseURI(loginURI),
}
event, _ := project.NewSAMLConfigChangedEvent(ctx,
&project.NewAggregate(projectID, resourceOwner).Aggregate,
appID,
oldEntityID,
changes,
)
return event
}
type roundTripperFunc func(*http.Request) *http.Response
// RoundTrip implements the http.RoundTripper interface.

View File

@@ -596,6 +596,8 @@ func TestCommandSide_RemoveApplication(t *testing.T) {
"https://test.com/saml/metadata",
[]byte("<?xml version=\"1.0\"?>\n<md:EntityDescriptor xmlns:md=\"urn:oasis:names:tc:SAML:2.0:metadata\"\n validUntil=\"2022-08-26T14:08:16Z\"\n cacheDuration=\"PT604800S\"\n entityID=\"https://test.com/saml/metadata\">\n <md:SPSSODescriptor AuthnRequestsSigned=\"false\" WantAssertionsSigned=\"false\" protocolSupportEnumeration=\"urn:oasis:names:tc:SAML:2.0:protocol\">\n <md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</md:NameIDFormat>\n <md:AssertionConsumerService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\"\n Location=\"https://test.com/saml/acs\"\n index=\"1\" />\n \n </md:SPSSODescriptor>\n</md:EntityDescriptor>"),
"",
domain.LoginVersionUnspecified,
"",
)),
),
expectPush(

View File

@@ -55,13 +55,15 @@ func oidcWriteModelToOIDCConfig(writeModel *OIDCApplicationWriteModel) *domain.O
func samlWriteModelToSAMLConfig(writeModel *SAMLApplicationWriteModel) *domain.SAMLApp {
return &domain.SAMLApp{
ObjectRoot: writeModelToObjectRoot(writeModel.WriteModel),
AppID: writeModel.AppID,
AppName: writeModel.AppName,
State: writeModel.State,
Metadata: writeModel.Metadata,
MetadataURL: writeModel.MetadataURL,
EntityID: writeModel.EntityID,
ObjectRoot: writeModelToObjectRoot(writeModel.WriteModel),
AppID: writeModel.AppID,
AppName: writeModel.AppName,
State: writeModel.State,
Metadata: writeModel.Metadata,
MetadataURL: writeModel.MetadataURL,
EntityID: writeModel.EntityID,
LoginVersion: writeModel.LoginVersion,
LoginBaseURI: writeModel.LoginBaseURI,
}
}

View File

@@ -988,6 +988,8 @@ func TestCommandSide_RemoveProject(t *testing.T) {
"https://test.com/saml/metadata",
[]byte("<?xml version=\"1.0\"?>\n<md:EntityDescriptor xmlns:md=\"urn:oasis:names:tc:SAML:2.0:metadata\"\n validUntil=\"2022-08-26T14:08:16Z\"\n cacheDuration=\"PT604800S\"\n entityID=\"https://test.com/saml/metadata\">\n <md:SPSSODescriptor AuthnRequestsSigned=\"false\" WantAssertionsSigned=\"false\" protocolSupportEnumeration=\"urn:oasis:names:tc:SAML:2.0:protocol\">\n <md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</md:NameIDFormat>\n <md:AssertionConsumerService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\"\n Location=\"https://test.com/saml/acs\"\n index=\"1\" />\n \n </md:SPSSODescriptor>\n</md:EntityDescriptor>"),
"http://localhost:8080/saml/metadata",
domain.LoginVersionUnspecified,
"",
),
),
),
@@ -1039,6 +1041,8 @@ func TestCommandSide_RemoveProject(t *testing.T) {
"https://test1.com/saml/metadata",
[]byte("<?xml version=\"1.0\"?>\n<md:EntityDescriptor xmlns:md=\"urn:oasis:names:tc:SAML:2.0:metadata\"\n validUntil=\"2022-08-26T14:08:16Z\"\n cacheDuration=\"PT604800S\"\n entityID=\"https://test.com/saml/metadata\">\n <md:SPSSODescriptor AuthnRequestsSigned=\"false\" WantAssertionsSigned=\"false\" protocolSupportEnumeration=\"urn:oasis:names:tc:SAML:2.0:protocol\">\n <md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</md:NameIDFormat>\n <md:AssertionConsumerService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\"\n Location=\"https://test.com/saml/acs\"\n index=\"1\" />\n \n </md:SPSSODescriptor>\n</md:EntityDescriptor>"),
"",
domain.LoginVersionUnspecified,
"",
),
),
eventFromEventPusher(project.NewApplicationAddedEvent(context.Background(),
@@ -1053,6 +1057,8 @@ func TestCommandSide_RemoveProject(t *testing.T) {
"https://test2.com/saml/metadata",
[]byte("<?xml version=\"1.0\"?>\n<md:EntityDescriptor xmlns:md=\"urn:oasis:names:tc:SAML:2.0:metadata\"\n validUntil=\"2022-08-26T14:08:16Z\"\n cacheDuration=\"PT604800S\"\n entityID=\"https://test.com/saml/metadata\">\n <md:SPSSODescriptor AuthnRequestsSigned=\"false\" WantAssertionsSigned=\"false\" protocolSupportEnumeration=\"urn:oasis:names:tc:SAML:2.0:protocol\">\n <md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</md:NameIDFormat>\n <md:AssertionConsumerService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\"\n Location=\"https://test.com/saml/acs\"\n index=\"1\" />\n \n </md:SPSSODescriptor>\n</md:EntityDescriptor>"),
"",
domain.LoginVersionUnspecified,
"",
),
),
eventFromEventPusher(project.NewApplicationAddedEvent(context.Background(),
@@ -1067,6 +1073,8 @@ func TestCommandSide_RemoveProject(t *testing.T) {
"https://test3.com/saml/metadata",
[]byte("<?xml version=\"1.0\"?>\n<md:EntityDescriptor xmlns:md=\"urn:oasis:names:tc:SAML:2.0:metadata\"\n validUntil=\"2022-08-26T14:08:16Z\"\n cacheDuration=\"PT604800S\"\n entityID=\"https://test.com/saml/metadata\">\n <md:SPSSODescriptor AuthnRequestsSigned=\"false\" WantAssertionsSigned=\"false\" protocolSupportEnumeration=\"urn:oasis:names:tc:SAML:2.0:protocol\">\n <md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</md:NameIDFormat>\n <md:AssertionConsumerService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\"\n Location=\"https://test.com/saml/acs\"\n index=\"1\" />\n \n </md:SPSSODescriptor>\n</md:EntityDescriptor>"),
"",
domain.LoginVersionUnspecified,
"",
),
),
),

View File

@@ -75,7 +75,9 @@ func (c *Commands) LinkSessionToSAMLRequest(ctx context.Context, id, sessionID,
return nil, nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-ttPKNdAIFT", "Errors.SAMLRequest.AlreadyHandled")
}
if checkLoginClient && authz.GetCtxData(ctx).UserID != writeModel.LoginClient {
return nil, nil, zerrors.ThrowPermissionDenied(nil, "COMMAND-KCd48Rxt7x", "Errors.SAMLRequest.WrongLoginClient")
if err := c.checkPermission(ctx, domain.PermissionSessionLink, writeModel.ResourceOwner, ""); err != nil {
return nil, nil, err
}
}
sessionWriteModel := NewSessionWriteModel(sessionID, authz.GetInstance(ctx).InstanceID())
err = c.eventstore.FilterToQueryReducer(ctx, sessionWriteModel)

View File

@@ -132,8 +132,9 @@ func TestCommands_AddSAMLRequest(t *testing.T) {
func TestCommands_LinkSessionToSAMLRequest(t *testing.T) {
mockCtx := authz.NewMockContext("instanceID", "orgID", "loginClient")
type fields struct {
eventstore func(t *testing.T) *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
@@ -207,7 +208,7 @@ func TestCommands_LinkSessionToSAMLRequest(t *testing.T) {
},
},
{
"wrong login client",
"wrong login client / not permitted",
fields{
eventstore: expectEventstore(
expectFilter(
@@ -225,7 +226,8 @@ func TestCommands_LinkSessionToSAMLRequest(t *testing.T) {
),
),
),
tokenVerifier: newMockTokenVerifierValid(),
tokenVerifier: newMockTokenVerifierValid(),
checkPermission: newMockPermissionCheckNotAllowed(),
},
args{
ctx: authz.NewMockContext("instanceID", "orgID", "wrongLoginClient"),
@@ -235,7 +237,7 @@ func TestCommands_LinkSessionToSAMLRequest(t *testing.T) {
checkLoginClient: true,
},
res{
wantErr: zerrors.ThrowPermissionDenied(nil, "COMMAND-KCd48Rxt7x", "Errors.SAMLRequest.WrongLoginClient"),
wantErr: zerrors.ThrowPermissionDenied(nil, "AUTHZ-HKJD33", "Errors.PermissionDenied"),
},
},
{
@@ -524,6 +526,86 @@ func TestCommands_LinkSessionToSAMLRequest(t *testing.T) {
AuthMethods: []domain.UserAuthMethodType{domain.UserAuthMethodTypePassword},
},
},
}, {
"linked with permission",
fields{
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
samlrequest.NewAddedEvent(mockCtx, &samlrequest.NewAggregate("V2_id", "instanceID").Aggregate,
"loginClient",
"application",
"acs",
"relaystate",
"request",
"binding",
"issuer",
"destination",
),
),
),
expectFilter(
eventFromEventPusher(
session.NewAddedEvent(mockCtx,
&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(mockCtx, &session.NewAggregate("sessionID", "instance1").Aggregate,
"userID", "org1", testNow, &language.Afrikaans),
),
eventFromEventPusher(
session.NewPasswordCheckedEvent(mockCtx, &session.NewAggregate("sessionID", "instance1").Aggregate,
testNow),
),
eventFromEventPusherWithCreationDateNow(
session.NewLifetimeSetEvent(mockCtx, &session.NewAggregate("sessionID", "instance1").Aggregate,
2*time.Minute),
),
),
expectPush(
samlrequest.NewSessionLinkedEvent(mockCtx, &samlrequest.NewAggregate("V2_id", "instanceID").Aggregate,
"sessionID",
"userID",
testNow,
[]domain.UserAuthMethodType{domain.UserAuthMethodTypePassword},
),
),
),
tokenVerifier: newMockTokenVerifierValid(),
checkPermission: newMockPermissionCheckAllowed(),
},
args{
ctx: authz.NewMockContext("instanceID", "orgID", "loginClient"),
id: "V2_id",
sessionID: "sessionID",
sessionToken: "token",
checkLoginClient: true,
},
res{
details: &domain.ObjectDetails{ResourceOwner: "instanceID"},
authReq: &CurrentSAMLRequest{
SAMLRequest: &SAMLRequest{
ID: "V2_id",
LoginClient: "loginClient",
ApplicationID: "application",
ACSURL: "acs",
RelayState: "relaystate",
RequestID: "request",
Binding: "binding",
Issuer: "issuer",
Destination: "destination",
},
SessionID: "sessionID",
UserID: "userID",
AuthMethods: []domain.UserAuthMethodType{domain.UserAuthMethodTypePassword},
},
},
},
{
"linked with login client check, application permission check",
@@ -669,6 +751,7 @@ func TestCommands_LinkSessionToSAMLRequest(t *testing.T) {
c := &Commands{
eventstore: tt.fields.eventstore(t),
sessionTokenVerifier: tt.fields.tokenVerifier,
checkPermission: tt.fields.checkPermission,
}
details, got, err := c.LinkSessionToSAMLRequest(tt.args.ctx, tt.args.id, tt.args.sessionID, tt.args.sessionToken, tt.args.checkLoginClient, tt.args.checkPermission)
require.ErrorIs(t, err, tt.res.wantErr)