merge main into v3.x

This commit is contained in:
Livio Spring
2025-03-27 10:42:13 +01:00
38 changed files with 706 additions and 166 deletions

View File

@@ -27,6 +27,7 @@ COPY --from=artifact /etc/ssl/certs /etc/ssl/certs
COPY --from=artifact /app/zitadel /app/zitadel
HEALTHCHECK NONE
EXPOSE 8080
USER zitadel
ENTRYPOINT ["/app/zitadel"]
ENTRYPOINT ["/app/zitadel"]

View File

@@ -2,36 +2,26 @@ package setup
import (
"context"
"embed"
"fmt"
"github.com/zitadel/logging"
_ "embed"
"github.com/zitadel/zitadel/internal/database"
"github.com/zitadel/zitadel/internal/eventstore"
)
type InitPermittedOrgsFunction52 struct {
var (
//go:embed 52.sql
renameTableIfNotExisting string
)
type IDPTemplate6LDAP2 struct {
dbClient *database.DB
}
//go:embed 52/*.sql
var permittedOrgsFunction52 embed.FS
func (mig *InitPermittedOrgsFunction52) Execute(ctx context.Context, _ eventstore.Event) error {
statements, err := readStatements(permittedOrgsFunction52, "52")
if err != nil {
return err
}
for _, stmt := range statements {
logging.WithFields("file", stmt.file, "migration", mig.String()).Info("execute statement")
if _, err := mig.dbClient.ExecContext(ctx, stmt.query); err != nil {
return fmt.Errorf("%s %s: %w", mig.String(), stmt.file, err)
}
}
return nil
func (mig *IDPTemplate6LDAP2) Execute(ctx context.Context, _ eventstore.Event) error {
_, err := mig.dbClient.ExecContext(ctx, renameTableIfNotExisting)
return err
}
func (*InitPermittedOrgsFunction52) String() string {
return "52_init_permitted_orgs_function"
func (mig *IDPTemplate6LDAP2) String() string {
return "52_idp_templates6_ldap2"
}

2
cmd/setup/52.sql Normal file
View File

@@ -0,0 +1,2 @@
ALTER TABLE IF EXISTS projections.idp_templates6_ldap3 RENAME COLUMN rootCA TO root_ca;
ALTER TABLE IF EXISTS projections.idp_templates6_ldap3 RENAME TO idp_templates6_ldap2;

37
cmd/setup/53.go Normal file
View File

@@ -0,0 +1,37 @@
package setup
import (
"context"
"embed"
"fmt"
"github.com/zitadel/logging"
"github.com/zitadel/zitadel/internal/database"
"github.com/zitadel/zitadel/internal/eventstore"
)
type InitPermittedOrgsFunction53 struct {
dbClient *database.DB
}
//go:embed 53/*.sql
var permittedOrgsFunction53 embed.FS
func (mig *InitPermittedOrgsFunction53) Execute(ctx context.Context, _ eventstore.Event) error {
statements, err := readStatements(permittedOrgsFunction53, "53")
if err != nil {
return err
}
for _, stmt := range statements {
logging.WithFields("file", stmt.file, "migration", mig.String()).Info("execute statement")
if _, err := mig.dbClient.ExecContext(ctx, stmt.query); err != nil {
return fmt.Errorf("%s %s: %w", mig.String(), stmt.file, err)
}
}
return nil
}
func (*InitPermittedOrgsFunction53) String() string {
return "53_init_permitted_orgs_function"
}

View File

@@ -27,6 +27,7 @@ import (
"github.com/zitadel/zitadel/internal/notification/handlers"
"github.com/zitadel/zitadel/internal/query/projection"
static_config "github.com/zitadel/zitadel/internal/static/config"
metrics "github.com/zitadel/zitadel/internal/telemetry/metrics/config"
)
type Config struct {
@@ -40,6 +41,7 @@ type Config struct {
ExternalPort uint16
ExternalSecure bool
Log *logging.Config
Metrics metrics.Config
EncryptionKeys *encryption.EncryptionKeyConfig
DefaultInstance command.InstanceSetup
Machine *id.Config
@@ -88,6 +90,9 @@ func MustNewConfig(v *viper.Viper) *Config {
err = config.Log.SetLogger()
logging.OnError(err).Fatal("unable to set logger")
err = config.Metrics.NewMeter()
logging.OnError(err).Fatal("unable to set meter")
id.Configure(config.Machine)
// Copy the global role permissions mappings to the instance until we allow instance-level configuration over the API.
@@ -143,7 +148,8 @@ type Steps struct {
s49InitPermittedOrgsFunction *InitPermittedOrgsFunction
s50IDPTemplate6UsePKCE *IDPTemplate6UsePKCE
s51IDPTemplate6RootCA *IDPTemplate6RootCA
s52InitPermittedOrgsFunction *InitPermittedOrgsFunction52
s52IDPTemplate6LDAP2 *IDPTemplate6LDAP2
s53InitPermittedOrgsFunction *InitPermittedOrgsFunction52
}
func MustNewSteps(v *viper.Viper) *Steps {

View File

@@ -178,7 +178,8 @@ func Setup(ctx context.Context, config *Config, steps *Steps, masterKey string)
steps.s49InitPermittedOrgsFunction = &InitPermittedOrgsFunction{eventstoreClient: dbClient}
steps.s50IDPTemplate6UsePKCE = &IDPTemplate6UsePKCE{dbClient: dbClient}
steps.s51IDPTemplate6RootCA = &IDPTemplate6RootCA{dbClient: dbClient}
steps.s52InitPermittedOrgsFunction = &InitPermittedOrgsFunction52{dbClient: dbClient}
steps.s52IDPTemplate6LDAP2 = &IDPTemplate6LDAP2{dbClient: dbClient}
steps.s53InitPermittedOrgsFunction = &InitPermittedOrgsFunction52{dbClient: dbClient}
err = projection.Create(ctx, dbClient, eventstoreClient, config.Projections, nil, nil, nil)
logging.OnError(err).Fatal("unable to start projections")
@@ -219,7 +220,8 @@ func Setup(ctx context.Context, config *Config, steps *Steps, masterKey string)
steps.s49InitPermittedOrgsFunction,
steps.s50IDPTemplate6UsePKCE,
steps.s51IDPTemplate6RootCA,
steps.s52InitPermittedOrgsFunction,
steps.s52IDPTemplate6LDAP2,
steps.s53InitPermittedOrgsFunction,
} {
mustExecuteMigration(ctx, eventstoreClient, step, "migration failed")
}

View File

@@ -3464,7 +3464,7 @@
"@zitadel/proto" "1.0.4"
jose "^5.3.0"
"@zitadel/proto@1.0.4", "@zitadel/proto@^1.0.4":
"@zitadel/proto@1.0.4":
version "1.0.4"
resolved "https://registry.yarnpkg.com/@zitadel/proto/-/proto-1.0.4.tgz#e2fe9895f2960643c3619191255aa2f4913ad873"
integrity sha512-s13ZMhuOTe0b+geV+JgJud+kpYdq7TgkuCe7RIY+q4Xs5KC0FHMKfvbAk/jpFbD+TSQHiwo/TBNZlGHdwUR9Ig==

View File

@@ -8156,9 +8156,9 @@ mz@^2.7.0:
thenify-all "^1.0.0"
nanoid@^3.3.7:
version "3.3.7"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8"
integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==
version "3.3.8"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.8.tgz#b1be3030bee36aaff18bacb375e5cce521684baf"
integrity sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==
negotiator@0.6.3:
version "0.6.3"

View File

@@ -181,9 +181,9 @@ aws4@^1.8.0:
integrity sha512-u5w79Rd7SU4JaIlA/zFqG+gOiuq25q5VLyZ8E+ijJeILuTxVzZgp2CaGw/UTw6pXYN9XMO9yiqj/nEHmhTG5CA==
axios@^1.6.1:
version "1.7.4"
resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.4.tgz#4c8ded1b43683c8dd362973c393f3ede24052aa2"
integrity sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==
version "1.8.4"
resolved "https://registry.yarnpkg.com/axios/-/axios-1.8.4.tgz#78990bb4bc63d2cae072952d374835950a82f447"
integrity sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==
dependencies:
follow-redirects "^1.15.6"
form-data "^4.0.0"

2
go.mod
View File

@@ -74,7 +74,7 @@ require (
github.com/zitadel/logging v0.6.2
github.com/zitadel/oidc/v3 v3.36.1
github.com/zitadel/passwap v0.7.0
github.com/zitadel/saml v0.3.4
github.com/zitadel/saml v0.3.5
github.com/zitadel/schema v1.3.1
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0

4
go.sum
View File

@@ -809,8 +809,8 @@ github.com/zitadel/oidc/v3 v3.36.1 h1:1AT1NqKKEqAwx4GmKJZ9fYkWH2WIn/VKMfQ46nBtRf
github.com/zitadel/oidc/v3 v3.36.1/go.mod h1:dApGZLvWZTHRuxmcbQlW5d2XVjVYR3vGOdq536igmTs=
github.com/zitadel/passwap v0.7.0 h1:TQTr9TV75PLATGICor1g5hZDRNHRvB9t0Hn4XkiR7xQ=
github.com/zitadel/passwap v0.7.0/go.mod h1:/NakQNYahdU+YFEitVD6mlm8BLfkiIT+IM5wgClRoAY=
github.com/zitadel/saml v0.3.4 h1:L2pybnx2Hs+kqebZmUbnZUd9L/CY2sNw5psMWw2D/6Q=
github.com/zitadel/saml v0.3.4/go.mod h1:M0losAULJpLtAmXrYqBnf375ia2rMgJ75b1mpaU/GlA=
github.com/zitadel/saml v0.3.5 h1:L1RKWS5y66cGepVxUGjx/WSBOtrtSpRA/J3nn5BJLOY=
github.com/zitadel/saml v0.3.5/go.mod h1:ybs3e4tIWdYgSYBpuCsvf3T4FNDfbXYM+GPv5vIpHYk=
github.com/zitadel/schema v1.3.1 h1:QT3kwiRIRXXLVAs6gCK/u044WmUVh6IlbLXUsn6yRQU=
github.com/zitadel/schema v1.3.1/go.mod h1:071u7D2LQacy1HAN+YnMd/mx1qVE2isb0Mjeqg46xnU=
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=

View File

@@ -10,7 +10,7 @@ import (
"google.golang.org/protobuf/types/known/timestamppb"
"github.com/zitadel/zitadel/internal/api/grpc/object/v2"
"github.com/zitadel/zitadel/internal/api/http"
http_utils "github.com/zitadel/zitadel/internal/api/http"
"github.com/zitadel/zitadel/internal/api/oidc"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/query"
@@ -158,7 +158,11 @@ func (s *Server) linkSessionToAuthRequest(ctx context.Context, authRequestID str
return nil, err
}
authReq := &oidc.AuthRequestV2{CurrentAuthRequest: aar}
ctx = op.ContextWithIssuer(ctx, http.DomainContext(ctx).Origin())
issuer := authReq.Issuer
if issuer == "" {
issuer = http_utils.DomainContext(ctx).Origin()
}
ctx = op.ContextWithIssuer(ctx, issuer)
var callback string
if aar.ResponseType == domain.OIDCResponseTypeCode {
callback, err = oidc.CreateCodeCallbackURL(ctx, authReq, s.op.Provider())

View File

@@ -4,9 +4,11 @@ import (
"context"
"github.com/zitadel/logging"
"github.com/zitadel/saml/pkg/provider"
"google.golang.org/protobuf/types/known/timestamppb"
"github.com/zitadel/zitadel/internal/api/grpc/object/v2"
http_utils "github.com/zitadel/zitadel/internal/api/http"
"github.com/zitadel/zitadel/internal/api/saml"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/query"
@@ -76,6 +78,11 @@ func (s *Server) linkSessionToSAMLRequest(ctx context.Context, samlRequestID str
return nil, err
}
authReq := &saml.AuthRequestV2{CurrentSAMLRequest: aar}
responseIssuer := authReq.ResponseIssuer
if responseIssuer == "" {
responseIssuer = http_utils.DomainContext(ctx).Origin()
}
ctx = provider.ContextWithIssuer(ctx, responseIssuer)
url, body, err := s.idp.CreateResponse(ctx, authReq)
if err != nil {
return nil, err

View File

@@ -111,6 +111,7 @@ func (o *OPStorage) createAuthRequestLoginClient(ctx context.Context, req *oidc.
Prompt: PromptToBusiness(req.Prompt),
UILocales: UILocalesToBusiness(req.UILocales),
MaxAge: MaxAgeToBusiness(req.MaxAge),
Issuer: o.contextToIssuer(ctx),
}
if req.LoginHint != "" {
authRequest.LoginHint = &req.LoginHint

View File

@@ -75,6 +75,7 @@ type OPStorage struct {
encAlg crypto.EncryptionAlgorithm
locker crdb.Locker
assetAPIPrefix func(ctx context.Context) string
contextToIssuer func(context.Context) string
}
// Provider is used to overload certain [op.Provider] methods
@@ -119,7 +120,7 @@ func NewServer(
if err != nil {
return nil, zerrors.ThrowInternal(err, "OIDC-EGrqd", "cannot create op config: %w")
}
storage := newStorage(config, command, query, repo, encryptionAlg, es, projections)
storage := newStorage(config, command, query, repo, encryptionAlg, es, projections, ContextToIssuer)
keyCache := newPublicKeyCache(ctx, config.PublicKeyCacheMaxAge, queryKeyFunc(query))
accessTokenKeySet := newOidcKeySet(keyCache, withKeyExpiryCheck(true))
idTokenHintKeySet := newOidcKeySet(keyCache)
@@ -182,9 +183,13 @@ func NewServer(
return server, nil
}
func ContextToIssuer(ctx context.Context) string {
return http_utils.DomainContext(ctx).Origin()
}
func IssuerFromContext(_ bool) (op.IssuerFromRequest, error) {
return func(r *http.Request) string {
return http_utils.DomainContext(r.Context()).Origin()
return ContextToIssuer(r.Context())
}, nil
}
@@ -220,7 +225,7 @@ func createOPConfig(config Config, defaultLogoutRedirectURI string, cryptoKey []
return opConfig, nil
}
func newStorage(config Config, command *command.Commands, query *query.Queries, repo repository.Repository, encAlg crypto.EncryptionAlgorithm, es *eventstore.Eventstore, db *database.DB) *OPStorage {
func newStorage(config Config, command *command.Commands, query *query.Queries, repo repository.Repository, encAlg crypto.EncryptionAlgorithm, es *eventstore.Eventstore, db *database.DB, contextToIssuer func(context.Context) string) *OPStorage {
return &OPStorage{
repo: repo,
command: command,
@@ -236,6 +241,7 @@ func newStorage(config Config, command *command.Commands, query *query.Queries,
encAlg: encAlg,
locker: crdb.NewLocker(db.DB, locksTable, signingKey),
assetAPIPrefix: assets.AssetAPI(),
contextToIssuer: contextToIssuer,
}
}

View File

@@ -3,7 +3,6 @@ package saml
import (
"context"
"encoding/base64"
"net/http"
"net/url"
"github.com/zitadel/saml/pkg/provider"
@@ -34,15 +33,9 @@ func (p *Provider) CreateResponse(ctx context.Context, authReq models.AuthReques
AcsUrl: authReq.GetAccessConsumerServiceURL(),
RequestID: authReq.GetAuthRequestID(),
Audience: authReq.GetIssuer(),
Issuer: p.GetEntityID(ctx),
}
issuer := ContextToIssuer(ctx)
req, err := http.NewRequestWithContext(provider.ContextWithIssuer(ctx, issuer), http.MethodGet, issuer, nil)
if err != nil {
return "", "", err
}
resp.Issuer = p.GetEntityID(req)
samlResponse, err := p.AuthCallbackResponse(ctx, authReq, resp)
if err != nil {
return "", "", err

View File

@@ -60,6 +60,7 @@ func NewProvider(
projections,
fmt.Sprintf("%s%s?%s=", login.HandlerPrefix, login.EndpointLogin, login.QueryAuthRequestID),
conf.DefaultLoginURLV2,
ContextToIssuer,
)
if err != nil {
return nil, err
@@ -117,6 +118,7 @@ func newStorage(
db *database.DB,
defaultLoginURL string,
defaultLoginURLV2 string,
contextToIssuer func(context.Context) string,
) (*Storage, error) {
return &Storage{
encAlg: encAlg,
@@ -128,6 +130,7 @@ func newStorage(
query: query,
defaultLoginURL: defaultLoginURL,
defaultLoginURLv2: defaultLoginURLV2,
contextToIssuer: contextToIssuer,
}, nil
}

View File

@@ -64,6 +64,7 @@ type Storage struct {
defaultLoginURL string
defaultLoginURLv2 string
contextToIssuer func(context.Context) string
}
func (p *Storage) GetEntityByID(ctx context.Context, entityID string) (*serviceprovider.ServiceProvider, error) {
@@ -137,14 +138,15 @@ func (p *Storage) createAuthRequestLoginClient(ctx context.Context, req *samlp.A
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
samlRequest := &command.SAMLRequest{
ApplicationID: applicationID,
ACSURL: acsUrl,
RelayState: relayState,
RequestID: req.Id,
Binding: protocolBinding,
Issuer: req.Issuer.Text,
Destination: req.Destination,
LoginClient: loginClient,
ApplicationID: applicationID,
ACSURL: acsUrl,
RelayState: relayState,
RequestID: req.Id,
Binding: protocolBinding,
Issuer: req.Issuer.Text,
Destination: req.Destination,
LoginClient: loginClient,
ResponseIssuer: p.contextToIssuer(ctx),
}
aar, err := p.command.AddSAMLRequest(ctx, samlRequest)

View File

@@ -29,6 +29,7 @@ type AuthRequest struct {
LoginHint *string
HintUserID *string
NeedRefreshToken bool
Issuer string
}
type CurrentAuthRequest struct {
@@ -73,6 +74,7 @@ func (c *Commands) AddAuthRequest(ctx context.Context, authRequest *AuthRequest)
authRequest.LoginHint,
authRequest.HintUserID,
authRequest.NeedRefreshToken,
authRequest.Issuer,
))
if err != nil {
return nil, err
@@ -180,6 +182,7 @@ func authRequestWriteModelToCurrentAuthRequest(writeModel *AuthRequestWriteModel
MaxAge: writeModel.MaxAge,
LoginHint: writeModel.LoginHint,
HintUserID: writeModel.HintUserID,
Issuer: writeModel.Issuer,
},
SessionID: writeModel.SessionID,
UserID: writeModel.UserID,

View File

@@ -36,6 +36,7 @@ type AuthRequestWriteModel struct {
AuthMethods []domain.UserAuthMethodType
AuthRequestState domain.AuthRequestState
NeedRefreshToken bool
Issuer string
}
func NewAuthRequestWriteModel(ctx context.Context, id string) *AuthRequestWriteModel {
@@ -68,6 +69,7 @@ func (m *AuthRequestWriteModel) Reduce() error {
m.HintUserID = e.HintUserID
m.AuthRequestState = domain.AuthRequestStateAdded
m.NeedRefreshToken = e.NeedRefreshToken
m.Issuer = e.Issuer
case *authrequest.SessionLinkedEvent:
m.SessionID = e.SessionID
m.UserID = e.UserID

View File

@@ -62,6 +62,7 @@ func TestCommands_AddAuthRequest(t *testing.T) {
nil,
nil,
false,
"issuer",
),
),
),
@@ -101,6 +102,7 @@ func TestCommands_AddAuthRequest(t *testing.T) {
gu.Ptr("loginHint"),
gu.Ptr("hintUserID"),
false,
"issuer",
),
),
),
@@ -127,6 +129,7 @@ func TestCommands_AddAuthRequest(t *testing.T) {
MaxAge: gu.Ptr(time.Duration(0)),
LoginHint: gu.Ptr("loginHint"),
HintUserID: gu.Ptr("hintUserID"),
Issuer: "issuer",
},
},
&CurrentAuthRequest{
@@ -150,6 +153,7 @@ func TestCommands_AddAuthRequest(t *testing.T) {
MaxAge: gu.Ptr(time.Duration(0)),
LoginHint: gu.Ptr("loginHint"),
HintUserID: gu.Ptr("hintUserID"),
Issuer: "issuer",
},
},
nil,
@@ -234,6 +238,7 @@ func TestCommands_LinkSessionToAuthRequest(t *testing.T) {
nil,
nil,
true,
"issuer",
),
),
eventFromEventPusher(
@@ -276,6 +281,7 @@ func TestCommands_LinkSessionToAuthRequest(t *testing.T) {
nil,
nil,
true,
"issuer",
),
),
),
@@ -317,6 +323,7 @@ func TestCommands_LinkSessionToAuthRequest(t *testing.T) {
nil,
nil,
true,
"issuer",
),
),
),
@@ -356,6 +363,7 @@ func TestCommands_LinkSessionToAuthRequest(t *testing.T) {
nil,
nil,
true,
"issuer",
),
),
),
@@ -418,6 +426,7 @@ func TestCommands_LinkSessionToAuthRequest(t *testing.T) {
nil,
nil,
true,
"issuer",
),
),
),
@@ -469,6 +478,7 @@ func TestCommands_LinkSessionToAuthRequest(t *testing.T) {
nil,
nil,
true,
"issuer",
),
),
),
@@ -527,6 +537,7 @@ func TestCommands_LinkSessionToAuthRequest(t *testing.T) {
Audience: []string{"audience"},
ResponseType: domain.OIDCResponseTypeCode,
ResponseMode: domain.OIDCResponseModeQuery,
Issuer: "issuer",
},
SessionID: "sessionID",
UserID: "userID",
@@ -557,6 +568,7 @@ func TestCommands_LinkSessionToAuthRequest(t *testing.T) {
nil,
nil,
true,
"issuer",
),
),
),
@@ -616,6 +628,7 @@ func TestCommands_LinkSessionToAuthRequest(t *testing.T) {
Audience: []string{"audience"},
ResponseType: domain.OIDCResponseTypeCode,
ResponseMode: domain.OIDCResponseModeQuery,
Issuer: "issuer",
},
SessionID: "sessionID",
UserID: "userID",
@@ -646,6 +659,7 @@ func TestCommands_LinkSessionToAuthRequest(t *testing.T) {
nil,
nil,
true,
"issuer",
),
),
),
@@ -706,6 +720,7 @@ func TestCommands_LinkSessionToAuthRequest(t *testing.T) {
Audience: []string{"audience"},
ResponseType: domain.OIDCResponseTypeCode,
ResponseMode: domain.OIDCResponseModeQuery,
Issuer: "issuer",
},
SessionID: "sessionID",
UserID: "userID",
@@ -736,6 +751,7 @@ func TestCommands_LinkSessionToAuthRequest(t *testing.T) {
nil,
nil,
true,
"issuer",
),
),
),
@@ -797,6 +813,7 @@ func TestCommands_LinkSessionToAuthRequest(t *testing.T) {
Audience: []string{"audience"},
ResponseType: domain.OIDCResponseTypeCode,
ResponseMode: domain.OIDCResponseModeQuery,
Issuer: "issuer",
},
SessionID: "sessionID",
UserID: "userID",
@@ -827,6 +844,7 @@ func TestCommands_LinkSessionToAuthRequest(t *testing.T) {
nil,
nil,
true,
"issuer",
),
),
),
@@ -950,6 +968,7 @@ func TestCommands_FailAuthRequest(t *testing.T) {
nil,
nil,
true,
"issuer",
),
),
),
@@ -978,6 +997,7 @@ func TestCommands_FailAuthRequest(t *testing.T) {
Audience: []string{"audience"},
ResponseType: domain.OIDCResponseTypeCode,
ResponseMode: domain.OIDCResponseModeQuery,
Issuer: "issuer",
},
},
},
@@ -1050,6 +1070,7 @@ func TestCommands_AddAuthRequestCode(t *testing.T) {
gu.Ptr("loginHint"),
gu.Ptr("hintUserID"),
true,
"issuer",
),
),
),
@@ -1088,6 +1109,7 @@ func TestCommands_AddAuthRequestCode(t *testing.T) {
gu.Ptr("loginHint"),
gu.Ptr("hintUserID"),
true,
"issuer",
),
),
eventFromEventPusher(

View File

@@ -138,6 +138,7 @@ func TestCommands_CreateOIDCSessionFromAuthRequest(t *testing.T) {
gu.Ptr("loginHint"),
gu.Ptr("hintUserID"),
true,
"issuer",
),
),
eventFromEventPusher(
@@ -182,6 +183,7 @@ func TestCommands_CreateOIDCSessionFromAuthRequest(t *testing.T) {
gu.Ptr("loginHint"),
gu.Ptr("hintUserID"),
true,
"issuer",
),
),
eventFromEventPusher(
@@ -234,6 +236,7 @@ func TestCommands_CreateOIDCSessionFromAuthRequest(t *testing.T) {
gu.Ptr("loginHint"),
gu.Ptr("hintUserID"),
true,
"issuer",
),
),
eventFromEventPusher(
@@ -331,6 +334,7 @@ func TestCommands_CreateOIDCSessionFromAuthRequest(t *testing.T) {
gu.Ptr("loginHint"),
gu.Ptr("hintUserID"),
true,
"issuer",
),
),
eventFromEventPusher(
@@ -465,6 +469,7 @@ func TestCommands_CreateOIDCSessionFromAuthRequest(t *testing.T) {
gu.Ptr("loginHint"),
gu.Ptr("hintUserID"),
true,
"issuer",
),
),
eventFromEventPusher(
@@ -610,6 +615,7 @@ func TestCommands_CreateOIDCSessionFromAuthRequest(t *testing.T) {
gu.Ptr("loginHint"),
gu.Ptr("hintUserID"),
true,
"issuer",
),
),
eventFromEventPusher(
@@ -748,6 +754,7 @@ func TestCommands_CreateOIDCSessionFromAuthRequest(t *testing.T) {
gu.Ptr("loginHint"),
gu.Ptr("hintUserID"),
false,
"issuer",
),
),
eventFromEventPusher(

View File

@@ -15,13 +15,14 @@ type SAMLRequest struct {
ID string
LoginClient string
ApplicationID string
ACSURL string
RelayState string
RequestID string
Binding string
Issuer string
Destination string
ApplicationID string
ACSURL string
RelayState string
RequestID string
Binding string
Issuer string
Destination string
ResponseIssuer string
}
type CurrentSAMLRequest struct {
@@ -56,6 +57,7 @@ func (c *Commands) AddSAMLRequest(ctx context.Context, samlRequest *SAMLRequest)
samlRequest.Binding,
samlRequest.Issuer,
samlRequest.Destination,
samlRequest.ResponseIssuer,
))
if err != nil {
return nil, err
@@ -131,15 +133,16 @@ func (c *Commands) FailSAMLRequest(ctx context.Context, id string, reason domain
func samlRequestWriteModelToCurrentSAMLRequest(writeModel *SAMLRequestWriteModel) (_ *CurrentSAMLRequest) {
return &CurrentSAMLRequest{
SAMLRequest: &SAMLRequest{
ID: writeModel.AggregateID,
LoginClient: writeModel.LoginClient,
ApplicationID: writeModel.ApplicationID,
ACSURL: writeModel.ACSURL,
RelayState: writeModel.RelayState,
RequestID: writeModel.RequestID,
Binding: writeModel.Binding,
Issuer: writeModel.Issuer,
Destination: writeModel.Destination,
ID: writeModel.AggregateID,
LoginClient: writeModel.LoginClient,
ApplicationID: writeModel.ApplicationID,
ACSURL: writeModel.ACSURL,
RelayState: writeModel.RelayState,
RequestID: writeModel.RequestID,
Binding: writeModel.Binding,
Issuer: writeModel.Issuer,
Destination: writeModel.Destination,
ResponseIssuer: writeModel.ResponseIssuer,
},
SessionID: writeModel.SessionID,
UserID: writeModel.UserID,

View File

@@ -15,14 +15,15 @@ type SAMLRequestWriteModel struct {
eventstore.WriteModel
aggregate *eventstore.Aggregate
LoginClient string
ApplicationID string
ACSURL string
RelayState string
RequestID string
Binding string
Issuer string
Destination string
LoginClient string
ApplicationID string
ACSURL string
RelayState string
RequestID string
Binding string
Issuer string
Destination string
ResponseIssuer string
SessionID string
UserID string
@@ -52,6 +53,7 @@ func (m *SAMLRequestWriteModel) Reduce() error {
m.Binding = e.Binding
m.Issuer = e.Issuer
m.Destination = e.Destination
m.ResponseIssuer = e.ResponseIssuer
m.SAMLRequestState = domain.SAMLRequestStateAdded
case *samlrequest.SessionLinkedEvent:
m.SessionID = e.SessionID

View File

@@ -54,6 +54,7 @@ func TestCommands_AddSAMLRequest(t *testing.T) {
"binding",
"issuer",
"destination",
"responseissuer",
),
),
),
@@ -82,6 +83,7 @@ func TestCommands_AddSAMLRequest(t *testing.T) {
"binding",
"issuer",
"destination",
"responseissuer",
),
),
),
@@ -90,27 +92,29 @@ func TestCommands_AddSAMLRequest(t *testing.T) {
args{
ctx: mockCtx,
request: &SAMLRequest{
LoginClient: "login",
ApplicationID: "application",
ACSURL: "acs",
RelayState: "relaystate",
RequestID: "request",
Binding: "binding",
Issuer: "issuer",
Destination: "destination",
LoginClient: "login",
ApplicationID: "application",
ACSURL: "acs",
RelayState: "relaystate",
RequestID: "request",
Binding: "binding",
Issuer: "issuer",
Destination: "destination",
ResponseIssuer: "responseissuer",
},
},
&CurrentSAMLRequest{
SAMLRequest: &SAMLRequest{
ID: "V2_id",
LoginClient: "login",
ApplicationID: "application",
ACSURL: "acs",
RelayState: "relaystate",
RequestID: "request",
Binding: "binding",
Issuer: "issuer",
Destination: "destination",
ID: "V2_id",
LoginClient: "login",
ApplicationID: "application",
ACSURL: "acs",
RelayState: "relaystate",
RequestID: "request",
Binding: "binding",
Issuer: "issuer",
Destination: "destination",
ResponseIssuer: "responseissuer",
},
},
nil,
@@ -187,6 +191,7 @@ func TestCommands_LinkSessionToSAMLRequest(t *testing.T) {
"binding",
"issuer",
"destination",
"responseissuer",
),
),
eventFromEventPusher(
@@ -222,6 +227,7 @@ func TestCommands_LinkSessionToSAMLRequest(t *testing.T) {
"binding",
"issuer",
"destination",
"responseissuer",
),
),
),
@@ -255,6 +261,7 @@ func TestCommands_LinkSessionToSAMLRequest(t *testing.T) {
"binding",
"issuer",
"destination",
"responseissuer",
),
),
),
@@ -286,6 +293,7 @@ func TestCommands_LinkSessionToSAMLRequest(t *testing.T) {
"binding",
"issuer",
"destination",
"responseissuer",
),
),
),
@@ -340,6 +348,7 @@ func TestCommands_LinkSessionToSAMLRequest(t *testing.T) {
"binding",
"issuer",
"destination",
"responseissuer",
),
),
),
@@ -383,6 +392,7 @@ func TestCommands_LinkSessionToSAMLRequest(t *testing.T) {
"binding",
"issuer",
"destination",
"responseissuer",
),
),
),
@@ -431,15 +441,16 @@ func TestCommands_LinkSessionToSAMLRequest(t *testing.T) {
details: &domain.ObjectDetails{ResourceOwner: "instanceID"},
authReq: &CurrentSAMLRequest{
SAMLRequest: &SAMLRequest{
ID: "V2_id",
LoginClient: "login",
ApplicationID: "application",
ACSURL: "acs",
RelayState: "relaystate",
RequestID: "request",
Binding: "binding",
Issuer: "issuer",
Destination: "destination",
ID: "V2_id",
LoginClient: "login",
ApplicationID: "application",
ACSURL: "acs",
RelayState: "relaystate",
RequestID: "request",
Binding: "binding",
Issuer: "issuer",
Destination: "destination",
ResponseIssuer: "responseissuer",
},
SessionID: "sessionID",
UserID: "userID",
@@ -462,6 +473,7 @@ func TestCommands_LinkSessionToSAMLRequest(t *testing.T) {
"binding",
"issuer",
"destination",
"responseissuer",
),
),
),
@@ -511,15 +523,16 @@ func TestCommands_LinkSessionToSAMLRequest(t *testing.T) {
details: &domain.ObjectDetails{ResourceOwner: "instanceID"},
authReq: &CurrentSAMLRequest{
SAMLRequest: &SAMLRequest{
ID: "V2_id",
LoginClient: "loginClient",
ApplicationID: "application",
ACSURL: "acs",
RelayState: "relaystate",
RequestID: "request",
Binding: "binding",
Issuer: "issuer",
Destination: "destination",
ID: "V2_id",
LoginClient: "loginClient",
ApplicationID: "application",
ACSURL: "acs",
RelayState: "relaystate",
RequestID: "request",
Binding: "binding",
Issuer: "issuer",
Destination: "destination",
ResponseIssuer: "responseissuer",
},
SessionID: "sessionID",
UserID: "userID",
@@ -541,6 +554,7 @@ func TestCommands_LinkSessionToSAMLRequest(t *testing.T) {
"binding",
"issuer",
"destination",
"responseissuer",
),
),
),
@@ -591,15 +605,16 @@ func TestCommands_LinkSessionToSAMLRequest(t *testing.T) {
details: &domain.ObjectDetails{ResourceOwner: "instanceID"},
authReq: &CurrentSAMLRequest{
SAMLRequest: &SAMLRequest{
ID: "V2_id",
LoginClient: "loginClient",
ApplicationID: "application",
ACSURL: "acs",
RelayState: "relaystate",
RequestID: "request",
Binding: "binding",
Issuer: "issuer",
Destination: "destination",
ID: "V2_id",
LoginClient: "loginClient",
ApplicationID: "application",
ACSURL: "acs",
RelayState: "relaystate",
RequestID: "request",
Binding: "binding",
Issuer: "issuer",
Destination: "destination",
ResponseIssuer: "responseissuer",
},
SessionID: "sessionID",
UserID: "userID",
@@ -622,6 +637,7 @@ func TestCommands_LinkSessionToSAMLRequest(t *testing.T) {
"binding",
"issuer",
"destination",
"responseissuer",
),
),
),
@@ -672,15 +688,16 @@ func TestCommands_LinkSessionToSAMLRequest(t *testing.T) {
details: &domain.ObjectDetails{ResourceOwner: "instanceID"},
authReq: &CurrentSAMLRequest{
SAMLRequest: &SAMLRequest{
ID: "V2_id",
LoginClient: "loginClient",
ApplicationID: "application",
ACSURL: "acs",
RelayState: "relaystate",
RequestID: "request",
Binding: "binding",
Issuer: "issuer",
Destination: "destination",
ID: "V2_id",
LoginClient: "loginClient",
ApplicationID: "application",
ACSURL: "acs",
RelayState: "relaystate",
RequestID: "request",
Binding: "binding",
Issuer: "issuer",
Destination: "destination",
ResponseIssuer: "responseissuer",
},
SessionID: "sessionID",
UserID: "userID",
@@ -703,6 +720,7 @@ func TestCommands_LinkSessionToSAMLRequest(t *testing.T) {
"binding",
"issuer",
"destination",
"responseissuer",
),
),
),
@@ -817,6 +835,7 @@ func TestCommands_FailSAMLRequest(t *testing.T) {
"binding",
"issuer",
"destination",
"responseissuer",
),
),
samlrequest.NewFailedEvent(mockCtx, &samlrequest.NewAggregate("V2_id", "instanceID").Aggregate,
@@ -850,6 +869,7 @@ func TestCommands_FailSAMLRequest(t *testing.T) {
"binding",
"issuer",
"destination",
"responseissuer",
),
),
),
@@ -870,15 +890,16 @@ func TestCommands_FailSAMLRequest(t *testing.T) {
details: &domain.ObjectDetails{ResourceOwner: "instanceID"},
samlReq: &CurrentSAMLRequest{
SAMLRequest: &SAMLRequest{
ID: "V2_id",
LoginClient: "login",
ApplicationID: "application",
ACSURL: "acs",
RelayState: "relaystate",
RequestID: "request",
Binding: "binding",
Issuer: "issuer",
Destination: "destination",
ID: "V2_id",
LoginClient: "login",
ApplicationID: "application",
ACSURL: "acs",
RelayState: "relaystate",
RequestID: "request",
Binding: "binding",
Issuer: "issuer",
Destination: "destination",
ResponseIssuer: "responseissuer",
},
},
},

