mirror of
https://github.com/andwati/zitadel.git
synced 2026-11-11 00:25:12 +00:00
feat(rt): IDP Intents Relational Table (#11374)
# Which Problems Are Solved In the context of moving our datastore to relational tables, Identity Provider Intents have no definition of a relational table nor any DB API that can be used to store the IDP intents as relational objects. # How the Problems Are Solved The PR follows the guidelines left in #11190 - A table `zitadel.identity_provider_intents` is created: it defines its relational constraints (foreign keys) with the other tables, as well as an ENUM to represent the state of the intent. - An IDP Intent repository has been created, which offers CRUD operations implemented as `Create`, `Get`, `Update` and `Delete` methods for the IDP intent. - The repository also defines all columns and conditions useful for performing DB operations on the IDP intents. - DB mocks have been added as well to ease testing on the `domain` package. - Reduction functions have been implemented for the existing IDP events so to keep the relational table in sync with the event store. # Additional Changes To allow testing LDAP intent flow, the `Session.FetchUser()` method has been extracted to a separate file. A corresponding mock has been created as well in `fetch_user_mock.go`. The files are mutually excluded and added at build time through go build tags: the mock is added when the `integration` tag is specified, while the real implementation is added when the same tag IS NOT added to the build tags. # Additional Context - Closes #11190 --------- Co-authored-by: Livio Spring <[email protected]> Co-authored-by: Silvan <[email protected]>
This commit is contained in:
co-authored by
Livio Spring
Silvan
parent
ca9c7619c6
commit
4936bdf2c9
@@ -0,0 +1,174 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/database"
|
||||
)
|
||||
|
||||
//go:generate enumer -type IDPIntentState -transform lower -trimprefix IDPIntentState -sql
|
||||
type IDPIntentState uint8
|
||||
|
||||
const (
|
||||
IDPIntentStateStarted IDPIntentState = iota
|
||||
IDPIntentStateSucceeded
|
||||
IDPIntentStateFailed
|
||||
)
|
||||
|
||||
type IDPIntent struct {
|
||||
ID string `json:"id,omitempty" db:"id"`
|
||||
InstanceID string `json:"instance_id,omitempty" db:"instance_id"`
|
||||
State IDPIntentState `json:"state" db:"state"`
|
||||
SuccessURL *url.URL `json:"success_url,omitempty" db:"success_url"`
|
||||
FailureURL *url.URL `json:"failure_url,omitempty" db:"failure_url"`
|
||||
CreatedAt time.Time `json:"created_at,omitzero" db:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at,omitzero" db:"updated_at"`
|
||||
IDPID string `json:"idp_id,omitempty" db:"idp_id"`
|
||||
IDPArguments map[string]any `json:"idp_arguments,omitempty" db:"idp_arguments"`
|
||||
IDPUser []byte `json:"idp_user,omitempty" db:"idp_user"`
|
||||
IDPUserID string `json:"idp_user_id,omitempty" db:"idp_user_id"`
|
||||
IDPUsername string `json:"idp_username,omitempty" db:"idp_username"`
|
||||
UserID string `json:"user_id,omitempty" db:"user_id"`
|
||||
IDPAccessToken []byte `json:"idp_access_token,omitempty" db:"idp_access_token"`
|
||||
IDPIDToken string `json:"idp_id_token,omitempty" db:"idp_id_token"`
|
||||
EntryAttributes map[string][]string `json:"idp_entry_attributes,omitempty" db:"idp_entry_attributes"`
|
||||
RequestID string `json:"request_id,omitempty" db:"request_id"`
|
||||
Assertion []byte `json:"assertion,omitempty" db:"assertion"`
|
||||
SucceededAt *time.Time `json:"succeeded_at,omitzero" db:"succeeded_at"`
|
||||
FailReason string `json:"fail_reason,omitempty" db:"fail_reason"`
|
||||
FailedAt *time.Time `json:"failed_at,omitzero" db:"failed_at"`
|
||||
ExpiresAt *time.Time `json:"expires_at,omitzero" db:"expires_at"`
|
||||
}
|
||||
|
||||
// idpIntentColumns defines all the columns of the identity_provider_intents table.
|
||||
type idpIntentColumns interface {
|
||||
// IDColumn returns the column for the field id
|
||||
IDColumn() database.Column
|
||||
// InstanceIDColumn returns the column for the field instance_id
|
||||
InstanceIDColumn() database.Column
|
||||
// StateColumn returns the column for the field state
|
||||
StateColumn() database.Column
|
||||
// SuccessURLColumn returns the column for the field success_url
|
||||
SuccessURLColumn() database.Column
|
||||
// FailureURLColumn returns the column for the field failure_url
|
||||
FailureURLColumn() database.Column
|
||||
// CreatedAtColumn returns the column for the field created_at
|
||||
CreatedAtColumn() database.Column
|
||||
// UpdatedAtColumn returns the column for the field updated_at
|
||||
UpdatedAtColumn() database.Column
|
||||
// IDPIDColumn returns the column for the field idp_id
|
||||
IDPIDColumn() database.Column
|
||||
// IDPArgumentsColumn returns the column for the field idp_arguments
|
||||
IDPArgumentsColumn() database.Column
|
||||
// IDPUserColumn returns the column for the field idp_user
|
||||
IDPUserColumn() database.Column
|
||||
// IDPUserIDColumn returns the column for the field idp_user_id
|
||||
IDPUserIDColumn() database.Column
|
||||
// IDPUsernameColumn returns the column for the field idp_username
|
||||
IDPUsernameColumn() database.Column
|
||||
// UserIDColumn returns the column for the field user_id
|
||||
UserIDColumn() database.Column
|
||||
// IDPAccessTokenColumn returns the column for the field idp_access_token
|
||||
IDPAccessTokenColumn() database.Column
|
||||
// IDPIDTokenColumn returns the column for the field idp_id_token
|
||||
IDPIDTokenColumn() database.Column
|
||||
// IDPEntryAttributesColumn returns the column for the field idp_entry_attributes
|
||||
IDPEntryAttributesColumn() database.Column
|
||||
// RequestIDColumn returns the column for the field request_id
|
||||
RequestIDColumn() database.Column
|
||||
// AssertionColumn returns the column for the field assertion
|
||||
AssertionColumn() database.Column
|
||||
// SucceededAtColumn returns the column for the field succeeded_at
|
||||
SucceededAtColumn() database.Column
|
||||
// FailReasonColumn returns the column for the field fail_reason
|
||||
FailReasonColumn() database.Column
|
||||
// FailedAtColumn returns the column for the field failed_at
|
||||
FailedAtColumn() database.Column
|
||||
// ExpiresAtColumn returns the column for the field expires_at
|
||||
ExpiresAtColumn() database.Column
|
||||
}
|
||||
|
||||
type idpIntentConditions interface {
|
||||
// PrimaryKeyCondition returns a filter on the primary key fields.
|
||||
PrimaryKeyCondition(instanceID, id string) database.Condition
|
||||
// IDCondition returns an equal filter on the ID
|
||||
IDCondition(id string) database.Condition
|
||||
// InstanceIDCondition returns an equal filter on the InstanceID
|
||||
InstanceIDCondition(instanceID string) database.Condition
|
||||
// StateCondition returns an equal filter on the State
|
||||
StateCondition(state IDPIntentState) database.Condition
|
||||
// IDPIDCondition returns an equal filter on the IDPID
|
||||
IDPIDCondition(idpID string) database.Condition
|
||||
// IDPUserIDCondition returns an equal filter on the IDPUserID
|
||||
IDPUserIDCondition(idpUserID string) database.Condition
|
||||
// IDPUsernameCondition returns an equal filter on the IDPUsername
|
||||
IDPUsernameCondition(idpUsername string) database.Condition
|
||||
// UserIDCondition returns an equal filter on the UserID
|
||||
UserIDCondition(userID string) database.Condition
|
||||
// RequestIDCondition returns an equal filter on the RequestID
|
||||
RequestIDCondition(requestID string) database.Condition
|
||||
// CreatedAtCondition returns a filter on the created at field.
|
||||
CreatedAtCondition(op database.NumberOperation, createdAt time.Time) database.Condition
|
||||
// UpdatedAtCondition returns a filter on the updated at field.
|
||||
UpdatedAtCondition(op database.NumberOperation, updatedAt time.Time) database.Condition
|
||||
}
|
||||
|
||||
type idpIntentChanges interface {
|
||||
// SetState sets the state of the IDP intent
|
||||
SetState(state IDPIntentState) database.Change
|
||||
// SetSuccessURL sets the successURL of the IDP intent
|
||||
SetSuccessURL(successURL url.URL) database.Change
|
||||
// SetFailureURL sets the failureURL of the IDP intent
|
||||
SetFailureURL(failureURL url.URL) database.Change
|
||||
// SetIDPID sets the idpID of the IDP intent
|
||||
SetIDPID(idpID string) database.Change
|
||||
// SetIDPArguments sets the idpArguments of the IDP intent
|
||||
SetIDPArguments(idpArguments []byte) database.Change
|
||||
// SetIDPUser sets the idpUser of the IDP intent
|
||||
SetIDPUser(idpUser []byte) database.Change
|
||||
// SetIDPUserID sets the idpUserID of the IDP intent
|
||||
SetIDPUserID(idpUserID string) database.Change
|
||||
// SetIDPUsername sets the idpUsername of the IDP intent
|
||||
SetIDPUsername(idpUsername string) database.Change
|
||||
// SetUserID sets the userID of the IDP intent
|
||||
SetUserID(userID string) database.Change
|
||||
// SetIDPAccessToken sets the idpAccessToken of the IDP intent
|
||||
SetIDPAccessToken(idpAccessToken []byte) database.Change
|
||||
// SetIDPIDToken sets the idpIDToken of the IDP intent
|
||||
SetIDPIDToken(idpIDToken string) database.Change
|
||||
// SetIDPEntryAttributes sets the idpEntryAttributes of the IDP intent
|
||||
SetIDPEntryAttributes(idpEntryAttributes []byte) database.Change
|
||||
// SetRequestID sets the requestID of the IDP intent
|
||||
SetRequestID(requestID string) database.Change
|
||||
// SetAssertion sets the assertion of the IDP intent
|
||||
SetAssertion(assertion []byte) database.Change
|
||||
// SetFailReason sets the fail reason of the IDP intent
|
||||
SetFailReason(reason string) database.Change
|
||||
// SetExpiresAt sets the expiration of the IDP intent
|
||||
SetExpiresAt(expiration time.Time) database.Change
|
||||
// SetSucceededAt sets the succededAt time of the IDP intent
|
||||
SetSucceededAt(succeededAt time.Time) database.Change
|
||||
// SetFailedAt sets the failedAt time of the IDP intent
|
||||
SetFailedAt(failedAt time.Time) database.Change
|
||||
}
|
||||
|
||||
//go:generate mockgen -typed -package domainmock -destination ./mock/idp_intent.mock.go . IDPIntentRepository
|
||||
type IDPIntentRepository interface {
|
||||
Repository
|
||||
|
||||
idpIntentColumns
|
||||
idpIntentConditions
|
||||
idpIntentChanges
|
||||
|
||||
// Get returns an idp intent based on the given condition.
|
||||
Get(ctx context.Context, client database.QueryExecutor, opts ...database.QueryOption) (*IDPIntent, error)
|
||||
// Create creates a new idp intent.
|
||||
Create(ctx context.Context, client database.QueryExecutor, intent *IDPIntent) error
|
||||
// Update one or more existing idp intents.
|
||||
// The condition must include at least the instanceID of the idp intent to update.
|
||||
Update(ctx context.Context, client database.QueryExecutor, condition database.Condition, changes ...database.Change) (int64, error)
|
||||
// Delete removes idp intents based on the given condition.
|
||||
Delete(ctx context.Context, client database.QueryExecutor, condition database.Condition) (int64, error)
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
// Code generated by "enumer -type IDPIntentState -transform lower -trimprefix IDPIntentState -sql"; DO NOT EDIT.
|
||||
|
||||
package domain
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const _IDPIntentStateName = "startedsucceededfailed"
|
||||
|
||||
var _IDPIntentStateIndex = [...]uint8{0, 7, 16, 22}
|
||||
|
||||
const _IDPIntentStateLowerName = "startedsucceededfailed"
|
||||
|
||||
func (i IDPIntentState) String() string {
|
||||
if i >= IDPIntentState(len(_IDPIntentStateIndex)-1) {
|
||||
return fmt.Sprintf("IDPIntentState(%d)", i)
|
||||
}
|
||||
return _IDPIntentStateName[_IDPIntentStateIndex[i]:_IDPIntentStateIndex[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 _IDPIntentStateNoOp() {
|
||||
var x [1]struct{}
|
||||
_ = x[IDPIntentStateStarted-(0)]
|
||||
_ = x[IDPIntentStateSucceeded-(1)]
|
||||
_ = x[IDPIntentStateFailed-(2)]
|
||||
}
|
||||
|
||||
var _IDPIntentStateValues = []IDPIntentState{IDPIntentStateStarted, IDPIntentStateSucceeded, IDPIntentStateFailed}
|
||||
|
||||
var _IDPIntentStateNameToValueMap = map[string]IDPIntentState{
|
||||
_IDPIntentStateName[0:7]: IDPIntentStateStarted,
|
||||
_IDPIntentStateLowerName[0:7]: IDPIntentStateStarted,
|
||||
_IDPIntentStateName[7:16]: IDPIntentStateSucceeded,
|
||||
_IDPIntentStateLowerName[7:16]: IDPIntentStateSucceeded,
|
||||
_IDPIntentStateName[16:22]: IDPIntentStateFailed,
|
||||
_IDPIntentStateLowerName[16:22]: IDPIntentStateFailed,
|
||||
}
|
||||
|
||||
var _IDPIntentStateNames = []string{
|
||||
_IDPIntentStateName[0:7],
|
||||
_IDPIntentStateName[7:16],
|
||||
_IDPIntentStateName[16:22],
|
||||
}
|
||||
|
||||
// IDPIntentStateString retrieves an enum value from the enum constants string name.
|
||||
// Throws an error if the param is not part of the enum.
|
||||
func IDPIntentStateString(s string) (IDPIntentState, error) {
|
||||
if val, ok := _IDPIntentStateNameToValueMap[s]; ok {
|
||||
return val, nil
|
||||
}
|
||||
|
||||
if val, ok := _IDPIntentStateNameToValueMap[strings.ToLower(s)]; ok {
|
||||
return val, nil
|
||||
}
|
||||
return 0, fmt.Errorf("%s does not belong to IDPIntentState values", s)
|
||||
}
|
||||
|
||||
// IDPIntentStateValues returns all values of the enum
|
||||
func IDPIntentStateValues() []IDPIntentState {
|
||||
return _IDPIntentStateValues
|
||||
}
|
||||
|
||||
// IDPIntentStateStrings returns a slice of all String values of the enum
|
||||
func IDPIntentStateStrings() []string {
|
||||
strs := make([]string, len(_IDPIntentStateNames))
|
||||
copy(strs, _IDPIntentStateNames)
|
||||
return strs
|
||||
}
|
||||
|
||||
// IsAIDPIntentState returns "true" if the value is listed in the enum definition. "false" otherwise
|
||||
func (i IDPIntentState) IsAIDPIntentState() bool {
|
||||
for _, v := range _IDPIntentStateValues {
|
||||
if i == v {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (i IDPIntentState) Value() (driver.Value, error) {
|
||||
return i.String(), nil
|
||||
}
|
||||
|
||||
func (i *IDPIntentState) Scan(value interface{}) error {
|
||||
if value == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var str string
|
||||
switch v := value.(type) {
|
||||
case []byte:
|
||||
str = string(v)
|
||||
case string:
|
||||
str = v
|
||||
case fmt.Stringer:
|
||||
str = v.String()
|
||||
default:
|
||||
return fmt.Errorf("invalid value of IDPIntentState: %[1]T(%[1]v)", value)
|
||||
}
|
||||
|
||||
val, err := IDPIntentStateString(str)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*i = val
|
||||
return nil
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
+16
@@ -0,0 +1,16 @@
|
||||
package migration
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
)
|
||||
|
||||
var (
|
||||
//go:embed 012_identity_provider_intents_table/up.sql
|
||||
up012IDPIntentsTable string
|
||||
//go:embed 012_identity_provider_intents_table/down.sql
|
||||
down012IDPIntentsTable string
|
||||
)
|
||||
|
||||
func init() {
|
||||
registerSQLMigration(12, up012IDPIntentsTable, down012IDPIntentsTable)
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
DROP TABLE IF EXISTS zitadel.identity_provider_intents;
|
||||
DROP TYPE IF EXISTS zitadel.idp_intent_state;
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
CREATE TYPE zitadel.idp_intent_state AS ENUM (
|
||||
'started',
|
||||
'succeeded',
|
||||
'failed',
|
||||
'consumed'
|
||||
);
|
||||
|
||||
CREATE TABLE zitadel.identity_provider_intents
|
||||
(
|
||||
instance_id TEXT NOT NULL,
|
||||
id TEXT NOT NULL CHECK ( id <> '' ),
|
||||
state zitadel.idp_intent_state NOT NULL DEFAULT 'started',
|
||||
success_url TEXT,
|
||||
failure_url TEXT,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
idp_id TEXT NOT NULL,
|
||||
idp_arguments JSONB,
|
||||
idp_user BYTEA,
|
||||
idp_user_id TEXT,
|
||||
idp_username TEXT,
|
||||
user_id TEXT,
|
||||
idp_access_token JSONB,
|
||||
idp_id_token TEXT,
|
||||
idp_entry_attributes JSONB,
|
||||
request_id TEXT,
|
||||
assertion JSONB,
|
||||
succeeded_at TIMESTAMPTZ,
|
||||
fail_reason TEXT,
|
||||
failed_at TIMESTAMPTZ,
|
||||
expires_at TIMESTAMPTZ,
|
||||
|
||||
PRIMARY KEY (instance_id, id),
|
||||
-- TODO(@adlerhurst): easiest would be to create the index in the user migrations so that we can merge this one but the problem is that the migration might fail if there are already intents created.
|
||||
-- FOREIGN KEY (instance_id, user_id) REFERENCES zitadel.users (instance_id, id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (instance_id, idp_id) REFERENCES zitadel.identity_providers (instance_id, id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TRIGGER trigger_set_updated_at
|
||||
BEFORE UPDATE
|
||||
ON zitadel.identity_provider_intents
|
||||
FOR EACH ROW
|
||||
WHEN (NEW.updated_at IS NULL)
|
||||
EXECUTE FUNCTION zitadel.set_updated_at();
|
||||
@@ -69,7 +69,7 @@ func TestMain(m *testing.M) {
|
||||
CTX = integration.WithSystemAuthorization(ctx)
|
||||
Instance = integration.NewInstance(CTX)
|
||||
|
||||
IAMCTX = Instance.WithAuthorization(ctx, integration.UserTypeIAMOwner)
|
||||
IAMCTX = Instance.WithAuthorizationToken(ctx, integration.UserTypeIAMOwner)
|
||||
SystemClient = integration.SystemClient()
|
||||
OrgClient = Instance.Client.OrgV2beta
|
||||
ProjectClient = Instance.Client.Projectv2Beta
|
||||
|
||||
@@ -145,17 +145,7 @@ func TestServer_TestIDProviderInstanceReduces(t *testing.T) {
|
||||
name := gofakeit.Name()
|
||||
|
||||
before := time.Now()
|
||||
addOIDC, err := AdminClient.AddOIDCIDP(IAMCTX, &admin.AddOIDCIDPRequest{
|
||||
Name: name,
|
||||
StylingType: idp_grpc.IDPStylingType_STYLING_TYPE_GOOGLE,
|
||||
ClientId: "clientID",
|
||||
ClientSecret: "clientSecret",
|
||||
Issuer: "issuer",
|
||||
Scopes: []string{"scope"},
|
||||
DisplayNameMapping: idp.OIDCMappingField_OIDC_MAPPING_FIELD_EMAIL,
|
||||
UsernameMapping: idp.OIDCMappingField_OIDC_MAPPING_FIELD_EMAIL,
|
||||
AutoRegister: true,
|
||||
})
|
||||
addOIDC, err := AdminClient.AddOIDCIDP(IAMCTX, defaultOIDCIDReq(name, true))
|
||||
after := time.Now()
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -190,17 +180,7 @@ func TestServer_TestIDProviderInstanceReduces(t *testing.T) {
|
||||
t.Run("test iam idp update reduces", func(t *testing.T) {
|
||||
name := gofakeit.Name()
|
||||
|
||||
addOIDC, err := AdminClient.AddOIDCIDP(IAMCTX, &admin.AddOIDCIDPRequest{
|
||||
Name: name,
|
||||
StylingType: idp_grpc.IDPStylingType_STYLING_TYPE_GOOGLE,
|
||||
ClientId: "clientID",
|
||||
ClientSecret: "clientSecret",
|
||||
Issuer: "issuer",
|
||||
Scopes: []string{"scope"},
|
||||
DisplayNameMapping: idp.OIDCMappingField_OIDC_MAPPING_FIELD_EMAIL,
|
||||
UsernameMapping: idp.OIDCMappingField_OIDC_MAPPING_FIELD_EMAIL,
|
||||
AutoRegister: true,
|
||||
})
|
||||
addOIDC, err := AdminClient.AddOIDCIDP(IAMCTX, defaultOIDCIDReq(name, true))
|
||||
require.NoError(t, err)
|
||||
|
||||
name = "new_" + name
|
||||
@@ -238,17 +218,7 @@ func TestServer_TestIDProviderInstanceReduces(t *testing.T) {
|
||||
t.Run("test iam idp deactivate reduces", func(t *testing.T) {
|
||||
name := gofakeit.Name()
|
||||
|
||||
addOIDC, err := AdminClient.AddOIDCIDP(IAMCTX, &admin.AddOIDCIDPRequest{
|
||||
Name: name,
|
||||
StylingType: idp_grpc.IDPStylingType_STYLING_TYPE_GOOGLE,
|
||||
ClientId: "clientID",
|
||||
ClientSecret: "clientSecret",
|
||||
Issuer: "issuer",
|
||||
Scopes: []string{"scope"},
|
||||
DisplayNameMapping: idp.OIDCMappingField_OIDC_MAPPING_FIELD_EMAIL,
|
||||
UsernameMapping: idp.OIDCMappingField_OIDC_MAPPING_FIELD_EMAIL,
|
||||
AutoRegister: true,
|
||||
})
|
||||
addOIDC, err := AdminClient.AddOIDCIDP(IAMCTX, defaultOIDCIDReq(name, true))
|
||||
require.NoError(t, err)
|
||||
|
||||
// deactivate idp
|
||||
@@ -280,17 +250,7 @@ func TestServer_TestIDProviderInstanceReduces(t *testing.T) {
|
||||
t.Run("test iam idp config reactivate reduces", func(t *testing.T) {
|
||||
name := gofakeit.Name()
|
||||
|
||||
addOIDC, err := AdminClient.AddOIDCIDP(IAMCTX, &admin.AddOIDCIDPRequest{
|
||||
Name: name,
|
||||
StylingType: idp_grpc.IDPStylingType_STYLING_TYPE_GOOGLE,
|
||||
ClientId: "clientID",
|
||||
ClientSecret: "clientSecret",
|
||||
Issuer: "issuer",
|
||||
Scopes: []string{"scope"},
|
||||
DisplayNameMapping: idp.OIDCMappingField_OIDC_MAPPING_FIELD_EMAIL,
|
||||
UsernameMapping: idp.OIDCMappingField_OIDC_MAPPING_FIELD_EMAIL,
|
||||
AutoRegister: true,
|
||||
})
|
||||
addOIDC, err := AdminClient.AddOIDCIDP(IAMCTX, defaultOIDCIDReq(name, true))
|
||||
require.NoError(t, err)
|
||||
|
||||
idpRepo := repository.IDProviderRepository()
|
||||
@@ -342,17 +302,7 @@ func TestServer_TestIDProviderInstanceReduces(t *testing.T) {
|
||||
name := gofakeit.Name()
|
||||
|
||||
// add idp
|
||||
addOIDC, err := AdminClient.AddOIDCIDP(IAMCTX, &admin.AddOIDCIDPRequest{
|
||||
Name: name,
|
||||
StylingType: idp_grpc.IDPStylingType_STYLING_TYPE_GOOGLE,
|
||||
ClientId: "clientID",
|
||||
ClientSecret: "clientSecret",
|
||||
Issuer: "issuer",
|
||||
Scopes: []string{"scope"},
|
||||
DisplayNameMapping: idp.OIDCMappingField_OIDC_MAPPING_FIELD_EMAIL,
|
||||
UsernameMapping: idp.OIDCMappingField_OIDC_MAPPING_FIELD_EMAIL,
|
||||
AutoRegister: true,
|
||||
})
|
||||
addOIDC, err := AdminClient.AddOIDCIDP(IAMCTX, defaultOIDCIDReq(name, true))
|
||||
require.NoError(t, err)
|
||||
|
||||
idpRepo := repository.IDProviderRepository()
|
||||
@@ -380,17 +330,7 @@ func TestServer_TestIDProviderInstanceReduces(t *testing.T) {
|
||||
name := gofakeit.Name()
|
||||
|
||||
// add oidc
|
||||
addOIDC, err := AdminClient.AddOIDCIDP(IAMCTX, &admin.AddOIDCIDPRequest{
|
||||
Name: name,
|
||||
StylingType: idp_grpc.IDPStylingType_STYLING_TYPE_GOOGLE,
|
||||
ClientId: "clientID",
|
||||
ClientSecret: "clientSecret",
|
||||
Issuer: "issuer",
|
||||
Scopes: []string{"scope"},
|
||||
DisplayNameMapping: idp.OIDCMappingField_OIDC_MAPPING_FIELD_EMAIL,
|
||||
UsernameMapping: idp.OIDCMappingField_OIDC_MAPPING_FIELD_EMAIL,
|
||||
AutoRegister: false,
|
||||
})
|
||||
addOIDC, err := AdminClient.AddOIDCIDP(IAMCTX, defaultOIDCIDReq(name, false))
|
||||
require.NoError(t, err)
|
||||
|
||||
idpRepo := repository.IDProviderRepository()
|
||||
@@ -427,17 +367,7 @@ func TestServer_TestIDProviderInstanceReduces(t *testing.T) {
|
||||
name := gofakeit.Name()
|
||||
|
||||
// add oidc
|
||||
addOIDC, err := AdminClient.AddOIDCIDP(IAMCTX, &admin.AddOIDCIDPRequest{
|
||||
Name: name,
|
||||
StylingType: idp_grpc.IDPStylingType_STYLING_TYPE_GOOGLE,
|
||||
ClientId: "clientID",
|
||||
ClientSecret: "clientSecret",
|
||||
Issuer: "issuer",
|
||||
Scopes: []string{"scope"},
|
||||
DisplayNameMapping: idp.OIDCMappingField_OIDC_MAPPING_FIELD_EMAIL,
|
||||
UsernameMapping: idp.OIDCMappingField_OIDC_MAPPING_FIELD_EMAIL,
|
||||
AutoRegister: true,
|
||||
})
|
||||
addOIDC, err := AdminClient.AddOIDCIDP(IAMCTX, defaultOIDCIDReq(name, true))
|
||||
require.NoError(t, err)
|
||||
|
||||
idpRepo := repository.IDProviderRepository()
|
||||
@@ -1968,40 +1898,7 @@ func TestServer_TestIDProviderInstanceReduces(t *testing.T) {
|
||||
|
||||
// add ldap
|
||||
before := time.Now()
|
||||
addLdap, err := AdminClient.AddLDAPProvider(IAMCTX, &admin.AddLDAPProviderRequest{
|
||||
Name: name,
|
||||
Servers: []string{"servers"},
|
||||
StartTls: true,
|
||||
BaseDn: "baseDN",
|
||||
BindDn: "bindND",
|
||||
BindPassword: "bindPassword",
|
||||
UserBase: "userBase",
|
||||
UserObjectClasses: []string{"userOhjectClasses"},
|
||||
UserFilters: []string{"userFilters"},
|
||||
Timeout: durationpb.New(time.Minute),
|
||||
Attributes: &idp_grpc.LDAPAttributes{
|
||||
IdAttribute: "idAttribute",
|
||||
FirstNameAttribute: "firstNameAttribute",
|
||||
LastNameAttribute: "lastNameAttribute",
|
||||
DisplayNameAttribute: "displayNameAttribute",
|
||||
NickNameAttribute: "nickNameAttribute",
|
||||
PreferredUsernameAttribute: "preferredUsernameAttribute",
|
||||
EmailAttribute: "emailAttribute",
|
||||
EmailVerifiedAttribute: "emailVerifiedAttribute",
|
||||
PhoneAttribute: "phoneAttribute",
|
||||
PhoneVerifiedAttribute: "phoneVerifiedAttribute",
|
||||
PreferredLanguageAttribute: "preferredLanguageAttribute",
|
||||
AvatarUrlAttribute: "avatarUrlAttribute",
|
||||
ProfileAttribute: "profileAttribute",
|
||||
},
|
||||
ProviderOptions: &idp_grpc.Options{
|
||||
IsLinkingAllowed: false,
|
||||
IsCreationAllowed: false,
|
||||
IsAutoCreation: false,
|
||||
IsAutoUpdate: false,
|
||||
AutoLinking: idp.AutoLinkingOption_AUTO_LINKING_OPTION_EMAIL,
|
||||
},
|
||||
})
|
||||
addLdap, err := AdminClient.AddLDAPProvider(IAMCTX, defaultInstanceLDAPRequest(name))
|
||||
after := time.Now()
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -2056,40 +1953,7 @@ func TestServer_TestIDProviderInstanceReduces(t *testing.T) {
|
||||
name := gofakeit.Name()
|
||||
|
||||
// add ldap
|
||||
addLdap, err := AdminClient.AddLDAPProvider(IAMCTX, &admin.AddLDAPProviderRequest{
|
||||
Name: name,
|
||||
Servers: []string{"servers"},
|
||||
StartTls: true,
|
||||
BaseDn: "baseDN",
|
||||
BindDn: "bindND",
|
||||
BindPassword: "bindPassword",
|
||||
UserBase: "userBase",
|
||||
UserObjectClasses: []string{"userOhjectClasses"},
|
||||
UserFilters: []string{"userFilters"},
|
||||
Timeout: durationpb.New(time.Minute),
|
||||
Attributes: &idp_grpc.LDAPAttributes{
|
||||
IdAttribute: "idAttribute",
|
||||
FirstNameAttribute: "firstNameAttribute",
|
||||
LastNameAttribute: "lastNameAttribute",
|
||||
DisplayNameAttribute: "displayNameAttribute",
|
||||
NickNameAttribute: "nickNameAttribute",
|
||||
PreferredUsernameAttribute: "preferredUsernameAttribute",
|
||||
EmailAttribute: "emailAttribute",
|
||||
EmailVerifiedAttribute: "emailVerifiedAttribute",
|
||||
PhoneAttribute: "phoneAttribute",
|
||||
PhoneVerifiedAttribute: "phoneVerifiedAttribute",
|
||||
PreferredLanguageAttribute: "preferredLanguageAttribute",
|
||||
AvatarUrlAttribute: "avatarUrlAttribute",
|
||||
ProfileAttribute: "profileAttribute",
|
||||
},
|
||||
ProviderOptions: &idp_grpc.Options{
|
||||
IsLinkingAllowed: false,
|
||||
IsCreationAllowed: false,
|
||||
IsAutoCreation: false,
|
||||
IsAutoUpdate: false,
|
||||
AutoLinking: idp.AutoLinkingOption_AUTO_LINKING_OPTION_EMAIL,
|
||||
},
|
||||
})
|
||||
addLdap, err := AdminClient.AddLDAPProvider(IAMCTX, defaultInstanceLDAPRequest(name))
|
||||
require.NoError(t, err)
|
||||
|
||||
idpRepo := repository.IDProviderRepository()
|
||||
@@ -2328,25 +2192,7 @@ func TestServer_TestIDProviderInstanceReduces(t *testing.T) {
|
||||
|
||||
// add saml
|
||||
before := time.Now()
|
||||
addSAML, err := AdminClient.AddSAMLProvider(IAMCTX, &admin.AddSAMLProviderRequest{
|
||||
Name: name,
|
||||
Metadata: &admin.AddSAMLProviderRequest_MetadataXml{
|
||||
MetadataXml: validSAMLMetadata1,
|
||||
},
|
||||
Binding: idp.SAMLBinding_SAML_BINDING_POST,
|
||||
WithSignedRequest: false,
|
||||
TransientMappingAttributeName: &name,
|
||||
FederatedLogoutEnabled: &federatedLogoutEnabled,
|
||||
NameIdFormat: idp.SAMLNameIDFormat_SAML_NAME_ID_FORMAT_TRANSIENT.Enum(),
|
||||
ProviderOptions: &idp_grpc.Options{
|
||||
IsLinkingAllowed: false,
|
||||
IsCreationAllowed: false,
|
||||
IsAutoCreation: false,
|
||||
IsAutoUpdate: false,
|
||||
AutoLinking: idp.AutoLinkingOption_AUTO_LINKING_OPTION_EMAIL,
|
||||
},
|
||||
SignatureAlgorithm: idp.SAMLSignatureAlgorithm_SAML_SIGNATURE_RSA_SHA1,
|
||||
})
|
||||
addSAML, err := AdminClient.AddSAMLProvider(IAMCTX, defaultInstanceSAMLRequest(name, &federatedLogoutEnabled))
|
||||
after := time.Now()
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -2389,25 +2235,7 @@ func TestServer_TestIDProviderInstanceReduces(t *testing.T) {
|
||||
federatedLogoutEnabled := false
|
||||
|
||||
// add saml
|
||||
addSAML, err := AdminClient.AddSAMLProvider(IAMCTX, &admin.AddSAMLProviderRequest{
|
||||
Name: name,
|
||||
Metadata: &admin.AddSAMLProviderRequest_MetadataXml{
|
||||
MetadataXml: validSAMLMetadata1,
|
||||
},
|
||||
Binding: idp.SAMLBinding_SAML_BINDING_POST,
|
||||
WithSignedRequest: false,
|
||||
TransientMappingAttributeName: &name,
|
||||
FederatedLogoutEnabled: &federatedLogoutEnabled,
|
||||
NameIdFormat: idp.SAMLNameIDFormat_SAML_NAME_ID_FORMAT_TRANSIENT.Enum(),
|
||||
ProviderOptions: &idp_grpc.Options{
|
||||
IsLinkingAllowed: false,
|
||||
IsCreationAllowed: false,
|
||||
IsAutoCreation: false,
|
||||
IsAutoUpdate: false,
|
||||
AutoLinking: idp.AutoLinkingOption_AUTO_LINKING_OPTION_EMAIL,
|
||||
},
|
||||
SignatureAlgorithm: idp.SAMLSignatureAlgorithm_SAML_SIGNATURE_RSA_SHA1,
|
||||
})
|
||||
addSAML, err := AdminClient.AddSAMLProvider(IAMCTX, defaultInstanceSAMLRequest(name, &federatedLogoutEnabled))
|
||||
require.NoError(t, err)
|
||||
|
||||
idpRepo := repository.IDProviderRepository()
|
||||
@@ -2484,17 +2312,7 @@ func TestServer_TestIDProviderInstanceReduces(t *testing.T) {
|
||||
name := gofakeit.Name()
|
||||
|
||||
// add idp
|
||||
addOIDC, err := AdminClient.AddOIDCIDP(IAMCTX, &admin.AddOIDCIDPRequest{
|
||||
Name: name,
|
||||
StylingType: idp_grpc.IDPStylingType_STYLING_TYPE_GOOGLE,
|
||||
ClientId: "clientID",
|
||||
ClientSecret: "clientSecret",
|
||||
Issuer: "issuer",
|
||||
Scopes: []string{"scope"},
|
||||
DisplayNameMapping: idp.OIDCMappingField_OIDC_MAPPING_FIELD_EMAIL,
|
||||
UsernameMapping: idp.OIDCMappingField_OIDC_MAPPING_FIELD_EMAIL,
|
||||
AutoRegister: true,
|
||||
})
|
||||
addOIDC, err := AdminClient.AddOIDCIDP(IAMCTX, defaultOIDCIDReq(name, true))
|
||||
require.NoError(t, err)
|
||||
|
||||
idpRepo := repository.IDProviderRepository()
|
||||
@@ -2528,3 +2346,76 @@ func TestServer_TestIDProviderInstanceReduces(t *testing.T) {
|
||||
}, retryDuration, tick)
|
||||
})
|
||||
}
|
||||
|
||||
func defaultInstanceSAMLRequest(name string, federatedLogoutEnable *bool) *admin.AddSAMLProviderRequest {
|
||||
return &admin.AddSAMLProviderRequest{
|
||||
Name: name,
|
||||
Metadata: &admin.AddSAMLProviderRequest_MetadataXml{
|
||||
MetadataXml: validSAMLMetadata1,
|
||||
},
|
||||
Binding: idp.SAMLBinding_SAML_BINDING_POST,
|
||||
WithSignedRequest: false,
|
||||
TransientMappingAttributeName: &name,
|
||||
FederatedLogoutEnabled: federatedLogoutEnable,
|
||||
NameIdFormat: idp.SAMLNameIDFormat_SAML_NAME_ID_FORMAT_TRANSIENT.Enum(),
|
||||
ProviderOptions: &idp_grpc.Options{
|
||||
IsLinkingAllowed: false,
|
||||
IsCreationAllowed: false,
|
||||
IsAutoCreation: false,
|
||||
IsAutoUpdate: false,
|
||||
AutoLinking: idp.AutoLinkingOption_AUTO_LINKING_OPTION_EMAIL,
|
||||
},
|
||||
SignatureAlgorithm: idp.SAMLSignatureAlgorithm_SAML_SIGNATURE_RSA_SHA1,
|
||||
}
|
||||
}
|
||||
|
||||
func defaultOIDCIDReq(name string, autoRegister bool) *admin.AddOIDCIDPRequest {
|
||||
return &admin.AddOIDCIDPRequest{
|
||||
Name: name,
|
||||
StylingType: idp_grpc.IDPStylingType_STYLING_TYPE_GOOGLE,
|
||||
ClientId: "clientID",
|
||||
ClientSecret: "clientSecret",
|
||||
Issuer: "issuer",
|
||||
Scopes: []string{"scope"},
|
||||
DisplayNameMapping: idp.OIDCMappingField_OIDC_MAPPING_FIELD_EMAIL,
|
||||
UsernameMapping: idp.OIDCMappingField_OIDC_MAPPING_FIELD_EMAIL,
|
||||
AutoRegister: autoRegister,
|
||||
}
|
||||
}
|
||||
|
||||
func defaultInstanceLDAPRequest(name string) *admin.AddLDAPProviderRequest {
|
||||
return &admin.AddLDAPProviderRequest{
|
||||
Name: name,
|
||||
Servers: []string{"servers"},
|
||||
StartTls: true,
|
||||
BaseDn: "baseDN",
|
||||
BindDn: "bindND",
|
||||
BindPassword: "bindPassword",
|
||||
UserBase: "userBase",
|
||||
UserObjectClasses: []string{"userOhjectClasses"},
|
||||
UserFilters: []string{"userFilters"},
|
||||
Timeout: durationpb.New(time.Minute),
|
||||
Attributes: &idp_grpc.LDAPAttributes{
|
||||
IdAttribute: "idAttribute",
|
||||
FirstNameAttribute: "firstNameAttribute",
|
||||
LastNameAttribute: "lastNameAttribute",
|
||||
DisplayNameAttribute: "displayNameAttribute",
|
||||
NickNameAttribute: "nickNameAttribute",
|
||||
PreferredUsernameAttribute: "preferredUsernameAttribute",
|
||||
EmailAttribute: "emailAttribute",
|
||||
EmailVerifiedAttribute: "emailVerifiedAttribute",
|
||||
PhoneAttribute: "phoneAttribute",
|
||||
PhoneVerifiedAttribute: "phoneVerifiedAttribute",
|
||||
PreferredLanguageAttribute: "preferredLanguageAttribute",
|
||||
AvatarUrlAttribute: "avatarUrlAttribute",
|
||||
ProfileAttribute: "profileAttribute",
|
||||
},
|
||||
ProviderOptions: &idp_grpc.Options{
|
||||
IsLinkingAllowed: false,
|
||||
IsCreationAllowed: false,
|
||||
IsAutoCreation: false,
|
||||
IsAutoUpdate: false,
|
||||
AutoLinking: idp.AutoLinkingOption_AUTO_LINKING_OPTION_EMAIL,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1852,40 +1852,7 @@ func TestServer_TestIDProviderOrgReduces(t *testing.T) {
|
||||
|
||||
// add ldap
|
||||
before := time.Now()
|
||||
addLdap, err := MgmtClient.AddLDAPProvider(IAMCTX, &management.AddLDAPProviderRequest{
|
||||
Name: name,
|
||||
Servers: []string{"servers"},
|
||||
StartTls: true,
|
||||
BaseDn: "baseDN",
|
||||
BindDn: "bindND",
|
||||
BindPassword: "bindPassword",
|
||||
UserBase: "userBase",
|
||||
UserObjectClasses: []string{"userOhjectClasses"},
|
||||
UserFilters: []string{"userFilters"},
|
||||
Timeout: durationpb.New(time.Minute),
|
||||
Attributes: &idp_grpc.LDAPAttributes{
|
||||
IdAttribute: "idAttribute",
|
||||
FirstNameAttribute: "firstNameAttribute",
|
||||
LastNameAttribute: "lastNameAttribute",
|
||||
DisplayNameAttribute: "displayNameAttribute",
|
||||
NickNameAttribute: "nickNameAttribute",
|
||||
PreferredUsernameAttribute: "preferredUsernameAttribute",
|
||||
EmailAttribute: "emailAttribute",
|
||||
EmailVerifiedAttribute: "emailVerifiedAttribute",
|
||||
PhoneAttribute: "phoneAttribute",
|
||||
PhoneVerifiedAttribute: "phoneVerifiedAttribute",
|
||||
PreferredLanguageAttribute: "preferredLanguageAttribute",
|
||||
AvatarUrlAttribute: "avatarUrlAttribute",
|
||||
ProfileAttribute: "profileAttribute",
|
||||
},
|
||||
ProviderOptions: &idp_grpc.Options{
|
||||
IsLinkingAllowed: false,
|
||||
IsCreationAllowed: false,
|
||||
IsAutoCreation: false,
|
||||
IsAutoUpdate: false,
|
||||
AutoLinking: idp.AutoLinkingOption_AUTO_LINKING_OPTION_EMAIL,
|
||||
},
|
||||
})
|
||||
addLdap, err := MgmtClient.AddLDAPProvider(IAMCTX, defaultOrganizationLDAPRequest(name))
|
||||
after := time.Now()
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -1940,40 +1907,7 @@ func TestServer_TestIDProviderOrgReduces(t *testing.T) {
|
||||
name := gofakeit.Name()
|
||||
|
||||
// add ldap
|
||||
addLdap, err := MgmtClient.AddLDAPProvider(IAMCTX, &management.AddLDAPProviderRequest{
|
||||
Name: name,
|
||||
Servers: []string{"servers"},
|
||||
StartTls: true,
|
||||
BaseDn: "baseDN",
|
||||
BindDn: "bindND",
|
||||
BindPassword: "bindPassword",
|
||||
UserBase: "userBase",
|
||||
UserObjectClasses: []string{"userOhjectClasses"},
|
||||
UserFilters: []string{"userFilters"},
|
||||
Timeout: durationpb.New(time.Minute),
|
||||
Attributes: &idp_grpc.LDAPAttributes{
|
||||
IdAttribute: "idAttribute",
|
||||
FirstNameAttribute: "firstNameAttribute",
|
||||
LastNameAttribute: "lastNameAttribute",
|
||||
DisplayNameAttribute: "displayNameAttribute",
|
||||
NickNameAttribute: "nickNameAttribute",
|
||||
PreferredUsernameAttribute: "preferredUsernameAttribute",
|
||||
EmailAttribute: "emailAttribute",
|
||||
EmailVerifiedAttribute: "emailVerifiedAttribute",
|
||||
PhoneAttribute: "phoneAttribute",
|
||||
PhoneVerifiedAttribute: "phoneVerifiedAttribute",
|
||||
PreferredLanguageAttribute: "preferredLanguageAttribute",
|
||||
AvatarUrlAttribute: "avatarUrlAttribute",
|
||||
ProfileAttribute: "profileAttribute",
|
||||
},
|
||||
ProviderOptions: &idp_grpc.Options{
|
||||
IsLinkingAllowed: false,
|
||||
IsCreationAllowed: false,
|
||||
IsAutoCreation: false,
|
||||
IsAutoUpdate: false,
|
||||
AutoLinking: idp.AutoLinkingOption_AUTO_LINKING_OPTION_EMAIL,
|
||||
},
|
||||
})
|
||||
addLdap, err := MgmtClient.AddLDAPProvider(IAMCTX, defaultOrganizationLDAPRequest(name))
|
||||
require.NoError(t, err)
|
||||
|
||||
idpRepo := repository.IDProviderRepository()
|
||||
@@ -2412,3 +2346,40 @@ func TestServer_TestIDProviderOrgReduces(t *testing.T) {
|
||||
}, retryDuration, tick)
|
||||
})
|
||||
}
|
||||
|
||||
func defaultOrganizationLDAPRequest(providerName string) *management.AddLDAPProviderRequest {
|
||||
return &management.AddLDAPProviderRequest{
|
||||
Name: providerName,
|
||||
Servers: []string{"servers"},
|
||||
StartTls: true,
|
||||
BaseDn: "baseDN",
|
||||
BindDn: "bindND",
|
||||
BindPassword: "bindPassword",
|
||||
UserBase: "userBase",
|
||||
UserObjectClasses: []string{"userOhjectClasses"},
|
||||
UserFilters: []string{"userFilters"},
|
||||
Timeout: durationpb.New(time.Minute),
|
||||
Attributes: &idp_grpc.LDAPAttributes{
|
||||
IdAttribute: "idAttribute",
|
||||
FirstNameAttribute: "firstNameAttribute",
|
||||
LastNameAttribute: "lastNameAttribute",
|
||||
DisplayNameAttribute: "displayNameAttribute",
|
||||
NickNameAttribute: "nickNameAttribute",
|
||||
PreferredUsernameAttribute: "preferredUsernameAttribute",
|
||||
EmailAttribute: "emailAttribute",
|
||||
EmailVerifiedAttribute: "emailVerifiedAttribute",
|
||||
PhoneAttribute: "phoneAttribute",
|
||||
PhoneVerifiedAttribute: "phoneVerifiedAttribute",
|
||||
PreferredLanguageAttribute: "preferredLanguageAttribute",
|
||||
AvatarUrlAttribute: "avatarUrlAttribute",
|
||||
ProfileAttribute: "profileAttribute",
|
||||
},
|
||||
ProviderOptions: &idp_grpc.Options{
|
||||
IsLinkingAllowed: false,
|
||||
IsCreationAllowed: false,
|
||||
IsAutoCreation: false,
|
||||
IsAutoUpdate: false,
|
||||
AutoLinking: idp.AutoLinkingOption_AUTO_LINKING_OPTION_EMAIL,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,396 @@
|
||||
//go:build integration
|
||||
|
||||
package events_test
|
||||
|
||||
// TODO(adlerhurst): Uncomment once users table is created and FK can be enforced
|
||||
|
||||
// import (
|
||||
// "net"
|
||||
// "net/url"
|
||||
// "testing"
|
||||
// "time"
|
||||
|
||||
// "github.com/brianvoe/gofakeit/v6"
|
||||
// "github.com/muhlemmer/gu"
|
||||
// "github.com/stretchr/testify/assert"
|
||||
// "github.com/stretchr/testify/require"
|
||||
// durationpb "google.golang.org/protobuf/types/known/durationpb"
|
||||
|
||||
// "github.com/zitadel/zitadel/backend/v3/domain"
|
||||
// "github.com/zitadel/zitadel/backend/v3/storage/database"
|
||||
// "github.com/zitadel/zitadel/backend/v3/storage/database/repository"
|
||||
// "github.com/zitadel/zitadel/internal/idp/providers/ldap"
|
||||
// "github.com/zitadel/zitadel/internal/integration"
|
||||
// "github.com/zitadel/zitadel/internal/integration/sink"
|
||||
// "github.com/zitadel/zitadel/pkg/grpc/admin"
|
||||
// "github.com/zitadel/zitadel/pkg/grpc/session/v2"
|
||||
// "github.com/zitadel/zitadel/pkg/grpc/user/v2"
|
||||
// )
|
||||
|
||||
// func TestServer_IDPIntentReduces(t *testing.T) {
|
||||
// retryDuration, tick := integration.WaitForAndTickWithMaxDuration(IAMCTX, time.Minute)
|
||||
|
||||
// idpIntentRepo := repository.IDPIntentRepository()
|
||||
// instanceID := Instance.ID()
|
||||
|
||||
// t.Run("start LDAP ID provider intent should create intent and succeed", func(t *testing.T) {
|
||||
// ldapProvider, err := AdminClient.AddLDAPProvider(IAMCTX, defaultInstanceLDAPRequest(gofakeit.Name()))
|
||||
// require.NoError(t, err)
|
||||
|
||||
// req := &user.StartIdentityProviderIntentRequest{
|
||||
// IdpId: ldapProvider.GetId(),
|
||||
// Content: &user.StartIdentityProviderIntentRequest_Ldap{
|
||||
// Ldap: &user.LDAPCredentials{
|
||||
// Username: "some-user",
|
||||
// Password: "some-pass",
|
||||
// },
|
||||
// },
|
||||
// }
|
||||
// res, err := UserClient.StartIdentityProviderIntent(IAMCTX, req)
|
||||
// require.NoError(t, err)
|
||||
// idpIntent, ok := res.GetNextStep().(*user.StartIdentityProviderIntentResponse_IdpIntent)
|
||||
// require.True(t, ok)
|
||||
// t.Cleanup(func() {
|
||||
// idpIntentRepo.Delete(IAMCTX, pool, idpIntentRepo.PrimaryKeyCondition(instanceID, idpIntent.IdpIntent.GetIdpIntentId()))
|
||||
// AdminClient.RemoveIDP(IAMCTX, &admin.RemoveIDPRequest{IdpId: ldapProvider.GetId()})
|
||||
// })
|
||||
// require.EventuallyWithT(t, func(collect *assert.CollectT) {
|
||||
// retrievedRelationalIntent, err := idpIntentRepo.Get(
|
||||
// IAMCTX,
|
||||
// pool,
|
||||
// database.WithCondition(database.And(
|
||||
// idpIntentRepo.PrimaryKeyCondition(instanceID, idpIntent.IdpIntent.GetIdpIntentId()),
|
||||
// idpIntentRepo.StateCondition(domain.IDPIntentStateSucceeded),
|
||||
// )),
|
||||
// )
|
||||
// require.NoError(collect, err)
|
||||
// assert.Equal(collect, domain.IDPIntentStateSucceeded, retrievedRelationalIntent.State)
|
||||
// assert.Empty(collect, retrievedRelationalIntent.SuccessURL)
|
||||
// assert.Empty(collect, retrievedRelationalIntent.FailureURL)
|
||||
// assert.Equal(collect, ldapProvider.GetId(), retrievedRelationalIntent.IDPID)
|
||||
// assert.Nil(collect, retrievedRelationalIntent.IDPArguments)
|
||||
// assert.NotEmpty(collect, retrievedRelationalIntent.IDPUser)
|
||||
// assert.NotEmpty(collect, retrievedRelationalIntent.IDPUserID)
|
||||
// assert.NotEmpty(collect, retrievedRelationalIntent.EntryAttributes)
|
||||
// assert.True(collect, retrievedRelationalIntent.ExpiresAt.IsZero())
|
||||
// }, retryDuration, tick)
|
||||
// })
|
||||
|
||||
// t.Run("oidc intent flow started with URLs should create intent in started state", func(t *testing.T) {
|
||||
// idpCreateReq := defaultOIDCIDReq(gofakeit.Name(), true)
|
||||
// idpCreateReq.Issuer = "http://localhost:8082"
|
||||
// oidcIDP, err := AdminClient.AddOIDCIDP(IAMCTX, idpCreateReq)
|
||||
// require.NoError(t, err)
|
||||
|
||||
// req := &user.StartIdentityProviderIntentRequest{
|
||||
// IdpId: oidcIDP.GetIdpId(),
|
||||
// Content: &user.StartIdentityProviderIntentRequest_Urls{
|
||||
// Urls: &user.RedirectURLs{
|
||||
// SuccessUrl: "https://localhost:8081/success",
|
||||
// FailureUrl: "https://localhost:8081/fail",
|
||||
// },
|
||||
// },
|
||||
// }
|
||||
// res, err := UserClient.StartIdentityProviderIntent(IAMCTX, req)
|
||||
// require.NoError(t, err)
|
||||
// authURL, ok := res.GetNextStep().(*user.StartIdentityProviderIntentResponse_AuthUrl)
|
||||
// require.True(t, ok)
|
||||
// parsedAuthURL, err := url.Parse(authURL.AuthUrl)
|
||||
// require.NoError(t, err)
|
||||
// idpIntentID := parsedAuthURL.Query().Get("state")
|
||||
// t.Cleanup(func() {
|
||||
// idpIntentRepo.Delete(IAMCTX, pool, idpIntentRepo.PrimaryKeyCondition(instanceID, idpIntentID))
|
||||
// AdminClient.RemoveIDP(IAMCTX, &admin.RemoveIDPRequest{IdpId: oidcIDP.GetIdpId()})
|
||||
// })
|
||||
// require.EventuallyWithT(t, func(collect *assert.CollectT) {
|
||||
// retrievedRelationalIntent, err := idpIntentRepo.Get(
|
||||
// IAMCTX,
|
||||
// pool,
|
||||
// database.WithCondition(idpIntentRepo.PrimaryKeyCondition(instanceID, idpIntentID)),
|
||||
// )
|
||||
// require.NoError(collect, err)
|
||||
// assert.Equal(collect, domain.IDPIntentStateStarted, retrievedRelationalIntent.State)
|
||||
// assert.Equal(collect, "https://localhost:8081/success", retrievedRelationalIntent.SuccessURL.String())
|
||||
// assert.Equal(collect, "https://localhost:8081/fail", retrievedRelationalIntent.FailureURL.String())
|
||||
// assert.Empty(collect, retrievedRelationalIntent.IDPArguments)
|
||||
// }, retryDuration, tick)
|
||||
|
||||
// })
|
||||
|
||||
// t.Run("when successful oidc intent should update intent state to succeeded", func(t *testing.T) {
|
||||
// beforeCreate := time.Now()
|
||||
// idpCreateReq := defaultOIDCIDReq(gofakeit.Name(), true)
|
||||
// idpCreateReq.Issuer = "http://localhost:8082"
|
||||
// oidcIDP, err := AdminClient.AddOIDCIDP(IAMCTX, idpCreateReq)
|
||||
// require.NoError(t, err)
|
||||
|
||||
// expiryDate := time.Now().Add(time.Minute * 30)
|
||||
// idpIntentID, _, _, _, err := sink.SuccessfulOIDCIntent(instanceID, oidcIDP.GetIdpId(), "some-idp-user", "some-user", expiryDate)
|
||||
// require.NoError(t, err)
|
||||
// afterSuccess := time.Now()
|
||||
// t.Cleanup(func() {
|
||||
// idpIntentRepo.Delete(IAMCTX, pool, idpIntentRepo.PrimaryKeyCondition(instanceID, idpIntentID))
|
||||
// AdminClient.RemoveIDP(IAMCTX, &admin.RemoveIDPRequest{IdpId: oidcIDP.GetIdpId()})
|
||||
// })
|
||||
// require.EventuallyWithT(t, func(collect *assert.CollectT) {
|
||||
// retrievedRelationalIntent, err := idpIntentRepo.Get(
|
||||
// IAMCTX,
|
||||
// pool,
|
||||
// database.WithCondition(idpIntentRepo.PrimaryKeyCondition(instanceID, idpIntentID)),
|
||||
// )
|
||||
// require.NoError(collect, err)
|
||||
// assert.Equal(collect, domain.IDPIntentStateSucceeded, retrievedRelationalIntent.State)
|
||||
// assert.NotEmpty(collect, retrievedRelationalIntent.IDPUser)
|
||||
// assert.Equal(collect, "some-idp-user", retrievedRelationalIntent.IDPUserID)
|
||||
// assert.Equal(collect, "username", retrievedRelationalIntent.IDPUsername)
|
||||
// assert.Equal(collect, "some-user", retrievedRelationalIntent.UserID)
|
||||
// assert.NotEmpty(collect, retrievedRelationalIntent.IDPAccessToken)
|
||||
// assert.Equal(collect, "idToken", retrievedRelationalIntent.IDPIDToken)
|
||||
// require.NotNil(collect, retrievedRelationalIntent.SucceededAt)
|
||||
// assert.WithinRange(collect, *retrievedRelationalIntent.SucceededAt, beforeCreate, afterSuccess)
|
||||
// require.NotNil(collect, retrievedRelationalIntent.ExpiresAt)
|
||||
// assert.WithinRange(collect, *retrievedRelationalIntent.ExpiresAt, expiryDate.Add(time.Millisecond*-5), expiryDate.Add(time.Second*1))
|
||||
// }, retryDuration, tick)
|
||||
|
||||
// })
|
||||
|
||||
// t.Run("when successful SAML intent should update intent state to succeeded", func(t *testing.T) {
|
||||
// beforeCreate := time.Now()
|
||||
// samlIDP, err := AdminClient.AddSAMLProvider(IAMCTX, defaultInstanceSAMLRequest(gofakeit.Name(), gu.Ptr(true)))
|
||||
// require.NoError(t, err)
|
||||
|
||||
// expiryDate := time.Now().Add(time.Minute * 30)
|
||||
// idpIntentID, _, _, _, err := sink.SuccessfulSAMLIntent(instanceID, samlIDP.GetId(), "some-idp-user", "some-user", "", expiryDate)
|
||||
// require.NoError(t, err)
|
||||
// afterSuccess := time.Now()
|
||||
// t.Cleanup(func() {
|
||||
// idpIntentRepo.Delete(IAMCTX, pool, idpIntentRepo.PrimaryKeyCondition(instanceID, idpIntentID))
|
||||
// AdminClient.RemoveIDP(IAMCTX, &admin.RemoveIDPRequest{IdpId: samlIDP.GetId()})
|
||||
// })
|
||||
// require.EventuallyWithT(t, func(collect *assert.CollectT) {
|
||||
// retrievedRelationalIntent, err := idpIntentRepo.Get(
|
||||
// IAMCTX,
|
||||
// pool,
|
||||
// database.WithCondition(idpIntentRepo.PrimaryKeyCondition(instanceID, idpIntentID)),
|
||||
// )
|
||||
// require.NoError(collect, err)
|
||||
// assert.Equal(collect, domain.IDPIntentStateSucceeded, retrievedRelationalIntent.State)
|
||||
// assert.NotEmpty(collect, retrievedRelationalIntent.IDPUser)
|
||||
// assert.Equal(collect, "some-idp-user", retrievedRelationalIntent.IDPUserID)
|
||||
// assert.Empty(collect, retrievedRelationalIntent.IDPUsername)
|
||||
// assert.Equal(collect, "some-user", retrievedRelationalIntent.UserID)
|
||||
// assert.NotEmpty(collect, retrievedRelationalIntent.Assertion)
|
||||
// require.NotNil(collect, retrievedRelationalIntent.SucceededAt)
|
||||
// assert.WithinRange(collect, *retrievedRelationalIntent.SucceededAt, beforeCreate, afterSuccess)
|
||||
// require.NotNil(collect, retrievedRelationalIntent.ExpiresAt)
|
||||
// assert.WithinRange(collect, *retrievedRelationalIntent.ExpiresAt, expiryDate.Add(time.Millisecond*-5), expiryDate.Add(time.Second*1))
|
||||
|
||||
// }, retryDuration, tick)
|
||||
// })
|
||||
|
||||
// t.Run("when emitting SAMLRequest event should update intent with request ID", func(t *testing.T) {
|
||||
// samlIDP, err := AdminClient.AddSAMLProvider(IAMCTX, defaultInstanceSAMLRequest(gofakeit.Name(), gu.Ptr(true)))
|
||||
// require.NoError(t, err)
|
||||
|
||||
// expiry := time.Now().Add(time.Hour * 1)
|
||||
// req := &user.StartIdentityProviderIntentRequest{
|
||||
// IdpId: samlIDP.GetId(),
|
||||
// Content: &user.StartIdentityProviderIntentRequest_Urls{
|
||||
// Urls: &user.RedirectURLs{
|
||||
// SuccessUrl: "https://localhost:8081/success",
|
||||
// FailureUrl: "https://localhost:8081/fail",
|
||||
// },
|
||||
// },
|
||||
// }
|
||||
// res, err := UserClient.StartIdentityProviderIntent(IAMCTX, req)
|
||||
// require.NoError(t, err)
|
||||
// formData, ok := res.GetNextStep().(*user.StartIdentityProviderIntentResponse_FormData)
|
||||
// require.True(t, ok)
|
||||
|
||||
// idpIntentID, ok := formData.FormData.GetFields()["RelayState"]
|
||||
// require.True(t, ok)
|
||||
|
||||
// t.Cleanup(func() {
|
||||
// idpIntentRepo.Delete(IAMCTX, pool, idpIntentRepo.PrimaryKeyCondition(instanceID, idpIntentID))
|
||||
// AdminClient.RemoveIDP(IAMCTX, &admin.RemoveIDPRequest{IdpId: samlIDP.GetId()})
|
||||
// })
|
||||
|
||||
// idpIntentID, token, _, _, err := sink.SuccessfulSAMLIntent(instanceID, samlIDP.GetId(), "idp-user-id", "some-user", idpIntentID, expiry)
|
||||
// require.NoError(t, err)
|
||||
|
||||
// _, err = UserClient.RetrieveIdentityProviderIntent(IAMCTX, &user.RetrieveIdentityProviderIntentRequest{
|
||||
// IdpIntentId: idpIntentID,
|
||||
// IdpIntentToken: token,
|
||||
// })
|
||||
// require.NoError(t, err)
|
||||
// require.EventuallyWithT(t, func(collect *assert.CollectT) {
|
||||
// retrievedRelationalIntent, err := idpIntentRepo.Get(
|
||||
// IAMCTX,
|
||||
// pool,
|
||||
// database.WithCondition(database.And(
|
||||
// idpIntentRepo.PrimaryKeyCondition(instanceID, idpIntentID),
|
||||
// )),
|
||||
// )
|
||||
// require.NoError(collect, err)
|
||||
// assert.NotEmpty(collect, retrievedRelationalIntent.RequestID)
|
||||
// }, retryDuration, tick)
|
||||
|
||||
// })
|
||||
|
||||
// t.Run("when LDAP intent flow fails should update intent state and reason", func(t *testing.T) {
|
||||
// beforeCreate := time.Now()
|
||||
// ldapProvider, err := AdminClient.AddLDAPProvider(IAMCTX, defaultInstanceLDAPRequest(gofakeit.Name()))
|
||||
// require.NoError(t, err)
|
||||
|
||||
// req := &user.StartIdentityProviderIntentRequest{
|
||||
// IdpId: ldapProvider.GetId(),
|
||||
// Content: &user.StartIdentityProviderIntentRequest_Ldap{
|
||||
// Ldap: &user.LDAPCredentials{
|
||||
// Username: ldap.FailingUser,
|
||||
// Password: ldap.FailingPsw,
|
||||
// },
|
||||
// },
|
||||
// }
|
||||
// _, err = UserClient.StartIdentityProviderIntent(IAMCTX, req)
|
||||
// require.Error(t, err)
|
||||
// afterFail := time.Now()
|
||||
|
||||
// var idpIntentID string
|
||||
// t.Cleanup(func() {
|
||||
// idpIntentRepo.Delete(IAMCTX, pool, idpIntentRepo.PrimaryKeyCondition(instanceID, idpIntentID))
|
||||
// AdminClient.RemoveIDP(IAMCTX, &admin.RemoveIDPRequest{IdpId: ldapProvider.GetId()})
|
||||
// })
|
||||
|
||||
// require.EventuallyWithT(t, func(collect *assert.CollectT) {
|
||||
// retrievedRelationalIntent, err := idpIntentRepo.Get(
|
||||
// IAMCTX,
|
||||
// pool,
|
||||
// database.WithCondition(database.And(
|
||||
// idpIntentRepo.InstanceIDCondition(instanceID),
|
||||
// idpIntentRepo.StateCondition(domain.IDPIntentStateFailed),
|
||||
// )),
|
||||
// database.WithOrderByDescending(idpIntentRepo.CreatedAtColumn()),
|
||||
// )
|
||||
// require.NoError(collect, err)
|
||||
// assert.Equal(collect, domain.IDPIntentStateFailed, retrievedRelationalIntent.State)
|
||||
// assert.NotEmpty(collect, retrievedRelationalIntent.FailReason)
|
||||
// require.NotNil(collect, retrievedRelationalIntent.FailedAt)
|
||||
// assert.WithinRange(collect, *retrievedRelationalIntent.FailedAt, beforeCreate, afterFail)
|
||||
// idpIntentID = retrievedRelationalIntent.ID
|
||||
// }, retryDuration, tick)
|
||||
// })
|
||||
|
||||
// t.Run("when session update checks intent should delete intent", func(t *testing.T) {
|
||||
// // ==========
|
||||
// // GIVEN
|
||||
// // ==========
|
||||
|
||||
// // Create a user
|
||||
// email := integration.Email()
|
||||
// testUser := Instance.CreateUserTypeHuman(IAMCTX, email)
|
||||
// _, err := UserClient.VerifyEmail(IAMCTX, &user.VerifyEmailRequest{
|
||||
// UserId: testUser.GetId(),
|
||||
// VerificationCode: testUser.GetEmailCode(),
|
||||
// })
|
||||
// require.NoError(t, err)
|
||||
// _, err = UserClient.SetPassword(CTX, &user.SetPasswordRequest{
|
||||
// UserId: testUser.GetId(),
|
||||
// NewPassword: &user.Password{
|
||||
// Password: integration.UserPassword,
|
||||
// },
|
||||
// Verification: nil,
|
||||
// })
|
||||
// require.NoError(t, err)
|
||||
|
||||
// // Create an ID Provider
|
||||
// idpID := Instance.AddGenericOAuthProvider(IAMCTX, integration.IDPName()).GetId()
|
||||
|
||||
// // Link the user with the provider
|
||||
// idpUserID := integration.ID()
|
||||
// _, err = UserClient.AddIDPLink(IAMCTX, &user.AddIDPLinkRequest{
|
||||
// UserId: testUser.GetId(),
|
||||
// IdpLink: &user.IDPLink{
|
||||
// IdpId: idpID,
|
||||
// UserId: idpUserID,
|
||||
// UserName: integration.Username(),
|
||||
// },
|
||||
// })
|
||||
// require.NoError(t, err)
|
||||
|
||||
// // Create an IDP intent
|
||||
// _, err = url.Parse(Instance.CreateIntent(CTX, idpID).GetAuthUrl())
|
||||
// require.NoError(t, err)
|
||||
// expiry := time.Now().Add(1 * time.Hour)
|
||||
|
||||
// // Fake a successful intent flow
|
||||
// intentID, intentToken, _, _, err := sink.SuccessfulOAuthIntent(Instance.ID(), idpID, idpUserID, testUser.GetId(), expiry)
|
||||
// require.NoError(t, err)
|
||||
|
||||
// // Create a session for the user
|
||||
// createdSessionID := createSessionForUser(t)
|
||||
// require.NoError(t, err)
|
||||
|
||||
// // ==========
|
||||
// // TEST
|
||||
// // ==========
|
||||
|
||||
// // Update the session with user, password and IPDIntent checks
|
||||
// _, err = SessionClient.SetSession(IAMCTX, &session.SetSessionRequest{
|
||||
// SessionId: createdSessionID,
|
||||
// Checks: &session.Checks{
|
||||
// User: &session.CheckUser{
|
||||
// Search: &session.CheckUser_LoginName{
|
||||
// LoginName: email,
|
||||
// },
|
||||
// },
|
||||
// Password: &session.CheckPassword{
|
||||
// Password: integration.UserPassword,
|
||||
// },
|
||||
// IdpIntent: &session.CheckIDPIntent{
|
||||
// IdpIntentId: intentID,
|
||||
// IdpIntentToken: intentToken,
|
||||
// },
|
||||
// },
|
||||
// })
|
||||
// require.NoError(t, err)
|
||||
|
||||
// // ==========
|
||||
// // VERIFY
|
||||
// // ==========
|
||||
// t.Cleanup(func() {
|
||||
// idpIntentRepo.Delete(IAMCTX, pool, idpIntentRepo.PrimaryKeyCondition(instanceID, intentID))
|
||||
// AdminClient.RemoveIDP(IAMCTX, &admin.RemoveIDPRequest{IdpId: idpID})
|
||||
// SessionClient.DeleteSession(IAMCTX, &session.DeleteSessionRequest{SessionId: createdSessionID})
|
||||
// })
|
||||
|
||||
// require.EventuallyWithT(t, func(collect *assert.CollectT) {
|
||||
// _, err := idpIntentRepo.Get(
|
||||
// IAMCTX,
|
||||
// pool,
|
||||
// database.WithCondition(idpIntentRepo.PrimaryKeyCondition(instanceID, intentID)),
|
||||
// )
|
||||
// assert.Error(collect, err)
|
||||
// }, retryDuration, tick)
|
||||
// })
|
||||
// }
|
||||
|
||||
// func createSessionForUser(t *testing.T) (sessionID string) {
|
||||
// t.Helper()
|
||||
// lifetime := time.Hour
|
||||
// createdSession, err := SessionClient.CreateSession(CTX, &session.CreateSessionRequest{
|
||||
// Checks: nil,
|
||||
// Metadata: nil,
|
||||
// Challenges: nil,
|
||||
// UserAgent: &session.UserAgent{
|
||||
// FingerprintId: gu.Ptr(integration.ID()),
|
||||
// Ip: gu.Ptr(net.IPv4(127, 0, 0, 1).String()),
|
||||
// Description: gu.Ptr("description"),
|
||||
// Header: map[string]*session.UserAgent_HeaderValues{
|
||||
// "User-Agent": {Values: []string{"ZITADEL-Integration-Test"}},
|
||||
// },
|
||||
// },
|
||||
// Lifetime: durationpb.New(lifetime),
|
||||
// })
|
||||
// require.NoError(t, err)
|
||||
// return createdSession.GetSessionId()
|
||||
// }
|
||||
@@ -28,19 +28,7 @@ func TestCreateIDProvider(t *testing.T) {
|
||||
}()
|
||||
|
||||
// create instance
|
||||
instanceId := gofakeit.Name()
|
||||
instance := domain.Instance{
|
||||
ID: instanceId,
|
||||
Name: gofakeit.Name(),
|
||||
DefaultOrgID: "defaultOrgId",
|
||||
IAMProjectID: "iamProject",
|
||||
ConsoleClientID: "managementConsoleCLient",
|
||||
ConsoleAppID: "managementConsoleApp",
|
||||
DefaultLanguage: "defaultLanguage",
|
||||
}
|
||||
instanceRepo := repository.InstanceRepository()
|
||||
err = instanceRepo.Create(t.Context(), tx, &instance)
|
||||
require.NoError(t, err)
|
||||
instanceId := createInstance(t, tx)
|
||||
|
||||
// create org
|
||||
orgId := gofakeit.Name()
|
||||
@@ -61,7 +49,6 @@ func TestCreateIDProvider(t *testing.T) {
|
||||
err error
|
||||
}
|
||||
|
||||
// TESTS
|
||||
tests := []test{
|
||||
{
|
||||
name: "happy path",
|
||||
@@ -83,11 +70,10 @@ func TestCreateIDProvider(t *testing.T) {
|
||||
{
|
||||
name: "create idp without name",
|
||||
idp: domain.IdentityProvider{
|
||||
InstanceID: instanceId,
|
||||
OrgID: &orgId,
|
||||
ID: gofakeit.Name(),
|
||||
State: domain.IDPStateActive,
|
||||
// Name: gofakeit.Name(),
|
||||
InstanceID: instanceId,
|
||||
OrgID: &orgId,
|
||||
ID: gofakeit.Name(),
|
||||
State: domain.IDPStateActive,
|
||||
Type: gu.Ptr(domain.IDPTypeOIDC),
|
||||
AllowCreation: true,
|
||||
AllowAutoCreation: true,
|
||||
@@ -231,9 +217,8 @@ func TestCreateIDProvider(t *testing.T) {
|
||||
{
|
||||
name: "adding idp with no id",
|
||||
idp: domain.IdentityProvider{
|
||||
InstanceID: instanceId,
|
||||
OrgID: &orgId,
|
||||
// ID: gofakeit.Name(),
|
||||
InstanceID: instanceId,
|
||||
OrgID: &orgId,
|
||||
State: domain.IDPStateActive,
|
||||
Name: gofakeit.Name(),
|
||||
Type: gu.Ptr(domain.IDPTypeOIDC),
|
||||
@@ -249,11 +234,10 @@ func TestCreateIDProvider(t *testing.T) {
|
||||
{
|
||||
name: "adding idp with no name",
|
||||
idp: domain.IdentityProvider{
|
||||
InstanceID: instanceId,
|
||||
OrgID: &orgId,
|
||||
ID: gofakeit.Name(),
|
||||
State: domain.IDPStateActive,
|
||||
// Name: gofakeit.Name(),
|
||||
InstanceID: instanceId,
|
||||
OrgID: &orgId,
|
||||
ID: gofakeit.Name(),
|
||||
State: domain.IDPStateActive,
|
||||
Type: gu.Ptr(domain.IDPTypeOIDC),
|
||||
AllowCreation: true,
|
||||
AllowAutoCreation: true,
|
||||
@@ -267,7 +251,6 @@ func TestCreateIDProvider(t *testing.T) {
|
||||
{
|
||||
name: "adding idp with no instance id",
|
||||
idp: domain.IdentityProvider{
|
||||
// InstanceID: instanceId,
|
||||
OrgID: &orgId,
|
||||
State: domain.IDPStateActive,
|
||||
Name: gofakeit.Name(),
|
||||
@@ -384,31 +367,10 @@ func TestUpdateIDProvider(t *testing.T) {
|
||||
}()
|
||||
|
||||
// create instance
|
||||
instanceId := gofakeit.Name()
|
||||
instance := domain.Instance{
|
||||
ID: instanceId,
|
||||
Name: gofakeit.Name(),
|
||||
DefaultOrgID: "defaultOrgId",
|
||||
IAMProjectID: "iamProject",
|
||||
ConsoleClientID: "managementConsoleCLient",
|
||||
ConsoleAppID: "managementConsoleApp",
|
||||
DefaultLanguage: "defaultLanguage",
|
||||
}
|
||||
instanceRepo := repository.InstanceRepository()
|
||||
err = instanceRepo.Create(t.Context(), tx, &instance)
|
||||
require.NoError(t, err)
|
||||
instanceId := createInstance(t, tx)
|
||||
|
||||
// create org
|
||||
orgId := gofakeit.Name()
|
||||
org := domain.Organization{
|
||||
ID: orgId,
|
||||
Name: gofakeit.Name(),
|
||||
InstanceID: instanceId,
|
||||
State: domain.OrgStateActive,
|
||||
}
|
||||
organizationRepo := repository.OrganizationRepository()
|
||||
err = organizationRepo.Create(t.Context(), tx, &org)
|
||||
require.NoError(t, err)
|
||||
orgId := createOrganization(t, tx, instanceId)
|
||||
|
||||
idpRepo := repository.IDProviderRepository()
|
||||
|
||||
@@ -669,44 +631,12 @@ func TestGetIDProvider(t *testing.T) {
|
||||
}
|
||||
}()
|
||||
|
||||
// create instance
|
||||
instanceId := gofakeit.Name()
|
||||
instance := domain.Instance{
|
||||
ID: instanceId,
|
||||
Name: gofakeit.Name(),
|
||||
DefaultOrgID: "defaultOrgId",
|
||||
IAMProjectID: "iamProject",
|
||||
ConsoleClientID: "managementConsoleCLient",
|
||||
ConsoleAppID: "managementConsoleApp",
|
||||
DefaultLanguage: "defaultLanguage",
|
||||
}
|
||||
instanceRepo := repository.InstanceRepository()
|
||||
err = instanceRepo.Create(t.Context(), tx, &instance)
|
||||
require.NoError(t, err)
|
||||
instanceId := createInstance(t, tx)
|
||||
orgId := createOrganization(t, tx, instanceId)
|
||||
|
||||
// create org
|
||||
orgId := gofakeit.Name()
|
||||
org := domain.Organization{
|
||||
ID: orgId,
|
||||
Name: gofakeit.Name(),
|
||||
InstanceID: instanceId,
|
||||
State: domain.OrgStateActive,
|
||||
}
|
||||
organizationRepo := repository.OrganizationRepository()
|
||||
err = organizationRepo.Create(t.Context(), tx, &org)
|
||||
require.NoError(t, err)
|
||||
|
||||
// create organization
|
||||
// this org is created as an additional org which should NOT
|
||||
// be returned in the results of the tests
|
||||
preexistingOrg := domain.Organization{
|
||||
ID: gofakeit.Name(),
|
||||
Name: gofakeit.Name(),
|
||||
InstanceID: instanceId,
|
||||
State: domain.OrgStateActive,
|
||||
}
|
||||
err = organizationRepo.Create(t.Context(), tx, &preexistingOrg)
|
||||
require.NoError(t, err)
|
||||
createOrganization(t, tx, instanceId)
|
||||
|
||||
idpRepo := repository.IDProviderRepository()
|
||||
type test struct {
|
||||
@@ -955,31 +885,10 @@ func TestListIDProvider(t *testing.T) {
|
||||
}()
|
||||
|
||||
// create instance
|
||||
instanceId := gofakeit.Name()
|
||||
instance := domain.Instance{
|
||||
ID: instanceId,
|
||||
Name: gofakeit.Name(),
|
||||
DefaultOrgID: "defaultOrgId",
|
||||
IAMProjectID: "iamProject",
|
||||
ConsoleClientID: "managementConsoleCLient",
|
||||
ConsoleAppID: "managementConsoleApp",
|
||||
DefaultLanguage: "defaultLanguage",
|
||||
}
|
||||
instanceRepo := repository.InstanceRepository()
|
||||
err = instanceRepo.Create(t.Context(), tx, &instance)
|
||||
require.NoError(t, err)
|
||||
instanceId := createInstance(t, tx)
|
||||
|
||||
// create org
|
||||
orgId := gofakeit.Name()
|
||||
org := domain.Organization{
|
||||
ID: orgId,
|
||||
Name: gofakeit.Name(),
|
||||
InstanceID: instanceId,
|
||||
State: domain.OrgStateActive,
|
||||
}
|
||||
organizationRepo := repository.OrganizationRepository()
|
||||
err = organizationRepo.Create(t.Context(), tx, &org)
|
||||
require.NoError(t, err)
|
||||
orgId := createOrganization(t, tx, instanceId)
|
||||
|
||||
idpRepo := repository.IDProviderRepository()
|
||||
|
||||
@@ -994,30 +903,10 @@ func TestListIDProvider(t *testing.T) {
|
||||
name: "multiple idps filter on instance",
|
||||
testFunc: func(t *testing.T) []*domain.IdentityProvider {
|
||||
// create instance
|
||||
newInstanceId := gofakeit.Name()
|
||||
instance := domain.Instance{
|
||||
ID: newInstanceId,
|
||||
Name: gofakeit.Name(),
|
||||
DefaultOrgID: "defaultOrgId",
|
||||
IAMProjectID: "iamProject",
|
||||
ConsoleClientID: "managementConsoleCLient",
|
||||
ConsoleAppID: "managementConsoleApp",
|
||||
DefaultLanguage: "defaultLanguage",
|
||||
}
|
||||
err = instanceRepo.Create(t.Context(), tx, &instance)
|
||||
require.NoError(t, err)
|
||||
newInstanceId := createInstance(t, tx)
|
||||
|
||||
// create org
|
||||
newOrgId := gofakeit.Name()
|
||||
org := domain.Organization{
|
||||
ID: newOrgId,
|
||||
Name: gofakeit.Name(),
|
||||
InstanceID: newInstanceId,
|
||||
State: domain.OrgStateActive,
|
||||
}
|
||||
organizationRepo := repository.OrganizationRepository()
|
||||
err = organizationRepo.Create(t.Context(), tx, &org)
|
||||
require.NoError(t, err)
|
||||
newOrgId := createOrganization(t, tx, newInstanceId)
|
||||
|
||||
// create idp
|
||||
// this idp is created as an additional idp which should NOT
|
||||
@@ -1785,31 +1674,10 @@ func TestDeleteIDProvider(t *testing.T) {
|
||||
}()
|
||||
|
||||
// create instance
|
||||
instanceId := gofakeit.Name()
|
||||
instance := domain.Instance{
|
||||
ID: instanceId,
|
||||
Name: gofakeit.Name(),
|
||||
DefaultOrgID: "defaultOrgId",
|
||||
IAMProjectID: "iamProject",
|
||||
ConsoleClientID: "managementConsoleCLient",
|
||||
ConsoleAppID: "managementConsoleApp",
|
||||
DefaultLanguage: "defaultLanguage",
|
||||
}
|
||||
instanceRepo := repository.InstanceRepository()
|
||||
err = instanceRepo.Create(t.Context(), tx, &instance)
|
||||
require.NoError(t, err)
|
||||
instanceId := createInstance(t, tx)
|
||||
|
||||
// create org
|
||||
orgId := gofakeit.Name()
|
||||
org := domain.Organization{
|
||||
ID: orgId,
|
||||
Name: gofakeit.Name(),
|
||||
InstanceID: instanceId,
|
||||
State: domain.OrgStateActive,
|
||||
}
|
||||
organizationRepo := repository.OrganizationRepository()
|
||||
err = organizationRepo.Create(t.Context(), tx, &org)
|
||||
require.NoError(t, err)
|
||||
orgId := createOrganization(t, tx, instanceId)
|
||||
|
||||
idpRepo := repository.IDProviderRepository()
|
||||
|
||||
|
||||
@@ -0,0 +1,442 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/muhlemmer/gu"
|
||||
|
||||
"github.com/zitadel/zitadel/backend/v3/domain"
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/database"
|
||||
)
|
||||
|
||||
type idpIntentRepository struct{}
|
||||
|
||||
func (i idpIntentRepository) qualifiedTableName() string {
|
||||
return "zitadel.identity_provider_intents"
|
||||
}
|
||||
|
||||
func (i idpIntentRepository) unqualifiedTableName() string {
|
||||
return "identity_provider_intents"
|
||||
}
|
||||
|
||||
func IDPIntentRepository() domain.IDPIntentRepository {
|
||||
return new(idpIntentRepository)
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// repository
|
||||
// -------------------------------------------------------------
|
||||
|
||||
const queryIDPIntentStmt = `
|
||||
SELECT
|
||||
identity_provider_intents.instance_id
|
||||
, identity_provider_intents.id
|
||||
, identity_provider_intents.state
|
||||
, identity_provider_intents.success_url
|
||||
, identity_provider_intents.failure_url
|
||||
, identity_provider_intents.created_at
|
||||
, identity_provider_intents.updated_at
|
||||
, identity_provider_intents.idp_id
|
||||
, identity_provider_intents.idp_arguments
|
||||
, identity_provider_intents.idp_user
|
||||
, identity_provider_intents.idp_user_id
|
||||
, identity_provider_intents.idp_username
|
||||
, identity_provider_intents.user_id
|
||||
, identity_provider_intents.idp_access_token
|
||||
, identity_provider_intents.idp_id_token
|
||||
, identity_provider_intents.idp_entry_attributes
|
||||
, identity_provider_intents.request_id
|
||||
, identity_provider_intents.assertion
|
||||
, identity_provider_intents.succeeded_at
|
||||
, identity_provider_intents.failed_at
|
||||
, identity_provider_intents.fail_reason
|
||||
, identity_provider_intents.expires_at
|
||||
FROM
|
||||
zitadel.identity_provider_intents
|
||||
`
|
||||
|
||||
// Create implements [domain.IDPIntentRepository].
|
||||
func (i idpIntentRepository) Create(ctx context.Context, client database.QueryExecutor, intent *domain.IDPIntent) error {
|
||||
var createdAt any = database.DefaultInstruction
|
||||
if !intent.CreatedAt.IsZero() {
|
||||
createdAt = intent.CreatedAt
|
||||
}
|
||||
var updatedAt = createdAt
|
||||
if !intent.UpdatedAt.IsZero() {
|
||||
updatedAt = intent.UpdatedAt
|
||||
}
|
||||
|
||||
builder := new(database.StatementBuilder)
|
||||
builder.WriteString(`INSERT INTO ` + i.qualifiedTableName() + ` (instance_id, id, success_url, failure_url, idp_id, idp_arguments, created_at, updated_at) VALUES ( `)
|
||||
builder.WriteArgs(intent.InstanceID, intent.ID, intent.SuccessURL.String(), intent.FailureURL.String(), intent.IDPID, intent.IDPArguments, createdAt, updatedAt)
|
||||
builder.WriteString(` ) RETURNING created_at, updated_at`)
|
||||
return client.QueryRow(ctx, builder.String(), builder.Args()...).Scan(&intent.CreatedAt, &intent.UpdatedAt)
|
||||
}
|
||||
|
||||
// Delete implements [domain.IDPIntentRepository].
|
||||
func (i idpIntentRepository) Delete(ctx context.Context, client database.QueryExecutor, condition database.Condition) (int64, error) {
|
||||
return deleteOne(ctx, client, i, condition)
|
||||
}
|
||||
|
||||
// Get implements [domain.IDPIntentRepository].
|
||||
func (i idpIntentRepository) Get(ctx context.Context, client database.QueryExecutor, opts ...database.QueryOption) (*domain.IDPIntent, error) {
|
||||
options := new(database.QueryOpts)
|
||||
for _, opt := range opts {
|
||||
opt(options)
|
||||
}
|
||||
|
||||
if options.Condition == nil || !options.Condition.IsRestrictingColumn(i.InstanceIDColumn()) {
|
||||
return nil, database.NewMissingConditionError(i.InstanceIDColumn())
|
||||
}
|
||||
|
||||
var builder database.StatementBuilder
|
||||
builder.WriteString(queryIDPIntentStmt)
|
||||
options.Write(&builder)
|
||||
fmt.Println(builder.String())
|
||||
|
||||
return scanIDPIntent(ctx, client, &builder)
|
||||
}
|
||||
|
||||
type rawIDPIntent struct {
|
||||
*domain.IDPIntent
|
||||
SuccessURL string `json:"success_url,omitempty" db:"success_url"`
|
||||
FailureURL string `json:"failure_url,omitempty" db:"failure_url"`
|
||||
IDPUserID *string `json:"idp_user_id,omitempty" db:"idp_user_id"`
|
||||
IDPUsername *string `json:"idp_username,omitempty" db:"idp_username"`
|
||||
UserID *string `json:"user_id,omitempty" db:"user_id"`
|
||||
IDPIDToken *string `json:"idp_id_token,omitempty" db:"idp_id_token"`
|
||||
RequestID *string `json:"request_id,omitempty" db:"request_id"`
|
||||
FailReason *string `json:"fail_reason,omitempty" db:"fail_reason"`
|
||||
}
|
||||
|
||||
func scanIDPIntent(ctx context.Context, querier database.QueryExecutor, builder *database.StatementBuilder) (*domain.IDPIntent, error) {
|
||||
rows, err := querier.Query(ctx, builder.String(), builder.Args()...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
raw := new(rawIDPIntent)
|
||||
if err := rows.(database.CollectableRows).CollectExactlyOneRow(raw); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return rawIDPIntentToDomain(raw)
|
||||
}
|
||||
|
||||
func rawIDPIntentToDomain(raw *rawIDPIntent) (*domain.IDPIntent, error) {
|
||||
successURL, _ := url.Parse(raw.SuccessURL)
|
||||
failureURL, _ := url.Parse(raw.FailureURL)
|
||||
raw.IDPIntent.SuccessURL = successURL
|
||||
raw.IDPIntent.FailureURL = failureURL
|
||||
raw.IDPIntent.IDPUserID = gu.Value(raw.IDPUserID)
|
||||
raw.IDPIntent.IDPUsername = gu.Value(raw.IDPUsername)
|
||||
raw.IDPIntent.UserID = gu.Value(raw.UserID)
|
||||
raw.IDPIntent.IDPIDToken = gu.Value(raw.IDPIDToken)
|
||||
raw.IDPIntent.RequestID = gu.Value(raw.RequestID)
|
||||
raw.IDPIntent.FailReason = gu.Value(raw.FailReason)
|
||||
|
||||
return raw.IDPIntent, nil
|
||||
}
|
||||
|
||||
// Update implements [domain.IDPIntentRepository].
|
||||
func (i idpIntentRepository) Update(ctx context.Context, client database.QueryExecutor, condition database.Condition, changes ...database.Change) (int64, error) {
|
||||
if changes == nil {
|
||||
return 0, database.ErrNoChanges
|
||||
}
|
||||
if condition == nil || !condition.IsRestrictingColumn(i.InstanceIDColumn()) {
|
||||
return 0, database.NewMissingConditionError(i.InstanceIDColumn())
|
||||
}
|
||||
if !database.Changes(changes).IsOnColumn(i.UpdatedAtColumn()) {
|
||||
changes = append(changes, database.NewChange(i.UpdatedAtColumn(), database.DefaultInstruction))
|
||||
}
|
||||
|
||||
builder := database.StatementBuilder{}
|
||||
builder.WriteString(`UPDATE zitadel.identity_provider_intents SET `)
|
||||
|
||||
err := database.Changes(changes).Write(&builder)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
writeCondition(&builder, condition)
|
||||
|
||||
return client.Exec(ctx, builder.String(), builder.Args()...)
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// changes
|
||||
// -------------------------------------------------------------
|
||||
|
||||
// SetAssertion implements [domain.idpIntentChanges].
|
||||
func (i idpIntentRepository) SetAssertion(assertion []byte) database.Change {
|
||||
return database.NewChange(i.AssertionColumn(), assertion)
|
||||
}
|
||||
|
||||
// SetFailureURL implements [domain.idpIntentChanges].
|
||||
func (i idpIntentRepository) SetFailureURL(failureURL url.URL) database.Change {
|
||||
return database.NewChange(i.FailureURLColumn(), failureURL.String())
|
||||
}
|
||||
|
||||
// SetIDPAccessToken implements [domain.idpIntentChanges].
|
||||
func (i idpIntentRepository) SetIDPAccessToken(idpAccessToken []byte) database.Change {
|
||||
return database.NewChange(i.IDPAccessTokenColumn(), idpAccessToken)
|
||||
}
|
||||
|
||||
// SetIDPIDToken implements [domain.IDPIntentRepository].
|
||||
func (i *idpIntentRepository) SetIDPIDToken(idpIDToken string) database.Change {
|
||||
return database.NewChange(i.IDPIDTokenColumn(), idpIDToken)
|
||||
}
|
||||
|
||||
// SetIDPArguments implements [domain.idpIntentChanges].
|
||||
func (i idpIntentRepository) SetIDPArguments(idpArguments []byte) database.Change {
|
||||
return database.NewChange(i.IDPArgumentsColumn(), idpArguments)
|
||||
}
|
||||
|
||||
// SetIDPEntryAttributes implements [domain.idpIntentChanges].
|
||||
func (i idpIntentRepository) SetIDPEntryAttributes(idpEntryAttributes []byte) database.Change {
|
||||
return database.NewChange(i.IDPEntryAttributesColumn(), idpEntryAttributes)
|
||||
}
|
||||
|
||||
// SetIDPID implements [domain.idpIntentChanges].
|
||||
func (i idpIntentRepository) SetIDPID(idpID string) database.Change {
|
||||
return database.NewChange(i.IDPIDColumn(), idpID)
|
||||
}
|
||||
|
||||
// SetIDPUser implements [domain.idpIntentChanges].
|
||||
func (i idpIntentRepository) SetIDPUser(idpUser []byte) database.Change {
|
||||
return database.NewChange(i.IDPUserColumn(), idpUser)
|
||||
}
|
||||
|
||||
// SetIDPUserID implements [domain.idpIntentChanges].
|
||||
func (i idpIntentRepository) SetIDPUserID(idpUserID string) database.Change {
|
||||
return database.NewChange(i.IDPUserIDColumn(), idpUserID)
|
||||
}
|
||||
|
||||
// SetIDPUsername implements [domain.idpIntentChanges].
|
||||
func (i idpIntentRepository) SetIDPUsername(idpUsername string) database.Change {
|
||||
return database.NewChange(i.IDPUsernameColumn(), idpUsername)
|
||||
}
|
||||
|
||||
// SetRequestID implements [domain.idpIntentChanges].
|
||||
func (i idpIntentRepository) SetRequestID(requestID string) database.Change {
|
||||
return database.NewChange(i.RequestIDColumn(), requestID)
|
||||
}
|
||||
|
||||
// SetState implements [domain.idpIntentChanges].
|
||||
func (i idpIntentRepository) SetState(state domain.IDPIntentState) database.Change {
|
||||
return database.NewChange(i.StateColumn(), state)
|
||||
}
|
||||
|
||||
// SetSuccessURL implements [domain.idpIntentChanges].
|
||||
func (i idpIntentRepository) SetSuccessURL(successURL url.URL) database.Change {
|
||||
return database.NewChange(i.SuccessURLColumn(), successURL.String())
|
||||
}
|
||||
|
||||
// SetUserID implements [domain.idpIntentChanges].
|
||||
func (i idpIntentRepository) SetUserID(userID string) database.Change {
|
||||
return database.NewChange(i.UserIDColumn(), userID)
|
||||
}
|
||||
|
||||
// SetExpiresAt implements [domain.IDPIntentRepository].
|
||||
func (i *idpIntentRepository) SetExpiresAt(expiration time.Time) database.Change {
|
||||
return database.NewChange(i.ExpiresAtColumn(), expiration)
|
||||
}
|
||||
|
||||
// SetSucceededAt implements [domain.IDPIntentRepository].
|
||||
func (i *idpIntentRepository) SetSucceededAt(succeededAt time.Time) database.Change {
|
||||
return database.NewChange(i.SucceededAtColumn(), succeededAt)
|
||||
}
|
||||
|
||||
// SetFailedAt implements [domain.IDPIntentRepository].
|
||||
func (i *idpIntentRepository) SetFailedAt(failedAt time.Time) database.Change {
|
||||
return database.NewChange(i.FailedAtColumn(), failedAt)
|
||||
}
|
||||
|
||||
// SetFailReason implements [domain.IDPIntentRepository].
|
||||
func (i *idpIntentRepository) SetFailReason(reason string) database.Change {
|
||||
return database.NewChange(i.FailReasonColumn(), reason)
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// conditions
|
||||
// -------------------------------------------------------------
|
||||
|
||||
// CreatedAtCondition implements [domain.idpIntentConditions].
|
||||
func (i idpIntentRepository) CreatedAtCondition(op database.NumberOperation, createdAt time.Time) database.Condition {
|
||||
return database.NewNumberCondition(i.CreatedAtColumn(), op, createdAt)
|
||||
}
|
||||
|
||||
// IDCondition implements [domain.idpIntentConditions].
|
||||
func (i idpIntentRepository) IDCondition(id string) database.Condition {
|
||||
return database.NewTextCondition(i.IDColumn(), database.TextOperationEqual, id)
|
||||
}
|
||||
|
||||
// IDPIDCondition implements [domain.idpIntentConditions].
|
||||
func (i idpIntentRepository) IDPIDCondition(idpID string) database.Condition {
|
||||
return database.NewTextCondition(i.IDPIDColumn(), database.TextOperationEqual, idpID)
|
||||
}
|
||||
|
||||
// IDPUserIDCondition implements [domain.idpIntentConditions].
|
||||
func (i idpIntentRepository) IDPUserIDCondition(idpUserID string) database.Condition {
|
||||
return database.NewTextCondition(i.IDPUserIDColumn(), database.TextOperationEqual, idpUserID)
|
||||
}
|
||||
|
||||
// IDPUsernameCondition implements [domain.idpIntentConditions].
|
||||
func (i idpIntentRepository) IDPUsernameCondition(idpUsername string) database.Condition {
|
||||
return database.NewTextCondition(i.IDPUsernameColumn(), database.TextOperationEqual, idpUsername)
|
||||
}
|
||||
|
||||
// InstanceIDCondition implements [domain.idpIntentConditions].
|
||||
func (i idpIntentRepository) InstanceIDCondition(instanceID string) database.Condition {
|
||||
return database.NewTextCondition(i.InstanceIDColumn(), database.TextOperationEqual, instanceID)
|
||||
}
|
||||
|
||||
// PrimaryKeyCondition implements [domain.idpIntentConditions].
|
||||
func (i idpIntentRepository) PrimaryKeyCondition(instanceID, id string) database.Condition {
|
||||
return database.And(
|
||||
i.InstanceIDCondition(instanceID),
|
||||
i.IDCondition(id),
|
||||
)
|
||||
}
|
||||
|
||||
// RequestIDCondition implements [domain.idpIntentConditions].
|
||||
func (i idpIntentRepository) RequestIDCondition(requestID string) database.Condition {
|
||||
return database.NewTextCondition(i.RequestIDColumn(), database.TextOperationEqual, requestID)
|
||||
}
|
||||
|
||||
// StateCondition implements [domain.idpIntentConditions].
|
||||
func (i idpIntentRepository) StateCondition(state domain.IDPIntentState) database.Condition {
|
||||
return database.NewTextCondition(i.StateColumn(), database.TextOperationEqual, state.String())
|
||||
}
|
||||
|
||||
// UpdatedAtCondition implements [domain.idpIntentConditions].
|
||||
func (i idpIntentRepository) UpdatedAtCondition(op database.NumberOperation, updatedAt time.Time) database.Condition {
|
||||
return database.NewNumberCondition(i.UpdatedAtColumn(), op, updatedAt)
|
||||
}
|
||||
|
||||
// UserIDCondition implements [domain.idpIntentConditions].
|
||||
func (i idpIntentRepository) UserIDCondition(userID string) database.Condition {
|
||||
return database.NewTextCondition(i.UserIDColumn(), database.TextOperationEqual, userID)
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// columns
|
||||
// -------------------------------------------------------------
|
||||
|
||||
// AssertionColumn implements [domain.idpIntentColumns].
|
||||
func (i idpIntentRepository) AssertionColumn() database.Column {
|
||||
return database.NewColumn(i.unqualifiedTableName(), "assertion")
|
||||
}
|
||||
|
||||
// CreatedAtColumn implements [domain.idpIntentColumns].
|
||||
func (i idpIntentRepository) CreatedAtColumn() database.Column {
|
||||
return database.NewColumn(i.unqualifiedTableName(), "created_at")
|
||||
}
|
||||
|
||||
// ExpiresAtColumn implements [domain.idpIntentColumns].
|
||||
func (i idpIntentRepository) ExpiresAtColumn() database.Column {
|
||||
return database.NewColumn(i.unqualifiedTableName(), "expires_at")
|
||||
}
|
||||
|
||||
// FailureURLColumn implements [domain.idpIntentColumns].
|
||||
func (i idpIntentRepository) FailureURLColumn() database.Column {
|
||||
return database.NewColumn(i.unqualifiedTableName(), "failure_url")
|
||||
}
|
||||
|
||||
// IDColumn implements [domain.idpIntentColumns].
|
||||
func (i idpIntentRepository) IDColumn() database.Column {
|
||||
return database.NewColumn(i.unqualifiedTableName(), "id")
|
||||
}
|
||||
|
||||
// IDPAccessTokenColumn implements [domain.idpIntentColumns].
|
||||
func (i idpIntentRepository) IDPAccessTokenColumn() database.Column {
|
||||
return database.NewColumn(i.unqualifiedTableName(), "idp_access_token")
|
||||
}
|
||||
|
||||
// IDPIDTokenColumn implements [domain.IDPIntentRepository].
|
||||
func (i *idpIntentRepository) IDPIDTokenColumn() database.Column {
|
||||
return database.NewColumn(i.unqualifiedTableName(), "idp_id_token")
|
||||
}
|
||||
|
||||
// IDPArgumentsColumn implements [domain.idpIntentColumns].
|
||||
func (i idpIntentRepository) IDPArgumentsColumn() database.Column {
|
||||
return database.NewColumn(i.unqualifiedTableName(), "idp_arguments")
|
||||
}
|
||||
|
||||
// IDPEntryAttributesColumn implements [domain.idpIntentColumns].
|
||||
func (i idpIntentRepository) IDPEntryAttributesColumn() database.Column {
|
||||
return database.NewColumn(i.unqualifiedTableName(), "idp_entry_attributes")
|
||||
}
|
||||
|
||||
// IDPIDColumn implements [domain.idpIntentColumns].
|
||||
func (i idpIntentRepository) IDPIDColumn() database.Column {
|
||||
return database.NewColumn(i.unqualifiedTableName(), "idp_id")
|
||||
}
|
||||
|
||||
// IDPUserColumn implements [domain.idpIntentColumns].
|
||||
func (i idpIntentRepository) IDPUserColumn() database.Column {
|
||||
return database.NewColumn(i.unqualifiedTableName(), "idp_user")
|
||||
}
|
||||
|
||||
// IDPUserIDColumn implements [domain.idpIntentColumns].
|
||||
func (i idpIntentRepository) IDPUserIDColumn() database.Column {
|
||||
return database.NewColumn(i.unqualifiedTableName(), "idp_user_id")
|
||||
}
|
||||
|
||||
// IDPUsernameColumn implements [domain.idpIntentColumns].
|
||||
func (i idpIntentRepository) IDPUsernameColumn() database.Column {
|
||||
return database.NewColumn(i.unqualifiedTableName(), "idp_username")
|
||||
}
|
||||
|
||||
// InstanceIDColumn implements [domain.idpIntentColumns].
|
||||
func (i idpIntentRepository) InstanceIDColumn() database.Column {
|
||||
return database.NewColumn(i.unqualifiedTableName(), "instance_id")
|
||||
}
|
||||
|
||||
// PrimaryKeyColumns implements [domain.IDPIntentRepository].
|
||||
func (i idpIntentRepository) PrimaryKeyColumns() []database.Column {
|
||||
return []database.Column{i.IDColumn(), i.InstanceIDColumn()}
|
||||
}
|
||||
|
||||
// RequestIDColumn implements [domain.idpIntentColumns].
|
||||
func (i idpIntentRepository) RequestIDColumn() database.Column {
|
||||
return database.NewColumn(i.unqualifiedTableName(), "request_id")
|
||||
}
|
||||
|
||||
// StateColumn implements [domain.idpIntentColumns].
|
||||
func (i idpIntentRepository) StateColumn() database.Column {
|
||||
return database.NewColumn(i.unqualifiedTableName(), "state")
|
||||
}
|
||||
|
||||
// SucceededAtColumn implements [domain.idpIntentColumns].
|
||||
func (i idpIntentRepository) SucceededAtColumn() database.Column {
|
||||
return database.NewColumn(i.unqualifiedTableName(), "succeeded_at")
|
||||
}
|
||||
|
||||
// SuccessURLColumn implements [domain.idpIntentColumns].
|
||||
func (i idpIntentRepository) SuccessURLColumn() database.Column {
|
||||
return database.NewColumn(i.unqualifiedTableName(), "success_url")
|
||||
}
|
||||
|
||||
// FailReasonColumn implements [domain.IDPIntentRepository].
|
||||
func (i *idpIntentRepository) FailReasonColumn() database.Column {
|
||||
return database.NewColumn(i.unqualifiedTableName(), "fail_reason")
|
||||
}
|
||||
|
||||
// FailedAtColumn implements [domain.IDPIntentRepository].
|
||||
func (i *idpIntentRepository) FailedAtColumn() database.Column {
|
||||
return database.NewColumn(i.unqualifiedTableName(), "failed_at")
|
||||
}
|
||||
|
||||
// UpdatedAtColumn implements [domain.idpIntentColumns].
|
||||
func (i idpIntentRepository) UpdatedAtColumn() database.Column {
|
||||
return database.NewColumn(i.unqualifiedTableName(), "updated_at")
|
||||
}
|
||||
|
||||
// UserIDColumn implements [domain.idpIntentColumns].
|
||||
func (i idpIntentRepository) UserIDColumn() database.Column {
|
||||
return database.NewColumn(i.unqualifiedTableName(), "user_id")
|
||||
}
|
||||
|
||||
var _ domain.IDPIntentRepository = (*idpIntentRepository)(nil)
|
||||
@@ -0,0 +1,591 @@
|
||||
package repository_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/url"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/brianvoe/gofakeit/v6"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/zitadel/zitadel/backend/v3/domain"
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/database"
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/database/repository"
|
||||
)
|
||||
|
||||
func TestCreateIDPIntent(t *testing.T) {
|
||||
beforeCreate := time.Now()
|
||||
tx, err := pool.Begin(t.Context(), nil)
|
||||
require.NoError(t, err)
|
||||
defer func() {
|
||||
err := tx.Rollback(t.Context())
|
||||
require.NoError(t, err)
|
||||
}()
|
||||
|
||||
instanceID := createInstance(t, tx)
|
||||
orgID := createOrganization(t, tx, instanceID)
|
||||
idpID := createIdentityProvider(t, tx, instanceID, orgID)
|
||||
|
||||
successURL, err := url.Parse("https://example.com/success")
|
||||
require.NoError(t, err)
|
||||
failURL, err := url.Parse("https://example.com/fail")
|
||||
require.NoError(t, err)
|
||||
|
||||
emptyURL, err := url.Parse("")
|
||||
require.NoError(t, err)
|
||||
|
||||
intentID := gofakeit.UUID()
|
||||
tt := []struct {
|
||||
testName string
|
||||
|
||||
inputID string
|
||||
inputInstanceID string
|
||||
inputSuccessURL *url.URL
|
||||
inputFailureURL *url.URL
|
||||
inputIDPID string
|
||||
inputIDPArgs map[string]any
|
||||
inputCreatedAt time.Time
|
||||
inputUpdatedAt time.Time
|
||||
inputMaxLifetime time.Duration
|
||||
|
||||
expectedError error
|
||||
}{
|
||||
{
|
||||
testName: "create and update timestamps not set / empty fail url / should generate timestamps and retrieve successfully",
|
||||
inputID: intentID,
|
||||
inputInstanceID: instanceID,
|
||||
inputSuccessURL: successURL,
|
||||
inputFailureURL: emptyURL,
|
||||
inputIDPID: idpID,
|
||||
inputIDPArgs: map[string]any{
|
||||
"arg1": map[string]any{"k1": 1, "k2": "v2"},
|
||||
"arg2": 2,
|
||||
"arg3": "3",
|
||||
"arg4": true,
|
||||
},
|
||||
inputMaxLifetime: time.Hour * 2,
|
||||
},
|
||||
{
|
||||
testName: "all set / should retrieve successfully",
|
||||
inputID: intentID,
|
||||
inputInstanceID: instanceID,
|
||||
inputSuccessURL: successURL,
|
||||
inputFailureURL: failURL,
|
||||
inputIDPID: idpID,
|
||||
inputIDPArgs: map[string]any{"arg1": map[string]any{"k1": 1, "k2": "v2"}},
|
||||
inputMaxLifetime: time.Hour * 2,
|
||||
inputCreatedAt: time.Now(),
|
||||
inputUpdatedAt: time.Now(),
|
||||
},
|
||||
{
|
||||
testName: "id not set / should return check error",
|
||||
inputCreatedAt: time.Now(),
|
||||
inputUpdatedAt: time.Now(),
|
||||
inputInstanceID: instanceID,
|
||||
inputSuccessURL: successURL,
|
||||
inputFailureURL: emptyURL,
|
||||
inputIDPID: idpID,
|
||||
inputIDPArgs: map[string]any{"arg1": map[string]any{"k1": 1, "k2": "v2"}},
|
||||
expectedError: database.NewCheckError("", "", nil),
|
||||
},
|
||||
{
|
||||
testName: "instance id not found / should return foreign key error",
|
||||
inputID: intentID,
|
||||
inputCreatedAt: time.Now(),
|
||||
inputUpdatedAt: time.Now(),
|
||||
inputSuccessURL: successURL,
|
||||
inputFailureURL: emptyURL,
|
||||
inputIDPID: idpID,
|
||||
inputIDPArgs: map[string]any{"arg1": map[string]any{"k1": 1, "k2": "v2"}},
|
||||
expectedError: database.NewForeignKeyError("", "", nil),
|
||||
},
|
||||
{
|
||||
testName: "idp id not found / should return foreign key error",
|
||||
inputID: intentID,
|
||||
inputInstanceID: instanceID,
|
||||
inputCreatedAt: time.Now(),
|
||||
inputUpdatedAt: time.Now(),
|
||||
inputSuccessURL: successURL,
|
||||
inputFailureURL: failURL,
|
||||
inputIDPArgs: map[string]any{"arg1": map[string]any{"k1": 1, "k2": "v2"}},
|
||||
expectedError: database.NewForeignKeyError("", "", nil),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.testName, func(t *testing.T) {
|
||||
// Given
|
||||
savePoint, savePointErr := tx.Begin(t.Context())
|
||||
require.NoError(t, savePointErr)
|
||||
defer func() {
|
||||
err := savePoint.Rollback(t.Context())
|
||||
require.NoError(t, err)
|
||||
}()
|
||||
|
||||
idpIntentRepo := repository.IDPIntentRepository()
|
||||
intent := domain.IDPIntent{
|
||||
ID: tc.inputID,
|
||||
InstanceID: tc.inputInstanceID,
|
||||
SuccessURL: tc.inputSuccessURL,
|
||||
FailureURL: tc.inputFailureURL,
|
||||
IDPID: tc.inputIDPID,
|
||||
IDPArguments: tc.inputIDPArgs,
|
||||
|
||||
State: domain.IDPIntentStateFailed, // Should disregard
|
||||
IDPUserID: "should disregard",
|
||||
}
|
||||
|
||||
// Test
|
||||
createErr := idpIntentRepo.Create(t.Context(), tx, &intent)
|
||||
afterCreate := time.Now()
|
||||
|
||||
// Verify
|
||||
assert.ErrorIs(t, createErr, tc.expectedError)
|
||||
if tc.expectedError == nil {
|
||||
retrieved, getErr := idpIntentRepo.Get(t.Context(), tx, database.WithCondition(idpIntentRepo.PrimaryKeyCondition(instanceID, intentID)))
|
||||
require.NoError(t, getErr)
|
||||
require.NotNil(t, retrieved)
|
||||
|
||||
assert.WithinRange(t, retrieved.CreatedAt, beforeCreate, afterCreate)
|
||||
assert.WithinRange(t, retrieved.UpdatedAt, beforeCreate, afterCreate)
|
||||
assert.NotZero(t, retrieved.ID)
|
||||
if tc.inputID != "" {
|
||||
assert.Equal(t, tc.inputID, retrieved.ID)
|
||||
}
|
||||
assert.Equal(t, tc.inputInstanceID, retrieved.InstanceID)
|
||||
assert.Equal(t, tc.inputSuccessURL, retrieved.SuccessURL)
|
||||
assert.Equal(t, tc.inputFailureURL, retrieved.FailureURL)
|
||||
assert.Equal(t, tc.inputIDPID, retrieved.IDPID)
|
||||
assert.NotZero(t, retrieved.IDPArguments)
|
||||
assert.Equal(t, domain.IDPIntentStateStarted, retrieved.State)
|
||||
assert.Zero(t, retrieved.IDPUserID)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetIDPIntent(t *testing.T) {
|
||||
tx, err := pool.Begin(t.Context(), nil)
|
||||
require.NoError(t, err)
|
||||
defer func() {
|
||||
err := tx.Rollback(t.Context())
|
||||
require.NoError(t, err)
|
||||
}()
|
||||
|
||||
instanceID := createInstance(t, tx)
|
||||
orgID := createOrganization(t, tx, instanceID)
|
||||
idpID := createIdentityProvider(t, tx, instanceID, orgID)
|
||||
|
||||
idpIntentRepo := repository.IDPIntentRepository()
|
||||
|
||||
intentID1 := createIDPIntent(t, tx, instanceID, idpID)
|
||||
successURL, err := url.Parse("https://example.com/success")
|
||||
require.NoError(t, err)
|
||||
failURL, err := url.Parse("https://example.com/fail")
|
||||
require.NoError(t, err)
|
||||
|
||||
intent2 := domain.IDPIntent{
|
||||
ID: gofakeit.UUID(),
|
||||
InstanceID: instanceID,
|
||||
SuccessURL: successURL,
|
||||
FailureURL: failURL,
|
||||
IDPID: idpID,
|
||||
IDPArguments: map[string]any{"arg1": map[string]any{"k1": 1, "k2": "v2"}},
|
||||
CreatedAt: time.Now().AddDate(0, 0, 1),
|
||||
}
|
||||
err = idpIntentRepo.Create(t.Context(), tx, &intent2)
|
||||
require.NoError(t, err)
|
||||
|
||||
tt := []struct {
|
||||
testName string
|
||||
inputQueryOpts []database.QueryOption
|
||||
expectedError error
|
||||
expectedIntentID string
|
||||
}{
|
||||
{
|
||||
testName: "when no condition set should return missing condition error",
|
||||
expectedError: database.NewMissingConditionError(nil),
|
||||
},
|
||||
{
|
||||
testName: "when no instance condition set should return missing condition error",
|
||||
inputQueryOpts: []database.QueryOption{
|
||||
database.WithCondition(idpIntentRepo.IDCondition(intentID1)),
|
||||
},
|
||||
expectedError: database.NewMissingConditionError(nil),
|
||||
},
|
||||
{
|
||||
testName: "when primary key condition set should return matching intent",
|
||||
inputQueryOpts: []database.QueryOption{
|
||||
database.WithCondition(idpIntentRepo.PrimaryKeyCondition(instanceID, intentID1)),
|
||||
},
|
||||
expectedIntentID: intentID1,
|
||||
},
|
||||
{
|
||||
testName: "when filtering by creation date descending should return matching intent with highest PK",
|
||||
inputQueryOpts: []database.QueryOption{
|
||||
database.WithCondition(database.And(
|
||||
idpIntentRepo.InstanceIDCondition(instanceID),
|
||||
)),
|
||||
database.WithOrderByDescending(idpIntentRepo.CreatedAtColumn()),
|
||||
database.WithLimit(1),
|
||||
},
|
||||
expectedIntentID: intent2.ID,
|
||||
},
|
||||
{
|
||||
testName: "when filtering by non-existent PK should return no row found error",
|
||||
inputQueryOpts: []database.QueryOption{
|
||||
database.WithCondition(database.And(
|
||||
idpIntentRepo.InstanceIDCondition(instanceID),
|
||||
idpIntentRepo.IDCondition("not existing"),
|
||||
)),
|
||||
},
|
||||
expectedError: database.NewNoRowFoundError(nil),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.testName, func(t *testing.T) {
|
||||
// Test
|
||||
retrieved, err := idpIntentRepo.Get(t.Context(), tx, tc.inputQueryOpts...)
|
||||
|
||||
// Verify
|
||||
assert.ErrorIs(t, err, tc.expectedError)
|
||||
if tc.expectedError == nil {
|
||||
assert.Equal(t, tc.expectedIntentID, retrieved.ID)
|
||||
assert.Equal(t, "https://example.com/success", retrieved.SuccessURL.String())
|
||||
assert.Equal(t, "https://example.com/fail", retrieved.FailureURL.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateIDPIntent(t *testing.T) {
|
||||
beforeUpdate := time.Now()
|
||||
|
||||
tx, err := pool.Begin(t.Context(), nil)
|
||||
require.NoError(t, err)
|
||||
defer func() {
|
||||
err := tx.Rollback(t.Context())
|
||||
require.NoError(t, err)
|
||||
}()
|
||||
|
||||
instanceID := createInstance(t, tx)
|
||||
orgID := createOrganization(t, tx, instanceID)
|
||||
idpID := createIdentityProvider(t, tx, instanceID, orgID)
|
||||
|
||||
idpIntentRepo := repository.IDPIntentRepository()
|
||||
|
||||
successURL, err := url.Parse("https://example.com/success")
|
||||
require.NoError(t, err)
|
||||
failURL, err := url.Parse("https://example.com/fail")
|
||||
require.NoError(t, err)
|
||||
|
||||
intentID1 := gofakeit.UUID()
|
||||
intent1 := domain.IDPIntent{
|
||||
ID: intentID1,
|
||||
InstanceID: instanceID,
|
||||
SuccessURL: successURL,
|
||||
FailureURL: failURL,
|
||||
IDPID: idpID,
|
||||
IDPArguments: map[string]any{"arg1": map[string]any{"k1": 1, "k2": "v2"}, "arg2": []int32{1}},
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
err = idpIntentRepo.Create(t.Context(), tx, &intent1)
|
||||
require.NoError(t, err)
|
||||
|
||||
entryAttrs := map[string][]string{
|
||||
"attr1": {"1", "2"},
|
||||
"attr2": {"3", "4"},
|
||||
}
|
||||
marshalledAttrs, marshallErr := json.Marshal(entryAttrs)
|
||||
require.NoError(t, marshallErr)
|
||||
|
||||
intentID2 := gofakeit.UUID()
|
||||
intent2 := domain.IDPIntent{
|
||||
ID: intentID2,
|
||||
InstanceID: instanceID,
|
||||
SuccessURL: successURL,
|
||||
FailureURL: failURL,
|
||||
IDPID: idpID,
|
||||
IDPArguments: map[string]any{"arg1": map[string]any{"k1": 1, "k2": "v2"}},
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
err = idpIntentRepo.Create(t.Context(), tx, &intent2)
|
||||
require.NoError(t, err)
|
||||
|
||||
now := time.Now()
|
||||
tomorrow := now.AddDate(0, 0, 1)
|
||||
tt := []struct {
|
||||
testName string
|
||||
inputConditions database.Condition
|
||||
inputChanges []database.Change
|
||||
expectedError error
|
||||
expectedUpdatedRecords func() []*domain.IDPIntent
|
||||
expectedUpdatedRows int64
|
||||
}{
|
||||
{
|
||||
testName: "when no changes should return no changes error",
|
||||
expectedError: database.ErrNoChanges,
|
||||
},
|
||||
{
|
||||
testName: "when no condition set should return missing condition error",
|
||||
inputChanges: database.Changes{
|
||||
idpIntentRepo.SetIDPUser([]byte("not a user")),
|
||||
},
|
||||
expectedError: database.NewMissingConditionError(idpIntentRepo.InstanceIDColumn()),
|
||||
},
|
||||
{
|
||||
testName: "when simulating SucceededEvent should update fields accordingly",
|
||||
inputConditions: idpIntentRepo.PrimaryKeyCondition(instanceID, intentID1),
|
||||
inputChanges: database.Changes{
|
||||
idpIntentRepo.SetState(domain.IDPIntentStateSucceeded),
|
||||
idpIntentRepo.SetIDPUser([]byte("update user")),
|
||||
idpIntentRepo.SetIDPUserID("idp user id updated"),
|
||||
idpIntentRepo.SetIDPUsername("idp username updated"),
|
||||
idpIntentRepo.SetUserID("some-user-id"),
|
||||
idpIntentRepo.SetIDPAccessToken([]byte(`{"secret": "g1g4 s3cr3t 4cc3ss t0k3n"}`)),
|
||||
idpIntentRepo.SetIDPIDToken("idp id token"),
|
||||
idpIntentRepo.SetSucceededAt(now),
|
||||
idpIntentRepo.SetExpiresAt(tomorrow),
|
||||
},
|
||||
expectedUpdatedRecords: func() []*domain.IDPIntent {
|
||||
toReturn := intent1
|
||||
toReturn.IDPUser = []byte("update user")
|
||||
toReturn.IDPUserID = "idp user id updated"
|
||||
toReturn.IDPUsername = "idp username updated"
|
||||
toReturn.UserID = "some-user-id"
|
||||
toReturn.IDPAccessToken = []byte(`{"secret": "g1g4 s3cr3t 4cc3ss t0k3n"}`)
|
||||
toReturn.IDPIDToken = "idp id token"
|
||||
toReturn.SucceededAt = &now
|
||||
toReturn.ExpiresAt = &tomorrow
|
||||
toReturn.State = domain.IDPIntentStateSucceeded
|
||||
return []*domain.IDPIntent{&toReturn}
|
||||
},
|
||||
expectedUpdatedRows: 1,
|
||||
},
|
||||
{
|
||||
testName: "when simulating SAMLSucceededEvent should update fields accordingly",
|
||||
inputConditions: idpIntentRepo.PrimaryKeyCondition(instanceID, intentID1),
|
||||
inputChanges: database.Changes{
|
||||
idpIntentRepo.SetState(domain.IDPIntentStateSucceeded),
|
||||
idpIntentRepo.SetIDPUser([]byte("update user")),
|
||||
idpIntentRepo.SetIDPUserID("idp user id updated"),
|
||||
idpIntentRepo.SetIDPUsername("idp username updated"),
|
||||
idpIntentRepo.SetUserID("some-user-id"),
|
||||
idpIntentRepo.SetAssertion([]byte(`{"assertion": "val1"}`)),
|
||||
idpIntentRepo.SetSucceededAt(now),
|
||||
idpIntentRepo.SetExpiresAt(tomorrow),
|
||||
},
|
||||
expectedUpdatedRecords: func() []*domain.IDPIntent {
|
||||
toReturn := intent1
|
||||
toReturn.IDPUser = []byte("update user")
|
||||
toReturn.IDPUserID = "idp user id updated"
|
||||
toReturn.IDPUsername = "idp username updated"
|
||||
toReturn.UserID = "some-user-id"
|
||||
toReturn.Assertion = []byte(`{"assertion": "val1"}`)
|
||||
toReturn.SucceededAt = &now
|
||||
toReturn.ExpiresAt = &tomorrow
|
||||
toReturn.State = domain.IDPIntentStateSucceeded
|
||||
return []*domain.IDPIntent{&toReturn}
|
||||
},
|
||||
expectedUpdatedRows: 1,
|
||||
},
|
||||
{
|
||||
testName: "when simulating LDAPSucceededEvent should update fields accordingly",
|
||||
inputConditions: idpIntentRepo.PrimaryKeyCondition(instanceID, intentID1),
|
||||
inputChanges: database.Changes{
|
||||
idpIntentRepo.SetState(domain.IDPIntentStateSucceeded),
|
||||
idpIntentRepo.SetIDPUser([]byte("update user")),
|
||||
idpIntentRepo.SetIDPUserID("idp user id updated"),
|
||||
idpIntentRepo.SetIDPUsername("idp username updated"),
|
||||
idpIntentRepo.SetUserID("some-user-id"),
|
||||
idpIntentRepo.SetIDPEntryAttributes(marshalledAttrs),
|
||||
idpIntentRepo.SetSucceededAt(now),
|
||||
idpIntentRepo.SetExpiresAt(tomorrow),
|
||||
},
|
||||
expectedUpdatedRecords: func() []*domain.IDPIntent {
|
||||
toReturn := intent1
|
||||
toReturn.IDPUser = []byte("update user")
|
||||
toReturn.IDPUserID = "idp user id updated"
|
||||
toReturn.IDPUsername = "idp username updated"
|
||||
toReturn.UserID = "some-user-id"
|
||||
toReturn.EntryAttributes = entryAttrs
|
||||
toReturn.SucceededAt = &now
|
||||
toReturn.ExpiresAt = &tomorrow
|
||||
toReturn.State = domain.IDPIntentStateSucceeded
|
||||
return []*domain.IDPIntent{&toReturn}
|
||||
},
|
||||
expectedUpdatedRows: 1,
|
||||
},
|
||||
{
|
||||
testName: "when simulating FailedEvent should update reason, state and failedAt",
|
||||
inputConditions: idpIntentRepo.PrimaryKeyCondition(instanceID, intentID1),
|
||||
inputChanges: database.Changes{
|
||||
idpIntentRepo.SetState(domain.IDPIntentStateFailed),
|
||||
idpIntentRepo.SetFailReason("mock failure"),
|
||||
idpIntentRepo.SetFailedAt(now),
|
||||
},
|
||||
expectedUpdatedRecords: func() []*domain.IDPIntent {
|
||||
toReturn := intent1
|
||||
toReturn.FailReason = "mock failure"
|
||||
toReturn.State = domain.IDPIntentStateFailed
|
||||
toReturn.FailedAt = &now
|
||||
return []*domain.IDPIntent{&toReturn}
|
||||
},
|
||||
expectedUpdatedRows: 1,
|
||||
},
|
||||
{
|
||||
testName: "when simulating SAMLRequestEvent should update request ID",
|
||||
inputConditions: idpIntentRepo.PrimaryKeyCondition(instanceID, intentID1),
|
||||
inputChanges: database.Changes{
|
||||
idpIntentRepo.SetRequestID("req-123"),
|
||||
},
|
||||
expectedUpdatedRecords: func() []*domain.IDPIntent {
|
||||
toReturn := intent1
|
||||
toReturn.RequestID = "req-123"
|
||||
return []*domain.IDPIntent{&toReturn}
|
||||
},
|
||||
expectedUpdatedRows: 1,
|
||||
},
|
||||
{
|
||||
testName: "when updating multiple records should return updated records",
|
||||
inputConditions: database.Or(
|
||||
idpIntentRepo.PrimaryKeyCondition(instanceID, intentID1),
|
||||
idpIntentRepo.PrimaryKeyCondition(instanceID, intentID2),
|
||||
),
|
||||
inputChanges: database.Changes{
|
||||
idpIntentRepo.SetState(domain.IDPIntentStateFailed),
|
||||
idpIntentRepo.SetRequestID("req-123"),
|
||||
idpIntentRepo.SetFailedAt(now),
|
||||
},
|
||||
expectedUpdatedRecords: func() []*domain.IDPIntent {
|
||||
toReturn1, toReturn2 := intent1, intent2
|
||||
toReturn1.State = domain.IDPIntentStateFailed
|
||||
toReturn1.RequestID = "req-123"
|
||||
toReturn1.FailedAt = &now
|
||||
toReturn2.State = domain.IDPIntentStateFailed
|
||||
toReturn2.RequestID = "req-123"
|
||||
toReturn2.FailedAt = &now
|
||||
return []*domain.IDPIntent{&toReturn1, &toReturn2}
|
||||
},
|
||||
expectedUpdatedRows: 2,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.testName, func(t *testing.T) {
|
||||
savePoint, err := tx.Begin(t.Context())
|
||||
require.NoError(t, err)
|
||||
defer func() {
|
||||
err := savePoint.Rollback(t.Context())
|
||||
require.NoError(t, err)
|
||||
}()
|
||||
|
||||
// Test
|
||||
updatedCount, err := idpIntentRepo.Update(t.Context(), savePoint, tc.inputConditions, tc.inputChanges...)
|
||||
afterUpdate := time.Now()
|
||||
|
||||
// Verify
|
||||
assert.ErrorIs(t, err, tc.expectedError)
|
||||
require.Equal(t, tc.expectedUpdatedRows, updatedCount)
|
||||
if tc.expectedError == nil {
|
||||
for _, expectedIntent := range tc.expectedUpdatedRecords() {
|
||||
retrievedIntent, err := idpIntentRepo.Get(t.Context(), savePoint, database.WithCondition(idpIntentRepo.PrimaryKeyCondition(instanceID, expectedIntent.ID)))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, expectedIntent.ID, retrievedIntent.ID)
|
||||
assert.Equal(t, expectedIntent.InstanceID, retrievedIntent.InstanceID)
|
||||
assert.Equal(t, expectedIntent.State, retrievedIntent.State)
|
||||
assert.Equal(t, expectedIntent.SuccessURL, retrievedIntent.SuccessURL)
|
||||
assert.Equal(t, expectedIntent.FailureURL, retrievedIntent.FailureURL)
|
||||
assert.Equal(t, expectedIntent.CreatedAt, retrievedIntent.CreatedAt)
|
||||
assert.WithinRange(t, retrievedIntent.UpdatedAt, beforeUpdate, afterUpdate)
|
||||
assert.Equal(t, expectedIntent.IDPID, retrievedIntent.IDPID)
|
||||
assert.Equal(t, expectedIntent.IDPUser, retrievedIntent.IDPUser)
|
||||
assert.Equal(t, expectedIntent.IDPUserID, retrievedIntent.IDPUserID)
|
||||
assert.Equal(t, expectedIntent.IDPUsername, retrievedIntent.IDPUsername)
|
||||
assert.Equal(t, expectedIntent.UserID, retrievedIntent.UserID)
|
||||
assert.Equal(t, expectedIntent.IDPAccessToken, retrievedIntent.IDPAccessToken)
|
||||
assert.Equal(t, expectedIntent.IDPIDToken, retrievedIntent.IDPIDToken)
|
||||
assert.Equal(t, expectedIntent.EntryAttributes, retrievedIntent.EntryAttributes)
|
||||
assert.Equal(t, expectedIntent.RequestID, retrievedIntent.RequestID)
|
||||
assert.Equal(t, expectedIntent.Assertion, retrievedIntent.Assertion)
|
||||
if expectedIntent.State == domain.IDPIntentStateSucceeded {
|
||||
require.NotNil(t, retrievedIntent.SucceededAt)
|
||||
assert.WithinRange(t, *retrievedIntent.SucceededAt, beforeUpdate, afterUpdate)
|
||||
require.NotNil(t, retrievedIntent.ExpiresAt)
|
||||
assert.WithinRange(t, *retrievedIntent.ExpiresAt, beforeUpdate.AddDate(0, 0, 1), afterUpdate.AddDate(0, 0, 1))
|
||||
}
|
||||
if expectedIntent.State == domain.IDPIntentStateFailed {
|
||||
require.NotNil(t, retrievedIntent.FailedAt)
|
||||
assert.WithinRange(t, *retrievedIntent.FailedAt, beforeUpdate, afterUpdate)
|
||||
}
|
||||
assert.Equal(t, expectedIntent.FailReason, retrievedIntent.FailReason)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteIDPIntent(t *testing.T) {
|
||||
tx, err := pool.Begin(t.Context(), nil)
|
||||
require.NoError(t, err)
|
||||
defer func() {
|
||||
err := tx.Rollback(t.Context())
|
||||
require.NoError(t, err)
|
||||
}()
|
||||
|
||||
instanceID := createInstance(t, tx)
|
||||
orgID := createOrganization(t, tx, instanceID)
|
||||
idpID := createIdentityProvider(t, tx, instanceID, orgID)
|
||||
intentID := createIDPIntent(t, tx, instanceID, idpID)
|
||||
otherIntentID := createIDPIntent(t, tx, instanceID, idpID)
|
||||
createIDPIntent(t, tx, instanceID, idpID)
|
||||
idpIntentRepo := repository.IDPIntentRepository()
|
||||
|
||||
tt := []struct {
|
||||
testName string
|
||||
inputConditions database.Condition
|
||||
expectedError error
|
||||
expectedDeletedCount int64
|
||||
}{
|
||||
{
|
||||
testName: "when PK condition not set should return missing condition error",
|
||||
inputConditions: idpIntentRepo.StateCondition(domain.IDPIntentStateFailed),
|
||||
expectedError: database.NewMissingConditionError(nil),
|
||||
},
|
||||
{
|
||||
testName: "when condition matches record should delete matching record",
|
||||
inputConditions: idpIntentRepo.PrimaryKeyCondition(instanceID, intentID),
|
||||
expectedDeletedCount: 1,
|
||||
},
|
||||
{
|
||||
testName: "when conditions matches record should delete matching records",
|
||||
inputConditions: database.Or(
|
||||
idpIntentRepo.PrimaryKeyCondition(instanceID, intentID),
|
||||
idpIntentRepo.PrimaryKeyCondition(instanceID, otherIntentID),
|
||||
),
|
||||
expectedDeletedCount: 2,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.testName, func(t *testing.T) {
|
||||
savePoint, err := tx.Begin(t.Context())
|
||||
require.NoError(t, err)
|
||||
defer func() {
|
||||
err := savePoint.Rollback(t.Context())
|
||||
require.NoError(t, err)
|
||||
}()
|
||||
|
||||
// Test
|
||||
deleteCount, err := idpIntentRepo.Delete(t.Context(), savePoint, tc.inputConditions)
|
||||
|
||||
// Verify
|
||||
assert.ErrorIs(t, err, tc.expectedError)
|
||||
assert.Equal(t, tc.expectedDeletedCount, deleteCount)
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -4,10 +4,13 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/url"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/brianvoe/gofakeit/v6"
|
||||
"github.com/muhlemmer/gu"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/zitadel/zitadel/backend/v3/domain"
|
||||
@@ -161,3 +164,49 @@ func createProjectGrant(t *testing.T, tx database.Transaction, instanceID, grant
|
||||
|
||||
return projectGrant.ID
|
||||
}
|
||||
|
||||
func createIdentityProvider(t *testing.T, tx database.Transaction, instanceID, orgID string) string {
|
||||
t.Helper()
|
||||
idp := domain.IdentityProvider{
|
||||
InstanceID: instanceID,
|
||||
OrgID: &orgID,
|
||||
ID: gofakeit.UUID(),
|
||||
State: domain.IDPStateActive,
|
||||
Name: gofakeit.Name(),
|
||||
Type: gu.Ptr(domain.IDPTypeOIDC),
|
||||
AllowCreation: true,
|
||||
AllowAutoCreation: true,
|
||||
AllowAutoUpdate: true,
|
||||
AllowLinking: true,
|
||||
StylingType: &stylingType,
|
||||
Payload: []byte("{}"),
|
||||
}
|
||||
idpRepo := repository.IDProviderRepository()
|
||||
err := idpRepo.Create(t.Context(), tx, &idp)
|
||||
require.NoError(t, err)
|
||||
|
||||
return idp.ID
|
||||
}
|
||||
|
||||
func createIDPIntent(t *testing.T, tx database.Transaction, instanceID, idpID string) string {
|
||||
t.Helper()
|
||||
successURL, err := url.Parse("https://example.com/success")
|
||||
require.NoError(t, err)
|
||||
failURL, err := url.Parse("https://example.com/fail")
|
||||
require.NoError(t, err)
|
||||
|
||||
intent := domain.IDPIntent{
|
||||
ID: gofakeit.UUID(),
|
||||
InstanceID: instanceID,
|
||||
SuccessURL: successURL,
|
||||
FailureURL: failURL,
|
||||
IDPID: idpID,
|
||||
IDPArguments: map[string]any{"arg1": map[string]any{"k1": 1, "k2": "v2"}},
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
idpIntentRepo := repository.IDPIntentRepository()
|
||||
err = idpIntentRepo.Create(t.Context(), tx, &intent)
|
||||
require.NoError(t, err)
|
||||
|
||||
return intent.ID
|
||||
}
|
||||
|
||||
@@ -365,9 +365,9 @@ func TestServer_RetrieveIdentityProviderIntent(t *testing.T) {
|
||||
ldapSuccessfulWithUserID, ldapWithUserToken, ldapWithUserChangeDate, ldapWithUserSequence, err := sink.SuccessfulLDAPIntent(Instance.ID(), ldapIdpID, "id", "user")
|
||||
require.NoError(t, err)
|
||||
|
||||
samlSuccessfulID, samlToken, samlChangeDate, samlSequence, err := sink.SuccessfulSAMLIntent(Instance.ID(), samlIdpID, "id", "", expiry)
|
||||
samlSuccessfulID, samlToken, samlChangeDate, samlSequence, err := sink.SuccessfulSAMLIntent(Instance.ID(), samlIdpID, "id", "", "", expiry)
|
||||
require.NoError(t, err)
|
||||
samlSuccessfulWithUserID, samlWithUserToken, samlWithUserChangeDate, samlWithUserSequence, err := sink.SuccessfulSAMLIntent(Instance.ID(), samlIdpID, "id", "user", expiry)
|
||||
samlSuccessfulWithUserID, samlWithUserToken, samlWithUserChangeDate, samlWithUserSequence, err := sink.SuccessfulSAMLIntent(Instance.ID(), samlIdpID, "id", "user", "", expiry)
|
||||
require.NoError(t, err)
|
||||
|
||||
jwtSuccessfulID, jwtToken, jwtChangeDate, jwtSequence, err := sink.SuccessfulJWTIntent(Instance.ID(), jwtIdPID, "id", "", expiry)
|
||||
|
||||
@@ -2245,9 +2245,9 @@ func TestServer_RetrieveIdentityProviderIntent(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
ldapSuccessfulWithUserID, ldapWithUserToken, ldapWithUserChangeDate, ldapWithUserSequence, err := sink.SuccessfulLDAPIntent(Instance.ID(), ldapIdpID, "id", "user")
|
||||
require.NoError(t, err)
|
||||
samlSuccessfulID, samlToken, samlChangeDate, samlSequence, err := sink.SuccessfulSAMLIntent(Instance.ID(), samlIdpID, "id", "", expiry)
|
||||
samlSuccessfulID, samlToken, samlChangeDate, samlSequence, err := sink.SuccessfulSAMLIntent(Instance.ID(), samlIdpID, "id", "", "", expiry)
|
||||
require.NoError(t, err)
|
||||
samlSuccessfulWithUserID, samlWithUserToken, samlWithUserChangeDate, samlWithUserSequence, err := sink.SuccessfulSAMLIntent(Instance.ID(), samlIdpID, "id", "user", expiry)
|
||||
samlSuccessfulWithUserID, samlWithUserToken, samlWithUserChangeDate, samlWithUserSequence, err := sink.SuccessfulSAMLIntent(Instance.ID(), samlIdpID, "id", "user", "", expiry)
|
||||
require.NoError(t, err)
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
//go:build !integration
|
||||
|
||||
package ldap
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/go-ldap/ldap/v3"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/idp"
|
||||
)
|
||||
|
||||
// FetchUser implements the [idp.Session] interface.
|
||||
func (s *Session) FetchUser(_ context.Context) (_ idp.User, err error) {
|
||||
var user *ldap.Entry
|
||||
for _, server := range s.Provider.servers {
|
||||
user, err = tryBind(server,
|
||||
s.Provider.startTLS,
|
||||
s.Provider.bindDN,
|
||||
s.Provider.bindPassword,
|
||||
s.Provider.baseDN,
|
||||
s.Provider.getNecessaryAttributes(),
|
||||
s.Provider.userObjectClasses,
|
||||
s.Provider.userFilters,
|
||||
s.User,
|
||||
s.Password,
|
||||
s.Provider.timeout,
|
||||
s.Provider.rootCA)
|
||||
// If there were invalid credentials or multiple users with the credentials cancel process
|
||||
if err != nil && (errors.Is(err, ErrFailedLogin) || errors.Is(err, ErrNoSingleUser)) {
|
||||
return nil, err
|
||||
}
|
||||
// If a user bind was successful and user is filled continue with login, otherwise try next server
|
||||
if err == nil && user != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.Entry = user
|
||||
|
||||
return mapLDAPEntryToUser(
|
||||
user,
|
||||
s.Provider.idAttribute,
|
||||
s.Provider.firstNameAttribute,
|
||||
s.Provider.lastNameAttribute,
|
||||
s.Provider.displayNameAttribute,
|
||||
s.Provider.nickNameAttribute,
|
||||
s.Provider.preferredUsernameAttribute,
|
||||
s.Provider.emailAttribute,
|
||||
s.Provider.emailVerifiedAttribute,
|
||||
s.Provider.phoneAttribute,
|
||||
s.Provider.phoneVerifiedAttribute,
|
||||
s.Provider.preferredLanguageAttribute,
|
||||
s.Provider.avatarURLAttribute,
|
||||
s.Provider.profileAttribute,
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
//go:build integration
|
||||
|
||||
package ldap
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/brianvoe/gofakeit/v6"
|
||||
"github.com/go-ldap/ldap/v3"
|
||||
"golang.org/x/text/language"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/idp"
|
||||
)
|
||||
|
||||
const (
|
||||
FailingUser = "IAM FAIL"
|
||||
FailingPsw = "test failed successfully"
|
||||
)
|
||||
|
||||
// FetchUser implements the [idp.Session] interface.
|
||||
//
|
||||
// This is a mock implementation of [idp.Session.FetchUser] that is used
|
||||
// during integration tests and substitutes the real [idp.Session.FetchUser]
|
||||
// implementation.
|
||||
//
|
||||
// This has been done to allow writing integration tests against a
|
||||
// fake (external) LDAP server.
|
||||
//
|
||||
// The substitution/overloading is done through build tags.
|
||||
//
|
||||
// When the LDAP start intent is called with `username = [FailingUser]` and
|
||||
// `password = [FailingPsw]`, the method return [ErrFailedLogin] to be able
|
||||
// to mock an intent failed event.
|
||||
func (s *Session) FetchUser(_ context.Context) (_ idp.User, err error) {
|
||||
if s.User == FailingUser && s.Password == FailingPsw {
|
||||
return nil, ErrFailedLogin
|
||||
}
|
||||
|
||||
usr := NewUser(
|
||||
gofakeit.UUID(),
|
||||
gofakeit.FirstName(),
|
||||
gofakeit.LastName(),
|
||||
gofakeit.Name(),
|
||||
gofakeit.Name(),
|
||||
gofakeit.Name(),
|
||||
domain.EmailAddress(gofakeit.Email()),
|
||||
true,
|
||||
domain.PhoneNumber(gofakeit.Phone()),
|
||||
true,
|
||||
language.English,
|
||||
gofakeit.URL(),
|
||||
gofakeit.UUID(),
|
||||
)
|
||||
|
||||
s.Entry = &ldap.Entry{
|
||||
DN: s.Provider.baseDN,
|
||||
Attributes: []*ldap.EntryAttribute{
|
||||
{Name: "uid", Values: []string{gofakeit.UUID()}},
|
||||
{Name: "givenName", Values: []string{gofakeit.FirstName()}},
|
||||
{Name: "sn", Values: []string{gofakeit.LastName()}},
|
||||
{Name: "mail", Values: []string{gofakeit.Email()}},
|
||||
{Name: "telephoneNumber", Values: []string{gofakeit.Phone()}},
|
||||
{Name: "displayName", Values: []string{gofakeit.Name()}},
|
||||
},
|
||||
}
|
||||
return usr, nil
|
||||
}
|
||||
@@ -245,6 +245,7 @@ func (p *Provider) IsAutoUpdate() bool {
|
||||
return p.isAutoUpdate
|
||||
}
|
||||
|
||||
// Do not remove, used in [Session.FetchUser] @ fetch_user.go
|
||||
func (p *Provider) getNecessaryAttributes() []string {
|
||||
attributes := []string{p.userBase}
|
||||
if p.idAttribute != "" {
|
||||
|
||||
@@ -48,58 +48,11 @@ func (s *Session) PersistentParameters() map[string]any {
|
||||
return nil
|
||||
}
|
||||
|
||||
// FetchUser implements the [idp.Session] interface.
|
||||
func (s *Session) FetchUser(_ context.Context) (_ idp.User, err error) {
|
||||
var user *ldap.Entry
|
||||
for _, server := range s.Provider.servers {
|
||||
user, err = tryBind(server,
|
||||
s.Provider.startTLS,
|
||||
s.Provider.bindDN,
|
||||
s.Provider.bindPassword,
|
||||
s.Provider.baseDN,
|
||||
s.Provider.getNecessaryAttributes(),
|
||||
s.Provider.userObjectClasses,
|
||||
s.Provider.userFilters,
|
||||
s.User,
|
||||
s.Password,
|
||||
s.Provider.timeout,
|
||||
s.Provider.rootCA)
|
||||
// If there were invalid credentials or multiple users with the credentials cancel process
|
||||
if err != nil && (errors.Is(err, ErrFailedLogin) || errors.Is(err, ErrNoSingleUser)) {
|
||||
return nil, err
|
||||
}
|
||||
// If a user bind was successful and user is filled continue with login, otherwise try next server
|
||||
if err == nil && user != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.Entry = user
|
||||
|
||||
return mapLDAPEntryToUser(
|
||||
user,
|
||||
s.Provider.idAttribute,
|
||||
s.Provider.firstNameAttribute,
|
||||
s.Provider.lastNameAttribute,
|
||||
s.Provider.displayNameAttribute,
|
||||
s.Provider.nickNameAttribute,
|
||||
s.Provider.preferredUsernameAttribute,
|
||||
s.Provider.emailAttribute,
|
||||
s.Provider.emailVerifiedAttribute,
|
||||
s.Provider.phoneAttribute,
|
||||
s.Provider.phoneVerifiedAttribute,
|
||||
s.Provider.preferredLanguageAttribute,
|
||||
s.Provider.avatarURLAttribute,
|
||||
s.Provider.profileAttribute,
|
||||
)
|
||||
}
|
||||
|
||||
func (s *Session) ExpiresAt() time.Time {
|
||||
return time.Time{} // falls back to the default expiration time
|
||||
}
|
||||
|
||||
// Do not remove, used in [Session.FetchUser] @ fetch_user.go
|
||||
func tryBind(
|
||||
server string,
|
||||
startTLS bool,
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
@@ -108,7 +109,12 @@ func SuccessfulOIDCIntent(instanceID, idpID, idpUserID, userID string, expiry ti
|
||||
return resp.IntentID, resp.Token, resp.ChangeDate, resp.Sequence, nil
|
||||
}
|
||||
|
||||
func SuccessfulSAMLIntent(instanceID, idpID, idpUserID, userID string, expiry time.Time) (string, string, time.Time, uint64, error) {
|
||||
// SuccessfulSAMLIntent creates a SAMLIntent and then executes the flow to put it in
|
||||
// successful state.
|
||||
//
|
||||
// If intentID is not empty, the intent creation is skipped and only the flow part to switch
|
||||
// the intent to a successful state is executed.
|
||||
func SuccessfulSAMLIntent(instanceID, idpID, idpUserID, userID, intentID string, expiry time.Time) (string, string, time.Time, uint64, error) {
|
||||
u := url.URL{
|
||||
Scheme: "http",
|
||||
Host: host,
|
||||
@@ -120,6 +126,7 @@ func SuccessfulSAMLIntent(instanceID, idpID, idpUserID, userID string, expiry ti
|
||||
IDPUserID: idpUserID,
|
||||
UserID: userID,
|
||||
Expiry: expiry,
|
||||
IntentID: intentID,
|
||||
})
|
||||
if err != nil {
|
||||
return "", "", time.Time{}, uint64(0), err
|
||||
@@ -341,6 +348,7 @@ type SuccessfulIntentRequest struct {
|
||||
IDPUserID string `json:"idp_user_id"`
|
||||
UserID string `json:"user_id"`
|
||||
Expiry time.Time `json:"expiry"`
|
||||
IntentID string `json:"intent_id"`
|
||||
}
|
||||
type SuccessfulIntentResponse struct {
|
||||
IntentID string `json:"intent_id"`
|
||||
@@ -519,7 +527,10 @@ func createSuccessfulOIDCIntent(ctx context.Context, cmd *command.Commands, req
|
||||
}
|
||||
|
||||
func createSuccessfulSAMLIntent(ctx context.Context, cmd *command.Commands, req *SuccessfulIntentRequest) (*SuccessfulIntentResponse, error) {
|
||||
intentID, err := createIntent(ctx, cmd, req.InstanceID, req.IDPID)
|
||||
intentID := strings.TrimSpace(req.IntentID)
|
||||
if intentID == "" {
|
||||
intentID, _ = createIntent(ctx, cmd, req.InstanceID, req.IDPID)
|
||||
}
|
||||
writeModel, err := cmd.GetIntentWriteModel(ctx, intentID, req.InstanceID)
|
||||
|
||||
idpUser := &saml.UserMapper{
|
||||
|
||||
@@ -0,0 +1,257 @@
|
||||
package projection
|
||||
|
||||
// TODO(adlerhurst): Uncomment once users table is created and FK can be enforced
|
||||
|
||||
// import (
|
||||
// "context"
|
||||
// "database/sql"
|
||||
// "encoding/json"
|
||||
|
||||
// "github.com/zitadel/zitadel/backend/v3/domain"
|
||||
// "github.com/zitadel/zitadel/backend/v3/storage/database"
|
||||
// v3_sql "github.com/zitadel/zitadel/backend/v3/storage/database/dialect/sql"
|
||||
// "github.com/zitadel/zitadel/backend/v3/storage/database/repository"
|
||||
// "github.com/zitadel/zitadel/internal/eventstore"
|
||||
// "github.com/zitadel/zitadel/internal/eventstore/handler/v2"
|
||||
// "github.com/zitadel/zitadel/internal/repository/idpintent"
|
||||
// "github.com/zitadel/zitadel/internal/zerrors"
|
||||
// )
|
||||
|
||||
// const (
|
||||
// IDPIntentRelationalProjectionTable = "zitadel.identity_provider_intents"
|
||||
// )
|
||||
|
||||
// type idpIntentRelationalProjection struct{}
|
||||
|
||||
// func (*idpIntentRelationalProjection) Name() string {
|
||||
// return IDPIntentRelationalProjectionTable
|
||||
// }
|
||||
|
||||
// func newIDPIntentRelationalProjection(ctx context.Context, config handler.Config) *handler.Handler {
|
||||
// return handler.NewHandler(ctx, &config, new(idpIntentRelationalProjection))
|
||||
// }
|
||||
|
||||
// // Reducers implements [handler.Projection].
|
||||
// func (i *idpIntentRelationalProjection) Reducers() []handler.AggregateReducer {
|
||||
// return []handler.AggregateReducer{
|
||||
// {
|
||||
// Aggregate: idpintent.AggregateType,
|
||||
// EventReducers: []handler.EventReducer{
|
||||
// {Event: idpintent.StartedEventType, Reduce: i.reduceStartedEvent},
|
||||
// {Event: idpintent.SucceededEventType, Reduce: i.reduceSucceededEvent},
|
||||
// {Event: idpintent.SAMLSucceededEventType, Reduce: i.reduceSAMLSucceededEvent},
|
||||
// {Event: idpintent.SAMLRequestEventType, Reduce: i.reduceSAMLRequestEvent},
|
||||
// {Event: idpintent.LDAPSucceededEventType, Reduce: i.reduceLDAPSucceededEvent},
|
||||
// {Event: idpintent.FailedEventType, Reduce: i.reduceFailedEvent},
|
||||
// {Event: idpintent.ConsumedEventType, Reduce: i.reduceConsumedEvent},
|
||||
// },
|
||||
// },
|
||||
// }
|
||||
|
||||
// }
|
||||
|
||||
// func (i *idpIntentRelationalProjection) reduceStartedEvent(evt eventstore.Event) (*handler.Statement, error) {
|
||||
// e, ok := evt.(*idpintent.StartedEvent)
|
||||
// if !ok {
|
||||
// return nil, zerrors.ThrowInternalf(nil, "HANDL-Lqj3HB", "reduce.wrong.event.type %s", idpintent.StartedEventType)
|
||||
// }
|
||||
// return handler.NewStatement(e, func(ctx context.Context, ex handler.Executer, _ string) error {
|
||||
// tx, ok := ex.(*sql.Tx)
|
||||
// if !ok {
|
||||
// return zerrors.ThrowInvalidArgumentf(nil, "HANDL-rirFQm", "reduce.wrong.db.pool %T", ex)
|
||||
// }
|
||||
|
||||
// repo := repository.IDPIntentRepository()
|
||||
// return repo.Create(ctx, v3_sql.SQLTx(tx), &domain.IDPIntent{
|
||||
// ID: e.Aggregate().ID,
|
||||
// InstanceID: e.Aggregate().InstanceID,
|
||||
// State: domain.IDPIntentStateStarted,
|
||||
// SuccessURL: e.SuccessURL,
|
||||
// FailureURL: e.FailureURL,
|
||||
// CreatedAt: e.CreationDate(),
|
||||
// UpdatedAt: e.CreationDate(),
|
||||
// IDPID: e.IDPID,
|
||||
// IDPArguments: e.IDPArguments,
|
||||
// })
|
||||
// }), nil
|
||||
// }
|
||||
|
||||
// func (i *idpIntentRelationalProjection) reduceSucceededEvent(evt eventstore.Event) (*handler.Statement, error) {
|
||||
// e, ok := evt.(*idpintent.SucceededEvent)
|
||||
// if !ok {
|
||||
// return nil, zerrors.ThrowInternalf(nil, "HANDL-hQSxj0", "reduce.wrong.event.type %s", idpintent.SucceededEventType)
|
||||
// }
|
||||
// return handler.NewStatement(e, func(ctx context.Context, ex handler.Executer, _ string) error {
|
||||
// tx, ok := ex.(*sql.Tx)
|
||||
// if !ok {
|
||||
// return zerrors.ThrowInvalidArgumentf(nil, "HANDL-ry4tFH", "reduce.wrong.db.pool %T", ex)
|
||||
// }
|
||||
// idpAccessToken, err := e.IDPAccessToken.Value()
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// jsonIDPAccessToken, ok := idpAccessToken.([]byte)
|
||||
// if !ok {
|
||||
// return zerrors.ThrowInternal(nil, "HANDL-32XE1L", "reduce.convertion.json.failed")
|
||||
// }
|
||||
// repo := repository.IDPIntentRepository()
|
||||
|
||||
// changes := []database.Change{
|
||||
// repo.SetState(domain.IDPIntentStateSucceeded),
|
||||
// repo.SetIDPUser(e.IDPUser),
|
||||
// repo.SetIDPUserID(e.IDPUserID),
|
||||
// repo.SetIDPUsername(e.IDPUserName),
|
||||
// repo.SetUserID(e.UserID),
|
||||
// repo.SetIDPAccessToken(jsonIDPAccessToken),
|
||||
// repo.SetIDPIDToken(e.IDPIDToken),
|
||||
// repo.SetSucceededAt(e.CreationDate()),
|
||||
// repo.SetExpiresAt(e.ExpiresAt),
|
||||
// }
|
||||
|
||||
// _, err = repo.Update(
|
||||
// ctx,
|
||||
// v3_sql.SQLTx(tx),
|
||||
// repo.PrimaryKeyCondition(e.Aggregate().InstanceID, e.Aggregate().ID),
|
||||
// changes...,
|
||||
// )
|
||||
// return err
|
||||
// }), nil
|
||||
// }
|
||||
|
||||
// func (i *idpIntentRelationalProjection) reduceSAMLSucceededEvent(evt eventstore.Event) (*handler.Statement, error) {
|
||||
// e, ok := evt.(*idpintent.SAMLSucceededEvent)
|
||||
// if !ok {
|
||||
// return nil, zerrors.ThrowInternalf(nil, "HANDL-t2uaiv", "reduce.wrong.event.type %s", idpintent.SAMLSucceededEventType)
|
||||
// }
|
||||
// return handler.NewStatement(e, func(ctx context.Context, ex handler.Executer, _ string) error {
|
||||
// tx, ok := ex.(*sql.Tx)
|
||||
// if !ok {
|
||||
// return zerrors.ThrowInvalidArgumentf(nil, "HANDL-0QGPXm", "reduce.wrong.db.pool %T", ex)
|
||||
// }
|
||||
// assertion, err := e.Assertion.Value()
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// jsonAssertion, ok := assertion.([]byte)
|
||||
// if !ok {
|
||||
// return zerrors.ThrowInternal(nil, "HANDL-1TFjTi", "reduce.convertion.json.failed")
|
||||
// }
|
||||
|
||||
// repo := repository.IDPIntentRepository()
|
||||
// _, err = repo.Update(
|
||||
// ctx,
|
||||
// v3_sql.SQLTx(tx),
|
||||
// repo.PrimaryKeyCondition(e.Aggregate().InstanceID, e.Aggregate().ID),
|
||||
// repo.SetState(domain.IDPIntentStateSucceeded),
|
||||
// repo.SetIDPUser(e.IDPUser),
|
||||
// repo.SetIDPUserID(e.IDPUserID),
|
||||
// repo.SetIDPUsername(e.IDPUserName),
|
||||
// repo.SetUserID(e.UserID),
|
||||
// repo.SetAssertion(jsonAssertion),
|
||||
// repo.SetSucceededAt(e.CreationDate()),
|
||||
// repo.SetExpiresAt(e.ExpiresAt),
|
||||
// )
|
||||
// return err
|
||||
// }), nil
|
||||
// }
|
||||
|
||||
// func (i *idpIntentRelationalProjection) reduceSAMLRequestEvent(evt eventstore.Event) (*handler.Statement, error) {
|
||||
// e, ok := evt.(*idpintent.SAMLRequestEvent)
|
||||
// if !ok {
|
||||
// return nil, zerrors.ThrowInternalf(nil, "HANDL-f7O6K2", "reduce.wrong.event.type %s", idpintent.SAMLRequestEventType)
|
||||
// }
|
||||
// return handler.NewStatement(e, func(ctx context.Context, ex handler.Executer, _ string) error {
|
||||
// tx, ok := ex.(*sql.Tx)
|
||||
// if !ok {
|
||||
// return zerrors.ThrowInvalidArgumentf(nil, "HANDL-ijgAPJ", "reduce.wrong.db.pool %T", ex)
|
||||
// }
|
||||
|
||||
// repo := repository.IDPIntentRepository()
|
||||
// _, err := repo.Update(
|
||||
// ctx,
|
||||
// v3_sql.SQLTx(tx),
|
||||
// repo.PrimaryKeyCondition(e.Aggregate().InstanceID, e.Aggregate().ID),
|
||||
// repo.SetRequestID(e.RequestID),
|
||||
// )
|
||||
// return err
|
||||
// }), nil
|
||||
// }
|
||||
|
||||
// func (i *idpIntentRelationalProjection) reduceLDAPSucceededEvent(evt eventstore.Event) (*handler.Statement, error) {
|
||||
// e, ok := evt.(*idpintent.LDAPSucceededEvent)
|
||||
// if !ok {
|
||||
// return nil, zerrors.ThrowInternalf(nil, "HANDL-Gw8ddW", "reduce.wrong.event.type %s", idpintent.LDAPSucceededEventType)
|
||||
// }
|
||||
// return handler.NewStatement(e, func(ctx context.Context, ex handler.Executer, _ string) error {
|
||||
// tx, ok := ex.(*sql.Tx)
|
||||
// if !ok {
|
||||
// return zerrors.ThrowInvalidArgumentf(nil, "HANDL-tNp8aa", "reduce.wrong.db.pool %T", ex)
|
||||
// }
|
||||
|
||||
// jsonAttributes, err := json.Marshal(e.EntryAttributes)
|
||||
// if err != nil {
|
||||
// return zerrors.ThrowInternal(nil, "HANDL-LuvCvD", "reduce.convertion.json.failed")
|
||||
// }
|
||||
|
||||
// repo := repository.IDPIntentRepository()
|
||||
// _, err = repo.Update(
|
||||
// ctx,
|
||||
// v3_sql.SQLTx(tx),
|
||||
// repo.PrimaryKeyCondition(e.Aggregate().InstanceID, e.Aggregate().ID),
|
||||
// repo.SetState(domain.IDPIntentStateSucceeded),
|
||||
// repo.SetIDPUser(e.IDPUser),
|
||||
// repo.SetIDPUserID(e.IDPUserID),
|
||||
// repo.SetIDPUsername(e.IDPUserName),
|
||||
// repo.SetUserID(e.UserID),
|
||||
// repo.SetIDPEntryAttributes(jsonAttributes),
|
||||
// repo.SetSucceededAt(e.CreationDate()),
|
||||
// repo.SetExpiresAt(e.ExpiresAt),
|
||||
// )
|
||||
// return err
|
||||
// }), nil
|
||||
// }
|
||||
|
||||
// func (i *idpIntentRelationalProjection) reduceFailedEvent(evt eventstore.Event) (*handler.Statement, error) {
|
||||
// e, ok := evt.(*idpintent.FailedEvent)
|
||||
// if !ok {
|
||||
// return nil, zerrors.ThrowInternalf(nil, "HANDL-x3o9LZ", "reduce.wrong.event.type %s", idpintent.FailedEventType)
|
||||
// }
|
||||
// return handler.NewStatement(e, func(ctx context.Context, ex handler.Executer, _ string) error {
|
||||
// tx, ok := ex.(*sql.Tx)
|
||||
// if !ok {
|
||||
// return zerrors.ThrowInvalidArgumentf(nil, "HANDL-iqmR8O", "reduce.wrong.db.pool %T", ex)
|
||||
// }
|
||||
|
||||
// repo := repository.IDPIntentRepository()
|
||||
// _, err := repo.Update(
|
||||
// ctx,
|
||||
// v3_sql.SQLTx(tx),
|
||||
// repo.PrimaryKeyCondition(e.Aggregate().InstanceID, e.Aggregate().ID),
|
||||
// repo.SetState(domain.IDPIntentStateFailed),
|
||||
// repo.SetFailReason(e.Reason),
|
||||
// repo.SetFailedAt(e.CreatedAt()),
|
||||
// )
|
||||
// return err
|
||||
// }), nil
|
||||
// }
|
||||
|
||||
// func (i *idpIntentRelationalProjection) reduceConsumedEvent(evt eventstore.Event) (*handler.Statement, error) {
|
||||
// e, ok := evt.(*idpintent.ConsumedEvent)
|
||||
// if !ok {
|
||||
// return nil, zerrors.ThrowInternalf(nil, "HANDL-jHZs6T", "reduce.wrong.event.type %s", idpintent.ConsumedEventType)
|
||||
// }
|
||||
// return handler.NewStatement(e, func(ctx context.Context, ex handler.Executer, _ string) error {
|
||||
// tx, ok := ex.(*sql.Tx)
|
||||
// if !ok {
|
||||
// return zerrors.ThrowInvalidArgumentf(nil, "HANDL-8X9U9w", "reduce.wrong.db.pool %T", ex)
|
||||
// }
|
||||
|
||||
// repo := repository.IDPIntentRepository()
|
||||
// _, err := repo.Delete(
|
||||
// ctx,
|
||||
// v3_sql.SQLTx(tx),
|
||||
// repo.PrimaryKeyCondition(e.Aggregate().InstanceID, e.Aggregate().ID),
|
||||
// )
|
||||
// return err
|
||||
// }), nil
|
||||
// }
|
||||
@@ -101,6 +101,8 @@ var (
|
||||
ProjectGrantRelationalProjection *handler.Handler
|
||||
SessionRelationalProjection *handler.Handler
|
||||
SettingsRelationalProjection *handler.Handler
|
||||
// TODO(adlerhurst): Uncomment once users table is created and FK can be enforced
|
||||
// IDPIntentRelationalProjection *handler.Handler
|
||||
|
||||
ProjectGrantFields *handler.FieldHandler
|
||||
OrgDomainVerifiedFields *handler.FieldHandler
|
||||
@@ -230,6 +232,8 @@ func Create(ctx context.Context, sqlClient *database.DB, es handler.EventStore,
|
||||
AuthorizationRelationalProjection = newAuthorizationRelationalProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["authorizations_relational"]))
|
||||
ProjectGrantRelationalProjection = newProjectGrantRelationalProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["project_grant_relational"]))
|
||||
SessionRelationalProjection = newSessionRelationalProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["session_relational"]))
|
||||
// TODO(adlerhurst): Uncomment once users table is created and FK can be enforced
|
||||
// IDPIntentRelationalProjection = newIDPIntentRelationalProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["idp_intent_relational"]))
|
||||
|
||||
newProjectionsList()
|
||||
newFieldsList()
|
||||
@@ -427,5 +431,7 @@ func newProjectionsList() {
|
||||
AuthorizationRelationalProjection,
|
||||
ProjectGrantRelationalProjection,
|
||||
SessionRelationalProjection,
|
||||
// TODO(adlerhurst): Uncomment once users table is created and FK can be enforced
|
||||
// IDPIntentRelationalProjection,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user