mirror of
https://github.com/zitadel/zitadel.git
synced 2025-10-20 20:29:16 +00:00
add user_grants to the userinfo query
This commit is contained in:
@@ -90,6 +90,36 @@ func UserGrantsFromQuery(c *actions.FieldConfig, userGrants *query.UserGrants) g
|
|||||||
return c.Runtime.ToValue(grantList)
|
return c.Runtime.ToValue(grantList)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func UserGrantsFromSlice(c *actions.FieldConfig, userGrants []query.UserGrant) goja.Value {
|
||||||
|
if userGrants == nil {
|
||||||
|
return c.Runtime.ToValue(nil)
|
||||||
|
}
|
||||||
|
grantList := &userGrantList{
|
||||||
|
Count: uint64(len(userGrants)),
|
||||||
|
Grants: make([]*userGrant, len(userGrants)),
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, grant := range userGrants {
|
||||||
|
grantList.Grants[i] = &userGrant{
|
||||||
|
Id: grant.ID,
|
||||||
|
ProjectGrantId: grant.GrantID,
|
||||||
|
State: grant.State,
|
||||||
|
CreationDate: grant.CreationDate,
|
||||||
|
ChangeDate: grant.ChangeDate,
|
||||||
|
Sequence: grant.Sequence,
|
||||||
|
UserId: grant.UserID,
|
||||||
|
Roles: grant.Roles,
|
||||||
|
UserResourceOwner: grant.UserResourceOwner,
|
||||||
|
UserGrantResourceOwner: grant.ResourceOwner,
|
||||||
|
UserGrantResourceOwnerName: grant.OrgName,
|
||||||
|
ProjectId: grant.ProjectID,
|
||||||
|
ProjectName: grant.ProjectName,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Runtime.ToValue(grantList)
|
||||||
|
}
|
||||||
|
|
||||||
func UserGrantsToDomain(userID string, actionUserGrants []UserGrant) []*domain.UserGrant {
|
func UserGrantsToDomain(userID string, actionUserGrants []UserGrant) []*domain.UserGrant {
|
||||||
if actionUserGrants == nil {
|
if actionUserGrants == nil {
|
||||||
return nil
|
return nil
|
||||||
|
@@ -795,7 +795,7 @@ func (o *OPStorage) assertRoles(ctx context.Context, userID, applicationID strin
|
|||||||
if len(requestedRoles) > 0 {
|
if len(requestedRoles) > 0 {
|
||||||
for _, requestedRole := range requestedRoles {
|
for _, requestedRole := range requestedRoles {
|
||||||
for _, grant := range grants.UserGrants {
|
for _, grant := range grants.UserGrants {
|
||||||
checkGrantedRoles(roles, grant, requestedRole, grant.ProjectID == projectID)
|
checkGrantedRoles(roles, *grant, requestedRole, grant.ProjectID == projectID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return grants, roles, nil
|
return grants, roles, nil
|
||||||
@@ -838,7 +838,7 @@ func (o *OPStorage) assertUserResourceOwner(ctx context.Context, userID string)
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkGrantedRoles(roles *projectsRoles, grant *query.UserGrant, requestedRole string, isRequested bool) {
|
func checkGrantedRoles(roles *projectsRoles, grant query.UserGrant, requestedRole string, isRequested bool) {
|
||||||
for _, grantedRole := range grant.Roles {
|
for _, grantedRole := range grant.Roles {
|
||||||
if requestedRole == grantedRole {
|
if requestedRole == grantedRole {
|
||||||
roles.Add(grant.ProjectID, grantedRole, grant.ResourceOwner, grant.OrgPrimaryDomain, isRequested)
|
roles.Add(grant.ProjectID, grantedRole, grant.ResourceOwner, grant.OrgPrimaryDomain, isRequested)
|
||||||
@@ -854,6 +854,26 @@ type projectsRoles struct {
|
|||||||
requestProjectID string
|
requestProjectID string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newProjectRoles(projectID string, grants []query.UserGrant, requestedRoles []string) *projectsRoles {
|
||||||
|
roles := new(projectsRoles)
|
||||||
|
// if specific roles where requested, check if they are granted and append them in the roles list
|
||||||
|
if len(requestedRoles) > 0 {
|
||||||
|
for _, requestedRole := range requestedRoles {
|
||||||
|
for _, grant := range grants {
|
||||||
|
checkGrantedRoles(roles, grant, requestedRole, grant.ProjectID == projectID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return roles
|
||||||
|
}
|
||||||
|
// no specific roles were requested, so convert any grants into roles
|
||||||
|
for _, grant := range grants {
|
||||||
|
for _, role := range grant.Roles {
|
||||||
|
roles.Add(grant.ProjectID, role, grant.ResourceOwner, grant.OrgPrimaryDomain, grant.ProjectID == projectID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return roles
|
||||||
|
}
|
||||||
|
|
||||||
func (p *projectsRoles) Add(projectID, roleKey, orgID, domain string, isRequested bool) {
|
func (p *projectsRoles) Add(projectID, roleKey, orgID, domain string, isRequested bool) {
|
||||||
if p.projects == nil {
|
if p.projects == nil {
|
||||||
p.projects = make(map[string]projectRoles, 1)
|
p.projects = make(map[string]projectRoles, 1)
|
||||||
|
@@ -87,7 +87,7 @@ func (s *Server) Introspect(ctx context.Context, r *op.Request[op.IntrospectionR
|
|||||||
if err = validateIntrospectionAudience(token.audience, client.clientID, client.projectID); err != nil {
|
if err = validateIntrospectionAudience(token.audience, client.clientID, client.projectID); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
userInfo, err := s.getUserInfoWithRoles(ctx, token.userID, client.projectID, token.scope, []string{client.projectID})
|
userInfo, err := s.userInfo(ctx, token.userID, client.projectID, token.scope, []string{client.projectID})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@@ -16,141 +16,42 @@ import (
|
|||||||
"github.com/zitadel/zitadel/internal/actions/object"
|
"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"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *Server) getUserInfoWithRoles(ctx context.Context, userID, projectID string, scope, roleAudience []string) (_ *oidc.UserInfo, err error) {
|
func (s *Server) userInfo(ctx context.Context, userID, projectID string, scope, roleAudience []string) (_ *oidc.UserInfo, err error) {
|
||||||
ctx, span := tracing.NewSpan(ctx)
|
roleAudience, requestedRoles := prepareRoles(ctx, projectID, scope, roleAudience)
|
||||||
defer func() { span.EndWithError(err) }()
|
qu, err := s.query.GetOIDCUserInfo(ctx, userID, roleAudience)
|
||||||
ctx, cancel := context.WithCancel(ctx)
|
if err != nil {
|
||||||
defer cancel()
|
return nil, err
|
||||||
|
|
||||||
userInfoChan := make(chan *userInfoResult)
|
|
||||||
go s.getUserInfo(ctx, userID, userInfoChan)
|
|
||||||
|
|
||||||
rolesChan := make(chan *assertRolesResult)
|
|
||||||
go s.assertRoles(ctx, userID, projectID, scope, roleAudience, rolesChan)
|
|
||||||
|
|
||||||
var (
|
|
||||||
userInfoResult *userInfoResult
|
|
||||||
assertRolesResult *assertRolesResult
|
|
||||||
)
|
|
||||||
|
|
||||||
// make sure both channels are always read,
|
|
||||||
// and cancel the context on first error
|
|
||||||
for i := 0; i < 2; i++ {
|
|
||||||
var resErr error
|
|
||||||
|
|
||||||
select {
|
|
||||||
case userInfoResult = <-userInfoChan:
|
|
||||||
resErr = userInfoResult.err
|
|
||||||
case assertRolesResult = <-rolesChan:
|
|
||||||
resErr = assertRolesResult.err
|
|
||||||
}
|
|
||||||
|
|
||||||
if resErr == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
cancel()
|
|
||||||
|
|
||||||
// we only care for the first error that occured,
|
|
||||||
// as the next error is most probably a context error.
|
|
||||||
if err == nil {
|
|
||||||
err = resErr
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
userInfo := userInfoToOIDC(userInfoResult.userInfo, scope, s.assetAPIPrefix(ctx))
|
userInfo := userInfoToOIDC(projectID, qu, scope, requestedRoles, s.assetAPIPrefix(ctx))
|
||||||
setUserInfoRoleClaims(userInfo, assertRolesResult.projectsRoles)
|
return userInfo, s.userinfoFlows(ctx, qu, userInfo)
|
||||||
|
|
||||||
return userInfo, s.userinfoFlows(ctx, userInfoResult.userInfo, assertRolesResult.userGrants, userInfo)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type userInfoResult struct {
|
func prepareRoles(ctx context.Context, projectID string, scope, roleAudience []string) (ra, requestedRoles []string) {
|
||||||
userInfo *query.OIDCUserInfo
|
// if all roles are requested take the audience for those from the scopes
|
||||||
err error
|
if slices.Contains(scope, ScopeProjectsRoles) {
|
||||||
}
|
roleAudience = domain.AddAudScopeToAudience(ctx, roleAudience, scope)
|
||||||
|
|
||||||
func (s *Server) getUserInfo(ctx context.Context, userID string, rc chan<- *userInfoResult) {
|
|
||||||
userInfo, err := s.query.GetOIDCUserInfo(ctx, userID)
|
|
||||||
rc <- &userInfoResult{
|
|
||||||
userInfo: userInfo,
|
|
||||||
err: err,
|
|
||||||
}
|
}
|
||||||
}
|
requestedRoles = make([]string, 0, len(scope))
|
||||||
|
for _, s := range scope {
|
||||||
type assertRolesResult struct {
|
if role, ok := strings.CutPrefix(s, ScopeProjectRolePrefix); ok {
|
||||||
userGrants *query.UserGrants
|
requestedRoles = append(requestedRoles, role)
|
||||||
projectsRoles *projectsRoles
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) assertRoles(ctx context.Context, userID, projectID string, scope, roleAudience []string, rc chan<- *assertRolesResult) {
|
|
||||||
userGrands, projectsRoles, err := func() (*query.UserGrants, *projectsRoles, error) {
|
|
||||||
// if all roles are requested take the audience for those from the scopes
|
|
||||||
if slices.Contains(scope, ScopeProjectsRoles) {
|
|
||||||
roleAudience = domain.AddAudScopeToAudience(ctx, roleAudience, scope)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
requestedRoles := make([]string, 0, len(scope))
|
|
||||||
for _, s := range scope {
|
|
||||||
if role, ok := strings.CutPrefix(s, ScopeProjectRolePrefix); ok {
|
|
||||||
requestedRoles = append(requestedRoles, role)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(requestedRoles) == 0 && len(roleAudience) == 0 {
|
|
||||||
return nil, nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ensure the projectID of the requesting is part of the roleAudience
|
|
||||||
if projectID != "" {
|
|
||||||
roleAudience = append(roleAudience, projectID)
|
|
||||||
}
|
|
||||||
queries := make([]query.SearchQuery, 0, 2)
|
|
||||||
projectQuery, err := query.NewUserGrantProjectIDsSearchQuery(roleAudience)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
queries = append(queries, projectQuery)
|
|
||||||
userIDQuery, err := query.NewUserGrantUserIDSearchQuery(userID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
queries = append(queries, userIDQuery)
|
|
||||||
grants, err := s.query.UserGrants(ctx, &query.UserGrantsQueries{
|
|
||||||
Queries: queries,
|
|
||||||
}, false, false) // triggers disabled
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
roles := new(projectsRoles)
|
|
||||||
// if specific roles where requested, check if they are granted and append them in the roles list
|
|
||||||
if len(requestedRoles) > 0 {
|
|
||||||
for _, requestedRole := range requestedRoles {
|
|
||||||
for _, grant := range grants.UserGrants {
|
|
||||||
checkGrantedRoles(roles, grant, requestedRole, grant.ProjectID == projectID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return grants, roles, nil
|
|
||||||
}
|
|
||||||
// no specific roles were requested, so convert any grants into roles
|
|
||||||
for _, grant := range grants.UserGrants {
|
|
||||||
for _, role := range grant.Roles {
|
|
||||||
roles.Add(grant.ProjectID, role, grant.ResourceOwner, grant.OrgPrimaryDomain, grant.ProjectID == projectID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return grants, roles, nil
|
|
||||||
}()
|
|
||||||
|
|
||||||
rc <- &assertRolesResult{
|
|
||||||
userGrants: userGrands,
|
|
||||||
projectsRoles: projectsRoles,
|
|
||||||
err: err,
|
|
||||||
}
|
}
|
||||||
|
if len(requestedRoles) == 0 && len(roleAudience) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensure the projectID of the requesting is part of the roleAudience
|
||||||
|
if !slices.Contains(roleAudience, projectID) {
|
||||||
|
roleAudience = append(roleAudience, projectID)
|
||||||
|
}
|
||||||
|
return roleAudience, requestedRoles
|
||||||
}
|
}
|
||||||
|
|
||||||
func userInfoToOIDC(user *query.OIDCUserInfo, scope []string, assetPrefix string) *oidc.UserInfo {
|
func userInfoToOIDC(projectID string, user *query.OIDCUserInfo, scope, requestedRoles []string, assetPrefix string) *oidc.UserInfo {
|
||||||
out := new(oidc.UserInfo)
|
out := new(oidc.UserInfo)
|
||||||
for _, s := range scope {
|
for _, s := range scope {
|
||||||
switch s {
|
switch s {
|
||||||
@@ -178,7 +79,7 @@ func userInfoToOIDC(user *query.OIDCUserInfo, scope []string, assetPrefix string
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
setUserInfoRoleClaims(out, newProjectRoles(projectID, user.UserGrants, requestedRoles))
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -259,8 +160,8 @@ func setUserInfoRoleClaims(userInfo *oidc.UserInfo, roles *projectsRoles) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) userinfoFlows(ctx context.Context, user *query.OIDCUserInfo, userGrants *query.UserGrants, userInfo *oidc.UserInfo) error {
|
func (s *Server) userinfoFlows(ctx context.Context, qu *query.OIDCUserInfo, userInfo *oidc.UserInfo) error {
|
||||||
queriedActions, err := s.query.GetActiveActionsByFlowAndTriggerType(ctx, domain.FlowTypeCustomiseToken, domain.TriggerTypePreUserinfoCreation, user.User.ResourceOwner, false)
|
queriedActions, err := s.query.GetActiveActionsByFlowAndTriggerType(ctx, domain.FlowTypeCustomiseToken, domain.TriggerTypePreUserinfoCreation, qu.User.ResourceOwner, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -270,17 +171,17 @@ func (s *Server) userinfoFlows(ctx context.Context, user *query.OIDCUserInfo, us
|
|||||||
actions.SetFields("claims", userinfoClaims(userInfo)),
|
actions.SetFields("claims", userinfoClaims(userInfo)),
|
||||||
actions.SetFields("getUser", func(c *actions.FieldConfig) interface{} {
|
actions.SetFields("getUser", func(c *actions.FieldConfig) interface{} {
|
||||||
return func(call goja.FunctionCall) goja.Value {
|
return func(call goja.FunctionCall) goja.Value {
|
||||||
return object.UserFromQuery(c, user.User)
|
return object.UserFromQuery(c, qu.User)
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
actions.SetFields("user",
|
actions.SetFields("user",
|
||||||
actions.SetFields("getMetadata", func(c *actions.FieldConfig) interface{} {
|
actions.SetFields("getMetadata", func(c *actions.FieldConfig) interface{} {
|
||||||
return func(goja.FunctionCall) goja.Value {
|
return func(goja.FunctionCall) goja.Value {
|
||||||
return object.UserMetadataListFromSlice(c, user.Metadata)
|
return object.UserMetadataListFromSlice(c, qu.Metadata)
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
actions.SetFields("grants", func(c *actions.FieldConfig) interface{} {
|
actions.SetFields("grants", func(c *actions.FieldConfig) interface{} {
|
||||||
return object.UserGrantsFromQuery(c, userGrants)
|
return object.UserGrantsFromSlice(c, qu.UserGrants)
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -334,7 +235,7 @@ func (s *Server) userinfoFlows(ctx context.Context, user *query.OIDCUserInfo, us
|
|||||||
Key: key,
|
Key: key,
|
||||||
Value: value,
|
Value: value,
|
||||||
}
|
}
|
||||||
if _, err = s.command.SetUserMetadata(ctx, metadata, userInfo.Subject, user.User.ResourceOwner); err != nil {
|
if _, err = s.command.SetUserMetadata(ctx, metadata, userInfo.Subject, qu.User.ResourceOwner); err != nil {
|
||||||
logging.WithError(err).Info("unable to set md in action")
|
logging.WithError(err).Info("unable to set md in action")
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
@@ -1,3 +1,6 @@
|
|||||||
|
-- deallocate q;
|
||||||
|
-- prepare q (text, text, text[]) as
|
||||||
|
|
||||||
with usr as (
|
with usr as (
|
||||||
select id, creation_date, change_date, sequence, state, resource_owner, username
|
select id, creation_date, change_date, sequence, state, resource_owner, username
|
||||||
from projections.users9 u
|
from projections.users9 u
|
||||||
@@ -20,6 +23,7 @@ machine as (
|
|||||||
and instance_id = $2
|
and instance_id = $2
|
||||||
) r
|
) r
|
||||||
),
|
),
|
||||||
|
-- find the user's metadata
|
||||||
metadata as (
|
metadata as (
|
||||||
select json_agg(row_to_json(r)) as metadata from (
|
select json_agg(row_to_json(r)) as metadata from (
|
||||||
select creation_date, change_date, sequence, resource_owner, key, encode(value, 'base64') as value
|
select creation_date, change_date, sequence, resource_owner, key, encode(value, 'base64') as value
|
||||||
@@ -28,14 +32,46 @@ metadata as (
|
|||||||
and instance_id = $2
|
and instance_id = $2
|
||||||
) r
|
) r
|
||||||
),
|
),
|
||||||
org as (
|
-- get all user grants, needed for the orgs query
|
||||||
|
user_grants as (
|
||||||
|
select id, grant_id, state, creation_date, change_date, sequence, user_id, roles, resource_owner, project_id
|
||||||
|
from projections.user_grants3
|
||||||
|
where user_id = $1
|
||||||
|
and instance_id = $2
|
||||||
|
and project_id = any($3)
|
||||||
|
),
|
||||||
|
-- filter all orgs we are interested in.
|
||||||
|
orgs as (
|
||||||
|
select id, name, primary_domain
|
||||||
|
from projections.orgs1
|
||||||
|
where id in (
|
||||||
|
select resource_owner from user_grants
|
||||||
|
union
|
||||||
|
select resource_owner from usr
|
||||||
|
)
|
||||||
|
and instance_id = $2
|
||||||
|
),
|
||||||
|
-- find the user's org
|
||||||
|
user_org as (
|
||||||
select row_to_json(r) as organization from (
|
select row_to_json(r) as organization from (
|
||||||
select name, primary_domain
|
select name, primary_domain
|
||||||
from projections.orgs1 o
|
from orgs o
|
||||||
join usr u on o.id = u.resource_owner
|
join usr u on o.id = u.resource_owner
|
||||||
where instance_id = $2
|
) r
|
||||||
|
),
|
||||||
|
-- join user grants to orgs, projects and user
|
||||||
|
grants as (
|
||||||
|
select json_agg(row_to_json(r)) as grants from (
|
||||||
|
select g.*,
|
||||||
|
o.name as org_name, o.primary_domain as org_primary_domain,
|
||||||
|
p.name as project_name, u.resource_owner as user_resource_owner
|
||||||
|
from user_grants g
|
||||||
|
left join orgs o on o.id = g.resource_owner
|
||||||
|
left join projections.projects3 p on p.id = g.project_id
|
||||||
|
left join usr u on u.id = g.user_id
|
||||||
) r
|
) r
|
||||||
)
|
)
|
||||||
|
-- build the final result JSON
|
||||||
select json_build_object(
|
select json_build_object(
|
||||||
'user', (
|
'user', (
|
||||||
select row_to_json(r) as usr from (
|
select row_to_json(r) as usr from (
|
||||||
@@ -45,6 +81,9 @@ select json_build_object(
|
|||||||
left join machine m on u.id = m.user_id
|
left join machine m on u.id = m.user_id
|
||||||
) r
|
) r
|
||||||
),
|
),
|
||||||
'org', (select organization from org),
|
'org', (select organization from user_org),
|
||||||
'metadata', (select metadata from metadata)
|
'metadata', (select metadata from metadata),
|
||||||
|
'user_grants', (select grants from grants)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
-- execute q('231965491734773762','230690539048009730', '{"236645808328409090","240762134579904514"}')
|
3
internal/query/testdata/userinfo_human.json
vendored
3
internal/query/testdata/userinfo_human.json
vendored
@@ -41,5 +41,6 @@
|
|||||||
"key": "foo",
|
"key": "foo",
|
||||||
"value": "YmFy"
|
"value": "YmFy"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"user_grants": null
|
||||||
}
|
}
|
||||||
|
86
internal/query/testdata/userinfo_human_grants.json
vendored
Normal file
86
internal/query/testdata/userinfo_human_grants.json
vendored
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
{
|
||||||
|
"user": {
|
||||||
|
"id": "231965491734773762",
|
||||||
|
"creation_date": "2023-09-15T06:10:07.434142+00:00",
|
||||||
|
"change_date": "2023-11-14T13:27:02.072318+00:00",
|
||||||
|
"sequence": 1148,
|
||||||
|
"state": 1,
|
||||||
|
"resource_owner": "231848297847848962",
|
||||||
|
"username": "tim+tesmail@zitadel.com",
|
||||||
|
"human": {
|
||||||
|
"first_name": "Tim",
|
||||||
|
"last_name": "Mohlmann",
|
||||||
|
"nick_name": "muhlemmer",
|
||||||
|
"display_name": "Tim Mohlmann",
|
||||||
|
"avatar_key": null,
|
||||||
|
"email": "tim+tesmail@zitadel.com",
|
||||||
|
"is_email_verified": true,
|
||||||
|
"phone": "+40123456789",
|
||||||
|
"is_phone_verified": false
|
||||||
|
},
|
||||||
|
"machine": null
|
||||||
|
},
|
||||||
|
"org": {
|
||||||
|
"name": "demo",
|
||||||
|
"primary_domain": "demo.localhost"
|
||||||
|
},
|
||||||
|
"metadata": [
|
||||||
|
{
|
||||||
|
"creation_date": "2023-11-14T13:26:03.553702+00:00",
|
||||||
|
"change_date": "2023-11-14T13:26:03.553702+00:00",
|
||||||
|
"sequence": 1147,
|
||||||
|
"resource_owner": "231848297847848962",
|
||||||
|
"key": "bar",
|
||||||
|
"value": "Zm9v"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"creation_date": "2023-11-14T13:25:57.171368+00:00",
|
||||||
|
"change_date": "2023-11-14T13:25:57.171368+00:00",
|
||||||
|
"sequence": 1146,
|
||||||
|
"resource_owner": "231848297847848962",
|
||||||
|
"key": "foo",
|
||||||
|
"value": "YmFy"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"user_grants": [
|
||||||
|
{
|
||||||
|
"id": "240749256523120642",
|
||||||
|
"grant_id": "",
|
||||||
|
"state": 1,
|
||||||
|
"creation_date": "2023-11-14T20:28:59.168208+00:00",
|
||||||
|
"change_date": "2023-11-14T20:50:58.822391+00:00",
|
||||||
|
"sequence": 2,
|
||||||
|
"user_id": "231965491734773762",
|
||||||
|
"roles": [
|
||||||
|
"role1",
|
||||||
|
"role2"
|
||||||
|
],
|
||||||
|
"resource_owner": "231848297847848962",
|
||||||
|
"project_id": "236645808328409090",
|
||||||
|
"org_name": "demo",
|
||||||
|
"org_primary_domain": "demo.localhost",
|
||||||
|
"project_name": "tests",
|
||||||
|
"user_resource_owner": "231848297847848962"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "240762315572510722",
|
||||||
|
"grant_id": "",
|
||||||
|
"state": 1,
|
||||||
|
"creation_date": "2023-11-14T22:38:42.967317+00:00",
|
||||||
|
"change_date": "2023-11-14T22:38:42.967317+00:00",
|
||||||
|
"sequence": 1,
|
||||||
|
"user_id": "231965491734773762",
|
||||||
|
"roles": [
|
||||||
|
"role3",
|
||||||
|
"role4"
|
||||||
|
],
|
||||||
|
"resource_owner": "231848297847848962",
|
||||||
|
"project_id": "240762134579904514",
|
||||||
|
"org_name": "demo",
|
||||||
|
"org_primary_domain": "demo.localhost",
|
||||||
|
"project_name": "tests2",
|
||||||
|
"user_resource_owner": "231848297847848962"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@@ -24,5 +24,6 @@
|
|||||||
"name": "demo",
|
"name": "demo",
|
||||||
"primary_domain": "demo.localhost"
|
"primary_domain": "demo.localhost"
|
||||||
},
|
},
|
||||||
"metadata": null
|
"metadata": null,
|
||||||
|
"user_grants": null
|
||||||
}
|
}
|
||||||
|
@@ -34,5 +34,6 @@
|
|||||||
"key": "second",
|
"key": "second",
|
||||||
"value": "QnllIFdvcmxkIQ=="
|
"value": "QnllIFdvcmxkIQ=="
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"user_grants": null
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"user": null,
|
"user": null,
|
||||||
"org": null,
|
"org": null,
|
||||||
"metadata": null
|
"metadata": null,
|
||||||
|
"user_grants": null
|
||||||
}
|
}
|
||||||
|
@@ -22,32 +22,32 @@ import (
|
|||||||
|
|
||||||
type UserGrant struct {
|
type UserGrant struct {
|
||||||
// ID represents the aggregate id (id of the user grant)
|
// ID represents the aggregate id (id of the user grant)
|
||||||
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"`
|
||||||
Sequence uint64
|
Sequence uint64 `json:"sequence,omitempty"`
|
||||||
Roles database.TextArray[string]
|
Roles database.TextArray[string] `json:"roles,omitempty"`
|
||||||
// GrantID represents the project grant id
|
// GrantID represents the project grant id
|
||||||
GrantID string
|
GrantID string `json:"grant_id,omitempty"`
|
||||||
State domain.UserGrantState
|
State domain.UserGrantState `json:"state,omitempty"`
|
||||||
|
|
||||||
UserID string
|
UserID string `json:"user_id,omitempty"`
|
||||||
Username string
|
Username string `json:"username,omitempty"`
|
||||||
UserType domain.UserType
|
UserType domain.UserType `json:"user_type,omitempty"`
|
||||||
UserResourceOwner string
|
UserResourceOwner string `json:"user_resource_owner,omitempty"`
|
||||||
FirstName string
|
FirstName string `json:"first_name,omitempty"`
|
||||||
LastName string
|
LastName string `json:"last_name,omitempty"`
|
||||||
Email string
|
Email string `json:"email,omitempty"`
|
||||||
DisplayName string
|
DisplayName string `json:"display_name,omitempty"`
|
||||||
AvatarURL string
|
AvatarURL string `json:"avatar_url,omitempty"`
|
||||||
PreferredLoginName string
|
PreferredLoginName string `json:"preferred_login_name,omitempty"`
|
||||||
|
|
||||||
ResourceOwner string
|
ResourceOwner string `json:"resource_owner,omitempty"`
|
||||||
OrgName string
|
OrgName string `json:"org_name,omitempty"`
|
||||||
OrgPrimaryDomain string
|
OrgPrimaryDomain string `json:"org_primary_domain,omitempty"`
|
||||||
|
|
||||||
ProjectID string
|
ProjectID string `json:"project_id,omitempty"`
|
||||||
ProjectName string
|
ProjectName string `json:"project_name,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UserGrants struct {
|
type UserGrants struct {
|
||||||
|
@@ -7,6 +7,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
"github.com/zitadel/zitadel/internal/api/authz"
|
"github.com/zitadel/zitadel/internal/api/authz"
|
||||||
|
"github.com/zitadel/zitadel/internal/database"
|
||||||
"github.com/zitadel/zitadel/internal/errors"
|
"github.com/zitadel/zitadel/internal/errors"
|
||||||
"github.com/zitadel/zitadel/internal/telemetry/tracing"
|
"github.com/zitadel/zitadel/internal/telemetry/tracing"
|
||||||
)
|
)
|
||||||
@@ -14,14 +15,17 @@ import (
|
|||||||
//go:embed embed/userinfo_by_id.sql
|
//go:embed embed/userinfo_by_id.sql
|
||||||
var oidcUserInfoQuery string
|
var oidcUserInfoQuery string
|
||||||
|
|
||||||
func (q *Queries) GetOIDCUserInfo(ctx context.Context, userID string) (_ *OIDCUserInfo, err error) {
|
func (q *Queries) GetOIDCUserInfo(ctx context.Context, userID string, roleAudience []string) (_ *OIDCUserInfo, err error) {
|
||||||
ctx, span := tracing.NewSpan(ctx)
|
ctx, span := tracing.NewSpan(ctx)
|
||||||
defer func() { span.EndWithError(err) }()
|
defer func() { span.EndWithError(err) }()
|
||||||
|
|
||||||
var data []byte
|
var data []byte
|
||||||
err = q.client.QueryRowContext(ctx, func(row *sql.Row) error {
|
err = q.client.QueryRowContext(ctx, func(row *sql.Row) error {
|
||||||
return row.Scan(&data)
|
return row.Scan(&data)
|
||||||
}, oidcUserInfoQuery, userID, authz.GetInstance(ctx).InstanceID())
|
},
|
||||||
|
oidcUserInfoQuery,
|
||||||
|
userID, authz.GetInstance(ctx).InstanceID(), database.TextArray[string](roleAudience),
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.ThrowInternal(err, "QUERY-Oath6", "Errors.Internal")
|
return nil, errors.ThrowInternal(err, "QUERY-Oath6", "Errors.Internal")
|
||||||
}
|
}
|
||||||
@@ -38,9 +42,10 @@ func (q *Queries) GetOIDCUserInfo(ctx context.Context, userID string) (_ *OIDCUs
|
|||||||
}
|
}
|
||||||
|
|
||||||
type OIDCUserInfo struct {
|
type OIDCUserInfo struct {
|
||||||
User *User `json:"user,omitempty"`
|
User *User `json:"user,omitempty"`
|
||||||
Metadata []UserMetadata `json:"metadata,omitempty"`
|
Metadata []UserMetadata `json:"metadata,omitempty"`
|
||||||
Org *userInfoOrg `json:"org,omitempty"`
|
Org *userInfoOrg `json:"org,omitempty"`
|
||||||
|
UserGrants []UserGrant `json:"user_grants,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type userInfoOrg struct {
|
type userInfoOrg struct {
|
||||||
|
@@ -22,6 +22,8 @@ var (
|
|||||||
testdataUserInfoHumanNoMD string
|
testdataUserInfoHumanNoMD string
|
||||||
//go:embed testdata/userinfo_human.json
|
//go:embed testdata/userinfo_human.json
|
||||||
testdataUserInfoHuman string
|
testdataUserInfoHuman string
|
||||||
|
//go:embed testdata/userinfo_human_grants.json
|
||||||
|
testdataUserInfoHumanGrants string
|
||||||
//go:embed testdata/userinfo_machine.json
|
//go:embed testdata/userinfo_machine.json
|
||||||
testdataUserInfoMachine string
|
testdataUserInfoMachine string
|
||||||
)
|
)
|
||||||
@@ -29,7 +31,8 @@ var (
|
|||||||
func TestQueries_GetOIDCUserInfo(t *testing.T) {
|
func TestQueries_GetOIDCUserInfo(t *testing.T) {
|
||||||
expQuery := regexp.QuoteMeta(oidcUserInfoQuery)
|
expQuery := regexp.QuoteMeta(oidcUserInfoQuery)
|
||||||
type args struct {
|
type args struct {
|
||||||
userID string
|
userID string
|
||||||
|
roleAudience []string
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@@ -43,7 +46,7 @@ func TestQueries_GetOIDCUserInfo(t *testing.T) {
|
|||||||
args: args{
|
args: args{
|
||||||
userID: "231965491734773762",
|
userID: "231965491734773762",
|
||||||
},
|
},
|
||||||
mock: mockQueryErr(expQuery, sql.ErrConnDone, "231965491734773762", "instanceID"),
|
mock: mockQueryErr(expQuery, sql.ErrConnDone, "231965491734773762", "instanceID", nil),
|
||||||
wantErr: sql.ErrConnDone,
|
wantErr: sql.ErrConnDone,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -51,7 +54,7 @@ func TestQueries_GetOIDCUserInfo(t *testing.T) {
|
|||||||
args: args{
|
args: args{
|
||||||
userID: "231965491734773762",
|
userID: "231965491734773762",
|
||||||
},
|
},
|
||||||
mock: mockQuery(expQuery, []string{"json_build_object"}, []driver.Value{`~~~`}, "231965491734773762", "instanceID"),
|
mock: mockQuery(expQuery, []string{"json_build_object"}, []driver.Value{`~~~`}, "231965491734773762", "instanceID", nil),
|
||||||
wantErr: errors.ThrowInternal(nil, "QUERY-Vohs6", "Errors.Internal"),
|
wantErr: errors.ThrowInternal(nil, "QUERY-Vohs6", "Errors.Internal"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -59,7 +62,7 @@ func TestQueries_GetOIDCUserInfo(t *testing.T) {
|
|||||||
args: args{
|
args: args{
|
||||||
userID: "231965491734773762",
|
userID: "231965491734773762",
|
||||||
},
|
},
|
||||||
mock: mockQuery(expQuery, []string{"json_build_object"}, []driver.Value{testdataUserInfoNotFound}, "231965491734773762", "instanceID"),
|
mock: mockQuery(expQuery, []string{"json_build_object"}, []driver.Value{testdataUserInfoNotFound}, "231965491734773762", "instanceID", nil),
|
||||||
wantErr: errors.ThrowNotFound(nil, "QUERY-ahs4S", "Errors.User.NotFound"),
|
wantErr: errors.ThrowNotFound(nil, "QUERY-ahs4S", "Errors.User.NotFound"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -67,7 +70,7 @@ func TestQueries_GetOIDCUserInfo(t *testing.T) {
|
|||||||
args: args{
|
args: args{
|
||||||
userID: "231965491734773762",
|
userID: "231965491734773762",
|
||||||
},
|
},
|
||||||
mock: mockQuery(expQuery, []string{"json_build_object"}, []driver.Value{testdataUserInfoHumanNoMD}, "231965491734773762", "instanceID"),
|
mock: mockQuery(expQuery, []string{"json_build_object"}, []driver.Value{testdataUserInfoHumanNoMD}, "231965491734773762", "instanceID", nil),
|
||||||
want: &OIDCUserInfo{
|
want: &OIDCUserInfo{
|
||||||
User: &User{
|
User: &User{
|
||||||
ID: "231965491734773762",
|
ID: "231965491734773762",
|
||||||
@@ -102,7 +105,7 @@ func TestQueries_GetOIDCUserInfo(t *testing.T) {
|
|||||||
args: args{
|
args: args{
|
||||||
userID: "231965491734773762",
|
userID: "231965491734773762",
|
||||||
},
|
},
|
||||||
mock: mockQuery(expQuery, []string{"json_build_object"}, []driver.Value{testdataUserInfoHuman}, "231965491734773762", "instanceID"),
|
mock: mockQuery(expQuery, []string{"json_build_object"}, []driver.Value{testdataUserInfoHuman}, "231965491734773762", "instanceID", nil),
|
||||||
want: &OIDCUserInfo{
|
want: &OIDCUserInfo{
|
||||||
User: &User{
|
User: &User{
|
||||||
ID: "231965491734773762",
|
ID: "231965491734773762",
|
||||||
@@ -149,12 +152,109 @@ func TestQueries_GetOIDCUserInfo(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "human with metadata and grants",
|
||||||
|
args: args{
|
||||||
|
userID: "231965491734773762",
|
||||||
|
roleAudience: []string{"236645808328409090", "240762134579904514"},
|
||||||
|
},
|
||||||
|
mock: mockQuery(expQuery,
|
||||||
|
[]string{"json_build_object"},
|
||||||
|
[]driver.Value{testdataUserInfoHumanGrants},
|
||||||
|
"231965491734773762", "instanceID", database.TextArray[string]{"236645808328409090", "240762134579904514"},
|
||||||
|
),
|
||||||
|
want: &OIDCUserInfo{
|
||||||
|
User: &User{
|
||||||
|
ID: "231965491734773762",
|
||||||
|
CreationDate: time.Date(2023, time.September, 15, 6, 10, 7, 434142000, time.FixedZone("", 0)),
|
||||||
|
ChangeDate: time.Date(2023, time.November, 14, 13, 27, 2, 72318000, time.FixedZone("", 0)),
|
||||||
|
Sequence: 1148,
|
||||||
|
State: 1,
|
||||||
|
ResourceOwner: "231848297847848962",
|
||||||
|
Username: "tim+tesmail@zitadel.com",
|
||||||
|
Human: &Human{
|
||||||
|
FirstName: "Tim",
|
||||||
|
LastName: "Mohlmann",
|
||||||
|
NickName: "muhlemmer",
|
||||||
|
DisplayName: "Tim Mohlmann",
|
||||||
|
AvatarKey: "",
|
||||||
|
Email: "tim+tesmail@zitadel.com",
|
||||||
|
IsEmailVerified: true,
|
||||||
|
Phone: "+40123456789",
|
||||||
|
IsPhoneVerified: false,
|
||||||
|
},
|
||||||
|
Machine: nil,
|
||||||
|
},
|
||||||
|
Org: &userInfoOrg{
|
||||||
|
Name: "demo",
|
||||||
|
PrimaryDomain: "demo.localhost",
|
||||||
|
},
|
||||||
|
Metadata: []UserMetadata{
|
||||||
|
{
|
||||||
|
CreationDate: time.Date(2023, time.November, 14, 13, 26, 3, 553702000, time.FixedZone("", 0)),
|
||||||
|
ChangeDate: time.Date(2023, time.November, 14, 13, 26, 3, 553702000, time.FixedZone("", 0)),
|
||||||
|
Sequence: 1147,
|
||||||
|
ResourceOwner: "231848297847848962",
|
||||||
|
Key: "bar",
|
||||||
|
Value: []byte("foo"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CreationDate: time.Date(2023, time.November, 14, 13, 25, 57, 171368000, time.FixedZone("", 0)),
|
||||||
|
ChangeDate: time.Date(2023, time.November, 14, 13, 25, 57, 171368000, time.FixedZone("", 0)),
|
||||||
|
Sequence: 1146,
|
||||||
|
ResourceOwner: "231848297847848962",
|
||||||
|
Key: "foo",
|
||||||
|
Value: []byte("bar"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
UserGrants: []UserGrant{
|
||||||
|
{
|
||||||
|
ID: "240749256523120642",
|
||||||
|
GrantID: "",
|
||||||
|
State: 1,
|
||||||
|
CreationDate: time.Date(2023, time.November, 14, 20, 28, 59, 168208000, time.FixedZone("", 0)),
|
||||||
|
ChangeDate: time.Date(2023, time.November, 14, 20, 50, 58, 822391000, time.FixedZone("", 0)),
|
||||||
|
Sequence: 2,
|
||||||
|
UserID: "231965491734773762",
|
||||||
|
Roles: []string{
|
||||||
|
"role1",
|
||||||
|
"role2",
|
||||||
|
},
|
||||||
|
ResourceOwner: "231848297847848962",
|
||||||
|
ProjectID: "236645808328409090",
|
||||||
|
OrgName: "demo",
|
||||||
|
OrgPrimaryDomain: "demo.localhost",
|
||||||
|
ProjectName: "tests",
|
||||||
|
UserResourceOwner: "231848297847848962",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "240762315572510722",
|
||||||
|
GrantID: "",
|
||||||
|
State: 1,
|
||||||
|
CreationDate: time.Date(2023, time.November, 14, 22, 38, 42, 967317000, time.FixedZone("", 0)),
|
||||||
|
ChangeDate: time.Date(2023, time.November, 14, 22, 38, 42, 967317000, time.FixedZone("", 0)),
|
||||||
|
Sequence: 1,
|
||||||
|
UserID: "231965491734773762",
|
||||||
|
Roles: []string{
|
||||||
|
"role3",
|
||||||
|
"role4",
|
||||||
|
},
|
||||||
|
ResourceOwner: "231848297847848962",
|
||||||
|
ProjectID: "240762134579904514",
|
||||||
|
OrgName: "demo",
|
||||||
|
OrgPrimaryDomain: "demo.localhost",
|
||||||
|
ProjectName: "tests2",
|
||||||
|
UserResourceOwner: "231848297847848962",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "machine with metadata",
|
name: "machine with metadata",
|
||||||
args: args{
|
args: args{
|
||||||
userID: "240707570677841922",
|
userID: "240707570677841922",
|
||||||
},
|
},
|
||||||
mock: mockQuery(expQuery, []string{"json_build_object"}, []driver.Value{testdataUserInfoMachine}, "240707570677841922", "instanceID"),
|
mock: mockQuery(expQuery, []string{"json_build_object"}, []driver.Value{testdataUserInfoMachine}, "240707570677841922", "instanceID", nil),
|
||||||
want: &OIDCUserInfo{
|
want: &OIDCUserInfo{
|
||||||
User: &User{
|
User: &User{
|
||||||
ID: "240707570677841922",
|
ID: "240707570677841922",
|
||||||
@@ -206,7 +306,7 @@ func TestQueries_GetOIDCUserInfo(t *testing.T) {
|
|||||||
}
|
}
|
||||||
ctx := authz.NewMockContext("instanceID", "orgID", "loginClient")
|
ctx := authz.NewMockContext("instanceID", "orgID", "loginClient")
|
||||||
|
|
||||||
got, err := q.GetOIDCUserInfo(ctx, tt.args.userID)
|
got, err := q.GetOIDCUserInfo(ctx, tt.args.userID, tt.args.roleAudience)
|
||||||
require.ErrorIs(t, err, tt.wantErr)
|
require.ErrorIs(t, err, tt.wantErr)
|
||||||
assert.Equal(t, tt.want, got)
|
assert.Equal(t, tt.want, got)
|
||||||
})
|
})
|
||||||
|
Reference in New Issue
Block a user