From 89132a2b5735e533bd7e01687fb3e1fe6581e5c0 Mon Sep 17 00:00:00 2001 From: Matthias Fischmann Date: Tue, 11 Aug 2026 12:59:47 +0200 Subject: [PATCH 1/4] Changelog. --- .../WPB-27953-make-scim-error-responses-comply-with-rfc7644 | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/1-api-changes/WPB-27953-make-scim-error-responses-comply-with-rfc7644 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 0000000000..f03aeea44f --- /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. From 4ecf42253619bae79bf3f84701b307060eebcfa6 Mon Sep 17 00:00:00 2001 From: Matthias Fischmann Date: Tue, 11 Aug 2026 13:02:53 +0200 Subject: [PATCH 2/4] Fix scim error response bodies. --- services/spar/spar.cabal | 1 + services/spar/src/Spar/Error.hs | 8 ++++- services/spar/test/Test/Spar/ErrorSpec.hs | 44 +++++++++++++++++++++++ 3 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 services/spar/test/Test/Spar/ErrorSpec.hs diff --git a/services/spar/spar.cabal b/services/spar/spar.cabal index 8bd4d03aac..962388000e 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 f15cb5dfad..a2cd61d9f7 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 0000000000..750d223948 --- /dev/null +++ b/services/spar/test/Test/Spar/ErrorSpec.hs @@ -0,0 +1,44 @@ +{-# 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 (encode) +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)) + errBody serverErr `shouldBe` encode scimErr + errHTTPCode serverErr `shouldBe` 400 + lookup "Content-Type" (errHeaders serverErr) + `shouldBe` Just "application/scim+json;charset=utf-8" From 21cafa3bebafebd838f845baf16904f00019dceb Mon Sep 17 00:00:00 2001 From: Matthias Fischmann Date: Tue, 11 Aug 2026 13:13:14 +0200 Subject: [PATCH 3/4] More changelog noise. --- ...e-scim-error-responses-comply-with-rfc7644 | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 changelog.d/0-release-notes/WPB-27953-make-scim-error-responses-comply-with-rfc7644 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 0000000000..d769a62ddf --- /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": "[...]", +} +``` From c0b8ea0bf63d8c0066445ff4c4d587c1583a9848 Mon Sep 17 00:00:00 2001 From: Matthias Fischmann Date: Wed, 12 Aug 2026 09:28:28 +0200 Subject: [PATCH 4/4] Make unit test more interesting. --- services/spar/test/Test/Spar/ErrorSpec.hs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/services/spar/test/Test/Spar/ErrorSpec.hs b/services/spar/test/Test/Spar/ErrorSpec.hs index 750d223948..1f0b697ab5 100644 --- a/services/spar/test/Test/Spar/ErrorSpec.hs +++ b/services/spar/test/Test/Spar/ErrorSpec.hs @@ -19,7 +19,8 @@ module Test.Spar.ErrorSpec where -import Data.Aeson (encode) +import Data.Aeson (eitherDecode') +import Data.Aeson.QQ (aesonQQ) import Imports import qualified SAML2.WebSSO as SAML import Servant (ServerError (..)) @@ -38,7 +39,17 @@ spec = describe "sparToServerError" $ do Scim.InvalidValue (Just "Could not process externalId.") serverErr = sparToServerError (SAML.CustomError (SparScimError scimErr)) - errBody serverErr `shouldBe` encode 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"