Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion platform-api/internal/handler/api_key.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ func (h *APIKeyHandler) UpdateAPIKey(w http.ResponseWriter, r *http.Request) {
}

// Validate that the name in the request body (if provided) matches the URL path parameter
if req.Name != nil && *req.Name != "" && *req.Name != keyName {
if err := utils.ValidateHandleImmutable(keyName, req.Name); err != nil {
h.slogger.Warn("API key name mismatch", "userId", userId, "orgId", orgId, "apiHandle", apiHandle, "urlKeyName", keyName, "bodyKeyName", *req.Name)
httputil.WriteJSON(w, http.StatusBadRequest, utils.NewErrorResponse(400, "Bad Request",
fmt.Sprintf("API key name mismatch: name in request body '%s' must match the key name in URL '%s'", *req.Name, keyName)))
Expand Down
2 changes: 1 addition & 1 deletion platform-api/internal/handler/gateway.go
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ func (h *GatewayHandler) UpdateGateway(w http.ResponseWriter, r *http.Request) {
return
}

if req.Id != nil && *req.Id != gatewayId {
if err := utils.ValidateHandleImmutable(gatewayId, req.Id); err != nil {
httputil.WriteJSON(w, http.StatusBadRequest, utils.NewErrorResponse(400, "Bad Request",
"Gateway id is immutable and cannot be changed"))
return
Expand Down
18 changes: 18 additions & 0 deletions platform-api/internal/handler/llm.go
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,12 @@ func (h *LLMHandler) UpdateLLMProviderTemplate(w http.ResponseWriter, r *http.Re
return
}

if err := utils.ValidateHandleImmutable(id, req.Id); err != nil {
httputil.WriteJSON(w, http.StatusBadRequest, utils.NewErrorResponse(400, "Bad Request",
"LLM provider template id is immutable and cannot be changed"))
return
}

updatedBy, ok := resolveActor(w, r, h.identity, h.slogger, "update LLM provider template")
if !ok {
return
Expand Down Expand Up @@ -564,6 +570,12 @@ func (h *LLMHandler) UpdateLLMProvider(w http.ResponseWriter, r *http.Request) {
return
}

if err := utils.ValidateHandleImmutable(id, req.Id); err != nil {
httputil.WriteJSON(w, http.StatusBadRequest, utils.NewErrorResponse(400, "Bad Request",
"LLM provider id is immutable and cannot be changed"))
return
}

updatedBy, ok := resolveActor(w, r, h.identity, h.slogger, "update LLM provider")
if !ok {
return
Expand Down Expand Up @@ -815,6 +827,12 @@ func (h *LLMHandler) UpdateLLMProxy(w http.ResponseWriter, r *http.Request) {
return
}

if err := utils.ValidateHandleImmutable(id, req.Id); err != nil {
httputil.WriteJSON(w, http.StatusBadRequest, utils.NewErrorResponse(400, "Bad Request",
"LLM proxy id is immutable and cannot be changed"))
return
}

updatedBy, ok := resolveActor(w, r, h.identity, h.slogger, "update LLM proxy")
if !ok {
return
Expand Down
6 changes: 6 additions & 0 deletions platform-api/internal/handler/mcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,12 @@ func (h *MCPProxyHandler) UpdateMCPProxy(w http.ResponseWriter, r *http.Request)
return
}

if err := utils.ValidateHandleImmutable(id, req.Id); err != nil {
httputil.WriteJSON(w, http.StatusBadRequest, utils.NewErrorResponse(400, "Bad Request",
"MCP proxy id is immutable and cannot be changed"))
return
}

updatedBy, ok := resolveActor(w, r, h.identity, h.slogger, "update MCP proxy")
if !ok {
return
Expand Down
10 changes: 10 additions & 0 deletions platform-api/internal/handler/project.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,16 @@ func (h *ProjectHandler) UpdateProject(w http.ResponseWriter, r *http.Request) {
return
}

var reqId string
if req.Id != nil {
reqId = *req.Id
}
if err := utils.ValidateHandleImmutableRequired(projectId, reqId); err != nil {
httputil.WriteJSON(w, http.StatusBadRequest, utils.NewErrorResponse(400, "Bad Request",
"Project id is immutable and cannot be changed"))
return
}

actor, ok := resolveActor(w, r, h.identity, h.slogger, "update project")
if !ok {
return
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,7 @@ func (h *SubscriptionPlanHandler) UpdateSubscriptionPlan(w http.ResponseWriter,
return
}

if req.Id != nil && *req.Id != "" && *req.Id != planId {
if err := utils.ValidateHandleImmutable(planId, req.Id); err != nil {
httputil.WriteJSON(w, http.StatusBadRequest, utils.NewErrorResponse(400, "Bad Request",
"The plan id is immutable and cannot be changed"))
return
Expand Down
6 changes: 4 additions & 2 deletions platform-api/internal/service/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -419,8 +419,10 @@ func (s *APIService) DeleteAPI(apiUUID, orgUUID, deletedBy string) error {
// UpdateAPIByHandle updates an existing API identified by handle
func (s *APIService) UpdateAPIByHandle(handle string, req *api.RESTAPI, orgId, updatedBy string) (*api.RESTAPI, error) {
// The id (handle) is immutable: a body id must match the API being updated.
if req != nil && req.Id != nil && *req.Id != "" && *req.Id != handle {
return nil, constants.ErrHandleImmutable
if req != nil {
if err := utils.ValidateHandleImmutable(handle, req.Id); err != nil {
return nil, err
}
}
apiUUID, err := s.getAPIUUIDByHandle(handle, orgId)
if err != nil {
Expand Down
4 changes: 2 additions & 2 deletions platform-api/internal/service/application.go
Original file line number Diff line number Diff line change
Expand Up @@ -269,8 +269,8 @@ func (s *ApplicationService) UpdateApplication(appIDOrHandle string, req *api.Ap
}

// The id (handle) is immutable: body id must be present and match the application being updated.
if req.Id == "" || req.Id != app.Handle {
return nil, constants.ErrHandleImmutable
if err := utils.ValidateHandleImmutableRequired(app.Handle, req.Id); err != nil {
return nil, err
}

name := strings.TrimSpace(req.DisplayName)
Expand Down
9 changes: 0 additions & 9 deletions platform-api/internal/service/llm.go
Original file line number Diff line number Diff line change
Expand Up @@ -325,9 +325,6 @@ func (s *LLMProviderTemplateService) Update(orgUUID, handle, updatedBy string, r
if handle == "" || req == nil {
return nil, constants.ErrInvalidInput
}
if req.Id != nil && *req.Id != "" && *req.Id != handle {
return nil, constants.ErrHandleImmutable
}
if req.DisplayName == "" {
return nil, constants.ErrInvalidInput
}
Expand Down Expand Up @@ -1030,9 +1027,6 @@ func (s *LLMProviderService) Update(orgUUID, handle, updatedBy string, req *api.
if handle == "" || req == nil {
return nil, constants.ErrInvalidInput
}
if req.Id != nil && *req.Id != "" && *req.Id != handle {
return nil, constants.ErrHandleImmutable
}
// Fetch existing provider to preserve sensitive fields on update
existing, err := s.repo.GetByID(handle, orgUUID)
if err != nil {
Expand Down Expand Up @@ -1482,9 +1476,6 @@ func (s *LLMProxyService) Update(orgUUID, handle, updatedBy string, req *api.LLM
if handle == "" || req == nil {
return nil, constants.ErrInvalidInput
}
if req.Id != nil && *req.Id != "" && *req.Id != handle {
return nil, constants.ErrHandleImmutable
}
if req.DisplayName == "" || req.Version == "" || req.Provider.Id == "" {
return nil, constants.ErrInvalidInput
}
Expand Down
13 changes: 0 additions & 13 deletions platform-api/internal/service/llm_provider_template_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -378,19 +378,6 @@ func TestLLMProviderTemplateServiceUpdate_PropagatesNameToFamily(t *testing.T) {
}
}

func TestLLMProviderTemplateServiceUpdate_RejectsMismatchedID(t *testing.T) {
repo := &mockLLMProviderTemplateCRUDRepo{managedByForHandleResult: "customer"}
svc := NewLLMProviderTemplateService(repo, &noopAuditRepo{}, newTestIdentityService())

req := validTemplateRequest("Name")
otherHandle := "some-other-handle"
req.Id = &otherHandle
_, err := svc.Update("org-1", "mistralai", "alice", req)
if !errors.Is(err, constants.ErrHandleImmutable) {
t.Fatalf("expected ErrHandleImmutable, got: %v", err)
}
}

// ---- CreateVersion ----

func TestLLMProviderTemplateServiceCreateVersion_Success(t *testing.T) {
Expand Down
3 changes: 0 additions & 3 deletions platform-api/internal/service/mcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -334,9 +334,6 @@ func (s *MCPProxyService) Update(orgUUID, handle, updatedBy string, req *api.MCP
if handle == "" || req == nil {
return nil, constants.ErrInvalidInput
}
if req.Id != nil && *req.Id != "" && *req.Id != handle {
return nil, constants.ErrHandleImmutable
}
if req.DisplayName == "" || req.Version == "" {
return nil, constants.ErrInvalidInput
}
Expand Down
5 changes: 0 additions & 5 deletions platform-api/internal/service/project.go
Original file line number Diff line number Diff line change
Expand Up @@ -207,11 +207,6 @@ func (s *ProjectService) UpdateProject(handle string, req *api.Project, orgId, a
return nil, constants.ErrProjectNotFound
}

// Validate that the handle in the body matches the path param (immutability check)
if req.Id == nil || *req.Id != handle {
return nil, constants.ErrHandleImmutable
}

if req.DisplayName != project.Name {
existingProjects, err := s.projectRepo.GetProjectsByOrganizationID(project.OrganizationID)
if err != nil {
Expand Down
4 changes: 0 additions & 4 deletions platform-api/internal/service/subscription_plan_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,10 +165,6 @@ func (s *SubscriptionPlanService) UpdatePlan(handle, orgUUID, actor string, upda
return nil, constants.ErrSubscriptionPlanNotFound
}

// The handle (id) is immutable; reject any attempt to change it to a different value.
if update.Handle != nil && *update.Handle != "" && *update.Handle != existing.Handle {
return nil, constants.ErrHandleImmutable
}
if update.Name != nil {
if *update.Name == "" {
return nil, fmt.Errorf("name is required")
Expand Down
19 changes: 19 additions & 0 deletions platform-api/internal/utils/handle.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,25 @@ var (
multipleHyphensRegex = regexp.MustCompile(`-+`)
)

// ValidateHandleImmutable checks a PUT request body's optional handle/id field against
// the resource's path handle. A nil or empty body value is treated as "not provided"
// and allowed; only a non-empty value that differs from the path handle is rejected.
func ValidateHandleImmutable(pathHandle string, bodyHandle *string) error {
Comment thread
Thushani-Jayasekera marked this conversation as resolved.
if bodyHandle == nil || *bodyHandle == "" {
return nil
}
return ValidateHandleImmutableRequired(pathHandle, *bodyHandle)
}

// ValidateHandleImmutableRequired is like ValidateHandleImmutable but requires the
// request body to explicitly include a handle/id value matching the path handle.
func ValidateHandleImmutableRequired(pathHandle, bodyHandle string) error {
if bodyHandle == "" || bodyHandle != pathHandle {
return constants.ErrHandleImmutable
}
return nil
}

// ValidateHandle validates a user-provided handle.
// Handle must be:
// - Lowercase only
Expand Down
6 changes: 6 additions & 0 deletions platform-api/plugins/eventgateway/handler/webbroker_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,12 @@ func (h *WebBrokerAPIHandler) UpdateWebBrokerAPI(w http.ResponseWriter, r *http.
return
}

if err := utils.ValidateHandleImmutable(id, req.Id); err != nil {
httputil.WriteJSON(w, http.StatusBadRequest, utils.NewErrorResponse(400, "Bad Request",
"WebBroker API id is immutable and cannot be changed"))
return
}

updatedBy, ok := resolveActor(w, r, h.identity, h.slogger, "update WebBroker API")
if !ok {
return
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ func (h *WebBrokerAPIKeyHandler) UpdateAPIKey(w http.ResponseWriter, r *http.Req
}

// Validate that the name in the request body (if provided) matches the URL path parameter
if req.Name != nil && *req.Name != "" && *req.Name != keyName {
if err := utils.ValidateHandleImmutable(keyName, req.Name); err != nil {
h.slogger.Warn("API key name mismatch", "orgId", orgID, "apiHandle", apiHandle, "urlKeyName", keyName, "bodyKeyName", *req.Name)
httputil.WriteJSON(w, http.StatusBadRequest, utils.NewErrorResponse(400, "Bad Request",
fmt.Sprintf("API key name mismatch: name in request body '%s' must match the key name in URL '%s'", *req.Name, keyName)))
Expand Down
6 changes: 6 additions & 0 deletions platform-api/plugins/eventgateway/handler/websub_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,12 @@ func (h *WebSubAPIHandler) UpdateWebSubAPI(w http.ResponseWriter, r *http.Reques
return
}

if err := utils.ValidateHandleImmutable(id, req.Id); err != nil {
httputil.WriteJSON(w, http.StatusBadRequest, utils.NewErrorResponse(400, "Bad Request",
"WebSub API id is immutable and cannot be changed"))
return
}

updatedBy, ok := resolveActor(w, r, h.identity, h.slogger, "update WebSub API")
if !ok {
return
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ func (h *WebSubAPIKeyHandler) UpdateAPIKey(w http.ResponseWriter, r *http.Reques
}

// Validate that the name in the request body (if provided) matches the URL path parameter
if req.Name != nil && *req.Name != "" && *req.Name != keyName {
if err := utils.ValidateHandleImmutable(keyName, req.Name); err != nil {
h.slogger.Warn("API key name mismatch", "orgId", orgID, "apiHandle", apiHandle, "urlKeyName", keyName, "bodyKeyName", *req.Name)
httputil.WriteJSON(w, http.StatusBadRequest, utils.NewErrorResponse(400, "Bad Request",
fmt.Sprintf("API key name mismatch: name in request body '%s' must match the key name in URL '%s'", *req.Name, keyName)))
Expand Down
3 changes: 0 additions & 3 deletions platform-api/plugins/eventgateway/service/webbroker_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -263,9 +263,6 @@ func (s *WebBrokerAPIService) Update(orgUUID, handle, updatedBy string, req *api
if handle == "" || req == nil {
return nil, constants.ErrInvalidInput
}
if req.Id != nil && *req.Id != "" && *req.Id != handle {
return nil, constants.ErrHandleImmutable
}
if req.DisplayName == "" || req.Version == "" {
return nil, constants.ErrInvalidInput
}
Expand Down
3 changes: 0 additions & 3 deletions platform-api/plugins/eventgateway/service/websub_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -262,9 +262,6 @@ func (s *WebSubAPIService) Update(orgUUID, handle, updatedBy string, req *api.We
if handle == "" || req == nil {
return nil, constants.ErrInvalidInput
}
if req.Id != nil && *req.Id != "" && *req.Id != handle {
return nil, constants.ErrHandleImmutable
}
if req.DisplayName == "" || req.Version == "" {
return nil, constants.ErrInvalidInput
}
Expand Down
Loading