mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-11 18:07:31 +00:00
chore(tests): use a coverage server binary (#8407)
# Which Problems Are Solved Use a single server instance for API integration tests. This optimizes the time taken for the integration test pipeline, because it allows running tests on multiple packages in parallel. Also, it saves time by not start and stopping a zitadel server for every package. # How the Problems Are Solved - Build a binary with `go build -race -cover ....` - Integration tests only construct clients. The server remains running in the background. - The integration package and tested packages now fully utilize the API. No more direct database access trough `query` and `command` packages. - Use Makefile recipes to setup, start and stop the server in the background. - The binary has the race detector enabled - Init and setup jobs are configured to halt immediately on race condition - Because the server runs in the background, races are only logged. When the server is stopped and race logs exist, the Makefile recipe will throw an error and print the logs. - Makefile recipes include logic to print logs and convert coverage reports after the server is stopped. - Some tests need a downstream HTTP server to make requests, like quota and milestones. A new `integration/sink` package creates an HTTP server and uses websockets to forward HTTP request back to the test packages. The package API uses Go channels for abstraction and easy usage. # Additional Changes - Integration test files already used the `//go:build integration` directive. In order to properly split integration from unit tests, integration test files need to be in a `integration_test` subdirectory of their package. - `UseIsolatedInstance` used to overwrite the `Tester.Client` for each instance. Now a `Instance` object is returned with a gRPC client that is connected to the isolated instance's hostname. - The `Tester` type is now `Instance`. The object is created for the first instance, used by default in any test. Isolated instances are also `Instance` objects and therefore benefit from the same methods and values. The first instance and any other us capable of creating an isolated instance over the system API. - All test packages run in an Isolated instance by calling `NewInstance()` - Individual tests that use an isolated instance use `t.Parallel()` # Additional Context - Closes #6684 - https://go.dev/doc/articles/race_detector - https://go.dev/doc/build-cover --------- Co-authored-by: Stefan Benz <46600784+stebenz@users.noreply.github.com>
This commit is contained in:
@@ -50,7 +50,7 @@ type ResourceListDetailsMsg interface {
|
||||
// If the change date is populated, it is checked with a tolerance of 1 minute around Now.
|
||||
//
|
||||
// The resource owner is compared with expected.
|
||||
func AssertDetails[D Details, M DetailsMsg[D]](t testing.TB, expected, actual M) {
|
||||
func AssertDetails[D Details, M DetailsMsg[D]](t assert.TestingT, expected, actual M) {
|
||||
wantDetails, gotDetails := expected.GetDetails(), actual.GetDetails()
|
||||
var nilDetails D
|
||||
if wantDetails == nilDetails {
|
||||
@@ -69,7 +69,7 @@ func AssertDetails[D Details, M DetailsMsg[D]](t testing.TB, expected, actual M)
|
||||
assert.Equal(t, wantDetails.GetResourceOwner(), gotDetails.GetResourceOwner())
|
||||
}
|
||||
|
||||
func AssertResourceDetails(t testing.TB, expected *resources_object.Details, actual *resources_object.Details) {
|
||||
func AssertResourceDetails(t assert.TestingT, expected *resources_object.Details, actual *resources_object.Details) {
|
||||
if expected.GetChanged() != nil {
|
||||
wantChangeDate := time.Now()
|
||||
gotChangeDate := actual.GetChanged().AsTime()
|
||||
@@ -87,7 +87,7 @@ func AssertResourceDetails(t testing.TB, expected *resources_object.Details, act
|
||||
}
|
||||
}
|
||||
|
||||
func AssertListDetails[L ListDetails, D ListDetailsMsg[L]](t testing.TB, expected, actual D) {
|
||||
func AssertListDetails[L ListDetails, D ListDetailsMsg[L]](t assert.TestingT, expected, actual D) {
|
||||
wantDetails, gotDetails := expected.GetDetails(), actual.GetDetails()
|
||||
var nilDetails L
|
||||
if wantDetails == nilDetails {
|
||||
@@ -99,11 +99,11 @@ func AssertListDetails[L ListDetails, D ListDetailsMsg[L]](t testing.TB, expecte
|
||||
if wantDetails.GetTimestamp() != nil {
|
||||
gotCD := gotDetails.GetTimestamp().AsTime()
|
||||
wantCD := time.Now()
|
||||
assert.WithinRange(t, gotCD, wantCD.Add(-time.Minute), wantCD.Add(time.Minute))
|
||||
assert.WithinRange(t, gotCD, wantCD.Add(-10*time.Minute), wantCD.Add(time.Minute))
|
||||
}
|
||||
}
|
||||
|
||||
func AssertResourceListDetails[D ResourceListDetailsMsg](t testing.TB, expected, actual D) {
|
||||
func AssertResourceListDetails[D ResourceListDetailsMsg](t assert.TestingT, expected, actual D) {
|
||||
wantDetails, gotDetails := expected.GetDetails(), actual.GetDetails()
|
||||
if wantDetails == nil {
|
||||
assert.Nil(t, gotDetails)
|
||||
@@ -116,7 +116,7 @@ func AssertResourceListDetails[D ResourceListDetailsMsg](t testing.TB, expected,
|
||||
if wantDetails.GetTimestamp() != nil {
|
||||
gotCD := gotDetails.GetTimestamp().AsTime()
|
||||
wantCD := time.Now()
|
||||
assert.WithinRange(t, gotCD, wantCD.Add(-time.Minute), wantCD.Add(time.Minute))
|
||||
assert.WithinRange(t, gotCD, wantCD.Add(-10*time.Minute), wantCD.Add(time.Minute))
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -6,26 +6,17 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
crewjam_saml "github.com/crewjam/saml"
|
||||
"github.com/brianvoe/gofakeit/v6"
|
||||
"github.com/muhlemmer/gu"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/zitadel/logging"
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
"golang.org/x/oauth2"
|
||||
"golang.org/x/text/language"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/protobuf/types/known/durationpb"
|
||||
"google.golang.org/protobuf/types/known/structpb"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/command"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/idp/providers/ldap"
|
||||
openid "github.com/zitadel/zitadel/internal/idp/providers/oidc"
|
||||
"github.com/zitadel/zitadel/internal/idp/providers/saml"
|
||||
idp_rp "github.com/zitadel/zitadel/internal/repository/idp"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/admin"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/auth"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/feature/v2"
|
||||
@@ -37,7 +28,7 @@ import (
|
||||
object_v3alpha "github.com/zitadel/zitadel/pkg/grpc/object/v3alpha"
|
||||
oidc_pb "github.com/zitadel/zitadel/pkg/grpc/oidc/v2"
|
||||
oidc_pb_v2beta "github.com/zitadel/zitadel/pkg/grpc/oidc/v2beta"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/org/v2"
|
||||
org "github.com/zitadel/zitadel/pkg/grpc/org/v2"
|
||||
org_v2beta "github.com/zitadel/zitadel/pkg/grpc/org/v2beta"
|
||||
action "github.com/zitadel/zitadel/pkg/grpc/resources/action/v3alpha"
|
||||
user_v3alpha "github.com/zitadel/zitadel/pkg/grpc/resources/user/v3alpha"
|
||||
@@ -47,7 +38,6 @@ import (
|
||||
session_v2beta "github.com/zitadel/zitadel/pkg/grpc/session/v2beta"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/settings/v2"
|
||||
settings_v2beta "github.com/zitadel/zitadel/pkg/grpc/settings/v2beta"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/system"
|
||||
user_pb "github.com/zitadel/zitadel/pkg/grpc/user"
|
||||
user_v2 "github.com/zitadel/zitadel/pkg/grpc/user/v2"
|
||||
user_v2beta "github.com/zitadel/zitadel/pkg/grpc/user/v2beta"
|
||||
@@ -68,7 +58,6 @@ type Client struct {
|
||||
OIDCv2 oidc_pb.OIDCServiceClient
|
||||
OrgV2beta org_v2beta.OrganizationServiceClient
|
||||
OrgV2 org.OrganizationServiceClient
|
||||
System system.SystemServiceClient
|
||||
ActionV3Alpha action.ZITADELActionsClient
|
||||
FeatureV2beta feature_v2beta.FeatureServiceClient
|
||||
FeatureV2 feature.FeatureServiceClient
|
||||
@@ -78,8 +67,14 @@ type Client struct {
|
||||
UserV3Alpha user_v3alpha.ZITADELUsersClient
|
||||
}
|
||||
|
||||
func newClient(cc *grpc.ClientConn) Client {
|
||||
return Client{
|
||||
func newClient(ctx context.Context, target string) (*Client, error) {
|
||||
cc, err := grpc.NewClient(target,
|
||||
grpc.WithTransportCredentials(insecure.NewCredentials()),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client := &Client{
|
||||
CC: cc,
|
||||
Admin: admin.NewAdminServiceClient(cc),
|
||||
Mgmt: mgmt.NewManagementServiceClient(cc),
|
||||
@@ -94,7 +89,6 @@ func newClient(cc *grpc.ClientConn) Client {
|
||||
OIDCv2: oidc_pb.NewOIDCServiceClient(cc),
|
||||
OrgV2beta: org_v2beta.NewOrganizationServiceClient(cc),
|
||||
OrgV2: org.NewOrganizationServiceClient(cc),
|
||||
System: system.NewSystemServiceClient(cc),
|
||||
ActionV3Alpha: action.NewZITADELActionsClient(cc),
|
||||
FeatureV2beta: feature_v2beta.NewFeatureServiceClient(cc),
|
||||
FeatureV2: feature.NewFeatureServiceClient(cc),
|
||||
@@ -103,60 +97,38 @@ func newClient(cc *grpc.ClientConn) Client {
|
||||
IDPv2: idp_pb.NewIdentityProviderServiceClient(cc),
|
||||
UserV3Alpha: user_v3alpha.NewZITADELUsersClient(cc),
|
||||
}
|
||||
return client, client.pollHealth(ctx)
|
||||
}
|
||||
|
||||
func (t *Tester) UseIsolatedInstance(tt *testing.T, iamOwnerCtx, systemCtx context.Context) (primaryDomain, instanceId, adminID string, authenticatedIamOwnerCtx context.Context) {
|
||||
primaryDomain = RandString(5) + ".integration.localhost"
|
||||
instance, err := t.Client.System.CreateInstance(systemCtx, &system.CreateInstanceRequest{
|
||||
InstanceName: "testinstance",
|
||||
CustomDomain: primaryDomain,
|
||||
Owner: &system.CreateInstanceRequest_Machine_{
|
||||
Machine: &system.CreateInstanceRequest_Machine{
|
||||
UserName: "owner",
|
||||
Name: "owner",
|
||||
PersonalAccessToken: &system.CreateInstanceRequest_PersonalAccessToken{},
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(tt, err)
|
||||
t.createClientConn(iamOwnerCtx, fmt.Sprintf("%s:%d", primaryDomain, t.Config.Port))
|
||||
instanceId = instance.GetInstanceId()
|
||||
owner, err := t.Queries.GetUserByLoginName(authz.WithInstanceID(iamOwnerCtx, instanceId), true, "owner@"+primaryDomain)
|
||||
require.NoError(tt, err)
|
||||
t.Users.Set(instanceId, IAMOwner, &User{
|
||||
User: owner,
|
||||
Token: instance.GetPat(),
|
||||
})
|
||||
newCtx := t.WithInstanceAuthorization(iamOwnerCtx, IAMOwner, instanceId)
|
||||
var adminUser *mgmt.ImportHumanUserResponse
|
||||
// the following serves two purposes:
|
||||
// 1. it ensures that the instance is ready to be used
|
||||
// 2. it enables a normal login with the default admin user credentials
|
||||
require.EventuallyWithT(tt, func(collectT *assert.CollectT) {
|
||||
var importErr error
|
||||
adminUser, importErr = t.Client.Mgmt.ImportHumanUser(newCtx, &mgmt.ImportHumanUserRequest{
|
||||
UserName: "zitadel-admin@zitadel.localhost",
|
||||
Email: &mgmt.ImportHumanUserRequest_Email{
|
||||
Email: "zitadel-admin@zitadel.localhost",
|
||||
IsEmailVerified: true,
|
||||
},
|
||||
Password: "Password1!",
|
||||
Profile: &mgmt.ImportHumanUserRequest_Profile{
|
||||
FirstName: "hodor",
|
||||
LastName: "hodor",
|
||||
NickName: "hodor",
|
||||
},
|
||||
})
|
||||
assert.NoError(collectT, importErr)
|
||||
}, 2*time.Minute, 100*time.Millisecond, "instance not ready")
|
||||
return primaryDomain, instanceId, adminUser.GetUserId(), t.updateInstanceAndOrg(newCtx, fmt.Sprintf("%s:%d", primaryDomain, t.Config.ExternalPort))
|
||||
// pollHealth waits until a healthy status is reported.
|
||||
func (c *Client) pollHealth(ctx context.Context) (err error) {
|
||||
for {
|
||||
err = func(ctx context.Context) error {
|
||||
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
_, err := c.Admin.Healthz(ctx, &admin.HealthzRequest{})
|
||||
return err
|
||||
}(ctx)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
logging.WithError(err).Debug("poll healthz")
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-time.After(time.Second):
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Tester) CreateHumanUser(ctx context.Context) *user_v2.AddHumanUserResponse {
|
||||
resp, err := s.Client.UserV2.AddHumanUser(ctx, &user_v2.AddHumanUserRequest{
|
||||
func (i *Instance) CreateHumanUser(ctx context.Context) *user_v2.AddHumanUserResponse {
|
||||
resp, err := i.Client.UserV2.AddHumanUser(ctx, &user_v2.AddHumanUserRequest{
|
||||
Organization: &object.Organization{
|
||||
Org: &object.Organization_OrgId{
|
||||
OrgId: s.Organisation.ID,
|
||||
OrgId: i.DefaultOrg.GetId(),
|
||||
},
|
||||
},
|
||||
Profile: &user_v2.SetHumanProfile{
|
||||
@@ -178,15 +150,15 @@ func (s *Tester) CreateHumanUser(ctx context.Context) *user_v2.AddHumanUserRespo
|
||||
},
|
||||
},
|
||||
})
|
||||
logging.OnError(err).Fatal("create human user")
|
||||
logging.OnError(err).Panic("create human user")
|
||||
return resp
|
||||
}
|
||||
|
||||
func (s *Tester) CreateHumanUserNoPhone(ctx context.Context) *user_v2.AddHumanUserResponse {
|
||||
resp, err := s.Client.UserV2.AddHumanUser(ctx, &user_v2.AddHumanUserRequest{
|
||||
func (i *Instance) CreateHumanUserNoPhone(ctx context.Context) *user_v2.AddHumanUserResponse {
|
||||
resp, err := i.Client.UserV2.AddHumanUser(ctx, &user_v2.AddHumanUserRequest{
|
||||
Organization: &object.Organization{
|
||||
Org: &object.Organization_OrgId{
|
||||
OrgId: s.Organisation.ID,
|
||||
OrgId: i.DefaultOrg.GetId(),
|
||||
},
|
||||
},
|
||||
Profile: &user_v2.SetHumanProfile{
|
||||
@@ -202,15 +174,15 @@ func (s *Tester) CreateHumanUserNoPhone(ctx context.Context) *user_v2.AddHumanUs
|
||||
},
|
||||
},
|
||||
})
|
||||
logging.OnError(err).Fatal("create human user")
|
||||
logging.OnError(err).Panic("create human user")
|
||||
return resp
|
||||
}
|
||||
|
||||
func (s *Tester) CreateHumanUserWithTOTP(ctx context.Context, secret string) *user_v2.AddHumanUserResponse {
|
||||
resp, err := s.Client.UserV2.AddHumanUser(ctx, &user_v2.AddHumanUserRequest{
|
||||
func (i *Instance) CreateHumanUserWithTOTP(ctx context.Context, secret string) *user_v2.AddHumanUserResponse {
|
||||
resp, err := i.Client.UserV2.AddHumanUser(ctx, &user_v2.AddHumanUserRequest{
|
||||
Organization: &object.Organization{
|
||||
Org: &object.Organization_OrgId{
|
||||
OrgId: s.Organisation.ID,
|
||||
OrgId: i.DefaultOrg.GetId(),
|
||||
},
|
||||
},
|
||||
Profile: &user_v2.SetHumanProfile{
|
||||
@@ -233,12 +205,12 @@ func (s *Tester) CreateHumanUserWithTOTP(ctx context.Context, secret string) *us
|
||||
},
|
||||
TotpSecret: gu.Ptr(secret),
|
||||
})
|
||||
logging.OnError(err).Fatal("create human user")
|
||||
logging.OnError(err).Panic("create human user")
|
||||
return resp
|
||||
}
|
||||
|
||||
func (s *Tester) CreateOrganization(ctx context.Context, name, adminEmail string) *org.AddOrganizationResponse {
|
||||
resp, err := s.Client.OrgV2.AddOrganization(ctx, &org.AddOrganizationRequest{
|
||||
func (i *Instance) CreateOrganization(ctx context.Context, name, adminEmail string) *org.AddOrganizationResponse {
|
||||
resp, err := i.Client.OrgV2.AddOrganization(ctx, &org.AddOrganizationRequest{
|
||||
Name: name,
|
||||
Admins: []*org.AddOrganizationRequest_Admin{
|
||||
{
|
||||
@@ -259,12 +231,12 @@ func (s *Tester) CreateOrganization(ctx context.Context, name, adminEmail string
|
||||
},
|
||||
},
|
||||
})
|
||||
logging.OnError(err).Fatal("create org")
|
||||
logging.OnError(err).Panic("create org")
|
||||
return resp
|
||||
}
|
||||
|
||||
func (s *Tester) DeactivateOrganization(ctx context.Context, orgID string) *mgmt.DeactivateOrgResponse {
|
||||
resp, err := s.Client.Mgmt.DeactivateOrg(
|
||||
func (i *Instance) DeactivateOrganization(ctx context.Context, orgID string) *mgmt.DeactivateOrgResponse {
|
||||
resp, err := i.Client.Mgmt.DeactivateOrg(
|
||||
SetOrgID(ctx, orgID),
|
||||
&mgmt.DeactivateOrgRequest{},
|
||||
)
|
||||
@@ -281,8 +253,8 @@ func SetOrgID(ctx context.Context, orgID string) context.Context {
|
||||
return metadata.NewOutgoingContext(ctx, md)
|
||||
}
|
||||
|
||||
func (s *Tester) CreateOrganizationWithUserID(ctx context.Context, name, userID string) *org.AddOrganizationResponse {
|
||||
resp, err := s.Client.OrgV2.AddOrganization(ctx, &org.AddOrganizationRequest{
|
||||
func (i *Instance) CreateOrganizationWithUserID(ctx context.Context, name, userID string) *org.AddOrganizationResponse {
|
||||
resp, err := i.Client.OrgV2.AddOrganization(ctx, &org.AddOrganizationRequest{
|
||||
Name: name,
|
||||
Admins: []*org.AddOrganizationRequest_Admin{
|
||||
{
|
||||
@@ -296,8 +268,8 @@ func (s *Tester) CreateOrganizationWithUserID(ctx context.Context, name, userID
|
||||
return resp
|
||||
}
|
||||
|
||||
func (s *Tester) CreateHumanUserVerified(ctx context.Context, org, email string) *user_v2.AddHumanUserResponse {
|
||||
resp, err := s.Client.UserV2.AddHumanUser(ctx, &user_v2.AddHumanUserRequest{
|
||||
func (i *Instance) CreateHumanUserVerified(ctx context.Context, org, email string) *user_v2.AddHumanUserResponse {
|
||||
resp, err := i.Client.UserV2.AddHumanUser(ctx, &user_v2.AddHumanUserRequest{
|
||||
Organization: &object.Organization{
|
||||
Org: &object.Organization_OrgId{
|
||||
OrgId: org,
|
||||
@@ -323,23 +295,23 @@ func (s *Tester) CreateHumanUserVerified(ctx context.Context, org, email string)
|
||||
},
|
||||
},
|
||||
})
|
||||
logging.OnError(err).Fatal("create human user")
|
||||
logging.OnError(err).Panic("create human user")
|
||||
return resp
|
||||
}
|
||||
|
||||
func (s *Tester) CreateMachineUser(ctx context.Context) *mgmt.AddMachineUserResponse {
|
||||
resp, err := s.Client.Mgmt.AddMachineUser(ctx, &mgmt.AddMachineUserRequest{
|
||||
func (i *Instance) CreateMachineUser(ctx context.Context) *mgmt.AddMachineUserResponse {
|
||||
resp, err := i.Client.Mgmt.AddMachineUser(ctx, &mgmt.AddMachineUserRequest{
|
||||
UserName: fmt.Sprintf("%d@mouse.com", time.Now().UnixNano()),
|
||||
Name: "Mickey",
|
||||
Description: "Mickey Mouse",
|
||||
AccessTokenType: user_pb.AccessTokenType_ACCESS_TOKEN_TYPE_BEARER,
|
||||
})
|
||||
logging.OnError(err).Fatal("create human user")
|
||||
logging.OnError(err).Panic("create human user")
|
||||
return resp
|
||||
}
|
||||
|
||||
func (s *Tester) CreateUserIDPlink(ctx context.Context, userID, externalID, idpID, username string) *user_v2.AddIDPLinkResponse {
|
||||
resp, err := s.Client.UserV2.AddIDPLink(
|
||||
func (i *Instance) CreateUserIDPlink(ctx context.Context, userID, externalID, idpID, username string) (*user_v2.AddIDPLinkResponse, error) {
|
||||
return i.Client.UserV2.AddIDPLink(
|
||||
ctx,
|
||||
&user_v2.AddIDPLinkRequest{
|
||||
UserId: userID,
|
||||
@@ -350,67 +322,65 @@ func (s *Tester) CreateUserIDPlink(ctx context.Context, userID, externalID, idpI
|
||||
},
|
||||
},
|
||||
)
|
||||
logging.OnError(err).Fatal("create human user link")
|
||||
return resp
|
||||
}
|
||||
|
||||
func (s *Tester) RegisterUserPasskey(ctx context.Context, userID string) {
|
||||
reg, err := s.Client.UserV2.CreatePasskeyRegistrationLink(ctx, &user_v2.CreatePasskeyRegistrationLinkRequest{
|
||||
func (i *Instance) RegisterUserPasskey(ctx context.Context, userID string) {
|
||||
reg, err := i.Client.UserV2.CreatePasskeyRegistrationLink(ctx, &user_v2.CreatePasskeyRegistrationLinkRequest{
|
||||
UserId: userID,
|
||||
Medium: &user_v2.CreatePasskeyRegistrationLinkRequest_ReturnCode{},
|
||||
})
|
||||
logging.OnError(err).Fatal("create user passkey")
|
||||
logging.OnError(err).Panic("create user passkey")
|
||||
|
||||
pkr, err := s.Client.UserV2.RegisterPasskey(ctx, &user_v2.RegisterPasskeyRequest{
|
||||
pkr, err := i.Client.UserV2.RegisterPasskey(ctx, &user_v2.RegisterPasskeyRequest{
|
||||
UserId: userID,
|
||||
Code: reg.GetCode(),
|
||||
Domain: s.Config.ExternalDomain,
|
||||
Domain: i.Domain,
|
||||
})
|
||||
logging.OnError(err).Fatal("create user passkey")
|
||||
attestationResponse, err := s.WebAuthN.CreateAttestationResponse(pkr.GetPublicKeyCredentialCreationOptions())
|
||||
logging.OnError(err).Fatal("create user passkey")
|
||||
logging.OnError(err).Panic("create user passkey")
|
||||
attestationResponse, err := i.WebAuthN.CreateAttestationResponse(pkr.GetPublicKeyCredentialCreationOptions())
|
||||
logging.OnError(err).Panic("create user passkey")
|
||||
|
||||
_, err = s.Client.UserV2.VerifyPasskeyRegistration(ctx, &user_v2.VerifyPasskeyRegistrationRequest{
|
||||
_, err = i.Client.UserV2.VerifyPasskeyRegistration(ctx, &user_v2.VerifyPasskeyRegistrationRequest{
|
||||
UserId: userID,
|
||||
PasskeyId: pkr.GetPasskeyId(),
|
||||
PublicKeyCredential: attestationResponse,
|
||||
PasskeyName: "nice name",
|
||||
})
|
||||
logging.OnError(err).Fatal("create user passkey")
|
||||
logging.OnError(err).Panic("create user passkey")
|
||||
}
|
||||
|
||||
func (s *Tester) RegisterUserU2F(ctx context.Context, userID string) {
|
||||
pkr, err := s.Client.UserV2.RegisterU2F(ctx, &user_v2.RegisterU2FRequest{
|
||||
func (i *Instance) RegisterUserU2F(ctx context.Context, userID string) {
|
||||
pkr, err := i.Client.UserV2.RegisterU2F(ctx, &user_v2.RegisterU2FRequest{
|
||||
UserId: userID,
|
||||
Domain: s.Config.ExternalDomain,
|
||||
Domain: i.Domain,
|
||||
})
|
||||
logging.OnError(err).Fatal("create user u2f")
|
||||
attestationResponse, err := s.WebAuthN.CreateAttestationResponse(pkr.GetPublicKeyCredentialCreationOptions())
|
||||
logging.OnError(err).Fatal("create user u2f")
|
||||
logging.OnError(err).Panic("create user u2f")
|
||||
attestationResponse, err := i.WebAuthN.CreateAttestationResponse(pkr.GetPublicKeyCredentialCreationOptions())
|
||||
logging.OnError(err).Panic("create user u2f")
|
||||
|
||||
_, err = s.Client.UserV2.VerifyU2FRegistration(ctx, &user_v2.VerifyU2FRegistrationRequest{
|
||||
_, err = i.Client.UserV2.VerifyU2FRegistration(ctx, &user_v2.VerifyU2FRegistrationRequest{
|
||||
UserId: userID,
|
||||
U2FId: pkr.GetU2FId(),
|
||||
PublicKeyCredential: attestationResponse,
|
||||
TokenName: "nice name",
|
||||
})
|
||||
logging.OnError(err).Fatal("create user u2f")
|
||||
logging.OnError(err).Panic("create user u2f")
|
||||
}
|
||||
|
||||
func (s *Tester) SetUserPassword(ctx context.Context, userID, password string, changeRequired bool) *object.Details {
|
||||
resp, err := s.Client.UserV2.SetPassword(ctx, &user_v2.SetPasswordRequest{
|
||||
func (i *Instance) SetUserPassword(ctx context.Context, userID, password string, changeRequired bool) *object.Details {
|
||||
resp, err := i.Client.UserV2.SetPassword(ctx, &user_v2.SetPasswordRequest{
|
||||
UserId: userID,
|
||||
NewPassword: &user_v2.Password{
|
||||
Password: password,
|
||||
ChangeRequired: changeRequired,
|
||||
},
|
||||
})
|
||||
logging.OnError(err).Fatal("set user password")
|
||||
logging.OnError(err).Panic("set user password")
|
||||
return resp.GetDetails()
|
||||
}
|
||||
|
||||
func (s *Tester) AddGenericOAuthIDP(ctx context.Context, name string) *admin.AddGenericOAuthProviderResponse {
|
||||
resp, err := s.Client.Admin.AddGenericOAuthProvider(ctx, &admin.AddGenericOAuthProviderRequest{
|
||||
func (i *Instance) AddGenericOAuthProvider(ctx context.Context, name string) *admin.AddGenericOAuthProviderResponse {
|
||||
resp, err := i.Client.Admin.AddGenericOAuthProvider(ctx, &admin.AddGenericOAuthProviderRequest{
|
||||
Name: name,
|
||||
ClientId: "clientID",
|
||||
ClientSecret: "clientSecret",
|
||||
@@ -427,136 +397,126 @@ func (s *Tester) AddGenericOAuthIDP(ctx context.Context, name string) *admin.Add
|
||||
AutoLinking: idp.AutoLinkingOption_AUTO_LINKING_OPTION_USERNAME,
|
||||
},
|
||||
})
|
||||
logging.OnError(err).Fatal("create generic OAuth idp")
|
||||
return resp
|
||||
}
|
||||
logging.OnError(err).Panic("create generic OAuth idp")
|
||||
|
||||
func (s *Tester) AddGenericOAuthProvider(t *testing.T, ctx context.Context) string {
|
||||
ctx = authz.WithInstance(ctx, s.Instance)
|
||||
id, _, err := s.Commands.AddInstanceGenericOAuthProvider(ctx, command.GenericOAuthProvider{
|
||||
Name: "idp",
|
||||
ClientID: "clientID",
|
||||
ClientSecret: "clientSecret",
|
||||
AuthorizationEndpoint: "https://example.com/oauth/v2/authorize",
|
||||
TokenEndpoint: "https://example.com/oauth/v2/token",
|
||||
UserEndpoint: "https://api.example.com/user",
|
||||
Scopes: []string{"openid", "profile", "email"},
|
||||
IDAttribute: "id",
|
||||
IDPOptions: idp_rp.Options{
|
||||
IsLinkingAllowed: true,
|
||||
IsCreationAllowed: true,
|
||||
IsAutoCreation: true,
|
||||
IsAutoUpdate: true,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
return id
|
||||
}
|
||||
|
||||
func (s *Tester) AddOrgGenericOAuthIDP(ctx context.Context, name string) *mgmt.AddGenericOAuthProviderResponse {
|
||||
resp, err := s.Client.Mgmt.AddGenericOAuthProvider(ctx, &mgmt.AddGenericOAuthProviderRequest{
|
||||
Name: name,
|
||||
ClientId: "clientID",
|
||||
ClientSecret: "clientSecret",
|
||||
AuthorizationEndpoint: "https://example.com/oauth/v2/authorize",
|
||||
TokenEndpoint: "https://example.com/oauth/v2/token",
|
||||
UserEndpoint: "https://api.example.com/user",
|
||||
Scopes: []string{"openid", "profile", "email"},
|
||||
IdAttribute: "id",
|
||||
ProviderOptions: &idp.Options{
|
||||
IsLinkingAllowed: true,
|
||||
IsCreationAllowed: true,
|
||||
IsAutoCreation: true,
|
||||
IsAutoUpdate: true,
|
||||
AutoLinking: idp.AutoLinkingOption_AUTO_LINKING_OPTION_USERNAME,
|
||||
},
|
||||
})
|
||||
logging.OnError(err).Fatal("create generic OAuth idp")
|
||||
return resp
|
||||
}
|
||||
|
||||
func (s *Tester) AddOrgGenericOAuthProvider(t *testing.T, ctx context.Context, orgID string) string {
|
||||
ctx = authz.WithInstance(ctx, s.Instance)
|
||||
id, _, err := s.Commands.AddOrgGenericOAuthProvider(ctx, orgID,
|
||||
command.GenericOAuthProvider{
|
||||
Name: "idp",
|
||||
ClientID: "clientID",
|
||||
ClientSecret: "clientSecret",
|
||||
AuthorizationEndpoint: "https://example.com/oauth/v2/authorize",
|
||||
TokenEndpoint: "https://example.com/oauth/v2/token",
|
||||
UserEndpoint: "https://api.example.com/user",
|
||||
Scopes: []string{"openid", "profile", "email"},
|
||||
IDAttribute: "id",
|
||||
IDPOptions: idp_rp.Options{
|
||||
IsLinkingAllowed: true,
|
||||
IsCreationAllowed: true,
|
||||
IsAutoCreation: true,
|
||||
IsAutoUpdate: true,
|
||||
},
|
||||
mustAwait(func() error {
|
||||
_, err := i.Client.Admin.GetProviderByID(ctx, &admin.GetProviderByIDRequest{
|
||||
Id: resp.GetId(),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
return id
|
||||
return err
|
||||
})
|
||||
|
||||
return resp
|
||||
}
|
||||
|
||||
func (s *Tester) AddSAMLProvider(t *testing.T, ctx context.Context) string {
|
||||
ctx = authz.WithInstance(ctx, s.Instance)
|
||||
id, _, err := s.Server.Commands.AddInstanceSAMLProvider(ctx, command.SAMLProvider{
|
||||
Name: "saml-idp",
|
||||
Metadata: []byte("<EntityDescriptor xmlns=\"urn:oasis:names:tc:SAML:2.0:metadata\" validUntil=\"2023-09-16T09:00:32.986Z\" cacheDuration=\"PT48H\" entityID=\"http://localhost:8000/metadata\">\n <IDPSSODescriptor xmlns=\"urn:oasis:names:tc:SAML:2.0:metadata\" protocolSupportEnumeration=\"urn:oasis:names:tc:SAML:2.0:protocol\">\n <KeyDescriptor use=\"signing\">\n <KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\n <X509Data xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\n <X509Certificate xmlns=\"http://www.w3.org/2000/09/xmldsig#\">MIIDBzCCAe+gAwIBAgIJAPr/Mrlc8EGhMA0GCSqGSIb3DQEBBQUAMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xNTEyMjgxOTE5NDVaFw0yNTEyMjUxOTE5NDVaMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANDoWzLos4LWxTn8Gyu2lEbl4WcelUbgLN5zYm4ron8Ahs+rvcsu2zkdD/s6jdGJI8WqJKhYK2u61ygnXgAZqC6ggtFPnBpizcDzjgND2g+aucSoUODHt67f0fQuAmupN/zp5MZysJ6IHLJnYLNpfJYk96lRz9ODnO1Mpqtr9PWxm+pz7nzq5F0vRepkgpcRxv6ufQBjlrFytccyEVdXrvFtkjXcnhVVNSR4kHuOOMS6D7pebSJ1mrCmshbD5SX1jXPBKFPAjozYX6PxqLxUx1Y4faFEf4MBBVcInyB4oURNB2s59hEEi2jq9izNE7EbEK6BY5sEhoCPl9m32zE6ljkCAwEAAaNQME4wHQYDVR0OBBYEFB9ZklC1Ork2zl56zg08ei7ss/+iMB8GA1UdIwQYMBaAFB9ZklC1Ork2zl56zg08ei7ss/+iMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAVoTSQ5pAirw8OR9FZ1bRSuTDhY9uxzl/OL7lUmsv2cMNeCB3BRZqm3mFt+cwN8GsH6f3uvNONIhgFpTGN5LEcXQz89zJEzB+qaHqmbFpHQl/sx2B8ezNgT/882H2IH00dXESEfy/+1gHg2pxjGnhRBN6el/gSaDiySIMKbilDrffuvxiCfbpPN0NRRiPJhd2ay9KuL/RxQRl1gl9cHaWiouWWba1bSBb2ZPhv2rPMUsFo98ntkGCObDX6Y1SpkqmoTbrsbGFsTG2DLxnvr4GdN1BSr0Uu/KV3adj47WkXVPeMYQti/bQmxQB8tRFhrw80qakTLUzreO96WzlBBMtY=</X509Certificate>\n </X509Data>\n </KeyInfo>\n </KeyDescriptor>\n <KeyDescriptor use=\"encryption\">\n <KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\n <X509Data xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\n <X509Certificate xmlns=\"http://www.w3.org/2000/09/xmldsig#\">MIIDBzCCAe+gAwIBAgIJAPr/Mrlc8EGhMA0GCSqGSIb3DQEBBQUAMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xNTEyMjgxOTE5NDVaFw0yNTEyMjUxOTE5NDVaMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANDoWzLos4LWxTn8Gyu2lEbl4WcelUbgLN5zYm4ron8Ahs+rvcsu2zkdD/s6jdGJI8WqJKhYK2u61ygnXgAZqC6ggtFPnBpizcDzjgND2g+aucSoUODHt67f0fQuAmupN/zp5MZysJ6IHLJnYLNpfJYk96lRz9ODnO1Mpqtr9PWxm+pz7nzq5F0vRepkgpcRxv6ufQBjlrFytccyEVdXrvFtkjXcnhVVNSR4kHuOOMS6D7pebSJ1mrCmshbD5SX1jXPBKFPAjozYX6PxqLxUx1Y4faFEf4MBBVcInyB4oURNB2s59hEEi2jq9izNE7EbEK6BY5sEhoCPl9m32zE6ljkCAwEAAaNQME4wHQYDVR0OBBYEFB9ZklC1Ork2zl56zg08ei7ss/+iMB8GA1UdIwQYMBaAFB9ZklC1Ork2zl56zg08ei7ss/+iMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAVoTSQ5pAirw8OR9FZ1bRSuTDhY9uxzl/OL7lUmsv2cMNeCB3BRZqm3mFt+cwN8GsH6f3uvNONIhgFpTGN5LEcXQz89zJEzB+qaHqmbFpHQl/sx2B8ezNgT/882H2IH00dXESEfy/+1gHg2pxjGnhRBN6el/gSaDiySIMKbilDrffuvxiCfbpPN0NRRiPJhd2ay9KuL/RxQRl1gl9cHaWiouWWba1bSBb2ZPhv2rPMUsFo98ntkGCObDX6Y1SpkqmoTbrsbGFsTG2DLxnvr4GdN1BSr0Uu/KV3adj47WkXVPeMYQti/bQmxQB8tRFhrw80qakTLUzreO96WzlBBMtY=</X509Certificate>\n </X509Data>\n </KeyInfo>\n <EncryptionMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#aes128-cbc\"></EncryptionMethod>\n <EncryptionMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#aes192-cbc\"></EncryptionMethod>\n <EncryptionMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#aes256-cbc\"></EncryptionMethod>\n <EncryptionMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p\"></EncryptionMethod>\n </KeyDescriptor>\n <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>\n <SingleSignOnService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect\" Location=\"http://localhost:8000/sso\"></SingleSignOnService>\n <SingleSignOnService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\" Location=\"http://localhost:8000/sso\"></SingleSignOnService>\n </IDPSSODescriptor>\n</EntityDescriptor>"),
|
||||
IDPOptions: idp_rp.Options{
|
||||
func (i *Instance) AddOrgGenericOAuthProvider(ctx context.Context, name string) *mgmt.AddGenericOAuthProviderResponse {
|
||||
resp, err := i.Client.Mgmt.AddGenericOAuthProvider(ctx, &mgmt.AddGenericOAuthProviderRequest{
|
||||
Name: name,
|
||||
ClientId: "clientID",
|
||||
ClientSecret: "clientSecret",
|
||||
AuthorizationEndpoint: "https://example.com/oauth/v2/authorize",
|
||||
TokenEndpoint: "https://example.com/oauth/v2/token",
|
||||
UserEndpoint: "https://api.example.com/user",
|
||||
Scopes: []string{"openid", "profile", "email"},
|
||||
IdAttribute: "id",
|
||||
ProviderOptions: &idp.Options{
|
||||
IsLinkingAllowed: true,
|
||||
IsCreationAllowed: true,
|
||||
IsAutoCreation: true,
|
||||
IsAutoUpdate: true,
|
||||
AutoLinking: idp.AutoLinkingOption_AUTO_LINKING_OPTION_USERNAME,
|
||||
},
|
||||
})
|
||||
logging.OnError(err).Panic("create generic OAuth idp")
|
||||
/*
|
||||
mustAwait(func() error {
|
||||
_, err := i.Client.Mgmt.GetProviderByID(ctx, &mgmt.GetProviderByIDRequest{
|
||||
Id: resp.GetId(),
|
||||
})
|
||||
return err
|
||||
})
|
||||
*/
|
||||
return resp
|
||||
}
|
||||
|
||||
func (i *Instance) AddSAMLProvider(ctx context.Context) string {
|
||||
resp, err := i.Client.Admin.AddSAMLProvider(ctx, &admin.AddSAMLProviderRequest{
|
||||
Name: "saml-idp",
|
||||
Metadata: &admin.AddSAMLProviderRequest_MetadataXml{
|
||||
MetadataXml: []byte("<EntityDescriptor xmlns=\"urn:oasis:names:tc:SAML:2.0:metadata\" validUntil=\"2023-09-16T09:00:32.986Z\" cacheDuration=\"PT48H\" entityID=\"http://localhost:8000/metadata\">\n <IDPSSODescriptor xmlns=\"urn:oasis:names:tc:SAML:2.0:metadata\" protocolSupportEnumeration=\"urn:oasis:names:tc:SAML:2.0:protocol\">\n <KeyDescriptor use=\"signing\">\n <KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\n <X509Data xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\n <X509Certificate xmlns=\"http://www.w3.org/2000/09/xmldsig#\">MIIDBzCCAe+gAwIBAgIJAPr/Mrlc8EGhMA0GCSqGSIb3DQEBBQUAMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xNTEyMjgxOTE5NDVaFw0yNTEyMjUxOTE5NDVaMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANDoWzLos4LWxTn8Gyu2lEbl4WcelUbgLN5zYm4ron8Ahs+rvcsu2zkdD/s6jdGJI8WqJKhYK2u61ygnXgAZqC6ggtFPnBpizcDzjgND2g+aucSoUODHt67f0fQuAmupN/zp5MZysJ6IHLJnYLNpfJYk96lRz9ODnO1Mpqtr9PWxm+pz7nzq5F0vRepkgpcRxv6ufQBjlrFytccyEVdXrvFtkjXcnhVVNSR4kHuOOMS6D7pebSJ1mrCmshbD5SX1jXPBKFPAjozYX6PxqLxUx1Y4faFEf4MBBVcInyB4oURNB2s59hEEi2jq9izNE7EbEK6BY5sEhoCPl9m32zE6ljkCAwEAAaNQME4wHQYDVR0OBBYEFB9ZklC1Ork2zl56zg08ei7ss/+iMB8GA1UdIwQYMBaAFB9ZklC1Ork2zl56zg08ei7ss/+iMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAVoTSQ5pAirw8OR9FZ1bRSuTDhY9uxzl/OL7lUmsv2cMNeCB3BRZqm3mFt+cwN8GsH6f3uvNONIhgFpTGN5LEcXQz89zJEzB+qaHqmbFpHQl/sx2B8ezNgT/882H2IH00dXESEfy/+1gHg2pxjGnhRBN6el/gSaDiySIMKbilDrffuvxiCfbpPN0NRRiPJhd2ay9KuL/RxQRl1gl9cHaWiouWWba1bSBb2ZPhv2rPMUsFo98ntkGCObDX6Y1SpkqmoTbrsbGFsTG2DLxnvr4GdN1BSr0Uu/KV3adj47WkXVPeMYQti/bQmxQB8tRFhrw80qakTLUzreO96WzlBBMtY=</X509Certificate>\n </X509Data>\n </KeyInfo>\n </KeyDescriptor>\n <KeyDescriptor use=\"encryption\">\n <KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\n <X509Data xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\n <X509Certificate xmlns=\"http://www.w3.org/2000/09/xmldsig#\">MIIDBzCCAe+gAwIBAgIJAPr/Mrlc8EGhMA0GCSqGSIb3DQEBBQUAMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xNTEyMjgxOTE5NDVaFw0yNTEyMjUxOTE5NDVaMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANDoWzLos4LWxTn8Gyu2lEbl4WcelUbgLN5zYm4ron8Ahs+rvcsu2zkdD/s6jdGJI8WqJKhYK2u61ygnXgAZqC6ggtFPnBpizcDzjgND2g+aucSoUODHt67f0fQuAmupN/zp5MZysJ6IHLJnYLNpfJYk96lRz9ODnO1Mpqtr9PWxm+pz7nzq5F0vRepkgpcRxv6ufQBjlrFytccyEVdXrvFtkjXcnhVVNSR4kHuOOMS6D7pebSJ1mrCmshbD5SX1jXPBKFPAjozYX6PxqLxUx1Y4faFEf4MBBVcInyB4oURNB2s59hEEi2jq9izNE7EbEK6BY5sEhoCPl9m32zE6ljkCAwEAAaNQME4wHQYDVR0OBBYEFB9ZklC1Ork2zl56zg08ei7ss/+iMB8GA1UdIwQYMBaAFB9ZklC1Ork2zl56zg08ei7ss/+iMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAVoTSQ5pAirw8OR9FZ1bRSuTDhY9uxzl/OL7lUmsv2cMNeCB3BRZqm3mFt+cwN8GsH6f3uvNONIhgFpTGN5LEcXQz89zJEzB+qaHqmbFpHQl/sx2B8ezNgT/882H2IH00dXESEfy/+1gHg2pxjGnhRBN6el/gSaDiySIMKbilDrffuvxiCfbpPN0NRRiPJhd2ay9KuL/RxQRl1gl9cHaWiouWWba1bSBb2ZPhv2rPMUsFo98ntkGCObDX6Y1SpkqmoTbrsbGFsTG2DLxnvr4GdN1BSr0Uu/KV3adj47WkXVPeMYQti/bQmxQB8tRFhrw80qakTLUzreO96WzlBBMtY=</X509Certificate>\n </X509Data>\n </KeyInfo>\n <EncryptionMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#aes128-cbc\"></EncryptionMethod>\n <EncryptionMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#aes192-cbc\"></EncryptionMethod>\n <EncryptionMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#aes256-cbc\"></EncryptionMethod>\n <EncryptionMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p\"></EncryptionMethod>\n </KeyDescriptor>\n <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>\n <SingleSignOnService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect\" Location=\"http://localhost:8000/sso\"></SingleSignOnService>\n <SingleSignOnService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\" Location=\"http://localhost:8000/sso\"></SingleSignOnService>\n </IDPSSODescriptor>\n</EntityDescriptor>"),
|
||||
},
|
||||
ProviderOptions: &idp.Options{
|
||||
IsLinkingAllowed: true,
|
||||
IsCreationAllowed: true,
|
||||
IsAutoCreation: true,
|
||||
IsAutoUpdate: true,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
return id
|
||||
logging.OnError(err).Panic("create saml idp")
|
||||
return resp.GetId()
|
||||
}
|
||||
|
||||
func (s *Tester) AddSAMLRedirectProvider(t *testing.T, ctx context.Context, transientMappingAttributeName string) string {
|
||||
ctx = authz.WithInstance(ctx, s.Instance)
|
||||
id, _, err := s.Server.Commands.AddInstanceSAMLProvider(ctx, command.SAMLProvider{
|
||||
Name: "saml-idp-redirect",
|
||||
Binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect",
|
||||
Metadata: []byte("<EntityDescriptor xmlns=\"urn:oasis:names:tc:SAML:2.0:metadata\" validUntil=\"2023-09-16T09:00:32.986Z\" cacheDuration=\"PT48H\" entityID=\"http://localhost:8000/metadata\">\n <IDPSSODescriptor xmlns=\"urn:oasis:names:tc:SAML:2.0:metadata\" protocolSupportEnumeration=\"urn:oasis:names:tc:SAML:2.0:protocol\">\n <KeyDescriptor use=\"signing\">\n <KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\n <X509Data xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\n <X509Certificate xmlns=\"http://www.w3.org/2000/09/xmldsig#\">MIIDBzCCAe+gAwIBAgIJAPr/Mrlc8EGhMA0GCSqGSIb3DQEBBQUAMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xNTEyMjgxOTE5NDVaFw0yNTEyMjUxOTE5NDVaMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANDoWzLos4LWxTn8Gyu2lEbl4WcelUbgLN5zYm4ron8Ahs+rvcsu2zkdD/s6jdGJI8WqJKhYK2u61ygnXgAZqC6ggtFPnBpizcDzjgND2g+aucSoUODHt67f0fQuAmupN/zp5MZysJ6IHLJnYLNpfJYk96lRz9ODnO1Mpqtr9PWxm+pz7nzq5F0vRepkgpcRxv6ufQBjlrFytccyEVdXrvFtkjXcnhVVNSR4kHuOOMS6D7pebSJ1mrCmshbD5SX1jXPBKFPAjozYX6PxqLxUx1Y4faFEf4MBBVcInyB4oURNB2s59hEEi2jq9izNE7EbEK6BY5sEhoCPl9m32zE6ljkCAwEAAaNQME4wHQYDVR0OBBYEFB9ZklC1Ork2zl56zg08ei7ss/+iMB8GA1UdIwQYMBaAFB9ZklC1Ork2zl56zg08ei7ss/+iMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAVoTSQ5pAirw8OR9FZ1bRSuTDhY9uxzl/OL7lUmsv2cMNeCB3BRZqm3mFt+cwN8GsH6f3uvNONIhgFpTGN5LEcXQz89zJEzB+qaHqmbFpHQl/sx2B8ezNgT/882H2IH00dXESEfy/+1gHg2pxjGnhRBN6el/gSaDiySIMKbilDrffuvxiCfbpPN0NRRiPJhd2ay9KuL/RxQRl1gl9cHaWiouWWba1bSBb2ZPhv2rPMUsFo98ntkGCObDX6Y1SpkqmoTbrsbGFsTG2DLxnvr4GdN1BSr0Uu/KV3adj47WkXVPeMYQti/bQmxQB8tRFhrw80qakTLUzreO96WzlBBMtY=</X509Certificate>\n </X509Data>\n </KeyInfo>\n </KeyDescriptor>\n <KeyDescriptor use=\"encryption\">\n <KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\n <X509Data xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\n <X509Certificate xmlns=\"http://www.w3.org/2000/09/xmldsig#\">MIIDBzCCAe+gAwIBAgIJAPr/Mrlc8EGhMA0GCSqGSIb3DQEBBQUAMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xNTEyMjgxOTE5NDVaFw0yNTEyMjUxOTE5NDVaMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANDoWzLos4LWxTn8Gyu2lEbl4WcelUbgLN5zYm4ron8Ahs+rvcsu2zkdD/s6jdGJI8WqJKhYK2u61ygnXgAZqC6ggtFPnBpizcDzjgND2g+aucSoUODHt67f0fQuAmupN/zp5MZysJ6IHLJnYLNpfJYk96lRz9ODnO1Mpqtr9PWxm+pz7nzq5F0vRepkgpcRxv6ufQBjlrFytccyEVdXrvFtkjXcnhVVNSR4kHuOOMS6D7pebSJ1mrCmshbD5SX1jXPBKFPAjozYX6PxqLxUx1Y4faFEf4MBBVcInyB4oURNB2s59hEEi2jq9izNE7EbEK6BY5sEhoCPl9m32zE6ljkCAwEAAaNQME4wHQYDVR0OBBYEFB9ZklC1Ork2zl56zg08ei7ss/+iMB8GA1UdIwQYMBaAFB9ZklC1Ork2zl56zg08ei7ss/+iMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAVoTSQ5pAirw8OR9FZ1bRSuTDhY9uxzl/OL7lUmsv2cMNeCB3BRZqm3mFt+cwN8GsH6f3uvNONIhgFpTGN5LEcXQz89zJEzB+qaHqmbFpHQl/sx2B8ezNgT/882H2IH00dXESEfy/+1gHg2pxjGnhRBN6el/gSaDiySIMKbilDrffuvxiCfbpPN0NRRiPJhd2ay9KuL/RxQRl1gl9cHaWiouWWba1bSBb2ZPhv2rPMUsFo98ntkGCObDX6Y1SpkqmoTbrsbGFsTG2DLxnvr4GdN1BSr0Uu/KV3adj47WkXVPeMYQti/bQmxQB8tRFhrw80qakTLUzreO96WzlBBMtY=</X509Certificate>\n </X509Data>\n </KeyInfo>\n <EncryptionMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#aes128-cbc\"></EncryptionMethod>\n <EncryptionMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#aes192-cbc\"></EncryptionMethod>\n <EncryptionMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#aes256-cbc\"></EncryptionMethod>\n <EncryptionMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p\"></EncryptionMethod>\n </KeyDescriptor>\n <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>\n <SingleSignOnService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect\" Location=\"http://localhost:8000/sso\"></SingleSignOnService>\n </IDPSSODescriptor>\n</EntityDescriptor>"),
|
||||
TransientMappingAttributeName: transientMappingAttributeName,
|
||||
IDPOptions: idp_rp.Options{
|
||||
func (i *Instance) AddSAMLRedirectProvider(ctx context.Context, transientMappingAttributeName string) string {
|
||||
resp, err := i.Client.Admin.AddSAMLProvider(ctx, &admin.AddSAMLProviderRequest{
|
||||
Name: "saml-idp-redirect",
|
||||
Binding: idp.SAMLBinding_SAML_BINDING_REDIRECT,
|
||||
Metadata: &admin.AddSAMLProviderRequest_MetadataXml{
|
||||
MetadataXml: []byte("<EntityDescriptor xmlns=\"urn:oasis:names:tc:SAML:2.0:metadata\" validUntil=\"2023-09-16T09:00:32.986Z\" cacheDuration=\"PT48H\" entityID=\"http://localhost:8000/metadata\">\n <IDPSSODescriptor xmlns=\"urn:oasis:names:tc:SAML:2.0:metadata\" protocolSupportEnumeration=\"urn:oasis:names:tc:SAML:2.0:protocol\">\n <KeyDescriptor use=\"signing\">\n <KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\n <X509Data xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\n <X509Certificate xmlns=\"http://www.w3.org/2000/09/xmldsig#\">MIIDBzCCAe+gAwIBAgIJAPr/Mrlc8EGhMA0GCSqGSIb3DQEBBQUAMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xNTEyMjgxOTE5NDVaFw0yNTEyMjUxOTE5NDVaMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANDoWzLos4LWxTn8Gyu2lEbl4WcelUbgLN5zYm4ron8Ahs+rvcsu2zkdD/s6jdGJI8WqJKhYK2u61ygnXgAZqC6ggtFPnBpizcDzjgND2g+aucSoUODHt67f0fQuAmupN/zp5MZysJ6IHLJnYLNpfJYk96lRz9ODnO1Mpqtr9PWxm+pz7nzq5F0vRepkgpcRxv6ufQBjlrFytccyEVdXrvFtkjXcnhVVNSR4kHuOOMS6D7pebSJ1mrCmshbD5SX1jXPBKFPAjozYX6PxqLxUx1Y4faFEf4MBBVcInyB4oURNB2s59hEEi2jq9izNE7EbEK6BY5sEhoCPl9m32zE6ljkCAwEAAaNQME4wHQYDVR0OBBYEFB9ZklC1Ork2zl56zg08ei7ss/+iMB8GA1UdIwQYMBaAFB9ZklC1Ork2zl56zg08ei7ss/+iMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAVoTSQ5pAirw8OR9FZ1bRSuTDhY9uxzl/OL7lUmsv2cMNeCB3BRZqm3mFt+cwN8GsH6f3uvNONIhgFpTGN5LEcXQz89zJEzB+qaHqmbFpHQl/sx2B8ezNgT/882H2IH00dXESEfy/+1gHg2pxjGnhRBN6el/gSaDiySIMKbilDrffuvxiCfbpPN0NRRiPJhd2ay9KuL/RxQRl1gl9cHaWiouWWba1bSBb2ZPhv2rPMUsFo98ntkGCObDX6Y1SpkqmoTbrsbGFsTG2DLxnvr4GdN1BSr0Uu/KV3adj47WkXVPeMYQti/bQmxQB8tRFhrw80qakTLUzreO96WzlBBMtY=</X509Certificate>\n </X509Data>\n </KeyInfo>\n </KeyDescriptor>\n <KeyDescriptor use=\"encryption\">\n <KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\n <X509Data xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\n <X509Certificate xmlns=\"http://www.w3.org/2000/09/xmldsig#\">MIIDBzCCAe+gAwIBAgIJAPr/Mrlc8EGhMA0GCSqGSIb3DQEBBQUAMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xNTEyMjgxOTE5NDVaFw0yNTEyMjUxOTE5NDVaMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANDoWzLos4LWxTn8Gyu2lEbl4WcelUbgLN5zYm4ron8Ahs+rvcsu2zkdD/s6jdGJI8WqJKhYK2u61ygnXgAZqC6ggtFPnBpizcDzjgND2g+aucSoUODHt67f0fQuAmupN/zp5MZysJ6IHLJnYLNpfJYk96lRz9ODnO1Mpqtr9PWxm+pz7nzq5F0vRepkgpcRxv6ufQBjlrFytccyEVdXrvFtkjXcnhVVNSR4kHuOOMS6D7pebSJ1mrCmshbD5SX1jXPBKFPAjozYX6PxqLxUx1Y4faFEf4MBBVcInyB4oURNB2s59hEEi2jq9izNE7EbEK6BY5sEhoCPl9m32zE6ljkCAwEAAaNQME4wHQYDVR0OBBYEFB9ZklC1Ork2zl56zg08ei7ss/+iMB8GA1UdIwQYMBaAFB9ZklC1Ork2zl56zg08ei7ss/+iMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAVoTSQ5pAirw8OR9FZ1bRSuTDhY9uxzl/OL7lUmsv2cMNeCB3BRZqm3mFt+cwN8GsH6f3uvNONIhgFpTGN5LEcXQz89zJEzB+qaHqmbFpHQl/sx2B8ezNgT/882H2IH00dXESEfy/+1gHg2pxjGnhRBN6el/gSaDiySIMKbilDrffuvxiCfbpPN0NRRiPJhd2ay9KuL/RxQRl1gl9cHaWiouWWba1bSBb2ZPhv2rPMUsFo98ntkGCObDX6Y1SpkqmoTbrsbGFsTG2DLxnvr4GdN1BSr0Uu/KV3adj47WkXVPeMYQti/bQmxQB8tRFhrw80qakTLUzreO96WzlBBMtY=</X509Certificate>\n </X509Data>\n </KeyInfo>\n <EncryptionMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#aes128-cbc\"></EncryptionMethod>\n <EncryptionMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#aes192-cbc\"></EncryptionMethod>\n <EncryptionMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#aes256-cbc\"></EncryptionMethod>\n <EncryptionMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p\"></EncryptionMethod>\n </KeyDescriptor>\n <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>\n <SingleSignOnService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect\" Location=\"http://localhost:8000/sso\"></SingleSignOnService>\n </IDPSSODescriptor>\n</EntityDescriptor>"),
|
||||
},
|
||||
TransientMappingAttributeName: &transientMappingAttributeName,
|
||||
ProviderOptions: &idp.Options{
|
||||
IsLinkingAllowed: true,
|
||||
IsCreationAllowed: true,
|
||||
IsAutoCreation: true,
|
||||
IsAutoUpdate: true,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
return id
|
||||
logging.OnError(err).Panic("create saml idp")
|
||||
return resp.GetId()
|
||||
}
|
||||
|
||||
func (s *Tester) AddSAMLPostProvider(t *testing.T, ctx context.Context) string {
|
||||
ctx = authz.WithInstance(ctx, s.Instance)
|
||||
id, _, err := s.Server.Commands.AddInstanceSAMLProvider(ctx, command.SAMLProvider{
|
||||
Name: "saml-idp-post",
|
||||
Binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
|
||||
Metadata: []byte("<EntityDescriptor xmlns=\"urn:oasis:names:tc:SAML:2.0:metadata\" validUntil=\"2023-09-16T09:00:32.986Z\" cacheDuration=\"PT48H\" entityID=\"http://localhost:8000/metadata\">\n <IDPSSODescriptor xmlns=\"urn:oasis:names:tc:SAML:2.0:metadata\" protocolSupportEnumeration=\"urn:oasis:names:tc:SAML:2.0:protocol\">\n <KeyDescriptor use=\"signing\">\n <KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\n <X509Data xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\n <X509Certificate xmlns=\"http://www.w3.org/2000/09/xmldsig#\">MIIDBzCCAe+gAwIBAgIJAPr/Mrlc8EGhMA0GCSqGSIb3DQEBBQUAMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xNTEyMjgxOTE5NDVaFw0yNTEyMjUxOTE5NDVaMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANDoWzLos4LWxTn8Gyu2lEbl4WcelUbgLN5zYm4ron8Ahs+rvcsu2zkdD/s6jdGJI8WqJKhYK2u61ygnXgAZqC6ggtFPnBpizcDzjgND2g+aucSoUODHt67f0fQuAmupN/zp5MZysJ6IHLJnYLNpfJYk96lRz9ODnO1Mpqtr9PWxm+pz7nzq5F0vRepkgpcRxv6ufQBjlrFytccyEVdXrvFtkjXcnhVVNSR4kHuOOMS6D7pebSJ1mrCmshbD5SX1jXPBKFPAjozYX6PxqLxUx1Y4faFEf4MBBVcInyB4oURNB2s59hEEi2jq9izNE7EbEK6BY5sEhoCPl9m32zE6ljkCAwEAAaNQME4wHQYDVR0OBBYEFB9ZklC1Ork2zl56zg08ei7ss/+iMB8GA1UdIwQYMBaAFB9ZklC1Ork2zl56zg08ei7ss/+iMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAVoTSQ5pAirw8OR9FZ1bRSuTDhY9uxzl/OL7lUmsv2cMNeCB3BRZqm3mFt+cwN8GsH6f3uvNONIhgFpTGN5LEcXQz89zJEzB+qaHqmbFpHQl/sx2B8ezNgT/882H2IH00dXESEfy/+1gHg2pxjGnhRBN6el/gSaDiySIMKbilDrffuvxiCfbpPN0NRRiPJhd2ay9KuL/RxQRl1gl9cHaWiouWWba1bSBb2ZPhv2rPMUsFo98ntkGCObDX6Y1SpkqmoTbrsbGFsTG2DLxnvr4GdN1BSr0Uu/KV3adj47WkXVPeMYQti/bQmxQB8tRFhrw80qakTLUzreO96WzlBBMtY=</X509Certificate>\n </X509Data>\n </KeyInfo>\n </KeyDescriptor>\n <KeyDescriptor use=\"encryption\">\n <KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\n <X509Data xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\n <X509Certificate xmlns=\"http://www.w3.org/2000/09/xmldsig#\">MIIDBzCCAe+gAwIBAgIJAPr/Mrlc8EGhMA0GCSqGSIb3DQEBBQUAMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xNTEyMjgxOTE5NDVaFw0yNTEyMjUxOTE5NDVaMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANDoWzLos4LWxTn8Gyu2lEbl4WcelUbgLN5zYm4ron8Ahs+rvcsu2zkdD/s6jdGJI8WqJKhYK2u61ygnXgAZqC6ggtFPnBpizcDzjgND2g+aucSoUODHt67f0fQuAmupN/zp5MZysJ6IHLJnYLNpfJYk96lRz9ODnO1Mpqtr9PWxm+pz7nzq5F0vRepkgpcRxv6ufQBjlrFytccyEVdXrvFtkjXcnhVVNSR4kHuOOMS6D7pebSJ1mrCmshbD5SX1jXPBKFPAjozYX6PxqLxUx1Y4faFEf4MBBVcInyB4oURNB2s59hEEi2jq9izNE7EbEK6BY5sEhoCPl9m32zE6ljkCAwEAAaNQME4wHQYDVR0OBBYEFB9ZklC1Ork2zl56zg08ei7ss/+iMB8GA1UdIwQYMBaAFB9ZklC1Ork2zl56zg08ei7ss/+iMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAVoTSQ5pAirw8OR9FZ1bRSuTDhY9uxzl/OL7lUmsv2cMNeCB3BRZqm3mFt+cwN8GsH6f3uvNONIhgFpTGN5LEcXQz89zJEzB+qaHqmbFpHQl/sx2B8ezNgT/882H2IH00dXESEfy/+1gHg2pxjGnhRBN6el/gSaDiySIMKbilDrffuvxiCfbpPN0NRRiPJhd2ay9KuL/RxQRl1gl9cHaWiouWWba1bSBb2ZPhv2rPMUsFo98ntkGCObDX6Y1SpkqmoTbrsbGFsTG2DLxnvr4GdN1BSr0Uu/KV3adj47WkXVPeMYQti/bQmxQB8tRFhrw80qakTLUzreO96WzlBBMtY=</X509Certificate>\n </X509Data>\n </KeyInfo>\n <EncryptionMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#aes128-cbc\"></EncryptionMethod>\n <EncryptionMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#aes192-cbc\"></EncryptionMethod>\n <EncryptionMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#aes256-cbc\"></EncryptionMethod>\n <EncryptionMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p\"></EncryptionMethod>\n </KeyDescriptor>\n <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>\n <SingleSignOnService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\" Location=\"http://localhost:8000/sso\"></SingleSignOnService>\n </IDPSSODescriptor>\n</EntityDescriptor>"),
|
||||
IDPOptions: idp_rp.Options{
|
||||
func (i *Instance) AddSAMLPostProvider(ctx context.Context) string {
|
||||
resp, err := i.Client.Admin.AddSAMLProvider(ctx, &admin.AddSAMLProviderRequest{
|
||||
Name: "saml-idp-post",
|
||||
Binding: idp.SAMLBinding_SAML_BINDING_POST,
|
||||
Metadata: &admin.AddSAMLProviderRequest_MetadataXml{
|
||||
MetadataXml: []byte("<EntityDescriptor xmlns=\"urn:oasis:names:tc:SAML:2.0:metadata\" validUntil=\"2023-09-16T09:00:32.986Z\" cacheDuration=\"PT48H\" entityID=\"http://localhost:8000/metadata\">\n <IDPSSODescriptor xmlns=\"urn:oasis:names:tc:SAML:2.0:metadata\" protocolSupportEnumeration=\"urn:oasis:names:tc:SAML:2.0:protocol\">\n <KeyDescriptor use=\"signing\">\n <KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\n <X509Data xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\n <X509Certificate xmlns=\"http://www.w3.org/2000/09/xmldsig#\">MIIDBzCCAe+gAwIBAgIJAPr/Mrlc8EGhMA0GCSqGSIb3DQEBBQUAMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xNTEyMjgxOTE5NDVaFw0yNTEyMjUxOTE5NDVaMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANDoWzLos4LWxTn8Gyu2lEbl4WcelUbgLN5zYm4ron8Ahs+rvcsu2zkdD/s6jdGJI8WqJKhYK2u61ygnXgAZqC6ggtFPnBpizcDzjgND2g+aucSoUODHt67f0fQuAmupN/zp5MZysJ6IHLJnYLNpfJYk96lRz9ODnO1Mpqtr9PWxm+pz7nzq5F0vRepkgpcRxv6ufQBjlrFytccyEVdXrvFtkjXcnhVVNSR4kHuOOMS6D7pebSJ1mrCmshbD5SX1jXPBKFPAjozYX6PxqLxUx1Y4faFEf4MBBVcInyB4oURNB2s59hEEi2jq9izNE7EbEK6BY5sEhoCPl9m32zE6ljkCAwEAAaNQME4wHQYDVR0OBBYEFB9ZklC1Ork2zl56zg08ei7ss/+iMB8GA1UdIwQYMBaAFB9ZklC1Ork2zl56zg08ei7ss/+iMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAVoTSQ5pAirw8OR9FZ1bRSuTDhY9uxzl/OL7lUmsv2cMNeCB3BRZqm3mFt+cwN8GsH6f3uvNONIhgFpTGN5LEcXQz89zJEzB+qaHqmbFpHQl/sx2B8ezNgT/882H2IH00dXESEfy/+1gHg2pxjGnhRBN6el/gSaDiySIMKbilDrffuvxiCfbpPN0NRRiPJhd2ay9KuL/RxQRl1gl9cHaWiouWWba1bSBb2ZPhv2rPMUsFo98ntkGCObDX6Y1SpkqmoTbrsbGFsTG2DLxnvr4GdN1BSr0Uu/KV3adj47WkXVPeMYQti/bQmxQB8tRFhrw80qakTLUzreO96WzlBBMtY=</X509Certificate>\n </X509Data>\n </KeyInfo>\n </KeyDescriptor>\n <KeyDescriptor use=\"encryption\">\n <KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\n <X509Data xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\n <X509Certificate xmlns=\"http://www.w3.org/2000/09/xmldsig#\">MIIDBzCCAe+gAwIBAgIJAPr/Mrlc8EGhMA0GCSqGSIb3DQEBBQUAMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xNTEyMjgxOTE5NDVaFw0yNTEyMjUxOTE5NDVaMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANDoWzLos4LWxTn8Gyu2lEbl4WcelUbgLN5zYm4ron8Ahs+rvcsu2zkdD/s6jdGJI8WqJKhYK2u61ygnXgAZqC6ggtFPnBpizcDzjgND2g+aucSoUODHt67f0fQuAmupN/zp5MZysJ6IHLJnYLNpfJYk96lRz9ODnO1Mpqtr9PWxm+pz7nzq5F0vRepkgpcRxv6ufQBjlrFytccyEVdXrvFtkjXcnhVVNSR4kHuOOMS6D7pebSJ1mrCmshbD5SX1jXPBKFPAjozYX6PxqLxUx1Y4faFEf4MBBVcInyB4oURNB2s59hEEi2jq9izNE7EbEK6BY5sEhoCPl9m32zE6ljkCAwEAAaNQME4wHQYDVR0OBBYEFB9ZklC1Ork2zl56zg08ei7ss/+iMB8GA1UdIwQYMBaAFB9ZklC1Ork2zl56zg08ei7ss/+iMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAVoTSQ5pAirw8OR9FZ1bRSuTDhY9uxzl/OL7lUmsv2cMNeCB3BRZqm3mFt+cwN8GsH6f3uvNONIhgFpTGN5LEcXQz89zJEzB+qaHqmbFpHQl/sx2B8ezNgT/882H2IH00dXESEfy/+1gHg2pxjGnhRBN6el/gSaDiySIMKbilDrffuvxiCfbpPN0NRRiPJhd2ay9KuL/RxQRl1gl9cHaWiouWWba1bSBb2ZPhv2rPMUsFo98ntkGCObDX6Y1SpkqmoTbrsbGFsTG2DLxnvr4GdN1BSr0Uu/KV3adj47WkXVPeMYQti/bQmxQB8tRFhrw80qakTLUzreO96WzlBBMtY=</X509Certificate>\n </X509Data>\n </KeyInfo>\n <EncryptionMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#aes128-cbc\"></EncryptionMethod>\n <EncryptionMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#aes192-cbc\"></EncryptionMethod>\n <EncryptionMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#aes256-cbc\"></EncryptionMethod>\n <EncryptionMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p\"></EncryptionMethod>\n </KeyDescriptor>\n <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>\n <SingleSignOnService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\" Location=\"http://localhost:8000/sso\"></SingleSignOnService>\n </IDPSSODescriptor>\n</EntityDescriptor>"),
|
||||
},
|
||||
ProviderOptions: &idp.Options{
|
||||
IsLinkingAllowed: true,
|
||||
IsCreationAllowed: true,
|
||||
IsAutoCreation: true,
|
||||
IsAutoUpdate: true,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
return id
|
||||
logging.OnError(err).Panic("create saml idp")
|
||||
return resp.GetId()
|
||||
}
|
||||
|
||||
func (s *Tester) CreateIntent(t *testing.T, ctx context.Context, idpID string) string {
|
||||
/*
|
||||
func (s *Instance) CreateIntent(t *testing.T, ctx context.Context, idpID string) string {
|
||||
resp, err := i.Client.UserV2.StartIdentityProviderIntent(ctx, &user.StartIdentityProviderIntentRequest{
|
||||
IdpId: idpID,
|
||||
Content: &user.StartIdentityProviderIntentRequest_Urls{
|
||||
Urls: &user.RedirectURLs{
|
||||
SuccessUrl: "https://example.com/success",
|
||||
FailureUrl: "https://example.com/failure",
|
||||
},
|
||||
AutoLinking: idp.AutoLinkingOption_AUTO_LINKING_OPTION_USERNAME,
|
||||
},
|
||||
})
|
||||
logging.OnError(err).Fatal("create generic OAuth idp")
|
||||
return resp
|
||||
}
|
||||
|
||||
func (i *Instance) CreateIntent(t *testing.T, ctx context.Context, idpID string) string {
|
||||
ctx = authz.WithInstance(context.WithoutCancel(ctx), s.Instance)
|
||||
writeModel, _, err := s.Commands.CreateIntent(ctx, idpID, "https://example.com/success", "https://example.com/failure", s.Instance.InstanceID())
|
||||
require.NoError(t, err)
|
||||
return writeModel.AggregateID
|
||||
}
|
||||
|
||||
func (s *Tester) CreateSuccessfulOAuthIntent(t *testing.T, ctx context.Context, idpID, userID, idpUserID string) (string, string, time.Time, uint64) {
|
||||
func (i *Instance) CreateSuccessfulOAuthIntent(t *testing.T, ctx context.Context, idpID, userID, idpUserID string) (string, string, time.Time, uint64) {
|
||||
ctx = authz.WithInstance(context.WithoutCancel(ctx), s.Instance)
|
||||
intentID := s.CreateIntent(t, ctx, idpID)
|
||||
writeModel, err := s.Commands.GetIntentWriteModel(ctx, intentID, s.Instance.InstanceID())
|
||||
@@ -582,7 +542,7 @@ func (s *Tester) CreateSuccessfulOAuthIntent(t *testing.T, ctx context.Context,
|
||||
return intentID, token, writeModel.ChangeDate, writeModel.ProcessedSequence
|
||||
}
|
||||
|
||||
func (s *Tester) CreateSuccessfulLDAPIntent(t *testing.T, ctx context.Context, idpID, userID, idpUserID string) (string, string, time.Time, uint64) {
|
||||
func (s *Instance) CreateSuccessfulLDAPIntent(t *testing.T, ctx context.Context, idpID, userID, idpUserID string) (string, string, time.Time, uint64) {
|
||||
ctx = authz.WithInstance(context.WithoutCancel(ctx), s.Instance)
|
||||
intentID := s.CreateIntent(t, ctx, idpID)
|
||||
writeModel, err := s.Commands.GetIntentWriteModel(ctx, intentID, s.Instance.InstanceID())
|
||||
@@ -610,7 +570,7 @@ func (s *Tester) CreateSuccessfulLDAPIntent(t *testing.T, ctx context.Context, i
|
||||
return intentID, token, writeModel.ChangeDate, writeModel.ProcessedSequence
|
||||
}
|
||||
|
||||
func (s *Tester) CreateSuccessfulSAMLIntent(t *testing.T, ctx context.Context, idpID, userID, idpUserID string) (string, string, time.Time, uint64) {
|
||||
func (s *Instance) CreateSuccessfulSAMLIntent(t *testing.T, ctx context.Context, idpID, userID, idpUserID string) (string, string, time.Time, uint64) {
|
||||
ctx = authz.WithInstance(context.WithoutCancel(ctx), s.Instance)
|
||||
intentID := s.CreateIntent(t, ctx, idpID)
|
||||
writeModel, err := s.Server.Commands.GetIntentWriteModel(ctx, intentID, s.Instance.InstanceID())
|
||||
@@ -626,17 +586,18 @@ func (s *Tester) CreateSuccessfulSAMLIntent(t *testing.T, ctx context.Context, i
|
||||
require.NoError(t, err)
|
||||
return intentID, token, writeModel.ChangeDate, writeModel.ProcessedSequence
|
||||
}
|
||||
*/
|
||||
|
||||
func (s *Tester) CreateVerifiedWebAuthNSession(t *testing.T, ctx context.Context, userID string) (id, token string, start, change time.Time) {
|
||||
return s.CreateVerifiedWebAuthNSessionWithLifetime(t, ctx, userID, 0)
|
||||
func (i *Instance) CreateVerifiedWebAuthNSession(t *testing.T, ctx context.Context, userID string) (id, token string, start, change time.Time) {
|
||||
return i.CreateVerifiedWebAuthNSessionWithLifetime(t, ctx, userID, 0)
|
||||
}
|
||||
|
||||
func (s *Tester) CreateVerifiedWebAuthNSessionWithLifetime(t *testing.T, ctx context.Context, userID string, lifetime time.Duration) (id, token string, start, change time.Time) {
|
||||
func (i *Instance) CreateVerifiedWebAuthNSessionWithLifetime(t *testing.T, ctx context.Context, userID string, lifetime time.Duration) (id, token string, start, change time.Time) {
|
||||
var sessionLifetime *durationpb.Duration
|
||||
if lifetime > 0 {
|
||||
sessionLifetime = durationpb.New(lifetime)
|
||||
}
|
||||
createResp, err := s.Client.SessionV2.CreateSession(ctx, &session.CreateSessionRequest{
|
||||
createResp, err := i.Client.SessionV2.CreateSession(ctx, &session.CreateSessionRequest{
|
||||
Checks: &session.Checks{
|
||||
User: &session.CheckUser{
|
||||
Search: &session.CheckUser_UserId{UserId: userID},
|
||||
@@ -644,7 +605,7 @@ func (s *Tester) CreateVerifiedWebAuthNSessionWithLifetime(t *testing.T, ctx con
|
||||
},
|
||||
Challenges: &session.RequestChallenges{
|
||||
WebAuthN: &session.RequestChallenges_WebAuthN{
|
||||
Domain: s.Config.ExternalDomain,
|
||||
Domain: i.Domain,
|
||||
UserVerificationRequirement: session.UserVerificationRequirement_USER_VERIFICATION_REQUIREMENT_REQUIRED,
|
||||
},
|
||||
},
|
||||
@@ -652,10 +613,10 @@ func (s *Tester) CreateVerifiedWebAuthNSessionWithLifetime(t *testing.T, ctx con
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
assertion, err := s.WebAuthN.CreateAssertionResponse(createResp.GetChallenges().GetWebAuthN().GetPublicKeyCredentialRequestOptions(), true)
|
||||
assertion, err := i.WebAuthN.CreateAssertionResponse(createResp.GetChallenges().GetWebAuthN().GetPublicKeyCredentialRequestOptions(), true)
|
||||
require.NoError(t, err)
|
||||
|
||||
updateResp, err := s.Client.SessionV2.SetSession(ctx, &session.SetSessionRequest{
|
||||
updateResp, err := i.Client.SessionV2.SetSession(ctx, &session.SetSessionRequest{
|
||||
SessionId: createResp.GetSessionId(),
|
||||
Checks: &session.Checks{
|
||||
WebAuthN: &session.CheckWebAuthN{
|
||||
@@ -668,8 +629,8 @@ func (s *Tester) CreateVerifiedWebAuthNSessionWithLifetime(t *testing.T, ctx con
|
||||
createResp.GetDetails().GetChangeDate().AsTime(), updateResp.GetDetails().GetChangeDate().AsTime()
|
||||
}
|
||||
|
||||
func (s *Tester) CreatePasswordSession(t *testing.T, ctx context.Context, userID, password string) (id, token string, start, change time.Time) {
|
||||
createResp, err := s.Client.SessionV2.CreateSession(ctx, &session.CreateSessionRequest{
|
||||
func (i *Instance) CreatePasswordSession(t *testing.T, ctx context.Context, userID, password string) (id, token string, start, change time.Time) {
|
||||
createResp, err := i.Client.SessionV2.CreateSession(ctx, &session.CreateSessionRequest{
|
||||
Checks: &session.Checks{
|
||||
User: &session.CheckUser{
|
||||
Search: &session.CheckUser_UserId{UserId: userID},
|
||||
@@ -684,8 +645,8 @@ func (s *Tester) CreatePasswordSession(t *testing.T, ctx context.Context, userID
|
||||
createResp.GetDetails().GetChangeDate().AsTime(), createResp.GetDetails().GetChangeDate().AsTime()
|
||||
}
|
||||
|
||||
func (s *Tester) CreateProjectUserGrant(t *testing.T, ctx context.Context, projectID, userID string) string {
|
||||
resp, err := s.Client.Mgmt.AddUserGrant(ctx, &mgmt.AddUserGrantRequest{
|
||||
func (i *Instance) CreateProjectUserGrant(t *testing.T, ctx context.Context, projectID, userID string) string {
|
||||
resp, err := i.Client.Mgmt.AddUserGrant(ctx, &mgmt.AddUserGrantRequest{
|
||||
UserId: userID,
|
||||
ProjectId: projectID,
|
||||
})
|
||||
@@ -693,16 +654,16 @@ func (s *Tester) CreateProjectUserGrant(t *testing.T, ctx context.Context, proje
|
||||
return resp.GetUserGrantId()
|
||||
}
|
||||
|
||||
func (s *Tester) CreateOrgMembership(t *testing.T, ctx context.Context, userID string) {
|
||||
_, err := s.Client.Mgmt.AddOrgMember(ctx, &mgmt.AddOrgMemberRequest{
|
||||
func (i *Instance) CreateOrgMembership(t *testing.T, ctx context.Context, userID string) {
|
||||
_, err := i.Client.Mgmt.AddOrgMember(ctx, &mgmt.AddOrgMemberRequest{
|
||||
UserId: userID,
|
||||
Roles: []string{domain.RoleOrgOwner},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func (s *Tester) CreateProjectMembership(t *testing.T, ctx context.Context, projectID, userID string) {
|
||||
_, err := s.Client.Mgmt.AddProjectMember(ctx, &mgmt.AddProjectMemberRequest{
|
||||
func (i *Instance) CreateProjectMembership(t *testing.T, ctx context.Context, projectID, userID string) {
|
||||
_, err := i.Client.Mgmt.AddProjectMember(ctx, &mgmt.AddProjectMemberRequest{
|
||||
ProjectId: projectID,
|
||||
UserId: userID,
|
||||
Roles: []string{domain.RoleProjectOwner},
|
||||
@@ -710,13 +671,12 @@ func (s *Tester) CreateProjectMembership(t *testing.T, ctx context.Context, proj
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func (s *Tester) CreateTarget(ctx context.Context, t *testing.T, name, endpoint string, ty domain.TargetType, interrupt bool) *action.CreateTargetResponse {
|
||||
nameSet := fmt.Sprint(time.Now().UnixNano() + 1)
|
||||
if name != "" {
|
||||
nameSet = name
|
||||
func (i *Instance) CreateTarget(ctx context.Context, t *testing.T, name, endpoint string, ty domain.TargetType, interrupt bool) *action.CreateTargetResponse {
|
||||
if name == "" {
|
||||
name = gofakeit.Name()
|
||||
}
|
||||
reqTarget := &action.Target{
|
||||
Name: nameSet,
|
||||
Name: name,
|
||||
Endpoint: endpoint,
|
||||
Timeout: durationpb.New(10 * time.Second),
|
||||
}
|
||||
@@ -738,20 +698,20 @@ func (s *Tester) CreateTarget(ctx context.Context, t *testing.T, name, endpoint
|
||||
RestAsync: &action.SetRESTAsync{},
|
||||
}
|
||||
}
|
||||
target, err := s.Client.ActionV3Alpha.CreateTarget(ctx, &action.CreateTargetRequest{Target: reqTarget})
|
||||
target, err := i.Client.ActionV3Alpha.CreateTarget(ctx, &action.CreateTargetRequest{Target: reqTarget})
|
||||
require.NoError(t, err)
|
||||
return target
|
||||
}
|
||||
|
||||
func (s *Tester) DeleteExecution(ctx context.Context, t *testing.T, cond *action.Condition) {
|
||||
_, err := s.Client.ActionV3Alpha.SetExecution(ctx, &action.SetExecutionRequest{
|
||||
func (i *Instance) DeleteExecution(ctx context.Context, t *testing.T, cond *action.Condition) {
|
||||
_, err := i.Client.ActionV3Alpha.SetExecution(ctx, &action.SetExecutionRequest{
|
||||
Condition: cond,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func (s *Tester) SetExecution(ctx context.Context, t *testing.T, cond *action.Condition, targets []*action.ExecutionTargetType) *action.SetExecutionResponse {
|
||||
target, err := s.Client.ActionV3Alpha.SetExecution(ctx, &action.SetExecutionRequest{
|
||||
func (i *Instance) SetExecution(ctx context.Context, t *testing.T, cond *action.Condition, targets []*action.ExecutionTargetType) *action.SetExecutionResponse {
|
||||
target, err := i.Client.ActionV3Alpha.SetExecution(ctx, &action.SetExecutionRequest{
|
||||
Condition: cond,
|
||||
Execution: &action.Execution{
|
||||
Targets: targets,
|
||||
@@ -761,15 +721,15 @@ func (s *Tester) SetExecution(ctx context.Context, t *testing.T, cond *action.Co
|
||||
return target
|
||||
}
|
||||
|
||||
func (s *Tester) CreateUserSchemaEmpty(ctx context.Context) *userschema_v3alpha.CreateUserSchemaResponse {
|
||||
return s.CreateUserSchemaEmptyWithType(ctx, fmt.Sprint(time.Now().UnixNano()+1))
|
||||
func (i *Instance) CreateUserSchemaEmpty(ctx context.Context) *userschema_v3alpha.CreateUserSchemaResponse {
|
||||
return i.CreateUserSchemaEmptyWithType(ctx, fmt.Sprint(time.Now().UnixNano()+1))
|
||||
}
|
||||
|
||||
func (s *Tester) CreateUserSchema(ctx context.Context, schemaData []byte) *userschema_v3alpha.CreateUserSchemaResponse {
|
||||
func (i *Instance) CreateUserSchema(ctx context.Context, schemaData []byte) *userschema_v3alpha.CreateUserSchemaResponse {
|
||||
userSchema := new(structpb.Struct)
|
||||
err := userSchema.UnmarshalJSON(schemaData)
|
||||
logging.OnError(err).Fatal("create userschema unmarshal")
|
||||
schema, err := s.Client.UserSchemaV3.CreateUserSchema(ctx, &userschema_v3alpha.CreateUserSchemaRequest{
|
||||
schema, err := i.Client.UserSchemaV3.CreateUserSchema(ctx, &userschema_v3alpha.CreateUserSchemaRequest{
|
||||
UserSchema: &userschema_v3alpha.UserSchema{
|
||||
Type: fmt.Sprint(time.Now().UnixNano() + 1),
|
||||
DataType: &userschema_v3alpha.UserSchema_Schema{
|
||||
@@ -781,7 +741,7 @@ func (s *Tester) CreateUserSchema(ctx context.Context, schemaData []byte) *users
|
||||
return schema
|
||||
}
|
||||
|
||||
func (s *Tester) CreateUserSchemaEmptyWithType(ctx context.Context, schemaType string) *userschema_v3alpha.CreateUserSchemaResponse {
|
||||
func (i *Instance) CreateUserSchemaEmptyWithType(ctx context.Context, schemaType string) *userschema_v3alpha.CreateUserSchemaResponse {
|
||||
userSchema := new(structpb.Struct)
|
||||
err := userSchema.UnmarshalJSON([]byte(`{
|
||||
"$schema": "urn:zitadel:schema:v1",
|
||||
@@ -789,7 +749,7 @@ func (s *Tester) CreateUserSchemaEmptyWithType(ctx context.Context, schemaType s
|
||||
"properties": {}
|
||||
}`))
|
||||
logging.OnError(err).Fatal("create userschema unmarshal")
|
||||
schema, err := s.Client.UserSchemaV3.CreateUserSchema(ctx, &userschema_v3alpha.CreateUserSchemaRequest{
|
||||
schema, err := i.Client.UserSchemaV3.CreateUserSchema(ctx, &userschema_v3alpha.CreateUserSchemaRequest{
|
||||
UserSchema: &userschema_v3alpha.UserSchema{
|
||||
Type: schemaType,
|
||||
DataType: &userschema_v3alpha.UserSchema_Schema{
|
||||
@@ -801,11 +761,11 @@ func (s *Tester) CreateUserSchemaEmptyWithType(ctx context.Context, schemaType s
|
||||
return schema
|
||||
}
|
||||
|
||||
func (s *Tester) CreateSchemaUser(ctx context.Context, orgID string, schemaID string, data []byte) *user_v3alpha.CreateUserResponse {
|
||||
func (i *Instance) CreateSchemaUser(ctx context.Context, orgID string, schemaID string, data []byte) *user_v3alpha.CreateUserResponse {
|
||||
userData := new(structpb.Struct)
|
||||
err := userData.UnmarshalJSON(data)
|
||||
logging.OnError(err).Fatal("create user unmarshal")
|
||||
user, err := s.Client.UserV3Alpha.CreateUser(ctx, &user_v3alpha.CreateUserRequest{
|
||||
user, err := i.Client.UserV3Alpha.CreateUser(ctx, &user_v3alpha.CreateUserRequest{
|
||||
Organization: &object_v3alpha.Organization{Property: &object_v3alpha.Organization_OrgId{OrgId: orgID}},
|
||||
User: &user_v3alpha.CreateUser{
|
||||
SchemaId: schemaID,
|
||||
|
53
internal/integration/config.go
Normal file
53
internal/integration/config.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
_ "embed"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/zitadel/logging"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Log *logging.Config
|
||||
Hostname string
|
||||
Port uint16
|
||||
Secure bool
|
||||
LoginURLV2 string
|
||||
LogoutURLV2 string
|
||||
WebAuthNName string
|
||||
}
|
||||
|
||||
var (
|
||||
//go:embed config/client.yaml
|
||||
clientYAML []byte
|
||||
)
|
||||
|
||||
var (
|
||||
tmpDir string
|
||||
loadedConfig Config
|
||||
)
|
||||
|
||||
// TmpDir returns the absolute path to the projects's temp directory.
|
||||
func TmpDir() string {
|
||||
return tmpDir
|
||||
}
|
||||
|
||||
func init() {
|
||||
cmd := exec.Command("git", "rev-parse", "--show-toplevel")
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
tmpDir = filepath.Join(string(bytes.TrimSpace(out)), "tmp")
|
||||
|
||||
if err := yaml.Unmarshal(clientYAML, &loadedConfig); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := loadedConfig.Log.SetLogger(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
SystemToken = systemUserToken()
|
||||
}
|
10
internal/integration/config/client.yaml
Normal file
10
internal/integration/config/client.yaml
Normal file
@@ -0,0 +1,10 @@
|
||||
Log:
|
||||
Level: info
|
||||
Formatter:
|
||||
Format: text
|
||||
Hostname: localhost
|
||||
Port: 8080
|
||||
Secure: false
|
||||
LoginURLV2: "/login?authRequest="
|
||||
LogoutURLV2: "/logout?post_logout_redirect="
|
||||
WebAuthNName: ZITADEL
|
@@ -14,7 +14,7 @@ services:
|
||||
- PGUSER=zitadel
|
||||
- POSTGRES_DB=zitadel
|
||||
- POSTGRES_HOST_AUTH_METHOD=trust
|
||||
command: postgres -c shared_preload_libraries=pg_stat_statements -c pg_stat_statements.track=all
|
||||
command: postgres -c shared_preload_libraries=pg_stat_statements -c pg_stat_statements.track=all -c shared_buffers=1GB -c work_mem=16MB -c effective_io_concurrency=100 -c wal_level=minimal -c archive_mode=off -c max_wal_senders=0
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready"]
|
||||
interval: '10s'
|
||||
|
@@ -1,10 +1,12 @@
|
||||
Database:
|
||||
EventPushConnRatio: 0.2 # 4
|
||||
ProjectionSpoolerConnRatio: 0.3 # 6
|
||||
postgres:
|
||||
Host: localhost
|
||||
Port: 5432
|
||||
Database: zitadel
|
||||
MaxOpenConns: 15
|
||||
MaxIdleConns: 10
|
||||
MaxOpenConns: 20
|
||||
MaxIdleConns: 20
|
||||
User:
|
||||
Username: zitadel
|
||||
SSL:
|
||||
|
13
internal/integration/config/steps.yaml
Normal file
13
internal/integration/config/steps.yaml
Normal file
@@ -0,0 +1,13 @@
|
||||
FirstInstance:
|
||||
Skip: false
|
||||
PatPath: tmp/admin-pat.txt
|
||||
InstanceName: ZITADEL
|
||||
DefaultLanguage: en
|
||||
Org:
|
||||
Name: ZITADEL
|
||||
Machine:
|
||||
Machine:
|
||||
Username: boss
|
||||
Name: boss
|
||||
Pat:
|
||||
ExpirationDate: 2099-01-01T00:00:00Z
|
@@ -1,15 +1,19 @@
|
||||
Log:
|
||||
Level: debug
|
||||
Level: info
|
||||
|
||||
ExternalSecure: false
|
||||
|
||||
TLS:
|
||||
Enabled: false
|
||||
|
||||
Quotas:
|
||||
Access:
|
||||
Enabled: true
|
||||
|
||||
Telemetry:
|
||||
Enabled: true
|
||||
Endpoints:
|
||||
- http://localhost:8081
|
||||
- http://localhost:8081/milestone
|
||||
Headers:
|
||||
single-value: "single-value"
|
||||
multi-value:
|
||||
@@ -27,14 +31,15 @@ LogStore:
|
||||
Enabled: true
|
||||
|
||||
Projections:
|
||||
HandleActiveInstances: 60s
|
||||
HandleActiveInstances: 30m
|
||||
RequeueEvery: 5s
|
||||
TransactionDuration: 1m
|
||||
Customizations:
|
||||
NotificationsQuotas:
|
||||
RequeueEvery: 1s
|
||||
telemetry:
|
||||
HandleActiveInstances: 60s
|
||||
Telemetry:
|
||||
RequeueEvery: 5s
|
||||
HandleActiveInstances: 60s
|
||||
RequeueEvery: 1s
|
||||
|
||||
DefaultInstance:
|
||||
LoginPolicy:
|
||||
|
354
internal/integration/instance.go
Normal file
354
internal/integration/instance.go
Normal file
@@ -0,0 +1,354 @@
|
||||
// Package integration provides helpers for integration testing.
|
||||
package integration
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
_ "embed"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/brianvoe/gofakeit/v6"
|
||||
"github.com/zitadel/logging"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
http_util "github.com/zitadel/zitadel/internal/api/http"
|
||||
"github.com/zitadel/zitadel/internal/webauthn"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/admin"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/auth"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/instance"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/management"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/org"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/system"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/user"
|
||||
user_v2 "github.com/zitadel/zitadel/pkg/grpc/user/v2"
|
||||
)
|
||||
|
||||
// NotEmpty can be used as placeholder, when the returned values is unknown.
|
||||
// It can be used in tests to assert whether a value should be empty or not.
|
||||
const NotEmpty = "not empty"
|
||||
|
||||
const (
|
||||
adminPATFile = "admin-pat.txt"
|
||||
)
|
||||
|
||||
// UserType provides constants that give
|
||||
// a short explanation with the purpose
|
||||
// a service user.
|
||||
// This allows to pre-create users with
|
||||
// different permissions and reuse them.
|
||||
type UserType int
|
||||
|
||||
//go:generate enumer -type UserType -transform snake -trimprefix UserType
|
||||
const (
|
||||
UserTypeUnspecified UserType = iota
|
||||
UserTypeIAMOwner
|
||||
UserTypeOrgOwner
|
||||
UserTypeLogin
|
||||
)
|
||||
|
||||
const (
|
||||
UserPassword = "VeryS3cret!"
|
||||
)
|
||||
|
||||
const (
|
||||
PortMilestoneServer = "8081"
|
||||
PortQuotaServer = "8082"
|
||||
)
|
||||
|
||||
// User information with a Personal Access Token.
|
||||
type User struct {
|
||||
ID string
|
||||
Username string
|
||||
Token string
|
||||
}
|
||||
|
||||
type UserMap map[UserType]*User
|
||||
|
||||
func (m UserMap) Set(typ UserType, user *User) {
|
||||
m[typ] = user
|
||||
}
|
||||
|
||||
func (m UserMap) Get(typ UserType) *User {
|
||||
return m[typ]
|
||||
}
|
||||
|
||||
// Host returns the primary host of zitadel, on which the first instance is served.
|
||||
// http://localhost:8080 by default
|
||||
func (c *Config) Host() string {
|
||||
return fmt.Sprintf("%s:%d", c.Hostname, c.Port)
|
||||
}
|
||||
|
||||
// Instance is a Zitadel server and client with all resources available for testing.
|
||||
type Instance struct {
|
||||
Config Config
|
||||
Domain string
|
||||
Instance *instance.InstanceDetail
|
||||
DefaultOrg *org.Org
|
||||
Users UserMap
|
||||
AdminUserID string // First human user for password login
|
||||
|
||||
Client *Client
|
||||
WebAuthN *webauthn.Client
|
||||
}
|
||||
|
||||
// GetFirstInstance returns the default instance and org information,
|
||||
// with authorized machine users.
|
||||
// Using the first instance is not recommended as parallel test might
|
||||
// interfere with each other.
|
||||
// It is recommended to use [NewInstance] instead.
|
||||
func GetFirstInstance(ctx context.Context) *Instance {
|
||||
i := &Instance{
|
||||
Config: loadedConfig,
|
||||
Domain: loadedConfig.Hostname,
|
||||
}
|
||||
token := loadInstanceOwnerPAT()
|
||||
i.setClient(ctx)
|
||||
i.setupInstance(ctx, token)
|
||||
return i
|
||||
}
|
||||
|
||||
// NewInstance returns a new instance that can be used for integration tests.
|
||||
// The instance contains a gRPC client connected to the domain of this instance.
|
||||
// The included users are the IAM_OWNER, ORG_OWNER of the default org and
|
||||
// a Login client user.
|
||||
//
|
||||
// The instance is isolated and is safe for parallel testing.
|
||||
func NewInstance(ctx context.Context) *Instance {
|
||||
primaryDomain := RandString(5) + ".integration.localhost"
|
||||
|
||||
ctx = WithSystemAuthorization(ctx)
|
||||
resp, err := SystemClient().CreateInstance(ctx, &system.CreateInstanceRequest{
|
||||
InstanceName: "testinstance",
|
||||
CustomDomain: primaryDomain,
|
||||
Owner: &system.CreateInstanceRequest_Machine_{
|
||||
Machine: &system.CreateInstanceRequest_Machine{
|
||||
UserName: "owner",
|
||||
Name: "owner",
|
||||
PersonalAccessToken: &system.CreateInstanceRequest_PersonalAccessToken{},
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
i := &Instance{
|
||||
Config: loadedConfig,
|
||||
Domain: primaryDomain,
|
||||
}
|
||||
i.setClient(ctx)
|
||||
i.awaitFirstUser(WithAuthorizationToken(ctx, resp.GetPat()))
|
||||
i.setupInstance(ctx, resp.GetPat())
|
||||
return i
|
||||
}
|
||||
|
||||
func (i *Instance) ID() string {
|
||||
return i.Instance.GetId()
|
||||
}
|
||||
|
||||
func (i *Instance) awaitFirstUser(ctx context.Context) {
|
||||
var allErrs []error
|
||||
for {
|
||||
resp, err := i.Client.UserV2.AddHumanUser(ctx, &user_v2.AddHumanUserRequest{
|
||||
Username: proto.String("zitadel-admin@zitadel.localhost"),
|
||||
Profile: &user_v2.SetHumanProfile{
|
||||
GivenName: "hodor",
|
||||
FamilyName: "hodor",
|
||||
NickName: proto.String("hodor"),
|
||||
},
|
||||
Email: &user_v2.SetHumanEmail{
|
||||
Email: "zitadel-admin@zitadel.localhost",
|
||||
Verification: &user_v2.SetHumanEmail_IsVerified{
|
||||
IsVerified: true,
|
||||
},
|
||||
},
|
||||
PasswordType: &user_v2.AddHumanUserRequest_Password{
|
||||
Password: &user_v2.Password{
|
||||
Password: "Password1!",
|
||||
ChangeRequired: false,
|
||||
},
|
||||
},
|
||||
})
|
||||
if err == nil {
|
||||
i.AdminUserID = resp.GetUserId()
|
||||
return
|
||||
}
|
||||
logging.WithError(err).Debug("await first instance user")
|
||||
allErrs = append(allErrs, err)
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
panic(errors.Join(append(allErrs, ctx.Err())...))
|
||||
case <-time.After(time.Second):
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (i *Instance) setupInstance(ctx context.Context, token string) {
|
||||
i.Users = make(UserMap)
|
||||
ctx = WithAuthorizationToken(ctx, token)
|
||||
i.setInstance(ctx)
|
||||
i.setOrganization(ctx)
|
||||
i.createMachineUserInstanceOwner(ctx, token)
|
||||
i.createMachineUserOrgOwner(ctx)
|
||||
i.createLoginClient(ctx)
|
||||
i.createWebAuthNClient()
|
||||
}
|
||||
|
||||
// Host returns the primary Domain of the instance with the port.
|
||||
func (i *Instance) Host() string {
|
||||
return fmt.Sprintf("%s:%d", i.Domain, i.Config.Port)
|
||||
}
|
||||
|
||||
func loadInstanceOwnerPAT() string {
|
||||
data, err := os.ReadFile(filepath.Join(tmpDir, adminPATFile))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return string(bytes.TrimSpace(data))
|
||||
}
|
||||
|
||||
func (i *Instance) createMachineUserInstanceOwner(ctx context.Context, token string) {
|
||||
mustAwait(func() error {
|
||||
user, err := i.Client.Auth.GetMyUser(WithAuthorizationToken(ctx, token), &auth.GetMyUserRequest{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
i.Users.Set(UserTypeIAMOwner, &User{
|
||||
ID: user.GetUser().GetId(),
|
||||
Username: user.GetUser().GetUserName(),
|
||||
Token: token,
|
||||
})
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (i *Instance) createMachineUserOrgOwner(ctx context.Context) {
|
||||
_, err := i.Client.Mgmt.AddOrgMember(ctx, &management.AddOrgMemberRequest{
|
||||
UserId: i.createMachineUser(ctx, UserTypeOrgOwner),
|
||||
Roles: []string{"ORG_OWNER"},
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (i *Instance) createLoginClient(ctx context.Context) {
|
||||
i.createMachineUser(ctx, UserTypeLogin)
|
||||
}
|
||||
|
||||
func (i *Instance) setClient(ctx context.Context) {
|
||||
client, err := newClient(ctx, i.Host())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
i.Client = client
|
||||
}
|
||||
|
||||
func (i *Instance) setInstance(ctx context.Context) {
|
||||
mustAwait(func() error {
|
||||
instance, err := i.Client.Admin.GetMyInstance(ctx, &admin.GetMyInstanceRequest{})
|
||||
i.Instance = instance.GetInstance()
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func (i *Instance) setOrganization(ctx context.Context) {
|
||||
mustAwait(func() error {
|
||||
resp, err := i.Client.Mgmt.GetMyOrg(ctx, &management.GetMyOrgRequest{})
|
||||
i.DefaultOrg = resp.GetOrg()
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func (i *Instance) createMachineUser(ctx context.Context, userType UserType) (userID string) {
|
||||
mustAwait(func() error {
|
||||
username := gofakeit.Username()
|
||||
userResp, err := i.Client.Mgmt.AddMachineUser(ctx, &management.AddMachineUserRequest{
|
||||
UserName: username,
|
||||
Name: username,
|
||||
Description: userType.String(),
|
||||
AccessTokenType: user.AccessTokenType_ACCESS_TOKEN_TYPE_JWT,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
userID = userResp.GetUserId()
|
||||
patResp, err := i.Client.Mgmt.AddPersonalAccessToken(ctx, &management.AddPersonalAccessTokenRequest{
|
||||
UserId: userID,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
i.Users.Set(userType, &User{
|
||||
ID: userID,
|
||||
Username: username,
|
||||
Token: patResp.GetToken(),
|
||||
})
|
||||
return nil
|
||||
})
|
||||
return userID
|
||||
}
|
||||
|
||||
func (i *Instance) createWebAuthNClient() {
|
||||
i.WebAuthN = webauthn.NewClient(i.Config.WebAuthNName, i.Domain, http_util.BuildOrigin(i.Host(), i.Config.Secure))
|
||||
}
|
||||
|
||||
func (i *Instance) WithAuthorization(ctx context.Context, u UserType) context.Context {
|
||||
return i.WithInstanceAuthorization(ctx, u)
|
||||
}
|
||||
|
||||
func (i *Instance) WithInstanceAuthorization(ctx context.Context, u UserType) context.Context {
|
||||
return WithAuthorizationToken(ctx, i.Users.Get(u).Token)
|
||||
}
|
||||
|
||||
func (i *Instance) GetUserID(u UserType) string {
|
||||
return i.Users.Get(u).ID
|
||||
}
|
||||
|
||||
func WithAuthorizationToken(ctx context.Context, token string) context.Context {
|
||||
md, ok := metadata.FromOutgoingContext(ctx)
|
||||
if !ok {
|
||||
md = make(metadata.MD)
|
||||
}
|
||||
md.Set("Authorization", fmt.Sprintf("Bearer %s", token))
|
||||
return metadata.NewOutgoingContext(ctx, md)
|
||||
}
|
||||
|
||||
func (i *Instance) BearerToken(ctx context.Context) string {
|
||||
md, ok := metadata.FromOutgoingContext(ctx)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return md.Get("Authorization")[0]
|
||||
}
|
||||
|
||||
func (i *Instance) WithSystemAuthorizationHTTP(u UserType) map[string]string {
|
||||
return map[string]string{"Authorization": fmt.Sprintf("Bearer %s", i.Users.Get(u).Token)}
|
||||
}
|
||||
|
||||
func await(af func() error) error {
|
||||
maxTimer := time.NewTimer(15 * time.Minute)
|
||||
for {
|
||||
err := af()
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
select {
|
||||
case <-maxTimer.C:
|
||||
return err
|
||||
case <-time.After(time.Second):
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func mustAwait(af func() error) {
|
||||
if err := await(af); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
@@ -1,449 +0,0 @@
|
||||
// Package integration provides helpers for integration testing.
|
||||
package integration
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"database/sql"
|
||||
_ "embed"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
"github.com/zitadel/logging"
|
||||
"github.com/zitadel/oidc/v3/pkg/client"
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
"google.golang.org/grpc/metadata"
|
||||
|
||||
"github.com/zitadel/zitadel/cmd"
|
||||
"github.com/zitadel/zitadel/cmd/start"
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
http_util "github.com/zitadel/zitadel/internal/api/http"
|
||||
z_oidc "github.com/zitadel/zitadel/internal/api/oidc"
|
||||
"github.com/zitadel/zitadel/internal/command"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
|
||||
"github.com/zitadel/zitadel/internal/net"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
"github.com/zitadel/zitadel/internal/webauthn"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/admin"
|
||||
)
|
||||
|
||||
var (
|
||||
//go:embed config/zitadel.yaml
|
||||
zitadelYAML []byte
|
||||
//go:embed config/cockroach.yaml
|
||||
cockroachYAML []byte
|
||||
//go:embed config/postgres.yaml
|
||||
postgresYAML []byte
|
||||
//go:embed config/system-user-key.pem
|
||||
systemUserKey []byte
|
||||
)
|
||||
|
||||
// NotEmpty can be used as placeholder, when the returned values is unknown.
|
||||
// It can be used in tests to assert whether a value should be empty or not.
|
||||
const NotEmpty = "not empty"
|
||||
|
||||
// UserType provides constants that give
|
||||
// a short explinanation with the purpose
|
||||
// a serverice user.
|
||||
// This allows to pre-create users with
|
||||
// different permissions and reuse them.
|
||||
type UserType int
|
||||
|
||||
//go:generate stringer -type=UserType
|
||||
const (
|
||||
Unspecified UserType = iota
|
||||
OrgOwner
|
||||
Login
|
||||
IAMOwner
|
||||
SystemUser // SystemUser is a user with access to the system service.
|
||||
)
|
||||
|
||||
const (
|
||||
FirstInstanceUsersKey = "first"
|
||||
UserPassword = "VeryS3cret!"
|
||||
)
|
||||
|
||||
const (
|
||||
PortMilestoneServer = "8081"
|
||||
PortQuotaServer = "8082"
|
||||
)
|
||||
|
||||
// User information with a Personal Access Token.
|
||||
type User struct {
|
||||
*query.User
|
||||
Token string
|
||||
}
|
||||
|
||||
type InstanceUserMap map[string]map[UserType]*User
|
||||
|
||||
func (m InstanceUserMap) Set(instanceID string, typ UserType, user *User) {
|
||||
if m[instanceID] == nil {
|
||||
m[instanceID] = make(map[UserType]*User)
|
||||
}
|
||||
m[instanceID][typ] = user
|
||||
}
|
||||
|
||||
func (m InstanceUserMap) Get(instanceID string, typ UserType) *User {
|
||||
if users, ok := m[instanceID]; ok {
|
||||
return users[typ]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Tester is a Zitadel server and client with all resources available for testing.
|
||||
type Tester struct {
|
||||
*start.Server
|
||||
|
||||
Instance authz.Instance
|
||||
Organisation *query.Org
|
||||
Users InstanceUserMap
|
||||
|
||||
MilestoneChan chan []byte
|
||||
milestoneServer *httptest.Server
|
||||
QuotaNotificationChan chan []byte
|
||||
quotaNotificationServer *httptest.Server
|
||||
|
||||
Client Client
|
||||
WebAuthN *webauthn.Client
|
||||
wg sync.WaitGroup // used for shutdown
|
||||
}
|
||||
|
||||
const commandLine = `start --masterkeyFromEnv`
|
||||
|
||||
func (s *Tester) Host() string {
|
||||
return fmt.Sprintf("%s:%d", s.Config.ExternalDomain, s.Config.Port)
|
||||
}
|
||||
|
||||
func (s *Tester) createClientConn(ctx context.Context, target string) {
|
||||
cc, err := grpc.DialContext(ctx, target,
|
||||
grpc.WithBlock(),
|
||||
grpc.WithTransportCredentials(insecure.NewCredentials()),
|
||||
)
|
||||
if err != nil {
|
||||
s.Shutdown <- os.Interrupt
|
||||
s.wg.Wait()
|
||||
}
|
||||
logging.OnError(err).Fatal("integration tester client dial")
|
||||
logging.New().WithField("target", target).Info("finished dialing grpc client conn")
|
||||
|
||||
s.Client = newClient(cc)
|
||||
err = s.pollHealth(ctx)
|
||||
logging.OnError(err).Fatal("integration tester health")
|
||||
}
|
||||
|
||||
// pollHealth waits until a healthy status is reported.
|
||||
// TODO: remove when we make the setup blocking on all
|
||||
// projections completed.
|
||||
func (s *Tester) pollHealth(ctx context.Context) (err error) {
|
||||
for {
|
||||
err = func(ctx context.Context) error {
|
||||
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
_, err := s.Client.Admin.Healthz(ctx, &admin.HealthzRequest{})
|
||||
return err
|
||||
}(ctx)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
logging.WithError(err).Info("poll healthz")
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-time.After(time.Second):
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
LoginUser = "loginClient"
|
||||
MachineUserOrgOwner = "integrationOrgOwner"
|
||||
MachineUserInstanceOwner = "integrationInstanceOwner"
|
||||
)
|
||||
|
||||
func (s *Tester) createMachineUserOrgOwner(ctx context.Context) {
|
||||
var err error
|
||||
|
||||
ctx, user := s.createMachineUser(ctx, MachineUserOrgOwner, OrgOwner)
|
||||
_, err = s.Commands.AddOrgMember(ctx, user.ResourceOwner, user.ID, "ORG_OWNER")
|
||||
target := new(zerrors.AlreadyExistsError)
|
||||
if !errors.As(err, &target) {
|
||||
logging.OnError(err).Fatal("add org member")
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Tester) createMachineUserInstanceOwner(ctx context.Context) {
|
||||
var err error
|
||||
|
||||
ctx, user := s.createMachineUser(ctx, MachineUserInstanceOwner, IAMOwner)
|
||||
_, err = s.Commands.AddInstanceMember(ctx, user.ID, "IAM_OWNER")
|
||||
target := new(zerrors.AlreadyExistsError)
|
||||
if !errors.As(err, &target) {
|
||||
logging.OnError(err).Fatal("add instance member")
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Tester) createLoginClient(ctx context.Context) {
|
||||
s.createMachineUser(ctx, LoginUser, Login)
|
||||
}
|
||||
|
||||
func (s *Tester) createMachineUser(ctx context.Context, username string, userType UserType) (context.Context, *query.User) {
|
||||
var err error
|
||||
ctx = s.updateInstanceAndOrg(ctx, s.Host())
|
||||
usernameQuery, err := query.NewUserUsernameSearchQuery(username, query.TextEquals)
|
||||
logging.OnError(err).Fatal("user query")
|
||||
user, err := s.Queries.GetUser(ctx, true, usernameQuery)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
_, err = s.Commands.AddMachine(ctx, &command.Machine{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
ResourceOwner: s.Organisation.ID,
|
||||
},
|
||||
Username: username,
|
||||
Name: username,
|
||||
Description: "who cares?",
|
||||
AccessTokenType: domain.OIDCTokenTypeJWT,
|
||||
})
|
||||
logging.WithFields("username", username).OnError(err).Fatal("add machine user")
|
||||
user, err = s.Queries.GetUser(ctx, true, usernameQuery)
|
||||
}
|
||||
logging.WithFields("username", username).OnError(err).Fatal("get user")
|
||||
|
||||
scopes := []string{oidc.ScopeOpenID, oidc.ScopeProfile, z_oidc.ScopeUserMetaData, z_oidc.ScopeResourceOwner}
|
||||
pat := command.NewPersonalAccessToken(user.ResourceOwner, user.ID, time.Now().Add(time.Hour), scopes, domain.UserTypeMachine)
|
||||
_, err = s.Commands.AddPersonalAccessToken(ctx, pat)
|
||||
logging.WithFields("username", SystemUser).OnError(err).Fatal("add pat")
|
||||
s.Users.Set(FirstInstanceUsersKey, userType, &User{
|
||||
User: user,
|
||||
Token: pat.Token,
|
||||
})
|
||||
return ctx, user
|
||||
}
|
||||
|
||||
func (s *Tester) WithAuthorization(ctx context.Context, u UserType) context.Context {
|
||||
return s.WithInstanceAuthorization(ctx, u, FirstInstanceUsersKey)
|
||||
}
|
||||
|
||||
func (s *Tester) WithInstanceAuthorization(ctx context.Context, u UserType, instanceID string) context.Context {
|
||||
if u == SystemUser {
|
||||
s.ensureSystemUser()
|
||||
}
|
||||
return s.WithAuthorizationToken(ctx, s.Users.Get(instanceID, u).Token)
|
||||
}
|
||||
|
||||
func (s *Tester) GetUserID(u UserType) string {
|
||||
if u == SystemUser {
|
||||
s.ensureSystemUser()
|
||||
}
|
||||
return s.Users.Get(FirstInstanceUsersKey, u).ID
|
||||
}
|
||||
|
||||
func (s *Tester) WithAuthorizationToken(ctx context.Context, token string) context.Context {
|
||||
md, ok := metadata.FromOutgoingContext(ctx)
|
||||
if !ok {
|
||||
md = make(metadata.MD)
|
||||
}
|
||||
md.Set("Authorization", fmt.Sprintf("Bearer %s", token))
|
||||
return metadata.NewOutgoingContext(ctx, md)
|
||||
}
|
||||
|
||||
func (s *Tester) BearerToken(ctx context.Context) string {
|
||||
md, ok := metadata.FromOutgoingContext(ctx)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return md.Get("Authorization")[0]
|
||||
}
|
||||
|
||||
func (s *Tester) ensureSystemUser() {
|
||||
const ISSUER = "tester"
|
||||
if s.Users.Get(FirstInstanceUsersKey, SystemUser) != nil {
|
||||
return
|
||||
}
|
||||
audience := http_util.BuildOrigin(s.Host(), s.Server.Config.ExternalSecure)
|
||||
signer, err := client.NewSignerFromPrivateKeyByte(systemUserKey, "")
|
||||
logging.OnError(err).Fatal("system key signer")
|
||||
jwt, err := client.SignedJWTProfileAssertion(ISSUER, []string{audience}, time.Hour, signer)
|
||||
logging.OnError(err).Fatal("system key jwt")
|
||||
s.Users.Set(FirstInstanceUsersKey, SystemUser, &User{Token: jwt})
|
||||
}
|
||||
|
||||
func (s *Tester) WithSystemAuthorizationHTTP(u UserType) map[string]string {
|
||||
return map[string]string{"Authorization": fmt.Sprintf("Bearer %s", s.Users.Get(FirstInstanceUsersKey, u).Token)}
|
||||
}
|
||||
|
||||
// Done send an interrupt signal to cleanly shutdown the server.
|
||||
func (s *Tester) Done() {
|
||||
err := s.Client.CC.Close()
|
||||
logging.OnError(err).Error("integration tester client close")
|
||||
|
||||
s.Shutdown <- os.Interrupt
|
||||
s.wg.Wait()
|
||||
s.milestoneServer.Close()
|
||||
s.quotaNotificationServer.Close()
|
||||
}
|
||||
|
||||
// NewTester start a new Zitadel server by passing the default commandline.
|
||||
// The server will listen on the configured port.
|
||||
// The database configuration that will be used can be set by the
|
||||
// INTEGRATION_DB_FLAVOR environment variable and can have the values "cockroach"
|
||||
// or "postgres". Defaults to "cockroach".
|
||||
//
|
||||
// The default Instance and Organisation are read from the DB and system
|
||||
// users are created as needed.
|
||||
//
|
||||
// After the server is started, a [grpc.ClientConn] will be created and
|
||||
// the server is polled for it's health status.
|
||||
//
|
||||
// Note: the database must already be setup and initialized before
|
||||
// using NewTester. See the CONTRIBUTING.md document for details.
|
||||
|
||||
func NewTester(ctx context.Context, zitadelConfigYAML ...string) *Tester {
|
||||
args := strings.Split(commandLine, " ")
|
||||
|
||||
sc := make(chan *start.Server)
|
||||
//nolint:contextcheck
|
||||
cmd := cmd.New(os.Stdout, os.Stdin, args, sc)
|
||||
cmd.SetArgs(args)
|
||||
for _, yaml := range append([]string{string(zitadelYAML)}, zitadelConfigYAML...) {
|
||||
err := viper.MergeConfig(bytes.NewBuffer([]byte(yaml)))
|
||||
logging.OnError(err).Fatal()
|
||||
}
|
||||
var err error
|
||||
flavor := os.Getenv("INTEGRATION_DB_FLAVOR")
|
||||
switch flavor {
|
||||
case "cockroach", "":
|
||||
err = viper.MergeConfig(bytes.NewBuffer(cockroachYAML))
|
||||
case "postgres":
|
||||
err = viper.MergeConfig(bytes.NewBuffer(postgresYAML))
|
||||
default:
|
||||
logging.New().WithField("flavor", flavor).Fatal("unknown db flavor set in INTEGRATION_DB_FLAVOR")
|
||||
}
|
||||
logging.OnError(err).Fatal()
|
||||
|
||||
tester := Tester{
|
||||
Users: make(InstanceUserMap),
|
||||
}
|
||||
tester.MilestoneChan = make(chan []byte, 100)
|
||||
tester.milestoneServer, err = runMilestoneServer(ctx, tester.MilestoneChan)
|
||||
logging.OnError(err).Fatal()
|
||||
tester.QuotaNotificationChan = make(chan []byte, 100)
|
||||
tester.quotaNotificationServer, err = runQuotaServer(ctx, tester.QuotaNotificationChan)
|
||||
logging.OnError(err).Fatal()
|
||||
|
||||
tester.wg.Add(1)
|
||||
go func(wg *sync.WaitGroup) {
|
||||
logging.OnError(cmd.Execute()).Fatal()
|
||||
wg.Done()
|
||||
}(&tester.wg)
|
||||
|
||||
select {
|
||||
case tester.Server = <-sc:
|
||||
case <-ctx.Done():
|
||||
logging.OnError(ctx.Err()).Fatal("waiting for integration tester server")
|
||||
}
|
||||
host := tester.Host()
|
||||
tester.createClientConn(ctx, host)
|
||||
tester.createLoginClient(ctx)
|
||||
tester.WebAuthN = webauthn.NewClient(tester.Config.WebAuthNName, tester.Config.ExternalDomain, http_util.BuildOrigin(host, tester.Config.ExternalSecure))
|
||||
tester.createMachineUserOrgOwner(ctx)
|
||||
tester.createMachineUserInstanceOwner(ctx)
|
||||
tester.WebAuthN = webauthn.NewClient(tester.Config.WebAuthNName, tester.Config.ExternalDomain, "https://"+tester.Host())
|
||||
return &tester
|
||||
}
|
||||
|
||||
func Contexts(timeout time.Duration) (ctx, errCtx context.Context, cancel context.CancelFunc) {
|
||||
errCtx, cancel = context.WithCancel(context.Background())
|
||||
cancel()
|
||||
ctx, cancel = context.WithTimeout(context.Background(), timeout)
|
||||
return ctx, errCtx, cancel
|
||||
}
|
||||
|
||||
func runMilestoneServer(ctx context.Context, bodies chan []byte) (*httptest.Server, error) {
|
||||
mockServer := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
body, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if r.Header.Get("single-value") != "single-value" {
|
||||
http.Error(w, "single-value header not set", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if reflect.DeepEqual(r.Header.Get("multi-value"), "multi-value-1,multi-value-2") {
|
||||
http.Error(w, "single-value header not set", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
bodies <- body
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}))
|
||||
config := net.ListenConfig()
|
||||
listener, err := config.Listen(ctx, "tcp", ":"+PortMilestoneServer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mockServer.Listener = listener
|
||||
mockServer.Start()
|
||||
return mockServer, nil
|
||||
}
|
||||
|
||||
func runQuotaServer(ctx context.Context, bodies chan []byte) (*httptest.Server, error) {
|
||||
mockServer := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
body, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
bodies <- body
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}))
|
||||
config := net.ListenConfig()
|
||||
listener, err := config.Listen(ctx, "tcp", ":"+PortQuotaServer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mockServer.Listener = listener
|
||||
mockServer.Start()
|
||||
return mockServer, nil
|
||||
}
|
||||
|
||||
func (s *Tester) updateInstanceAndOrg(ctx context.Context, domain string) context.Context {
|
||||
var err error
|
||||
s.Instance, err = s.Queries.InstanceByHost(ctx, domain, "")
|
||||
logging.OnError(err).Fatal("query instance")
|
||||
ctx = authz.WithInstance(ctx, s.Instance)
|
||||
|
||||
s.Organisation, err = s.Queries.OrgByID(ctx, true, s.Instance.DefaultOrganisationID())
|
||||
logging.OnError(err).Fatal("query organisation")
|
||||
return ctx
|
||||
}
|
||||
|
||||
func await(af func() error) error {
|
||||
maxTimer := time.NewTimer(15 * time.Minute)
|
||||
for {
|
||||
err := af()
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
select {
|
||||
case <-maxTimer.C:
|
||||
return err
|
||||
case <-time.After(time.Second / 10):
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,16 +0,0 @@
|
||||
//go:build integration
|
||||
|
||||
package integration
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestNewTester(t *testing.T) {
|
||||
ctx, _, cancel := Contexts(time.Hour)
|
||||
defer cancel()
|
||||
|
||||
s := NewTester(ctx)
|
||||
defer s.Done()
|
||||
}
|
@@ -24,11 +24,11 @@ import (
|
||||
"github.com/zitadel/zitadel/pkg/grpc/user"
|
||||
)
|
||||
|
||||
func (s *Tester) CreateOIDCClient(ctx context.Context, redirectURI, logoutRedirectURI, projectID string, appType app.OIDCAppType, authMethod app.OIDCAuthMethodType, devMode bool, grantTypes ...app.OIDCGrantType) (*management.AddOIDCAppResponse, error) {
|
||||
func (i *Instance) CreateOIDCClient(ctx context.Context, redirectURI, logoutRedirectURI, projectID string, appType app.OIDCAppType, authMethod app.OIDCAuthMethodType, devMode bool, grantTypes ...app.OIDCGrantType) (*management.AddOIDCAppResponse, error) {
|
||||
if len(grantTypes) == 0 {
|
||||
grantTypes = []app.OIDCGrantType{app.OIDCGrantType_OIDC_GRANT_TYPE_AUTHORIZATION_CODE, app.OIDCGrantType_OIDC_GRANT_TYPE_REFRESH_TOKEN}
|
||||
}
|
||||
resp, err := s.Client.Mgmt.AddOIDCApp(ctx, &management.AddOIDCAppRequest{
|
||||
resp, err := i.Client.Mgmt.AddOIDCApp(ctx, &management.AddOIDCAppRequest{
|
||||
ProjectId: projectID,
|
||||
Name: fmt.Sprintf("app-%d", time.Now().UnixNano()),
|
||||
RedirectUris: []string{redirectURI},
|
||||
@@ -51,7 +51,7 @@ func (s *Tester) CreateOIDCClient(ctx context.Context, redirectURI, logoutRedire
|
||||
return nil, err
|
||||
}
|
||||
return resp, await(func() error {
|
||||
_, err := s.Client.Mgmt.GetAppByID(ctx, &management.GetAppByIDRequest{
|
||||
_, err := i.Client.Mgmt.GetAppByID(ctx, &management.GetAppByIDRequest{
|
||||
ProjectId: projectID,
|
||||
AppId: resp.GetAppId(),
|
||||
})
|
||||
@@ -59,20 +59,20 @@ func (s *Tester) CreateOIDCClient(ctx context.Context, redirectURI, logoutRedire
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Tester) CreateOIDCNativeClient(ctx context.Context, redirectURI, logoutRedirectURI, projectID string, devMode bool) (*management.AddOIDCAppResponse, error) {
|
||||
return s.CreateOIDCClient(ctx, redirectURI, logoutRedirectURI, projectID, app.OIDCAppType_OIDC_APP_TYPE_NATIVE, app.OIDCAuthMethodType_OIDC_AUTH_METHOD_TYPE_NONE, devMode)
|
||||
func (i *Instance) CreateOIDCNativeClient(ctx context.Context, redirectURI, logoutRedirectURI, projectID string, devMode bool) (*management.AddOIDCAppResponse, error) {
|
||||
return i.CreateOIDCClient(ctx, redirectURI, logoutRedirectURI, projectID, app.OIDCAppType_OIDC_APP_TYPE_NATIVE, app.OIDCAuthMethodType_OIDC_AUTH_METHOD_TYPE_NONE, devMode)
|
||||
}
|
||||
|
||||
func (s *Tester) CreateOIDCWebClientBasic(ctx context.Context, redirectURI, logoutRedirectURI, projectID string) (*management.AddOIDCAppResponse, error) {
|
||||
return s.CreateOIDCClient(ctx, redirectURI, logoutRedirectURI, projectID, app.OIDCAppType_OIDC_APP_TYPE_WEB, app.OIDCAuthMethodType_OIDC_AUTH_METHOD_TYPE_BASIC, false)
|
||||
func (i *Instance) CreateOIDCWebClientBasic(ctx context.Context, redirectURI, logoutRedirectURI, projectID string) (*management.AddOIDCAppResponse, error) {
|
||||
return i.CreateOIDCClient(ctx, redirectURI, logoutRedirectURI, projectID, app.OIDCAppType_OIDC_APP_TYPE_WEB, app.OIDCAuthMethodType_OIDC_AUTH_METHOD_TYPE_BASIC, false)
|
||||
}
|
||||
|
||||
func (s *Tester) CreateOIDCWebClientJWT(ctx context.Context, redirectURI, logoutRedirectURI, projectID string, grantTypes ...app.OIDCGrantType) (client *management.AddOIDCAppResponse, keyData []byte, err error) {
|
||||
client, err = s.CreateOIDCClient(ctx, redirectURI, logoutRedirectURI, projectID, app.OIDCAppType_OIDC_APP_TYPE_WEB, app.OIDCAuthMethodType_OIDC_AUTH_METHOD_TYPE_PRIVATE_KEY_JWT, false, grantTypes...)
|
||||
func (i *Instance) CreateOIDCWebClientJWT(ctx context.Context, redirectURI, logoutRedirectURI, projectID string, grantTypes ...app.OIDCGrantType) (client *management.AddOIDCAppResponse, keyData []byte, err error) {
|
||||
client, err = i.CreateOIDCClient(ctx, redirectURI, logoutRedirectURI, projectID, app.OIDCAppType_OIDC_APP_TYPE_WEB, app.OIDCAuthMethodType_OIDC_AUTH_METHOD_TYPE_PRIVATE_KEY_JWT, false, grantTypes...)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
key, err := s.Client.Mgmt.AddAppKey(ctx, &management.AddAppKeyRequest{
|
||||
key, err := i.Client.Mgmt.AddAppKey(ctx, &management.AddAppKeyRequest{
|
||||
ProjectId: projectID,
|
||||
AppId: client.GetAppId(),
|
||||
Type: authn.KeyType_KEY_TYPE_JSON,
|
||||
@@ -81,15 +81,23 @@ func (s *Tester) CreateOIDCWebClientJWT(ctx context.Context, redirectURI, logout
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
mustAwait(func() error {
|
||||
_, err := i.Client.Mgmt.GetAppByID(ctx, &management.GetAppByIDRequest{
|
||||
ProjectId: projectID,
|
||||
AppId: client.GetAppId(),
|
||||
})
|
||||
return err
|
||||
})
|
||||
|
||||
return client, key.GetKeyDetails(), nil
|
||||
}
|
||||
|
||||
func (s *Tester) CreateOIDCInactivateClient(ctx context.Context, redirectURI, logoutRedirectURI, projectID string) (*management.AddOIDCAppResponse, error) {
|
||||
client, err := s.CreateOIDCNativeClient(ctx, redirectURI, logoutRedirectURI, projectID, false)
|
||||
func (i *Instance) CreateOIDCInactivateClient(ctx context.Context, redirectURI, logoutRedirectURI, projectID string) (*management.AddOIDCAppResponse, error) {
|
||||
client, err := i.CreateOIDCNativeClient(ctx, redirectURI, logoutRedirectURI, projectID, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = s.Client.Mgmt.DeactivateApp(ctx, &management.DeactivateAppRequest{
|
||||
_, err = i.Client.Mgmt.DeactivateApp(ctx, &management.DeactivateAppRequest{
|
||||
ProjectId: projectID,
|
||||
AppId: client.GetAppId(),
|
||||
})
|
||||
@@ -99,14 +107,14 @@ func (s *Tester) CreateOIDCInactivateClient(ctx context.Context, redirectURI, lo
|
||||
return client, err
|
||||
}
|
||||
|
||||
func (s *Tester) CreateOIDCImplicitFlowClient(ctx context.Context, redirectURI string) (*management.AddOIDCAppResponse, error) {
|
||||
project, err := s.Client.Mgmt.AddProject(ctx, &management.AddProjectRequest{
|
||||
func (i *Instance) CreateOIDCImplicitFlowClient(ctx context.Context, redirectURI string) (*management.AddOIDCAppResponse, error) {
|
||||
project, err := i.Client.Mgmt.AddProject(ctx, &management.AddProjectRequest{
|
||||
Name: fmt.Sprintf("project-%d", time.Now().UnixNano()),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := s.Client.Mgmt.AddOIDCApp(ctx, &management.AddOIDCAppRequest{
|
||||
resp, err := i.Client.Mgmt.AddOIDCApp(ctx, &management.AddOIDCAppRequest{
|
||||
ProjectId: project.GetId(),
|
||||
Name: fmt.Sprintf("app-%d", time.Now().UnixNano()),
|
||||
RedirectUris: []string{redirectURI},
|
||||
@@ -129,7 +137,7 @@ func (s *Tester) CreateOIDCImplicitFlowClient(ctx context.Context, redirectURI s
|
||||
return nil, err
|
||||
}
|
||||
return resp, await(func() error {
|
||||
_, err := s.Client.Mgmt.GetAppByID(ctx, &management.GetAppByIDRequest{
|
||||
_, err := i.Client.Mgmt.GetAppByID(ctx, &management.GetAppByIDRequest{
|
||||
ProjectId: project.GetId(),
|
||||
AppId: resp.GetAppId(),
|
||||
})
|
||||
@@ -137,32 +145,32 @@ func (s *Tester) CreateOIDCImplicitFlowClient(ctx context.Context, redirectURI s
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Tester) CreateOIDCTokenExchangeClient(ctx context.Context) (client *management.AddOIDCAppResponse, keyData []byte, err error) {
|
||||
project, err := s.Client.Mgmt.AddProject(ctx, &management.AddProjectRequest{
|
||||
func (i *Instance) CreateOIDCTokenExchangeClient(ctx context.Context) (client *management.AddOIDCAppResponse, keyData []byte, err error) {
|
||||
project, err := i.Client.Mgmt.AddProject(ctx, &management.AddProjectRequest{
|
||||
Name: fmt.Sprintf("project-%d", time.Now().UnixNano()),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return s.CreateOIDCWebClientJWT(ctx, "", "", project.GetId(), app.OIDCGrantType_OIDC_GRANT_TYPE_TOKEN_EXCHANGE, app.OIDCGrantType_OIDC_GRANT_TYPE_AUTHORIZATION_CODE, app.OIDCGrantType_OIDC_GRANT_TYPE_REFRESH_TOKEN)
|
||||
return i.CreateOIDCWebClientJWT(ctx, "", "", project.GetId(), app.OIDCGrantType_OIDC_GRANT_TYPE_TOKEN_EXCHANGE, app.OIDCGrantType_OIDC_GRANT_TYPE_AUTHORIZATION_CODE, app.OIDCGrantType_OIDC_GRANT_TYPE_REFRESH_TOKEN)
|
||||
}
|
||||
|
||||
func (s *Tester) CreateProject(ctx context.Context) (*management.AddProjectResponse, error) {
|
||||
return s.Client.Mgmt.AddProject(ctx, &management.AddProjectRequest{
|
||||
func (i *Instance) CreateProject(ctx context.Context) (*management.AddProjectResponse, error) {
|
||||
return i.Client.Mgmt.AddProject(ctx, &management.AddProjectRequest{
|
||||
Name: fmt.Sprintf("project-%d", time.Now().UnixNano()),
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Tester) CreateAPIClientJWT(ctx context.Context, projectID string) (*management.AddAPIAppResponse, error) {
|
||||
return s.Client.Mgmt.AddAPIApp(ctx, &management.AddAPIAppRequest{
|
||||
func (i *Instance) CreateAPIClientJWT(ctx context.Context, projectID string) (*management.AddAPIAppResponse, error) {
|
||||
return i.Client.Mgmt.AddAPIApp(ctx, &management.AddAPIAppRequest{
|
||||
ProjectId: projectID,
|
||||
Name: fmt.Sprintf("api-%d", time.Now().UnixNano()),
|
||||
AuthMethodType: app.APIAuthMethodType_API_AUTH_METHOD_TYPE_PRIVATE_KEY_JWT,
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Tester) CreateAPIClientBasic(ctx context.Context, projectID string) (*management.AddAPIAppResponse, error) {
|
||||
return s.Client.Mgmt.AddAPIApp(ctx, &management.AddAPIAppRequest{
|
||||
func (i *Instance) CreateAPIClientBasic(ctx context.Context, projectID string) (*management.AddAPIAppResponse, error) {
|
||||
return i.Client.Mgmt.AddAPIApp(ctx, &management.AddAPIAppRequest{
|
||||
ProjectId: projectID,
|
||||
Name: fmt.Sprintf("api-%d", time.Now().UnixNano()),
|
||||
AuthMethodType: app.APIAuthMethodType_API_AUTH_METHOD_TYPE_BASIC,
|
||||
@@ -171,36 +179,36 @@ func (s *Tester) CreateAPIClientBasic(ctx context.Context, projectID string) (*m
|
||||
|
||||
const CodeVerifier = "codeVerifier"
|
||||
|
||||
func (s *Tester) CreateOIDCAuthRequest(ctx context.Context, clientID, loginClient, redirectURI string, scope ...string) (authRequestID string, err error) {
|
||||
return s.CreateOIDCAuthRequestWithDomain(ctx, s.Config.ExternalDomain, clientID, loginClient, redirectURI, scope...)
|
||||
func (i *Instance) CreateOIDCAuthRequest(ctx context.Context, clientID, loginClient, redirectURI string, scope ...string) (authRequestID string, err error) {
|
||||
return i.CreateOIDCAuthRequestWithDomain(ctx, i.Domain, clientID, loginClient, redirectURI, scope...)
|
||||
}
|
||||
func (s *Tester) CreateOIDCAuthRequestWithDomain(ctx context.Context, domain, clientID, loginClient, redirectURI string, scope ...string) (authRequestID string, err error) {
|
||||
provider, err := s.CreateRelyingPartyForDomain(ctx, domain, clientID, redirectURI, scope...)
|
||||
func (i *Instance) CreateOIDCAuthRequestWithDomain(ctx context.Context, domain, clientID, loginClient, redirectURI string, scope ...string) (authRequestID string, err error) {
|
||||
provider, err := i.CreateRelyingPartyForDomain(ctx, domain, clientID, redirectURI, scope...)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return "", fmt.Errorf("create relying party: %w", err)
|
||||
}
|
||||
codeChallenge := oidc.NewSHACodeChallenge(CodeVerifier)
|
||||
authURL := rp.AuthURL("state", provider, rp.WithCodeChallenge(codeChallenge))
|
||||
|
||||
req, err := GetRequest(authURL, map[string]string{oidc_internal.LoginClientHeader: loginClient})
|
||||
if err != nil {
|
||||
return "", err
|
||||
return "", fmt.Errorf("get request: %w", err)
|
||||
}
|
||||
|
||||
loc, err := CheckRedirect(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return "", fmt.Errorf("check redirect: %w", err)
|
||||
}
|
||||
|
||||
prefixWithHost := provider.Issuer() + s.Config.OIDC.DefaultLoginURLV2
|
||||
prefixWithHost := provider.Issuer() + i.Config.LoginURLV2
|
||||
if !strings.HasPrefix(loc.String(), prefixWithHost) {
|
||||
return "", fmt.Errorf("login location has not prefix %s, but is %s", prefixWithHost, loc.String())
|
||||
}
|
||||
return strings.TrimPrefix(loc.String(), prefixWithHost), nil
|
||||
}
|
||||
|
||||
func (s *Tester) CreateOIDCAuthRequestImplicit(ctx context.Context, clientID, loginClient, redirectURI string, scope ...string) (authRequestID string, err error) {
|
||||
provider, err := s.CreateRelyingParty(ctx, clientID, redirectURI, scope...)
|
||||
func (i *Instance) CreateOIDCAuthRequestImplicit(ctx context.Context, clientID, loginClient, redirectURI string, scope ...string) (authRequestID string, err error) {
|
||||
provider, err := i.CreateRelyingParty(ctx, clientID, redirectURI, scope...)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -224,48 +232,49 @@ func (s *Tester) CreateOIDCAuthRequestImplicit(ctx context.Context, clientID, lo
|
||||
return "", err
|
||||
}
|
||||
|
||||
prefixWithHost := provider.Issuer() + s.Config.OIDC.DefaultLoginURLV2
|
||||
prefixWithHost := provider.Issuer() + i.Config.LoginURLV2
|
||||
if !strings.HasPrefix(loc.String(), prefixWithHost) {
|
||||
return "", fmt.Errorf("login location has not prefix %s, but is %s", prefixWithHost, loc.String())
|
||||
}
|
||||
return strings.TrimPrefix(loc.String(), prefixWithHost), nil
|
||||
}
|
||||
|
||||
func (s *Tester) OIDCIssuer() string {
|
||||
return http_util.BuildHTTP(s.Config.ExternalDomain, s.Config.Port, s.Config.ExternalSecure)
|
||||
func (i *Instance) OIDCIssuer() string {
|
||||
return http_util.BuildHTTP(i.Domain, i.Config.Port, i.Config.Secure)
|
||||
}
|
||||
|
||||
func (s *Tester) CreateRelyingParty(ctx context.Context, clientID, redirectURI string, scope ...string) (rp.RelyingParty, error) {
|
||||
return s.CreateRelyingPartyForDomain(ctx, s.Config.ExternalDomain, clientID, redirectURI, scope...)
|
||||
func (i *Instance) CreateRelyingParty(ctx context.Context, clientID, redirectURI string, scope ...string) (rp.RelyingParty, error) {
|
||||
return i.CreateRelyingPartyForDomain(ctx, i.Domain, clientID, redirectURI, scope...)
|
||||
}
|
||||
|
||||
func (s *Tester) CreateRelyingPartyForDomain(ctx context.Context, domain, clientID, redirectURI string, scope ...string) (rp.RelyingParty, error) {
|
||||
func (i *Instance) CreateRelyingPartyForDomain(ctx context.Context, domain, clientID, redirectURI string, scope ...string) (rp.RelyingParty, error) {
|
||||
if len(scope) == 0 {
|
||||
scope = []string{oidc.ScopeOpenID}
|
||||
}
|
||||
loginClient := &http.Client{Transport: &loginRoundTripper{http.DefaultTransport}}
|
||||
return rp.NewRelyingPartyOIDC(ctx, http_util.BuildHTTP(domain, s.Config.Port, s.Config.ExternalSecure), clientID, "", redirectURI, scope, rp.WithHTTPClient(loginClient))
|
||||
loginClient := &http.Client{Transport: &loginRoundTripper{http.DefaultTransport, i.Users.Get(UserTypeLogin).Username}}
|
||||
return rp.NewRelyingPartyOIDC(ctx, http_util.BuildHTTP(domain, i.Config.Port, i.Config.Secure), clientID, "", redirectURI, scope, rp.WithHTTPClient(loginClient))
|
||||
}
|
||||
|
||||
type loginRoundTripper struct {
|
||||
http.RoundTripper
|
||||
loginUsername string
|
||||
}
|
||||
|
||||
func (c *loginRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
req.Header.Set(oidc_internal.LoginClientHeader, LoginUser)
|
||||
req.Header.Set(oidc_internal.LoginClientHeader, c.loginUsername)
|
||||
return c.RoundTripper.RoundTrip(req)
|
||||
}
|
||||
|
||||
func (s *Tester) CreateResourceServerJWTProfile(ctx context.Context, keyFileData []byte) (rs.ResourceServer, error) {
|
||||
func (i *Instance) CreateResourceServerJWTProfile(ctx context.Context, keyFileData []byte) (rs.ResourceServer, error) {
|
||||
keyFile, err := client.ConfigFromKeyFileData(keyFileData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return rs.NewResourceServerJWTProfile(ctx, s.OIDCIssuer(), keyFile.ClientID, keyFile.KeyID, []byte(keyFile.Key))
|
||||
return rs.NewResourceServerJWTProfile(ctx, i.OIDCIssuer(), keyFile.ClientID, keyFile.KeyID, []byte(keyFile.Key))
|
||||
}
|
||||
|
||||
func (s *Tester) CreateResourceServerClientCredentials(ctx context.Context, clientID, clientSecret string) (rs.ResourceServer, error) {
|
||||
return rs.NewResourceServerClientCredentials(ctx, s.OIDCIssuer(), clientID, clientSecret)
|
||||
func (i *Instance) CreateResourceServerClientCredentials(ctx context.Context, clientID, clientSecret string) (rs.ResourceServer, error) {
|
||||
return rs.NewResourceServerClientCredentials(ctx, i.OIDCIssuer(), clientID, clientSecret)
|
||||
}
|
||||
|
||||
func GetRequest(url string, headers map[string]string) (*http.Request, error) {
|
||||
@@ -313,9 +322,9 @@ func CheckRedirect(req *http.Request) (*url.URL, error) {
|
||||
return resp.Location()
|
||||
}
|
||||
|
||||
func (s *Tester) CreateOIDCCredentialsClient(ctx context.Context) (machine *management.AddMachineUserResponse, name, clientID, clientSecret string, err error) {
|
||||
func (i *Instance) CreateOIDCCredentialsClient(ctx context.Context) (machine *management.AddMachineUserResponse, name, clientID, clientSecret string, err error) {
|
||||
name = gofakeit.Username()
|
||||
machine, err = s.Client.Mgmt.AddMachineUser(ctx, &management.AddMachineUserRequest{
|
||||
machine, err = i.Client.Mgmt.AddMachineUser(ctx, &management.AddMachineUserRequest{
|
||||
Name: name,
|
||||
UserName: name,
|
||||
AccessTokenType: user.AccessTokenType_ACCESS_TOKEN_TYPE_JWT,
|
||||
@@ -323,7 +332,7 @@ func (s *Tester) CreateOIDCCredentialsClient(ctx context.Context) (machine *mana
|
||||
if err != nil {
|
||||
return nil, "", "", "", err
|
||||
}
|
||||
secret, err := s.Client.Mgmt.GenerateMachineSecret(ctx, &management.GenerateMachineSecretRequest{
|
||||
secret, err := i.Client.Mgmt.GenerateMachineSecret(ctx, &management.GenerateMachineSecretRequest{
|
||||
UserId: machine.GetUserId(),
|
||||
})
|
||||
if err != nil {
|
||||
@@ -332,9 +341,9 @@ func (s *Tester) CreateOIDCCredentialsClient(ctx context.Context) (machine *mana
|
||||
return machine, name, secret.GetClientId(), secret.GetClientSecret(), nil
|
||||
}
|
||||
|
||||
func (s *Tester) CreateOIDCJWTProfileClient(ctx context.Context) (machine *management.AddMachineUserResponse, name string, keyData []byte, err error) {
|
||||
func (i *Instance) CreateOIDCJWTProfileClient(ctx context.Context) (machine *management.AddMachineUserResponse, name string, keyData []byte, err error) {
|
||||
name = gofakeit.Username()
|
||||
machine, err = s.Client.Mgmt.AddMachineUser(ctx, &management.AddMachineUserRequest{
|
||||
machine, err = i.Client.Mgmt.AddMachineUser(ctx, &management.AddMachineUserRequest{
|
||||
Name: name,
|
||||
UserName: name,
|
||||
AccessTokenType: user.AccessTokenType_ACCESS_TOKEN_TYPE_JWT,
|
||||
@@ -342,7 +351,7 @@ func (s *Tester) CreateOIDCJWTProfileClient(ctx context.Context) (machine *manag
|
||||
if err != nil {
|
||||
return nil, "", nil, err
|
||||
}
|
||||
keyResp, err := s.Client.Mgmt.AddMachineKey(ctx, &management.AddMachineKeyRequest{
|
||||
keyResp, err := i.Client.Mgmt.AddMachineKey(ctx, &management.AddMachineKeyRequest{
|
||||
UserId: machine.GetUserId(),
|
||||
Type: authn.KeyType_KEY_TYPE_JSON,
|
||||
ExpirationDate: timestamppb.New(time.Now().Add(time.Hour)),
|
||||
|
9
internal/integration/sink/channel.go
Normal file
9
internal/integration/sink/channel.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package sink
|
||||
|
||||
//go:generate enumer -type Channel -trimprefix Channel -transform snake
|
||||
type Channel int
|
||||
|
||||
const (
|
||||
ChannelMilestone Channel = iota
|
||||
ChannelQuota
|
||||
)
|
78
internal/integration/sink/channel_enumer.go
Normal file
78
internal/integration/sink/channel_enumer.go
Normal file
@@ -0,0 +1,78 @@
|
||||
// Code generated by "enumer -type Channel -trimprefix Channel -transform snake"; DO NOT EDIT.
|
||||
|
||||
package sink
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const _ChannelName = "milestonequota"
|
||||
|
||||
var _ChannelIndex = [...]uint8{0, 9, 14}
|
||||
|
||||
const _ChannelLowerName = "milestonequota"
|
||||
|
||||
func (i Channel) String() string {
|
||||
if i < 0 || i >= Channel(len(_ChannelIndex)-1) {
|
||||
return fmt.Sprintf("Channel(%d)", i)
|
||||
}
|
||||
return _ChannelName[_ChannelIndex[i]:_ChannelIndex[i+1]]
|
||||
}
|
||||
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
func _ChannelNoOp() {
|
||||
var x [1]struct{}
|
||||
_ = x[ChannelMilestone-(0)]
|
||||
_ = x[ChannelQuota-(1)]
|
||||
}
|
||||
|
||||
var _ChannelValues = []Channel{ChannelMilestone, ChannelQuota}
|
||||
|
||||
var _ChannelNameToValueMap = map[string]Channel{
|
||||
_ChannelName[0:9]: ChannelMilestone,
|
||||
_ChannelLowerName[0:9]: ChannelMilestone,
|
||||
_ChannelName[9:14]: ChannelQuota,
|
||||
_ChannelLowerName[9:14]: ChannelQuota,
|
||||
}
|
||||
|
||||
var _ChannelNames = []string{
|
||||
_ChannelName[0:9],
|
||||
_ChannelName[9:14],
|
||||
}
|
||||
|
||||
// ChannelString retrieves an enum value from the enum constants string name.
|
||||
// Throws an error if the param is not part of the enum.
|
||||
func ChannelString(s string) (Channel, error) {
|
||||
if val, ok := _ChannelNameToValueMap[s]; ok {
|
||||
return val, nil
|
||||
}
|
||||
|
||||
if val, ok := _ChannelNameToValueMap[strings.ToLower(s)]; ok {
|
||||
return val, nil
|
||||
}
|
||||
return 0, fmt.Errorf("%s does not belong to Channel values", s)
|
||||
}
|
||||
|
||||
// ChannelValues returns all values of the enum
|
||||
func ChannelValues() []Channel {
|
||||
return _ChannelValues
|
||||
}
|
||||
|
||||
// ChannelStrings returns a slice of all String values of the enum
|
||||
func ChannelStrings() []string {
|
||||
strs := make([]string, len(_ChannelNames))
|
||||
copy(strs, _ChannelNames)
|
||||
return strs
|
||||
}
|
||||
|
||||
// IsAChannel returns "true" if the value is listed in the enum definition. "false" otherwise
|
||||
func (i Channel) IsAChannel() bool {
|
||||
for _, v := range _ChannelValues {
|
||||
if i == v {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
167
internal/integration/sink/server.go
Normal file
167
internal/integration/sink/server.go
Normal file
@@ -0,0 +1,167 @@
|
||||
//go:build integration
|
||||
|
||||
package sink
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/zitadel/logging"
|
||||
)
|
||||
|
||||
const (
|
||||
port = "8081"
|
||||
listenAddr = "127.0.0.1:" + port
|
||||
host = "localhost:" + port
|
||||
)
|
||||
|
||||
// CallURL returns the full URL to the handler of a [Channel].
|
||||
func CallURL(ch Channel) string {
|
||||
u := url.URL{
|
||||
Scheme: "http",
|
||||
Host: host,
|
||||
Path: rootPath(ch),
|
||||
}
|
||||
return u.String()
|
||||
}
|
||||
|
||||
// StartServer starts a simple HTTP server on localhost:8081
|
||||
// ZITADEL can use the server to send HTTP requests which can be
|
||||
// used to validate tests through [Subscribe]rs.
|
||||
// For each [Channel] a route is registered on http://localhost:8081/<channel_name>.
|
||||
// The route must be used to send the HTTP request to be validated.
|
||||
// [CallURL] can be used to obtain the full URL for a given Channel.
|
||||
//
|
||||
// This function is only active when the `integration` build tag is enabled
|
||||
func StartServer() (close func()) {
|
||||
router := chi.NewRouter()
|
||||
for _, ch := range ChannelValues() {
|
||||
fwd := &forwarder{
|
||||
channelID: ch,
|
||||
subscribers: make(map[int64]chan<- *Request),
|
||||
}
|
||||
router.HandleFunc(rootPath(ch), fwd.receiveHandler)
|
||||
router.HandleFunc(subscribePath(ch), fwd.subscriptionHandler)
|
||||
}
|
||||
s := &http.Server{
|
||||
Addr: listenAddr,
|
||||
Handler: router,
|
||||
}
|
||||
|
||||
logging.WithFields("listen_addr", listenAddr).Warn("!!!! A sink server is started which may expose sensitive data on a public endpoint. Make sure the `integration` build tag is disabled for production builds. !!!!")
|
||||
go func() {
|
||||
err := s.ListenAndServe()
|
||||
if !errors.Is(err, http.ErrServerClosed) {
|
||||
logging.WithError(err).Fatal("sink server")
|
||||
}
|
||||
}()
|
||||
return func() {
|
||||
logging.OnError(s.Close()).Error("sink server")
|
||||
}
|
||||
}
|
||||
|
||||
func rootPath(c Channel) string {
|
||||
return path.Join("/", c.String())
|
||||
}
|
||||
|
||||
func subscribePath(c Channel) string {
|
||||
return path.Join("/", c.String(), "subscribe")
|
||||
}
|
||||
|
||||
// forwarder handles incoming HTTP requests from ZITADEL and
|
||||
// forwards them to all subscribed web sockets.
|
||||
type forwarder struct {
|
||||
channelID Channel
|
||||
id atomic.Int64
|
||||
mtx sync.RWMutex
|
||||
subscribers map[int64]chan<- *Request
|
||||
upgrader websocket.Upgrader
|
||||
}
|
||||
|
||||
// receiveHandler receives a simple HTTP for a single [Channel]
|
||||
// and forwards them on all active subscribers of that Channel.
|
||||
func (c *forwarder) receiveHandler(w http.ResponseWriter, r *http.Request) {
|
||||
req := &Request{
|
||||
Header: r.Header.Clone(),
|
||||
}
|
||||
var err error
|
||||
req.Body, err = io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
}
|
||||
|
||||
c.mtx.RLock()
|
||||
for _, reqChan := range c.subscribers {
|
||||
reqChan <- req
|
||||
}
|
||||
c.mtx.RUnlock()
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
// subscriptionHandler upgrades HTTP request to a websocket connection for subscribers.
|
||||
// All received HTTP requests on a subscriber's channel are send on the websocket to the client.
|
||||
func (c *forwarder) subscriptionHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ws, err := c.upgrader.Upgrade(w, r, nil)
|
||||
logging.OnError(err).Error("websocket upgrade")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
done := readLoop(ws)
|
||||
|
||||
id := c.id.Add(1)
|
||||
reqChannel := make(chan *Request, 100)
|
||||
|
||||
c.mtx.Lock()
|
||||
c.subscribers[id] = reqChannel
|
||||
c.mtx.Unlock()
|
||||
|
||||
logging.WithFields("id", id, "channel", c.channelID).Info("websocket opened")
|
||||
|
||||
defer func() {
|
||||
c.mtx.Lock()
|
||||
delete(c.subscribers, id)
|
||||
c.mtx.Unlock()
|
||||
|
||||
ws.Close()
|
||||
close(reqChannel)
|
||||
}()
|
||||
|
||||
for {
|
||||
select {
|
||||
case err := <-done:
|
||||
logging.WithError(err).WithFields(logrus.Fields{"id": id, "channel": c.channelID}).Info("websocket closed")
|
||||
return
|
||||
case req := <-reqChannel:
|
||||
if err := ws.WriteJSON(req); err != nil {
|
||||
logging.WithError(err).WithFields(logrus.Fields{"id": id, "channel": c.channelID}).Error("websocket write json")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// readLoop makes sure we can receive close messages
|
||||
func readLoop(ws *websocket.Conn) (done chan error) {
|
||||
done = make(chan error, 1)
|
||||
|
||||
go func(done chan<- error) {
|
||||
for {
|
||||
_, _, err := ws.NextReader()
|
||||
if err != nil {
|
||||
done <- err
|
||||
break
|
||||
}
|
||||
}
|
||||
close(done)
|
||||
}(done)
|
||||
|
||||
return done
|
||||
}
|
4
internal/integration/sink/sink.go
Normal file
4
internal/integration/sink/sink.go
Normal file
@@ -0,0 +1,4 @@
|
||||
// Package sink provides a simple HTTP server where Zitadel can send HTTP based messages,
|
||||
// which are then possible to be observed using observers on websockets.
|
||||
// The contents of this package become available when the `integration` build tag is enabled.
|
||||
package sink
|
9
internal/integration/sink/stub.go
Normal file
9
internal/integration/sink/stub.go
Normal file
@@ -0,0 +1,9 @@
|
||||
//go:build !integration
|
||||
|
||||
package sink
|
||||
|
||||
// StartServer and its returned close function are a no-op
|
||||
// when the `integration` build tag is disabled.
|
||||
func StartServer() (close func()) {
|
||||
return func() {}
|
||||
}
|
90
internal/integration/sink/subscription.go
Normal file
90
internal/integration/sink/subscription.go
Normal file
@@ -0,0 +1,90 @@
|
||||
//go:build integration
|
||||
|
||||
package sink
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/zitadel/logging"
|
||||
)
|
||||
|
||||
// Request is a message forwarded from the handler to [Subscription]s.
|
||||
type Request struct {
|
||||
Header http.Header
|
||||
Body json.RawMessage
|
||||
}
|
||||
|
||||
// Subscription is a websocket client to which [Request]s are forwarded by the server.
|
||||
type Subscription struct {
|
||||
conn *websocket.Conn
|
||||
closed atomic.Bool
|
||||
reqChannel chan *Request
|
||||
}
|
||||
|
||||
// Subscribe to a channel.
|
||||
// The subscription forwards all requests it received on the channel's
|
||||
// handler, after Subscribe has returned.
|
||||
// Multiple subscription may be active on a single channel.
|
||||
// Each request is always forwarded to each Subscription.
|
||||
// Close must be called to cleanup up the Subscription's channel and go routine.
|
||||
func Subscribe(ctx context.Context, ch Channel) *Subscription {
|
||||
u := url.URL{
|
||||
Scheme: "ws",
|
||||
Host: listenAddr,
|
||||
Path: subscribePath(ch),
|
||||
}
|
||||
conn, resp, err := websocket.DefaultDialer.DialContext(ctx, u.String(), nil)
|
||||
if err != nil {
|
||||
if resp != nil {
|
||||
defer resp.Body.Close()
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
err = fmt.Errorf("subscribe: %w, status: %s, body: %s", err, resp.Status, body)
|
||||
}
|
||||
panic(err)
|
||||
}
|
||||
|
||||
sub := &Subscription{
|
||||
conn: conn,
|
||||
reqChannel: make(chan *Request, 10),
|
||||
}
|
||||
go sub.readToChan()
|
||||
return sub
|
||||
}
|
||||
|
||||
func (s *Subscription) readToChan() {
|
||||
for {
|
||||
if s.closed.Load() {
|
||||
break
|
||||
}
|
||||
req := new(Request)
|
||||
if err := s.conn.ReadJSON(req); err != nil {
|
||||
opErr := new(net.OpError)
|
||||
if errors.As(err, &opErr) {
|
||||
break
|
||||
}
|
||||
logging.WithError(err).Error("subscription read")
|
||||
break
|
||||
}
|
||||
s.reqChannel <- req
|
||||
}
|
||||
close(s.reqChannel)
|
||||
}
|
||||
|
||||
// Recv returns the channel over which [Request]s are send.
|
||||
func (s *Subscription) Recv() <-chan *Request {
|
||||
return s.reqChannel
|
||||
}
|
||||
|
||||
func (s *Subscription) Close() error {
|
||||
s.closed.Store(true)
|
||||
return s.conn.Close()
|
||||
}
|
59
internal/integration/system.go
Normal file
59
internal/integration/system.go
Normal file
@@ -0,0 +1,59 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"context"
|
||||
_ "embed"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/zitadel/oidc/v3/pkg/client"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
|
||||
http_util "github.com/zitadel/zitadel/internal/api/http"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/system"
|
||||
)
|
||||
|
||||
var (
|
||||
//go:embed config/system-user-key.pem
|
||||
systemUserKey []byte
|
||||
)
|
||||
|
||||
var (
|
||||
// SystemClient creates a system connection once and reuses it on every use.
|
||||
// Each client call automatically gets the authorization context for the system user.
|
||||
SystemClient = sync.OnceValue[system.SystemServiceClient](systemClient)
|
||||
SystemToken string
|
||||
)
|
||||
|
||||
func systemClient() system.SystemServiceClient {
|
||||
cc, err := grpc.NewClient(loadedConfig.Host(),
|
||||
grpc.WithTransportCredentials(insecure.NewCredentials()),
|
||||
grpc.WithChainUnaryInterceptor(func(ctx context.Context, method string, req, reply any, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
|
||||
ctx = WithSystemAuthorization(ctx)
|
||||
return invoker(ctx, method, req, reply, cc, opts...)
|
||||
}),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return system.NewSystemServiceClient(cc)
|
||||
}
|
||||
|
||||
func systemUserToken() string {
|
||||
const ISSUER = "tester"
|
||||
audience := http_util.BuildOrigin(loadedConfig.Host(), loadedConfig.Secure)
|
||||
signer, err := client.NewSignerFromPrivateKeyByte(systemUserKey, "")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
token, err := client.SignedJWTProfileAssertion(ISSUER, []string{audience}, time.Hour, signer)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return token
|
||||
}
|
||||
|
||||
func WithSystemAuthorization(ctx context.Context) context.Context {
|
||||
return WithAuthorizationToken(ctx, SystemToken)
|
||||
}
|
@@ -11,10 +11,10 @@ import (
|
||||
"github.com/zitadel/zitadel/pkg/grpc/management"
|
||||
)
|
||||
|
||||
func (s *Tester) CreateMachineUserPATWithMembership(ctx context.Context, roles ...string) (id, pat string, err error) {
|
||||
user := s.CreateMachineUser(ctx)
|
||||
func (i *Instance) CreateMachineUserPATWithMembership(ctx context.Context, roles ...string) (id, pat string, err error) {
|
||||
user := i.CreateMachineUser(ctx)
|
||||
|
||||
patResp, err := s.Client.Mgmt.AddPersonalAccessToken(ctx, &management.AddPersonalAccessTokenRequest{
|
||||
patResp, err := i.Client.Mgmt.AddPersonalAccessToken(ctx, &management.AddPersonalAccessTokenRequest{
|
||||
UserId: user.GetUserId(),
|
||||
ExpirationDate: timestamppb.New(time.Now().Add(24 * time.Hour)),
|
||||
})
|
||||
@@ -35,7 +35,7 @@ func (s *Tester) CreateMachineUserPATWithMembership(ctx context.Context, roles .
|
||||
}
|
||||
|
||||
if len(orgRoles) > 0 {
|
||||
_, err := s.Client.Mgmt.AddOrgMember(ctx, &management.AddOrgMemberRequest{
|
||||
_, err := i.Client.Mgmt.AddOrgMember(ctx, &management.AddOrgMemberRequest{
|
||||
UserId: user.GetUserId(),
|
||||
Roles: orgRoles,
|
||||
})
|
||||
@@ -44,7 +44,7 @@ func (s *Tester) CreateMachineUserPATWithMembership(ctx context.Context, roles .
|
||||
}
|
||||
}
|
||||
if len(iamRoles) > 0 {
|
||||
_, err := s.Client.Admin.AddIAMMember(ctx, &admin.AddIAMMemberRequest{
|
||||
_, err := i.Client.Admin.AddIAMMember(ctx, &admin.AddIAMMemberRequest{
|
||||
UserId: user.GetUserId(),
|
||||
Roles: iamRoles,
|
||||
})
|
||||
|
86
internal/integration/usertype_enumer.go
Normal file
86
internal/integration/usertype_enumer.go
Normal file
@@ -0,0 +1,86 @@
|
||||
// Code generated by "enumer -type UserType -transform snake -trimprefix UserType"; DO NOT EDIT.
|
||||
|
||||
package integration
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const _UserTypeName = "unspecifiediam_ownerorg_ownerlogin"
|
||||
|
||||
var _UserTypeIndex = [...]uint8{0, 11, 20, 29, 34}
|
||||
|
||||
const _UserTypeLowerName = "unspecifiediam_ownerorg_ownerlogin"
|
||||
|
||||
func (i UserType) String() string {
|
||||
if i < 0 || i >= UserType(len(_UserTypeIndex)-1) {
|
||||
return fmt.Sprintf("UserType(%d)", i)
|
||||
}
|
||||
return _UserTypeName[_UserTypeIndex[i]:_UserTypeIndex[i+1]]
|
||||
}
|
||||
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
func _UserTypeNoOp() {
|
||||
var x [1]struct{}
|
||||
_ = x[UserTypeUnspecified-(0)]
|
||||
_ = x[UserTypeIAMOwner-(1)]
|
||||
_ = x[UserTypeOrgOwner-(2)]
|
||||
_ = x[UserTypeLogin-(3)]
|
||||
}
|
||||
|
||||
var _UserTypeValues = []UserType{UserTypeUnspecified, UserTypeIAMOwner, UserTypeOrgOwner, UserTypeLogin}
|
||||
|
||||
var _UserTypeNameToValueMap = map[string]UserType{
|
||||
_UserTypeName[0:11]: UserTypeUnspecified,
|
||||
_UserTypeLowerName[0:11]: UserTypeUnspecified,
|
||||
_UserTypeName[11:20]: UserTypeIAMOwner,
|
||||
_UserTypeLowerName[11:20]: UserTypeIAMOwner,
|
||||
_UserTypeName[20:29]: UserTypeOrgOwner,
|
||||
_UserTypeLowerName[20:29]: UserTypeOrgOwner,
|
||||
_UserTypeName[29:34]: UserTypeLogin,
|
||||
_UserTypeLowerName[29:34]: UserTypeLogin,
|
||||
}
|
||||
|
||||
var _UserTypeNames = []string{
|
||||
_UserTypeName[0:11],
|
||||
_UserTypeName[11:20],
|
||||
_UserTypeName[20:29],
|
||||
_UserTypeName[29:34],
|
||||
}
|
||||
|
||||
// UserTypeString retrieves an enum value from the enum constants string name.
|
||||
// Throws an error if the param is not part of the enum.
|
||||
func UserTypeString(s string) (UserType, error) {
|
||||
if val, ok := _UserTypeNameToValueMap[s]; ok {
|
||||
return val, nil
|
||||
}
|
||||
|
||||
if val, ok := _UserTypeNameToValueMap[strings.ToLower(s)]; ok {
|
||||
return val, nil
|
||||
}
|
||||
return 0, fmt.Errorf("%s does not belong to UserType values", s)
|
||||
}
|
||||
|
||||
// UserTypeValues returns all values of the enum
|
||||
func UserTypeValues() []UserType {
|
||||
return _UserTypeValues
|
||||
}
|
||||
|
||||
// UserTypeStrings returns a slice of all String values of the enum
|
||||
func UserTypeStrings() []string {
|
||||
strs := make([]string, len(_UserTypeNames))
|
||||
copy(strs, _UserTypeNames)
|
||||
return strs
|
||||
}
|
||||
|
||||
// IsAUserType returns "true" if the value is listed in the enum definition. "false" otherwise
|
||||
func (i UserType) IsAUserType() bool {
|
||||
for _, v := range _UserTypeValues {
|
||||
if i == v {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
@@ -1,27 +0,0 @@
|
||||
// Code generated by "stringer -type=UserType"; DO NOT EDIT.
|
||||
|
||||
package integration
|
||||
|
||||
import "strconv"
|
||||
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[Unspecified-0]
|
||||
_ = x[OrgOwner-1]
|
||||
_ = x[Login-2]
|
||||
_ = x[IAMOwner-3]
|
||||
_ = x[SystemUser-4]
|
||||
}
|
||||
|
||||
const _UserType_name = "UnspecifiedOrgOwnerLoginIAMOwnerSystemUser"
|
||||
|
||||
var _UserType_index = [...]uint8{0, 11, 19, 24, 32, 42}
|
||||
|
||||
func (i UserType) String() string {
|
||||
if i < 0 || i >= UserType(len(_UserType_index)-1) {
|
||||
return "UserType(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _UserType_name[_UserType_index[i]:_UserType_index[i+1]]
|
||||
}
|
Reference in New Issue
Block a user