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
1 change: 1 addition & 0 deletions changelog.d/0-release-notes/pr-1857
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Deploy galley before brig.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
End-points for configuring self-deleting messages.
2 changes: 2 additions & 0 deletions docs/reference/cassandra-schema.cql
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,8 @@ CREATE TABLE galley_test.team_features (
file_sharing int,
legalhold_status int,
search_visibility_status int,
self_deleting_messages_status int,
self_deleting_messages_ttl int,
sso_status int,
validate_saml_emails int
) WITH bloom_filter_fp_chance = 0.1
Expand Down
12 changes: 9 additions & 3 deletions libs/galley-types/src/Galley/Types/Teams.hs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ module Galley.Types.Teams
flagAppLockDefaults,
flagClassifiedDomains,
flagConferenceCalling,
flagSelfDeletingMessages,
Defaults (..),
unDefaults,
FeatureSSO (..),
Expand Down Expand Up @@ -214,7 +215,8 @@ data FeatureFlags = FeatureFlags
_flagAppLockDefaults :: !(Defaults (TeamFeatureStatus 'TeamFeatureAppLock)),
_flagClassifiedDomains :: !(TeamFeatureStatus 'TeamFeatureClassifiedDomains),
_flagFileSharing :: !(Defaults (TeamFeatureStatus 'TeamFeatureFileSharing)),
_flagConferenceCalling :: !(Defaults (TeamFeatureStatus 'TeamFeatureConferenceCalling))
_flagConferenceCalling :: !(Defaults (TeamFeatureStatus 'TeamFeatureConferenceCalling)),
_flagSelfDeletingMessages :: !(Defaults (TeamFeatureStatus 'TeamFeatureSelfDeletingMessages))
}
deriving (Eq, Show, Generic)

Expand Down Expand Up @@ -260,17 +262,19 @@ instance FromJSON FeatureFlags where
<*> (fromMaybe defaultClassifiedDomains <$> (obj .:? "classifiedDomains"))
<*> (fromMaybe (Defaults (TeamFeatureStatusNoConfig TeamFeatureEnabled)) <$> (obj .:? "fileSharing"))
<*> (fromMaybe (Defaults (TeamFeatureStatusNoConfig TeamFeatureEnabled)) <$> (obj .:? "conferenceCalling"))
<*> (fromMaybe (Defaults defaultSelfDeletingMessagesStatus) <$> (obj .:? "selfDeletingMessages"))

instance ToJSON FeatureFlags where
toJSON (FeatureFlags sso legalhold searchVisibility appLock classifiedDomains fileSharing conferenceCalling) =
toJSON (FeatureFlags sso legalhold searchVisibility appLock classifiedDomains fileSharing conferenceCalling selfDeletingMessages) =
object $
[ "sso" .= sso,
"legalhold" .= legalhold,
"teamSearchVisibility" .= searchVisibility,
"appLock" .= appLock,
"classifiedDomains" .= classifiedDomains,
"fileSharing" .= fileSharing,
"conferenceCalling" .= conferenceCalling
"conferenceCalling" .= conferenceCalling,
"selfDeletingMessages" .= selfDeletingMessages
]

instance FromJSON FeatureSSO where
Expand Down Expand Up @@ -362,6 +366,7 @@ roleHiddenPermissions role = HiddenPermissions p p
ChangeTeamFeature TeamFeatureAppLock,
ChangeTeamFeature TeamFeatureFileSharing,
ChangeTeamFeature TeamFeatureClassifiedDomains {- the features not listed here can only be changed in stern -},
ChangeTeamFeature TeamFeatureSelfDeletingMessages,
ReadIdp,
CreateUpdateDeleteIdp,
CreateReadDeleteScimToken,
Expand All @@ -381,6 +386,7 @@ roleHiddenPermissions role = HiddenPermissions p p
ViewTeamFeature TeamFeatureFileSharing,
ViewTeamFeature TeamFeatureClassifiedDomains,
ViewTeamFeature TeamFeatureConferenceCalling,
ViewTeamFeature TeamFeatureSelfDeletingMessages,
ViewLegalHoldUserSettings,
ViewTeamSearchVisibility
]
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 @@ -96,3 +96,4 @@ instance Arbitrary FeatureFlags where
<*> arbitrary
<*> arbitrary
<*> arbitrary
<*> arbitrary
4 changes: 3 additions & 1 deletion libs/wire-api/src/Wire/API/Event/FeatureConfig.hs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import Data.Json.Util (ToJSONObject (..))
import Data.Schema
import qualified Data.Swagger as S
import Imports
import Wire.API.Team.Feature (TeamFeatureAppLockConfig, TeamFeatureClassifiedDomainsConfig, TeamFeatureName (..), TeamFeatureStatusNoConfig, TeamFeatureStatusWithConfig)
import Wire.API.Team.Feature (TeamFeatureAppLockConfig, TeamFeatureClassifiedDomainsConfig, TeamFeatureName (..), TeamFeatureSelfDeletingMessagesConfig, TeamFeatureStatusNoConfig, TeamFeatureStatusWithConfig)

data Event = Event
{ _eventType :: EventType,
Expand All @@ -53,6 +53,7 @@ data EventData
= EdFeatureWithoutConfigChanged TeamFeatureStatusNoConfig
| EdFeatureApplockChanged (TeamFeatureStatusWithConfig TeamFeatureAppLockConfig)
| EdFeatureClassifiedDomainsChanged (TeamFeatureStatusWithConfig TeamFeatureClassifiedDomainsConfig)
| EdFeatureSelfDeletingMessagesChanged (TeamFeatureStatusWithConfig TeamFeatureSelfDeletingMessagesConfig)
deriving (Eq, Show, Generic)

makePrisms ''EventData
Expand All @@ -73,6 +74,7 @@ taggedEventDataSchema =
TeamFeatureFileSharing -> tag _EdFeatureWithoutConfigChanged (unnamed schema)
TeamFeatureClassifiedDomains -> tag _EdFeatureClassifiedDomainsChanged (unnamed schema)
TeamFeatureConferenceCalling -> tag _EdFeatureWithoutConfigChanged (unnamed schema)
TeamFeatureSelfDeletingMessages -> tag _EdFeatureSelfDeletingMessagesChanged (unnamed schema)

eventObjectSchema :: ObjectSchema SwaggerDoc Event
eventObjectSchema =
Expand Down
12 changes: 11 additions & 1 deletion libs/wire-api/src/Wire/API/Routes/Public/Galley.hs
Original file line number Diff line number Diff line change
Expand Up @@ -601,6 +601,7 @@ data Api routes = Api
'[Servant.JSON]
(PostOtrResponses MessageSendingStatus)
(Either (MessageNotSent MessageSendingStatus) MessageSendingStatus),
-- team features
teamFeatureStatusSSOGet ::
routes
:- FeatureStatusGet 'TeamFeatureSSO,
Expand Down Expand Up @@ -652,6 +653,12 @@ data Api routes = Api
teamFeatureStatusConferenceCallingGet ::
routes
:- FeatureStatusGet 'TeamFeatureConferenceCalling,
teamFeatureStatusSelfDeletingMessagesGet ::
routes
:- FeatureStatusGet 'TeamFeatureSelfDeletingMessages,
teamFeatureStatusSelfDeletingMessagesPut ::
routes
:- FeatureStatusPut 'TeamFeatureSelfDeletingMessages,
featureAllFeatureConfigsGet ::
routes
:- AllFeatureConfigsGet,
Expand Down Expand Up @@ -681,7 +688,10 @@ data Api routes = Api
:- FeatureConfigGet 'TeamFeatureClassifiedDomains,
featureConfigConferenceCallingGet ::
routes
:- FeatureConfigGet 'TeamFeatureConferenceCalling
:- FeatureConfigGet 'TeamFeatureConferenceCalling,
featureConfigSelfDeletingMessagesGet ::
routes
:- FeatureConfigGet 'TeamFeatureSelfDeletingMessages
}
deriving (Generic)

Expand Down
1 change: 1 addition & 0 deletions libs/wire-api/src/Wire/API/Swagger.hs
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ models =
Team.Feature.modelForTeamFeature Team.Feature.TeamFeatureClassifiedDomains,
Team.Feature.modelTeamFeatureAppLockConfig,
Team.Feature.modelTeamFeatureClassifiedDomainsConfig,
Team.Feature.modelTeamFeatureSelfDeletingMessagesConfig,
Team.Invitation.modelTeamInvitation,
Team.Invitation.modelTeamInvitationList,
Team.Invitation.modelTeamInvitationRequest,
Expand Down
48 changes: 45 additions & 3 deletions libs/wire-api/src/Wire/API/Team/Feature.hs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ module Wire.API.Team.Feature
( TeamFeatureName (..),
TeamFeatureStatus,
TeamFeatureAppLockConfig (..),
TeamFeatureSelfDeletingMessagesConfig (..),
TeamFeatureClassifiedDomainsConfig (..),
TeamFeatureStatusValue (..),
FeatureHasNoConfig,
Expand All @@ -33,6 +34,7 @@ module Wire.API.Team.Feature
AllFeatureConfigs (..),
defaultAppLockStatus,
defaultClassifiedDomains,
defaultSelfDeletingMessagesStatus,

-- * Swagger
typeTeamFeatureName,
Expand All @@ -41,6 +43,7 @@ module Wire.API.Team.Feature
modelTeamFeatureStatusWithConfig,
modelTeamFeatureAppLockConfig,
modelTeamFeatureClassifiedDomainsConfig,
modelTeamFeatureSelfDeletingMessagesConfig,
modelForTeamFeature,
)
where
Expand Down Expand Up @@ -90,10 +93,13 @@ import Wire.API.Arbitrary (Arbitrary, GenericUniform (..))
-- * services/galley/test/integration/API/Teams/Feature.hs
-- * add an integration test for the feature
-- * extend testAllFeatures
-- * consider personal-account configurability (like for `conferenceCalling`, see
-- eg. https://github.com/wireapp/wire-server/pull/1811,
-- https://github.com/wireapp/wire-server/pull/1818)
--
--
-- An overview of places to change (including compiler errors and failing tests) can be found
-- in eg. https://github.com/wireapp/wire-server/pull/1652.
-- An example of all the places to change (including compiler errors and failing tests) can be found
-- in eg. https://github.com/wireapp/wire-server/pull/1652. (applock and conference calling also
-- add interesting aspects, though.)
--
-- Using something like '[minBound..]' on those expressions would require dependent types. We
-- could generate exhaustive lists of those calls using TH, along the lines of:
Expand All @@ -119,6 +125,7 @@ data TeamFeatureName
| TeamFeatureFileSharing
| TeamFeatureClassifiedDomains
| TeamFeatureConferenceCalling
| TeamFeatureSelfDeletingMessages
deriving stock (Eq, Show, Ord, Generic, Enum, Bounded, Typeable)
deriving (Arbitrary) via (GenericUniform TeamFeatureName)

Expand Down Expand Up @@ -162,6 +169,10 @@ instance KnownTeamFeatureName 'TeamFeatureConferenceCalling where
type KnownTeamFeatureNameSymbol 'TeamFeatureConferenceCalling = "conferenceCalling"
knownTeamFeatureName = TeamFeatureConferenceCalling

instance KnownTeamFeatureName 'TeamFeatureSelfDeletingMessages where
type KnownTeamFeatureNameSymbol 'TeamFeatureSelfDeletingMessages = "selfDeletingMessages"
knownTeamFeatureName = TeamFeatureSelfDeletingMessages

instance FromByteString TeamFeatureName where
parser =
Parser.takeByteString >>= \b ->
Expand All @@ -179,6 +190,7 @@ instance FromByteString TeamFeatureName where
Right "fileSharing" -> pure TeamFeatureFileSharing
Right "classifiedDomains" -> pure TeamFeatureClassifiedDomains
Right "conferenceCalling" -> pure TeamFeatureConferenceCalling
Right "selfDeletingMessages" -> pure TeamFeatureSelfDeletingMessages
Right t -> fail $ "Invalid TeamFeatureName: " <> T.unpack t

-- TODO: how do we make this consistent with 'KnownTeamFeatureNameSymbol'? add a test for
Expand All @@ -193,6 +205,7 @@ instance ToByteString TeamFeatureName where
builder TeamFeatureFileSharing = "fileSharing"
builder TeamFeatureClassifiedDomains = "classifiedDomains"
builder TeamFeatureConferenceCalling = "conferenceCalling"
builder TeamFeatureSelfDeletingMessages = "selfDeletingMessages"

instance ToSchema TeamFeatureName where
schema =
Expand Down Expand Up @@ -280,6 +293,7 @@ type family TeamFeatureStatus (a :: TeamFeatureName) :: * where
TeamFeatureStatus 'TeamFeatureFileSharing = TeamFeatureStatusNoConfig
TeamFeatureStatus 'TeamFeatureClassifiedDomains = TeamFeatureStatusWithConfig TeamFeatureClassifiedDomainsConfig
TeamFeatureStatus 'TeamFeatureConferenceCalling = TeamFeatureStatusNoConfig
TeamFeatureStatus 'TeamFeatureSelfDeletingMessages = TeamFeatureStatusWithConfig TeamFeatureSelfDeletingMessagesConfig

type FeatureHasNoConfig (a :: TeamFeatureName) = (TeamFeatureStatus a ~ TeamFeatureStatusNoConfig) :: Constraint

Expand All @@ -294,6 +308,7 @@ modelForTeamFeature name@TeamFeatureAppLock = modelTeamFeatureStatusWithConfig n
modelForTeamFeature TeamFeatureFileSharing = modelTeamFeatureStatusNoConfig
modelForTeamFeature name@TeamFeatureClassifiedDomains = modelTeamFeatureStatusWithConfig name modelTeamFeatureClassifiedDomainsConfig
modelForTeamFeature TeamFeatureConferenceCalling = modelTeamFeatureStatusNoConfig
modelForTeamFeature name@TeamFeatureSelfDeletingMessages = modelTeamFeatureStatusWithConfig name modelTeamFeatureSelfDeletingMessagesConfig

----------------------------------------------------------------------
-- TeamFeatureStatusNoConfig
Expand Down Expand Up @@ -409,6 +424,33 @@ defaultAppLockStatus =
TeamFeatureEnabled
(TeamFeatureAppLockConfig (EnforceAppLock False) 60)

----------------------------------------------------------------------
-- TeamFeatureSelfDeletingMessagesConfig

data TeamFeatureSelfDeletingMessagesConfig = TeamFeatureSelfDeletingMessagesConfig
{ sdmEnforcedTimeoutSeconds :: Int32
}
deriving stock (Eq, Show, Generic)
deriving (FromJSON, ToJSON, S.ToSchema) via (Schema TeamFeatureSelfDeletingMessagesConfig)
deriving (Arbitrary) via (GenericUniform TeamFeatureSelfDeletingMessagesConfig)

instance ToSchema TeamFeatureSelfDeletingMessagesConfig where
schema =
object "TeamFeatureSelfDeletingMessagesConfig" $
TeamFeatureSelfDeletingMessagesConfig
<$> sdmEnforcedTimeoutSeconds .= field "enforcedTimeoutSeconds" schema

modelTeamFeatureSelfDeletingMessagesConfig :: Doc.Model
modelTeamFeatureSelfDeletingMessagesConfig =
Doc.defineModel "TeamFeatureSelfDeletingMessagesConfig" $ do
Doc.property "enforcedTimeoutSeconds" Doc.int32' $ Doc.description "optional; default: `0` (no enforcement)"

defaultSelfDeletingMessagesStatus :: TeamFeatureStatusWithConfig TeamFeatureSelfDeletingMessagesConfig
defaultSelfDeletingMessagesStatus =
TeamFeatureStatusWithConfig
TeamFeatureEnabled
(TeamFeatureSelfDeletingMessagesConfig 0)

----------------------------------------------------------------------
-- internal

Expand Down
1 change: 1 addition & 0 deletions libs/wire-api/test/unit/Test/Wire/API/Roundtrip/Aeson.hs
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ tests =
testRoundTrip @(Team.Feature.TeamFeatureStatus 'Team.Feature.TeamFeatureFileSharing),
testRoundTrip @(Team.Feature.TeamFeatureStatus 'Team.Feature.TeamFeatureClassifiedDomains),
testRoundTrip @(Team.Feature.TeamFeatureStatus 'Team.Feature.TeamFeatureConferenceCalling),
testRoundTrip @(Team.Feature.TeamFeatureStatus 'Team.Feature.TeamFeatureSelfDeletingMessages),
testRoundTrip @Team.Feature.TeamFeatureStatusValue,
testRoundTrip @Team.Invitation.InvitationRequest,
testRoundTrip @Team.Invitation.Invitation,
Expand Down
3 changes: 2 additions & 1 deletion services/galley/galley.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ cabal-version: 1.12
--
-- see: https://github.com/sol/hpack
--
-- hash: 8bf007e90cc28a7b92252e0fccfb998d850e30df040205e1bc7316b9008a0c9f
-- hash: 503d71d65ade51f149de711b6c0b958b0d7de6c3472c4831fc5549c20410b37a

name: galley
version: 0.83.0
Expand Down Expand Up @@ -385,6 +385,7 @@ executable galley-schema
V51_FeatureFileSharing
V52_FeatureConferenceCalling
V53_AddRemoteConvStatus
V54_TeamFeatureSelfDeletingMessages
Paths_galley
hs-source-dirs:
schema/src
Expand Down
4 changes: 3 additions & 1 deletion services/galley/schema/src/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ import qualified V50_AddLegalholdWhitelisted
import qualified V51_FeatureFileSharing
import qualified V52_FeatureConferenceCalling
import qualified V53_AddRemoteConvStatus
import qualified V54_TeamFeatureSelfDeletingMessages

main :: IO ()
main = do
Expand Down Expand Up @@ -97,7 +98,8 @@ main = do
V50_AddLegalholdWhitelisted.migration,
V51_FeatureFileSharing.migration,
V52_FeatureConferenceCalling.migration,
V53_AddRemoteConvStatus.migration
V53_AddRemoteConvStatus.migration,
V54_TeamFeatureSelfDeletingMessages.migration
-- When adding migrations here, don't forget to update
-- 'schemaVersion' in Galley.Data
-- (see also docs/developer/cassandra-interaction.md)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
-- This file is part of the Wire Server implementation.
--
-- Copyright (C) 2020 Wire Swiss GmbH <opensource@wire.com>
--
-- This program is free software: you can redistribute it and/or modify it under
-- the terms of the GNU Affero General Public License as published by the Free
-- Software Foundation, either version 3 of the License, or (at your option) any
-- later version.
--
-- This program is distributed in the hope that it will be useful, but WITHOUT
-- ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
-- FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
-- details.
--
-- You should have received a copy of the GNU Affero General Public License along
-- with this program. If not, see <https://www.gnu.org/licenses/>.

module V54_TeamFeatureSelfDeletingMessages
( migration,
)
where

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

migration :: Migration
migration = Migration 54 "Add feature config for self-deleting messages" $ do
schema'
[r| ALTER TABLE team_features ADD (
self_deleting_messages_status int,
self_deleting_messages_ttl int
)
|]
8 changes: 8 additions & 0 deletions services/galley/src/Galley/API/Internal.hs
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,12 @@ data InternalApi routes = InternalApi
iTeamFeatureStatusConferenceCallingGet ::
routes
:- IFeatureStatusGet 'Public.TeamFeatureConferenceCalling,
iTeamFeatureStatusSelfDeletingMessagesPut ::
routes
:- IFeatureStatusPut 'Public.TeamFeatureSelfDeletingMessages,
iTeamFeatureStatusSelfDeletingMessagesGet ::
routes
:- IFeatureStatusGet 'Public.TeamFeatureSelfDeletingMessages,
-- This endpoint can lead to the following events being sent:
-- - MemberLeave event to members for all conversations the user was in
iDeleteUser ::
Expand Down Expand Up @@ -281,6 +287,8 @@ servantSitemap =
iTeamFeatureStatusClassifiedDomainsGet = iGetTeamFeature @'Public.TeamFeatureClassifiedDomains Features.getClassifiedDomainsInternal,
iTeamFeatureStatusConferenceCallingPut = iPutTeamFeature @'Public.TeamFeatureConferenceCalling Features.setConferenceCallingInternal,
iTeamFeatureStatusConferenceCallingGet = iGetTeamFeature @'Public.TeamFeatureConferenceCalling Features.getConferenceCallingInternal,
iTeamFeatureStatusSelfDeletingMessagesPut = iPutTeamFeature @'Public.TeamFeatureSelfDeletingMessages Features.setSelfDeletingMessagesInternal,
iTeamFeatureStatusSelfDeletingMessagesGet = iGetTeamFeature @'Public.TeamFeatureSelfDeletingMessages Features.getSelfDeletingMessagesInternal,
iDeleteUser = rmUser,
iConnect = Create.createConnectConversation,
iUpsertOne2OneConversation = One2One.iUpsertOne2OneConversation
Expand Down
9 changes: 8 additions & 1 deletion services/galley/src/Galley/API/Public.hs
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,12 @@ servantSitemap =
GalleyAPI.teamFeatureStatusConferenceCallingGet =
getFeatureStatus @'Public.TeamFeatureConferenceCalling Features.getConferenceCallingInternal
. DoAuth,
GalleyAPI.teamFeatureStatusSelfDeletingMessagesGet =
getFeatureStatus @'Public.TeamFeatureSelfDeletingMessages Features.getSelfDeletingMessagesInternal
. DoAuth,
GalleyAPI.teamFeatureStatusSelfDeletingMessagesPut =
setFeatureStatus @'Public.TeamFeatureSelfDeletingMessages Features.setSelfDeletingMessagesInternal
. DoAuth,
GalleyAPI.featureAllFeatureConfigsGet = Features.getAllFeatureConfigs,
GalleyAPI.featureConfigLegalHoldGet = Features.getFeatureConfig @'Public.TeamFeatureLegalHold Features.getLegalholdStatusInternal,
GalleyAPI.featureConfigSSOGet = Features.getFeatureConfig @'Public.TeamFeatureSSO Features.getSSOStatusInternal,
Expand All @@ -171,7 +177,8 @@ servantSitemap =
GalleyAPI.featureConfigAppLockGet = Features.getFeatureConfig @'Public.TeamFeatureAppLock Features.getAppLockInternal,
GalleyAPI.featureConfigFileSharingGet = Features.getFeatureConfig @'Public.TeamFeatureFileSharing Features.getFileSharingInternal,
GalleyAPI.featureConfigClassifiedDomainsGet = Features.getFeatureConfig @'Public.TeamFeatureClassifiedDomains Features.getClassifiedDomainsInternal,
GalleyAPI.featureConfigConferenceCallingGet = Features.getFeatureConfig @'Public.TeamFeatureConferenceCalling Features.getConferenceCallingInternal
GalleyAPI.featureConfigConferenceCallingGet = Features.getFeatureConfig @'Public.TeamFeatureConferenceCalling Features.getConferenceCallingInternal,
GalleyAPI.featureConfigSelfDeletingMessagesGet = Features.getFeatureConfig @'Public.TeamFeatureSelfDeletingMessages Features.getSelfDeletingMessagesInternal
}

sitemap :: Routes ApiBuilder (Galley GalleyEffects) ()
Expand Down
Loading