Skip to content

Commit 8021cc2

Browse files
mawasilegithub-actions[bot]
andauthored
Enhance error handling for 403 & 404 WebApi responses (#714)
* Enhance error handling for HTTP responses by adding support for 403 Forbidden and 404 Not Found status codes across multiple API functions * Enhance error handling for 403 and 404 WebApi responses * Fix error message formatting in DeleteDataRecord function --------- Co-authored-by: github-actions[bot] <tfmod442916@users.noreply.github.com>
1 parent 1db6816 commit 8021cc2

6 files changed

Lines changed: 226 additions & 50 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
kind: fixed
2+
body: Enhance error handling for 403 & 404 WebApi responses
3+
time: 2025-04-15T18:54:54.092381468Z
4+
custom:
5+
Issue: "714"

internal/api/client.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,20 @@ func (client *Client) Execute(ctx context.Context, scopes []string, method, url
182182
}
183183
}
184184

185+
func (client *Client) HandleNotFoundResponse(resp *Response) error {
186+
if resp.HttpResponse.StatusCode == http.StatusNotFound {
187+
return fmt.Errorf("resource not found at '%s'", resp.HttpResponse.Request.URL)
188+
}
189+
return nil
190+
}
191+
192+
func (client *Client) HandleForbiddenResponse(resp *Response) error {
193+
if resp.HttpResponse.StatusCode == http.StatusForbidden {
194+
return fmt.Errorf("access denied to resource at '%s'. Please validate your permissions", resp.HttpResponse.Request.URL)
195+
}
196+
return nil
197+
}
198+
185199
func validateNoManagementApplicationPermissionsForBapiRequest(resp *Response) error {
186200
if resp.HttpResponse.StatusCode == http.StatusForbidden && len(resp.BodyAsBytes) > 0 && strings.Contains(string(resp.BodyAsBytes), "does not have permission to access the path") {
187201
return errors.New(constants.NO_MANAGEMENT_APPLICATION_ERROR_MSG)

internal/services/authorization/api_user.go

Lines changed: 56 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,16 @@ func (client *client) GetDataverseUsers(ctx context.Context, environmentId strin
5959
Path: "/api/data/v9.2/systemusers",
6060
}
6161
userArray := userArrayDto{}
62-
_, err = client.Api.Execute(ctx, nil, "GET", apiUrl.String(), nil, nil, []int{http.StatusOK}, &userArray)
62+
resp, err := client.Api.Execute(ctx, nil, "GET", apiUrl.String(), nil, nil, []int{http.StatusOK, http.StatusForbidden, http.StatusNotFound}, &userArray)
6363
if err != nil {
6464
return nil, err
6565
}
66+
if err := client.Api.HandleForbiddenResponse(resp); err != nil {
67+
return nil, err
68+
}
69+
if err := client.Api.HandleNotFoundResponse(resp); err != nil {
70+
return nil, err
71+
}
6672
return userArray.Value, nil
6773
}
6874

@@ -81,12 +87,14 @@ func (client *client) GetDataverseUserBySystemUserId(ctx context.Context, enviro
8187
apiUrl.RawQuery = values.Encode()
8288

8389
user := userDto{}
84-
_, err = client.Api.Execute(ctx, nil, "GET", apiUrl.String(), nil, nil, []int{http.StatusOK}, &user)
90+
resp, err := client.Api.Execute(ctx, nil, "GET", apiUrl.String(), nil, nil, []int{http.StatusOK, http.StatusForbidden, http.StatusNotFound}, &user)
8591
if err != nil {
86-
var unexpectedError *customerrors.UnexpectedHttpStatusCodeError
87-
if errors.As(err, &unexpectedError) && unexpectedError.StatusCode == http.StatusNotFound {
88-
return nil, customerrors.WrapIntoProviderError(err, customerrors.ERROR_OBJECT_NOT_FOUND, fmt.Sprintf("User with systemUserId %s not found", systemUserId))
89-
}
92+
return nil, err
93+
}
94+
if err := client.Api.HandleForbiddenResponse(resp); err != nil {
95+
return nil, err
96+
}
97+
if err := client.Api.HandleNotFoundResponse(resp); err != nil {
9098
return nil, err
9199
}
92100
return &user, nil
@@ -152,13 +160,14 @@ func (client *client) GetDataverseUserByAadObjectId(ctx context.Context, environ
152160
apiUrl.RawQuery = values.Encode()
153161

154162
user := userArrayDto{}
155-
_, err = client.Api.Execute(ctx, nil, "GET", apiUrl.String(), nil, nil, []int{http.StatusOK}, &user)
163+
resp, err := client.Api.Execute(ctx, nil, "GET", apiUrl.String(), nil, nil, []int{http.StatusOK, http.StatusForbidden, http.StatusNotFound}, &user)
156164
if err != nil {
157-
var httpError *customerrors.UnexpectedHttpStatusCodeError
158-
if errors.As(err, &httpError) && httpError.StatusCode == http.StatusNotFound {
159-
return nil, customerrors.WrapIntoProviderError(err, customerrors.ERROR_OBJECT_NOT_FOUND, fmt.Sprintf("User with aadObjectId %s not found", aadObjectId))
160-
}
161-
165+
return nil, err
166+
}
167+
if err := client.Api.HandleForbiddenResponse(resp); err != nil {
168+
return nil, err
169+
}
170+
if err := client.Api.HandleNotFoundResponse(resp); err != nil {
162171
return nil, err
163172
}
164173

@@ -336,10 +345,16 @@ func (client *client) UpdateDataverseUser(ctx context.Context, environmentId, sy
336345
Path: "/api/data/v9.2/systemusers(" + systemUserId + ")",
337346
}
338347

339-
_, err = client.Api.Execute(ctx, nil, "PATCH", apiUrl.String(), nil, userUpdate, []int{http.StatusOK}, nil)
348+
resp, err := client.Api.Execute(ctx, nil, "PATCH", apiUrl.String(), nil, userUpdate, []int{http.StatusOK, http.StatusForbidden, http.StatusNotFound}, nil)
340349
if err != nil {
341350
return nil, err
342351
}
352+
if err := client.Api.HandleForbiddenResponse(resp); err != nil {
353+
return nil, err
354+
}
355+
if err := client.Api.HandleNotFoundResponse(resp); err != nil {
356+
return nil, err
357+
}
343358

344359
user, err := client.GetDataverseUserBySystemUserId(ctx, environmentId, systemUserId)
345360
if err != nil {
@@ -359,10 +374,16 @@ func (client *client) DeleteDataverseUser(ctx context.Context, environmentId, sy
359374
Path: "/api/data/v9.2/systemusers(" + systemUserId + ")",
360375
}
361376

362-
_, err = client.Api.Execute(ctx, nil, "DELETE", apiUrl.String(), nil, nil, []int{http.StatusNoContent}, nil)
377+
resp, err := client.Api.Execute(ctx, nil, "DELETE", apiUrl.String(), nil, nil, []int{http.StatusNoContent, http.StatusForbidden, http.StatusNotFound}, nil)
363378
if err != nil {
364379
return err
365380
}
381+
if err := client.Api.HandleForbiddenResponse(resp); err != nil {
382+
return err
383+
} else if err := client.Api.HandleNotFoundResponse(resp); err != nil {
384+
return err
385+
}
386+
366387
return nil
367388
}
368389

@@ -382,13 +403,19 @@ func (client *client) RemoveDataverseSecurityRoles(ctx context.Context, environm
382403
values.Add("$id", fmt.Sprintf("https://%s/api/data/v9.2/roles(%s)", environmentHost, roleId))
383404
apiUrl.RawQuery = values.Encode()
384405

385-
_, err = client.Api.Execute(ctx, nil, "DELETE", apiUrl.String(), nil, nil, []int{http.StatusNoContent}, nil)
406+
resp, err := client.Api.Execute(ctx, nil, "DELETE", apiUrl.String(), nil, nil, []int{http.StatusNoContent, http.StatusForbidden, http.StatusNotFound}, nil)
386407
if err != nil {
387408
if strings.Contains(err.Error(), "0x80060888") && strings.Contains(err.Error(), roleId) {
388409
return nil, fmt.Errorf("role with id '%s' is not valid", roleId)
389410
}
390411
return nil, err
391412
}
413+
if err := client.Api.HandleForbiddenResponse(resp); err != nil {
414+
return nil, err
415+
}
416+
if err := client.Api.HandleNotFoundResponse(resp); err != nil {
417+
return nil, err
418+
}
392419
}
393420

394421
user, err := client.GetDataverseUserBySystemUserId(ctx, environmentId, systemUserId)
@@ -413,13 +440,19 @@ func (client *client) AddDataverseSecurityRoles(ctx context.Context, environment
413440
roleToassociate := map[string]any{
414441
"@odata.id": fmt.Sprintf("https://%s/api/data/v9.2/roles(%s)", environmentHost, roleId),
415442
}
416-
_, err = client.Api.Execute(ctx, nil, "POST", apiUrl.String(), nil, roleToassociate, []int{http.StatusNoContent}, nil)
443+
resp, err := client.Api.Execute(ctx, nil, "POST", apiUrl.String(), nil, roleToassociate, []int{http.StatusNoContent, http.StatusForbidden, http.StatusNotFound}, nil)
417444
if err != nil {
418445
if strings.Contains(err.Error(), "0x80060888") && strings.Contains(err.Error(), roleId) {
419446
return nil, fmt.Errorf("role with id '%s' is not valid", roleId)
420447
}
421448
return nil, err
422449
}
450+
if err := client.Api.HandleForbiddenResponse(resp); err != nil {
451+
return nil, err
452+
}
453+
if err := client.Api.HandleNotFoundResponse(resp); err != nil {
454+
return nil, err
455+
}
423456
}
424457
user, err := client.GetDataverseUserBySystemUserId(ctx, environmentId, systemUserId)
425458
if err != nil {
@@ -484,13 +517,14 @@ func (client *client) GetDataverseSecurityRoles(ctx context.Context, environment
484517
apiUrl.RawQuery = values.Encode()
485518
}
486519
securityRoleArray := securityRoleArrayDto{}
487-
_, err = client.Api.Execute(ctx, nil, "GET", apiUrl.String(), nil, nil, []int{http.StatusOK}, &securityRoleArray)
520+
resp, err := client.Api.Execute(ctx, nil, "GET", apiUrl.String(), nil, nil, []int{http.StatusOK, http.StatusForbidden, http.StatusNotFound}, &securityRoleArray)
488521
if err != nil {
489-
var httpError *customerrors.UnexpectedHttpStatusCodeError
490-
if errors.As(err, &httpError) && httpError.StatusCode == http.StatusNotFound {
491-
tflog.Debug(ctx, fmt.Sprintf("Error getting security roles: %s", err.Error()))
492-
return nil, customerrors.WrapIntoProviderError(err, customerrors.ERROR_OBJECT_NOT_FOUND, "security roles not found")
493-
}
522+
return nil, err
523+
}
524+
if err := client.Api.HandleForbiddenResponse(resp); err != nil {
525+
return nil, err
526+
}
527+
if err := client.Api.HandleNotFoundResponse(resp); err != nil {
494528
return nil, err
495529
}
496530
return securityRoleArray.Value, nil

internal/services/data_record/api_data_record.go

Lines changed: 78 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,16 @@ func getEntityDefinition(ctx context.Context, client *client, environmentId, ent
4242
}
4343

4444
entityDefinition := entityDefinitionsDto{}
45-
_, err = client.Api.Execute(ctx, nil, "GET", entityDefinitionApiUrl.String(), nil, nil, []int{http.StatusOK}, &entityDefinition)
45+
resp, err := client.Api.Execute(ctx, nil, "GET", entityDefinitionApiUrl.String(), nil, nil, []int{http.StatusOK, http.StatusForbidden, http.StatusNotFound}, &entityDefinition)
4646
if err != nil {
4747
return nil, err
4848
}
49+
if err := client.Api.HandleForbiddenResponse(resp); err != nil {
50+
return nil, err
51+
}
52+
if err := client.Api.HandleNotFoundResponse(resp); err != nil {
53+
return nil, err
54+
}
4955

5056
return &entityDefinition, nil
5157
}
@@ -100,10 +106,16 @@ func (client *client) GetDataRecordsByODataQuery(ctx context.Context, environmen
100106
apiUrl := fmt.Sprintf("https://%s/api/data/%s/%s", environmentHost, constants.DATAVERSE_API_VERSION, query)
101107

102108
response := map[string]any{}
103-
_, err = client.Api.Execute(ctx, nil, "GET", apiUrl, h, nil, []int{http.StatusOK}, &response)
109+
resp, err := client.Api.Execute(ctx, nil, "GET", apiUrl, h, nil, []int{http.StatusOK, http.StatusForbidden, http.StatusNotFound}, &response)
104110
if err != nil {
105111
return nil, err
106112
}
113+
if err := client.Api.HandleForbiddenResponse(resp); err != nil {
114+
return nil, err
115+
}
116+
if err := client.Api.HandleNotFoundResponse(resp); err != nil {
117+
return nil, err
118+
}
107119

108120
var totalRecords *int64
109121
if response["@Microsoft.Dynamics.CRM.totalrecordcount"] != nil {
@@ -175,12 +187,14 @@ func (client *client) GetDataRecord(ctx context.Context, recordId, environmentId
175187

176188
result := make(map[string]any, 0)
177189

178-
_, err = client.Api.Execute(ctx, nil, "GET", apiUrl.String(), nil, nil, []int{http.StatusOK, http.StatusNotFound}, &result)
190+
resp, err := client.Api.Execute(ctx, nil, "GET", apiUrl.String(), nil, nil, []int{http.StatusOK, http.StatusNotFound, http.StatusForbidden}, &result)
179191
if err != nil {
180-
var httpError *customerrors.UnexpectedHttpStatusCodeError
181-
if errors.As(err, &httpError) && httpError.StatusCode == http.StatusNotFound {
182-
return nil, customerrors.WrapIntoProviderError(err, customerrors.ERROR_OBJECT_NOT_FOUND, fmt.Sprintf("Data Record '%s' not found", recordId))
183-
}
192+
return nil, err
193+
}
194+
if err := client.Api.HandleForbiddenResponse(resp); err != nil {
195+
return nil, err
196+
}
197+
if err := client.Api.HandleNotFoundResponse(resp); err != nil {
184198
return nil, err
185199
}
186200

@@ -207,10 +221,16 @@ func (client *client) GetRelationData(ctx context.Context, environmentId, tableN
207221

208222
result := make(map[string]any, 0)
209223

210-
_, err = client.Api.Execute(ctx, nil, "GET", apiUrl.String(), nil, nil, []int{http.StatusOK}, &result)
224+
resp, err := client.Api.Execute(ctx, nil, "GET", apiUrl.String(), nil, nil, []int{http.StatusOK, http.StatusForbidden, http.StatusNotFound}, &result)
211225
if err != nil {
212226
return nil, err
213227
}
228+
if err := client.Api.HandleForbiddenResponse(resp); err != nil {
229+
return nil, err
230+
}
231+
if err := client.Api.HandleNotFoundResponse(resp); err != nil {
232+
return nil, err
233+
}
214234

215235
field, ok := result["value"]
216236
if !ok {
@@ -241,10 +261,16 @@ func (client *client) GetTableSingularNameFromPlural(ctx context.Context, enviro
241261
q.Add("$select", "PrimaryIdAttribute,LogicalCollectionName,LogicalName")
242262
apiUrl.RawQuery = q.Encode()
243263

244-
response, err := client.Api.Execute(ctx, nil, "GET", apiUrl.String(), nil, nil, []int{http.StatusOK}, nil)
264+
response, err := client.Api.Execute(ctx, nil, "GET", apiUrl.String(), nil, nil, []int{http.StatusOK, http.StatusForbidden, http.StatusNotFound}, nil)
245265
if err != nil {
246266
return nil, err
247267
}
268+
if err := client.Api.HandleForbiddenResponse(response); err != nil {
269+
return nil, err
270+
}
271+
if err := client.Api.HandleNotFoundResponse(response); err != nil {
272+
return nil, err
273+
}
248274

249275
var mapResponse map[string]any
250276
err = json.Unmarshal(response.BodyAsBytes, &mapResponse)
@@ -366,10 +392,16 @@ func (client *client) GetEntityAttributesDefinition(ctx context.Context, environ
366392
apiUrl := fmt.Sprintf("https://%s/api/data/%s/EntityDefinitions(LogicalName='%s')/Attributes?$select=LogicalName", environmentHost, constants.DATAVERSE_API_VERSION, entityLogicalName)
367393

368394
results := attributesApiResponseDto{}
369-
_, err = client.Api.Execute(ctx, nil, "GET", apiUrl, nil, nil, []int{http.StatusOK}, &results)
395+
resp, err := client.Api.Execute(ctx, nil, "GET", apiUrl, nil, nil, []int{http.StatusOK, http.StatusForbidden, http.StatusNotFound}, &results)
370396
if err != nil {
371397
return nil, err
372398
}
399+
if err := client.Api.HandleForbiddenResponse(resp); err != nil {
400+
return nil, err
401+
}
402+
if err := client.Api.HandleNotFoundResponse(resp); err != nil {
403+
return nil, err
404+
}
373405
return results.Value, nil
374406
}
375407

@@ -381,10 +413,16 @@ func (client *client) GetEntityRelationDefinitionInfo(ctx context.Context, envir
381413

382414
apiUrl := fmt.Sprintf("https://%s/api/data/%s/EntityDefinitions(LogicalName='%s')?$expand=OneToManyRelationships,ManyToManyRelationships,ManyToOneRelationships", environmentHost, constants.DATAVERSE_API_VERSION, entityLogicalName)
383415

384-
response, err := client.Api.Execute(ctx, nil, "GET", apiUrl, nil, nil, []int{http.StatusOK}, nil)
416+
response, err := client.Api.Execute(ctx, nil, "GET", apiUrl, nil, nil, []int{http.StatusOK, http.StatusForbidden, http.StatusNotFound}, nil)
385417
if err != nil {
386418
return "", err
387419
}
420+
if err := client.Api.HandleForbiddenResponse(response); err != nil {
421+
return "", err
422+
}
423+
if err := client.Api.HandleNotFoundResponse(response); err != nil {
424+
return "", err
425+
}
388426

389427
var mapResponse map[string]any
390428
err = json.Unmarshal(response.BodyAsBytes, &mapResponse)
@@ -473,7 +511,7 @@ func (client *client) ApplyDataRecord(ctx context.Context, recordId, environment
473511
Path: apiPath,
474512
}
475513

476-
response, err := client.Api.Execute(ctx, nil, method, apiUrl.String(), nil, columns, []int{http.StatusOK, http.StatusNoContent, http.StatusPreconditionFailed}, nil)
514+
response, err := client.Api.Execute(ctx, nil, method, apiUrl.String(), nil, columns, []int{http.StatusOK, http.StatusNoContent, http.StatusPreconditionFailed, http.StatusForbidden, http.StatusNotFound}, nil)
477515
if err != nil {
478516
return nil, err
479517
}
@@ -488,6 +526,12 @@ func (client *client) ApplyDataRecord(ctx context.Context, recordId, environment
488526
return nil, err
489527
}
490528
}
529+
if err := client.Api.HandleForbiddenResponse(response); err != nil {
530+
return nil, err
531+
}
532+
if err := client.Api.HandleNotFoundResponse(response); err != nil {
533+
return nil, err
534+
}
491535

492536
if len(response.BodyAsBytes) != 0 {
493537
err = json.Unmarshal(response.BodyAsBytes, &result)
@@ -548,9 +592,12 @@ func (client *client) DeleteDataRecord(ctx context.Context, recordId string, env
548592
Host: environmentHost,
549593
Path: fmt.Sprintf("/api/data/%s/%s(%s)/%s(%s)/$ref", constants.DATAVERSE_API_VERSION, tableEntityDefinition.LogicalCollectionName, recordId, key, dataRecordId),
550594
}
551-
_, err = client.Api.Execute(ctx, nil, "DELETE", apiUrl.String(), nil, nil, []int{http.StatusOK, http.StatusNoContent, http.StatusNotFound}, nil)
595+
resp, err := client.Api.Execute(ctx, nil, "DELETE", apiUrl.String(), nil, nil, []int{http.StatusOK, http.StatusNoContent, http.StatusNotFound, http.StatusForbidden}, nil)
552596
if err != nil {
553-
return errors.New("error while deleting data record. %w")
597+
return fmt.Errorf("error while deleting data record: %w", err)
598+
}
599+
if err := client.Api.HandleForbiddenResponse(resp); err != nil {
600+
return err
554601
}
555602
}
556603
}
@@ -563,9 +610,11 @@ func (client *client) DeleteDataRecord(ctx context.Context, recordId string, env
563610
}
564611

565612
// 200, 201, or 404 are acceptable status codes for delete and not error
566-
_, err = client.Api.Execute(ctx, nil, "DELETE", apiUrl.String(), nil, columns, []int{http.StatusOK, http.StatusNoContent, http.StatusNotFound}, nil)
613+
resp, err := client.Api.Execute(ctx, nil, "DELETE", apiUrl.String(), nil, columns, []int{http.StatusOK, http.StatusNoContent, http.StatusNotFound, http.StatusForbidden}, nil)
567614
if err != nil {
568615
return err
616+
} else if err := client.Api.HandleForbiddenResponse(resp); err != nil {
617+
return err
569618
}
570619
return nil
571620
}
@@ -608,10 +657,16 @@ func applyRelation(ctx context.Context, environmentHost string, entityDefinition
608657

609658
existingRelationsResponse := relationApiResponseDto{}
610659

611-
apiResponse, err := client.Api.Execute(ctx, nil, "GET", apiUrl.String(), nil, nil, []int{http.StatusOK, http.StatusNoContent}, nil)
660+
apiResponse, err := client.Api.Execute(ctx, nil, "GET", apiUrl.String(), nil, nil, []int{http.StatusOK, http.StatusNoContent, http.StatusForbidden, http.StatusNotFound}, nil)
612661
if err != nil {
613662
return err
614663
}
664+
if err := client.Api.HandleForbiddenResponse(apiResponse); err != nil {
665+
return err
666+
}
667+
if err := client.Api.HandleNotFoundResponse(apiResponse); err != nil {
668+
return err
669+
}
615670

616671
// TODO: execute will unmarshal the response into the existingRelationsResponse use that instead
617672
err = json.Unmarshal(apiResponse.BodyAsBytes, &existingRelationsResponse)
@@ -674,10 +729,16 @@ func applyRelation(ctx context.Context, environmentHost string, entityDefinition
674729
relation := relationApiBodyDto{
675730
OdataID: fmt.Sprintf("https://%s/api/data/%s/%s(%s)", environmentHost, constants.DATAVERSE_API_VERSION, entityDefinition.LogicalCollectionName, dataRecordId),
676731
}
677-
_, err = client.Api.Execute(ctx, nil, "POST", apiUrl.String(), nil, relation, []int{http.StatusOK, http.StatusNoContent}, nil)
732+
resp, err := client.Api.Execute(ctx, nil, "POST", apiUrl.String(), nil, relation, []int{http.StatusOK, http.StatusNoContent, http.StatusForbidden, http.StatusNotFound}, nil)
678733
if err != nil {
679734
return err
680735
}
736+
if err := client.Api.HandleForbiddenResponse(resp); err != nil {
737+
return err
738+
}
739+
if err := client.Api.HandleNotFoundResponse(resp); err != nil {
740+
return err
741+
}
681742
}
682743
return nil
683744
}

0 commit comments

Comments
 (0)