diff --git a/changelog.d/0-release-notes/WPB-27953-make-scim-error-responses-comply-with-rfc7644 b/changelog.d/0-release-notes/WPB-27953-make-scim-error-responses-comply-with-rfc7644 new file mode 100644 index 00000000000..d769a62ddfb --- /dev/null +++ b/changelog.d/0-release-notes/WPB-27953-make-scim-error-responses-comply-with-rfc7644 @@ -0,0 +1,22 @@ +Make SCIM error responses comply with RFC7644. Any code that processes SCIM error responses must be changed to follow the standard, instead of the previous Wire implementation. + +Previous schema (incompatible with RFC): + +``` +{ + "code": 400, + "label": "scim-error", + "message": "{\"detail\":\"[...]\",\"schemas\":[\"urn:ietf:params:scim:api:messages:2.0:Error\"],\"scimType\":\"invalidValue\",\"status\":\"400\"}" +} +``` + +New schema (RFC-compliant): + +``` +{ + "schemas": ["urn:ietf:params:scim:api:messages:2.0:Error"], + "status": "400" + "scimType": "invalidValue", + "detail": "[...]", +} +``` diff --git a/changelog.d/1-api-changes/WPB-27953-make-scim-error-responses-comply-with-rfc7644 b/changelog.d/1-api-changes/WPB-27953-make-scim-error-responses-comply-with-rfc7644 new file mode 100644 index 00000000000..f03aeea44f6 --- /dev/null +++ b/changelog.d/1-api-changes/WPB-27953-make-scim-error-responses-comply-with-rfc7644 @@ -0,0 +1 @@ +Make scim error responses comply with RFC7644. diff --git a/services/spar/spar.cabal b/services/spar/spar.cabal index 8bd4d03aac8..962388000ed 100644 --- a/services/spar/spar.cabal +++ b/services/spar/spar.cabal @@ -530,6 +530,7 @@ test-suite spec Paths_spar Test.Spar.APISpec Test.Spar.DataSpec + Test.Spar.ErrorSpec Test.Spar.Intra.BrigSpec Test.Spar.Roundtrip.ByteString Test.Spar.Saml.IdPSpec diff --git a/services/spar/src/Spar/Error.hs b/services/spar/src/Spar/Error.hs index f15cb5dfad0..a2cd61d9f73 100644 --- a/services/spar/src/Spar/Error.hs +++ b/services/spar/src/Spar/Error.hs @@ -135,7 +135,13 @@ sparToServerErrorWithLogging logger err = do pure errServant sparToServerError :: SparError -> ServerError -sparToServerError = httpErrorToServerError . renderSparError +-- SCIM errors have their own response format (RFC 7644, section 3.12): the body +-- must be the bare SCIM error object. Going through 'renderSparError' / +-- 'httpErrorToServerError' would instead nest it into a wire-server 'Wai.Error' +-- ('{"code":..,"label":"scim-error","message":}'), so we +-- render it directly here. +sparToServerError (SAML.CustomError (SparScimError err)) = Scim.scimToServerError err +sparToServerError err = httpErrorToServerError (renderSparError err) waiToServant :: Wai.Error -> ServerError waiToServant waierr = diff --git a/services/spar/test/Test/Spar/ErrorSpec.hs b/services/spar/test/Test/Spar/ErrorSpec.hs new file mode 100644 index 00000000000..1f0b697ab53 --- /dev/null +++ b/services/spar/test/Test/Spar/ErrorSpec.hs @@ -0,0 +1,55 @@ +{-# LANGUAGE OverloadedStrings #-} + +-- This file is part of the Wire Server implementation. +-- +-- Copyright (C) 2025 Wire Swiss GmbH +-- +-- This program is free software: you can redistribute it and/or modify it under +-- the terms of the GNU Affero General Public License as published by the Free +-- Software Foundation, either version 3 of the License, or (at your option) any +-- later version. +-- +-- This program is distributed in the hope that it will be useful, but WITHOUT +-- ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +-- FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +-- details. +-- +-- You should have received a copy of the GNU Affero General Public License along +-- with this program. If not, see . + +module Test.Spar.ErrorSpec where + +import Data.Aeson (eitherDecode') +import Data.Aeson.QQ (aesonQQ) +import Imports +import qualified SAML2.WebSSO as SAML +import Servant (ServerError (..)) +import Spar.Error +import Test.Hspec +import qualified Web.Scim.Schema.Error as Scim + +spec :: Spec +spec = describe "sparToServerError" $ do + -- RFC 7644 section 3.12 requires that the response body of a SCIM error *is* + -- the SCIM error object, not a wire-server 'Wai.Error' with the SCIM error + -- object nested (double-encoded) into its 'message' field. + it "renders a SCIM error as the bare RFC 7644 error object" $ do + let scimErr = + Scim.badRequest + Scim.InvalidValue + (Just "Could not process externalId.") + serverErr = sparToServerError (SAML.CustomError (SparScimError scimErr)) + eitherDecode' (errBody serverErr) + `shouldBe` Right + [aesonQQ| + { + "detail": "Could not process externalId.", + "schemas": [ + "urn:ietf:params:scim:api:messages:2.0:Error" + ], + "scimType": "invalidValue", + "status": "400" + }|] + errHTTPCode serverErr `shouldBe` 400 + lookup "Content-Type" (errHeaders serverErr) + `shouldBe` Just "application/scim+json;charset=utf-8"