Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions api/agent.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2041,6 +2041,12 @@ components:
type: string
description: User types that can sign up through this agent.
example: ["employee", "contractor"]
allowedAgentTypes:
type: array
items:
type: string
description: Agent types allowed to sign in to this agent. An agent can authenticate only when its agent type is listed here; when the list is empty no agent can sign in.
example: ["default"]
inboundAuthConfig:
type: array
items:
Expand Down Expand Up @@ -2117,6 +2123,11 @@ components:
items:
type: string
example: ["employee", "contractor"]
allowedAgentTypes:
type: array
items:
type: string
example: ["default"]
inboundAuthConfig:
type: array
items:
Expand Down Expand Up @@ -2191,6 +2202,11 @@ components:
items:
type: string
example: ["employee", "contractor"]
allowedAgentTypes:
type: array
items:
type: string
example: ["default"]
inboundAuthConfig:
type: array
items:
Expand Down Expand Up @@ -2268,6 +2284,11 @@ components:
items:
type: string
example: ["employee", "contractor"]
allowedAgentTypes:
type: array
items:
type: string
example: ["default"]
inboundAuthConfig:
type: array
items:
Expand Down
18 changes: 18 additions & 0 deletions api/application.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -854,6 +854,12 @@ components:
type: string
description: User types allowed to sign up through this application.
example: ["employee", "customer", "partner"]
allowedAgentTypes:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have we thought about experience that we're going to offer in the application/agent token configs with this? Assertion/ access token/ id token and userinfo attributes are currently derived from allowed user types. This is performed in both console UI and backend service as well.

When a user is going to login to the application, only user attributes will be applicable. However when an agent is going to login, we should get agent attributes right?

Two questions;

  1. Have we thought about how we're going to provide token config experience with this? In both console UI and API payload (if applicable).
  2. Have we updated runtime token processing logic to consider agent types and retrieve attributes for agents when a agent login is happening? Or is it a separate followup PR?

Also related to ongoing discussion: #5219

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. We haven't finalized an approach on how to provide the token configuration experience yet.
  2. No. Current logic already works and agents can get a valid token. The token includes any attributes that are common in both agent schema and user types. It only misses any attribute that is only there in the agent schema, as there is no way to configure them.

I think both of these should be answered in a separate PR as they need to land together. I will raise an issue covering both the config experience plus the validation changes, and send a followup PR later.

type: array
items:
type: string
description: Agent types allowed to sign in to this application. An agent can authenticate only when its agent type is listed here; when the list is empty no agent can sign in.
example: ["default"]
passkeyAllowedOrigins:
type: array
items:
Expand Down Expand Up @@ -1000,6 +1006,12 @@ components:
type: string
description: User types allowed to sign up through this application.
example: ["employee", "customer", "partner"]
allowedAgentTypes:
type: array
items:
type: string
description: Agent types allowed to sign in to this application. An agent can authenticate only when its agent type is listed here; when the list is empty no agent can sign in.
example: ["default"]
passkeyAllowedOrigins:
type: array
items:
Expand Down Expand Up @@ -1127,6 +1139,12 @@ components:
type: string
description: User types allowed to sign up through this application.
example: ["employee", "customer", "partner"]
allowedAgentTypes:
type: array
items:
type: string
description: Agent types allowed to sign in to this application. An agent can authenticate only when its agent type is listed here; when the list is empty no agent can sign in.
example: ["default"]
passkeyAllowedOrigins:
type: array
items:
Expand Down
1 change: 1 addition & 0 deletions backend/internal/actorprovider/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ func assembleApplication(
Assertion: client.Assertion,
LoginConsent: client.LoginConsent,
AllowedUserTypes: client.AllowedUserTypes,
AllowedAgentTypes: client.AllowedAgentTypes,
SubjectAttribute: client.SubjectAttribute,
PasskeyAllowedOrigins: client.PasskeyAllowedOrigins,
},
Expand Down
2 changes: 2 additions & 0 deletions backend/internal/agent/declarative_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@ func makeAgentEntityParser(
Assertion: req.Assertion,
LoginConsent: req.LoginConsent,
AllowedUserTypes: req.AllowedUserTypes,
AllowedAgentTypes: req.AllowedAgentTypes,
PasskeyAllowedOrigins: req.PasskeyAllowedOrigins,
Attestation: req.Attestation,
},
Expand Down Expand Up @@ -297,6 +298,7 @@ func makeAgentInboundParser(agentSvc AgentServiceInterface) func([]byte) (*inbou
Assertion: req.Assertion,
LoginConsent: req.LoginConsent,
AllowedUserTypes: req.AllowedUserTypes,
AllowedAgentTypes: req.AllowedAgentTypes,
PasskeyAllowedOrigins: req.PasskeyAllowedOrigins,
Attestation: req.Attestation,
},
Expand Down
14 changes: 14 additions & 0 deletions backend/internal/agent/error_constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,20 @@ var (
},
}

