mirror of
https://github.com/zitadel/zitadel.git
synced 2024-12-16 04:48:04 +00:00
7caa43ab23
# Which Problems Are Solved The action v2 messages were didn't contain anything providing security for the sent content. # How the Problems Are Solved Each Target now has a SigningKey, which can also be newly generated through the API and returned at creation and through the Get-Endpoints. There is now a HTTP header "Zitadel-Signature", which is generated with the SigningKey and Payload, and also contains a timestamp to check with a tolerance if the message took to long to sent. # Additional Changes The functionality to create and check the signature is provided in the pkg/actions package, and can be reused in the SDK. # Additional Context Closes #7924 --------- Co-authored-by: Livio Spring <livio.a@gmail.com>
412 lines
15 KiB
Go
412 lines
15 KiB
Go
package action
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
|
|
"google.golang.org/protobuf/types/known/durationpb"
|
|
|
|
resource_object "github.com/zitadel/zitadel/internal/api/grpc/resources/object/v3alpha"
|
|
"github.com/zitadel/zitadel/internal/command"
|
|
"github.com/zitadel/zitadel/internal/domain"
|
|
"github.com/zitadel/zitadel/internal/query"
|
|
"github.com/zitadel/zitadel/internal/zerrors"
|
|
object "github.com/zitadel/zitadel/pkg/grpc/object/v3alpha"
|
|
action "github.com/zitadel/zitadel/pkg/grpc/resources/action/v3alpha"
|
|
)
|
|
|
|
const (
|
|
conditionIDAllSegmentCount = 0
|
|
conditionIDRequestResponseServiceSegmentCount = 1
|
|
conditionIDRequestResponseMethodSegmentCount = 2
|
|
conditionIDEventGroupSegmentCount = 1
|
|
)
|
|
|
|
func (s *Server) GetTarget(ctx context.Context, req *action.GetTargetRequest) (*action.GetTargetResponse, error) {
|
|
if err := checkActionsEnabled(ctx); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
resp, err := s.query.GetTargetByID(ctx, req.GetId())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &action.GetTargetResponse{
|
|
Target: targetToPb(resp),
|
|
}, nil
|
|
}
|
|
|
|
type InstanceContext interface {
|
|
GetInstanceId() string
|
|
GetInstanceDomain() string
|
|
}
|
|
|
|
type Context interface {
|
|
GetOwner() InstanceContext
|
|
}
|
|
|
|
func (s *Server) SearchTargets(ctx context.Context, req *action.SearchTargetsRequest) (*action.SearchTargetsResponse, error) {
|
|
if err := checkActionsEnabled(ctx); err != nil {
|
|
return nil, err
|
|
}
|
|
queries, err := s.searchTargetsRequestToModel(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
resp, err := s.query.SearchTargets(ctx, queries)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &action.SearchTargetsResponse{
|
|
Result: targetsToPb(resp.Targets),
|
|
Details: resource_object.ToSearchDetailsPb(queries.SearchRequest, resp.SearchResponse),
|
|
}, nil
|
|
}
|
|
|
|
func (s *Server) SearchExecutions(ctx context.Context, req *action.SearchExecutionsRequest) (*action.SearchExecutionsResponse, error) {
|
|
if err := checkActionsEnabled(ctx); err != nil {
|
|
return nil, err
|
|
}
|
|
queries, err := s.searchExecutionsRequestToModel(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
resp, err := s.query.SearchExecutions(ctx, queries)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &action.SearchExecutionsResponse{
|
|
Result: executionsToPb(resp.Executions),
|
|
Details: resource_object.ToSearchDetailsPb(queries.SearchRequest, resp.SearchResponse),
|
|
}, nil
|
|
}
|
|
|
|
func targetsToPb(targets []*query.Target) []*action.GetTarget {
|
|
t := make([]*action.GetTarget, len(targets))
|
|
for i, target := range targets {
|
|
t[i] = targetToPb(target)
|
|
}
|
|
return t
|
|
}
|
|
|
|
func targetToPb(t *query.Target) *action.GetTarget {
|
|
target := &action.GetTarget{
|
|
Details: resource_object.DomainToDetailsPb(&t.ObjectDetails, object.OwnerType_OWNER_TYPE_INSTANCE, t.ResourceOwner),
|
|
Config: &action.Target{
|
|
Name: t.Name,
|
|
Timeout: durationpb.New(t.Timeout),
|
|
Endpoint: t.Endpoint,
|
|
},
|
|
SigningKey: t.SigningKey,
|
|
}
|
|
switch t.TargetType {
|
|
case domain.TargetTypeWebhook:
|
|
target.Config.TargetType = &action.Target_RestWebhook{RestWebhook: &action.SetRESTWebhook{InterruptOnError: t.InterruptOnError}}
|
|
case domain.TargetTypeCall:
|
|
target.Config.TargetType = &action.Target_RestCall{RestCall: &action.SetRESTCall{InterruptOnError: t.InterruptOnError}}
|
|
case domain.TargetTypeAsync:
|
|
target.Config.TargetType = &action.Target_RestAsync{RestAsync: &action.SetRESTAsync{}}
|
|
default:
|
|
target.Config.TargetType = nil
|
|
}
|
|
return target
|
|
}
|
|
|
|
func (s *Server) searchTargetsRequestToModel(req *action.SearchTargetsRequest) (*query.TargetSearchQueries, error) {
|
|
offset, limit, asc, err := resource_object.SearchQueryPbToQuery(s.systemDefaults, req.Query)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
queries, err := targetQueriesToQuery(req.Filters)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &query.TargetSearchQueries{
|
|
SearchRequest: query.SearchRequest{
|
|
Offset: offset,
|
|
Limit: limit,
|
|
Asc: asc,
|
|
SortingColumn: targetFieldNameToSortingColumn(req.SortingColumn),
|
|
},
|
|
Queries: queries,
|
|
}, nil
|
|
}
|
|
|
|
func targetQueriesToQuery(queries []*action.TargetSearchFilter) (_ []query.SearchQuery, err error) {
|
|
q := make([]query.SearchQuery, len(queries))
|
|
for i, qry := range queries {
|
|
q[i], err = targetQueryToQuery(qry)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return q, nil
|
|
}
|
|
|
|
func targetQueryToQuery(filter *action.TargetSearchFilter) (query.SearchQuery, error) {
|
|
switch q := filter.Filter.(type) {
|
|
case *action.TargetSearchFilter_TargetNameFilter:
|
|
return targetNameQueryToQuery(q.TargetNameFilter)
|
|
case *action.TargetSearchFilter_InTargetIdsFilter:
|
|
return targetInTargetIdsQueryToQuery(q.InTargetIdsFilter)
|
|
default:
|
|
return nil, zerrors.ThrowInvalidArgument(nil, "GRPC-vR9nC", "List.Query.Invalid")
|
|
}
|
|
}
|
|
|
|
func targetNameQueryToQuery(q *action.TargetNameFilter) (query.SearchQuery, error) {
|
|
return query.NewTargetNameSearchQuery(resource_object.TextMethodPbToQuery(q.Method), q.GetTargetName())
|
|
}
|
|
|
|
func targetInTargetIdsQueryToQuery(q *action.InTargetIDsFilter) (query.SearchQuery, error) {
|
|
return query.NewTargetInIDsSearchQuery(q.GetTargetIds())
|
|
}
|
|
|
|
// targetFieldNameToSortingColumn defaults to the creation date because this ensures deterministic pagination
|
|
func targetFieldNameToSortingColumn(field *action.TargetFieldName) query.Column {
|
|
if field == nil {
|
|
return query.TargetColumnCreationDate
|
|
}
|
|
switch *field {
|
|
case action.TargetFieldName_TARGET_FIELD_NAME_UNSPECIFIED:
|
|
return query.TargetColumnID
|
|
case action.TargetFieldName_TARGET_FIELD_NAME_ID:
|
|
return query.TargetColumnID
|
|
case action.TargetFieldName_TARGET_FIELD_NAME_CREATED_DATE:
|
|
return query.TargetColumnCreationDate
|
|
case action.TargetFieldName_TARGET_FIELD_NAME_CHANGED_DATE:
|
|
return query.TargetColumnChangeDate
|
|
case action.TargetFieldName_TARGET_FIELD_NAME_NAME:
|
|
return query.TargetColumnName
|
|
case action.TargetFieldName_TARGET_FIELD_NAME_TARGET_TYPE:
|
|
return query.TargetColumnTargetType
|
|
case action.TargetFieldName_TARGET_FIELD_NAME_URL:
|
|
return query.TargetColumnURL
|
|
case action.TargetFieldName_TARGET_FIELD_NAME_TIMEOUT:
|
|
return query.TargetColumnTimeout
|
|
case action.TargetFieldName_TARGET_FIELD_NAME_INTERRUPT_ON_ERROR:
|
|
return query.TargetColumnInterruptOnError
|
|
default:
|
|
return query.TargetColumnCreationDate
|
|
}
|
|
}
|
|
|
|
// executionFieldNameToSortingColumn defaults to the creation date because this ensures deterministic pagination
|
|
func executionFieldNameToSortingColumn(field *action.ExecutionFieldName) query.Column {
|
|
if field == nil {
|
|
return query.ExecutionColumnCreationDate
|
|
}
|
|
switch *field {
|
|
case action.ExecutionFieldName_EXECUTION_FIELD_NAME_UNSPECIFIED:
|
|
return query.ExecutionColumnID
|
|
case action.ExecutionFieldName_EXECUTION_FIELD_NAME_ID:
|
|
return query.ExecutionColumnID
|
|
case action.ExecutionFieldName_EXECUTION_FIELD_NAME_CREATED_DATE:
|
|
return query.ExecutionColumnCreationDate
|
|
case action.ExecutionFieldName_EXECUTION_FIELD_NAME_CHANGED_DATE:
|
|
return query.ExecutionColumnChangeDate
|
|
default:
|
|
return query.ExecutionColumnCreationDate
|
|
}
|
|
}
|
|
|
|
func (s *Server) searchExecutionsRequestToModel(req *action.SearchExecutionsRequest) (*query.ExecutionSearchQueries, error) {
|
|
offset, limit, asc, err := resource_object.SearchQueryPbToQuery(s.systemDefaults, req.Query)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
queries, err := executionQueriesToQuery(req.Filters)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &query.ExecutionSearchQueries{
|
|
SearchRequest: query.SearchRequest{
|
|
Offset: offset,
|
|
Limit: limit,
|
|
Asc: asc,
|
|
SortingColumn: executionFieldNameToSortingColumn(req.SortingColumn),
|
|
},
|
|
Queries: queries,
|
|
}, nil
|
|
}
|
|
|
|
func executionQueriesToQuery(queries []*action.ExecutionSearchFilter) (_ []query.SearchQuery, err error) {
|
|
q := make([]query.SearchQuery, len(queries))
|
|
for i, query := range queries {
|
|
q[i], err = executionQueryToQuery(query)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return q, nil
|
|
}
|
|
|
|
func executionQueryToQuery(searchQuery *action.ExecutionSearchFilter) (query.SearchQuery, error) {
|
|
switch q := searchQuery.Filter.(type) {
|
|
case *action.ExecutionSearchFilter_InConditionsFilter:
|
|
return inConditionsQueryToQuery(q.InConditionsFilter)
|
|
case *action.ExecutionSearchFilter_ExecutionTypeFilter:
|
|
return executionTypeToQuery(q.ExecutionTypeFilter)
|
|
case *action.ExecutionSearchFilter_IncludeFilter:
|
|
include, err := conditionToInclude(q.IncludeFilter.GetInclude())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return query.NewIncludeSearchQuery(include)
|
|
case *action.ExecutionSearchFilter_TargetFilter:
|
|
return query.NewTargetSearchQuery(q.TargetFilter.GetTargetId())
|
|
default:
|
|
return nil, zerrors.ThrowInvalidArgument(nil, "GRPC-vR9nC", "List.Query.Invalid")
|
|
}
|
|
}
|
|
|
|
func executionTypeToQuery(q *action.ExecutionTypeFilter) (query.SearchQuery, error) {
|
|
switch q.ExecutionType {
|
|
case action.ExecutionType_EXECUTION_TYPE_UNSPECIFIED:
|
|
return query.NewExecutionTypeSearchQuery(domain.ExecutionTypeUnspecified)
|
|
case action.ExecutionType_EXECUTION_TYPE_REQUEST:
|
|
return query.NewExecutionTypeSearchQuery(domain.ExecutionTypeRequest)
|
|
case action.ExecutionType_EXECUTION_TYPE_RESPONSE:
|
|
return query.NewExecutionTypeSearchQuery(domain.ExecutionTypeResponse)
|
|
case action.ExecutionType_EXECUTION_TYPE_EVENT:
|
|
return query.NewExecutionTypeSearchQuery(domain.ExecutionTypeEvent)
|
|
case action.ExecutionType_EXECUTION_TYPE_FUNCTION:
|
|
return query.NewExecutionTypeSearchQuery(domain.ExecutionTypeFunction)
|
|
default:
|
|
return query.NewExecutionTypeSearchQuery(domain.ExecutionTypeUnspecified)
|
|
}
|
|
}
|
|
|
|
func inConditionsQueryToQuery(q *action.InConditionsFilter) (query.SearchQuery, error) {
|
|
values := make([]string, len(q.GetConditions()))
|
|
for i, condition := range q.GetConditions() {
|
|
id, err := conditionToID(condition)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
values[i] = id
|
|
}
|
|
return query.NewExecutionInIDsSearchQuery(values)
|
|
}
|
|
|
|
func conditionToID(q *action.Condition) (string, error) {
|
|
switch t := q.GetConditionType().(type) {
|
|
case *action.Condition_Request:
|
|
cond := &command.ExecutionAPICondition{
|
|
Method: t.Request.GetMethod(),
|
|
Service: t.Request.GetService(),
|
|
All: t.Request.GetAll(),
|
|
}
|
|
return cond.ID(domain.ExecutionTypeRequest), nil
|
|
case *action.Condition_Response:
|
|
cond := &command.ExecutionAPICondition{
|
|
Method: t.Response.GetMethod(),
|
|
Service: t.Response.GetService(),
|
|
All: t.Response.GetAll(),
|
|
}
|
|
return cond.ID(domain.ExecutionTypeResponse), nil
|
|
case *action.Condition_Event:
|
|
cond := &command.ExecutionEventCondition{
|
|
Event: t.Event.GetEvent(),
|
|
Group: t.Event.GetGroup(),
|
|
All: t.Event.GetAll(),
|
|
}
|
|
return cond.ID(), nil
|
|
case *action.Condition_Function:
|
|
return command.ExecutionFunctionCondition(t.Function.GetName()).ID(), nil
|
|
default:
|
|
return "", zerrors.ThrowInvalidArgument(nil, "GRPC-vR9nC", "List.Query.Invalid")
|
|
}
|
|
}
|
|
|
|
func executionsToPb(executions []*query.Execution) []*action.GetExecution {
|
|
e := make([]*action.GetExecution, len(executions))
|
|
for i, execution := range executions {
|
|
e[i] = executionToPb(execution)
|
|
}
|
|
return e
|
|
}
|
|
|
|
func executionToPb(e *query.Execution) *action.GetExecution {
|
|
targets := make([]*action.ExecutionTargetType, len(e.Targets))
|
|
for i := range e.Targets {
|
|
switch e.Targets[i].Type {
|
|
case domain.ExecutionTargetTypeInclude:
|
|
targets[i] = &action.ExecutionTargetType{Type: &action.ExecutionTargetType_Include{Include: executionIDToCondition(e.Targets[i].Target)}}
|
|
case domain.ExecutionTargetTypeTarget:
|
|
targets[i] = &action.ExecutionTargetType{Type: &action.ExecutionTargetType_Target{Target: e.Targets[i].Target}}
|
|
case domain.ExecutionTargetTypeUnspecified:
|
|
continue
|
|
default:
|
|
continue
|
|
}
|
|
}
|
|
|
|
return &action.GetExecution{
|
|
Details: resource_object.DomainToDetailsPb(&e.ObjectDetails, object.OwnerType_OWNER_TYPE_INSTANCE, e.ResourceOwner),
|
|
Execution: &action.Execution{
|
|
Targets: targets,
|
|
},
|
|
}
|
|
}
|
|
|
|
func executionIDToCondition(include string) *action.Condition {
|
|
if strings.HasPrefix(include, domain.ExecutionTypeRequest.String()) {
|
|
return includeRequestToCondition(strings.TrimPrefix(include, domain.ExecutionTypeRequest.String()))
|
|
}
|
|
if strings.HasPrefix(include, domain.ExecutionTypeResponse.String()) {
|
|
return includeResponseToCondition(strings.TrimPrefix(include, domain.ExecutionTypeResponse.String()))
|
|
}
|
|
if strings.HasPrefix(include, domain.ExecutionTypeEvent.String()) {
|
|
return includeEventToCondition(strings.TrimPrefix(include, domain.ExecutionTypeEvent.String()))
|
|
}
|
|
if strings.HasPrefix(include, domain.ExecutionTypeFunction.String()) {
|
|
return includeFunctionToCondition(strings.TrimPrefix(include, domain.ExecutionTypeFunction.String()))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func includeRequestToCondition(id string) *action.Condition {
|
|
switch strings.Count(id, "/") {
|
|
case conditionIDRequestResponseMethodSegmentCount:
|
|
return &action.Condition{ConditionType: &action.Condition_Request{Request: &action.RequestExecution{Condition: &action.RequestExecution_Method{Method: id}}}}
|
|
case conditionIDRequestResponseServiceSegmentCount:
|
|
return &action.Condition{ConditionType: &action.Condition_Request{Request: &action.RequestExecution{Condition: &action.RequestExecution_Service{Service: strings.TrimPrefix(id, "/")}}}}
|
|
case conditionIDAllSegmentCount:
|
|
return &action.Condition{ConditionType: &action.Condition_Request{Request: &action.RequestExecution{Condition: &action.RequestExecution_All{All: true}}}}
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
func includeResponseToCondition(id string) *action.Condition {
|
|
switch strings.Count(id, "/") {
|
|
case conditionIDRequestResponseMethodSegmentCount:
|
|
return &action.Condition{ConditionType: &action.Condition_Response{Response: &action.ResponseExecution{Condition: &action.ResponseExecution_Method{Method: id}}}}
|
|
case conditionIDRequestResponseServiceSegmentCount:
|
|
return &action.Condition{ConditionType: &action.Condition_Response{Response: &action.ResponseExecution{Condition: &action.ResponseExecution_Service{Service: strings.TrimPrefix(id, "/")}}}}
|
|
case conditionIDAllSegmentCount:
|
|
return &action.Condition{ConditionType: &action.Condition_Response{Response: &action.ResponseExecution{Condition: &action.ResponseExecution_All{All: true}}}}
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func includeEventToCondition(id string) *action.Condition {
|
|
switch strings.Count(id, "/") {
|
|
case conditionIDEventGroupSegmentCount:
|
|
if strings.HasSuffix(id, command.EventGroupSuffix) {
|
|
return &action.Condition{ConditionType: &action.Condition_Event{Event: &action.EventExecution{Condition: &action.EventExecution_Group{Group: strings.TrimSuffix(strings.TrimPrefix(id, "/"), command.EventGroupSuffix)}}}}
|
|
} else {
|
|
return &action.Condition{ConditionType: &action.Condition_Event{Event: &action.EventExecution{Condition: &action.EventExecution_Event{Event: strings.TrimPrefix(id, "/")}}}}
|
|
}
|
|
case conditionIDAllSegmentCount:
|
|
return &action.Condition{ConditionType: &action.Condition_Event{Event: &action.EventExecution{Condition: &action.EventExecution_All{All: true}}}}
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func includeFunctionToCondition(id string) *action.Condition {
|
|
return &action.Condition{ConditionType: &action.Condition_Function{Function: &action.FunctionExecution{Name: strings.TrimPrefix(id, "/")}}}
|
|
}
|