github.com/kyma-incubator/compass/components/director@v0.0.0-20230623144113-d764f56ff805/pkg/graphql/webhook_validation.go (about)

     1  package graphql
     2  
     3  import (
     4  	"net/url"
     5  
     6  	validation "github.com/go-ozzo/ozzo-validation/v4"
     7  	"github.com/go-ozzo/ozzo-validation/v4/is"
     8  	"github.com/kyma-incubator/compass/components/director/internal/model"
     9  	"github.com/kyma-incubator/compass/components/director/pkg/apperrors"
    10  	"github.com/kyma-incubator/compass/components/director/pkg/webhook"
    11  )
    12  
    13  var emptyApplicationLifecycleWebhookRequestObject = &webhook.ApplicationLifecycleWebhookRequestObject{
    14  	Application: &Application{
    15  		BaseEntity: &BaseEntity{},
    16  	},
    17  }
    18  
    19  var emptyFormationConfigurationChangeInput = &webhook.FormationConfigurationChangeInput{
    20  	Assignment: &webhook.FormationAssignment{
    21  		Value: "\"\"",
    22  	},
    23  	ReverseAssignment: &webhook.FormationAssignment{
    24  		Value: "\"\"",
    25  	},
    26  	ApplicationTemplate: &webhook.ApplicationTemplateWithLabels{
    27  		ApplicationTemplate: &model.ApplicationTemplate{},
    28  		Labels:              map[string]string{},
    29  	},
    30  	Application: &webhook.ApplicationWithLabels{
    31  		Application: &model.Application{
    32  			BaseEntity: &model.BaseEntity{},
    33  		},
    34  		Labels: map[string]string{},
    35  	},
    36  	Runtime: &webhook.RuntimeWithLabels{
    37  		Runtime: &model.Runtime{},
    38  		Labels:  map[string]string{},
    39  	},
    40  	RuntimeContext: &webhook.RuntimeContextWithLabels{
    41  		RuntimeContext: &model.RuntimeContext{},
    42  		Labels:         map[string]string{},
    43  	},
    44  	CustomerTenantContext: &webhook.CustomerTenantContext{},
    45  }
    46  
    47  var emptyApplicationTenantMappingInput = &webhook.ApplicationTenantMappingInput{
    48  	Assignment: &webhook.FormationAssignment{
    49  		Value: "\"\"",
    50  	},
    51  	ReverseAssignment: &webhook.FormationAssignment{
    52  		Value: "\"\"",
    53  	},
    54  	SourceApplicationTemplate: &webhook.ApplicationTemplateWithLabels{
    55  		ApplicationTemplate: &model.ApplicationTemplate{},
    56  		Labels:              map[string]string{},
    57  	},
    58  	SourceApplication: &webhook.ApplicationWithLabels{
    59  		Application: &model.Application{
    60  			BaseEntity: &model.BaseEntity{},
    61  		},
    62  		Labels: map[string]string{},
    63  	},
    64  	TargetApplicationTemplate: &webhook.ApplicationTemplateWithLabels{
    65  		ApplicationTemplate: &model.ApplicationTemplate{},
    66  		Labels:              map[string]string{},
    67  	},
    68  	TargetApplication: &webhook.ApplicationWithLabels{
    69  		Application: &model.Application{
    70  			BaseEntity: &model.BaseEntity{},
    71  		},
    72  		Labels: map[string]string{},
    73  	},
    74  	CustomerTenantContext: &webhook.CustomerTenantContext{},
    75  }
    76  
    77  var emptyFormationLifecycleInput = &webhook.FormationLifecycleInput{
    78  	Formation:             &model.Formation{},
    79  	CustomerTenantContext: &webhook.CustomerTenantContext{},
    80  }
    81  
    82  var webhookTemplateInputByType = map[WebhookType]webhook.TemplateInput{
    83  	WebhookTypeRegisterApplication:      emptyApplicationLifecycleWebhookRequestObject,
    84  	WebhookTypeUnregisterApplication:    emptyApplicationLifecycleWebhookRequestObject,
    85  	WebhookTypeConfigurationChanged:     emptyFormationConfigurationChangeInput,
    86  	WebhookTypeApplicationTenantMapping: emptyApplicationTenantMappingInput,
    87  	WebhookTypeFormationLifecycle:       emptyFormationLifecycleInput,
    88  }
    89  
    90  // Validate missing godoc
    91  func (i WebhookInput) Validate() error {
    92  	if err := validation.ValidateStruct(&i,
    93  		validation.Field(&i.Type, validation.Required, validation.In(WebhookTypeConfigurationChanged, WebhookTypeApplicationTenantMapping, WebhookTypeRegisterApplication, WebhookTypeUnregisterApplication, WebhookTypeOpenResourceDiscovery, WebhookTypeFormationLifecycle)),
    94  		validation.Field(&i.URL, is.URL, validation.RuneLength(0, longStringLengthLimit)),
    95  		validation.Field(&i.CorrelationIDKey, validation.RuneLength(0, longStringLengthLimit)),
    96  		validation.Field(&i.Mode, validation.In(WebhookModeSync, WebhookModeAsync, WebhookModeAsyncCallback), validation.When(i.Type == WebhookTypeConfigurationChanged || i.Type == WebhookTypeApplicationTenantMapping || i.Type == WebhookTypeFormationLifecycle, validation.In(WebhookModeSync, WebhookModeAsyncCallback)).Else(validation.NotIn(WebhookModeAsyncCallback))),
    97  		validation.Field(&i.RetryInterval, validation.Min(0)),
    98  		validation.Field(&i.Timeout, validation.Min(0)),
    99  		validation.Field(&i.Auth),
   100  	); err != nil {
   101  		return err
   102  	}
   103  
   104  	if i.URL == nil && i.URLTemplate == nil {
   105  		return apperrors.NewInvalidDataError("missing webhook url")
   106  	}
   107  
   108  	if i.URL != nil && i.URLTemplate != nil {
   109  		return apperrors.NewInvalidDataError("cannot provide both webhook url and url template")
   110  	}
   111  
   112  	if i.URL != nil {
   113  		_, err := url.ParseRequestURI(*i.URL)
   114  		if err != nil {
   115  			return apperrors.NewInvalidDataError("failed to parse webhook url: %s", err)
   116  		}
   117  	}
   118  
   119  	requestObject := webhookTemplateInputByType[i.Type]
   120  	if i.URLTemplate != nil {
   121  		if requestObject == nil {
   122  			return apperrors.NewInvalidDataError("missing template input for type: %s", i.Type)
   123  		}
   124  		if _, err := requestObject.ParseURLTemplate(i.URLTemplate); err != nil {
   125  			return apperrors.NewInvalidDataError("failed to parse webhook url template: %s", err)
   126  		}
   127  	}
   128  
   129  	if i.InputTemplate != nil {
   130  		if requestObject == nil {
   131  			return apperrors.NewInvalidDataError("missing template input for type: %s", i.Type)
   132  		}
   133  		if _, err := requestObject.ParseInputTemplate(i.InputTemplate); err != nil {
   134  			return apperrors.NewInvalidDataError("failed to parse webhook input template: %s", err)
   135  		}
   136  	}
   137  
   138  	if i.HeaderTemplate != nil {
   139  		if requestObject == nil {
   140  			return apperrors.NewInvalidDataError("missing template input for type: %s", i.Type)
   141  		}
   142  		if _, err := requestObject.ParseHeadersTemplate(i.HeaderTemplate); err != nil {
   143  			return apperrors.NewInvalidDataError("failed to parse webhook headers template: %s", err)
   144  		}
   145  	}
   146  
   147  	if i.OutputTemplate == nil && isOutTemplateMandatory(i.Type) {
   148  		return apperrors.NewInvalidDataError("outputTemplate is required for type: %v", i.Type)
   149  	}
   150  
   151  	var responseObject webhook.ResponseObject
   152  	if i.OutputTemplate != nil {
   153  		if _, err := responseObject.ParseOutputTemplate(i.OutputTemplate); err != nil {
   154  			return apperrors.NewInvalidDataError("failed to parse webhook output template: %s", err)
   155  		}
   156  	}
   157  
   158  	if i.Mode != nil && *i.Mode == WebhookModeAsync {
   159  		if i.StatusTemplate != nil {
   160  			if _, err := responseObject.ParseStatusTemplate(i.StatusTemplate); err != nil {
   161  				return apperrors.NewInvalidDataError("failed to parse webhook status template: %s", err)
   162  			}
   163  		} else {
   164  			return apperrors.NewInvalidDataError("missing webhook status template")
   165  		}
   166  	}
   167  
   168  	return nil
   169  }
   170  
   171  func isOutTemplateMandatory(webhookType WebhookType) bool {
   172  	switch webhookType {
   173  	case WebhookTypeRegisterApplication,
   174  		WebhookTypeUnregisterApplication:
   175  		return true
   176  	default:
   177  		return false
   178  	}
   179  }