(ctx context.Context, project *store.ProjectMessage, userEmail string, request *v1pb.CreateIssueRequest, labels []string)
| 432 | } |
| 433 | |
| 434 | func (s *IssueService) buildIssueMessage(ctx context.Context, project *store.ProjectMessage, userEmail string, request *v1pb.CreateIssueRequest, labels []string) (*store.IssueMessage, error) { |
| 435 | var planUID *int64 |
| 436 | var roleGrant *storepb.RoleGrant |
| 437 | var title, description string |
| 438 | |
| 439 | // Type-specific validation and preparation |
| 440 | switch request.Issue.Type { |
| 441 | case v1pb.Issue_DATABASE_EXPORT: |
| 442 | return nil, connect.NewError(connect.CodeInvalidArgument, errors.Errorf("data export issue creation is no longer supported")) |
| 443 | |
| 444 | case v1pb.Issue_ROLE_GRANT: |
| 445 | // Title is required for role grant requests. |
| 446 | if strings.TrimSpace(request.Issue.Title) == "" { |
| 447 | return nil, connect.NewError(connect.CodeInvalidArgument, errors.Errorf("issue title is required")) |
| 448 | } |
| 449 | |
| 450 | // Check if role grant workflow feature is enabled. |
| 451 | if err := s.licenseService.IsFeatureEnabled(ctx, common.GetWorkspaceIDFromContext(ctx), v1pb.PlanFeature_FEATURE_REQUEST_ROLE_WORKFLOW); err != nil { |
| 452 | return nil, connect.NewError(connect.CodePermissionDenied, |
| 453 | errors.Errorf("role request requires approval workflow feature (available in Enterprise plan)")) |
| 454 | } |
| 455 | |
| 456 | // Validate role grant fields. |
| 457 | if request.Issue.RoleGrant.GetRole() == "" { |
| 458 | return nil, connect.NewError(connect.CodeInvalidArgument, errors.Errorf("expect role grant role")) |
| 459 | } |
| 460 | if request.Issue.RoleGrant.GetUser() == "" { |
| 461 | return nil, connect.NewError(connect.CodeInvalidArgument, errors.Errorf("expect role grant user")) |
| 462 | } |
| 463 | |
| 464 | // Validate CEL expression if it's not empty. |
| 465 | if expression := request.Issue.RoleGrant.GetCondition().GetExpression(); expression != "" { |
| 466 | e, err := cel.NewEnv(common.IAMPolicyConditionCELAttributes...) |
| 467 | if err != nil { |
| 468 | return nil, connect.NewError(connect.CodeInternal, errors.Wrapf(err, "failed to create cel environment")) |
| 469 | } |
| 470 | if _, issues := e.Compile(expression); issues != nil { |
| 471 | return nil, connect.NewError(connect.CodeInvalidArgument, errors.Errorf("found issues in role grant condition expression, issues: %v", issues.String())) |
| 472 | } |
| 473 | } |
| 474 | |
| 475 | // Enforce the workspace maximum request expiration cap (project owner is |
| 476 | // exempt), mirroring SetIamPolicy. The role grant's condition carries the |
| 477 | // `request.time < timestamp(...)` clause that becomes the IAM binding, so |
| 478 | // validating it here also rejects a missing expiration when a cap is set. |
| 479 | if request.Issue.RoleGrant.GetRole() != fmt.Sprintf("roles/%s", store.ProjectOwnerRole) { |
| 480 | workspaceProfileSetting, err := s.store.GetWorkspaceProfileSetting(ctx, common.GetWorkspaceIDFromContext(ctx)) |
| 481 | if err != nil { |
| 482 | return nil, connect.NewError(connect.CodeInternal, errors.New("failed to get workspace profile setting")) |
| 483 | } |
| 484 | if maximumRequestExpiration := workspaceProfileSetting.GetMaximumRequestExpiration(); maximumRequestExpiration != nil { |
| 485 | if err := validateExpirationInExpression(request.Issue.RoleGrant.GetCondition().GetExpression(), maximumRequestExpiration); err != nil { |
| 486 | return nil, connect.NewError(connect.CodeInvalidArgument, errors.Wrapf(err, "failed to validate role expiration")) |
| 487 | } |
| 488 | } |
| 489 | } |
| 490 | |
| 491 | roleGrantUserEmail, err := common.GetUserEmail(request.Issue.RoleGrant.User) |
no test coverage detected