mirror of
https://github.com/zitadel/zitadel.git
synced 2025-10-25 20:38:48 +00:00
userinfo from events for v2 tokens
This commit is contained in:
@@ -27,6 +27,7 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
// TODO: remove declarations: (moved to domain package)
|
||||
ScopeProjectRolePrefix = "urn:zitadel:iam:org:project:role:"
|
||||
ScopeProjectsRoles = "urn:zitadel:iam:org:projects:roles"
|
||||
ClaimProjectRoles = "urn:zitadel:iam:org:project:roles"
|
||||
@@ -179,21 +180,6 @@ func (o *OPStorage) SetIntrospectionFromToken(ctx context.Context, introspection
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
if strings.HasPrefix(tokenID, command.IDPrefixV2) {
|
||||
token, err := o.query.ActiveAccessTokenByToken(ctx, tokenID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
projectID, err := o.query.ProjectIDFromClientID(ctx, clientID, false)
|
||||
if err != nil {
|
||||
return errors.ThrowPermissionDenied(nil, "OIDC-Adfg5", "client not found")
|
||||
}
|
||||
return o.introspect(ctx, introspection,
|
||||
tokenID, token.UserID, token.ClientID, clientID, projectID,
|
||||
token.Audience, token.Scope,
|
||||
token.AccessTokenCreation, token.AccessTokenExpiration)
|
||||
}
|
||||
|
||||
token, err := o.repo.TokenByIDs(ctx, subject, tokenID)
|
||||
if err != nil {
|
||||
return errors.ThrowPermissionDenied(nil, "OIDC-Dsfb2", "token is not valid or has expired")
|
||||
|
||||
110
internal/api/oidc/introspect.go
Normal file
110
internal/api/oidc/introspect.go
Normal file
@@ -0,0 +1,110 @@
|
||||
package oidc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
"github.com/zitadel/oidc/v3/pkg/op"
|
||||
"github.com/zitadel/zitadel/internal/command"
|
||||
errz "github.com/zitadel/zitadel/internal/errors"
|
||||
)
|
||||
|
||||
func (s *Server) Introspect(ctx context.Context, r *op.Request[op.IntrospectionRequest]) (_ *op.Response, err error) {
|
||||
clientID, err := s.authenticateResourceClient(ctx, r.Data.ClientCredentials)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
response := new(oidc.IntrospectionResponse)
|
||||
tokenID, subject, err := s.getTokenIDAndSubject(ctx, r.Data.Token)
|
||||
if err != nil {
|
||||
// TODO: log error
|
||||
return op.NewResponse(response), nil
|
||||
}
|
||||
if strings.HasPrefix(tokenID, command.IDPrefixV2) {
|
||||
err = s.introspect(ctx, response, tokenID, subject, clientID)
|
||||
return op.NewResponse(response), nil
|
||||
}
|
||||
|
||||
err = s.storage.SetIntrospectionFromToken(ctx, response, tokenID, subject, clientID)
|
||||
if err != nil {
|
||||
return op.NewResponse(response), nil
|
||||
}
|
||||
response.Active = true
|
||||
return op.NewResponse(response), nil
|
||||
}
|
||||
|
||||
func (s *Server) authenticateResourceClient(ctx context.Context, cc *op.ClientCredentials) (clientID string, err error) {
|
||||
if cc.ClientAssertion != "" {
|
||||
verifier := op.NewJWTProfileVerifier(s.storage, op.IssuerFromContext(ctx), 1*time.Hour, time.Second)
|
||||
profile, err := op.VerifyJWTAssertion(ctx, cc.ClientAssertion, verifier)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return profile.Issuer, nil
|
||||
}
|
||||
|
||||
if err = s.storage.AuthorizeClientIDSecret(ctx, cc.ClientID, cc.ClientSecret); err != nil {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
return cc.ClientID, nil
|
||||
}
|
||||
|
||||
func (s *Server) getTokenIDAndSubject(ctx context.Context, accessToken string) (idToken, subject string, err error) {
|
||||
provider := s.Provider()
|
||||
tokenIDSubject, err := provider.Crypto().Decrypt(accessToken)
|
||||
if err == nil {
|
||||
splitToken := strings.Split(tokenIDSubject, ":")
|
||||
if len(splitToken) != 2 {
|
||||
return "", "", errors.New("invalid token format")
|
||||
}
|
||||
return splitToken[0], splitToken[1], nil
|
||||
}
|
||||
|
||||
verifier := op.NewAccessTokenVerifier(op.IssuerFromContext(ctx), s.storage.keySet)
|
||||
accessTokenClaims, err := op.VerifyAccessToken[*oidc.AccessTokenClaims](ctx, accessToken, verifier)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
return accessTokenClaims.JWTID, accessTokenClaims.Subject, nil
|
||||
}
|
||||
|
||||
func (s *Server) introspect(ctx context.Context, introspection *oidc.IntrospectionResponse, tokenID, subject, clientID string) (err error) {
|
||||
// TODO: give clients their own aggregate, so we can skip this query
|
||||
projectID, err := s.storage.query.ProjectIDFromClientID(ctx, clientID, false)
|
||||
if err != nil {
|
||||
return errz.ThrowPermissionDenied(nil, "OIDC-Adfg5", "client not found")
|
||||
}
|
||||
|
||||
token, err := s.storage.query.ActiveAccessTokenByToken(ctx, tokenID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !slices.ContainsFunc(token.Audience, func(aud string) bool {
|
||||
return aud == token.ClientID || aud == projectID
|
||||
}) {
|
||||
return errz.ThrowPermissionDenied(nil, "OIDC-sdg3G", "token is not valid for this client")
|
||||
}
|
||||
|
||||
userInfo, err := s.storage.query.GetOIDCUserinfo(ctx, token.UserID, token.Scope, []string{projectID})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
introspection.SetUserInfo(userinfoToOIDC(userInfo, token.Scope))
|
||||
introspection.Scope = token.Scope
|
||||
introspection.ClientID = token.ClientID
|
||||
introspection.TokenType = oidc.BearerToken
|
||||
introspection.Expiration = oidc.FromTime(token.AccessTokenExpiration)
|
||||
introspection.IssuedAt = oidc.FromTime(token.AccessTokenCreation)
|
||||
introspection.NotBefore = oidc.FromTime(token.AccessTokenCreation)
|
||||
introspection.Audience = token.Audience
|
||||
introspection.Issuer = op.IssuerFromContext(ctx)
|
||||
introspection.JWTID = tokenID
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -2,10 +2,7 @@ package oidc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
"github.com/zitadel/oidc/v3/pkg/op"
|
||||
@@ -163,62 +160,6 @@ func (s *Server) DeviceToken(ctx context.Context, r *op.ClientRequest[oidc.Devic
|
||||
return s.LegacyServer.DeviceToken(ctx, r)
|
||||
}
|
||||
|
||||
func (s *Server) authenticateResourceClient(ctx context.Context, cc *op.ClientCredentials) (clientID string, err error) {
|
||||
if cc.ClientAssertion != "" {
|
||||
verifier := op.NewJWTProfileVerifier(s.storage, op.IssuerFromContext(ctx), 1*time.Hour, time.Second)
|
||||
profile, err := op.VerifyJWTAssertion(ctx, cc.ClientAssertion, verifier)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return profile.Issuer, nil
|
||||
}
|
||||
|
||||
if err = s.storage.AuthorizeClientIDSecret(ctx, cc.ClientID, cc.ClientSecret); err != nil {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
return cc.ClientID, nil
|
||||
}
|
||||
|
||||
func (s *Server) getTokenIDAndSubject(ctx context.Context, accessToken string) (idToken, subject string, err error) {
|
||||
provider := s.Provider()
|
||||
tokenIDSubject, err := provider.Crypto().Decrypt(accessToken)
|
||||
if err == nil {
|
||||
splitToken := strings.Split(tokenIDSubject, ":")
|
||||
if len(splitToken) != 2 {
|
||||
return "", "", errors.New("invalid token format")
|
||||
}
|
||||
return splitToken[0], splitToken[1], nil
|
||||
}
|
||||
|
||||
verifier := op.NewAccessTokenVerifier(op.IssuerFromContext(ctx), s.storage.keySet)
|
||||
accessTokenClaims, err := op.VerifyAccessToken[*oidc.AccessTokenClaims](ctx, accessToken, verifier)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
return accessTokenClaims.JWTID, accessTokenClaims.Subject, nil
|
||||
}
|
||||
|
||||
func (s *Server) Introspect(ctx context.Context, r *op.Request[op.IntrospectionRequest]) (_ *op.Response, err error) {
|
||||
clientID, err := s.authenticateResourceClient(ctx, r.Data.ClientCredentials)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
response := new(oidc.IntrospectionResponse)
|
||||
tokenID, subject, err := s.getTokenIDAndSubject(ctx, r.Data.Token)
|
||||
if err != nil {
|
||||
// TODO: log error
|
||||
return op.NewResponse(response), nil
|
||||
}
|
||||
err = s.storage.SetIntrospectionFromToken(ctx, response, tokenID, subject, clientID)
|
||||
if err != nil {
|
||||
return op.NewResponse(response), nil
|
||||
}
|
||||
response.Active = true
|
||||
return op.NewResponse(response), nil
|
||||
}
|
||||
|
||||
func (s *Server) UserInfo(ctx context.Context, r *op.Request[oidc.UserInfoRequest]) (_ *op.Response, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
88
internal/api/oidc/userinfo.go
Normal file
88
internal/api/oidc/userinfo.go
Normal file
@@ -0,0 +1,88 @@
|
||||
package oidc
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
)
|
||||
|
||||
func userinfoToOIDC(user *query.OIDCUserinfo, scopes []string) *oidc.UserInfo {
|
||||
out := new(oidc.UserInfo)
|
||||
for _, scope := range scopes {
|
||||
switch scope {
|
||||
case oidc.ScopeOpenID:
|
||||
out.Subject = user.ID
|
||||
case oidc.ScopeEmail:
|
||||
out.UserInfoEmail = userInfoEmailToOIDC(user)
|
||||
case oidc.ScopeProfile:
|
||||
out.UserInfoProfile = userInfoProfileToOidc(user)
|
||||
case oidc.ScopePhone:
|
||||
out.UserInfoPhone = userInfoPhoneToOIDC(user)
|
||||
case oidc.ScopeAddress:
|
||||
out.Address = userInfoAddressToOIDC(user)
|
||||
case ScopeUserMetaData:
|
||||
if len(user.Metadata) > 0 {
|
||||
out.AppendClaims(ClaimUserMetaData, user.Metadata)
|
||||
}
|
||||
case ScopeResourceOwner:
|
||||
setUserInfoOrgClaims(user, out)
|
||||
default:
|
||||
if strings.HasPrefix(scope, domain.OrgDomainPrimaryScope) {
|
||||
out.AppendClaims(domain.OrgDomainPrimaryClaim, strings.TrimPrefix(scope, domain.OrgDomainPrimaryScope))
|
||||
}
|
||||
if strings.HasPrefix(scope, domain.OrgIDScope) {
|
||||
out.AppendClaims(domain.OrgIDClaim, strings.TrimPrefix(scope, domain.OrgIDScope))
|
||||
setUserInfoOrgClaims(user, out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func userInfoEmailToOIDC(user *query.OIDCUserinfo) oidc.UserInfoEmail {
|
||||
return oidc.UserInfoEmail{
|
||||
Email: string(user.Email),
|
||||
EmailVerified: oidc.Bool(user.IsEmailVerified),
|
||||
}
|
||||
}
|
||||
|
||||
func userInfoProfileToOidc(user *query.OIDCUserinfo) oidc.UserInfoProfile {
|
||||
return oidc.UserInfoProfile{
|
||||
Name: user.Name,
|
||||
GivenName: user.FirstName,
|
||||
FamilyName: user.LastName,
|
||||
Nickname: user.NickName,
|
||||
// Picture: domain.AvatarURL(o.assetAPIPrefix(ctx), user.ResourceOwner, user.Human.AvatarKey),
|
||||
Gender: getGender(user.Gender),
|
||||
Locale: oidc.NewLocale(user.PreferredLanguage),
|
||||
UpdatedAt: oidc.FromTime(user.UpdatedAt),
|
||||
// PreferredUsername: user.PreferredLoginName,
|
||||
}
|
||||
}
|
||||
|
||||
func userInfoPhoneToOIDC(user *query.OIDCUserinfo) oidc.UserInfoPhone {
|
||||
return oidc.UserInfoPhone{
|
||||
PhoneNumber: string(user.Phone),
|
||||
PhoneNumberVerified: user.IsPhoneVerified,
|
||||
}
|
||||
}
|
||||
|
||||
func userInfoAddressToOIDC(user *query.OIDCUserinfo) *oidc.UserInfoAddress {
|
||||
return &oidc.UserInfoAddress{
|
||||
// Formatted: ??,
|
||||
StreetAddress: user.StreetAddress,
|
||||
Locality: user.Locality,
|
||||
Region: user.Region,
|
||||
PostalCode: user.PostalCode,
|
||||
Country: user.Country,
|
||||
}
|
||||
}
|
||||
|
||||
func setUserInfoOrgClaims(user *query.OIDCUserinfo, out *oidc.UserInfo) {
|
||||
out.AppendClaims(ClaimResourceOwner+"id", user.OrgID)
|
||||
out.AppendClaims(ClaimResourceOwner+"name", user.OrgName)
|
||||
out.AppendClaims(ClaimResourceOwner+"primary_domain", user.OrgPrimaryDomain)
|
||||
}
|
||||
22
internal/domain/oidc_scopes.go
Normal file
22
internal/domain/oidc_scopes.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package domain
|
||||
|
||||
import "github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
|
||||
const (
|
||||
ScopeOpenID = oidc.ScopeOpenID
|
||||
ScopeProfile = oidc.ScopeProfile
|
||||
ScopeEmail = oidc.ScopeEmail
|
||||
ScopeAddress = oidc.ScopeAddress
|
||||
ScopePhone = oidc.ScopePhone
|
||||
ScopeOfflineAccess = oidc.ScopeOfflineAccess
|
||||
|
||||
ScopeProjectRolePrefix = "urn:zitadel:iam:org:project:role:"
|
||||
ScopeProjectsRoles = "urn:zitadel:iam:org:projects:roles"
|
||||
ClaimProjectRoles = "urn:zitadel:iam:org:project:roles"
|
||||
ClaimProjectRolesFormat = "urn:zitadel:iam:org:project:%s:roles"
|
||||
ScopeUserMetaData = "urn:zitadel:iam:user:metadata"
|
||||
ClaimUserMetaData = ScopeUserMetaData
|
||||
ScopeResourceOwner = "urn:zitadel:iam:user:resourceowner"
|
||||
ClaimResourceOwner = ScopeResourceOwner + ":"
|
||||
ClaimActionLogFormat = "urn:zitadel:iam:action:%s:log"
|
||||
)
|
||||
@@ -52,11 +52,8 @@ func (wm *OIDCSessionAccessTokenReadModel) Reduce() error {
|
||||
return wm.WriteModel.Reduce()
|
||||
}
|
||||
|
||||
func (wm *OIDCSessionAccessTokenReadModel) Query() *eventstore.SearchQueryBuilder {
|
||||
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
|
||||
AwaitOpenTransactions().
|
||||
AllowTimeTravel().
|
||||
AddQuery().
|
||||
func (wm *OIDCSessionAccessTokenReadModel) addQuery(b *eventstore.SearchQueryBuilder) *eventstore.SearchQueryBuilder {
|
||||
return b.AddQuery().
|
||||
AggregateTypes(oidcsession.AggregateType).
|
||||
AggregateIDs(wm.AggregateID).
|
||||
EventTypes(
|
||||
@@ -68,6 +65,14 @@ func (wm *OIDCSessionAccessTokenReadModel) Query() *eventstore.SearchQueryBuilde
|
||||
Builder()
|
||||
}
|
||||
|
||||
func (wm *OIDCSessionAccessTokenReadModel) Query() *eventstore.SearchQueryBuilder {
|
||||
return wm.addQuery(
|
||||
eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
|
||||
AwaitOpenTransactions().
|
||||
AllowTimeTravel(),
|
||||
)
|
||||
}
|
||||
|
||||
func (wm *OIDCSessionAccessTokenReadModel) reduceAdded(e *oidcsession.AddedEvent) {
|
||||
wm.UserID = e.UserID
|
||||
wm.SessionID = e.SessionID
|
||||
|
||||
254
internal/query/userinfo.go
Normal file
254
internal/query/userinfo.go
Normal file
@@ -0,0 +1,254 @@
|
||||
package query
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/muhlemmer/gu"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/repository/org"
|
||||
"github.com/zitadel/zitadel/internal/repository/user"
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
func (q *Queries) GetOIDCUserinfo(ctx context.Context, userID string, scope, roleAudience []string) (_ *OIDCUserinfo, err error) {
|
||||
if slices.Contains(scope, domain.ScopeProjectsRoles) {
|
||||
roleAudience = domain.AddAudScopeToAudience(ctx, roleAudience, scope)
|
||||
// TODO: we need to get the project roles and user roles.
|
||||
}
|
||||
|
||||
user := newOidcUserinfoReadModel(userID, scope)
|
||||
if err = q.eventstore.FilterToQueryReducer(ctx, user); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if hasOrgScope(scope) {
|
||||
org := newoidcUserinfoOrganizationReadModel(user.ResourceOwner)
|
||||
if err = q.eventstore.FilterToQueryReducer(ctx, org); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
user.OrgID = org.AggregateID
|
||||
user.OrgName = org.Name
|
||||
user.OrgPrimaryDomain = org.PrimaryDomain
|
||||
}
|
||||
|
||||
return &user.OIDCUserinfo, nil
|
||||
}
|
||||
|
||||
func hasOrgScope(scope []string) bool {
|
||||
return slices.ContainsFunc(scope, func(s string) bool {
|
||||
return s == domain.ScopeResourceOwner || strings.HasPrefix(s, domain.OrgIDScope)
|
||||
})
|
||||
}
|
||||
|
||||
type OIDCUserinfo struct {
|
||||
ID string
|
||||
UserName string
|
||||
Name string
|
||||
FirstName string
|
||||
LastName string
|
||||
NickName string
|
||||
PreferredLanguage language.Tag
|
||||
Gender domain.Gender
|
||||
Avatar string
|
||||
UpdatedAt time.Time
|
||||
|
||||
Email domain.EmailAddress
|
||||
IsEmailVerified bool
|
||||
|
||||
Phone domain.PhoneNumber
|
||||
IsPhoneVerified bool
|
||||
|
||||
Country string
|
||||
Locality string
|
||||
PostalCode string
|
||||
Region string
|
||||
StreetAddress string
|
||||
|
||||
UserState domain.UserState
|
||||
UserType domain.UserType
|
||||
|
||||
OrgID string
|
||||
OrgName string
|
||||
OrgPrimaryDomain string
|
||||
|
||||
Metadata map[string]string
|
||||
}
|
||||
|
||||
type oidcUserinfoReadmodel struct {
|
||||
eventstore.ReadModel
|
||||
scope []string // Scope is used to determine events
|
||||
OIDCUserinfo
|
||||
}
|
||||
|
||||
func newOidcUserinfoReadModel(userID string, scope []string) *oidcUserinfoReadmodel {
|
||||
return &oidcUserinfoReadmodel{
|
||||
ReadModel: eventstore.ReadModel{
|
||||
AggregateID: userID,
|
||||
},
|
||||
scope: scope,
|
||||
OIDCUserinfo: OIDCUserinfo{
|
||||
ID: userID,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (rm *oidcUserinfoReadmodel) Query() *eventstore.SearchQueryBuilder {
|
||||
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
|
||||
AwaitOpenTransactions().
|
||||
AllowTimeTravel().
|
||||
AddQuery().
|
||||
AggregateTypes(user.AggregateType).
|
||||
AggregateIDs(rm.AggregateID).
|
||||
EventTypes(rm.scopeToEventTypes()...).
|
||||
Builder()
|
||||
}
|
||||
|
||||
// scopeToEventTypes sets required user events to obtain get the correct userinfo.
|
||||
// Events such as UserLocked, UserDeactivated and UserRemoved are not checked,
|
||||
// as access tokens should already be revoked.
|
||||
func (rm *oidcUserinfoReadmodel) scopeToEventTypes() []eventstore.EventType {
|
||||
types := make([]eventstore.EventType, 0, len(rm.scope))
|
||||
types = append(types, user.HumanAddedType, user.MachineAddedEventType)
|
||||
|
||||
for _, scope := range rm.scope {
|
||||
switch scope {
|
||||
case domain.ScopeEmail:
|
||||
types = append(types, user.HumanEmailChangedType, user.HumanEmailVerifiedType)
|
||||
case domain.ScopeProfile:
|
||||
types = append(types, user.HumanProfileChangedType, user.HumanAvatarAddedType, user.HumanAvatarRemovedType)
|
||||
case domain.ScopePhone:
|
||||
types = append(types, user.HumanPhoneChangedType, user.HumanPhoneVerifiedType, user.HumanPhoneRemovedType)
|
||||
case domain.ScopeAddress:
|
||||
types = append(types, user.HumanAddressChangedType)
|
||||
case domain.ScopeUserMetaData:
|
||||
types = append(types, user.MetadataSetType, user.MetadataRemovedType, user.MetadataRemovedAllType)
|
||||
}
|
||||
}
|
||||
return slices.Compact(types)
|
||||
}
|
||||
|
||||
func (rm *oidcUserinfoReadmodel) Reduce() error {
|
||||
for _, event := range rm.Events {
|
||||
switch e := event.(type) {
|
||||
case *user.HumanAddedEvent:
|
||||
rm.UserName = e.UserName
|
||||
rm.FirstName = e.FirstName
|
||||
rm.LastName = e.LastName
|
||||
rm.NickName = e.NickName
|
||||
rm.Name = e.DisplayName
|
||||
rm.PreferredLanguage = e.PreferredLanguage
|
||||
rm.Gender = e.Gender
|
||||
rm.Email = e.EmailAddress
|
||||
rm.Phone = e.PhoneNumber
|
||||
rm.Country = e.Country
|
||||
rm.Locality = e.Locality
|
||||
rm.PostalCode = e.PostalCode
|
||||
rm.Region = e.Region
|
||||
rm.StreetAddress = e.StreetAddress
|
||||
rm.UpdatedAt = e.Creation
|
||||
case *user.MachineAddedEvent:
|
||||
rm.UserName = e.UserName
|
||||
rm.Name = e.Name
|
||||
rm.UpdatedAt = e.Creation
|
||||
case *user.HumanEmailChangedEvent:
|
||||
rm.Email = e.EmailAddress
|
||||
rm.IsEmailVerified = false
|
||||
rm.UpdatedAt = e.Creation
|
||||
case *user.HumanEmailVerifiedEvent:
|
||||
rm.IsEmailVerified = e.IsEmailVerified
|
||||
rm.UpdatedAt = e.Creation
|
||||
case *user.HumanProfileChangedEvent:
|
||||
rm.FirstName = e.FirstName
|
||||
rm.LastName = e.LastName
|
||||
rm.NickName = gu.Value(e.NickName)
|
||||
rm.Name = gu.Value(e.DisplayName)
|
||||
rm.PreferredLanguage = gu.Value(e.PreferredLanguage)
|
||||
rm.Gender = gu.Value(e.Gender)
|
||||
rm.UpdatedAt = e.Creation
|
||||
case *user.HumanAvatarAddedEvent:
|
||||
rm.Avatar = e.StoreKey
|
||||
rm.UpdatedAt = e.Creation
|
||||
case *user.HumanAvatarRemovedEvent:
|
||||
rm.Avatar = ""
|
||||
rm.UpdatedAt = e.Creation
|
||||
case *user.HumanPhoneChangedEvent:
|
||||
rm.Phone = e.PhoneNumber
|
||||
rm.IsPhoneVerified = false
|
||||
rm.UpdatedAt = e.Creation
|
||||
case *user.HumanPhoneVerifiedEvent:
|
||||
rm.IsEmailVerified = e.IsPhoneVerified
|
||||
rm.UpdatedAt = e.Creation
|
||||
case *user.HumanPhoneRemovedEvent:
|
||||
rm.Phone = ""
|
||||
rm.IsPhoneVerified = false
|
||||
rm.UpdatedAt = e.Creation
|
||||
case *user.HumanAddressChangedEvent:
|
||||
rm.Country = gu.Value(e.Country)
|
||||
rm.Locality = gu.Value(e.Locality)
|
||||
rm.PostalCode = gu.Value(e.PostalCode)
|
||||
rm.Region = gu.Value(e.Region)
|
||||
rm.StreetAddress = gu.Value(e.StreetAddress)
|
||||
rm.UpdatedAt = e.Creation
|
||||
case *user.MetadataSetEvent:
|
||||
rm.Metadata[e.Key] = base64.RawURLEncoding.EncodeToString(e.Value)
|
||||
rm.UpdatedAt = e.Creation
|
||||
case *user.MetadataRemovedEvent:
|
||||
delete(rm.Metadata, e.Key)
|
||||
rm.UpdatedAt = e.Creation
|
||||
case *user.MetadataRemovedAllEvent:
|
||||
for key := range rm.Metadata {
|
||||
delete(rm.Metadata, key)
|
||||
}
|
||||
rm.UpdatedAt = e.Creation
|
||||
}
|
||||
}
|
||||
|
||||
return rm.ReadModel.Reduce()
|
||||
}
|
||||
|
||||
type oidcUserinfoOrganizationReadModel struct {
|
||||
eventstore.ReadModel
|
||||
|
||||
Name string
|
||||
PrimaryDomain string
|
||||
}
|
||||
|
||||
func newoidcUserinfoOrganizationReadModel(orgID string) *oidcUserinfoOrganizationReadModel {
|
||||
return &oidcUserinfoOrganizationReadModel{
|
||||
ReadModel: eventstore.ReadModel{
|
||||
AggregateID: orgID,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (rm *oidcUserinfoOrganizationReadModel) Query() *eventstore.SearchQueryBuilder {
|
||||
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
|
||||
AwaitOpenTransactions().
|
||||
AllowTimeTravel().
|
||||
AddQuery().
|
||||
AggregateTypes(org.AggregateType).
|
||||
AggregateIDs(rm.AggregateID).
|
||||
EventTypes(org.OrgAddedEventType, org.OrgChangedEventType, org.OrgDomainPrimarySetEventType).
|
||||
Builder()
|
||||
}
|
||||
|
||||
func (rm *oidcUserinfoOrganizationReadModel) Reduce() error {
|
||||
for _, event := range rm.Events {
|
||||
switch e := event.(type) {
|
||||
case *org.OrgAddedEvent:
|
||||
rm.Name = e.Name
|
||||
case *org.OrgChangedEvent:
|
||||
rm.Name = e.Name
|
||||
case *org.DomainPrimarySetEvent:
|
||||
rm.PrimaryDomain = e.Domain
|
||||
}
|
||||
}
|
||||
|
||||
return rm.ReadModel.Reduce()
|
||||
}
|
||||
Reference in New Issue
Block a user