diff --git a/shortcuts/base/base_errors.go b/shortcuts/base/base_errors.go index f9ca40596..2cf4e1c95 100644 --- a/shortcuts/base/base_errors.go +++ b/shortcuts/base/base_errors.go @@ -160,6 +160,9 @@ func baseAPIErrorFromResult(resultMap map[string]interface{}, cc errclass.Classi if logID := extractBaseErrorLogID(resultMap); logID != "" { resultMap["log_id"] = logID } + if err := baseResourcePermissionError(resultMap, cc, hint); err != nil { + return err + } err := errclass.BuildAPIError(resultMap, cc) if err == nil { return nil @@ -170,6 +173,33 @@ func baseAPIErrorFromResult(resultMap map[string]interface{}, cc errclass.Classi return err } +func baseResourcePermissionError(resultMap map[string]interface{}, cc errclass.ClassifyContext, upstreamHint string) error { + code, ok := util.ToFloat64(resultMap["code"]) + if !ok || int(code) != 91403 { + return nil + } + msg, _ := resultMap["msg"].(string) + if strings.TrimSpace(msg) == "" { + msg = "you don't have permission" + } + identity := strings.TrimSpace(cc.Identity) + if identity == "" { + identity = "current identity" + } + hint := fmt.Sprintf("resource access denied for %s; ask the Base owner to grant access, or retry once with --as user only if a user identity is already logged in. Do not run auth login or switch identities repeatedly for this resource-level permission error", identity) + if upstreamHint != "" { + hint = upstreamHint + "; " + hint + } + err := errs.NewPermissionError(errs.SubtypePermissionDenied, "%s", msg). + WithCode(91403). + WithHint("%s", hint) + err.Identity = identity + if logID, _ := resultMap["log_id"].(string); strings.TrimSpace(logID) != "" { + err.WithLogID(strings.TrimSpace(logID)) + } + return err +} + func enrichBaseAPIErrorFromBody(err error, body []byte, cc errclass.ClassifyContext) error { if _, ok := errs.ProblemOf(err); !ok { return err diff --git a/shortcuts/base/base_errors_test.go b/shortcuts/base/base_errors_test.go index 61d408371..877fe9627 100644 --- a/shortcuts/base/base_errors_test.go +++ b/shortcuts/base/base_errors_test.go @@ -118,6 +118,38 @@ func TestHandleBaseAPIResultClassifiesKnownPermissionCode(t *testing.T) { } } +func TestHandleBaseAPIResultClassifiesBaseResourcePermission(t *testing.T) { + result := map[string]interface{}{ + "code": 91403, + "msg": "you don't have permission", + "data": map[string]interface{}{}, + } + + _, err := handleBaseAPIResultAny(result, nil, "list dashboards") + p, ok := errs.ProblemOf(err) + if !ok { + t.Fatalf("expected typed error, got %T %v", err, err) + } + if p.Code != 91403 { + t.Fatalf("code=%d", p.Code) + } + if p.Category != errs.CategoryAuthorization || p.Subtype != errs.SubtypePermissionDenied { + t.Fatalf("category/subtype=%s/%s", p.Category, p.Subtype) + } + perm, ok := err.(*errs.PermissionError) + if !ok { + t.Fatalf("err=%T, want *errs.PermissionError", err) + } + if perm.Identity != "current identity" { + t.Fatalf("identity=%q", perm.Identity) + } + for _, want := range []string{"resource access denied", "Base owner", "Do not run auth login"} { + if !strings.Contains(p.Hint, want) { + t.Fatalf("hint=%q missing %q", p.Hint, want) + } + } +} + func TestAttachBaseResponseLogIDFromHeader(t *testing.T) { result := map[string]interface{}{ "code": 91402,