feat(api): add generic oauth provider template (#5260)

adds functionality to manage templates based OIDC IDPs
This commit is contained in:
Livio Spring
2023-02-24 15:16:06 +01:00
committed by GitHub
parent aa9518ac02
commit 737d14e81b
28 changed files with 3199 additions and 117 deletions

View File

@@ -20,6 +20,542 @@ import (
"github.com/zitadel/zitadel/internal/repository/instance"
)
func TestCommandSide_AddInstanceGenericOAuthIDP(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
idGenerator id.Generator
secretCrypto crypto.EncryptionAlgorithm
}
type args struct {
ctx context.Context
provider GenericOAuthProvider
}
type res struct {
id string
want *domain.ObjectDetails
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
"invalid name",
fields{
eventstore: eventstoreExpect(t),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
},
args{
ctx: authz.WithInstanceID(context.Background(), "instance1"),
provider: GenericOAuthProvider{},
},
res{
err: caos_errors.IsErrorInvalidArgument,
},
},
{
"invalid clientID",
fields{
eventstore: eventstoreExpect(t),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
},
args{
ctx: authz.WithInstanceID(context.Background(), "instance1"),
provider: GenericOAuthProvider{
Name: "name",
},
},
res{
err: caos_errors.IsErrorInvalidArgument,
},
},
{
"invalid clientSecret",
fields{
eventstore: eventstoreExpect(t),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
},
args{
ctx: authz.WithInstanceID(context.Background(), "instance1"),
provider: GenericOAuthProvider{
Name: "name",
ClientID: "clientID",
},
},
res{
err: caos_errors.IsErrorInvalidArgument,
},
},
{
"invalid auth endpoint",
fields{
eventstore: eventstoreExpect(t),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
},
args{
ctx: authz.WithInstanceID(context.Background(), "instance1"),
provider: GenericOAuthProvider{
Name: "name",
ClientID: "clientID",
ClientSecret: "clientSecret",
},
},
res{
err: caos_errors.IsErrorInvalidArgument,
},
},
{
"invalid token endpoint",
fields{
eventstore: eventstoreExpect(t),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
},
args{
ctx: authz.WithInstanceID(context.Background(), "instance1"),
provider: GenericOAuthProvider{
Name: "name",
ClientID: "clientID",
ClientSecret: "clientSecret",
AuthorizationEndpoint: "auth",
},
},
res{
err: caos_errors.IsErrorInvalidArgument,
},
},
{
"invalid user endpoint",
fields{
eventstore: eventstoreExpect(t),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
},
args{
ctx: authz.WithInstanceID(context.Background(), "instance1"),
provider: GenericOAuthProvider{
Name: "name",
ClientID: "clientID",
ClientSecret: "clientSecret",
AuthorizationEndpoint: "auth",
TokenEndpoint: "token",
},
},
res{
err: caos_errors.IsErrorInvalidArgument,
},
},
{
name: "ok",
fields: fields{
eventstore: eventstoreExpect(t,
expectFilter(),
expectPush(
[]*repository.Event{
eventFromEventPusherWithInstanceID(
"instance1",
instance.NewOAuthIDPAddedEvent(context.Background(), &instance.NewAggregate("instance1").Aggregate,
"id1",
"name",
"clientID",
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("clientSecret"),
},
"auth",
"token",
"user",
nil,
idp.Options{},
)),
},
),
),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
secretCrypto: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
},
args: args{
ctx: authz.WithInstanceID(context.Background(), "instance1"),
provider: GenericOAuthProvider{
Name: "name",
ClientID: "clientID",
ClientSecret: "clientSecret",
AuthorizationEndpoint: "auth",
TokenEndpoint: "token",
UserEndpoint: "user",
},
},
res: res{
id: "id1",
want: &domain.ObjectDetails{ResourceOwner: "instance1"},
},
},
{
name: "ok all set",
fields: fields{
eventstore: eventstoreExpect(t,
expectFilter(),
expectPush(
[]*repository.Event{
eventFromEventPusherWithInstanceID(
"instance1",
instance.NewOAuthIDPAddedEvent(context.Background(), &instance.NewAggregate("instance1").Aggregate,
"id1",
"name",
"clientID",
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("clientSecret"),
},
"auth",
"token",
"user",
[]string{"user"},
idp.Options{
IsCreationAllowed: true,
IsLinkingAllowed: true,
IsAutoCreation: true,
IsAutoUpdate: true,
},
)),
},
),
),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
secretCrypto: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
},
args: args{
ctx: authz.WithInstanceID(context.Background(), "instance1"),
provider: GenericOAuthProvider{
Name: "name",
ClientID: "clientID",
ClientSecret: "clientSecret",
AuthorizationEndpoint: "auth",
TokenEndpoint: "token",
UserEndpoint: "user",
Scopes: []string{"user"},
IDPOptions: idp.Options{
IsCreationAllowed: true,
IsLinkingAllowed: true,
IsAutoCreation: true,
IsAutoUpdate: true,
},
},
},
res: res{
id: "id1",
want: &domain.ObjectDetails{ResourceOwner: "instance1"},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &Commands{
eventstore: tt.fields.eventstore,
idGenerator: tt.fields.idGenerator,
idpConfigEncryption: tt.fields.secretCrypto,
}
id, got, err := c.AddInstanceGenericOAuthProvider(tt.args.ctx, tt.args.provider)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.id, id)
assert.Equal(t, tt.res.want, got)
}
})
}
}
func TestCommandSide_UpdateInstanceGenericOAuthIDP(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
secretCrypto crypto.EncryptionAlgorithm
}
type args struct {
ctx context.Context
id string
provider GenericOAuthProvider
}
type res struct {
want *domain.ObjectDetails
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
"invalid id",
fields{
eventstore: eventstoreExpect(t),
},
args{
ctx: authz.WithInstanceID(context.Background(), "instance1"),
provider: GenericOAuthProvider{},
},
res{
err: caos_errors.IsErrorInvalidArgument,
},
},
{
"invalid name",
fields{
eventstore: eventstoreExpect(t),
},
args{
ctx: authz.WithInstanceID(context.Background(), "instance1"),
id: "id1",
provider: GenericOAuthProvider{},
},
res{
err: caos_errors.IsErrorInvalidArgument,
},
},
{
"invalid clientID",
fields{
eventstore: eventstoreExpect(t),
},
args{
ctx: authz.WithInstanceID(context.Background(), "instance1"),
id: "id1",
provider: GenericOAuthProvider{
Name: "name",
},
},
res{
err: caos_errors.IsErrorInvalidArgument,
},
},
{
"invalid auth endpoint",
fields{
eventstore: eventstoreExpect(t),
},
args{
ctx: authz.WithInstanceID(context.Background(), "instance1"),
id: "id1",
provider: GenericOAuthProvider{
Name: "name",
},
},
res{
err: caos_errors.IsErrorInvalidArgument,
},
},
{
"invalid token endpoint",
fields{
eventstore: eventstoreExpect(t),
},
args{
ctx: authz.WithInstanceID(context.Background(), "instance1"),
id: "id1",
provider: GenericOAuthProvider{
Name: "name",
ClientID: "clientID",
AuthorizationEndpoint: "auth",
},
},
res{
err: caos_errors.IsErrorInvalidArgument,
},
},
{
"invalid user endpoint",
fields{
eventstore: eventstoreExpect(t),
},
args{
ctx: authz.WithInstanceID(context.Background(), "instance1"),
id: "id1",
provider: GenericOAuthProvider{
Name: "name",
ClientID: "clientID",
AuthorizationEndpoint: "auth",
TokenEndpoint: "token",
},
},
res{
err: caos_errors.IsErrorInvalidArgument,
},
},
{
name: "not found",
fields: fields{
eventstore: eventstoreExpect(t,
expectFilter(),
),
},
args: args{
ctx: authz.WithInstanceID(context.Background(), "instance1"),
id: "id1",
provider: GenericOAuthProvider{
Name: "name",
ClientID: "clientID",
AuthorizationEndpoint: "auth",
TokenEndpoint: "token",
UserEndpoint: "user",
},
},
res: res{
err: caos_errors.IsNotFound,
},
},
{
name: "no changes",
fields: fields{
eventstore: eventstoreExpect(t,
expectFilter(
eventFromEventPusher(
instance.NewOAuthIDPAddedEvent(context.Background(), &instance.NewAggregate("instance1").Aggregate,
"id1",
"name",
"clientID",
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("clientSecret"),
},
"auth",
"token",
"user",
nil,
idp.Options{},
)),
),
),
},
args: args{
ctx: authz.WithInstanceID(context.Background(), "instance1"),
id: "id1",
provider: GenericOAuthProvider{
Name: "name",
ClientID: "clientID",
AuthorizationEndpoint: "auth",
TokenEndpoint: "token",
UserEndpoint: "user",
},
},
res: res{
want: &domain.ObjectDetails{ResourceOwner: "instance1"},
},
},
{
name: "change ok",
fields: fields{
eventstore: eventstoreExpect(t,
expectFilter(
eventFromEventPusher(
instance.NewOAuthIDPAddedEvent(context.Background(), &instance.NewAggregate("instance1").Aggregate,
"id1",
"name",
"clientID",
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("clientSecret"),
},
"auth",
"token",
"user",
nil,
idp.Options{},
)),
),
expectPush(
[]*repository.Event{
eventFromEventPusherWithInstanceID(
"instance1",
func() eventstore.Command {
t := true
event, _ := instance.NewOAuthIDPChangedEvent(context.Background(), &instance.NewAggregate("instance1").Aggregate,
"id1",
[]idp.OAuthIDPChanges{
idp.ChangeOAuthName("new name"),
idp.ChangeOAuthClientID("clientID2"),
idp.ChangeOAuthClientSecret(&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("newSecret"),
}),
idp.ChangeOAuthAuthorizationEndpoint("new auth"),
idp.ChangeOAuthTokenEndpoint("new token"),
idp.ChangeOAuthUserEndpoint("new user"),
idp.ChangeOAuthScopes([]string{"openid", "profile"}),
idp.ChangeOAuthOptions(idp.OptionChanges{
IsCreationAllowed: &t,
IsLinkingAllowed: &t,
IsAutoCreation: &t,
IsAutoUpdate: &t,
}),
},
)
return event
}(),
),
},
),
),
secretCrypto: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
},
args: args{
ctx: authz.WithInstanceID(context.Background(), "instance1"),
id: "id1",
provider: GenericOAuthProvider{
Name: "new name",
ClientID: "clientID2",
ClientSecret: "newSecret",
AuthorizationEndpoint: "new auth",
TokenEndpoint: "new token",
UserEndpoint: "new user",
Scopes: []string{"openid", "profile"},
IDPOptions: idp.Options{
IsCreationAllowed: true,
IsLinkingAllowed: true,
IsAutoCreation: true,
IsAutoUpdate: true,
},
},
},
res: res{
want: &domain.ObjectDetails{ResourceOwner: "instance1"},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &Commands{
eventstore: tt.fields.eventstore,
idpConfigEncryption: tt.fields.secretCrypto,
}
got, err := c.UpdateInstanceGenericOAuthProvider(tt.args.ctx, tt.args.id, tt.args.provider)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.want, got)
}
})
}
}
func TestCommandSide_AddInstanceGoogleIDP(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
@@ -281,7 +817,7 @@ func TestCommandSide_UpdateInstanceGoogleIDP(t *testing.T) {
},
},
res: res{
want: &domain.ObjectDetails{},
want: &domain.ObjectDetails{ResourceOwner: "instance1"},
},
},
{
@@ -893,7 +1429,7 @@ func TestCommandSide_UpdateInstanceLDAPIDP(t *testing.T) {
},
},
res: res{
want: &domain.ObjectDetails{},
want: &domain.ObjectDetails{ResourceOwner: "instance1"},
},
},
{