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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions cassandra-schema.cql
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,9 @@ CREATE TABLE galley_test.team_features (
mls_allowed_ciphersuites set<int>,
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<uuid>,
mls_status int,
outlook_cal_integration_lock_status int,
Expand Down
1 change: 1 addition & 0 deletions changelog.d/2-features/pr-3082
Original file line number Diff line number Diff line change
@@ -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`
4 changes: 4 additions & 0 deletions charts/galley/templates/configmap.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
6 changes: 6 additions & 0 deletions charts/galley/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,12 @@ config:
defaults:
status: disabled
lockStatus: locked
mlsE2EId:
defaults:
status: disabled
config:
verificationExpiration: null
lockStatus: unlocked

aws:
region: "eu-west-1"
Expand Down
15 changes: 15 additions & 0 deletions docs/src/developer/reference/config-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
23 changes: 23 additions & 0 deletions docs/src/understand/team-feature-settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
```
9 changes: 7 additions & 2 deletions libs/galley-types/src/Galley/Types/Teams.hs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ module Galley.Types.Teams
flagTeamFeatureSearchVisibilityInbound,
flagOutlookCalIntegration,
flagMLS,
flagMlsE2EId,
Defaults (..),
ImplicitLockStatus (..),
unImplicitLockStatus,
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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
Expand All @@ -224,6 +227,7 @@ instance ToJSON FeatureFlags where
searchVisibilityInbound
mls
outlookCalIntegration
mlsE2EId
) =
object
[ "sso" .= sso,
Expand All @@ -239,7 +243,8 @@ instance ToJSON FeatureFlags where
"sndFactorPasswordChallenge" .= sndFactorPasswordChallenge,
"searchVisibilityInbound" .= searchVisibilityInbound,
"mls" .= mls,
"outlookCalIntegration" .= outlookCalIntegration
"outlookCalIntegration" .= outlookCalIntegration,
"mlsE2EId" .= mlsE2EId
]

instance FromJSON FeatureSSO where
Expand Down
1 change: 1 addition & 0 deletions libs/galley-types/test/unit/Test/Galley/Types.hs
Original file line number Diff line number Diff line change
Expand Up @@ -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
5 changes: 5 additions & 0 deletions libs/wire-api/src/Wire/API/Routes/Internal/Galley.hs
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
12 changes: 10 additions & 2 deletions libs/wire-api/src/Wire/API/Routes/Public/Brig.hs
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
2 changes: 2 additions & 0 deletions libs/wire-api/src/Wire/API/Routes/Public/Galley/Feature.hs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
67 changes: 57 additions & 10 deletions libs/wire-api/src/Wire/API/SystemSettings.hs
Original file line number Diff line number Diff line change
@@ -1,10 +1,27 @@
-- This file is part of the Wire Server implementation.
--
-- Copyright (C) 2022 Wire Swiss GmbH <opensource@wire.com>
--
-- This program is free software: you can redistribute it and/or modify it under
-- the terms of the GNU Affero General Public License as published by the Free
-- Software Foundation, either version 3 of the License, or (at your option) any
-- later version.
--
-- This program is distributed in the hope that it will be useful, but WITHOUT
-- ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
-- FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
-- details.
--
-- You should have received a copy of the GNU Affero General Public License along
-- with this program. If not, see <https://www.gnu.org/licenses/>.

module 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
Expand All @@ -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
57 changes: 52 additions & 5 deletions libs/wire-api/src/Wire/API/Team/Feature.hs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ module Wire.API.Team.Feature
setStatus,
setLockStatus,
setConfig,
setConfig',
setTTL,
setWsTTL,
WithStatusPatch,
wsPatch,
Expand Down Expand Up @@ -76,6 +78,7 @@ module Wire.API.Team.Feature
FileSharingConfig (..),
MLSConfig (..),
OutlookCalIntegrationConfig (..),
MlsE2EIdConfig (..),
AllFeatureConfigs (..),
unImplicitLockStatus,
ImplicitLockStatus (..),
Expand All @@ -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))
Expand Down Expand Up @@ -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
--
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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)
Expand All @@ -979,6 +1024,7 @@ instance ToSchema AllFeatureConfigs where
<*> afcMLS .= featureField
<*> afcExposeInvitationURLsToTeamAdmin .= featureField
<*> afcOutlookCalIntegration .= featureField
<*> afcMlsE2EId .= featureField
where
featureField ::
forall cfg.
Expand All @@ -1005,5 +1051,6 @@ instance Arbitrary AllFeatureConfigs where
<*> arbitrary
<*> arbitrary
<*> arbitrary
<*> arbitrary

makeLenses ''ImplicitLockStatus
Loading