diff --git a/cassandra-schema.cql b/cassandra-schema.cql index b2cf569a01..292d52425f 100644 --- a/cassandra-schema.cql +++ b/cassandra-schema.cql @@ -137,6 +137,9 @@ CREATE TABLE galley_test.team_features ( mls_allowed_ciphersuites set, mls_default_ciphersuite int, mls_default_protocol int, + mls_e2eid_lock_status int, + mls_e2eid_status int, + mls_e2eid_ver_exp timestamp, mls_protocol_toggle_users set, mls_status int, outlook_cal_integration_lock_status int, diff --git a/changelog.d/2-features/pr-3082 b/changelog.d/2-features/pr-3082 new file mode 100644 index 0000000000..e48847a440 --- /dev/null +++ b/changelog.d/2-features/pr-3082 @@ -0,0 +1 @@ +Team feature setting for MLS end-to-end identity was added and server setting `setEnableMls` is exposed via new authorized endpoint `GET /system/settings` diff --git a/charts/galley/templates/configmap.yaml b/charts/galley/templates/configmap.yaml index 2e783e5bdf..4cdca97b5c 100644 --- a/charts/galley/templates/configmap.yaml +++ b/charts/galley/templates/configmap.yaml @@ -119,5 +119,9 @@ data: outlookCalIntegration: {{- toYaml .settings.featureFlags.outlookCalIntegration | nindent 10 }} {{- end }} + {{- if .settings.featureFlags.mlsE2EId }} + mlsE2EId: + {{- toYaml .settings.featureFlags.mlsE2EId | nindent 10 }} + {{- end }} {{- end }} {{- end }} diff --git a/charts/galley/values.yaml b/charts/galley/values.yaml index 35723a03c8..c54d90292b 100644 --- a/charts/galley/values.yaml +++ b/charts/galley/values.yaml @@ -88,6 +88,12 @@ config: defaults: status: disabled lockStatus: locked + mlsE2EId: + defaults: + status: disabled + config: + verificationExpiration: null + lockStatus: unlocked aws: region: "eu-west-1" diff --git a/docs/src/developer/reference/config-options.md b/docs/src/developer/reference/config-options.md index 37068b2ad5..687aba4776 100644 --- a/docs/src/developer/reference/config-options.md +++ b/docs/src/developer/reference/config-options.md @@ -335,6 +335,21 @@ mls: This default configuration can be overriden on a per-team basis through the [feature config API](../developer/features.md) +### MLS End-to-End Identity + +The MLS end-to-end identity team feature adds an extra level of security and practicability. If turned on, automatic device authentication ensures that team members know they are communicating with people using authenticated devices. Team members get a certificate on all their devices. + +A timer can be set to configure until when team members need to get the verification certificate. When the timer goes off, they will be logged out and get the certificate automatically on their devices. The timer is set as a unix timestamp (number of seconds that have passed since 00:00:00 UTC on Thursday, 1 January 1970) after which the period for clients to verify their identity expires. + +```yaml +# galley.yaml +mlsE2EId: + defaults: + status: disabled + config: + verificationExpiration: 1676377048 + lockStatus: unlocked +``` ### Federation Domain diff --git a/docs/src/understand/team-feature-settings.md b/docs/src/understand/team-feature-settings.md index 21733829f9..1d65c36d33 100644 --- a/docs/src/understand/team-feature-settings.md +++ b/docs/src/understand/team-feature-settings.md @@ -83,3 +83,26 @@ brig: # ... setNonceTtlSecs: 360 # 6 minutes ``` + +## MLS End-to-End Identity + +The MLS end-to-end identity team feature adds an extra level of security and practicability. If turned on, automatic device authentication ensures that team members know they are communicating with people using authenticated devices. Team members get a certificate on all their devices. + +A timer can be set to configure until when team members need to get the verification certificate. When the timer goes off, they will be logged out and get the certificate automatically on their devices. The timer is set as a unix timestamp (number of seconds that have passed since 00:00:00 UTC on Thursday, 1 January 1970) after which the period for clients to verify their identity expires. + +```yaml +galley: + # ... + config: + # ... + settings: + # ... + featureFlags: + # ... + mlsE2EId: + defaults: + status: disabled + config: + verificationExpiration: 1676377048 + lockStatus: unlocked +``` diff --git a/libs/galley-types/src/Galley/Types/Teams.hs b/libs/galley-types/src/Galley/Types/Teams.hs index 89c52fcdba..96d410f5ab 100644 --- a/libs/galley-types/src/Galley/Types/Teams.hs +++ b/libs/galley-types/src/Galley/Types/Teams.hs @@ -40,6 +40,7 @@ module Galley.Types.Teams flagTeamFeatureSearchVisibilityInbound, flagOutlookCalIntegration, flagMLS, + flagMlsE2EId, Defaults (..), ImplicitLockStatus (..), unImplicitLockStatus, @@ -152,7 +153,8 @@ data FeatureFlags = FeatureFlags _flagTeamFeatureSndFactorPasswordChallengeStatus :: !(Defaults (WithStatus SndFactorPasswordChallengeConfig)), _flagTeamFeatureSearchVisibilityInbound :: !(Defaults (ImplicitLockStatus SearchVisibilityInboundConfig)), _flagMLS :: !(Defaults (ImplicitLockStatus MLSConfig)), - _flagOutlookCalIntegration :: !(Defaults (WithStatus OutlookCalIntegrationConfig)) + _flagOutlookCalIntegration :: !(Defaults (WithStatus OutlookCalIntegrationConfig)), + _flagMlsE2EId :: !(Defaults (WithStatus MlsE2EIdConfig)) } deriving (Eq, Show, Generic) @@ -203,6 +205,7 @@ instance FromJSON FeatureFlags where <*> withImplicitLockStatusOrDefault obj "searchVisibilityInbound" <*> withImplicitLockStatusOrDefault obj "mls" <*> (fromMaybe (Defaults (defFeatureStatus @OutlookCalIntegrationConfig)) <$> (obj .:? "outlookCalIntegration")) + <*> (fromMaybe (Defaults (defFeatureStatus @MlsE2EIdConfig)) <$> (obj .:? "mlsE2EId")) where withImplicitLockStatusOrDefault :: forall cfg. (IsFeatureConfig cfg, Schema.ToSchema cfg) => Object -> Key -> A.Parser (Defaults (ImplicitLockStatus cfg)) withImplicitLockStatusOrDefault obj fieldName = fromMaybe (Defaults (ImplicitLockStatus (defFeatureStatus @cfg))) <$> obj .:? fieldName @@ -224,6 +227,7 @@ instance ToJSON FeatureFlags where searchVisibilityInbound mls outlookCalIntegration + mlsE2EId ) = object [ "sso" .= sso, @@ -239,7 +243,8 @@ instance ToJSON FeatureFlags where "sndFactorPasswordChallenge" .= sndFactorPasswordChallenge, "searchVisibilityInbound" .= searchVisibilityInbound, "mls" .= mls, - "outlookCalIntegration" .= outlookCalIntegration + "outlookCalIntegration" .= outlookCalIntegration, + "mlsE2EId" .= mlsE2EId ] instance FromJSON FeatureSSO where diff --git a/libs/galley-types/test/unit/Test/Galley/Types.hs b/libs/galley-types/test/unit/Test/Galley/Types.hs index b66e4ded3d..756ccebc2a 100644 --- a/libs/galley-types/test/unit/Test/Galley/Types.hs +++ b/libs/galley-types/test/unit/Test/Galley/Types.hs @@ -97,6 +97,7 @@ instance Arbitrary FeatureFlags where <*> fmap (fmap unlocked) arbitrary <*> fmap (fmap unlocked) arbitrary <*> arbitrary + <*> arbitrary where unlocked :: ImplicitLockStatus a -> ImplicitLockStatus a unlocked = ImplicitLockStatus . Public.setLockStatus Public.LockStatusUnlocked . _unImplicitLockStatus diff --git a/libs/wire-api/src/Wire/API/Routes/Internal/Galley.hs b/libs/wire-api/src/Wire/API/Routes/Internal/Galley.hs index 20008dac42..f7e533d53b 100644 --- a/libs/wire-api/src/Wire/API/Routes/Internal/Galley.hs +++ b/libs/wire-api/src/Wire/API/Routes/Internal/Galley.hs @@ -147,6 +147,11 @@ type IFeatureAPI = :<|> IFeatureStatusPut '[] '() OutlookCalIntegrationConfig :<|> IFeatureStatusPatch '[] '() OutlookCalIntegrationConfig :<|> IFeatureStatusLockStatusPut OutlookCalIntegrationConfig + -- MlsE2EIdConfig + :<|> IFeatureStatusGet MlsE2EIdConfig + :<|> IFeatureStatusPut '[] '() MlsE2EIdConfig + :<|> IFeatureStatusPatch '[] '() MlsE2EIdConfig + :<|> IFeatureStatusLockStatusPut MlsE2EIdConfig -- all feature configs :<|> Named "feature-configs-internal" diff --git a/libs/wire-api/src/Wire/API/Routes/Public/Brig.hs b/libs/wire-api/src/Wire/API/Routes/Public/Brig.hs index 430b20c002..365c82621a 100644 --- a/libs/wire-api/src/Wire/API/Routes/Public/Brig.hs +++ b/libs/wire-api/src/Wire/API/Routes/Public/Brig.hs @@ -1486,11 +1486,19 @@ type TeamsAPI = type SystemSettingsAPI = Named - "get-system-settings" + "get-system-settings-unauthorized" ( Summary "Returns a curated set of system configuration settings." :> From 'V3 :> "system" :> "settings" :> "unauthorized" - :> Get '[JSON] SystemSettings + :> Get '[JSON] SystemSettingsPublic ) + :<|> Named + "get-system-settings" + ( Summary "Returns a curated set of system configuration settings for authorized users." + :> ZUser + :> "system" + :> "settings" + :> Get '[JSON] SystemSettings + ) diff --git a/libs/wire-api/src/Wire/API/Routes/Public/Galley/Feature.hs b/libs/wire-api/src/Wire/API/Routes/Public/Galley/Feature.hs index a07a09fdfb..370a2a3d0f 100644 --- a/libs/wire-api/src/Wire/API/Routes/Public/Galley/Feature.hs +++ b/libs/wire-api/src/Wire/API/Routes/Public/Galley/Feature.hs @@ -87,6 +87,8 @@ type FeatureAPI = :<|> FeatureStatusPut '[] '() SearchVisibilityInboundConfig :<|> FeatureStatusGet OutlookCalIntegrationConfig :<|> FeatureStatusPut '[] '() OutlookCalIntegrationConfig + :<|> FeatureStatusGet MlsE2EIdConfig + :<|> FeatureStatusPut '[] '() MlsE2EIdConfig :<|> AllFeatureConfigsUserGet :<|> AllFeatureConfigsTeamGet :<|> FeatureConfigDeprecatedGet "The usage of this endpoint was removed in iOS in version 3.101. It is not used by team management, or webapp, and is potentially used by the old Android client as of June 2022" LegalholdConfig diff --git a/libs/wire-api/src/Wire/API/SystemSettings.hs b/libs/wire-api/src/Wire/API/SystemSettings.hs index 179470f23f..dbd973a80a 100644 --- a/libs/wire-api/src/Wire/API/SystemSettings.hs +++ b/libs/wire-api/src/Wire/API/SystemSettings.hs @@ -1,10 +1,27 @@ +-- This file is part of the Wire Server implementation. +-- +-- Copyright (C) 2022 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 Wire.API.SystemSettings where import Control.Lens hiding ((.=)) import qualified Data.Aeson as A import Data.Schema as Schema import qualified Data.Swagger as S -import Imports hiding (head) +import Imports import Servant.Swagger.Internal.Orphans () import Test.QuickCheck import Wire.Arbitrary @@ -13,19 +30,49 @@ import Wire.Arbitrary -- -- Used to expose settings via the @/system/settings/unauthorized@ endpoint. -- ALWAYS CHECK WITH SECURITY IF YOU WANT TO ADD SETTINGS HERE. +data SystemSettingsPublic = SystemSettingsPublic + { sspSetRestrictUserCreation :: !Bool + } + deriving (Eq, Show, Generic) + deriving (A.ToJSON, A.FromJSON, S.ToSchema) via Schema SystemSettingsPublic + deriving (Arbitrary) via (GenericUniform SystemSettingsPublic) + +instance ToSchema SystemSettingsPublic where + schema = + object "SystemSettingsPublic" $ settingsPublicObjectSchema + +settingsPublicObjectSchema :: ObjectSchema SwaggerDoc SystemSettingsPublic +settingsPublicObjectSchema = + SystemSettingsPublic + <$> sspSetRestrictUserCreation .= fieldWithDocModifier "setRestrictUserCreation" (description ?~ "Do not allow certain user creation flows") schema + +data SystemSettingsInternal = SystemSettingsInternal + { ssiSetEnableMls :: !Bool + } + deriving (Eq, Show, Generic) + deriving (A.ToJSON, A.FromJSON, S.ToSchema) via Schema SystemSettingsInternal + deriving (Arbitrary) via (GenericUniform SystemSettingsInternal) + +instance ToSchema SystemSettingsInternal where + schema = + object "SystemSettingsInternal" $ settingsInternalObjectSchema + +settingsInternalObjectSchema :: ObjectSchema SwaggerDoc SystemSettingsInternal +settingsInternalObjectSchema = + SystemSettingsInternal + <$> ssiSetEnableMls .= fieldWithDocModifier "setEnableMls" (description ?~ "Whether MLS is enabled or not") schema + data SystemSettings = SystemSettings - { systemSettingsSetRestrictUserCreation :: !Bool + { ssPublic :: !SystemSettingsPublic, + ssInternal :: !SystemSettingsInternal } deriving (Eq, Show, Generic) - deriving (A.ToJSON, A.FromJSON, S.ToSchema) via Schema.Schema SystemSettings + deriving (A.ToJSON, A.FromJSON, S.ToSchema) via Schema SystemSettings deriving (Arbitrary) via (GenericUniform SystemSettings) -instance Schema.ToSchema SystemSettings where +instance ToSchema SystemSettings where schema = - Schema.object "SystemSettings" $ + object "SystemSettings" $ SystemSettings - <$> systemSettingsSetRestrictUserCreation - Schema..= Schema.fieldWithDocModifier - "setRestrictUserCreation" - (description ?~ "Do not allow certain user creation flows") - Schema.schema + <$> ssPublic .= settingsPublicObjectSchema + <*> ssInternal .= settingsInternalObjectSchema diff --git a/libs/wire-api/src/Wire/API/Team/Feature.hs b/libs/wire-api/src/Wire/API/Team/Feature.hs index 1b5179e05e..5aa29a23a9 100644 --- a/libs/wire-api/src/Wire/API/Team/Feature.hs +++ b/libs/wire-api/src/Wire/API/Team/Feature.hs @@ -35,6 +35,8 @@ module Wire.API.Team.Feature setStatus, setLockStatus, setConfig, + setConfig', + setTTL, setWsTTL, WithStatusPatch, wsPatch, @@ -76,6 +78,7 @@ module Wire.API.Team.Feature FileSharingConfig (..), MLSConfig (..), OutlookCalIntegrationConfig (..), + MlsE2EIdConfig (..), AllFeatureConfigs (..), unImplicitLockStatus, ImplicitLockStatus (..), @@ -101,10 +104,13 @@ import qualified Data.Swagger as S import qualified Data.Text as T import qualified Data.Text.Encoding as T import qualified Data.Text.Lazy as TL +import Data.Time (UTCTime) +import Data.Time.Clock.POSIX (posixSecondsToUTCTime, utcTimeToPOSIXSeconds) import Deriving.Aeson import GHC.TypeLits import Imports import Servant (FromHttpApiData (..), ToHttpApiData (..)) +import Test.QuickCheck (oneof) import Test.QuickCheck.Arbitrary (arbitrary) import Test.QuickCheck.Gen (suchThat) import Wire.API.Conversation.Protocol (ProtocolTag (ProtocolProteusTag)) @@ -138,8 +144,7 @@ import Wire.Arbitrary (Arbitrary, GenericUniform (..)) -- because the lockstatus is checked in 'setFeatureStatus' before which is the public API for setting the feature status. -- -- 6. Add public routes to Wire.API.Routes.Public.Galley.Feature: 'FeatureStatusGet', --- 'FeatureStatusPut' (optional) and by by user: 'FeatureConfigGet'. Then --- implement them in Galley.API.Public.Feature. +-- 'FeatureStatusPut' (optional). Then implement them in Galley.API.Public.Feature. -- -- 7. Add internal routes in Wire.API.Routes.Internal.Galley -- @@ -219,10 +224,16 @@ setLockStatus :: LockStatus -> WithStatus cfg -> WithStatus cfg setLockStatus ls (WithStatusBase s _ c ttl) = WithStatusBase s (Identity ls) c ttl setConfig :: cfg -> WithStatus cfg -> WithStatus cfg -setConfig c (WithStatusBase s ls _ ttl) = WithStatusBase s ls (Identity c) ttl +setConfig = setConfig' + +setConfig' :: forall (m :: Type -> Type) (cfg :: Type). Applicative m => cfg -> WithStatusBase m cfg -> WithStatusBase m cfg +setConfig' c (WithStatusBase s ls _ ttl) = WithStatusBase s ls (pure c) ttl + +setTTL :: forall (m :: Type -> Type) (cfg :: Type). Applicative m => FeatureTTL -> WithStatusBase m cfg -> WithStatusBase m cfg +setTTL ttl (WithStatusBase s ls c _) = WithStatusBase s ls c (pure ttl) setWsTTL :: FeatureTTL -> WithStatus cfg -> WithStatus cfg -setWsTTL ttl (WithStatusBase s ls c _) = WithStatusBase s ls c (Identity ttl) +setWsTTL = setTTL type WithStatus (cfg :: Type) = WithStatusBase Identity cfg @@ -879,6 +890,39 @@ instance ToSchema OutlookCalIntegrationConfig where instance FeatureTrivialConfig OutlookCalIntegrationConfig where trivialConfig = OutlookCalIntegrationConfig +---------------------------------------------------------------------- +-- MlsE2EId + +data MlsE2EIdConfig = MlsE2EIdConfig {verificationExpiration :: Maybe UTCTime} + deriving stock (Eq, Show, Generic) + +instance Arbitrary MlsE2EIdConfig where + arbitrary = oneof [pure $ MlsE2EIdConfig Nothing, MlsE2EIdConfig . Just . posixSecondsToUTCTime . fromIntegral <$> (arbitrary @Word32)] + +instance ToSchema MlsE2EIdConfig where + schema :: ValueSchema NamedSwaggerDoc MlsE2EIdConfig + schema = + object "MlsE2EIdConfig" $ + MlsE2EIdConfig + <$> (fmap toSeconds . verificationExpiration) .= maybe_ (optFieldWithDocModifier "verificationExpiration" desc (fromSeconds <$> schema)) + where + fromSeconds :: Int -> UTCTime + fromSeconds = posixSecondsToUTCTime . fromIntegral + + toSeconds :: UTCTime -> Int + toSeconds = truncate . utcTimeToPOSIXSeconds + + desc :: NamedSwaggerDoc -> NamedSwaggerDoc + desc = + description + ?~ "Unix timestamp (number of seconds that have passed since 00:00:00 UTC on Thursday, 1 January 1970) after which the period for clients to verify their identity expires. \ + \When the timer goes off, they will be logged out and get the certificate automatically on their devices." + +instance IsFeatureConfig MlsE2EIdConfig where + type FeatureSymbol MlsE2EIdConfig = "mlsE2EId" + defFeatureStatus = withStatus FeatureStatusDisabled LockStatusUnlocked (MlsE2EIdConfig Nothing) FeatureTTLUnlimited + objectSchema = field "config" schema + ---------------------------------------------------------------------- -- FeatureStatus @@ -954,7 +998,8 @@ data AllFeatureConfigs = AllFeatureConfigs afcSndFactorPasswordChallenge :: WithStatus SndFactorPasswordChallengeConfig, afcMLS :: WithStatus MLSConfig, afcExposeInvitationURLsToTeamAdmin :: WithStatus ExposeInvitationURLsToTeamAdminConfig, - afcOutlookCalIntegration :: WithStatus OutlookCalIntegrationConfig + afcOutlookCalIntegration :: WithStatus OutlookCalIntegrationConfig, + afcMlsE2EId :: WithStatus MlsE2EIdConfig } deriving stock (Eq, Show) deriving (FromJSON, ToJSON, S.ToSchema) via (Schema AllFeatureConfigs) @@ -979,6 +1024,7 @@ instance ToSchema AllFeatureConfigs where <*> afcMLS .= featureField <*> afcExposeInvitationURLsToTeamAdmin .= featureField <*> afcOutlookCalIntegration .= featureField + <*> afcMlsE2EId .= featureField where featureField :: forall cfg. @@ -1005,5 +1051,6 @@ instance Arbitrary AllFeatureConfigs where <*> arbitrary <*> arbitrary <*> arbitrary + <*> arbitrary makeLenses ''ImplicitLockStatus diff --git a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated.hs b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated.hs index 06f9b0b979..ef53079d9b 100644 --- a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated.hs +++ b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated.hs @@ -1278,6 +1278,10 @@ tests = testObjects [ (Test.Wire.API.Golden.Generated.WithStatus_team.testObject_WithStatus_team_17, "testObject_WithStatus_team_17.json") ], + testGroup "Golden: WithStatus_team 12" $ + testObjects + [ (Test.Wire.API.Golden.Generated.WithStatus_team.testObject_WithStatus_team_18, "testObject_WithStatus_team_18.json") + ], testGroup "Golden: InvitationRequest_team" $ testObjects [(Test.Wire.API.Golden.Generated.InvitationRequest_team.testObject_InvitationRequest_team_1, "testObject_InvitationRequest_team_1.json"), (Test.Wire.API.Golden.Generated.InvitationRequest_team.testObject_InvitationRequest_team_2, "testObject_InvitationRequest_team_2.json"), (Test.Wire.API.Golden.Generated.InvitationRequest_team.testObject_InvitationRequest_team_3, "testObject_InvitationRequest_team_3.json"), (Test.Wire.API.Golden.Generated.InvitationRequest_team.testObject_InvitationRequest_team_4, "testObject_InvitationRequest_team_4.json"), (Test.Wire.API.Golden.Generated.InvitationRequest_team.testObject_InvitationRequest_team_5, "testObject_InvitationRequest_team_5.json"), (Test.Wire.API.Golden.Generated.InvitationRequest_team.testObject_InvitationRequest_team_6, "testObject_InvitationRequest_team_6.json"), (Test.Wire.API.Golden.Generated.InvitationRequest_team.testObject_InvitationRequest_team_7, "testObject_InvitationRequest_team_7.json"), (Test.Wire.API.Golden.Generated.InvitationRequest_team.testObject_InvitationRequest_team_8, "testObject_InvitationRequest_team_8.json"), (Test.Wire.API.Golden.Generated.InvitationRequest_team.testObject_InvitationRequest_team_9, "testObject_InvitationRequest_team_9.json"), (Test.Wire.API.Golden.Generated.InvitationRequest_team.testObject_InvitationRequest_team_10, "testObject_InvitationRequest_team_10.json"), (Test.Wire.API.Golden.Generated.InvitationRequest_team.testObject_InvitationRequest_team_11, "testObject_InvitationRequest_team_11.json"), (Test.Wire.API.Golden.Generated.InvitationRequest_team.testObject_InvitationRequest_team_12, "testObject_InvitationRequest_team_12.json"), (Test.Wire.API.Golden.Generated.InvitationRequest_team.testObject_InvitationRequest_team_13, "testObject_InvitationRequest_team_13.json"), (Test.Wire.API.Golden.Generated.InvitationRequest_team.testObject_InvitationRequest_team_14, "testObject_InvitationRequest_team_14.json"), (Test.Wire.API.Golden.Generated.InvitationRequest_team.testObject_InvitationRequest_team_15, "testObject_InvitationRequest_team_15.json"), (Test.Wire.API.Golden.Generated.InvitationRequest_team.testObject_InvitationRequest_team_16, "testObject_InvitationRequest_team_16.json"), (Test.Wire.API.Golden.Generated.InvitationRequest_team.testObject_InvitationRequest_team_17, "testObject_InvitationRequest_team_17.json"), (Test.Wire.API.Golden.Generated.InvitationRequest_team.testObject_InvitationRequest_team_18, "testObject_InvitationRequest_team_18.json"), (Test.Wire.API.Golden.Generated.InvitationRequest_team.testObject_InvitationRequest_team_19, "testObject_InvitationRequest_team_19.json"), (Test.Wire.API.Golden.Generated.InvitationRequest_team.testObject_InvitationRequest_team_20, "testObject_InvitationRequest_team_20.json")], testGroup "Golden: Invitation_team" $ diff --git a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/WithStatus_team.hs b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/WithStatus_team.hs index 0b100dc130..8d4b48f5a1 100644 --- a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/WithStatus_team.hs +++ b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/WithStatus_team.hs @@ -20,6 +20,7 @@ module Test.Wire.API.Golden.Generated.WithStatus_team where import Data.Domain +import Data.Time.Clock.POSIX (posixSecondsToUTCTime) import Imports import Wire.API.Team.Feature hiding (withStatus) import qualified Wire.API.Team.Feature as F @@ -75,5 +76,8 @@ testObject_WithStatus_team_16 = withStatus FeatureStatusDisabled LockStatusUnloc testObject_WithStatus_team_17 :: WithStatus SearchVisibilityInboundConfig testObject_WithStatus_team_17 = withStatus FeatureStatusEnabled LockStatusUnlocked SearchVisibilityInboundConfig +testObject_WithStatus_team_18 :: WithStatus MlsE2EIdConfig +testObject_WithStatus_team_18 = withStatus FeatureStatusEnabled LockStatusLocked (MlsE2EIdConfig (Just (posixSecondsToUTCTime 1676377048))) + withStatus :: FeatureStatus -> LockStatus -> cfg -> WithStatus cfg withStatus fs ls cfg = F.withStatus fs ls cfg FeatureTTLUnlimited diff --git a/libs/wire-api/test/golden/testObject_WithStatus_team_18.json b/libs/wire-api/test/golden/testObject_WithStatus_team_18.json new file mode 100644 index 0000000000..dcf13549a5 --- /dev/null +++ b/libs/wire-api/test/golden/testObject_WithStatus_team_18.json @@ -0,0 +1,8 @@ +{ + "config": { + "verificationExpiration": 1676377048 + }, + "lockStatus": "locked", + "status": "enabled", + "ttl": "unlimited" +} diff --git a/libs/wire-api/test/unit/Test/Wire/API/Roundtrip/Aeson.hs b/libs/wire-api/test/unit/Test/Wire/API/Roundtrip/Aeson.hs index 2cb462e9d5..3586bb0a0b 100644 --- a/libs/wire-api/test/unit/Test/Wire/API/Roundtrip/Aeson.hs +++ b/libs/wire-api/test/unit/Test/Wire/API/Roundtrip/Aeson.hs @@ -47,6 +47,7 @@ import qualified Wire.API.Provider.Service as Provider.Service import qualified Wire.API.Provider.Service.Tag as Provider.Service.Tag import qualified Wire.API.Push.Token as Push.Token import qualified Wire.API.Routes.Internal.Galley.TeamsIntra as TeamsIntra +import qualified Wire.API.SystemSettings as SystemSettings import qualified Wire.API.Team as Team import qualified Wire.API.Team.Conversation as Team.Conversation import qualified Wire.API.Team.Feature as Team.Feature @@ -180,6 +181,9 @@ tests = testRoundTrip @Push.Token.PushToken, testRoundTrip @Push.Token.PushTokenList, testRoundTrip @Scim.CreateScimToken, + testRoundTrip @SystemSettings.SystemSettings, + testRoundTrip @SystemSettings.SystemSettingsPublic, + testRoundTrip @SystemSettings.SystemSettingsInternal, testRoundTrip @Team.BindingNewTeam, testRoundTrip @Team.TeamBinding, testRoundTrip @Team.Team, diff --git a/services/brig/src/Brig/API/Public.hs b/services/brig/src/Brig/API/Public.hs index c3cf258f7a..2018a5c71e 100644 --- a/services/brig/src/Brig/API/Public.hs +++ b/services/brig/src/Brig/API/Public.hs @@ -356,7 +356,9 @@ servantSitemap = :<|> Named @"get-calls-config-v2" Calling.getCallsConfigV2 systemSettingsAPI :: ServerT SystemSettingsAPI (Handler r) - systemSettingsAPI = Named @"get-system-settings" getSystemSettings + systemSettingsAPI = + Named @"get-system-settings-unauthorized" getSystemSettings + :<|> Named @"get-system-settings" getSystemSettingsInternal -- Note [ephemeral user sideeffect] -- If the user is ephemeral and expired, it will be removed upon calling @@ -1067,13 +1069,19 @@ sendVerificationCode req = do mbStatusEnabled <- lift $ liftSem $ GalleyProvider.getVerificationCodeEnabled `traverse` (Public.userTeam <$> accountUser =<< mbAccount) pure $ fromMaybe False mbStatusEnabled -getSystemSettings :: ExceptT Brig.API.Error.Error (AppT r) SystemSettings +getSystemSettings :: (Handler r) SystemSettingsPublic getSystemSettings = do optSettings <- view settings pure $ - SystemSettings - { systemSettingsSetRestrictUserCreation = fromMaybe False (setRestrictUserCreation optSettings) - } + SystemSettingsPublic $ + fromMaybe False (setRestrictUserCreation optSettings) + +getSystemSettingsInternal :: UserId -> (Handler r) SystemSettings +getSystemSettingsInternal _ = do + optSettings <- view settings + let pSettings = SystemSettingsPublic $ fromMaybe False (setRestrictUserCreation optSettings) + let iSettings = SystemSettingsInternal $ fromMaybe False (setEnableMLS optSettings) + pure $ SystemSettings pSettings iSettings -- Deprecated diff --git a/services/brig/test/integration/API/SystemSettings.hs b/services/brig/test/integration/API/SystemSettings.hs index a46e631b05..ea4b11a3a0 100644 --- a/services/brig/test/integration/API/SystemSettings.hs +++ b/services/brig/test/integration/API/SystemSettings.hs @@ -1,3 +1,20 @@ +-- This file is part of the Wire Server implementation. +-- +-- Copyright (C) 2022 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 API.SystemSettings (tests) where import Bilge @@ -5,6 +22,7 @@ import Bilge.Assert import Brig.Options import Control.Lens import qualified Data.ByteString.Char8 as BS +import Data.Id import Imports import Network.Wai.Test as WaiTest import Test.Tasty @@ -17,7 +35,8 @@ tests :: Opts -> Manager -> IO TestTree tests opts m = pure $ do testGroup "settings" - [ test m "GET /system/settings/unauthorized" $ testGetSettings opts + [ test m "GET /system/settings/unauthorized" $ testGetSettings opts, + test m "GET /system/settings" $ testGetSettingsInternal opts ] testGetSettings :: Opts -> Http () @@ -35,14 +54,34 @@ testGetSettings opts = liftIO $ do -- made. This happens due to the `MonadHttp WaiTest.Session` instance. queriedSettings <- withSettingsOverrides newOpts $ getSystemSettings liftIO $ - queriedSettings @?= SystemSettings expectedRes + queriedSettings @?= SystemSettingsPublic expectedRes + + getSystemSettings :: WaiTest.Session SystemSettingsPublic + getSystemSettings = + responseJsonError + =<< get (path (BS.pack ("/" ++ latestVersion ++ "/system/settings/unauthorized"))) + Http () +testGetSettingsInternal opts = liftIO $ do + uid <- randomId + expectResultForEnableMls uid Nothing False + expectResultForEnableMls uid (Just False) False + expectResultForEnableMls uid (Just True) True where - latestVersion :: String - latestVersion = map toLower $ show (maxBound :: Version) + expectResultForEnableMls :: UserId -> Maybe Bool -> Bool -> IO () + expectResultForEnableMls uid setEnableMlsValue expectedRes = do + let newOpts = opts & (optionSettings . enableMLS) .~ setEnableMlsValue + -- Run call in `WaiTest.Session` with an adjusted brig `Application`. I.e. + -- the response is created by running the brig `Application` (with + -- modified options) directly on the `Request`. No real HTTP request is + -- made. This happens due to the `MonadHttp WaiTest.Session` instance. + queriedSettings <- withSettingsOverrides newOpts $ getSystemSettings uid + liftIO $ ssInternal queriedSettings @?= SystemSettingsInternal expectedRes + + getSystemSettings :: UserId -> WaiTest.Session SystemSettings + getSystemSettings uid = + responseJsonError =<< get (paths ["system", "settings"] . zUser uid) +-- +-- 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 V79_TeamFeatureMlsE2EId + ( migration, + ) +where + +import Cassandra.Schema +import Imports +import Text.RawString.QQ + +migration :: Migration +migration = Migration 79 "Add feature config for team feature MLS MlsE2EId" $ do + schema' + [r| ALTER TABLE team_features ADD ( + mls_e2eid_status int, + mls_e2eid_lock_status int, + mls_e2eid_ver_exp timestamp + ) + |] diff --git a/services/galley/src/Galley/API/Internal.hs b/services/galley/src/Galley/API/Internal.hs index a3401ad4b0..cef9264c17 100644 --- a/services/galley/src/Galley/API/Internal.hs +++ b/services/galley/src/Galley/API/Internal.hs @@ -208,6 +208,10 @@ featureAPI = <@> mkNamedAPI @'("iput", OutlookCalIntegrationConfig) (setFeatureStatusInternal @Cassandra) <@> mkNamedAPI @'("ipatch", OutlookCalIntegrationConfig) (patchFeatureStatusInternal @Cassandra) <@> mkNamedAPI @'("ilock", OutlookCalIntegrationConfig) (updateLockStatus @Cassandra @OutlookCalIntegrationConfig) + <@> mkNamedAPI @'("iget", MlsE2EIdConfig) (getFeatureStatus @Cassandra DontDoAuth) + <@> mkNamedAPI @'("iput", MlsE2EIdConfig) (setFeatureStatusInternal @Cassandra) + <@> mkNamedAPI @'("ipatch", MlsE2EIdConfig) (patchFeatureStatusInternal @Cassandra) + <@> mkNamedAPI @'("ilock", MlsE2EIdConfig) (updateLockStatus @Cassandra @MlsE2EIdConfig) <@> mkNamedAPI @"feature-configs-internal" (maybe getAllFeatureConfigsForServer (getAllFeatureConfigsForUser @Cassandra)) internalSitemap :: Routes a (Sem GalleyEffects) () diff --git a/services/galley/src/Galley/API/Public/Feature.hs b/services/galley/src/Galley/API/Public/Feature.hs index 3c959fe19c..028c3cfc1a 100644 --- a/services/galley/src/Galley/API/Public/Feature.hs +++ b/services/galley/src/Galley/API/Public/Feature.hs @@ -62,6 +62,8 @@ featureAPI = <@> mkNamedAPI @'("put", SearchVisibilityInboundConfig) (setFeatureStatus @Cassandra . DoAuth) <@> mkNamedAPI @'("get", OutlookCalIntegrationConfig) (getFeatureStatus @Cassandra . DoAuth) <@> mkNamedAPI @'("put", OutlookCalIntegrationConfig) (setFeatureStatus @Cassandra . DoAuth) + <@> mkNamedAPI @'("get", MlsE2EIdConfig) (getFeatureStatus @Cassandra . DoAuth) + <@> mkNamedAPI @'("put", MlsE2EIdConfig) (setFeatureStatus @Cassandra . DoAuth) <@> mkNamedAPI @"get-all-feature-configs-for-user" (getAllFeatureConfigsForUser @Cassandra) <@> mkNamedAPI @"get-all-feature-configs-for-team" (getAllFeatureConfigsForTeam @Cassandra) <@> mkNamedAPI @'("get-config", LegalholdConfig) (getFeatureStatusForUser @Cassandra) diff --git a/services/galley/src/Galley/API/Teams/Features.hs b/services/galley/src/Galley/API/Teams/Features.hs index 61dca43056..b5172a853f 100644 --- a/services/galley/src/Galley/API/Teams/Features.hs +++ b/services/galley/src/Galley/API/Teams/Features.hs @@ -170,6 +170,24 @@ class GetFeatureConfig (db :: Type) cfg => SetFeatureConfig (db :: Type) cfg whe TeamId -> WithStatusNoLock cfg -> Sem r (WithStatus cfg) + default setConfigForTeam :: + ( GetConfigForTeamConstraints db cfg r, + FeaturePersistentConstraint db cfg, + IsFeatureConfig cfg, + KnownSymbol (FeatureSymbol cfg), + ToSchema cfg, + Members + '[ TeamFeatureStore db, + P.Logger (Log.Msg -> Log.Msg), + GundeckAccess, + TeamStore + ] + r + ) => + TeamId -> + WithStatusNoLock cfg -> + Sem r (WithStatus cfg) + setConfigForTeam tid wsnl = persistAndPushEvent @db tid wsnl type FeaturePersistentAllFeatures db = ( FeaturePersistentConstraint db LegalholdConfig, @@ -187,7 +205,8 @@ type FeaturePersistentAllFeatures db = FeaturePersistentConstraint db MLSConfig, FeaturePersistentConstraint db SearchVisibilityInboundConfig, FeaturePersistentConstraint db ExposeInvitationURLsToTeamAdminConfig, - FeaturePersistentConstraint db OutlookCalIntegrationConfig + FeaturePersistentConstraint db OutlookCalIntegrationConfig, + FeaturePersistentConstraint db MlsE2EIdConfig ) getFeatureStatus :: @@ -418,6 +437,7 @@ getAllFeatureConfigsForServer = <*> getConfigForServer @MLSConfig <*> getConfigForServer @ExposeInvitationURLsToTeamAdminConfig <*> getConfigForServer @OutlookCalIntegrationConfig + <*> getConfigForServer @MlsE2EIdConfig getAllFeatureConfigsUser :: forall db r. @@ -451,6 +471,7 @@ getAllFeatureConfigsUser uid = <*> getConfigForUser @db @MLSConfig uid <*> getConfigForUser @db @ExposeInvitationURLsToTeamAdminConfig uid <*> getConfigForUser @db @OutlookCalIntegrationConfig uid + <*> getConfigForUser @db @MlsE2EIdConfig uid getAllFeatureConfigsTeam :: forall db r. @@ -480,6 +501,7 @@ getAllFeatureConfigsTeam tid = <*> getConfigForTeam @db @MLSConfig tid <*> getConfigForTeam @db @ExposeInvitationURLsToTeamAdminConfig tid <*> getConfigForTeam @db @OutlookCalIntegrationConfig tid + <*> getConfigForTeam @db @MlsE2EIdConfig tid -- | Note: this is an internal function which doesn't cover all features, e.g. LegalholdConfig genericGetConfigForTeam :: @@ -630,13 +652,11 @@ instance GetFeatureConfig db ValidateSAMLEmailsConfig where getConfigForServer = inputs (view (optSettings . setFeatureFlags . flagsTeamFeatureValidateSAMLEmailsStatus . unDefaults . unImplicitLockStatus)) -instance SetFeatureConfig db ValidateSAMLEmailsConfig where - setConfigForTeam tid wsnl = persistAndPushEvent @db tid wsnl +instance SetFeatureConfig db ValidateSAMLEmailsConfig instance GetFeatureConfig db DigitalSignaturesConfig -instance SetFeatureConfig db DigitalSignaturesConfig where - setConfigForTeam tid wsnl = persistAndPushEvent @db tid wsnl +instance SetFeatureConfig db DigitalSignaturesConfig instance GetFeatureConfig db LegalholdConfig where type @@ -729,8 +749,7 @@ instance GetFeatureConfig db FileSharingConfig where getConfigForServer = input <&> view (optSettings . setFeatureFlags . flagFileSharing . unDefaults) -instance SetFeatureConfig db FileSharingConfig where - setConfigForTeam tid wsnl = persistAndPushEvent @db tid wsnl +instance SetFeatureConfig db FileSharingConfig instance GetFeatureConfig db AppLockConfig where getConfigForServer = @@ -769,25 +788,21 @@ instance GetFeatureConfig db ConferenceCallingConfig where wsnl <- getAccountConferenceCallingConfigClient uid pure $ withLockStatus (wsLockStatus (defFeatureStatus @ConferenceCallingConfig)) wsnl -instance SetFeatureConfig db ConferenceCallingConfig where - setConfigForTeam tid wsnl = persistAndPushEvent @db tid wsnl +instance SetFeatureConfig db ConferenceCallingConfig instance GetFeatureConfig db SelfDeletingMessagesConfig where getConfigForServer = input <&> view (optSettings . setFeatureFlags . flagSelfDeletingMessages . unDefaults) -instance SetFeatureConfig db SelfDeletingMessagesConfig where - setConfigForTeam tid wsnl = persistAndPushEvent @db tid wsnl +instance SetFeatureConfig db SelfDeletingMessagesConfig -instance SetFeatureConfig db GuestLinksConfig where - setConfigForTeam tid wsnl = persistAndPushEvent @db tid wsnl +instance SetFeatureConfig db GuestLinksConfig instance GetFeatureConfig db GuestLinksConfig where getConfigForServer = input <&> view (optSettings . setFeatureFlags . flagConversationGuestLinks . unDefaults) -instance SetFeatureConfig db SndFactorPasswordChallengeConfig where - setConfigForTeam tid wsnl = persistAndPushEvent @db tid wsnl +instance SetFeatureConfig db SndFactorPasswordChallengeConfig instance GetFeatureConfig db SndFactorPasswordChallengeConfig where getConfigForServer = @@ -807,9 +822,7 @@ instance GetFeatureConfig db MLSConfig where getConfigForServer = input <&> view (optSettings . setFeatureFlags . flagMLS . unDefaults . unImplicitLockStatus) -instance SetFeatureConfig db MLSConfig where - setConfigForTeam tid wsnl = do - persistAndPushEvent @db tid wsnl +instance SetFeatureConfig db MLSConfig instance GetFeatureConfig db ExposeInvitationURLsToTeamAdminConfig where getConfigForTeam tid = do @@ -832,17 +845,20 @@ instance GetFeatureConfig db ExposeInvitationURLsToTeamAdminConfig where ExposeInvitationURLsToTeamAdminConfig FeatureTTLUnlimited -instance SetFeatureConfig db ExposeInvitationURLsToTeamAdminConfig where - type SetConfigForTeamConstraints db ExposeInvitationURLsToTeamAdminConfig (r :: EffectRow) = (Member (ErrorS OperationDenied) r) - setConfigForTeam tid wsnl = persistAndPushEvent @db tid wsnl +instance SetFeatureConfig db ExposeInvitationURLsToTeamAdminConfig -instance SetFeatureConfig db OutlookCalIntegrationConfig where - setConfigForTeam tid wsnl = persistAndPushEvent @db tid wsnl +instance SetFeatureConfig db OutlookCalIntegrationConfig instance GetFeatureConfig db OutlookCalIntegrationConfig where getConfigForServer = input <&> view (optSettings . setFeatureFlags . flagOutlookCalIntegration . unDefaults) +instance SetFeatureConfig db MlsE2EIdConfig + +instance GetFeatureConfig db MlsE2EIdConfig where + getConfigForServer = + input <&> view (optSettings . setFeatureFlags . flagMlsE2EId . unDefaults) + -- -- | If second factor auth is enabled, make sure that end-points that don't support it, but should, are blocked completely. (This is a workaround until we have 2FA for those end-points as well.) -- -- -- This function exists to resolve a cyclic dependency. diff --git a/services/galley/src/Galley/Cassandra.hs b/services/galley/src/Galley/Cassandra.hs index 2056cfc2c2..8d75052d2d 100644 --- a/services/galley/src/Galley/Cassandra.hs +++ b/services/galley/src/Galley/Cassandra.hs @@ -20,4 +20,4 @@ module Galley.Cassandra (schemaVersion) where import Imports schemaVersion :: Int32 -schemaVersion = 78 +schemaVersion = 79 diff --git a/services/galley/src/Galley/Cassandra/TeamFeatures.hs b/services/galley/src/Galley/Cassandra/TeamFeatures.hs index cfa27d7e86..272a6d27d1 100644 --- a/services/galley/src/Galley/Cassandra/TeamFeatures.hs +++ b/services/galley/src/Galley/Cassandra/TeamFeatures.hs @@ -28,6 +28,7 @@ import qualified Cassandra as C import Control.Monad.Trans.Maybe import Data.Id import Data.Proxy +import Data.Time (UTCTime) import Galley.Cassandra.Instances () import Galley.Cassandra.Store import qualified Galley.Effects.TeamFeatureStore as TFS @@ -316,6 +317,35 @@ instance FeatureStatusCassandra MLSConfig where "insert into team_features (team_id, mls_status, mls_default_protocol, \ \mls_protocol_toggle_users, mls_allowed_ciphersuites, mls_default_ciphersuite) values (?, ?, ?, ?, ?, ?)" +instance FeatureStatusCassandra MlsE2EIdConfig where + getFeatureConfig _ tid = do + let q = query1 select (params LocalQuorum (Identity tid)) + retry x1 q <&> \case + Nothing -> Nothing + Just (Nothing, _) -> Nothing + Just (mStatus, mTimeout) -> + WithStatusNoLock + <$> mStatus + <*> maybe (pure $ wsConfig defFeatureStatus) (pure . MlsE2EIdConfig) (Just mTimeout) + <*> Just FeatureTTLUnlimited + where + select :: PrepQuery R (Identity TeamId) (Maybe FeatureStatus, Maybe UTCTime) + select = + fromString $ + "select mls_e2eid_status, mls_e2eid_ver_exp from team_features where team_id = ?" + + setFeatureConfig _ tid status = do + let statusValue = wssStatus status + timeout = verificationExpiration . wssConfig $ status + retry x5 $ write insert (params LocalQuorum (tid, statusValue, timeout)) + where + insert :: PrepQuery W (TeamId, FeatureStatus, Maybe UTCTime) () + insert = + "insert into team_features (team_id, mls_e2eid_status, mls_e2eid_ver_exp) values (?, ?, ?)" + + getFeatureLockStatus _ = getLockStatusC "mls_e2eid_lock_status" + setFeatureLockStatus _ = setLockStatusC "mls_e2eid_lock_status" + instance FeatureStatusCassandra ExposeInvitationURLsToTeamAdminConfig where getFeatureConfig _ = getTrivialConfigC "expose_invitation_urls_to_team_admin" setFeatureConfig _ tid statusNoLock = setFeatureStatusC "expose_invitation_urls_to_team_admin" tid (wssStatus statusNoLock) diff --git a/services/galley/test/integration/API/Teams/Feature.hs b/services/galley/test/integration/API/Teams/Feature.hs index 1ad9684d67..d0c6ee5edf 100644 --- a/services/galley/test/integration/API/Teams/Feature.hs +++ b/services/galley/test/integration/API/Teams/Feature.hs @@ -57,8 +57,7 @@ import qualified Wire.API.Event.FeatureConfig as FeatureConfig import Wire.API.Internal.Notification (Notification) import Wire.API.MLS.CipherSuite import Wire.API.Routes.Internal.Galley.TeamFeatureNoConfigMulti as Multi -import Wire.API.Team.Feature (ExposeInvitationURLsToTeamAdminConfig (..), FeatureStatus (..), FeatureTTL, FeatureTTL' (..), LockStatus (LockStatusUnlocked), MLSConfig (MLSConfig)) -import qualified Wire.API.Team.Feature as Public +import Wire.API.Team.Feature hiding (setLockStatus) tests :: IO TestSetup -> TestTree tests s = @@ -69,76 +68,85 @@ tests s = test s "LegalHold - set with HTTP PUT" (testLegalHold putLegalHoldInternal), test s "LegalHold - set with HTTP PATCH" (testLegalHold patchLegalHoldInternal), test s "SearchVisibility" testSearchVisibility, - test s "DigitalSignatures" $ testSimpleFlag @Public.DigitalSignaturesConfig Public.FeatureStatusDisabled, - test s "ValidateSAMLEmails" $ testSimpleFlag @Public.ValidateSAMLEmailsConfig Public.FeatureStatusEnabled, - test s "FileSharing with lock status" $ testSimpleFlagWithLockStatus @Public.FileSharingConfig Public.FeatureStatusEnabled Public.LockStatusUnlocked, + test s "DigitalSignatures" $ testSimpleFlag @DigitalSignaturesConfig FeatureStatusDisabled, + test s "ValidateSAMLEmails" $ testSimpleFlag @ValidateSAMLEmailsConfig FeatureStatusEnabled, + test s "FileSharing with lock status" $ testSimpleFlagWithLockStatus @FileSharingConfig FeatureStatusEnabled LockStatusUnlocked, test s "Classified Domains (enabled)" testClassifiedDomainsEnabled, test s "Classified Domains (disabled)" testClassifiedDomainsDisabled, test s "All features" testAllFeatures, test s "Feature Configs / Team Features Consistency" testFeatureConfigConsistency, - test s "ConferenceCalling" $ testSimpleFlag @Public.ConferenceCallingConfig Public.FeatureStatusEnabled, + test s "ConferenceCalling" $ testSimpleFlag @ConferenceCallingConfig FeatureStatusEnabled, test s "SelfDeletingMessages" testSelfDeletingMessages, test s "ConversationGuestLinks - public API" testGuestLinksPublic, test s "ConversationGuestLinks - internal API" testGuestLinksInternal, - test s "ConversationGuestLinks - lock status" $ testSimpleFlagWithLockStatus @Public.GuestLinksConfig Public.FeatureStatusEnabled Public.LockStatusUnlocked, - test s "SndFactorPasswordChallenge - lock status" $ testSimpleFlagWithLockStatus @Public.SndFactorPasswordChallengeConfig Public.FeatureStatusDisabled Public.LockStatusLocked, + test s "ConversationGuestLinks - lock status" $ testSimpleFlagWithLockStatus @GuestLinksConfig FeatureStatusEnabled LockStatusUnlocked, + test s "SndFactorPasswordChallenge - lock status" $ testSimpleFlagWithLockStatus @SndFactorPasswordChallengeConfig FeatureStatusDisabled LockStatusLocked, test s "SearchVisibilityInbound - internal API" testSearchVisibilityInbound, test s "SearchVisibilityInbound - internal multi team API" testFeatureNoConfigMultiSearchVisibilityInbound, - test s "OutlookCalIntegration" $ testSimpleFlagWithLockStatus @Public.OutlookCalIntegrationConfig Public.FeatureStatusDisabled Public.LockStatusLocked, + test s "OutlookCalIntegration" $ testSimpleFlagWithLockStatus @OutlookCalIntegrationConfig FeatureStatusDisabled LockStatusLocked, testGroup "TTL / Conference calling" - [ test s "ConferenceCalling unlimited TTL" $ testSimpleFlagTTL @Public.ConferenceCallingConfig Public.FeatureStatusEnabled FeatureTTLUnlimited, - test s "ConferenceCalling 2s TTL" $ testSimpleFlagTTL @Public.ConferenceCallingConfig Public.FeatureStatusEnabled (FeatureTTLSeconds 2) + [ test s "ConferenceCalling unlimited TTL" $ testSimpleFlagTTL @ConferenceCallingConfig FeatureStatusEnabled FeatureTTLUnlimited, + test s "ConferenceCalling 2s TTL" $ testSimpleFlagTTL @ConferenceCallingConfig FeatureStatusEnabled (FeatureTTLSeconds 2) ], testGroup "TTL / Overrides" - [ test s "increase to unlimited" $ testSimpleFlagTTLOverride @Public.ConferenceCallingConfig Public.FeatureStatusEnabled (FeatureTTLSeconds 2) FeatureTTLUnlimited, - test s "increase" $ testSimpleFlagTTLOverride @Public.ConferenceCallingConfig Public.FeatureStatusEnabled (FeatureTTLSeconds 2) (FeatureTTLSeconds 4), - test s "reduce from unlimited" $ testSimpleFlagTTLOverride @Public.ConferenceCallingConfig Public.FeatureStatusEnabled FeatureTTLUnlimited (FeatureTTLSeconds 2), - test s "reduce" $ testSimpleFlagTTLOverride @Public.ConferenceCallingConfig Public.FeatureStatusEnabled (FeatureTTLSeconds 5) (FeatureTTLSeconds 2), - test s "Unlimited to unlimited" $ testSimpleFlagTTLOverride @Public.ConferenceCallingConfig Public.FeatureStatusEnabled FeatureTTLUnlimited FeatureTTLUnlimited + [ test s "increase to unlimited" $ testSimpleFlagTTLOverride @ConferenceCallingConfig FeatureStatusEnabled (FeatureTTLSeconds 2) FeatureTTLUnlimited, + test s "increase" $ testSimpleFlagTTLOverride @ConferenceCallingConfig FeatureStatusEnabled (FeatureTTLSeconds 2) (FeatureTTLSeconds 4), + test s "reduce from unlimited" $ testSimpleFlagTTLOverride @ConferenceCallingConfig FeatureStatusEnabled FeatureTTLUnlimited (FeatureTTLSeconds 2), + test s "reduce" $ testSimpleFlagTTLOverride @ConferenceCallingConfig FeatureStatusEnabled (FeatureTTLSeconds 5) (FeatureTTLSeconds 2), + test s "Unlimited to unlimited" $ testSimpleFlagTTLOverride @ConferenceCallingConfig FeatureStatusEnabled FeatureTTLUnlimited FeatureTTLUnlimited ], test s "MLS feature config" testMLS, - test s "SearchVisibilityInbound" $ testSimpleFlag @Public.SearchVisibilityInboundConfig Public.FeatureStatusDisabled, + test s "SearchVisibilityInbound" $ testSimpleFlag @SearchVisibilityInboundConfig FeatureStatusDisabled, + test s "MlsE2EId feature config" $ + testNonTrivialConfigNoTTL + ( withStatus + FeatureStatusDisabled + LockStatusUnlocked + (MlsE2EIdConfig Nothing) + FeatureTTLUnlimited + ), testGroup "Patch" [ -- Note: `SSOConfig` and `LegalHoldConfig` may not be able to be reset -- (depending on prior state or configuration). Thus, they cannot be -- tested here (setting random values), but are tested with separate -- tests. - test s (unpack $ Public.featureNameBS @Public.SearchVisibilityAvailableConfig) $ - testPatch IgnoreLockStatusChange Public.FeatureStatusEnabled Public.SearchVisibilityAvailableConfig, - test s (unpack $ Public.featureNameBS @Public.ValidateSAMLEmailsConfig) $ - testPatch IgnoreLockStatusChange Public.FeatureStatusEnabled Public.ValidateSAMLEmailsConfig, - test s (unpack $ Public.featureNameBS @Public.DigitalSignaturesConfig) $ - testPatch IgnoreLockStatusChange Public.FeatureStatusEnabled Public.DigitalSignaturesConfig, - test s (unpack $ Public.featureNameBS @Public.AppLockConfig) $ - testPatchWithCustomGen IgnoreLockStatusChange Public.FeatureStatusEnabled (Public.AppLockConfig (Public.EnforceAppLock False) 60) validAppLockConfigGen, - test s (unpack $ Public.featureNameBS @Public.ConferenceCallingConfig) $ - testPatch IgnoreLockStatusChange Public.FeatureStatusEnabled Public.ConferenceCallingConfig, - test s (unpack $ Public.featureNameBS @Public.SearchVisibilityAvailableConfig) $ - testPatch IgnoreLockStatusChange Public.FeatureStatusEnabled Public.SearchVisibilityAvailableConfig, - test s (unpack $ Public.featureNameBS @Public.MLSConfig) $ + test s (unpack $ featureNameBS @SearchVisibilityAvailableConfig) $ + testPatch IgnoreLockStatusChange FeatureStatusEnabled SearchVisibilityAvailableConfig, + test s (unpack $ featureNameBS @ValidateSAMLEmailsConfig) $ + testPatch IgnoreLockStatusChange FeatureStatusEnabled ValidateSAMLEmailsConfig, + test s (unpack $ featureNameBS @DigitalSignaturesConfig) $ + testPatch IgnoreLockStatusChange FeatureStatusEnabled DigitalSignaturesConfig, + test s (unpack $ featureNameBS @AppLockConfig) $ + testPatchWithCustomGen IgnoreLockStatusChange FeatureStatusEnabled (AppLockConfig (EnforceAppLock False) 60) validAppLockConfigGen, + test s (unpack $ featureNameBS @ConferenceCallingConfig) $ + testPatch IgnoreLockStatusChange FeatureStatusEnabled ConferenceCallingConfig, + test s (unpack $ featureNameBS @SearchVisibilityAvailableConfig) $ + testPatch IgnoreLockStatusChange FeatureStatusEnabled SearchVisibilityAvailableConfig, + test s (unpack $ featureNameBS @MLSConfig) $ testPatchWithCustomGen IgnoreLockStatusChange - Public.FeatureStatusEnabled - ( Public.MLSConfig + FeatureStatusEnabled + ( MLSConfig [] ProtocolProteusTag [MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519] MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519 ) validMLSConfigGen, - test s (unpack $ Public.featureNameBS @Public.FileSharingConfig) $ - testPatch AssertLockStatusChange Public.FeatureStatusEnabled Public.FileSharingConfig, - test s (unpack $ Public.featureNameBS @Public.GuestLinksConfig) $ - testPatch AssertLockStatusChange Public.FeatureStatusEnabled Public.GuestLinksConfig, - test s (unpack $ Public.featureNameBS @Public.SndFactorPasswordChallengeConfig) $ - testPatch AssertLockStatusChange Public.FeatureStatusDisabled Public.SndFactorPasswordChallengeConfig, - test s (unpack $ Public.featureNameBS @Public.SelfDeletingMessagesConfig) $ - testPatch AssertLockStatusChange Public.FeatureStatusEnabled (Public.SelfDeletingMessagesConfig 0), - test s (unpack $ Public.featureNameBS @Public.OutlookCalIntegrationConfig) $ - testPatch AssertLockStatusChange Public.FeatureStatusDisabled Public.OutlookCalIntegrationConfig + test s (unpack $ featureNameBS @FileSharingConfig) $ + testPatch AssertLockStatusChange FeatureStatusEnabled FileSharingConfig, + test s (unpack $ featureNameBS @GuestLinksConfig) $ + testPatch AssertLockStatusChange FeatureStatusEnabled GuestLinksConfig, + test s (unpack $ featureNameBS @SndFactorPasswordChallengeConfig) $ + testPatch AssertLockStatusChange FeatureStatusDisabled SndFactorPasswordChallengeConfig, + test s (unpack $ featureNameBS @SelfDeletingMessagesConfig) $ + testPatch AssertLockStatusChange FeatureStatusEnabled (SelfDeletingMessagesConfig 0), + test s (unpack $ featureNameBS @OutlookCalIntegrationConfig) $ + testPatch AssertLockStatusChange FeatureStatusDisabled OutlookCalIntegrationConfig, + test s (unpack $ featureNameBS @MlsE2EIdConfig) $ testPatchWithArbitrary AssertLockStatusChange FeatureStatusDisabled (MlsE2EIdConfig Nothing) ], testGroup "ExposeInvitationURLsToTeamAdmin" @@ -149,11 +157,11 @@ tests s = ] -- | Provides a `Gen` with test objects that are realistic and can easily be asserted -validMLSConfigGen :: Gen (Public.WithStatusPatch MLSConfig) +validMLSConfigGen :: Gen (WithStatusPatch MLSConfig) validMLSConfigGen = arbitrary - `suchThat` ( \cfg -> case Public.wspConfig cfg of - Just (Public.MLSConfig us _ cTags ctag) -> + `suchThat` ( \cfg -> case wspConfig cfg of + Just (MLSConfig us _ cTags ctag) -> sortedAndNoDuplicates us && sortedAndNoDuplicates cTags && elem ctag cTags @@ -162,11 +170,11 @@ validMLSConfigGen = where sortedAndNoDuplicates xs = (sort . nub) xs == xs -validAppLockConfigGen :: Gen (Public.WithStatusPatch Public.AppLockConfig) +validAppLockConfigGen :: Gen (WithStatusPatch AppLockConfig) validAppLockConfigGen = arbitrary - `suchThat` ( \cfg -> case Public.wspConfig cfg of - Just (Public.AppLockConfig _ secs) -> secs >= 30 + `suchThat` ( \cfg -> case wspConfig cfg of + Just (AppLockConfig _ secs) -> secs >= 30 Nothing -> True ) @@ -174,20 +182,39 @@ validAppLockConfigGen = data AssertLockStatusChange = AssertLockStatusChange | IgnoreLockStatusChange deriving (Eq) +testPatchWithArbitrary :: + forall cfg. + ( HasCallStack, + IsFeatureConfig cfg, + Typeable cfg, + ToSchema cfg, + Eq cfg, + Show cfg, + KnownSymbol (FeatureSymbol cfg), + Arbitrary (WithStatusPatch cfg) + ) => + AssertLockStatusChange -> + FeatureStatus -> + cfg -> + TestM () +testPatchWithArbitrary assertLockStatusChange featureStatus cfg = do + generatedConfig <- liftIO $ generate arbitrary + testPatch' assertLockStatusChange generatedConfig featureStatus cfg + testPatchWithCustomGen :: forall cfg. ( HasCallStack, - Public.IsFeatureConfig cfg, + IsFeatureConfig cfg, Typeable cfg, ToSchema cfg, Eq cfg, Show cfg, - KnownSymbol (Public.FeatureSymbol cfg) + KnownSymbol (FeatureSymbol cfg) ) => AssertLockStatusChange -> - Public.FeatureStatus -> + FeatureStatus -> cfg -> - Gen (Public.WithStatusPatch cfg) -> + Gen (WithStatusPatch cfg) -> TestM () testPatchWithCustomGen assertLockStatusChange featureStatus cfg gen = do generatedConfig <- liftIO $ generate gen @@ -196,16 +223,16 @@ testPatchWithCustomGen assertLockStatusChange featureStatus cfg gen = do testPatch :: forall cfg. ( HasCallStack, - Public.IsFeatureConfig cfg, + IsFeatureConfig cfg, Typeable cfg, ToSchema cfg, Eq cfg, Show cfg, - KnownSymbol (Public.FeatureSymbol cfg), - Arbitrary (Public.WithStatusPatch cfg) + KnownSymbol (FeatureSymbol cfg), + Arbitrary (WithStatusPatch cfg) ) => AssertLockStatusChange -> - Public.FeatureStatus -> + FeatureStatus -> cfg -> TestM () testPatch assertLockStatusChange status cfg = testPatchWithCustomGen assertLockStatusChange status cfg arbitrary @@ -213,16 +240,16 @@ testPatch assertLockStatusChange status cfg = testPatchWithCustomGen assertLockS testPatch' :: forall cfg. ( HasCallStack, - Public.IsFeatureConfig cfg, + IsFeatureConfig cfg, Typeable cfg, ToSchema cfg, Eq cfg, Show cfg, - KnownSymbol (Public.FeatureSymbol cfg) + KnownSymbol (FeatureSymbol cfg) ) => AssertLockStatusChange -> - Public.WithStatusPatch cfg -> - Public.FeatureStatus -> + WithStatusPatch cfg -> + FeatureStatus -> cfg -> TestM () testPatch' testLockStatusChange rndFeatureConfig defStatus defConfig = do @@ -231,132 +258,132 @@ testPatch' testLockStatusChange rndFeatureConfig defStatus defConfig = do patchFeatureStatusInternal tid rndFeatureConfig !!! statusCode === const 200 Just actual <- responseJsonMaybe <$> getFeatureStatusInternal @cfg tid liftIO $ - if Public.wsLockStatus actual == Public.LockStatusLocked + if wsLockStatus actual == LockStatusLocked then do - Public.wsStatus actual @?= defStatus - Public.wsConfig actual @?= defConfig + wsStatus actual @?= defStatus + wsConfig actual @?= defConfig else do - Public.wsStatus actual @?= fromMaybe (Public.wsStatus original) (Public.wspStatus rndFeatureConfig) + wsStatus actual @?= fromMaybe (wsStatus original) (wspStatus rndFeatureConfig) when (testLockStatusChange == AssertLockStatusChange) $ - Public.wsLockStatus actual @?= fromMaybe (Public.wsLockStatus original) (Public.wspLockStatus rndFeatureConfig) - Public.wsConfig actual @?= fromMaybe (Public.wsConfig original) (Public.wspConfig rndFeatureConfig) + wsLockStatus actual @?= fromMaybe (wsLockStatus original) (wspLockStatus rndFeatureConfig) + wsConfig actual @?= fromMaybe (wsConfig original) (wspConfig rndFeatureConfig) -testSSO :: (TeamId -> Public.FeatureStatus -> TestM ()) -> TestM () +testSSO :: (TeamId -> FeatureStatus -> TestM ()) -> TestM () testSSO setSSOFeature = do (_owner, tid, member : _) <- createBindingTeamWithNMembers 1 nonMember <- randomUser - let getSSO :: HasCallStack => Public.FeatureStatus -> TestM () - getSSO = assertFlagNoConfig @Public.SSOConfig $ getTeamFeatureFlag @Public.SSOConfig member tid - getSSOFeatureConfig :: HasCallStack => Public.FeatureStatus -> TestM () + let getSSO :: HasCallStack => FeatureStatus -> TestM () + getSSO = assertFlagNoConfig @SSOConfig $ getTeamFeatureFlag @SSOConfig member tid + getSSOFeatureConfig :: HasCallStack => FeatureStatus -> TestM () getSSOFeatureConfig expectedStatus = do - actual <- Util.getFeatureConfig @Public.SSOConfig member - liftIO $ Public.wsStatus actual @?= expectedStatus - getSSOInternal :: HasCallStack => Public.FeatureStatus -> TestM () - getSSOInternal = assertFlagNoConfig @Public.SSOConfig $ getTeamFeatureFlagInternal @Public.SSOConfig tid + actual <- Util.getFeatureConfig @SSOConfig member + liftIO $ wsStatus actual @?= expectedStatus + getSSOInternal :: HasCallStack => FeatureStatus -> TestM () + getSSOInternal = assertFlagNoConfig @SSOConfig $ getTeamFeatureFlagInternal @SSOConfig tid - assertFlagForbidden $ getTeamFeatureFlag @Public.SSOConfig nonMember tid + assertFlagForbidden $ getTeamFeatureFlag @SSOConfig nonMember tid featureSSO <- view (tsGConf . optSettings . setFeatureFlags . flagSSO) case featureSSO of FeatureSSODisabledByDefault -> do -- Test default - getSSO Public.FeatureStatusDisabled - getSSOInternal Public.FeatureStatusDisabled - getSSOFeatureConfig Public.FeatureStatusDisabled + getSSO FeatureStatusDisabled + getSSOInternal FeatureStatusDisabled + getSSOFeatureConfig FeatureStatusDisabled -- Test override - setSSOFeature tid Public.FeatureStatusEnabled - getSSO Public.FeatureStatusEnabled - getSSOInternal Public.FeatureStatusEnabled - getSSOFeatureConfig Public.FeatureStatusEnabled + setSSOFeature tid FeatureStatusEnabled + getSSO FeatureStatusEnabled + getSSOInternal FeatureStatusEnabled + getSSOFeatureConfig FeatureStatusEnabled FeatureSSOEnabledByDefault -> do -- since we don't allow to disable (see 'disableSsoNotImplemented'), we can't test -- much here. (disable failure is covered in "enable/disable SSO" above.) - getSSO Public.FeatureStatusEnabled - getSSOInternal Public.FeatureStatusEnabled - getSSOFeatureConfig Public.FeatureStatusEnabled + getSSO FeatureStatusEnabled + getSSOInternal FeatureStatusEnabled + getSSOFeatureConfig FeatureStatusEnabled -putSSOInternal :: HasCallStack => TeamId -> Public.FeatureStatus -> TestM () +putSSOInternal :: HasCallStack => TeamId -> FeatureStatus -> TestM () putSSOInternal tid = void - . putTeamFeatureFlagInternal @Public.SSOConfig expect2xx tid - . (\st -> Public.WithStatusNoLock st Public.SSOConfig Public.FeatureTTLUnlimited) + . putTeamFeatureFlagInternal @SSOConfig expect2xx tid + . (\st -> WithStatusNoLock st SSOConfig FeatureTTLUnlimited) -patchSSOInternal :: HasCallStack => TeamId -> Public.FeatureStatus -> TestM () -patchSSOInternal tid status = void $ patchFeatureStatusInternalWithMod @Public.SSOConfig expect2xx tid (Public.withStatus' (Just status) Nothing Nothing (Just Public.FeatureTTLUnlimited)) +patchSSOInternal :: HasCallStack => TeamId -> FeatureStatus -> TestM () +patchSSOInternal tid status = void $ patchFeatureStatusInternalWithMod @SSOConfig expect2xx tid (withStatus' (Just status) Nothing Nothing (Just FeatureTTLUnlimited)) -testLegalHold :: ((Request -> Request) -> TeamId -> Public.FeatureStatus -> TestM ()) -> TestM () +testLegalHold :: ((Request -> Request) -> TeamId -> FeatureStatus -> TestM ()) -> TestM () testLegalHold setLegalHoldInternal = do (_owner, tid, member : _) <- createBindingTeamWithNMembers 1 nonMember <- randomUser - let getLegalHold :: HasCallStack => Public.FeatureStatus -> TestM () - getLegalHold = assertFlagNoConfig @Public.LegalholdConfig $ getTeamFeatureFlag @Public.LegalholdConfig member tid - getLegalHoldInternal :: HasCallStack => Public.FeatureStatus -> TestM () - getLegalHoldInternal = assertFlagNoConfig @Public.LegalholdConfig $ getTeamFeatureFlagInternal @Public.LegalholdConfig tid + let getLegalHold :: HasCallStack => FeatureStatus -> TestM () + getLegalHold = assertFlagNoConfig @LegalholdConfig $ getTeamFeatureFlag @LegalholdConfig member tid + getLegalHoldInternal :: HasCallStack => FeatureStatus -> TestM () + getLegalHoldInternal = assertFlagNoConfig @LegalholdConfig $ getTeamFeatureFlagInternal @LegalholdConfig tid getLegalHoldFeatureConfig expectedStatus = do - actual <- Util.getFeatureConfig @Public.LegalholdConfig member - liftIO $ Public.wsStatus actual @?= expectedStatus + actual <- Util.getFeatureConfig @LegalholdConfig member + liftIO $ wsStatus actual @?= expectedStatus - getLegalHold Public.FeatureStatusDisabled - getLegalHoldInternal Public.FeatureStatusDisabled + getLegalHold FeatureStatusDisabled + getLegalHoldInternal FeatureStatusDisabled - assertFlagForbidden $ getTeamFeatureFlag @Public.LegalholdConfig nonMember tid + assertFlagForbidden $ getTeamFeatureFlag @LegalholdConfig nonMember tid -- FUTUREWORK: run two galleys, like below for custom search visibility. featureLegalHold <- view (tsGConf . optSettings . setFeatureFlags . flagLegalHold) case featureLegalHold of FeatureLegalHoldDisabledByDefault -> do -- Test default - getLegalHold Public.FeatureStatusDisabled - getLegalHoldInternal Public.FeatureStatusDisabled - getLegalHoldFeatureConfig Public.FeatureStatusDisabled + getLegalHold FeatureStatusDisabled + getLegalHoldInternal FeatureStatusDisabled + getLegalHoldFeatureConfig FeatureStatusDisabled -- Test override - setLegalHoldInternal expect2xx tid Public.FeatureStatusEnabled - getLegalHold Public.FeatureStatusEnabled - getLegalHoldInternal Public.FeatureStatusEnabled - getLegalHoldFeatureConfig Public.FeatureStatusEnabled + setLegalHoldInternal expect2xx tid FeatureStatusEnabled + getLegalHold FeatureStatusEnabled + getLegalHoldInternal FeatureStatusEnabled + getLegalHoldFeatureConfig FeatureStatusEnabled -- turned off for instance FeatureLegalHoldDisabledPermanently -> do - setLegalHoldInternal expect4xx tid Public.FeatureStatusEnabled + setLegalHoldInternal expect4xx tid FeatureStatusEnabled -- turned off but for whitelisted teams with implicit consent FeatureLegalHoldWhitelistTeamsAndImplicitConsent -> do - setLegalHoldInternal expect4xx tid Public.FeatureStatusEnabled + setLegalHoldInternal expect4xx tid FeatureStatusEnabled -putLegalHoldInternal :: HasCallStack => (Request -> Request) -> TeamId -> Public.FeatureStatus -> TestM () +putLegalHoldInternal :: HasCallStack => (Request -> Request) -> TeamId -> FeatureStatus -> TestM () putLegalHoldInternal expectation tid = void - . putTeamFeatureFlagInternal @Public.LegalholdConfig expectation tid - . (\st -> Public.WithStatusNoLock st Public.LegalholdConfig Public.FeatureTTLUnlimited) + . putTeamFeatureFlagInternal @LegalholdConfig expectation tid + . (\st -> WithStatusNoLock st LegalholdConfig FeatureTTLUnlimited) -patchLegalHoldInternal :: HasCallStack => (Request -> Request) -> TeamId -> Public.FeatureStatus -> TestM () -patchLegalHoldInternal expectation tid status = void $ patchFeatureStatusInternalWithMod @Public.LegalholdConfig expectation tid (Public.withStatus' (Just status) Nothing Nothing (Just Public.FeatureTTLUnlimited)) +patchLegalHoldInternal :: HasCallStack => (Request -> Request) -> TeamId -> FeatureStatus -> TestM () +patchLegalHoldInternal expectation tid status = void $ patchFeatureStatusInternalWithMod @LegalholdConfig expectation tid (withStatus' (Just status) Nothing Nothing (Just FeatureTTLUnlimited)) testSearchVisibility :: TestM () testSearchVisibility = do - let getTeamSearchVisibility :: TeamId -> UserId -> Public.FeatureStatus -> TestM () + let getTeamSearchVisibility :: TeamId -> UserId -> FeatureStatus -> TestM () getTeamSearchVisibility teamid uid expected = do g <- viewGalley getTeamSearchVisibilityAvailable g uid teamid !!! do statusCode === const 200 - responseJsonEither === const (Right (Public.WithStatusNoLock expected Public.SearchVisibilityAvailableConfig Public.FeatureTTLUnlimited)) + responseJsonEither === const (Right (WithStatusNoLock expected SearchVisibilityAvailableConfig FeatureTTLUnlimited)) - let getTeamSearchVisibilityInternal :: TeamId -> Public.FeatureStatus -> TestM () + let getTeamSearchVisibilityInternal :: TeamId -> FeatureStatus -> TestM () getTeamSearchVisibilityInternal teamid expected = do g <- viewGalley getTeamSearchVisibilityAvailableInternal g teamid !!! do statusCode === const 200 - responseJsonEither === const (Right (Public.WithStatusNoLock expected Public.SearchVisibilityAvailableConfig Public.FeatureTTLUnlimited)) + responseJsonEither === const (Right (WithStatusNoLock expected SearchVisibilityAvailableConfig FeatureTTLUnlimited)) - let getTeamSearchVisibilityFeatureConfig :: UserId -> Public.FeatureStatus -> TestM () + let getTeamSearchVisibilityFeatureConfig :: UserId -> FeatureStatus -> TestM () getTeamSearchVisibilityFeatureConfig uid expected = do - actual <- Util.getFeatureConfig @Public.SearchVisibilityAvailableConfig uid - liftIO $ Public.wsStatus actual @?= expected + actual <- Util.getFeatureConfig @SearchVisibilityAvailableConfig uid + liftIO $ wsStatus actual @?= expected - let setTeamSearchVisibilityInternal :: TeamId -> Public.FeatureStatus -> TestM () + let setTeamSearchVisibilityInternal :: TeamId -> FeatureStatus -> TestM () setTeamSearchVisibilityInternal teamid val = do g <- viewGalley putTeamSearchVisibilityAvailableInternal g teamid val @@ -364,74 +391,74 @@ testSearchVisibility = do (owner, tid, [member]) <- createBindingTeamWithNMembers 1 nonMember <- randomUser - assertFlagForbidden $ getTeamFeatureFlag @Public.SearchVisibilityAvailableConfig nonMember tid + assertFlagForbidden $ getTeamFeatureFlag @SearchVisibilityAvailableConfig nonMember tid withCustomSearchFeature FeatureTeamSearchVisibilityUnavailableByDefault $ do - getTeamSearchVisibility tid owner Public.FeatureStatusDisabled - getTeamSearchVisibilityInternal tid Public.FeatureStatusDisabled - getTeamSearchVisibilityFeatureConfig member Public.FeatureStatusDisabled + getTeamSearchVisibility tid owner FeatureStatusDisabled + getTeamSearchVisibilityInternal tid FeatureStatusDisabled + getTeamSearchVisibilityFeatureConfig member FeatureStatusDisabled - setTeamSearchVisibilityInternal tid Public.FeatureStatusEnabled - getTeamSearchVisibility tid owner Public.FeatureStatusEnabled - getTeamSearchVisibilityInternal tid Public.FeatureStatusEnabled - getTeamSearchVisibilityFeatureConfig member Public.FeatureStatusEnabled + setTeamSearchVisibilityInternal tid FeatureStatusEnabled + getTeamSearchVisibility tid owner FeatureStatusEnabled + getTeamSearchVisibilityInternal tid FeatureStatusEnabled + getTeamSearchVisibilityFeatureConfig member FeatureStatusEnabled - setTeamSearchVisibilityInternal tid Public.FeatureStatusDisabled - getTeamSearchVisibility tid owner Public.FeatureStatusDisabled - getTeamSearchVisibilityInternal tid Public.FeatureStatusDisabled - getTeamSearchVisibilityFeatureConfig member Public.FeatureStatusDisabled + setTeamSearchVisibilityInternal tid FeatureStatusDisabled + getTeamSearchVisibility tid owner FeatureStatusDisabled + getTeamSearchVisibilityInternal tid FeatureStatusDisabled + getTeamSearchVisibilityFeatureConfig member FeatureStatusDisabled (owner2, tid2, team2member : _) <- createBindingTeamWithNMembers 1 withCustomSearchFeature FeatureTeamSearchVisibilityAvailableByDefault $ do - getTeamSearchVisibility tid2 owner2 Public.FeatureStatusEnabled - getTeamSearchVisibilityInternal tid2 Public.FeatureStatusEnabled - getTeamSearchVisibilityFeatureConfig team2member Public.FeatureStatusEnabled + getTeamSearchVisibility tid2 owner2 FeatureStatusEnabled + getTeamSearchVisibilityInternal tid2 FeatureStatusEnabled + getTeamSearchVisibilityFeatureConfig team2member FeatureStatusEnabled - setTeamSearchVisibilityInternal tid2 Public.FeatureStatusDisabled - getTeamSearchVisibility tid2 owner2 Public.FeatureStatusDisabled - getTeamSearchVisibilityInternal tid2 Public.FeatureStatusDisabled - getTeamSearchVisibilityFeatureConfig team2member Public.FeatureStatusDisabled + setTeamSearchVisibilityInternal tid2 FeatureStatusDisabled + getTeamSearchVisibility tid2 owner2 FeatureStatusDisabled + getTeamSearchVisibilityInternal tid2 FeatureStatusDisabled + getTeamSearchVisibilityFeatureConfig team2member FeatureStatusDisabled - setTeamSearchVisibilityInternal tid2 Public.FeatureStatusEnabled - getTeamSearchVisibility tid2 owner2 Public.FeatureStatusEnabled - getTeamSearchVisibilityInternal tid2 Public.FeatureStatusEnabled - getTeamSearchVisibilityFeatureConfig team2member Public.FeatureStatusEnabled + setTeamSearchVisibilityInternal tid2 FeatureStatusEnabled + getTeamSearchVisibility tid2 owner2 FeatureStatusEnabled + getTeamSearchVisibilityInternal tid2 FeatureStatusEnabled + getTeamSearchVisibilityFeatureConfig team2member FeatureStatusEnabled getClassifiedDomains :: (HasCallStack, HasGalley m, MonadIO m, MonadHttp m, MonadCatch m) => UserId -> TeamId -> - Public.WithStatusNoLock Public.ClassifiedDomainsConfig -> + WithStatusNoLock ClassifiedDomainsConfig -> m () getClassifiedDomains member tid = - assertFlagWithConfig @Public.ClassifiedDomainsConfig $ - getTeamFeatureFlag @Public.ClassifiedDomainsConfig member tid + assertFlagWithConfig @ClassifiedDomainsConfig $ + getTeamFeatureFlag @ClassifiedDomainsConfig member tid getClassifiedDomainsInternal :: (HasCallStack, HasGalley m, MonadIO m, MonadHttp m, MonadCatch m) => TeamId -> - Public.WithStatusNoLock Public.ClassifiedDomainsConfig -> + WithStatusNoLock ClassifiedDomainsConfig -> m () getClassifiedDomainsInternal tid = - assertFlagWithConfig @Public.ClassifiedDomainsConfig $ - getTeamFeatureFlagInternal @Public.ClassifiedDomainsConfig tid + assertFlagWithConfig @ClassifiedDomainsConfig $ + getTeamFeatureFlagInternal @ClassifiedDomainsConfig tid testClassifiedDomainsEnabled :: TestM () testClassifiedDomainsEnabled = do (_owner, tid, member : _) <- createBindingTeamWithNMembers 1 let expected = - Public.WithStatusNoLock Public.FeatureStatusEnabled (Public.ClassifiedDomainsConfig [Domain "example.com"]) Public.FeatureTTLUnlimited + WithStatusNoLock FeatureStatusEnabled (ClassifiedDomainsConfig [Domain "example.com"]) FeatureTTLUnlimited let getClassifiedDomainsFeatureConfig :: (HasCallStack, HasGalley m, MonadIO m, MonadHttp m, MonadCatch m) => UserId -> - Public.WithStatusNoLock Public.ClassifiedDomainsConfig -> + WithStatusNoLock ClassifiedDomainsConfig -> m () getClassifiedDomainsFeatureConfig uid expected' = do - result <- Util.getFeatureConfig @Public.ClassifiedDomainsConfig uid - liftIO $ Public.wsStatus result @?= Public.wssStatus expected' - liftIO $ Public.wsConfig result @?= Public.wssConfig expected' + result <- Util.getFeatureConfig @ClassifiedDomainsConfig uid + liftIO $ wsStatus result @?= wssStatus expected' + liftIO $ wsConfig result @?= wssConfig expected' getClassifiedDomains member tid expected getClassifiedDomainsInternal tid expected @@ -441,23 +468,23 @@ testClassifiedDomainsDisabled :: TestM () testClassifiedDomainsDisabled = do (_owner, tid, member : _) <- createBindingTeamWithNMembers 1 let expected = - Public.WithStatusNoLock Public.FeatureStatusDisabled (Public.ClassifiedDomainsConfig []) Public.FeatureTTLUnlimited + WithStatusNoLock FeatureStatusDisabled (ClassifiedDomainsConfig []) FeatureTTLUnlimited let getClassifiedDomainsFeatureConfig :: (HasCallStack, HasGalley m, MonadIO m, MonadHttp m, MonadCatch m) => UserId -> - Public.WithStatusNoLock Public.ClassifiedDomainsConfig -> + WithStatusNoLock ClassifiedDomainsConfig -> m () getClassifiedDomainsFeatureConfig uid expected' = do - result <- Util.getFeatureConfig @Public.ClassifiedDomainsConfig uid - liftIO $ Public.wsStatus result @?= Public.wssStatus expected' - liftIO $ Public.wsConfig result @?= Public.wssConfig expected' + result <- Util.getFeatureConfig @ClassifiedDomainsConfig uid + liftIO $ wsStatus result @?= wssStatus expected' + liftIO $ wsConfig result @?= wssConfig expected' let classifiedDomainsDisabled opts = opts & over (optSettings . setFeatureFlags . flagClassifiedDomains) - (\(ImplicitLockStatus s) -> ImplicitLockStatus (s & Public.setStatus Public.FeatureStatusDisabled & Public.setConfig (Public.ClassifiedDomainsConfig []))) + (\(ImplicitLockStatus s) -> ImplicitLockStatus (s & setStatus FeatureStatusDisabled & setConfig (ClassifiedDomainsConfig []))) withSettingsOverrides classifiedDomainsDisabled $ do getClassifiedDomains member tid expected getClassifiedDomainsInternal tid expected @@ -467,13 +494,13 @@ testSimpleFlag :: forall cfg. ( HasCallStack, Typeable cfg, - Public.IsFeatureConfig cfg, - KnownSymbol (Public.FeatureSymbol cfg), - Public.FeatureTrivialConfig cfg, + IsFeatureConfig cfg, + KnownSymbol (FeatureSymbol cfg), + FeatureTrivialConfig cfg, ToSchema cfg, - FromJSON (Public.WithStatusNoLock cfg) + FromJSON (WithStatusNoLock cfg) ) => - Public.FeatureStatus -> + FeatureStatus -> TestM () testSimpleFlag defaultValue = testSimpleFlagTTL @cfg defaultValue FeatureTTLUnlimited @@ -481,13 +508,13 @@ testSimpleFlagTTLOverride :: forall cfg. ( HasCallStack, Typeable cfg, - Public.IsFeatureConfig cfg, - KnownSymbol (Public.FeatureSymbol cfg), - Public.FeatureTrivialConfig cfg, + IsFeatureConfig cfg, + KnownSymbol (FeatureSymbol cfg), + FeatureTrivialConfig cfg, ToSchema cfg, - FromJSON (Public.WithStatusNoLock cfg) + FromJSON (WithStatusNoLock cfg) ) => - Public.FeatureStatus -> + FeatureStatus -> FeatureTTL -> FeatureTTL -> TestM () @@ -495,23 +522,23 @@ testSimpleFlagTTLOverride defaultValue ttl ttlAfter = do (_owner, tid, member : _) <- createBindingTeamWithNMembers 1 nonMember <- randomUser - let getFlag :: HasCallStack => Public.FeatureStatus -> TestM () + let getFlag :: HasCallStack => FeatureStatus -> TestM () getFlag expected = eventually $ do flip (assertFlagNoConfig @cfg) expected $ getTeamFeatureFlag @cfg member tid - getFeatureConfig :: HasCallStack => Public.FeatureStatus -> FeatureTTL -> TestM () + getFeatureConfig :: HasCallStack => FeatureStatus -> FeatureTTL -> TestM () getFeatureConfig expectedStatus expectedTtl = eventually $ do actual <- Util.getFeatureConfig @cfg member - liftIO $ Public.wsStatus actual @?= expectedStatus - liftIO $ checkTtl (Public.wsTTL actual) expectedTtl + liftIO $ wsStatus actual @?= expectedStatus + liftIO $ checkTtl (wsTTL actual) expectedTtl - getFlagInternal :: HasCallStack => Public.FeatureStatus -> TestM () + getFlagInternal :: HasCallStack => FeatureStatus -> TestM () getFlagInternal expected = eventually $ do flip (assertFlagNoConfig @cfg) expected $ getTeamFeatureFlagInternal @cfg tid - setFlagInternal :: Public.FeatureStatus -> FeatureTTL -> TestM () + setFlagInternal :: FeatureStatus -> FeatureTTL -> TestM () setFlagInternal statusValue ttl' = - void $ putTeamFeatureFlagInternalTTL @cfg expect2xx tid (Public.WithStatusNoLock statusValue (Public.trivialConfig @cfg) ttl') + void $ putTeamFeatureFlagInternalTTL @cfg expect2xx tid (WithStatusNoLock statusValue (trivialConfig @cfg) ttl') select :: PrepQuery R (Identity TeamId) (Identity (Maybe FeatureTTL)) select = fromString "select ttl(conference_calling) from team_features where team_id = ?" @@ -554,8 +581,8 @@ testSimpleFlagTTLOverride defaultValue ttl ttlAfter = do assertFlagForbidden $ getTeamFeatureFlag @cfg nonMember tid let otherValue = case defaultValue of - Public.FeatureStatusDisabled -> Public.FeatureStatusEnabled - Public.FeatureStatusEnabled -> Public.FeatureStatusDisabled + FeatureStatusDisabled -> FeatureStatusEnabled + FeatureStatusEnabled -> FeatureStatusDisabled -- Initial value should be the default value getFlag defaultValue @@ -621,35 +648,35 @@ testSimpleFlagTTL :: forall cfg. ( HasCallStack, Typeable cfg, - Public.IsFeatureConfig cfg, - KnownSymbol (Public.FeatureSymbol cfg), - Public.FeatureTrivialConfig cfg, + IsFeatureConfig cfg, + KnownSymbol (FeatureSymbol cfg), + FeatureTrivialConfig cfg, ToSchema cfg, - FromJSON (Public.WithStatusNoLock cfg) + FromJSON (WithStatusNoLock cfg) ) => - Public.FeatureStatus -> + FeatureStatus -> FeatureTTL -> TestM () testSimpleFlagTTL defaultValue ttl = do (_owner, tid, member : _) <- createBindingTeamWithNMembers 1 nonMember <- randomUser - let getFlag :: HasCallStack => Public.FeatureStatus -> TestM () + let getFlag :: HasCallStack => FeatureStatus -> TestM () getFlag expected = flip (assertFlagNoConfig @cfg) expected $ getTeamFeatureFlag @cfg member tid - getFeatureConfig :: HasCallStack => Public.FeatureStatus -> TestM () + getFeatureConfig :: HasCallStack => FeatureStatus -> TestM () getFeatureConfig expected = do actual <- Util.getFeatureConfig @cfg member - liftIO $ Public.wsStatus actual @?= expected + liftIO $ wsStatus actual @?= expected - getFlagInternal :: HasCallStack => Public.FeatureStatus -> TestM () + getFlagInternal :: HasCallStack => FeatureStatus -> TestM () getFlagInternal expected = flip (assertFlagNoConfig @cfg) expected $ getTeamFeatureFlagInternal @cfg tid - setFlagInternal :: Public.FeatureStatus -> FeatureTTL -> TestM () + setFlagInternal :: FeatureStatus -> FeatureTTL -> TestM () setFlagInternal statusValue ttl' = - void $ putTeamFeatureFlagInternalTTL @cfg expect2xx tid (Public.WithStatusNoLock statusValue (Public.trivialConfig @cfg) ttl') + void $ putTeamFeatureFlagInternalTTL @cfg expect2xx tid (WithStatusNoLock statusValue (trivialConfig @cfg) ttl') select :: PrepQuery R (Identity TeamId) (Identity (Maybe FeatureTTL)) select = fromString "select ttl(conference_calling) from team_features where team_id = ?" @@ -677,8 +704,8 @@ testSimpleFlagTTL defaultValue ttl = do assertFlagForbidden $ getTeamFeatureFlag @cfg nonMember tid let otherValue = case defaultValue of - Public.FeatureStatusDisabled -> Public.FeatureStatusEnabled - Public.FeatureStatusEnabled -> Public.FeatureStatusDisabled + FeatureStatusDisabled -> FeatureStatusEnabled + FeatureStatusEnabled -> FeatureStatusDisabled -- Initial value should be the default value getFlag defaultValue @@ -718,32 +745,32 @@ testSimpleFlagWithLockStatus :: Typeable cfg, Eq cfg, Show cfg, - Public.FeatureTrivialConfig cfg, - Public.IsFeatureConfig cfg, - KnownSymbol (Public.FeatureSymbol cfg), + FeatureTrivialConfig cfg, + IsFeatureConfig cfg, + KnownSymbol (FeatureSymbol cfg), ToSchema cfg, - ToJSON (Public.WithStatusNoLock cfg) + ToJSON (WithStatusNoLock cfg) ) => - Public.FeatureStatus -> - Public.LockStatus -> + FeatureStatus -> + LockStatus -> TestM () testSimpleFlagWithLockStatus defaultStatus defaultLockStatus = do galley <- viewGalley (owner, tid, member : _) <- createBindingTeamWithNMembers 1 nonMember <- randomUser - let getFlag :: HasCallStack => Public.FeatureStatus -> Public.LockStatus -> TestM () + let getFlag :: HasCallStack => FeatureStatus -> LockStatus -> TestM () getFlag expectedStatus expectedLockStatus = do let flag = getTeamFeatureFlag @cfg member tid assertFlagNoConfigWithLockStatus @cfg flag expectedStatus expectedLockStatus - getFeatureConfig :: HasCallStack => Public.FeatureStatus -> Public.LockStatus -> TestM () + getFeatureConfig :: HasCallStack => FeatureStatus -> LockStatus -> TestM () getFeatureConfig expectedStatus expectedLockStatus = do actual <- Util.getFeatureConfig @cfg member - liftIO $ Public.wsStatus actual @?= expectedStatus - liftIO $ Public.wsLockStatus actual @?= expectedLockStatus + liftIO $ wsStatus actual @?= expectedStatus + liftIO $ wsLockStatus actual @?= expectedLockStatus - getFlagInternal :: HasCallStack => Public.FeatureStatus -> Public.LockStatus -> TestM () + getFlagInternal :: HasCallStack => FeatureStatus -> LockStatus -> TestM () getFlagInternal expectedStatus expectedLockStatus = do let flag = getTeamFeatureFlagInternal @cfg tid assertFlagNoConfigWithLockStatus @cfg flag expectedStatus expectedLockStatus @@ -753,19 +780,19 @@ testSimpleFlagWithLockStatus defaultStatus defaultLockStatus = do getFeatureConfig expectedStatus expectedLockStatus getFlagInternal expectedStatus expectedLockStatus - setFlagWithGalley :: Public.FeatureStatus -> TestM () + setFlagWithGalley :: FeatureStatus -> TestM () setFlagWithGalley statusValue = - putTeamFeatureFlagWithGalley @cfg galley owner tid (Public.WithStatusNoLock statusValue (Public.trivialConfig @cfg) Public.FeatureTTLUnlimited) + putTeamFeatureFlagWithGalley @cfg galley owner tid (WithStatusNoLock statusValue (trivialConfig @cfg) FeatureTTLUnlimited) !!! statusCode === const 200 - assertSetStatusForbidden :: Public.FeatureStatus -> TestM () + assertSetStatusForbidden :: FeatureStatus -> TestM () assertSetStatusForbidden statusValue = - putTeamFeatureFlagWithGalley @cfg galley owner tid (Public.WithStatusNoLock statusValue (Public.trivialConfig @cfg) Public.FeatureTTLUnlimited) + putTeamFeatureFlagWithGalley @cfg galley owner tid (WithStatusNoLock statusValue (trivialConfig @cfg) FeatureTTLUnlimited) !!! statusCode === const 409 - setLockStatus :: Public.LockStatus -> TestM () + setLockStatus :: LockStatus -> TestM () setLockStatus lockStatus = Util.setLockStatusInternal @cfg galley tid lockStatus !!! statusCode @@ -774,14 +801,14 @@ testSimpleFlagWithLockStatus defaultStatus defaultLockStatus = do assertFlagForbidden $ getTeamFeatureFlag @cfg nonMember tid let otherStatus = case defaultStatus of - Public.FeatureStatusDisabled -> Public.FeatureStatusEnabled - Public.FeatureStatusEnabled -> Public.FeatureStatusDisabled + FeatureStatusDisabled -> FeatureStatusEnabled + FeatureStatusEnabled -> FeatureStatusDisabled -- Initial status and lock status should be the defaults getFlags defaultStatus defaultLockStatus -- unlock feature if it is locked - when (defaultLockStatus == Public.LockStatusLocked) $ setLockStatus Public.LockStatusUnlocked + when (defaultLockStatus == LockStatusLocked) $ setLockStatus LockStatusUnlocked -- setting should work cannon <- view tsCannon @@ -790,19 +817,19 @@ testSimpleFlagWithLockStatus defaultStatus defaultLockStatus = do setFlagWithGalley otherStatus void . liftIO $ WS.assertMatch (5 # Second) ws $ - wsAssertFeatureConfigWithLockStatusUpdate @cfg otherStatus Public.LockStatusUnlocked + wsAssertFeatureConfigWithLockStatusUpdate @cfg otherStatus LockStatusUnlocked - getFlags otherStatus Public.LockStatusUnlocked + getFlags otherStatus LockStatusUnlocked -- lock feature - setLockStatus Public.LockStatusLocked + setLockStatus LockStatusLocked -- feature status should now be the default again - getFlags defaultStatus Public.LockStatusLocked + getFlags defaultStatus LockStatusLocked assertSetStatusForbidden defaultStatus -- unlock feature - setLockStatus Public.LockStatusUnlocked + setLockStatus LockStatusUnlocked -- feature status should be the previously set value - getFlags otherStatus Public.LockStatusUnlocked + getFlags otherStatus LockStatusUnlocked -- clean up setFlagWithGalley defaultStatus @@ -811,34 +838,34 @@ testSimpleFlagWithLockStatus defaultStatus defaultLockStatus = do testSelfDeletingMessages :: TestM () testSelfDeletingMessages = do - defLockStatus :: Public.LockStatus <- + defLockStatus :: LockStatus <- view ( tsGConf . optSettings . setFeatureFlags . flagSelfDeletingMessages . unDefaults - . to Public.wsLockStatus + . to wsLockStatus ) -- personal users - let settingWithoutLockStatus :: FeatureStatus -> Int32 -> Public.WithStatusNoLock Public.SelfDeletingMessagesConfig + let settingWithoutLockStatus :: FeatureStatus -> Int32 -> WithStatusNoLock SelfDeletingMessagesConfig settingWithoutLockStatus stat tout = - Public.WithStatusNoLock + WithStatusNoLock stat - (Public.SelfDeletingMessagesConfig tout) - Public.FeatureTTLUnlimited - settingWithLockStatus :: FeatureStatus -> Int32 -> Public.LockStatus -> Public.WithStatus Public.SelfDeletingMessagesConfig + (SelfDeletingMessagesConfig tout) + FeatureTTLUnlimited + settingWithLockStatus :: FeatureStatus -> Int32 -> LockStatus -> WithStatus SelfDeletingMessagesConfig settingWithLockStatus stat tout lockStatus = - Public.withStatus + withStatus stat lockStatus - (Public.SelfDeletingMessagesConfig tout) - Public.FeatureTTLUnlimited + (SelfDeletingMessagesConfig tout) + FeatureTTLUnlimited personalUser <- randomUser do - result <- Util.getFeatureConfig @Public.SelfDeletingMessagesConfig personalUser + result <- Util.getFeatureConfig @SelfDeletingMessagesConfig personalUser liftIO $ result @?= settingWithLockStatus FeatureStatusEnabled 0 defLockStatus -- team users @@ -848,7 +875,7 @@ testSelfDeletingMessages = do let checkSet :: FeatureStatus -> Int32 -> Int -> TestM () checkSet stat tout expectedStatusCode = do - putTeamFeatureFlagInternal @Public.SelfDeletingMessagesConfig + putTeamFeatureFlagInternal @SelfDeletingMessagesConfig galley tid (settingWithoutLockStatus stat tout) @@ -856,21 +883,21 @@ testSelfDeletingMessages = do === const expectedStatusCode -- internal, public (/team/:tid/features), and team-agnostic (/feature-configs). - checkGet :: HasCallStack => FeatureStatus -> Int32 -> Public.LockStatus -> TestM () + checkGet :: HasCallStack => FeatureStatus -> Int32 -> LockStatus -> TestM () checkGet stat tout lockStatus = do let expected = settingWithLockStatus stat tout lockStatus forM_ - [ getTeamFeatureFlagInternal @Public.SelfDeletingMessagesConfig tid, - getTeamFeatureFlagWithGalley @Public.SelfDeletingMessagesConfig galley owner tid + [ getTeamFeatureFlagInternal @SelfDeletingMessagesConfig tid, + getTeamFeatureFlagWithGalley @SelfDeletingMessagesConfig galley owner tid ] (!!! responseJsonEither === const (Right expected)) - result <- Util.getFeatureConfig @Public.SelfDeletingMessagesConfig owner + result <- Util.getFeatureConfig @SelfDeletingMessagesConfig owner liftIO $ result @?= expected - checkSetLockStatus :: HasCallStack => Public.LockStatus -> TestM () + checkSetLockStatus :: HasCallStack => LockStatus -> TestM () checkSetLockStatus status = do - Util.setLockStatusInternal @Public.SelfDeletingMessagesConfig galley tid status + Util.setLockStatusInternal @SelfDeletingMessagesConfig galley tid status !!! statusCode === const 200 @@ -880,100 +907,100 @@ testSelfDeletingMessages = do checkGet FeatureStatusEnabled 0 defLockStatus case defLockStatus of - Public.LockStatusLocked -> do + LockStatusLocked -> do checkSet FeatureStatusDisabled 0 409 - Public.LockStatusUnlocked -> do + LockStatusUnlocked -> do checkSet FeatureStatusDisabled 0 200 - checkGet FeatureStatusDisabled 0 Public.LockStatusUnlocked + checkGet FeatureStatusDisabled 0 LockStatusUnlocked checkSet FeatureStatusEnabled 0 200 - checkGet FeatureStatusEnabled 0 Public.LockStatusUnlocked + checkGet FeatureStatusEnabled 0 LockStatusUnlocked -- now don't worry about what's in the config, write something to cassandra, and test with that. - checkSetLockStatus Public.LockStatusLocked - checkGet FeatureStatusEnabled 0 Public.LockStatusLocked + checkSetLockStatus LockStatusLocked + checkGet FeatureStatusEnabled 0 LockStatusLocked checkSet FeatureStatusDisabled 0 409 - checkGet FeatureStatusEnabled 0 Public.LockStatusLocked + checkGet FeatureStatusEnabled 0 LockStatusLocked checkSet FeatureStatusEnabled 30 409 - checkGet FeatureStatusEnabled 0 Public.LockStatusLocked - checkSetLockStatus Public.LockStatusUnlocked - checkGet FeatureStatusEnabled 0 Public.LockStatusUnlocked + checkGet FeatureStatusEnabled 0 LockStatusLocked + checkSetLockStatus LockStatusUnlocked + checkGet FeatureStatusEnabled 0 LockStatusUnlocked checkSet FeatureStatusDisabled 0 200 - checkGet FeatureStatusDisabled 0 Public.LockStatusUnlocked + checkGet FeatureStatusDisabled 0 LockStatusUnlocked checkSet FeatureStatusEnabled 30 200 - checkGet FeatureStatusEnabled 30 Public.LockStatusUnlocked + checkGet FeatureStatusEnabled 30 LockStatusUnlocked checkSet FeatureStatusDisabled 30 200 - checkGet FeatureStatusDisabled 30 Public.LockStatusUnlocked - checkSetLockStatus Public.LockStatusLocked - checkGet FeatureStatusEnabled 0 Public.LockStatusLocked + checkGet FeatureStatusDisabled 30 LockStatusUnlocked + checkSetLockStatus LockStatusLocked + checkGet FeatureStatusEnabled 0 LockStatusLocked checkSet FeatureStatusEnabled 50 409 - checkSetLockStatus Public.LockStatusUnlocked - checkGet FeatureStatusDisabled 30 Public.LockStatusUnlocked + checkSetLockStatus LockStatusUnlocked + checkGet FeatureStatusDisabled 30 LockStatusUnlocked testGuestLinksInternal :: TestM () testGuestLinksInternal = do galley <- viewGalley testGuestLinks - (const $ getTeamFeatureFlagInternal @Public.GuestLinksConfig) - (const $ putTeamFeatureFlagInternal @Public.GuestLinksConfig galley) - (Util.setLockStatusInternal @Public.GuestLinksConfig galley) + (const $ getTeamFeatureFlagInternal @GuestLinksConfig) + (const $ putTeamFeatureFlagInternal @GuestLinksConfig galley) + (Util.setLockStatusInternal @GuestLinksConfig galley) testGuestLinksPublic :: TestM () testGuestLinksPublic = do galley <- viewGalley testGuestLinks - (getTeamFeatureFlagWithGalley @Public.GuestLinksConfig galley) - (putTeamFeatureFlagWithGalley @Public.GuestLinksConfig galley) - (Util.setLockStatusInternal @Public.GuestLinksConfig galley) + (getTeamFeatureFlagWithGalley @GuestLinksConfig galley) + (putTeamFeatureFlagWithGalley @GuestLinksConfig galley) + (Util.setLockStatusInternal @GuestLinksConfig galley) testGuestLinks :: (UserId -> TeamId -> TestM ResponseLBS) -> - (UserId -> TeamId -> Public.WithStatusNoLock Public.GuestLinksConfig -> TestM ResponseLBS) -> - (TeamId -> Public.LockStatus -> TestM ResponseLBS) -> + (UserId -> TeamId -> WithStatusNoLock GuestLinksConfig -> TestM ResponseLBS) -> + (TeamId -> LockStatus -> TestM ResponseLBS) -> TestM () testGuestLinks getStatus putStatus setLockStatusInternal = do (owner, tid, []) <- createBindingTeamWithNMembers 0 - let checkGet :: HasCallStack => Public.FeatureStatus -> Public.LockStatus -> TestM () + let checkGet :: HasCallStack => FeatureStatus -> LockStatus -> TestM () checkGet status lock = getStatus owner tid !!! do statusCode === const 200 - responseJsonEither === const (Right (Public.withStatus status lock Public.GuestLinksConfig Public.FeatureTTLUnlimited)) + responseJsonEither === const (Right (withStatus status lock GuestLinksConfig FeatureTTLUnlimited)) - checkSet :: HasCallStack => Public.FeatureStatus -> Int -> TestM () + checkSet :: HasCallStack => FeatureStatus -> Int -> TestM () checkSet status expectedStatusCode = - putStatus owner tid (Public.WithStatusNoLock status Public.GuestLinksConfig Public.FeatureTTLUnlimited) !!! statusCode === const expectedStatusCode + putStatus owner tid (WithStatusNoLock status GuestLinksConfig FeatureTTLUnlimited) !!! statusCode === const expectedStatusCode - checkSetLockStatusInternal :: HasCallStack => Public.LockStatus -> TestM () + checkSetLockStatusInternal :: HasCallStack => LockStatus -> TestM () checkSetLockStatusInternal lockStatus = setLockStatusInternal tid lockStatus !!! statusCode === const 200 - checkGet Public.FeatureStatusEnabled Public.LockStatusUnlocked - checkSet Public.FeatureStatusDisabled 200 - checkGet Public.FeatureStatusDisabled Public.LockStatusUnlocked - checkSet Public.FeatureStatusEnabled 200 - checkGet Public.FeatureStatusEnabled Public.LockStatusUnlocked - checkSet Public.FeatureStatusDisabled 200 - checkGet Public.FeatureStatusDisabled Public.LockStatusUnlocked + checkGet FeatureStatusEnabled LockStatusUnlocked + checkSet FeatureStatusDisabled 200 + checkGet FeatureStatusDisabled LockStatusUnlocked + checkSet FeatureStatusEnabled 200 + checkGet FeatureStatusEnabled LockStatusUnlocked + checkSet FeatureStatusDisabled 200 + checkGet FeatureStatusDisabled LockStatusUnlocked -- when locks status is locked the team default feature status should be returned -- and the team feature status can not be changed - checkSetLockStatusInternal Public.LockStatusLocked - checkGet Public.FeatureStatusEnabled Public.LockStatusLocked - checkSet Public.FeatureStatusDisabled 409 + checkSetLockStatusInternal LockStatusLocked + checkGet FeatureStatusEnabled LockStatusLocked + checkSet FeatureStatusDisabled 409 -- when lock status is unlocked again the previously set feature status is restored - checkSetLockStatusInternal Public.LockStatusUnlocked - checkGet Public.FeatureStatusDisabled Public.LockStatusUnlocked + checkSetLockStatusInternal LockStatusUnlocked + checkGet FeatureStatusDisabled LockStatusUnlocked -- | Call 'GET /teams/:tid/features' and 'GET /feature-configs', and check if all -- features are there. testAllFeatures :: TestM () testAllFeatures = do - defLockStatus :: Public.LockStatus <- + defLockStatus :: LockStatus <- view ( tsGConf . optSettings . setFeatureFlags . flagSelfDeletingMessages . unDefaults - . to Public.wsLockStatus + . to wsLockStatus ) (_owner, tid, member : _) <- createBindingTeamWithNMembers 1 @@ -981,12 +1008,12 @@ testAllFeatures = do statusCode === const 200 responseJsonMaybe === const (Just (expected FeatureStatusEnabled defLockStatus {- determined by default in galley -})) - -- This block catches potential errors in the logic that reverts to default if there is a disinction made between + -- This block catches potential errors in the logic that reverts to default if there is a distinction made between -- 1. there is no row for a team_id in galley.team_features -- 2. there is a row for team_id in galley.team_features but the feature has a no entry (null value) galley <- viewGalley -- this sets the guest links config to its default value thereby creating a row for the team in galley.team_features - putTeamFeatureFlagInternal @Public.GuestLinksConfig galley tid (Public.WithStatusNoLock FeatureStatusEnabled Public.GuestLinksConfig Public.FeatureTTLUnlimited) + putTeamFeatureFlagInternal @GuestLinksConfig galley tid (WithStatusNoLock FeatureStatusEnabled GuestLinksConfig FeatureTTLUnlimited) !!! statusCode === const 200 getAllTeamFeatures member tid !!! do @@ -1003,23 +1030,24 @@ testAllFeatures = do responseJsonMaybe === const (Just (expected FeatureStatusEnabled defLockStatus {- determined by 'getAfcConferenceCallingDefNew' in brig -})) where expected confCalling lockStateSelfDeleting = - Public.AllFeatureConfigs - { Public.afcLegalholdStatus = Public.withStatus FeatureStatusDisabled Public.LockStatusUnlocked Public.LegalholdConfig Public.FeatureTTLUnlimited, - Public.afcSSOStatus = Public.withStatus FeatureStatusDisabled Public.LockStatusUnlocked Public.SSOConfig Public.FeatureTTLUnlimited, - Public.afcTeamSearchVisibilityAvailable = Public.withStatus FeatureStatusDisabled Public.LockStatusUnlocked Public.SearchVisibilityAvailableConfig Public.FeatureTTLUnlimited, - Public.afcValidateSAMLEmails = Public.withStatus FeatureStatusEnabled Public.LockStatusUnlocked Public.ValidateSAMLEmailsConfig Public.FeatureTTLUnlimited, - Public.afcDigitalSignatures = Public.withStatus FeatureStatusDisabled Public.LockStatusUnlocked Public.DigitalSignaturesConfig Public.FeatureTTLUnlimited, - Public.afcAppLock = Public.withStatus FeatureStatusEnabled Public.LockStatusUnlocked (Public.AppLockConfig (Public.EnforceAppLock False) (60 :: Int32)) Public.FeatureTTLUnlimited, - Public.afcFileSharing = Public.withStatus FeatureStatusEnabled Public.LockStatusUnlocked Public.FileSharingConfig Public.FeatureTTLUnlimited, - Public.afcClassifiedDomains = Public.withStatus FeatureStatusEnabled Public.LockStatusUnlocked (Public.ClassifiedDomainsConfig [Domain "example.com"]) Public.FeatureTTLUnlimited, - Public.afcConferenceCalling = Public.withStatus confCalling Public.LockStatusUnlocked Public.ConferenceCallingConfig Public.FeatureTTLUnlimited, - Public.afcSelfDeletingMessages = Public.withStatus FeatureStatusEnabled lockStateSelfDeleting (Public.SelfDeletingMessagesConfig 0) Public.FeatureTTLUnlimited, - Public.afcGuestLink = Public.withStatus FeatureStatusEnabled Public.LockStatusUnlocked Public.GuestLinksConfig Public.FeatureTTLUnlimited, - Public.afcSndFactorPasswordChallenge = Public.withStatus FeatureStatusDisabled Public.LockStatusLocked Public.SndFactorPasswordChallengeConfig Public.FeatureTTLUnlimited, - Public.afcMLS = Public.withStatus FeatureStatusDisabled Public.LockStatusUnlocked (Public.MLSConfig [] ProtocolProteusTag [MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519] MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519) Public.FeatureTTLUnlimited, - Public.afcSearchVisibilityInboundConfig = Public.withStatus FeatureStatusDisabled Public.LockStatusUnlocked Public.SearchVisibilityInboundConfig Public.FeatureTTLUnlimited, - Public.afcExposeInvitationURLsToTeamAdmin = Public.withStatus FeatureStatusDisabled Public.LockStatusLocked Public.ExposeInvitationURLsToTeamAdminConfig Public.FeatureTTLUnlimited, - Public.afcOutlookCalIntegration = Public.withStatus FeatureStatusDisabled Public.LockStatusLocked Public.OutlookCalIntegrationConfig Public.FeatureTTLUnlimited + AllFeatureConfigs + { afcLegalholdStatus = withStatus FeatureStatusDisabled LockStatusUnlocked LegalholdConfig FeatureTTLUnlimited, + afcSSOStatus = withStatus FeatureStatusDisabled LockStatusUnlocked SSOConfig FeatureTTLUnlimited, + afcTeamSearchVisibilityAvailable = withStatus FeatureStatusDisabled LockStatusUnlocked SearchVisibilityAvailableConfig FeatureTTLUnlimited, + afcValidateSAMLEmails = withStatus FeatureStatusEnabled LockStatusUnlocked ValidateSAMLEmailsConfig FeatureTTLUnlimited, + afcDigitalSignatures = withStatus FeatureStatusDisabled LockStatusUnlocked DigitalSignaturesConfig FeatureTTLUnlimited, + afcAppLock = withStatus FeatureStatusEnabled LockStatusUnlocked (AppLockConfig (EnforceAppLock False) (60 :: Int32)) FeatureTTLUnlimited, + afcFileSharing = withStatus FeatureStatusEnabled LockStatusUnlocked FileSharingConfig FeatureTTLUnlimited, + afcClassifiedDomains = withStatus FeatureStatusEnabled LockStatusUnlocked (ClassifiedDomainsConfig [Domain "example.com"]) FeatureTTLUnlimited, + afcConferenceCalling = withStatus confCalling LockStatusUnlocked ConferenceCallingConfig FeatureTTLUnlimited, + afcSelfDeletingMessages = withStatus FeatureStatusEnabled lockStateSelfDeleting (SelfDeletingMessagesConfig 0) FeatureTTLUnlimited, + afcGuestLink = withStatus FeatureStatusEnabled LockStatusUnlocked GuestLinksConfig FeatureTTLUnlimited, + afcSndFactorPasswordChallenge = withStatus FeatureStatusDisabled LockStatusLocked SndFactorPasswordChallengeConfig FeatureTTLUnlimited, + afcMLS = withStatus FeatureStatusDisabled LockStatusUnlocked (MLSConfig [] ProtocolProteusTag [MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519] MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519) FeatureTTLUnlimited, + afcSearchVisibilityInboundConfig = withStatus FeatureStatusDisabled LockStatusUnlocked SearchVisibilityInboundConfig FeatureTTLUnlimited, + afcExposeInvitationURLsToTeamAdmin = withStatus FeatureStatusDisabled LockStatusLocked ExposeInvitationURLsToTeamAdminConfig FeatureTTLUnlimited, + afcOutlookCalIntegration = withStatus FeatureStatusDisabled LockStatusLocked OutlookCalIntegrationConfig FeatureTTLUnlimited, + afcMlsE2EId = withStatus FeatureStatusDisabled LockStatusUnlocked (MlsE2EIdConfig Nothing) FeatureTTLUnlimited } testFeatureConfigConsistency :: TestM () @@ -1048,17 +1076,17 @@ testSearchVisibilityInbound = do let defaultValue = FeatureStatusDisabled (_owner, tid, _) <- createBindingTeamWithNMembers 1 - let getFlagInternal :: HasCallStack => Public.FeatureStatus -> TestM () + let getFlagInternal :: HasCallStack => FeatureStatus -> TestM () getFlagInternal expected = - flip (assertFlagNoConfig @Public.SearchVisibilityInboundConfig) expected $ getTeamFeatureFlagInternal @Public.SearchVisibilityInboundConfig tid + flip (assertFlagNoConfig @SearchVisibilityInboundConfig) expected $ getTeamFeatureFlagInternal @SearchVisibilityInboundConfig tid - setFlagInternal :: Public.FeatureStatus -> TestM () + setFlagInternal :: FeatureStatus -> TestM () setFlagInternal statusValue = - void $ putTeamFeatureFlagInternal @Public.SearchVisibilityInboundConfig expect2xx tid (Public.WithStatusNoLock statusValue Public.SearchVisibilityInboundConfig Public.FeatureTTLUnlimited) + void $ putTeamFeatureFlagInternal @SearchVisibilityInboundConfig expect2xx tid (WithStatusNoLock statusValue SearchVisibilityInboundConfig FeatureTTLUnlimited) let otherValue = case defaultValue of - Public.FeatureStatusDisabled -> Public.FeatureStatusEnabled - Public.FeatureStatusEnabled -> Public.FeatureStatusDisabled + FeatureStatusDisabled -> FeatureStatusEnabled + FeatureStatusEnabled -> FeatureStatusDisabled -- Initial value should be the default value getFlagInternal defaultValue @@ -1070,27 +1098,114 @@ testFeatureNoConfigMultiSearchVisibilityInbound = do (_owner1, team1, _) <- createBindingTeamWithNMembers 0 (_owner2, team2, _) <- createBindingTeamWithNMembers 0 - let setFlagInternal :: TeamId -> Public.FeatureStatus -> TestM () + let setFlagInternal :: TeamId -> FeatureStatus -> TestM () setFlagInternal tid statusValue = - void $ putTeamFeatureFlagInternal @Public.SearchVisibilityInboundConfig expect2xx tid (Public.WithStatusNoLock statusValue Public.SearchVisibilityInboundConfig Public.FeatureTTLUnlimited) + void $ putTeamFeatureFlagInternal @SearchVisibilityInboundConfig expect2xx tid (WithStatusNoLock statusValue SearchVisibilityInboundConfig FeatureTTLUnlimited) - setFlagInternal team2 Public.FeatureStatusEnabled + setFlagInternal team2 FeatureStatusEnabled r <- - getFeatureStatusMulti @Public.SearchVisibilityInboundConfig (Multi.TeamFeatureNoConfigMultiRequest [team1, team2]) + getFeatureStatusMulti @SearchVisibilityInboundConfig (Multi.TeamFeatureNoConfigMultiRequest [team1, team2]) + WithStatus cfg -> + TestM () +testNonTrivialConfigNoTTL defaultCfg = do + (owner, tid, member : _) <- createBindingTeamWithNMembers 1 + nonMember <- randomUser + + galley <- viewGalley + cannon <- view tsCannon + + let getForTeam :: HasCallStack => WithStatusNoLock cfg -> TestM () + getForTeam expected = + flip assertFlagWithConfig expected $ getTeamFeatureFlag @cfg member tid + + getForTeamInternal :: HasCallStack => WithStatusNoLock cfg -> TestM () + getForTeamInternal expected = + flip assertFlagWithConfig expected $ getTeamFeatureFlagInternal @cfg tid + + getForUser :: HasCallStack => WithStatusNoLock cfg -> TestM () + getForUser expected = do + result <- Util.getFeatureConfig @cfg member + liftIO $ wsStatus result @?= wssStatus expected + liftIO $ wsConfig result @?= wssConfig expected + + getViaEndpoints :: HasCallStack => WithStatusNoLock cfg -> TestM () + getViaEndpoints expected = do + getForTeam expected + getForTeamInternal expected + getForUser expected + + setForTeam :: HasCallStack => WithStatusNoLock cfg -> TestM () + setForTeam wsnl = + putTeamFeatureFlagWithGalley @cfg galley owner tid wsnl + !!! statusCode + === const 200 + + setForTeamInternal :: HasCallStack => WithStatusNoLock cfg -> TestM () + setForTeamInternal wsnl = + void $ putTeamFeatureFlagInternal @cfg expect2xx tid wsnl + setLockStatus :: LockStatus -> TestM () + setLockStatus lockStatus = + Util.setLockStatusInternal @cfg galley tid lockStatus + !!! statusCode + === const 200 + + assertFlagForbidden $ getTeamFeatureFlag @cfg nonMember tid + + getViaEndpoints (forgetLock defaultCfg) + + -- unlock feature + setLockStatus LockStatusUnlocked + + config2 <- liftIO $ generate arbitrary <&> (forgetLock . setTTL FeatureTTLUnlimited) + config3 <- liftIO $ generate arbitrary <&> (forgetLock . setTTL FeatureTTLUnlimited) + + WS.bracketR cannon member $ \ws -> do + setForTeam config2 + void . liftIO $ + WS.assertMatch (5 # Second) ws $ + wsAssertFeatureConfigUpdate @cfg config2 LockStatusUnlocked + getViaEndpoints config2 + + WS.bracketR cannon member $ \ws -> do + setForTeamInternal config3 + void . liftIO $ + WS.assertMatch (5 # Second) ws $ + wsAssertFeatureConfigUpdate @cfg config3 LockStatusUnlocked + getViaEndpoints config3 + + -- lock the feature + setLockStatus LockStatusLocked + -- feature status should now be the default again + getViaEndpoints (forgetLock defaultCfg) + -- unlock feature + setLockStatus LockStatusUnlocked + -- feature status should be the previously set value + getViaEndpoints config3 testMLS :: TestM () testMLS = do @@ -1099,49 +1214,49 @@ testMLS = do galley <- viewGalley cannon <- view tsCannon - let getForTeam :: HasCallStack => Public.WithStatusNoLock MLSConfig -> TestM () + let getForTeam :: HasCallStack => WithStatusNoLock MLSConfig -> TestM () getForTeam expected = flip assertFlagWithConfig expected $ getTeamFeatureFlag @MLSConfig member tid - getForTeamInternal :: HasCallStack => Public.WithStatusNoLock MLSConfig -> TestM () + getForTeamInternal :: HasCallStack => WithStatusNoLock MLSConfig -> TestM () getForTeamInternal expected = - flip assertFlagWithConfig expected $ getTeamFeatureFlagInternal @Public.MLSConfig tid + flip assertFlagWithConfig expected $ getTeamFeatureFlagInternal @MLSConfig tid - getForUser :: HasCallStack => Public.WithStatusNoLock MLSConfig -> TestM () + getForUser :: HasCallStack => WithStatusNoLock MLSConfig -> TestM () getForUser expected = do result <- Util.getFeatureConfig @MLSConfig member - liftIO $ Public.wsStatus result @?= Public.wssStatus expected - liftIO $ Public.wsConfig result @?= Public.wssConfig expected + liftIO $ wsStatus result @?= wssStatus expected + liftIO $ wsConfig result @?= wssConfig expected - getViaEndpoints :: HasCallStack => Public.WithStatusNoLock MLSConfig -> TestM () + getViaEndpoints :: HasCallStack => WithStatusNoLock MLSConfig -> TestM () getViaEndpoints expected = do getForTeam expected getForTeamInternal expected getForUser expected - setForTeam :: HasCallStack => Public.WithStatusNoLock MLSConfig -> TestM () + setForTeam :: HasCallStack => WithStatusNoLock MLSConfig -> TestM () setForTeam wsnl = putTeamFeatureFlagWithGalley @MLSConfig galley owner tid wsnl !!! statusCode === const 200 - setForTeamInternal :: HasCallStack => Public.WithStatusNoLock MLSConfig -> TestM () + setForTeamInternal :: HasCallStack => WithStatusNoLock MLSConfig -> TestM () setForTeamInternal wsnl = - void $ putTeamFeatureFlagInternal @Public.MLSConfig expect2xx tid wsnl + void $ putTeamFeatureFlagInternal @MLSConfig expect2xx tid wsnl let cipherSuite = MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519 let defaultConfig = - Public.WithStatusNoLock + WithStatusNoLock FeatureStatusDisabled (MLSConfig [] ProtocolProteusTag [cipherSuite] cipherSuite) FeatureTTLUnlimited let config2 = - Public.WithStatusNoLock + WithStatusNoLock FeatureStatusEnabled (MLSConfig [member] ProtocolMLSTag [] cipherSuite) FeatureTTLUnlimited let config3 = - Public.WithStatusNoLock + WithStatusNoLock FeatureStatusDisabled (MLSConfig [] ProtocolMLSTag [cipherSuite] cipherSuite) FeatureTTLUnlimited @@ -1170,12 +1285,12 @@ testExposeInvitationURLsToTeamAdminTeamIdInAllowList = do void $ withSettingsOverrides (\opts -> opts & optSettings . setExposeInvitationURLsTeamAllowlist ?~ [tid]) $ do g <- viewGalley - assertExposeInvitationURLsToTeamAdminConfigStatus owner tid FeatureStatusDisabled Public.LockStatusUnlocked - let enabled = Public.WithStatusNoLock Public.FeatureStatusEnabled ExposeInvitationURLsToTeamAdminConfig Public.FeatureTTLUnlimited + assertExposeInvitationURLsToTeamAdminConfigStatus owner tid FeatureStatusDisabled LockStatusUnlocked + let enabled = WithStatusNoLock FeatureStatusEnabled ExposeInvitationURLsToTeamAdminConfig FeatureTTLUnlimited void $ putTeamFeatureFlagWithGalley @ExposeInvitationURLsToTeamAdminConfig g owner tid enabled !!! do const 200 === statusCode - assertExposeInvitationURLsToTeamAdminConfigStatus owner tid FeatureStatusEnabled Public.LockStatusUnlocked + assertExposeInvitationURLsToTeamAdminConfigStatus owner tid FeatureStatusEnabled LockStatusUnlocked testExposeInvitationURLsToTeamAdminEmptyAllowList :: TestM () testExposeInvitationURLsToTeamAdminEmptyAllowList = do @@ -1185,12 +1300,12 @@ testExposeInvitationURLsToTeamAdminEmptyAllowList = do void $ withSettingsOverrides (\opts -> opts & optSettings . setExposeInvitationURLsTeamAllowlist .~ Nothing) $ do g <- viewGalley - assertExposeInvitationURLsToTeamAdminConfigStatus owner tid FeatureStatusDisabled Public.LockStatusLocked - let enabled = Public.WithStatusNoLock Public.FeatureStatusEnabled ExposeInvitationURLsToTeamAdminConfig Public.FeatureTTLUnlimited + assertExposeInvitationURLsToTeamAdminConfigStatus owner tid FeatureStatusDisabled LockStatusLocked + let enabled = WithStatusNoLock FeatureStatusEnabled ExposeInvitationURLsToTeamAdminConfig FeatureTTLUnlimited void $ putTeamFeatureFlagWithGalley @ExposeInvitationURLsToTeamAdminConfig g owner tid enabled !!! do const 409 === statusCode - assertExposeInvitationURLsToTeamAdminConfigStatus owner tid FeatureStatusDisabled Public.LockStatusLocked + assertExposeInvitationURLsToTeamAdminConfigStatus owner tid FeatureStatusDisabled LockStatusLocked -- | Ensure that the server config takes precedence over a saved team config. -- @@ -1206,28 +1321,28 @@ testExposeInvitationURLsToTeamAdminServerConfigTakesPrecedence = do void $ withSettingsOverrides (\opts -> opts & optSettings . setExposeInvitationURLsTeamAllowlist ?~ [tid]) $ do g <- viewGalley - assertExposeInvitationURLsToTeamAdminConfigStatus owner tid FeatureStatusDisabled Public.LockStatusUnlocked - let enabled = Public.WithStatusNoLock Public.FeatureStatusEnabled ExposeInvitationURLsToTeamAdminConfig Public.FeatureTTLUnlimited + assertExposeInvitationURLsToTeamAdminConfigStatus owner tid FeatureStatusDisabled LockStatusUnlocked + let enabled = WithStatusNoLock FeatureStatusEnabled ExposeInvitationURLsToTeamAdminConfig FeatureTTLUnlimited void $ putTeamFeatureFlagWithGalley @ExposeInvitationURLsToTeamAdminConfig g owner tid enabled !!! do const 200 === statusCode - assertExposeInvitationURLsToTeamAdminConfigStatus owner tid FeatureStatusEnabled Public.LockStatusUnlocked + assertExposeInvitationURLsToTeamAdminConfigStatus owner tid FeatureStatusEnabled LockStatusUnlocked void $ withSettingsOverrides (\opts -> opts & optSettings . setExposeInvitationURLsTeamAllowlist .~ Nothing) $ do g <- viewGalley - assertExposeInvitationURLsToTeamAdminConfigStatus owner tid FeatureStatusDisabled Public.LockStatusLocked - let enabled = Public.WithStatusNoLock Public.FeatureStatusEnabled ExposeInvitationURLsToTeamAdminConfig Public.FeatureTTLUnlimited + assertExposeInvitationURLsToTeamAdminConfigStatus owner tid FeatureStatusDisabled LockStatusLocked + let enabled = WithStatusNoLock FeatureStatusEnabled ExposeInvitationURLsToTeamAdminConfig FeatureTTLUnlimited void $ putTeamFeatureFlagWithGalley @ExposeInvitationURLsToTeamAdminConfig g owner tid enabled !!! do const 409 === statusCode - assertExposeInvitationURLsToTeamAdminConfigStatus owner tid FeatureStatusDisabled Public.LockStatusLocked + assertExposeInvitationURLsToTeamAdminConfigStatus owner tid FeatureStatusDisabled LockStatusLocked assertExposeInvitationURLsToTeamAdminConfigStatus :: UserId -> TeamId -> FeatureStatus -> LockStatus -> TestM () assertExposeInvitationURLsToTeamAdminConfigStatus owner tid fStatus lStatus = do g <- viewGalley Util.getTeamFeatureFlagWithGalley @ExposeInvitationURLsToTeamAdminConfig g owner tid !!! do const 200 === statusCode - const (Right (Public.withStatus fStatus lStatus Public.ExposeInvitationURLsToTeamAdminConfig Public.FeatureTTLUnlimited)) === responseJsonEither + const (Right (withStatus fStatus lStatus ExposeInvitationURLsToTeamAdminConfig FeatureTTLUnlimited)) === responseJsonEither assertFlagForbidden :: HasCallStack => TestM ResponseLBS -> TestM () assertFlagForbidden res = do @@ -1239,16 +1354,16 @@ assertFlagNoConfig :: forall cfg. ( HasCallStack, Typeable cfg, - FromJSON (Public.WithStatusNoLock cfg) + FromJSON (WithStatusNoLock cfg) ) => TestM ResponseLBS -> - Public.FeatureStatus -> + FeatureStatus -> TestM () assertFlagNoConfig res expected = do res !!! do statusCode === const 200 - ( fmap Public.wssStatus - . responseJsonEither @(Public.WithStatusNoLock cfg) + ( fmap wssStatus + . responseJsonEither @(WithStatusNoLock cfg) ) === const (Right expected) @@ -1256,20 +1371,20 @@ assertFlagNoConfigWithLockStatus :: forall cfg. ( HasCallStack, Typeable cfg, - Public.FeatureTrivialConfig cfg, - FromJSON (Public.WithStatus cfg), + FeatureTrivialConfig cfg, + FromJSON (WithStatus cfg), Eq cfg, Show cfg ) => TestM ResponseLBS -> - Public.FeatureStatus -> - Public.LockStatus -> + FeatureStatus -> + LockStatus -> TestM () assertFlagNoConfigWithLockStatus res expectedStatus expectedLockStatus = do res !!! do statusCode === const 200 - responseJsonEither @(Public.WithStatus cfg) - === const (Right (Public.withStatus expectedStatus expectedLockStatus (Public.trivialConfig @cfg) Public.FeatureTTLUnlimited)) + responseJsonEither @(WithStatus cfg) + === const (Right (withStatus expectedStatus expectedLockStatus (trivialConfig @cfg) FeatureTTLUnlimited)) assertFlagWithConfig :: forall cfg m. @@ -1278,68 +1393,68 @@ assertFlagWithConfig :: ToSchema cfg, Show cfg, Typeable cfg, - Public.IsFeatureConfig cfg, + IsFeatureConfig cfg, MonadIO m, MonadCatch m ) => m ResponseLBS -> - Public.WithStatusNoLock cfg -> + WithStatusNoLock cfg -> m () assertFlagWithConfig response expected = do r <- response - let rJson = responseJsonEither @(Public.WithStatusNoLock cfg) r + let rJson = responseJsonEither @(WithStatusNoLock cfg) r pure r !!! statusCode === const 200 liftIO $ do - fmap Public.wssStatus rJson @?= (Right . Public.wssStatus $ expected) - fmap Public.wssConfig rJson @?= (Right . Public.wssConfig $ expected) + fmap wssStatus rJson @?= (Right . wssStatus $ expected) + fmap wssConfig rJson @?= (Right . wssConfig $ expected) wsAssertFeatureTrivialConfigUpdate :: forall cfg. - ( Public.IsFeatureConfig cfg, - KnownSymbol (Public.FeatureSymbol cfg), - Public.FeatureTrivialConfig cfg, + ( IsFeatureConfig cfg, + KnownSymbol (FeatureSymbol cfg), + FeatureTrivialConfig cfg, ToSchema cfg ) => - Public.FeatureStatus -> - Public.FeatureTTL -> + FeatureStatus -> + FeatureTTL -> Notification -> IO () wsAssertFeatureTrivialConfigUpdate status ttl notification = do let e :: FeatureConfig.Event = List1.head (WS.unpackPayload notification) FeatureConfig._eventType e @?= FeatureConfig.Update - FeatureConfig._eventFeatureName e @?= Public.featureName @cfg + FeatureConfig._eventFeatureName e @?= featureName @cfg FeatureConfig._eventData e @?= Aeson.toJSON - (Public.withStatus status (Public.wsLockStatus (Public.defFeatureStatus @cfg)) (Public.trivialConfig @cfg) ttl) + (withStatus status (wsLockStatus (defFeatureStatus @cfg)) (trivialConfig @cfg) ttl) wsAssertFeatureConfigWithLockStatusUpdate :: forall cfg. - ( Public.IsFeatureConfig cfg, + ( IsFeatureConfig cfg, ToSchema cfg, - KnownSymbol (Public.FeatureSymbol cfg), - Public.FeatureTrivialConfig cfg + KnownSymbol (FeatureSymbol cfg), + FeatureTrivialConfig cfg ) => - Public.FeatureStatus -> - Public.LockStatus -> + FeatureStatus -> + LockStatus -> Notification -> IO () wsAssertFeatureConfigWithLockStatusUpdate status lockStatus notification = do let e :: FeatureConfig.Event = List1.head (WS.unpackPayload notification) FeatureConfig._eventType e @?= FeatureConfig.Update - FeatureConfig._eventFeatureName e @?= (Public.featureName @cfg) - FeatureConfig._eventData e @?= Aeson.toJSON (Public.withStatus status lockStatus (Public.trivialConfig @cfg) FeatureTTLUnlimited) + FeatureConfig._eventFeatureName e @?= (featureName @cfg) + FeatureConfig._eventData e @?= Aeson.toJSON (withStatus status lockStatus (trivialConfig @cfg) FeatureTTLUnlimited) wsAssertFeatureConfigUpdate :: forall cfg. - ( KnownSymbol (Public.FeatureSymbol cfg), - ToJSON (Public.WithStatus cfg) + ( KnownSymbol (FeatureSymbol cfg), + ToJSON (WithStatus cfg) ) => - Public.WithStatusNoLock cfg -> - Public.LockStatus -> + WithStatusNoLock cfg -> + LockStatus -> Notification -> IO () wsAssertFeatureConfigUpdate config lockStatus notification = do let e :: FeatureConfig.Event = List1.head (WS.unpackPayload notification) FeatureConfig._eventType e @?= FeatureConfig.Update - FeatureConfig._eventFeatureName e @?= Public.featureName @cfg - FeatureConfig._eventData e @?= Aeson.toJSON (Public.withLockStatus lockStatus config) + FeatureConfig._eventFeatureName e @?= featureName @cfg + FeatureConfig._eventData e @?= Aeson.toJSON (withLockStatus lockStatus config)