View File

@@ -99,6 +99,7 @@ func TestCommands_CreateSAMLSessionFromSAMLRequest(t *testing.T) {
"binding",
"issuer",
"destination",
"responseissuer",
),
),
),
@@ -129,6 +130,7 @@ func TestCommands_CreateSAMLSessionFromSAMLRequest(t *testing.T) {
"binding",
"issuer",
"destination",
"responseissuer",
),
),
eventFromEventPusher(
@@ -167,6 +169,7 @@ func TestCommands_CreateSAMLSessionFromSAMLRequest(t *testing.T) {
"binding",
"issuer",
"destination",
"responseissuer",
),
),
eventFromEventPusher(
@@ -248,6 +251,7 @@ func TestCommands_CreateSAMLSessionFromSAMLRequest(t *testing.T) {
"binding",
"issuer",
"destination",
"responseissuer",
),
),
eventFromEventPusher(

View File

@@ -67,6 +67,8 @@ type Handler struct {
cacheInvalidations []func(ctx context.Context, aggregates []*eventstore.Aggregate)
queryInstances func() ([]string, error)
metrics *ProjectionMetrics
}
var _ migration.Migration = (*Handler)(nil)
@@ -159,6 +161,8 @@ func NewHandler(
aggregates[reducer.Aggregate] = eventTypes
}
metrics := NewProjectionMetrics()
handler := &Handler{
projection: projection,
client: config.Client,
@@ -178,6 +182,7 @@ func NewHandler(
}
return nil, nil
},
metrics: metrics,
}
return handler
@@ -483,6 +488,8 @@ func (h *Handler) processEvents(ctx context.Context, config *triggerConfig) (add
defer cancel()
}
start := time.Now()
tx, err := h.client.BeginTx(txCtx, nil)
if err != nil {
return false, err
@@ -502,7 +509,7 @@ func (h *Handler) processEvents(ctx context.Context, config *triggerConfig) (add
}
return additionalIteration, err
}
// stop execution if currentState.eventTimestamp >= config.maxCreatedAt
// stop execution if currentState.position >= config.maxPosition
if config.maxPosition != 0 && currentState.position >= config.maxPosition {
return false, nil
}
@@ -518,7 +525,14 @@ func (h *Handler) processEvents(ctx context.Context, config *triggerConfig) (add
if err == nil {
err = commitErr
}
h.metrics.ProjectionEventsProcessed(ctx, h.ProjectionName(), int64(len(statements)), err == nil)
if err == nil && currentState.aggregateID != "" && len(statements) > 0 {
// Don't update projection timing or latency unless we successfully processed events
h.metrics.ProjectionUpdateTiming(ctx, h.ProjectionName(), float64(time.Since(start).Seconds()))
h.metrics.ProjectionStateLatency(ctx, h.ProjectionName(), time.Since(currentState.eventTimestamp).Seconds())
h.invalidateCaches(ctx, aggregatesFromStatements(statements))
}
}()
@@ -540,6 +554,7 @@ func (h *Handler) processEvents(ctx context.Context, config *triggerConfig) (add
currentState.aggregateType = statements[lastProcessedIndex].Aggregate.Type
currentState.sequence = statements[lastProcessedIndex].Sequence
currentState.eventTimestamp = statements[lastProcessedIndex].CreationDate
err = h.setState(tx, currentState)
return additionalIteration, err

View File

@@ -0,0 +1,70 @@
package handler
import (
"context"
"github.com/zitadel/logging"
"go.opentelemetry.io/otel/attribute"
"github.com/zitadel/zitadel/internal/telemetry/metrics"
)
const (
ProjectionLabel = "projection"
SuccessLabel = "success"
ProjectionEventsProcessed = "projection_events_processed"
ProjectionHandleTimerMetric = "projection_handle_timer"
ProjectionStateLatencyMetric = "projection_state_latency"
)
type ProjectionMetrics struct {
provider metrics.Metrics
}
func NewProjectionMetrics() *ProjectionMetrics {
projectionMetrics := &ProjectionMetrics{provider: metrics.M}
err := projectionMetrics.provider.RegisterCounter(
ProjectionEventsProcessed,
"Number of events reduced to process projection updates",
)
logging.OnError(err).Error("failed to register projection events processed counter")
err = projectionMetrics.provider.RegisterHistogram(
ProjectionHandleTimerMetric,
"Time taken to process a projection update",
"s",
[]float64{0.005, 0.01, 0.05, 0.1, 1, 5, 10, 30, 60, 120},
)
logging.OnError(err).Error("failed to register projection handle timer metric")
err = projectionMetrics.provider.RegisterHistogram(
ProjectionStateLatencyMetric,
"When finishing processing a batch of events, this track the age of the last events seen from current time",
"s",
[]float64{0.1, 0.5, 1, 5, 10, 30, 60, 300, 600, 1800},
)
logging.OnError(err).Error("failed to register projection state latency metric")
return projectionMetrics
}
func (m *ProjectionMetrics) ProjectionUpdateTiming(ctx context.Context, projection string, duration float64) {
err := m.provider.AddHistogramMeasurement(ctx, ProjectionHandleTimerMetric, duration, map[string]attribute.Value{
ProjectionLabel: attribute.StringValue(projection),
})
logging.OnError(err).Error("failed to add projection trigger timing")
}
func (m *ProjectionMetrics) ProjectionEventsProcessed(ctx context.Context, projection string, count int64, success bool) {
err := m.provider.AddCount(ctx, ProjectionEventsProcessed, count, map[string]attribute.Value{
ProjectionLabel: attribute.StringValue(projection),
SuccessLabel: attribute.BoolValue(success),
})
logging.OnError(err).Error("failed to add projection events processed metric")
}
func (m *ProjectionMetrics) ProjectionStateLatency(ctx context.Context, projection string, latency float64) {
err := m.provider.AddHistogramMeasurement(ctx, ProjectionStateLatencyMetric, latency, map[string]attribute.Value{
ProjectionLabel: attribute.StringValue(projection),
})
logging.OnError(err).Error("failed to add projection state latency metric")
}

View File

@@ -0,0 +1,132 @@
package handler
import (
"context"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/zitadel/zitadel/internal/telemetry/metrics"
)
func TestNewProjectionMetrics(t *testing.T) {
mockMetrics := metrics.NewMockMetrics()
metrics.M = mockMetrics
metrics := NewProjectionMetrics()
require.NotNil(t, metrics)
assert.NotNil(t, metrics.provider)
}
func TestProjectionMetrics_ProjectionUpdateTiming(t *testing.T) {
mockMetrics := metrics.NewMockMetrics()
metrics.M = mockMetrics
projectionMetrics := NewProjectionMetrics()
ctx := context.Background()
projection := "test_projection"
duration := 0.5
projectionMetrics.ProjectionUpdateTiming(ctx, projection, duration)
values := mockMetrics.GetHistogramValues(ProjectionHandleTimerMetric)
require.Len(t, values, 1)
assert.Equal(t, duration, values[0])
labels := mockMetrics.GetHistogramLabels(ProjectionHandleTimerMetric)
require.Len(t, labels, 1)
assert.Equal(t, projection, labels[0][ProjectionLabel].AsString())
}
func TestProjectionMetrics_ProjectionEventsProcessed(t *testing.T) {
mockMetrics := metrics.NewMockMetrics()
metrics.M = mockMetrics
projectionMetrics := NewProjectionMetrics()
ctx := context.Background()
projection := "test_projection"
count := int64(5)
success := true
projectionMetrics.ProjectionEventsProcessed(ctx, projection, count, success)
value := mockMetrics.GetCounterValue(ProjectionEventsProcessed)
assert.Equal(t, count, value)
labels := mockMetrics.GetCounterLabels(ProjectionEventsProcessed)
require.Len(t, labels, 1)
assert.Equal(t, projection, labels[0][ProjectionLabel].AsString())
assert.Equal(t, success, labels[0][SuccessLabel].AsBool())
}
func TestProjectionMetrics_ProjectionStateLatency(t *testing.T) {
mockMetrics := metrics.NewMockMetrics()
metrics.M = mockMetrics
projectionMetrics := NewProjectionMetrics()
ctx := context.Background()
projection := "test_projection"
latency := 10.0
projectionMetrics.ProjectionStateLatency(ctx, projection, latency)
values := mockMetrics.GetHistogramValues(ProjectionStateLatencyMetric)
require.Len(t, values, 1)
assert.Equal(t, latency, values[0])
labels := mockMetrics.GetHistogramLabels(ProjectionStateLatencyMetric)
require.Len(t, labels, 1)
assert.Equal(t, projection, labels[0][ProjectionLabel].AsString())
}
func TestProjectionMetrics_Integration(t *testing.T) {
mockMetrics := metrics.NewMockMetrics()
metrics.M = mockMetrics
projectionMetrics := NewProjectionMetrics()
ctx := context.Background()
projection := "test_projection"
start := time.Now()
projectionMetrics.ProjectionEventsProcessed(ctx, projection, 3, true)
projectionMetrics.ProjectionEventsProcessed(ctx, projection, 1, false)
duration := time.Since(start).Seconds()
projectionMetrics.ProjectionUpdateTiming(ctx, projection, duration)
latency := 5.0
projectionMetrics.ProjectionStateLatency(ctx, projection, latency)
value := mockMetrics.GetCounterValue(ProjectionEventsProcessed)
assert.Equal(t, int64(4), value)
timingValues := mockMetrics.GetHistogramValues(ProjectionHandleTimerMetric)
require.Len(t, timingValues, 1)
assert.Equal(t, duration, timingValues[0])
latencyValues := mockMetrics.GetHistogramValues(ProjectionStateLatencyMetric)
require.Len(t, latencyValues, 1)
assert.Equal(t, latency, latencyValues[0])
eventsLabels := mockMetrics.GetCounterLabels(ProjectionEventsProcessed)
require.Len(t, eventsLabels, 2)
assert.Equal(t, projection, eventsLabels[0][ProjectionLabel].AsString())
assert.Equal(t, true, eventsLabels[0][SuccessLabel].AsBool())
assert.Equal(t, projection, eventsLabels[1][ProjectionLabel].AsString())
assert.Equal(t, false, eventsLabels[1][SuccessLabel].AsBool())
timingLabels := mockMetrics.GetHistogramLabels(ProjectionHandleTimerMetric)
require.Len(t, timingLabels, 1)
assert.Equal(t, projection, timingLabels[0][ProjectionLabel].AsString())
latencyLabels := mockMetrics.GetHistogramLabels(ProjectionStateLatencyMetric)
require.Len(t, latencyLabels, 1)
assert.Equal(t, projection, latencyLabels[0][ProjectionLabel].AsString())
}

View File

@@ -38,6 +38,7 @@ type AddedEvent struct {
LoginHint *string `json:"login_hint,omitempty"`
HintUserID *string `json:"hint_user_id,omitempty"`
NeedRefreshToken bool `json:"need_refresh_token,omitempty"`
Issuer string `json:"issuer,omitempty"`
}
func (e *AddedEvent) Payload() interface{} {
@@ -66,6 +67,7 @@ func NewAddedEvent(ctx context.Context,
loginHint,
hintUserID *string,
needRefreshToken bool,
issuer string,
) *AddedEvent {
return &AddedEvent{
BaseEvent: *eventstore.NewBaseEventForPush(
@@ -89,6 +91,7 @@ func NewAddedEvent(ctx context.Context,
LoginHint: loginHint,
HintUserID: hintUserID,
NeedRefreshToken: needRefreshToken,
Issuer: issuer,
}
}

View File

@@ -19,14 +19,15 @@ const (
type AddedEvent struct {
*eventstore.BaseEvent `json:"-"`
LoginClient string `json:"login_client,omitempty"`
ApplicationID string `json:"application_id,omitempty"`
ACSURL string `json:"acs_url,omitempty"`
RelayState string `json:"relay_state,omitempty"`
RequestID string `json:"request_id,omitempty"`
Binding string `json:"binding,omitempty"`
Issuer string `json:"issuer,omitempty"`
Destination string `json:"destination,omitempty"`
LoginClient string `json:"login_client,omitempty"`
ApplicationID string `json:"application_id,omitempty"`
ACSURL string `json:"acs_url,omitempty"`
RelayState string `json:"relay_state,omitempty"`
RequestID string `json:"request_id,omitempty"`
Binding string `json:"binding,omitempty"`
Issuer string `json:"issuer,omitempty"`
Destination string `json:"destination,omitempty"`
ResponseIssuer string `json:"response_issuer,omitempty"`
}
func (e *AddedEvent) SetBaseEvent(event *eventstore.BaseEvent) {
@@ -51,6 +52,7 @@ func NewAddedEvent(ctx context.Context,
binding string,
issuer string,
destination string,
responseIssuer string,
) *AddedEvent {
return &AddedEvent{
BaseEvent: eventstore.NewBaseEventForPush(
@@ -58,14 +60,15 @@ func NewAddedEvent(ctx context.Context,
aggregate,
AddedType,
),
LoginClient: loginClient,
ApplicationID: applicationID,
ACSURL: acsURL,
RelayState: relayState,
RequestID: requestID,
Binding: binding,
Issuer: issuer,
Destination: destination,
LoginClient: loginClient,
ApplicationID: applicationID,
ACSURL: acsURL,
RelayState: relayState,
RequestID: requestID,
Binding: binding,
Issuer: issuer,
Destination: destination,
ResponseIssuer: responseIssuer,
}
}

View File

@@ -1,6 +1,7 @@
package config
import (
"github.com/zitadel/zitadel/internal/telemetry/metrics"
"github.com/zitadel/zitadel/internal/telemetry/metrics/otel"
"github.com/zitadel/zitadel/internal/zerrors"
)
@@ -12,11 +13,16 @@ type Config struct {
var meter = map[string]func(map[string]interface{}) error{
"otel": otel.NewTracerFromConfig,
"none": NoMetrics,
"": NoMetrics,
"none": registerNoopMetrics,
"": registerNoopMetrics,
}
func (c *Config) NewMeter() error {
// When using start-from-init or start-from-setup the metric provider
// was already set in the setup phase and the start phase must not overwrite it.
if metrics.M != nil {
return nil
}
t, ok := meter[c.Type]
if !ok {
return zerrors.ThrowInternalf(nil, "METER-Dfqsx", "config type %s not supported", c.Type)
@@ -25,6 +31,7 @@ func (c *Config) NewMeter() error {
return t(c.Config)
}
func NoMetrics(_ map[string]interface{}) error {
func registerNoopMetrics(rawConfig map[string]interface{}) (err error) {
metrics.M = &metrics.NoopMetrics{}
return nil
}

View File

@@ -22,8 +22,10 @@ type Metrics interface {
GetMetricsProvider() metric.MeterProvider
RegisterCounter(name, description string) error
AddCount(ctx context.Context, name string, value int64, labels map[string]attribute.Value) error
AddHistogramMeasurement(ctx context.Context, name string, value float64, labels map[string]attribute.Value) error
RegisterUpDownSumObserver(name, description string, callbackFunc metric.Int64Callback) error
RegisterValueObserver(name, description string, callbackFunc metric.Int64Callback) error
RegisterHistogram(name, description, unit string, buckets []float64) error
}
var M Metrics
@@ -56,6 +58,20 @@ func AddCount(ctx context.Context, name string, value int64, labels map[string]a
return M.AddCount(ctx, name, value, labels)
}
func AddHistogramMeasurement(ctx context.Context, name string, value float64, labels map[string]attribute.Value) error {
if M == nil {
return nil
}
return M.AddHistogramMeasurement(ctx, name, value, labels)
}
func RegisterHistogram(name, description, unit string, buckets []float64) error {
if M == nil {
return nil
}
return M.RegisterHistogram(name, description, unit, buckets)
}
func RegisterUpDownSumObserver(name, description string, callbackFunc metric.Int64Callback) error {
if M == nil {
return nil

View File

@@ -0,0 +1,95 @@
package metrics
import (
"context"
"net/http"
"sync"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/metric"
)
// MockMetrics implements the metrics.Metrics interface for testing
type MockMetrics struct {
mu sync.RWMutex
histogramValues map[string][]float64
counterValues map[string]int64
histogramLabels map[string][]map[string]attribute.Value
counterLabels map[string][]map[string]attribute.Value
}
var _ Metrics = new(MockMetrics)
// NewMockMetrics creates a new Metrics instance for testing
func NewMockMetrics() *MockMetrics {
return &MockMetrics{
histogramValues: make(map[string][]float64),
counterValues: make(map[string]int64),
histogramLabels: make(map[string][]map[string]attribute.Value),
counterLabels: make(map[string][]map[string]attribute.Value),
}
}
func (m *MockMetrics) GetExporter() http.Handler {
return nil
}
func (m *MockMetrics) GetMetricsProvider() metric.MeterProvider {
return nil
}
func (m *MockMetrics) RegisterCounter(name, description string) error {
return nil
}
func (m *MockMetrics) AddCount(ctx context.Context, name string, value int64, labels map[string]attribute.Value) error {
m.mu.Lock()
defer m.mu.Unlock()
m.counterValues[name] += value
m.counterLabels[name] = append(m.counterLabels[name], labels)
return nil
}
func (m *MockMetrics) AddHistogramMeasurement(ctx context.Context, name string, value float64, labels map[string]attribute.Value) error {
m.mu.Lock()
defer m.mu.Unlock()
m.histogramValues[name] = append(m.histogramValues[name], value)
m.histogramLabels[name] = append(m.histogramLabels[name], labels)
return nil
}
func (m *MockMetrics) RegisterUpDownSumObserver(name, description string, callbackFunc metric.Int64Callback) error {
return nil
}
func (m *MockMetrics) RegisterValueObserver(name, description string, callbackFunc metric.Int64Callback) error {
return nil
}
func (m *MockMetrics) RegisterHistogram(name, description, unit string, buckets []float64) error {
return nil
}
func (m *MockMetrics) GetHistogramValues(name string) []float64 {
m.mu.RLock()
defer m.mu.RUnlock()
return m.histogramValues[name]
}
func (m *MockMetrics) GetHistogramLabels(name string) []map[string]attribute.Value {
m.mu.RLock()
defer m.mu.RUnlock()
return m.histogramLabels[name]
}
func (m *MockMetrics) GetCounterValue(name string) int64 {
m.mu.RLock()
defer m.mu.RUnlock()
return m.counterValues[name]
}
func (m *MockMetrics) GetCounterLabels(name string) []map[string]attribute.Value {
m.mu.RLock()
defer m.mu.RUnlock()
return m.counterLabels[name]
}

View File

@@ -0,0 +1,45 @@
package metrics
import (
"context"
"net/http"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/metric"
)
type NoopMetrics struct{}
var _ Metrics = new(NoopMetrics)
func (n *NoopMetrics) GetExporter() http.Handler {
return nil
}
func (n *NoopMetrics) GetMetricsProvider() metric.MeterProvider {
return nil
}
func (n *NoopMetrics) RegisterCounter(name, description string) error {
return nil
}
func (n *NoopMetrics) AddCount(ctx context.Context, name string, value int64, labels map[string]attribute.Value) error {
return nil
}
func (n *NoopMetrics) AddHistogramMeasurement(ctx context.Context, name string, value float64, labels map[string]attribute.Value) error {
return nil
}
func (n *NoopMetrics) RegisterUpDownSumObserver(name, description string, callbackFunc metric.Int64Callback) error {
return nil
}
func (n *NoopMetrics) RegisterValueObserver(name, description string, callbackFunc metric.Int64Callback) error {
return nil
}
func (n *NoopMetrics) RegisterHistogram(name, description, unit string, buckets []float64) error {
return nil
}

View File

@@ -24,6 +24,7 @@ type Metrics struct {
Counters sync.Map
UpDownSumObserver sync.Map
ValueObservers sync.Map
Histograms sync.Map
}
func NewMetrics(meterName string) (metrics.Metrics, error) {
@@ -84,6 +85,33 @@ func (m *Metrics) AddCount(ctx context.Context, name string, value int64, labels
return nil
}
func (m *Metrics) AddHistogramMeasurement(ctx context.Context, name string, value float64, labels map[string]attribute.Value) error {
histogram, exists := m.Histograms.Load(name)
if !exists {
return zerrors.ThrowNotFound(nil, "METER-5wwb1", "Errors.Metrics.Histogram.NotFound")
}
histogram.(metric.Float64Histogram).Record(ctx, value, MapToRecordOption(labels)...)
return nil
}
func (m *Metrics) RegisterHistogram(name, description, unit string, buckets []float64) error {
if _, exists := m.Histograms.Load(name); exists {
return nil
}
histogram, err := m.Meter.Float64Histogram(name,
metric.WithDescription(description),
metric.WithUnit(unit),
metric.WithExplicitBucketBoundaries(buckets...),
)
if err != nil {
return err
}
m.Histograms.Store(name, histogram)
return nil
}
func (m *Metrics) RegisterUpDownSumObserver(name, description string, callbackFunc metric.Int64Callback) error {
if _, exists := m.UpDownSumObserver.Load(name); exists {
return nil
@@ -113,15 +141,23 @@ func (m *Metrics) RegisterValueObserver(name, description string, callbackFunc m
}
func MapToAddOption(labels map[string]attribute.Value) []metric.AddOption {
return []metric.AddOption{metric.WithAttributes(labelsToAttributes(labels)...)}
}
func MapToRecordOption(labels map[string]attribute.Value) []metric.RecordOption {
return []metric.RecordOption{metric.WithAttributes(labelsToAttributes(labels)...)}
}
func labelsToAttributes(labels map[string]attribute.Value) []attribute.KeyValue {
if labels == nil {
return nil
}
keyValues := make([]attribute.KeyValue, 0, len(labels))
attributes := make([]attribute.KeyValue, 0, len(labels))
for key, value := range labels {
keyValues = append(keyValues, attribute.KeyValue{
attributes = append(attributes, attribute.KeyValue{
Key: attribute.Key(key),
Value: value,
})
}
return []metric.AddOption{metric.WithAttributes(keyValues...)}
return attributes
}