get oidc user info from projections and add actions

This commit is contained in:
Tim Möhlmann
2023-11-13 18:13:34 +02:00
parent d69b9999a1
commit 8eea5eccd1
8 changed files with 309 additions and 337 deletions

View File

@@ -34,6 +34,27 @@ func UserMetadataListFromQuery(c *actions.FieldConfig, metadata *query.UserMetad
return c.Runtime.ToValue(result) return c.Runtime.ToValue(result)
} }
func UserMetadataListFromSlice(c *actions.FieldConfig, metadata []query.UserMetadata) goja.Value {
result := &userMetadataList{
// Count was the only field ever queries from the DB in the old implementation,
// so Sequence and LastRun are omitted.
Count: uint64(len(metadata)),
Metadata: make([]*userMetadata, len(metadata)),
}
for i, md := range metadata {
result.Metadata[i] = &userMetadata{
CreationDate: md.CreationDate,
ChangeDate: md.ChangeDate,
ResourceOwner: md.ResourceOwner,
Sequence: md.Sequence,
Key: md.Key,
Value: metadataByteArrayToValue(md.Value, c.Runtime),
}
}
return c.Runtime.ToValue(result)
}
func metadataByteArrayToValue(val []byte, runtime *goja.Runtime) goja.Value { func metadataByteArrayToValue(val []byte, runtime *goja.Runtime) goja.Value {
var value interface{} var value interface{}
if !json.Valid(val) { if !json.Valid(val) {

View File

@@ -9,6 +9,7 @@ import (
"github.com/zitadel/logging" "github.com/zitadel/logging"
"github.com/zitadel/oidc/v3/pkg/oidc" "github.com/zitadel/oidc/v3/pkg/oidc"
"github.com/zitadel/oidc/v3/pkg/op" "github.com/zitadel/oidc/v3/pkg/op"
"github.com/zitadel/zitadel/internal/command"
"github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/query" "github.com/zitadel/zitadel/internal/query"
"github.com/zitadel/zitadel/internal/telemetry/tracing" "github.com/zitadel/zitadel/internal/telemetry/tracing"
@@ -19,7 +20,8 @@ type Server struct {
storage *OPStorage storage *OPStorage
*op.LegacyServer *op.LegacyServer
query *query.Queries query *query.Queries
command *command.Commands
fallbackLogger *slog.Logger fallbackLogger *slog.Logger
hashAlg crypto.HashAlgorithm hashAlg crypto.HashAlgorithm

View File

@@ -2,11 +2,18 @@ package oidc
import ( import (
"context" "context"
"encoding/base64"
"encoding/json"
"fmt"
"slices" "slices"
"strings" "strings"
"github.com/dop251/goja"
"github.com/zitadel/logging"
"github.com/zitadel/oidc/v3/pkg/oidc" "github.com/zitadel/oidc/v3/pkg/oidc"
"github.com/zitadel/zitadel/internal/actions"
"github.com/zitadel/zitadel/internal/actions/object"
"github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/query" "github.com/zitadel/zitadel/internal/query"
"github.com/zitadel/zitadel/internal/telemetry/tracing" "github.com/zitadel/zitadel/internal/telemetry/tracing"
@@ -19,7 +26,7 @@ func (s *Server) getUserInfoWithRoles(ctx context.Context, userID, projectID str
defer cancel() defer cancel()
userInfoChan := make(chan *userInfoResult) userInfoChan := make(chan *userInfoResult)
go s.getUserInfo(ctx, userID, scope, roleAudience, userInfoChan) go s.getUserInfo(ctx, userID, userInfoChan)
rolesChan := make(chan *assertRolesResult) rolesChan := make(chan *assertRolesResult)
go s.assertRoles(ctx, userID, projectID, scope, roleAudience, rolesChan) go s.assertRoles(ctx, userID, projectID, scope, roleAudience, rolesChan)
@@ -56,7 +63,7 @@ func (s *Server) getUserInfoWithRoles(ctx context.Context, userID, projectID str
userInfo := userInfoToOIDC(userInfoResult.userInfo, scope) userInfo := userInfoToOIDC(userInfoResult.userInfo, scope)
setUserInfoRoleClaims(userInfo, assertRolesResult.projectsRoles) setUserInfoRoleClaims(userInfo, assertRolesResult.projectsRoles)
return userInfo, nil return userInfo, s.userinfoFlows(ctx, userInfoResult.userInfo, assertRolesResult.userGrants, userInfo)
} }
type userInfoResult struct { type userInfoResult struct {
@@ -64,8 +71,8 @@ type userInfoResult struct {
err error err error
} }
func (s *Server) getUserInfo(ctx context.Context, userID string, scope, roleAudience []string, rc chan<- *userInfoResult) { func (s *Server) getUserInfo(ctx context.Context, userID string, rc chan<- *userInfoResult) {
userInfo, err := s.storage.query.GetOIDCUserInfo(ctx, userID, scope, roleAudience) userInfo, err := s.storage.query.GetOIDCUserInfo(ctx, userID)
rc <- &userInfoResult{ rc <- &userInfoResult{
userInfo: userInfo, userInfo: userInfo,
err: err, err: err,
@@ -81,7 +88,7 @@ type assertRolesResult struct {
func (s *Server) assertRoles(ctx context.Context, userID, projectID string, scope, roleAudience []string, rc chan<- *assertRolesResult) { func (s *Server) assertRoles(ctx context.Context, userID, projectID string, scope, roleAudience []string, rc chan<- *assertRolesResult) {
userGrands, projectsRoles, err := func() (*query.UserGrants, *projectsRoles, error) { userGrands, projectsRoles, err := func() (*query.UserGrants, *projectsRoles, error) {
// if all roles are requested take the audience for those from the scopes // if all roles are requested take the audience for those from the scopes
if slices.Contains(scope, domain.ScopeProjectsRoles) { if slices.Contains(scope, ScopeProjectsRoles) {
roleAudience = domain.AddAudScopeToAudience(ctx, roleAudience, scope) roleAudience = domain.AddAudScopeToAudience(ctx, roleAudience, scope)
} }
@@ -148,19 +155,17 @@ func userInfoToOIDC(user *query.OIDCUserInfo, scope []string) *oidc.UserInfo {
for _, s := range scope { for _, s := range scope {
switch s { switch s {
case oidc.ScopeOpenID: case oidc.ScopeOpenID:
out.Subject = user.ID out.Subject = user.User.ID
case oidc.ScopeEmail: case oidc.ScopeEmail:
out.UserInfoEmail = userInfoEmailToOIDC(user) out.UserInfoEmail = userInfoEmailToOIDC(user.User)
case oidc.ScopeProfile: case oidc.ScopeProfile:
out.UserInfoProfile = userInfoProfileToOidc(user) out.UserInfoProfile = userInfoProfileToOidc(user.User)
case oidc.ScopePhone: case oidc.ScopePhone:
out.UserInfoPhone = userInfoPhoneToOIDC(user) out.UserInfoPhone = userInfoPhoneToOIDC(user.User)
case oidc.ScopeAddress: case oidc.ScopeAddress:
out.Address = userInfoAddressToOIDC(user) //TODO: handle address for human users as soon as implemented
case ScopeUserMetaData: case ScopeUserMetaData:
if len(user.Metadata) > 0 { setUserInfoMetadata(user.Metadata, out)
out.AppendClaims(ClaimUserMetaData, user.Metadata)
}
case ScopeResourceOwner: case ScopeResourceOwner:
setUserInfoOrgClaims(user, out) setUserInfoOrgClaims(user, out)
default: default:
@@ -177,47 +182,173 @@ func userInfoToOIDC(user *query.OIDCUserInfo, scope []string) *oidc.UserInfo {
return out return out
} }
func userInfoEmailToOIDC(user *query.OIDCUserInfo) oidc.UserInfoEmail { func userInfoEmailToOIDC(user *query.User) oidc.UserInfoEmail {
return oidc.UserInfoEmail{ if human := user.Human; human != nil {
Email: string(user.Email), return oidc.UserInfoEmail{
EmailVerified: oidc.Bool(user.IsEmailVerified), Email: string(human.Email),
EmailVerified: oidc.Bool(human.IsEmailVerified),
}
} }
return oidc.UserInfoEmail{}
} }
func userInfoProfileToOidc(user *query.OIDCUserInfo) oidc.UserInfoProfile { func userInfoProfileToOidc(user *query.User) oidc.UserInfoProfile {
if human := user.Human; human != nil {
return oidc.UserInfoProfile{
Name: human.DisplayName,
GivenName: human.FirstName,
FamilyName: human.LastName,
Nickname: human.NickName,
// Picture: domain.AvatarURL(o.assetAPIPrefix(ctx), user.ResourceOwner, user.Human.AvatarKey),
Gender: getGender(human.Gender),
Locale: oidc.NewLocale(human.PreferredLanguage),
UpdatedAt: oidc.FromTime(user.ChangeDate),
PreferredUsername: user.PreferredLoginName,
}
}
if machine := user.Machine; machine != nil {
return oidc.UserInfoProfile{
Name: machine.Name,
UpdatedAt: oidc.FromTime(user.ChangeDate),
PreferredUsername: user.PreferredLoginName,
}
}
return oidc.UserInfoProfile{ return oidc.UserInfoProfile{
Name: user.Name, UpdatedAt: oidc.FromTime(user.ChangeDate),
GivenName: user.FirstName, PreferredUsername: user.PreferredLoginName,
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 { func userInfoPhoneToOIDC(user *query.User) oidc.UserInfoPhone {
return oidc.UserInfoPhone{ if human := user.Human; human != nil {
PhoneNumber: string(user.Phone), return oidc.UserInfoPhone{
PhoneNumberVerified: user.IsPhoneVerified, PhoneNumber: string(human.Phone),
PhoneNumberVerified: human.IsPhoneVerified,
}
} }
return oidc.UserInfoPhone{}
} }
func userInfoAddressToOIDC(user *query.OIDCUserInfo) *oidc.UserInfoAddress { func setUserInfoMetadata(metadata []query.UserMetadata, out *oidc.UserInfo) {
return &oidc.UserInfoAddress{ if len(metadata) == 0 {
// Formatted: ??, return
StreetAddress: user.StreetAddress,
Locality: user.Locality,
Region: user.Region,
PostalCode: user.PostalCode,
Country: user.Country,
} }
mdmap := make(map[string]string, len(metadata))
for _, md := range metadata {
mdmap[md.Key] = base64.RawURLEncoding.EncodeToString(md.Value)
}
out.AppendClaims(ClaimUserMetaData, mdmap)
} }
func setUserInfoOrgClaims(user *query.OIDCUserInfo, out *oidc.UserInfo) { func setUserInfoOrgClaims(user *query.OIDCUserInfo, out *oidc.UserInfo) {
out.AppendClaims(ClaimResourceOwner+"id", user.OrgID) if org := user.Org; org != nil {
out.AppendClaims(ClaimResourceOwner+"name", user.OrgName) out.AppendClaims(ClaimResourceOwner+"id", org.ID)
out.AppendClaims(ClaimResourceOwner+"primary_domain", user.OrgPrimaryDomain) out.AppendClaims(ClaimResourceOwner+"name", org.Name)
out.AppendClaims(ClaimResourceOwner+"primary_domain", org.PrimaryDomain)
}
}
func (s *Server) userinfoFlows(ctx context.Context, user *query.OIDCUserInfo, userGrants *query.UserGrants, userInfo *oidc.UserInfo) error {
queriedActions, err := s.query.GetActiveActionsByFlowAndTriggerType(ctx, domain.FlowTypeCustomiseToken, domain.TriggerTypePreUserinfoCreation, user.User.ResourceOwner, false)
if err != nil {
return err
}
ctxFields := actions.SetContextFields(
actions.SetFields("v1",
actions.SetFields("claims", userinfoClaims(userInfo)),
actions.SetFields("getUser", func(c *actions.FieldConfig) interface{} {
return func(call goja.FunctionCall) goja.Value {
return object.UserFromQuery(c, user.User)
}
}),
actions.SetFields("user",
actions.SetFields("getMetadata", func(c *actions.FieldConfig) interface{} {
return func(goja.FunctionCall) goja.Value {
return object.UserMetadataListFromSlice(c, user.Metadata)
}
}),
actions.SetFields("grants", func(c *actions.FieldConfig) interface{} {
return object.UserGrantsFromQuery(c, userGrants)
}),
),
),
)
for _, action := range queriedActions {
actionCtx, cancel := context.WithTimeout(ctx, action.Timeout())
claimLogs := []string{}
apiFields := actions.WithAPIFields(
actions.SetFields("v1",
actions.SetFields("userinfo",
actions.SetFields("setClaim", func(key string, value interface{}) {
if userInfo.Claims[key] == nil {
userInfo.AppendClaims(key, value)
return
}
claimLogs = append(claimLogs, fmt.Sprintf("key %q already exists", key))
}),
actions.SetFields("appendLogIntoClaims", func(entry string) {
claimLogs = append(claimLogs, entry)
}),
),
actions.SetFields("claims",
actions.SetFields("setClaim", func(key string, value interface{}) {
if userInfo.Claims[key] == nil {
userInfo.AppendClaims(key, value)
return
}
claimLogs = append(claimLogs, fmt.Sprintf("key %q already exists", key))
}),
actions.SetFields("appendLogIntoClaims", func(entry string) {
claimLogs = append(claimLogs, entry)
}),
),
actions.SetFields("user",
actions.SetFields("setMetadata", func(call goja.FunctionCall) goja.Value {
if len(call.Arguments) != 2 {
panic("exactly 2 (key, value) arguments expected")
}
key := call.Arguments[0].Export().(string)
val := call.Arguments[1].Export()
value, err := json.Marshal(val)
if err != nil {
logging.WithError(err).Debug("unable to marshal")
panic(err)
}
metadata := &domain.Metadata{
Key: key,
Value: value,
}
if _, err = s.command.SetUserMetadata(ctx, metadata, userInfo.Subject, user.User.ResourceOwner); err != nil {
logging.WithError(err).Info("unable to set md in action")
panic(err)
}
return nil
}),
),
),
)
err = actions.Run(
actionCtx,
ctxFields,
apiFields,
action.Script,
action.Name,
append(actions.ActionToOptions(action), actions.WithHTTP(actionCtx), actions.WithUUID(actionCtx))...,
)
cancel()
if err != nil {
return err
}
if len(claimLogs) > 0 {
userInfo.AppendClaims(fmt.Sprintf(ClaimActionLogFormat, action.Name), claimLogs)
}
}
return nil
} }

View File

@@ -1,22 +0,0 @@
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"
)

View File

@@ -0,0 +1,50 @@
with usr as (
select id, creation_date, change_date, sequence, state, resource_owner, username
from projections.users8 u
where id = $1
and instance_id = $2
),
human as (
select $1 as user_id, row_to_json(r) as human from (
select first_name, last_name, nick_name, display_name, avatar_key, email, is_email_verified, phone, is_phone_verified
from projections.users8_humans
where user_id = $1
and instance_id = $2
) r
),
machine as (
select $1 as user_id, row_to_json(r) as machine from (
select name, description
from projections.users8_machines
where user_id = $1
and instance_id = $2
) r
),
metadata as (
select json_agg(row_to_json(r)) as metadata from (
select creation_date, change_date, sequence, resource_owner, key, encode(value, 'base64') as value
from projections.user_metadata4
where user_id = $1
and instance_id = $2
) r
),
org as (
select row_to_json(r) as organization from (
select name, primary_domain
from projections.orgs1 o
join usr u on o.id = u.resource_owner
where instance_id = $2
) r
)
select json_build_object(
'user', (
select row_to_json(r) as usr from (
select u.*, h.human, m.machine
from usr u
left join human h on u.id = h.user_id
left join machine m on u.id = m.user_id
) r
),
'organization', (select organization from org),
'metadata', (select metadata from metadata)
);

View File

@@ -28,32 +28,32 @@ type Users struct {
} }
type User struct { type User struct {
ID string ID string `json:"id,omitempty"`
CreationDate time.Time CreationDate time.Time `json:"creation_date,omitempty"`
ChangeDate time.Time ChangeDate time.Time `json:"change_date,omitempty"`
ResourceOwner string ResourceOwner string `json:"resource_owner,omitempty"`
Sequence uint64 Sequence uint64 `json:"sequence,omitempty"`
State domain.UserState State domain.UserState `json:"state,omitempty"`
Type domain.UserType Type domain.UserType `json:"type,omitempty"`
Username string Username string `json:"username,omitempty"`
LoginNames database.TextArray[string] LoginNames database.TextArray[string] `json:"login_names,omitempty"`
PreferredLoginName string PreferredLoginName string `json:"preferred_login_name,omitempty"`
Human *Human Human *Human `json:"human,omitempty"`
Machine *Machine Machine *Machine `json:"machine,omitempty"`
} }
type Human struct { type Human struct {
FirstName string FirstName string `json:"first_name,omitempty"`
LastName string LastName string `json:"last_name,omitempty"`
NickName string NickName string `json:"nick_name,omitempty"`
DisplayName string DisplayName string `json:"display_name,omitempty"`
AvatarKey string AvatarKey string `json:"avatar_key,omitempty"`
PreferredLanguage language.Tag PreferredLanguage language.Tag `json:"preferred_language,omitempty"`
Gender domain.Gender Gender domain.Gender `json:"gender,omitempty"`
Email domain.EmailAddress Email domain.EmailAddress `json:"email,omitempty"`
IsEmailVerified bool IsEmailVerified bool `json:"is_email_verified,omitempty"`
Phone domain.PhoneNumber Phone domain.PhoneNumber `json:"phone,omitempty"`
IsPhoneVerified bool IsPhoneVerified bool `json:"is_phone_verified,omitempty"`
} }
type Profile struct { type Profile struct {
@@ -92,10 +92,10 @@ type Phone struct {
} }
type Machine struct { type Machine struct {
Name string Name string `json:"name,omitempty"`
Description string Description string `json:"description,omitempty"`
HasSecret bool HasSecret bool `json:"has_secret,omitempty"`
AccessTokenType domain.OIDCTokenType AccessTokenType domain.OIDCTokenType `json:"access_token_type,omitempty"`
} }
type NotifyUser struct { type NotifyUser struct {

View File

@@ -24,12 +24,12 @@ type UserMetadataList struct {
} }
type UserMetadata struct { type UserMetadata struct {
CreationDate time.Time CreationDate time.Time `json:"creation_date,omitempty"`
ChangeDate time.Time ChangeDate time.Time `json:"change_date,omitempty"`
ResourceOwner string ResourceOwner string `json:"resource_owner,omitempty"`
Sequence uint64 Sequence uint64 `json:"sequence,omitempty"`
Key string Key string `json:"key,omitempty"`
Value []byte Value []byte `json:"value,omitempty"`
} }
type UserMetadataSearchQueries struct { type UserMetadataSearchQueries struct {

View File

@@ -2,253 +2,43 @@ package query
import ( import (
"context" "context"
"encoding/base64" "database/sql"
"slices" _ "embed"
"strings" "encoding/json"
"time" "fmt"
"github.com/muhlemmer/gu" "github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/errors"
"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) { //go:embed embed/userinfo_by_id.sql
if slices.Contains(scope, domain.ScopeProjectsRoles) { var oidcUserInfoQuery string
roleAudience = domain.AddAudScopeToAudience(ctx, roleAudience, scope)
// TODO: we need to get the project roles and user roles.
}
user := newOidcUserinfoReadModel(userID, scope) func (q *Queries) GetOIDCUserInfo(ctx context.Context, userID string) (_ *OIDCUserInfo, err error) {
if err = q.eventstore.FilterToQueryReducer(ctx, user); err != nil { userInfo := new(OIDCUserInfo)
return nil, err err = q.client.QueryRowContext(ctx, func(row *sql.Row) error {
} var data []byte
if err := row.Scan(&data); err != nil {
if hasOrgScope(scope) { return err
org := newoidcUserinfoOrganizationReadModel(user.ResourceOwner)
if err = q.eventstore.FilterToQueryReducer(ctx, org); err != nil {
return nil, err
} }
return json.Unmarshal(data, userInfo)
user.OrgID = org.AggregateID }, oidcUserInfoQuery, userID, authz.GetInstance(ctx).InstanceID())
user.OrgName = org.Name if err != nil {
user.OrgPrimaryDomain = org.PrimaryDomain return nil, fmt.Errorf("get oidc user info: %w", err)
}
if userInfo.User == nil {
return nil, errors.ThrowNotFound(nil, "QUERY-ahs4S", "Errors.User.NotFound")
} }
return &user.OIDCUserInfo, nil return userInfo, 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 { type OIDCUserInfo struct {
ID string User *User `json:"user,omitempty"`
UserName string Metadata []UserMetadata `json:"metadata,omitempty"`
Name string Org *struct {
FirstName string ID string `json:"id,omitempty"`
LastName string Name string `json:"name,omitempty"`
NickName string PrimaryDomain string `json:"primary_domain,omitempty"`
PreferredLanguage language.Tag } `json:"org,omitempty"`
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()
} }