From 416a35537f89b1c3ccd3d123289cea37b3309bba Mon Sep 17 00:00:00 2001 From: Stefan Benz <46600784+stebenz@users.noreply.github.com> Date: Tue, 29 Jul 2025 00:08:12 +0200 Subject: [PATCH] feat: actions context information add clientID (#10339) # Which Problems Are Solved There is no information contained in the context info sent to Actions v2. # How the Problems Are Solved Add application information to the context information sent to Actions v2, to give more information about the execution. # Additional Changes None # Additional Context Closes #9377 --- .../integration_test/execution_target_test.go | 29 ++++++++-------- internal/api/oidc/introspect.go | 1 + internal/api/oidc/token.go | 6 ++-- internal/api/oidc/token_exchange.go | 2 +- internal/api/oidc/userinfo.go | 33 ++++++++++++++----- 5 files changed, 45 insertions(+), 26 deletions(-) diff --git a/internal/api/grpc/action/v2beta/integration_test/execution_target_test.go b/internal/api/grpc/action/v2beta/integration_test/execution_target_test.go index 3353c4f0dd..a2e6131e11 100644 --- a/internal/api/grpc/action/v2beta/integration_test/execution_target_test.go +++ b/internal/api/grpc/action/v2beta/integration_test/execution_target_test.go @@ -605,7 +605,7 @@ func TestServer_ExecutionTargetPreUserinfo(t *testing.T) { {Key: "added", Value: "value"}, }, } - return expectPreUserinfoExecution(ctx, t, instance, req, response) + return expectPreUserinfoExecution(ctx, t, instance, client.GetClientId(), req, response) }, req: &oidc_pb.CreateCallbackRequest{ AuthRequestId: func() string { @@ -630,7 +630,7 @@ func TestServer_ExecutionTargetPreUserinfo(t *testing.T) { "addedLog", }, } - return expectPreUserinfoExecution(ctx, t, instance, req, response) + return expectPreUserinfoExecution(ctx, t, instance, client.GetClientId(), req, response) }, req: &oidc_pb.CreateCallbackRequest{ AuthRequestId: func() string { @@ -655,7 +655,7 @@ func TestServer_ExecutionTargetPreUserinfo(t *testing.T) { {Key: "key", Value: []byte("value")}, }, } - return expectPreUserinfoExecution(ctx, t, instance, req, response) + return expectPreUserinfoExecution(ctx, t, instance, client.GetClientId(), req, response) }, req: &oidc_pb.CreateCallbackRequest{ AuthRequestId: func() string { @@ -692,7 +692,7 @@ func TestServer_ExecutionTargetPreUserinfo(t *testing.T) { {Key: "added3", Value: "value3"}, }, } - return expectPreUserinfoExecution(ctx, t, instance, req, response) + return expectPreUserinfoExecution(ctx, t, instance, client.GetClientId(), req, response) }, req: &oidc_pb.CreateCallbackRequest{ AuthRequestId: func() string { @@ -755,7 +755,7 @@ func TestServer_ExecutionTargetPreUserinfo(t *testing.T) { } } -func expectPreUserinfoExecution(ctx context.Context, t *testing.T, instance *integration.Instance, req *oidc_pb.CreateCallbackRequest, response *oidc_api.ContextInfoResponse) (string, func()) { +func expectPreUserinfoExecution(ctx context.Context, t *testing.T, instance *integration.Instance, clientID string, req *oidc_pb.CreateCallbackRequest, response *oidc_api.ContextInfoResponse) (string, func()) { userEmail := gofakeit.Email() userPhone := "+41" + gofakeit.Phone() userResp := instance.CreateHumanUserVerified(ctx, instance.DefaultOrg.Id, userEmail, userPhone) @@ -767,7 +767,7 @@ func expectPreUserinfoExecution(ctx context.Context, t *testing.T, instance *int SessionToken: sessionResp.GetSessionToken(), }, } - expectedContextInfo := contextInfoForUserOIDC(instance, "function/preuserinfo", userResp, userEmail, userPhone) + expectedContextInfo := contextInfoForUserOIDC(instance, "function/preuserinfo", clientID, userResp, userEmail, userPhone) targetURL, closeF, _, _ := integration.TestServerCall(expectedContextInfo, 0, http.StatusOK, response) @@ -845,7 +845,7 @@ func getAccessTokenClaims(ctx context.Context, t *testing.T, instance *integrati return claims } -func contextInfoForUserOIDC(instance *integration.Instance, function string, userResp *user.AddHumanUserResponse, email, phone string) *oidc_api.ContextInfo { +func contextInfoForUserOIDC(instance *integration.Instance, function string, clientID string, userResp *user.AddHumanUserResponse, email, phone string) *oidc_api.ContextInfo { return &oidc_api.ContextInfo{ Function: function, UserInfo: &oidc.UserInfo{ @@ -878,6 +878,9 @@ func contextInfoForUserOIDC(instance *integration.Instance, function string, use }, }, UserMetadata: nil, + Application: &oidc_api.ContextInfoApplication{ + ClientID: clientID, + }, Org: &query.UserInfoOrg{ ID: instance.DefaultOrg.GetId(), Name: instance.DefaultOrg.GetName(), @@ -918,7 +921,7 @@ func TestServer_ExecutionTargetPreAccessToken(t *testing.T) { {Key: "added1", Value: "value"}, }, } - return expectPreAccessTokenExecution(ctx, t, instance, req, response) + return expectPreAccessTokenExecution(ctx, t, instance, client.GetClientId(), req, response) }, req: &oidc_pb.CreateCallbackRequest{ AuthRequestId: func() string { @@ -943,7 +946,7 @@ func TestServer_ExecutionTargetPreAccessToken(t *testing.T) { "addedLog", }, } - return expectPreAccessTokenExecution(ctx, t, instance, req, response) + return expectPreAccessTokenExecution(ctx, t, instance, client.GetClientId(), req, response) }, req: &oidc_pb.CreateCallbackRequest{ AuthRequestId: func() string { @@ -968,7 +971,7 @@ func TestServer_ExecutionTargetPreAccessToken(t *testing.T) { {Key: "key", Value: []byte("value")}, }, } - return expectPreAccessTokenExecution(ctx, t, instance, req, response) + return expectPreAccessTokenExecution(ctx, t, instance, client.GetClientId(), req, response) }, req: &oidc_pb.CreateCallbackRequest{ AuthRequestId: func() string { @@ -1005,7 +1008,7 @@ func TestServer_ExecutionTargetPreAccessToken(t *testing.T) { {Key: "added3", Value: "value3"}, }, } - return expectPreAccessTokenExecution(ctx, t, instance, req, response) + return expectPreAccessTokenExecution(ctx, t, instance, client.GetClientId(), req, response) }, req: &oidc_pb.CreateCallbackRequest{ AuthRequestId: func() string { @@ -1060,7 +1063,7 @@ func TestServer_ExecutionTargetPreAccessToken(t *testing.T) { } } -func expectPreAccessTokenExecution(ctx context.Context, t *testing.T, instance *integration.Instance, req *oidc_pb.CreateCallbackRequest, response *oidc_api.ContextInfoResponse) (string, func()) { +func expectPreAccessTokenExecution(ctx context.Context, t *testing.T, instance *integration.Instance, clientID string, req *oidc_pb.CreateCallbackRequest, response *oidc_api.ContextInfoResponse) (string, func()) { userEmail := gofakeit.Email() userPhone := "+41" + gofakeit.Phone() userResp := instance.CreateHumanUserVerified(ctx, instance.DefaultOrg.Id, userEmail, userPhone) @@ -1072,7 +1075,7 @@ func expectPreAccessTokenExecution(ctx context.Context, t *testing.T, instance * SessionToken: sessionResp.GetSessionToken(), }, } - expectedContextInfo := contextInfoForUserOIDC(instance, "function/preaccesstoken", userResp, userEmail, userPhone) + expectedContextInfo := contextInfoForUserOIDC(instance, "function/preaccesstoken", clientID, userResp, userEmail, userPhone) targetURL, closeF, _, _ := integration.TestServerCall(expectedContextInfo, 0, http.StatusOK, response) diff --git a/internal/api/oidc/introspect.go b/internal/api/oidc/introspect.go index e5479a4683..6ce5d72e24 100644 --- a/internal/api/oidc/introspect.go +++ b/internal/api/oidc/introspect.go @@ -100,6 +100,7 @@ func (s *Server) Introspect(ctx context.Context, r *op.Request[op.IntrospectionR token.userID, token.scope, client.projectID, + client.clientID, client.projectRoleAssertion, true, true, diff --git a/internal/api/oidc/token.go b/internal/api/oidc/token.go index 2efc0fb583..d7a258259a 100644 --- a/internal/api/oidc/token.go +++ b/internal/api/oidc/token.go @@ -31,7 +31,7 @@ for example the v2 code exchange and refresh token. */ func (s *Server) accessTokenResponseFromSession(ctx context.Context, client op.Client, session *command.OIDCSession, state, projectID string, projectRoleAssertion, accessTokenRoleAssertion, idTokenRoleAssertion, userInfoAssertion bool) (_ *oidc.AccessTokenResponse, err error) { - getUserInfo := s.getUserInfo(session.UserID, projectID, projectRoleAssertion, userInfoAssertion, session.Scope) + getUserInfo := s.getUserInfo(session.UserID, projectID, client.GetID(), projectRoleAssertion, userInfoAssertion, session.Scope) getSigner := s.getSignerOnce() resp := &oidc.AccessTokenResponse{ @@ -113,8 +113,8 @@ type userInfoFunc func(ctx context.Context, roleAssertion bool, triggerType doma // getUserInfo returns a function which retrieves userinfo from the database once. // However, each time, role claims are asserted and also action flows will trigger. -func (s *Server) getUserInfo(userID, projectID string, projectRoleAssertion, userInfoAssertion bool, scope []string) userInfoFunc { - userInfo := s.userInfo(userID, scope, projectID, projectRoleAssertion, userInfoAssertion, false) +func (s *Server) getUserInfo(userID, projectID, clientID string, projectRoleAssertion, userInfoAssertion bool, scope []string) userInfoFunc { + userInfo := s.userInfo(userID, scope, projectID, clientID, projectRoleAssertion, userInfoAssertion, false) return func(ctx context.Context, roleAssertion bool, triggerType domain.TriggerType) (*oidc.UserInfo, error) { return userInfo(ctx, roleAssertion, triggerType) } diff --git a/internal/api/oidc/token_exchange.go b/internal/api/oidc/token_exchange.go index 030066ea1c..8cb087e760 100644 --- a/internal/api/oidc/token_exchange.go +++ b/internal/api/oidc/token_exchange.go @@ -218,7 +218,7 @@ func validateTokenExchangeAudience(requestedAudience, subjectAudience, actorAudi // Both tokens may point to the same object (subjectToken) in case of a regular Token Exchange. // When the subject and actor Tokens point to different objects, the new tokens will be for impersonation / delegation. func (s *Server) createExchangeTokens(ctx context.Context, tokenType oidc.TokenType, client *Client, subjectToken, actorToken *exchangeToken, audience, scopes []string) (_ *oidc.TokenExchangeResponse, err error) { - getUserInfo := s.getUserInfo(subjectToken.userID, client.client.ProjectID, client.client.ProjectRoleAssertion, client.IDTokenUserinfoClaimsAssertion(), scopes) + getUserInfo := s.getUserInfo(subjectToken.userID, client.client.ProjectID, client.GetID(), client.client.ProjectRoleAssertion, client.IDTokenUserinfoClaimsAssertion(), scopes) getSigner := s.getSignerOnce() resp := &oidc.TokenExchangeResponse{ diff --git a/internal/api/oidc/userinfo.go b/internal/api/oidc/userinfo.go index 170ff49c94..833c7a6ee4 100644 --- a/internal/api/oidc/userinfo.go +++ b/internal/api/oidc/userinfo.go @@ -54,6 +54,7 @@ func (s *Server) UserInfo(ctx context.Context, r *op.Request[oidc.UserInfoReques token.userID, token.scope, projectID, + token.clientID, assertion, true, false, @@ -86,6 +87,7 @@ func (s *Server) userInfo( userID string, scope []string, projectID string, + clientID string, projectRoleAssertion, userInfoAssertion, currentProjectOnly bool, ) func(ctx context.Context, roleAssertion bool, triggerType domain.TriggerType) (_ *oidc.UserInfo, err error) { var ( @@ -120,7 +122,7 @@ func (s *Server) userInfo( Claims: maps.Clone(rawUserInfo.Claims), } assertRoles(projectID, qu, roleAudience, requestedRoles, roleAssertion, userInfo) - return userInfo, s.userinfoFlows(ctx, qu, userInfo, triggerType) + return userInfo, s.userinfoFlows(ctx, qu, userInfo, triggerType, clientID) } } @@ -285,7 +287,8 @@ func setUserInfoRoleClaims(userInfo *oidc.UserInfo, roles *projectsRoles) { } } -func (s *Server) userinfoFlows(ctx context.Context, qu *query.OIDCUserInfo, userInfo *oidc.UserInfo, triggerType domain.TriggerType) (err error) { +//nolint:gocognit +func (s *Server) userinfoFlows(ctx context.Context, qu *query.OIDCUserInfo, userInfo *oidc.UserInfo, triggerType domain.TriggerType, clientID string) (err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() @@ -319,6 +322,13 @@ func (s *Server) userinfoFlows(ctx context.Context, qu *query.OIDCUserInfo, user } }), ), + actions.SetFields("application", + actions.SetFields("getClientId", func(c *actions.FieldConfig) interface{} { + return func(goja.FunctionCall) goja.Value { + return c.Runtime.ToValue(clientID) + } + }), + ), ), ) @@ -427,6 +437,7 @@ func (s *Server) userinfoFlows(ctx context.Context, qu *query.OIDCUserInfo, user User: qu.User, UserMetadata: qu.Metadata, Org: qu.Org, + Application: &ContextInfoApplication{ClientID: clientID}, UserGrants: qu.UserGrants, } @@ -463,13 +474,17 @@ func (s *Server) userinfoFlows(ctx context.Context, qu *query.OIDCUserInfo, user } type ContextInfo struct { - Function string `json:"function,omitempty"` - UserInfo *oidc.UserInfo `json:"userinfo,omitempty"` - User *query.User `json:"user,omitempty"` - UserMetadata []query.UserMetadata `json:"user_metadata,omitempty"` - Org *query.UserInfoOrg `json:"org,omitempty"` - UserGrants []query.UserGrant `json:"user_grants,omitempty"` - Response *ContextInfoResponse `json:"response,omitempty"` + Function string `json:"function,omitempty"` + UserInfo *oidc.UserInfo `json:"userinfo,omitempty"` + User *query.User `json:"user,omitempty"` + UserMetadata []query.UserMetadata `json:"user_metadata,omitempty"` + Org *query.UserInfoOrg `json:"org,omitempty"` + UserGrants []query.UserGrant `json:"user_grants,omitempty"` + Application *ContextInfoApplication `json:"application,omitempty"` + Response *ContextInfoResponse `json:"response,omitempty"` +} +type ContextInfoApplication struct { + ClientID string `json:"client_id,omitempty"` } type ContextInfoResponse struct {