// ErrorInvalidAllowedAgentType is returned when an allowed agent type does not exist.
ErrorInvalidAllowedAgentType = tidcommon.ServiceError{
Type: tidcommon.ClientErrorType,
Code: "AGT-1043",
Error: tidcommon.I18nMessage{
Key: "error.agentservice.invalid_allowed_agent_type",
DefaultValue: "Invalid agent type",
},
ErrorDescription: tidcommon.I18nMessage{
Key: "error.agentservice.invalid_allowed_agent_type_description",
DefaultValue: "One or more specified allowed agent types are invalid",
},
}

// ErrorThemeNotFound is returned when the referenced theme does not exist.
ErrorThemeNotFound = tidcommon.ServiceError{
Type: tidcommon.ClientErrorType,
Expand Down
1 change: 1 addition & 0 deletions backend/internal/agent/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ func (h *agentHandler) HandleAgentPostRequest(w http.ResponseWriter, r *http.Req
Assertion: req.Assertion,
LoginConsent: req.LoginConsent,
AllowedUserTypes: req.AllowedUserTypes,
AllowedAgentTypes: req.AllowedAgentTypes,
PasskeyAllowedOrigins: req.PasskeyAllowedOrigins,
Attestation: req.Attestation,
},
Expand Down
23 changes: 16 additions & 7 deletions backend/internal/agent/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ func (s *agentService) CreateAgent(ctx context.Context, agent *model.Agent) (
agent.Type, agent.Name, agent.Description, agent.LogoURL, createdEntity.Attributes,
authFlowID, regFlowID, agent.IsRegistrationFlowEnabled,
agent.ThemeID, agent.LayoutID, assertion, loginConsent,
agent.AllowedUserTypes, inboundConfigs)
agent.AllowedUserTypes, agent.AllowedAgentTypes, inboundConfigs)
resp.OUID = agent.OUID
s.populateOUHandleForComplete(ctx, resp)
return resp, nil
Expand Down Expand Up @@ -340,7 +340,7 @@ func (s *agentService) UpdateAgent(ctx context.Context, agentID string,
req.Type, req.Name, req.Description, req.LogoURL, req.Attributes,
authFlowID, regFlowID, resolvedClient.IsRegistrationFlowEnabled,
req.ThemeID, req.LayoutID, assertion, loginConsent,
req.AllowedUserTypes, inboundConfigs)
req.AllowedUserTypes, req.AllowedAgentTypes, inboundConfigs)
resp.OUID = ouID
s.populateOUHandleForComplete(ctx, resp)
return resp, nil
Expand Down Expand Up @@ -689,7 +689,7 @@ func (s *agentService) ValidateAgent(ctx context.Context, agent *model.Agent, ex

client := buildInboundClientRecord("", agent.AuthFlowID, agent.RegistrationFlowID,
agent.IsRegistrationFlowEnabled, agent.ThemeID, agent.LayoutID, agent.Assertion,
agent.LoginConsent, agent.AllowedUserTypes, agent.SubjectAttribute)
agent.LoginConsent, agent.AllowedUserTypes, agent.AllowedAgentTypes, agent.SubjectAttribute)

