Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
1c2d9e5
Allow two teams with same IdP (entityID).
fisx Sep 8, 2021
2a3d79b
RESET_PLS
fisx Sep 8, 2021
3b229e9
Revert "RESET_PLS"
fisx Sep 8, 2021
05e269d
Tests.
fisx Sep 9, 2021
fc588eb
...
fisx Sep 9, 2021
182af83
...
fisx Sep 9, 2021
47c377e
...
fisx Sep 9, 2021
d2c0467
...
fisx Sep 9, 2021
d09283e
...
fisx Sep 9, 2021
e4c9097
RESET_PLS
fisx Sep 9, 2021
da36da6
Revert "RESET_PLS"
fisx Sep 9, 2021
c9d3a71
...
fisx Sep 9, 2021
31ff727
...
fisx Sep 9, 2021
57d06ae
Fix typo.
fisx Sep 9, 2021
f944be2
...
fisx Sep 10, 2021
188626b
RESET_PLS
fisx Sep 10, 2021
83acf32
Revert "RESET_PLS"
fisx Sep 10, 2021
edfb77d
Try out skipAPIVersions.
fisx Sep 10, 2021
a8675c9
Update stack.yaml.
fisx Sep 10, 2021
3eca40e
Fix stale comment.
fisx Sep 10, 2021
1124443
better test.
fisx Sep 10, 2021
24c783f
fix authnreq issuer computation.
fisx Sep 10, 2021
23e5e9c
fix authnreq issuer computation *test*.
fisx Sep 10, 2021
cf36000
Test multi-team idp issuer (failing).
fisx Sep 10, 2021
fe57c6c
Fix: it's ok to have two teams with the same IdP issuer/entityID...
fisx Sep 10, 2021
f0111fc
The fix is becoming more invovled...
fisx Sep 10, 2021
42865d3
...
fisx Sep 11, 2021
689caa2
...
fisx Sep 11, 2021
67cc420
...
fisx Sep 11, 2021
c230589
More debug logging.
fisx Sep 13, 2021
0c0e292
Fix: pass teamid from path to all the functions in finalize-login.
fisx Sep 13, 2021
eb1fdf6
Fix: delete now hits issuer_idp_v2.
fisx Sep 13, 2021
7203c7f
Fix tests: replacing idps.
fisx Sep 13, 2021
efafe7a
Fix: post several idps with same entityID.
fisx Sep 13, 2021
97cf8c7
Fix test case.
fisx Sep 13, 2021
a3ed0fb
Another fix.
fisx Sep 13, 2021
ad211e8
Tweak race condition mitigation in test case.
fisx Sep 13, 2021
333ff54
Tweak.
fisx Sep 13, 2021
1d15c4e
Update services/spar/test-integration/Test/Spar/APISpec.hs
fisx Sep 13, 2021
3950ce6
Update services/spar/test-integration/Test/Spar/APISpec.hs
fisx Sep 13, 2021
178528f
...
fisx Sep 13, 2021
5ede16f
...
fisx Sep 13, 2021
83f3bc5
add a changelog entry.
julialongtin Sep 13, 2021
a7bab5d
cassandra-schema docs
jschaul Sep 13, 2021
91de317
Nit-pick.
fisx Sep 13, 2021
6f91c57
Reduce code duplication.
fisx Sep 13, 2021
8cfa08b
Fix: test against sso-url from config.
fisx Sep 13, 2021
234f95a
ormolu
fisx Sep 13, 2021
b9498f4
...
fisx Sep 13, 2021
8cad1b2
...
fisx Sep 13, 2021
79488d5
More explicit error case handling.
fisx Sep 13, 2021
379c56c
...
fisx Sep 13, 2021
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
3 changes: 3 additions & 0 deletions changelog.d/2-features/pr-1755
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Support using a single IDP with a single EntityID (aka issuer ID) to set up two teams.
Sets up a migration, and makes teamID + EntityID unique, rather than relying on EntityID to be unique.
Required to support multiple teams in environments where the IDP software cannot present anything but one EntityID (E.G.: DualShield).
22 changes: 22 additions & 0 deletions docs/reference/cassandra-schema.cql
Original file line number Diff line number Diff line change
Expand Up @@ -1513,6 +1513,7 @@ CREATE TABLE spar_test.issuer_idp (

CREATE TABLE spar_test.idp (
idp uuid PRIMARY KEY,
api_version int,
extra_public_keys list<blob>,
issuer text,
old_issuers list<text>,
Expand Down Expand Up @@ -1681,6 +1682,27 @@ CREATE TABLE spar_test.team_idp (
AND read_repair_chance = 0.0
AND speculative_retry = '99PERCENTILE';

CREATE TABLE spar_test.issuer_idp_v2 (
issuer text,
team uuid,
idp uuid,
PRIMARY KEY (issuer, team)
) WITH CLUSTERING ORDER BY (team ASC)
AND bloom_filter_fp_chance = 0.1
AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'}
AND comment = ''
AND compaction = {'class': 'org.apache.cassandra.db.compaction.LeveledCompactionStrategy'}
AND compression = {'chunk_length_in_kb': '64', 'class': 'org.apache.cassandra.io.compress.LZ4Compressor'}
AND crc_check_chance = 1.0
AND dclocal_read_repair_chance = 0.1
AND default_time_to_live = 0
AND gc_grace_seconds = 864000
AND max_index_interval = 2048
AND memtable_flush_period_in_ms = 0
AND min_index_interval = 128
AND read_repair_chance = 0.0
AND speculative_retry = '99PERCENTILE';

CREATE TABLE spar_test.scim_user_times (
uid uuid PRIMARY KEY,
created_at timestamp,
Expand Down
27 changes: 22 additions & 5 deletions libs/wire-api/src/Wire/API/Routes/Public/Spar.hs
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,10 @@ type API =

type APISSO =
"metadata" :> SAML.APIMeta
:<|> "metadata" :> Capture "team" TeamId :> SAML.APIMeta
:<|> "initiate-login" :> APIAuthReqPrecheck
:<|> "initiate-login" :> APIAuthReq
:<|> APIAuthRespLegacy
:<|> APIAuthResp
:<|> "settings" :> SsoSettingsGet

Expand Down Expand Up @@ -131,8 +133,16 @@ data DoInitiate = DoInitiateLogin | DoInitiateBind

type WithSetBindCookie = Headers '[Servant.Header "Set-Cookie" SetBindCookie]

type APIAuthRespLegacy =
"finalize-login"
:> Header "Cookie" ST
-- (SAML.APIAuthResp from here on, except for response)
:> MultipartForm Mem SAML.AuthnResponseBody
:> Post '[PlainText] Void

type APIAuthResp =
"finalize-login"
:> Capture "team" TeamId
:> Header "Cookie" ST
-- (SAML.APIAuthResp from here on, except for response)
:> MultipartForm Mem SAML.AuthnResponseBody
Expand All @@ -156,6 +166,7 @@ type IdpGetAll = Get '[JSON] IdPList
type IdpCreate =
ReqBodyCustomError '[RawXML, JSON] "wai-error" IdPMetadataInfo
:> QueryParam' '[Optional, Strict] "replaces" SAML.IdPId
:> QueryParam' '[Optional, Strict] "api-version" WireIdPAPIVersion
:> PostCreated '[JSON] IdP

type IdpUpdate =
Expand All @@ -176,11 +187,17 @@ type APIINTERNAL =
:<|> "teams" :> Capture "team" TeamId :> DeleteNoContent
:<|> "sso" :> "settings" :> ReqBody '[JSON] SsoSettings :> Put '[JSON] NoContent

sparSPIssuer :: SAML.HasConfig m => m SAML.Issuer
sparSPIssuer = SAML.Issuer <$> SAML.getSsoURI (Proxy @APISSO) (Proxy @APIAuthResp)

sparResponseURI :: SAML.HasConfig m => m URI.URI
sparResponseURI = SAML.getSsoURI (Proxy @APISSO) (Proxy @APIAuthResp)
sparSPIssuer :: SAML.HasConfig m => Maybe TeamId -> m SAML.Issuer
sparSPIssuer Nothing =
SAML.Issuer <$> SAML.getSsoURI (Proxy @APISSO) (Proxy @APIAuthRespLegacy)
sparSPIssuer (Just tid) =
SAML.Issuer <$> SAML.getSsoURI' (Proxy @APISSO) (Proxy @APIAuthResp) tid

sparResponseURI :: SAML.HasConfig m => Maybe TeamId -> m URI.URI
sparResponseURI Nothing =
SAML.getSsoURI (Proxy @APISSO) (Proxy @APIAuthRespLegacy)
sparResponseURI (Just tid) =
SAML.getSsoURI' (Proxy @APISSO) (Proxy @APIAuthResp) tid

-- SCIM

Expand Down
60 changes: 60 additions & 0 deletions libs/wire-api/src/Wire/API/User/IdentityProvider.hs
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,14 @@

module Wire.API.User.IdentityProvider where

import qualified Cassandra as Cql
import Control.Lens (makeLenses, (.~), (?~))
import Control.Monad.Except
import Data.Aeson
import Data.Aeson.TH
import qualified Data.Attoparsec.ByteString as AP
import qualified Data.Binary.Builder as BSB
import qualified Data.ByteString.Conversion as BSC
import Data.HashMap.Strict.InsOrd (InsOrdHashMap)
import qualified Data.HashMap.Strict.InsOrd as InsOrdHashMap
import Data.Id (TeamId)
Expand All @@ -33,6 +37,7 @@ import SAML2.WebSSO (IdPConfig)
import qualified SAML2.WebSSO as SAML
import SAML2.WebSSO.Types.TH (deriveJSONOptions)
import Servant.API as Servant hiding (MkLink, URI (..))
import Wire.API.Arbitrary (Arbitrary, GenericUniform (GenericUniform))
import Wire.API.User.Orphans (samlSchemaOptions)

-- | The identity provider type used in Spar.
Expand All @@ -43,6 +48,7 @@ data WireIdP = WireIdP
-- | list of issuer names that this idp has replaced, most recent first. this is used
-- for finding users that are still stored under the old issuer, see
-- 'findUserWithOldIssuer', 'moveUserToNewIssuer'.
_wiApiVersion :: Maybe WireIdPAPIVersion,
_wiOldIssuers :: [SAML.Issuer],
-- | the issuer that has replaced this one. this is set iff a new issuer is created
-- with the @"replaces"@ query parameter, and it is used to decide whether users not
Expand All @@ -51,10 +57,61 @@ data WireIdP = WireIdP
}
deriving (Eq, Show, Generic)

data WireIdPAPIVersion
= -- | initial API
WireIdPAPIV1
| -- | support for different SP entityIDs per team
WireIdPAPIV2
deriving stock (Eq, Show, Enum, Bounded, Generic)
deriving (Arbitrary) via (GenericUniform WireIdPAPIVersion)

defWireIdPAPIVersion :: WireIdPAPIVersion
defWireIdPAPIVersion = WireIdPAPIV1

makeLenses ''WireIdP

deriveJSON deriveJSONOptions ''WireIdPAPIVersion
deriveJSON deriveJSONOptions ''WireIdP

instance BSC.ToByteString WireIdPAPIVersion where
builder =
BSB.fromByteString . \case
WireIdPAPIV1 -> "v1"
WireIdPAPIV2 -> "v2"

instance BSC.FromByteString WireIdPAPIVersion where
parser =
(AP.string "v1" >> pure WireIdPAPIV1)
<|> (AP.string "v2" >> pure WireIdPAPIV2)

instance FromHttpApiData WireIdPAPIVersion where
parseQueryParam txt = maybe err Right $ BSC.fromByteString' (cs txt)
where
err = Left $ "FromHttpApiData WireIdPAPIVersion: " <> txt

instance ToHttpApiData WireIdPAPIVersion where
toQueryParam = cs . BSC.toByteString'

instance ToParamSchema WireIdPAPIVersion where
toParamSchema Proxy =
mempty
{ _paramSchemaDefault = Just "v2",
_paramSchemaType = Just SwaggerString,
_paramSchemaEnum = Just (String . toQueryParam <$> [(minBound :: WireIdPAPIVersion) ..])
}

instance Cql.Cql WireIdPAPIVersion where
ctype = Cql.Tagged Cql.IntColumn

toCql WireIdPAPIV1 = Cql.CqlInt 1
toCql WireIdPAPIV2 = Cql.CqlInt 2

fromCql (Cql.CqlInt i) = case i of
1 -> return WireIdPAPIV1
2 -> return WireIdPAPIV2
n -> Left $ "Unexpected ClientCapability value: " ++ show n
fromCql _ = Left "ClientCapability value: int expected"

-- | A list of 'IdP's, returned by some endpoints. Wrapped into an object to
-- allow extensibility later on.
data IdPList = IdPList
Expand Down Expand Up @@ -104,6 +161,9 @@ instance ToJSON IdPMetadataInfo where
instance ToSchema IdPList where
declareNamedSchema = genericDeclareNamedSchema samlSchemaOptions

instance ToSchema WireIdPAPIVersion where
declareNamedSchema = genericDeclareNamedSchema samlSchemaOptions

instance ToSchema WireIdP where
declareNamedSchema = genericDeclareNamedSchema samlSchemaOptions

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import qualified Wire.API.User as User
import qualified Wire.API.User.Activation as User.Activation
import qualified Wire.API.User.Auth as User.Auth
import qualified Wire.API.User.Identity as User.Identity
import qualified Wire.API.User.IdentityProvider as User.IdentityProvider
import qualified Wire.API.User.Password as User.Password
import qualified Wire.API.User.Profile as User.Profile
import qualified Wire.API.User.Search as User.Search
Expand Down Expand Up @@ -84,7 +85,8 @@ tests =
testRoundTrip @Team.Role.Role,
testRoundTrip @User.Search.TeamUserSearchSortBy,
testRoundTrip @User.Search.TeamUserSearchSortOrder,
testRoundTrip @User.Search.RoleFilter
testRoundTrip @User.Search.RoleFilter,
testRoundTrip @User.IdentityProvider.WireIdPAPIVersion
-- FUTUREWORK:
-- testCase "Call.Config.TurnUsername (doesn't have FromByteString)" ...
-- testCase "User.Activation.ActivationTarget (doesn't have FromByteString)" ...
Expand Down
4 changes: 3 additions & 1 deletion services/spar/schema/src/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import qualified V11
import qualified V12
import qualified V13
import qualified V14
import qualified V15
import qualified V2
import qualified V3
import qualified V4
Expand Down Expand Up @@ -61,7 +62,8 @@ main = do
V11.migration,
V12.migration,
V13.migration,
V14.migration
V14.migration,
V15.migration
-- When adding migrations here, don't forget to update
-- 'schemaVersion' in Spar.Data

Expand Down
43 changes: 43 additions & 0 deletions services/spar/schema/src/V15.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
-- This file is part of the Wire Server implementation.
--
-- Copyright (C) 2020 Wire Swiss GmbH <opensource@wire.com>
--
-- 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 <https://www.gnu.org/licenses/>.

module V15
( migration,
)
where

import Cassandra.Schema
import Imports
import Text.RawString.QQ

migration :: Migration
migration = Migration 15 "Optionally index IdP by teamid (in addition to entityID); add idp api version." $ do
void $
schema'
[r|
CREATE TABLE if not exists issuer_idp_v2
( issuer text
, team uuid
, idp uuid
, PRIMARY KEY (issuer, team)
) with compaction = {'class': 'LeveledCompactionStrategy'};
|]
void $
schema'
[r|
ALTER TABLE idp ADD api_version int;
|]
3 changes: 2 additions & 1 deletion services/spar/spar.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ cabal-version: 1.12
--
-- see: https://github.com/sol/hpack
--
-- hash: 2afa3a03f475aac5d63ab87ded5843f404fee3d8506ac70390ee37dde18f22d4
-- hash: 6a8deefc6739b8a56d89eae84bd4333254d31f2b1bf1ab22b830d7d120992bbf

name: spar
version: 0.1
Expand Down Expand Up @@ -364,6 +364,7 @@ executable spar-schema
V12
V13
V14
V15
V2
V3
V4
Expand Down
Loading