github.com/kyma-incubator/compass/components/director@v0.0.0-20230623144113-d764f56ff805/internal/formationmapping/auth_middleware.go (about) 1 package formationmapping 2 3 import ( 4 "context" 5 "encoding/json" 6 "net/http" 7 8 tenantpkg "github.com/kyma-incubator/compass/components/director/pkg/tenant" 9 10 "github.com/kyma-incubator/compass/components/director/pkg/correlation" 11 12 "github.com/kyma-incubator/compass/components/director/pkg/graphql" 13 14 "github.com/kyma-incubator/compass/components/director/internal/domain/formationassignment" 15 webhookclient "github.com/kyma-incubator/compass/components/director/pkg/webhook_client" 16 17 "github.com/gorilla/mux" 18 "github.com/kyma-incubator/compass/components/director/internal/domain/tenant" 19 "github.com/kyma-incubator/compass/components/director/internal/model" 20 "github.com/kyma-incubator/compass/components/director/pkg/consumer" 21 "github.com/kyma-incubator/compass/components/director/pkg/httputils" 22 "github.com/kyma-incubator/compass/components/director/pkg/log" 23 "github.com/kyma-incubator/compass/components/director/pkg/persistence" 24 "github.com/pkg/errors" 25 ) 26 27 const ( 28 // FormationIDParam is formation URL path parameter placeholder 29 FormationIDParam = "ucl-formation-id" 30 // FormationAssignmentIDParam is formation assignment URL path parameter placeholder 31 FormationAssignmentIDParam = "ucl-assignment-id" 32 ) 33 34 // FormationAssignmentService is responsible for the service-layer FormationAssignment operations 35 // 36 //go:generate mockery --name=FormationAssignmentService --output=automock --outpkg=automock --case=underscore --disable-version-string 37 type FormationAssignmentService interface { 38 GetGlobalByIDAndFormationID(ctx context.Context, formationAssignmentID, formationID string) (*model.FormationAssignment, error) 39 GetReverseBySourceAndTarget(ctx context.Context, formationID, sourceID, targetID string) (*model.FormationAssignment, error) 40 ProcessFormationAssignmentPair(ctx context.Context, mappingPair *formationassignment.AssignmentMappingPairWithOperation) (bool, error) 41 Delete(ctx context.Context, id string) error 42 ListFormationAssignmentsForObjectID(ctx context.Context, formationID, objectID string) ([]*model.FormationAssignment, error) 43 } 44 45 //go:generate mockery --exported --name=formationAssignmentStatusService --output=automock --outpkg=automock --case=underscore --disable-version-string 46 type formationAssignmentStatusService interface { 47 UpdateWithConstraints(ctx context.Context, fa *model.FormationAssignment, operation model.FormationOperation) error 48 SetAssignmentToErrorStateWithConstraints(ctx context.Context, assignment *model.FormationAssignment, errorMessage string, errorCode formationassignment.AssignmentErrorCode, state model.FormationAssignmentState, operation model.FormationOperation) error 49 DeleteWithConstraints(ctx context.Context, id string) error 50 } 51 52 // FormationAssignmentNotificationService represents the formation assignment notification service for generating notifications 53 // 54 //go:generate mockery --name=FormationAssignmentNotificationService --output=automock --outpkg=automock --case=underscore --disable-version-string 55 type FormationAssignmentNotificationService interface { 56 GenerateFormationAssignmentNotification(ctx context.Context, formationAssignment *model.FormationAssignment, operation model.FormationOperation) (*webhookclient.FormationAssignmentNotificationRequest, error) 57 } 58 59 // formationService is responsible for the service-layer Formation operations 60 // 61 //go:generate mockery --exported --name=formationService --output=automock --outpkg=automock --case=underscore --disable-version-string 62 type formationService interface { 63 UnassignFormation(ctx context.Context, tnt, objectID string, objectType graphql.FormationObjectType, formation model.Formation) (*model.Formation, error) 64 Get(ctx context.Context, id string) (*model.Formation, error) 65 GetGlobalByID(ctx context.Context, id string) (*model.Formation, error) 66 ResynchronizeFormationNotifications(ctx context.Context, formationID string) (*model.Formation, error) 67 } 68 69 //go:generate mockery --exported --name=formationStatusService --output=automock --outpkg=automock --case=underscore --disable-version-string 70 type formationStatusService interface { 71 UpdateWithConstraints(ctx context.Context, formation *model.Formation, operation model.FormationOperation) error 72 SetFormationToErrorStateWithConstraints(ctx context.Context, formation *model.Formation, errorMessage string, errorCode formationassignment.AssignmentErrorCode, state model.FormationState, operation model.FormationOperation) error 73 DeleteFormationEntityAndScenariosWithConstraints(ctx context.Context, tnt string, formation *model.Formation) error 74 } 75 76 // RuntimeRepository is responsible for the repo-layer runtime operations 77 // 78 //go:generate mockery --name=RuntimeRepository --output=automock --outpkg=automock --case=underscore --disable-version-string 79 type RuntimeRepository interface { 80 OwnerExists(ctx context.Context, tenant, id string) (bool, error) 81 } 82 83 // RuntimeContextRepository is responsible for the repo-layer runtime context operations 84 // 85 //go:generate mockery --name=RuntimeContextRepository --output=automock --outpkg=automock --case=underscore --disable-version-string 86 type RuntimeContextRepository interface { 87 GetByID(ctx context.Context, tenant, id string) (*model.RuntimeContext, error) 88 } 89 90 // ApplicationRepository is responsible for the repo-layer application operations 91 // 92 //go:generate mockery --name=ApplicationRepository --output=automock --outpkg=automock --case=underscore --disable-version-string 93 type ApplicationRepository interface { 94 GetByID(ctx context.Context, tenant, id string) (*model.Application, error) 95 OwnerExists(ctx context.Context, tenant, id string) (bool, error) 96 } 97 98 // TenantRepository is responsible for the repo-layer tenant operations 99 // 100 //go:generate mockery --name=TenantRepository --output=automock --outpkg=automock --case=underscore --disable-version-string 101 type TenantRepository interface { 102 Get(ctx context.Context, id string) (*model.BusinessTenantMapping, error) 103 } 104 105 // ApplicationTemplateRepository is responsible for the repo-layer application template operations 106 // 107 //go:generate mockery --name=ApplicationTemplateRepository --output=automock --outpkg=automock --case=underscore --disable-version-string 108 type ApplicationTemplateRepository interface { 109 Exists(ctx context.Context, id string) (bool, error) 110 } 111 112 // LabelRepository is responsible for the repo-layer label operations 113 // 114 //go:generate mockery --name=LabelRepository --output=automock --outpkg=automock --case=underscore --disable-version-string 115 type LabelRepository interface { 116 ListForGlobalObject(ctx context.Context, objectType model.LabelableObject, objectID string) (map[string]*model.Label, error) 117 } 118 119 // FormationRepository is responsible for the repo-layer formation operations 120 // 121 //go:generate mockery --name=FormationRepository --output=automock --outpkg=automock --case=underscore --disable-version-string 122 type FormationRepository interface { 123 GetGlobalByID(ctx context.Context, id string) (*model.Formation, error) 124 } 125 126 // FormationTemplateRepository is responsible for the repo-layer formation template operations 127 // 128 //go:generate mockery --name=FormationTemplateRepository --output=automock --outpkg=automock --case=underscore --disable-version-string 129 type FormationTemplateRepository interface { 130 Get(ctx context.Context, id string) (*model.FormationTemplate, error) 131 } 132 133 // ErrorResponse structure used for the JSON encoded response 134 type ErrorResponse struct { 135 Message string `json:"error"` 136 } 137 138 // Authenticator struct containing all dependencies to verify the request authenticity 139 type Authenticator struct { 140 transact persistence.Transactioner 141 faService FormationAssignmentService 142 runtimeRepo RuntimeRepository 143 runtimeContextRepo RuntimeContextRepository 144 appRepo ApplicationRepository 145 appTemplateRepo ApplicationTemplateRepository 146 labelRepo LabelRepository 147 formationRepo FormationRepository 148 formationTemplateRepo FormationTemplateRepository 149 tenantRepo TenantRepository 150 consumerSubaccountLabelKey string 151 } 152 153 // NewFormationMappingAuthenticator creates a new Authenticator 154 func NewFormationMappingAuthenticator( 155 transact persistence.Transactioner, 156 faService FormationAssignmentService, 157 runtimeRepo RuntimeRepository, 158 runtimeContextRepo RuntimeContextRepository, 159 appRepo ApplicationRepository, 160 appTemplateRepo ApplicationTemplateRepository, 161 labelRepo LabelRepository, 162 formationRepo FormationRepository, 163 formationTemplateRepo FormationTemplateRepository, 164 tenantRepo TenantRepository, 165 consumerSubaccountLabelKey string, 166 ) *Authenticator { 167 return &Authenticator{ 168 transact: transact, 169 faService: faService, 170 runtimeRepo: runtimeRepo, 171 runtimeContextRepo: runtimeContextRepo, 172 appRepo: appRepo, 173 appTemplateRepo: appTemplateRepo, 174 labelRepo: labelRepo, 175 formationRepo: formationRepo, 176 formationTemplateRepo: formationTemplateRepo, 177 tenantRepo: tenantRepo, 178 consumerSubaccountLabelKey: consumerSubaccountLabelKey, 179 } 180 } 181 182 // FormationAssignmentHandler is a handler middleware that executes authorization check for the formation assignments requests reporting status 183 func (a *Authenticator) FormationAssignmentHandler() func(next http.Handler) http.Handler { 184 return func(next http.Handler) http.Handler { 185 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 186 ctx := r.Context() 187 correlationID := correlation.CorrelationIDFromContext(ctx) 188 189 if r.Method != http.MethodPatch { 190 w.WriteHeader(http.StatusMethodNotAllowed) 191 return 192 } 193 194 routeVars := mux.Vars(r) 195 formationID := routeVars[FormationIDParam] 196 formationAssignmentID := routeVars[FormationAssignmentIDParam] 197 198 if formationID == "" || formationAssignmentID == "" { 199 log.C(ctx).Errorf("Missing required parameters: %q or/and %q", FormationIDParam, FormationAssignmentIDParam) 200 respondWithError(ctx, w, http.StatusBadRequest, errors.New("Not all of the required parameters are provided")) 201 return 202 } 203 204 isAuthorized, statusCode, err := a.isFormationAssignmentAuthorized(ctx, formationAssignmentID, formationID) 205 if err != nil { 206 log.C(ctx).Error(err.Error()) 207 respondWithError(ctx, w, statusCode, errors.Errorf("An unexpected error occurred while processing the request. X-Request-Id: %s", correlationID)) 208 return 209 } 210 211 if !isAuthorized { 212 httputils.Respond(w, http.StatusUnauthorized) 213 return 214 } 215 216 next.ServeHTTP(w, r.WithContext(ctx)) 217 }) 218 } 219 } 220 221 // FormationHandler is a handler middleware that executes authorization check for the formation requests reporting status 222 func (a *Authenticator) FormationHandler() func(next http.Handler) http.Handler { 223 return func(next http.Handler) http.Handler { 224 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 225 ctx := r.Context() 226 correlationID := correlation.CorrelationIDFromContext(ctx) 227 228 if r.Method != http.MethodPatch { 229 w.WriteHeader(http.StatusMethodNotAllowed) 230 return 231 } 232 233 routeVars := mux.Vars(r) 234 formationID := routeVars[FormationIDParam] 235 236 if formationID == "" { 237 log.C(ctx).Errorf("Missing required parameters: %q", FormationIDParam) 238 respondWithError(ctx, w, http.StatusBadRequest, errors.New("Not all of the required parameters are provided")) 239 return 240 } 241 242 isAuthorized, statusCode, err := a.isFormationAuthorized(ctx, formationID) 243 if err != nil { 244 log.C(ctx).Error(err.Error()) 245 respondWithError(ctx, w, statusCode, errors.Errorf("An unexpected error occurred while processing the request. X-Request-Id: %s", correlationID)) 246 return 247 } 248 249 if !isAuthorized { 250 httputils.Respond(w, http.StatusUnauthorized) 251 return 252 } 253 254 next.ServeHTTP(w, r.WithContext(ctx)) 255 }) 256 } 257 } 258 259 func (a *Authenticator) isFormationAuthorized(ctx context.Context, formationID string) (bool, int, error) { 260 consumerInfo, err := consumer.LoadFromContext(ctx) 261 if err != nil { 262 return false, http.StatusInternalServerError, errors.Wrap(err, "while fetching consumer info from context") 263 } 264 consumerID := consumerInfo.ConsumerID 265 consumerType := consumerInfo.ConsumerType 266 log.C(ctx).Infof("Consumer with ID: %q and type: %q is trying to update formation with ID: %q", consumerID, consumerType, formationID) 267 268 tx, err := a.transact.Begin() 269 if err != nil { 270 return false, http.StatusInternalServerError, errors.Wrap(err, "Unable to establish connection with database") 271 } 272 defer a.transact.RollbackUnlessCommitted(ctx, tx) 273 ctx = persistence.SaveToContext(ctx, tx) 274 275 f, err := a.formationRepo.GetGlobalByID(ctx, formationID) 276 if err != nil { 277 return false, http.StatusInternalServerError, errors.Wrapf(err, "while getting formation with ID: %q globally", formationID) 278 } 279 280 ft, err := a.formationTemplateRepo.Get(ctx, f.FormationTemplateID) 281 if err != nil { 282 return false, http.StatusInternalServerError, errors.Wrapf(err, "while getting formation template with ID: %q", f.FormationTemplateID) 283 } 284 285 if err = tx.Commit(); err != nil { 286 log.C(ctx).Errorf("An error occurred while closing database transaction: %s", err.Error()) 287 return false, http.StatusInternalServerError, errors.Wrap(err, "unable to finalize database operation") 288 } 289 290 for _, id := range ft.LeadingProductIDs { 291 if id == consumerID { 292 log.C(ctx).Infof("Consumer with ID: %q is contained in the leading product IDs list from formation template with ID: %q and name: %q", consumerID, ft.ID, ft.Name) 293 return true, http.StatusOK, nil 294 } 295 } 296 297 log.C(ctx).Infof("Consumer with ID: %q did not match any of the leading product IDs from formation template with ID: %q and name: %q", consumerID, ft.ID, ft.Name) 298 return false, http.StatusUnauthorized, nil 299 } 300 301 // isFormationAssignmentAuthorized verify through custom logic the caller is authorized to update the formation assignment status 302 func (a *Authenticator) isFormationAssignmentAuthorized(ctx context.Context, formationAssignmentID, formationID string) (bool, int, error) { 303 consumerInfo, err := consumer.LoadFromContext(ctx) 304 if err != nil { 305 return false, http.StatusInternalServerError, errors.Wrap(err, "while fetching consumer info from context") 306 } 307 consumerID := consumerInfo.ConsumerID 308 consumerType := consumerInfo.ConsumerType 309 310 tx, err := a.transact.Begin() 311 if err != nil { 312 return false, http.StatusInternalServerError, errors.Wrap(err, "Unable to establish connection with database") 313 } 314 defer a.transact.RollbackUnlessCommitted(ctx, tx) 315 ctx = persistence.SaveToContext(ctx, tx) 316 317 fa, err := a.faService.GetGlobalByIDAndFormationID(ctx, formationAssignmentID, formationID) 318 if err != nil { 319 return false, http.StatusInternalServerError, err 320 } 321 322 if fa.TargetType == model.FormationAssignmentTypeApplication { 323 tnt, err := a.tenantRepo.Get(ctx, fa.TenantID) 324 if err != nil { 325 return false, http.StatusInternalServerError, errors.Wrapf(err, "while getting tenant with ID: %q", fa.TenantID) 326 } 327 328 if consumerType == consumer.BusinessIntegration && tnt.Type == tenantpkg.ResourceGroup { 329 if err := tx.Commit(); err != nil { 330 return false, http.StatusInternalServerError, errors.Wrap(err, "while closing database transaction") 331 } 332 333 log.C(ctx).Infof("The caller with ID: %s and type: %s is allowed to update formation assignments in tenants of type %s", consumerID, consumerType, tnt.Type) 334 return true, http.StatusOK, nil 335 } 336 337 app, err := a.appRepo.GetByID(ctx, fa.TenantID, fa.Target) 338 if err != nil { 339 return false, http.StatusInternalServerError, errors.Wrapf(err, "while getting application with ID: %q", fa.Target) 340 } 341 log.C(ctx).Infof("Successfully got application with ID: %q", fa.Target) 342 343 // If the consumer is integration system validate the formation assignment type is application that can be managed by the integration system caller 344 if consumerType == consumer.IntegrationSystem && app.IntegrationSystemID != nil && *app.IntegrationSystemID == consumerID { 345 if err := tx.Commit(); err != nil { 346 return false, http.StatusInternalServerError, errors.Wrap(err, "while closing database transaction") 347 } 348 349 log.C(ctx).Infof("The caller with ID: %q and type: %q manages the target of the formation assignment with ID: %q and type: %q that is being updated", consumerID, consumerType, fa.Target, fa.TargetType) 350 return true, http.StatusOK, nil 351 } 352 353 if app.ApplicationTemplateID != nil && *app.ApplicationTemplateID == consumerID { 354 if err := tx.Commit(); err != nil { 355 return false, http.StatusInternalServerError, errors.Wrap(err, "while closing database transaction") 356 } 357 358 log.C(ctx).Infof("The caller with ID: %q and type: %q is the parent of the target of the formation assignment with ID: %q and type: %q that is being updated", consumerID, consumerType, fa.Target, fa.TargetType) 359 return true, http.StatusOK, nil 360 } 361 362 consumerTenantPair, err := tenant.LoadTenantPairFromContext(ctx) 363 if err != nil { 364 return false, http.StatusInternalServerError, errors.Wrap(err, "while loading tenant pair from context") 365 } 366 consumerInternalTenantID := consumerTenantPair.InternalID 367 consumerExternalTenantID := consumerTenantPair.ExternalID 368 369 log.C(ctx).Infof("Tenant with internal ID: %q and external ID: %q for consumer with type: %q is trying to update formation assignment with ID: %q for formation with ID: %q about source: %q and source type: %q, and target: %q and target type: %q", consumerInternalTenantID, consumerExternalTenantID, consumerType, fa.ID, fa.FormationID, fa.Source, fa.SourceType, fa.Target, fa.TargetType) 370 371 // Verify if the caller has owner access to the target of the formation assignment with type application that is being updated 372 exists, err := a.appRepo.OwnerExists(ctx, consumerInternalTenantID, fa.Target) 373 if err != nil { 374 return false, http.StatusInternalServerError, errors.Wrapf(err, "an error occurred while verifying caller with internal tenant ID: %q has owner access to the target of the formation assignment with ID: %q and type: %q that is being updated", consumerInternalTenantID, fa.Target, fa.TargetType) 375 } 376 377 if exists { 378 if err := tx.Commit(); err != nil { 379 log.C(ctx).Errorf("An error occurred while closing database transaction: %s", err.Error()) 380 return false, http.StatusInternalServerError, errors.Wrap(err, "Unable to finalize database operation") 381 } 382 383 log.C(ctx).Infof("The caller with internal tenant ID: %q has owner access to the target of the formation assignment with ID: %q and type: %q that is being updated", consumerInternalTenantID, fa.Target, fa.TargetType) 384 return true, http.StatusOK, nil 385 } 386 log.C(ctx).Warningf("The caller with internal tenant ID: %q has NOT direct owner access to the target of the formation assignment with ID: %q and type: %q that is being updated. Checking if the application is made through subscription...", consumerInternalTenantID, fa.Target, fa.TargetType) 387 388 // Validate if the application is registered through subscription and the caller has owner access to the application template of that application 389 return a.validateSubscriptionProvider(ctx, tx, app.ApplicationTemplateID, consumerExternalTenantID, fa.Target, string(fa.TargetType)) 390 } 391 392 consumerTenantPair, err := tenant.LoadTenantPairFromContext(ctx) 393 if err != nil { 394 return false, http.StatusInternalServerError, errors.Wrap(err, "while loading tenant pair from context") 395 } 396 consumerInternalTenantID := consumerTenantPair.InternalID 397 398 log.C(ctx).Infof("Tenant with internal ID: %q and external ID: %q for consumer with type: %q is trying to update formation assignment with ID: %q for formation with ID: %q about source: %q and source type: %q, and target: %q and target type: %q", consumerInternalTenantID, consumerTenantPair.ExternalID, consumerType, fa.ID, fa.FormationID, fa.Source, fa.SourceType, fa.Target, fa.TargetType) 399 if fa.TargetType == model.FormationAssignmentTypeRuntime { 400 exists, err := a.runtimeRepo.OwnerExists(ctx, consumerInternalTenantID, fa.Target) 401 if err != nil { 402 return false, http.StatusUnauthorized, errors.Wrapf(err, "while verifying caller with internal tenant ID: %q has owner access to the target of the formation assignment with ID: %q and type: %q that is being updated", consumerInternalTenantID, fa.Target, fa.TargetType) 403 } 404 405 if exists { 406 if err := tx.Commit(); err != nil { 407 log.C(ctx).Errorf("An error occurred while closing database transaction: %s", err.Error()) 408 return false, http.StatusInternalServerError, errors.Wrap(err, "Unable to finalize database operation") 409 } 410 411 log.C(ctx).Infof("The caller with internal tenant ID: %q has owner access to the target of the formation assignment with ID: %q and type: %q that is being updated", consumerInternalTenantID, fa.Target, fa.TargetType) 412 return true, http.StatusOK, nil 413 } 414 415 return false, http.StatusUnauthorized, nil 416 } 417 418 if fa.TargetType == model.FormationAssignmentTypeRuntimeContext { 419 rtmCtx, err := a.runtimeContextRepo.GetByID(ctx, fa.TenantID, fa.Target) 420 if err != nil { 421 return false, http.StatusInternalServerError, errors.Wrapf(err, "while getting runtime context with ID: %q", fa.Target) 422 } 423 424 exists, err := a.runtimeRepo.OwnerExists(ctx, consumerInternalTenantID, rtmCtx.RuntimeID) 425 if err != nil { 426 return false, http.StatusUnauthorized, errors.Wrapf(err, "while verifying caller with internal tenant ID: %q has owner access to the target's parent of the formation assignment with ID: %q and type: %q that is being updated", consumerInternalTenantID, fa.Target, fa.TargetType) 427 } 428 429 if exists { 430 if err := tx.Commit(); err != nil { 431 log.C(ctx).Errorf("An error occurred while closing database transaction: %s", err.Error()) 432 return false, http.StatusInternalServerError, errors.Wrap(err, "Unable to finalize database operation") 433 } 434 435 log.C(ctx).Infof("The caller with internal tenant ID: %q has owner access to the target's parent of the formation assignment with ID: %q and type: %q that is being updated", consumerInternalTenantID, fa.Target, fa.TargetType) 436 return true, http.StatusOK, nil 437 } 438 439 return false, http.StatusUnauthorized, nil 440 } 441 442 if err := tx.Commit(); err != nil { 443 log.C(ctx).Errorf("An error occurred while closing database transaction: %s", err.Error()) 444 return false, http.StatusInternalServerError, errors.Wrap(err, "Unable to finalize database operation") 445 } 446 447 return false, http.StatusUnauthorized, nil 448 } 449 450 // validateSubscriptionProvider validates if the application is registered through subscription and the caller has owner access to the application template 451 func (a *Authenticator) validateSubscriptionProvider(ctx context.Context, tx persistence.PersistenceTx, appTemplateID *string, consumerExternalTenantID, faTarget, faTargetType string) (bool, int, error) { 452 if appTemplateID == nil || (appTemplateID != nil && *appTemplateID == "") { 453 log.C(ctx).Warning("Application template ID should not be nil or empty") 454 return false, http.StatusUnauthorized, nil 455 } 456 457 appTemplateExists, err := a.appTemplateRepo.Exists(ctx, *appTemplateID) 458 if err != nil { 459 return false, http.StatusUnauthorized, errors.Wrapf(err, "while checking application template existence for ID: %q", *appTemplateID) 460 } 461 462 if !appTemplateExists { 463 return false, http.StatusUnauthorized, errors.Wrapf(err, "application template with ID: %q doesn't exist", *appTemplateID) 464 } 465 466 labels, err := a.labelRepo.ListForGlobalObject(ctx, model.AppTemplateLabelableObject, *appTemplateID) 467 if err != nil { 468 return false, http.StatusInternalServerError, errors.Wrapf(err, "while getting labels for application template with ID: %q", *appTemplateID) 469 } 470 471 consumerSubaccountLbl, consumerSubaccountLblExists := labels[a.consumerSubaccountLabelKey] 472 473 if !consumerSubaccountLblExists { 474 return false, http.StatusUnauthorized, errors.Errorf("%q label should exist as part of the provider's application template", a.consumerSubaccountLabelKey) 475 } 476 477 consumerSubaccountLblValue, ok := consumerSubaccountLbl.Value.(string) 478 if !ok { 479 return false, http.StatusUnauthorized, errors.Errorf("unexpected type of %q label, expect: string, got: %T", a.consumerSubaccountLabelKey, consumerSubaccountLbl.Value) 480 } 481 482 if consumerExternalTenantID == consumerSubaccountLblValue { 483 if err := tx.Commit(); err != nil { 484 log.C(ctx).Errorf("An error occurred while closing database transaction: %s", err.Error()) 485 return false, http.StatusInternalServerError, errors.Wrap(err, "Unable to finalize database operation") 486 } 487 488 log.C(ctx).Infof("The caller with external ID: %q has owner access to the target's parent of the formation assignment with ID: %q and type: %q that is being updated", consumerExternalTenantID, faTarget, faTargetType) 489 return true, http.StatusOK, nil 490 } 491 492 return false, http.StatusUnauthorized, nil 493 } 494 495 // respondWithError writes a http response using with the JSON encoded error wrapped in an ErrorResponse struct 496 func respondWithError(ctx context.Context, w http.ResponseWriter, status int, err error) { 497 log.C(ctx).Errorf("Responding with error: %v", err) 498 w.Header().Add(httputils.HeaderContentTypeKey, httputils.ContentTypeApplicationJSON) 499 w.WriteHeader(status) 500 errorResponse := ErrorResponse{Message: err.Error()} 501 encodingErr := json.NewEncoder(w).Encode(errorResponse) 502 if encodingErr != nil { 503 log.C(ctx).WithError(err).Errorf("Failed to encode error response: %v", err) 504 } 505 }