if needsInboundClient(agent) {
oauthProfile := buildOAuthProfile(agent.InboundAuthConfig)
Expand Down Expand Up @@ -902,7 +902,7 @@ func (s *agentService) createInboundForAgent(ctx context.Context, agentID string
inboundmodel.InboundClient, *providers.OAuthProfile, *tidcommon.ServiceError) {
client := buildInboundClientRecord(agentID, agent.AuthFlowID, agent.RegistrationFlowID,
agent.IsRegistrationFlowEnabled, agent.ThemeID, agent.LayoutID, agent.Assertion,
agent.LoginConsent, agent.AllowedUserTypes, agent.SubjectAttribute)
agent.LoginConsent, agent.AllowedUserTypes, agent.AllowedAgentTypes, agent.SubjectAttribute)
setLogoProperty(&client, agent.LogoURL)

seedClientSubTypeAttribute(agent.InboundAuthConfig)
Expand Down Expand Up @@ -966,7 +966,7 @@ func (s *agentService) reconcileInboundForUpdate(ctx context.Context, agentID st

client := buildInboundClientRecord(agentID, profile.AuthFlowID, profile.RegistrationFlowID,
req.IsRegistrationFlowEnabled, req.ThemeID, req.LayoutID, req.Assertion,
req.LoginConsent, req.AllowedUserTypes, nil)
req.LoginConsent, req.AllowedUserTypes, req.AllowedAgentTypes, nil)
setLogoProperty(&client, req.LogoURL)
oauthProfile := buildOAuthProfile(req.InboundAuthConfig)
hasSecret := clientSecret != ""
Expand Down Expand Up @@ -1030,6 +1030,7 @@ func (s *agentService) composeGetResponse(ctx context.Context, e *providers.Enti
resp.Assertion = inbound.Assertion
resp.LoginConsent = inbound.LoginConsent
resp.AllowedUserTypes = inbound.AllowedUserTypes
resp.AllowedAgentTypes = inbound.AllowedAgentTypes
resp.LogoURL = logoURLFromProperties(inbound.Properties)

oauth, oauthErr := s.inboundClientService.GetOAuthProfileByEntityID(ctx, e.ID)
Expand Down Expand Up @@ -1170,6 +1171,7 @@ func needsInboundClient(agent *model.Agent) bool {
agent.Assertion != nil ||
agent.LoginConsent != nil ||
len(agent.AllowedUserTypes) > 0 ||
len(agent.AllowedAgentTypes) > 0 ||
len(agent.InboundAuthConfig) > 0
}

Expand All @@ -1188,6 +1190,7 @@ func updateNeedsInboundClient(req *model.UpdateAgentRequest) bool {
req.Assertion != nil ||
req.LoginConsent != nil ||
len(req.AllowedUserTypes) > 0 ||
len(req.AllowedAgentTypes) > 0 ||
len(req.InboundAuthConfig) > 0
}

Expand Down Expand Up @@ -1356,7 +1359,7 @@ func readSystemAttributes(raw json.RawMessage) (name, description, owner, client
// buildInboundClientRecord constructs an InboundClient record from the agent's identity and inbound auth fields.
func buildInboundClientRecord(agentID, authFlowID, regFlowID string, isRegEnabled bool,
themeID, layoutID string, assertion *inboundmodel.AssertionConfig,
loginConsent *inboundmodel.LoginConsentConfig, allowedUserTypes []string,
loginConsent *inboundmodel.LoginConsentConfig, allowedUserTypes, allowedAgentTypes []string,
subjectAttribute map[string]string) inboundmodel.InboundClient {
return inboundmodel.InboundClient{
ID: agentID,
Expand All @@ -1368,6 +1371,7 @@ func buildInboundClientRecord(agentID, authFlowID, regFlowID string, isRegEnable
Assertion: assertion,
LoginConsent: loginConsent,
AllowedUserTypes: allowedUserTypes,
AllowedAgentTypes: allowedAgentTypes,
SubjectAttribute: subjectAttribute,
}
}
Expand Down Expand Up @@ -1513,7 +1517,7 @@ func convertGrantAndResponseTypes(
func buildCompleteResponse(agentID, owner, clientID, clientSecret, agentType, name, description, logoURL string,
attributes json.RawMessage, authFlowID, regFlowID string, isRegEnabled bool,
themeID, layoutID string, assertion *inboundmodel.AssertionConfig,
loginConsent *inboundmodel.LoginConsentConfig, allowedUserTypes []string,
loginConsent *inboundmodel.LoginConsentConfig, allowedUserTypes, allowedAgentTypes []string,
inboundAuthConfig []providers.InboundAuthConfigWithSecret,
) *model.AgentCompleteResponse {
resp := &model.AgentCompleteResponse{
Expand All @@ -1533,6 +1537,7 @@ func buildCompleteResponse(agentID, owner, clientID, clientSecret, agentType, na
Assertion: assertion,
LoginConsent: loginConsent,
AllowedUserTypes: allowedUserTypes,
AllowedAgentTypes: allowedAgentTypes,
},
}
if len(inboundAuthConfig) > 0 {
Expand Down Expand Up @@ -1838,8 +1843,12 @@ func translateInboundClientFKError(err error) *tidcommon.ServiceError {
return &ErrorLayoutNotFound
case errors.Is(err, inboundclient.ErrFKInvalidUserType):
return &ErrorInvalidUserType
case errors.Is(err, inboundclient.ErrFKInvalidAgentType):
return &ErrorInvalidAllowedAgentType
case errors.Is(err, inboundclient.ErrUserSchemaLookupFailed):
return &tidcommon.InternalServerError
case errors.Is(err, inboundclient.ErrAgentSchemaLookupFailed):
return &tidcommon.InternalServerError
case errors.Is(err, inboundclient.ErrUniqueAttributeLookupFailed):
return &tidcommon.InternalServerError
case errors.Is(err, inboundclient.ErrFKInvalidSubjectAttributeMapping):
Expand Down
1 change: 1 addition & 0 deletions backend/internal/application/declarative_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ func parseToApplicationDTO(data []byte) (*model.ApplicationDTO, error) {
Assertion: appRequest.Assertion,
LoginConsent: appRequest.LoginConsent,
AllowedUserTypes: appRequest.AllowedUserTypes,
AllowedAgentTypes: appRequest.AllowedAgentTypes,
PasskeyAllowedOrigins: appRequest.PasskeyAllowedOrigins,
Attestation: appRequest.Attestation,
},
Expand Down
14 changes: 14 additions & 0 deletions backend/internal/application/error_constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,20 @@ var (
DefaultValue: "One or more user types in allowed_user_types do not exist in the system",
},
}
// ErrorInvalidAgentType is the error returned when an invalid agent type is provided in
// allowedAgentTypes.
ErrorInvalidAgentType = tidcommon.ServiceError{
Type: tidcommon.ClientErrorType,
Code: "APP-1046",
Error: tidcommon.I18nMessage{
Key: "error.applicationservice.invalid_agent_type",
DefaultValue: "Invalid agent type",
},
ErrorDescription: tidcommon.I18nMessage{
Key: "error.applicationservice.invalid_agent_type_description",
DefaultValue: "One or more agent types in allowedAgentTypes do not exist in the system",
},
}
// ErrorThemeNotFound is the error returned when theme is not found.
ErrorThemeNotFound = tidcommon.ServiceError{
Type: tidcommon.ClientErrorType,
Expand Down
5 changes: 5 additions & 0 deletions backend/internal/application/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ func (ah *applicationHandler) HandleApplicationPostRequest(w http.ResponseWriter
Assertion: appRequest.Assertion,
LoginConsent: appRequest.LoginConsent,
AllowedUserTypes: appRequest.AllowedUserTypes,
AllowedAgentTypes: appRequest.AllowedAgentTypes,
PasskeyAllowedOrigins: appRequest.PasskeyAllowedOrigins,
Attestation: appRequest.Attestation,
},
Expand Down Expand Up @@ -106,6 +107,7 @@ func (ah *applicationHandler) HandleApplicationPostRequest(w http.ResponseWriter
Assertion: createdAppDTO.Assertion,
LoginConsent: createdAppDTO.LoginConsent,
AllowedUserTypes: createdAppDTO.AllowedUserTypes,
AllowedAgentTypes: createdAppDTO.AllowedAgentTypes,
PasskeyAllowedOrigins: createdAppDTO.PasskeyAllowedOrigins,
Attestation: createdAppDTO.Attestation,
},
Expand Down Expand Up @@ -188,6 +190,7 @@ func (ah *applicationHandler) HandleApplicationGetRequest(w http.ResponseWriter,
Assertion: appDTO.Assertion,
LoginConsent: appDTO.LoginConsent,
AllowedUserTypes: appDTO.AllowedUserTypes,
AllowedAgentTypes: appDTO.AllowedAgentTypes,
PasskeyAllowedOrigins: appDTO.PasskeyAllowedOrigins,
Attestation: appDTO.Attestation,
},
Expand Down Expand Up @@ -332,6 +335,7 @@ func (ah *applicationHandler) HandleApplicationPutRequest(w http.ResponseWriter,
Assertion: appRequest.Assertion,
LoginConsent: appRequest.LoginConsent,
AllowedUserTypes: appRequest.AllowedUserTypes,
AllowedAgentTypes: appRequest.AllowedAgentTypes,
PasskeyAllowedOrigins: appRequest.PasskeyAllowedOrigins,
Attestation: appRequest.Attestation,
},
Expand Down Expand Up @@ -371,6 +375,7 @@ func (ah *applicationHandler) HandleApplicationPutRequest(w http.ResponseWriter,
Assertion: updatedAppDTO.Assertion,
LoginConsent: updatedAppDTO.LoginConsent,
AllowedUserTypes: updatedAppDTO.AllowedUserTypes,
AllowedAgentTypes: updatedAppDTO.AllowedAgentTypes,
PasskeyAllowedOrigins: updatedAppDTO.PasskeyAllowedOrigins,
Attestation: updatedAppDTO.Attestation,
},
Expand Down
9 changes: 9 additions & 0 deletions backend/internal/application/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -831,6 +831,7 @@ func toInboundClient(dto *model.ApplicationProcessedDTO) inboundmodel.InboundCli
Assertion: dto.Assertion,
LoginConsent: dto.LoginConsent,
AllowedUserTypes: dto.AllowedUserTypes,
AllowedAgentTypes: dto.AllowedAgentTypes,
SubjectAttribute: dto.SubjectAttribute,
PasskeyAllowedOrigins: dto.PasskeyAllowedOrigins,
Attestation: dto.Attestation,
Expand Down Expand Up @@ -888,6 +889,7 @@ func toProcessedDTO(
Assertion: dao.Assertion,
LoginConsent: dao.LoginConsent,
AllowedUserTypes: dao.AllowedUserTypes,
AllowedAgentTypes: dao.AllowedAgentTypes,
SubjectAttribute: dao.SubjectAttribute,
PasskeyAllowedOrigins: dao.PasskeyAllowedOrigins,
Attestation: dao.Attestation.WithoutCredentials(),
Expand Down Expand Up @@ -1584,8 +1586,12 @@ func translateInboundClientFKError(err error) *tidcommon.ServiceError {
return &ErrorLayoutNotFound
case errors.Is(err, inboundclient.ErrFKInvalidUserType):
return &ErrorInvalidUserType
case errors.Is(err, inboundclient.ErrFKInvalidAgentType):
return &ErrorInvalidAgentType
case errors.Is(err, inboundclient.ErrUserSchemaLookupFailed):
return &tidcommon.InternalServerError
case errors.Is(err, inboundclient.ErrAgentSchemaLookupFailed):
return &tidcommon.InternalServerError
case errors.Is(err, inboundclient.ErrUniqueAttributeLookupFailed):
return &tidcommon.InternalServerError
case errors.Is(err, inboundclient.ErrFKInvalidSubjectAttributeMapping):
Expand Down Expand Up @@ -1847,6 +1853,7 @@ func buildApplicationResponse(dto *model.ApplicationProcessedDTO) *providers.App
LayoutID: dto.LayoutID,
Assertion: dto.Assertion,
AllowedUserTypes: dto.AllowedUserTypes,
AllowedAgentTypes: dto.AllowedAgentTypes,
SubjectAttribute: dto.SubjectAttribute,
PasskeyAllowedOrigins: dto.PasskeyAllowedOrigins,
LoginConsent: dto.LoginConsent,
Expand Down Expand Up @@ -1960,6 +1967,7 @@ func buildBaseApplicationProcessedDTO(appID string, app *model.ApplicationDTO,
LayoutID: app.LayoutID,
Assertion: assertion,
AllowedUserTypes: app.AllowedUserTypes,
AllowedAgentTypes: app.AllowedAgentTypes,
SubjectAttribute: app.SubjectAttribute,
PasskeyAllowedOrigins: app.PasskeyAllowedOrigins,
LoginConsent: app.LoginConsent,
Expand Down Expand Up @@ -2046,6 +2054,7 @@ func buildReturnApplicationDTO(
LayoutID: app.LayoutID,
Assertion: assertion,
AllowedUserTypes: app.AllowedUserTypes,
AllowedAgentTypes: app.AllowedAgentTypes,
SubjectAttribute: app.SubjectAttribute,
PasskeyAllowedOrigins: app.PasskeyAllowedOrigins,
LoginConsent: app.LoginConsent,
Expand Down
Loading
Loading