diff --git a/changelog.d/0-release-notes/pr-1857 b/changelog.d/0-release-notes/pr-1857 new file mode 100644 index 0000000000..0592108c6f --- /dev/null +++ b/changelog.d/0-release-notes/pr-1857 @@ -0,0 +1 @@ +Deploy galley before brig. diff --git a/changelog.d/2-features/pr-1857-self-deleting-messages-feature b/changelog.d/2-features/pr-1857-self-deleting-messages-feature new file mode 100644 index 0000000000..dbfd30c783 --- /dev/null +++ b/changelog.d/2-features/pr-1857-self-deleting-messages-feature @@ -0,0 +1 @@ +End-points for configuring self-deleting messages. \ No newline at end of file diff --git a/docs/reference/cassandra-schema.cql b/docs/reference/cassandra-schema.cql index 5df3899b42..7b70e2f464 100644 --- a/docs/reference/cassandra-schema.cql +++ b/docs/reference/cassandra-schema.cql @@ -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 diff --git a/libs/galley-types/src/Galley/Types/Teams.hs b/libs/galley-types/src/Galley/Types/Teams.hs index bcbc0ce3ec..8e727e90a1 100644 --- a/libs/galley-types/src/Galley/Types/Teams.hs +++ b/libs/galley-types/src/Galley/Types/Teams.hs @@ -31,6 +31,7 @@ module Galley.Types.Teams flagAppLockDefaults, flagClassifiedDomains, flagConferenceCalling, + flagSelfDeletingMessages, Defaults (..), unDefaults, FeatureSSO (..), @@ -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) @@ -260,9 +262,10 @@ 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, @@ -270,7 +273,8 @@ instance ToJSON FeatureFlags where "appLock" .= appLock, "classifiedDomains" .= classifiedDomains, "fileSharing" .= fileSharing, - "conferenceCalling" .= conferenceCalling + "conferenceCalling" .= conferenceCalling, + "selfDeletingMessages" .= selfDeletingMessages ] instance FromJSON FeatureSSO where @@ -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, @@ -381,6 +386,7 @@ roleHiddenPermissions role = HiddenPermissions p p ViewTeamFeature TeamFeatureFileSharing, ViewTeamFeature TeamFeatureClassifiedDomains, ViewTeamFeature TeamFeatureConferenceCalling, + ViewTeamFeature TeamFeatureSelfDeletingMessages, ViewLegalHoldUserSettings, ViewTeamSearchVisibility ] diff --git a/libs/galley-types/test/unit/Test/Galley/Types.hs b/libs/galley-types/test/unit/Test/Galley/Types.hs index 73791c71a6..3ed957c77d 100644 --- a/libs/galley-types/test/unit/Test/Galley/Types.hs +++ b/libs/galley-types/test/unit/Test/Galley/Types.hs @@ -96,3 +96,4 @@ instance Arbitrary FeatureFlags where <*> arbitrary <*> arbitrary <*> arbitrary + <*> arbitrary diff --git a/libs/wire-api/src/Wire/API/Event/FeatureConfig.hs b/libs/wire-api/src/Wire/API/Event/FeatureConfig.hs index d6177821af..d64dac272f 100644 --- a/libs/wire-api/src/Wire/API/Event/FeatureConfig.hs +++ b/libs/wire-api/src/Wire/API/Event/FeatureConfig.hs @@ -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, @@ -53,6 +53,7 @@ data EventData = EdFeatureWithoutConfigChanged TeamFeatureStatusNoConfig | EdFeatureApplockChanged (TeamFeatureStatusWithConfig TeamFeatureAppLockConfig) | EdFeatureClassifiedDomainsChanged (TeamFeatureStatusWithConfig TeamFeatureClassifiedDomainsConfig) + | EdFeatureSelfDeletingMessagesChanged (TeamFeatureStatusWithConfig TeamFeatureSelfDeletingMessagesConfig) deriving (Eq, Show, Generic) makePrisms ''EventData @@ -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 = diff --git a/libs/wire-api/src/Wire/API/Routes/Public/Galley.hs b/libs/wire-api/src/Wire/API/Routes/Public/Galley.hs index 41cd9d2ae1..136c82de3a 100644 --- a/libs/wire-api/src/Wire/API/Routes/Public/Galley.hs +++ b/libs/wire-api/src/Wire/API/Routes/Public/Galley.hs @@ -601,6 +601,7 @@ data Api routes = Api '[Servant.JSON] (PostOtrResponses MessageSendingStatus) (Either (MessageNotSent MessageSendingStatus) MessageSendingStatus), + -- team features teamFeatureStatusSSOGet :: routes :- FeatureStatusGet 'TeamFeatureSSO, @@ -652,6 +653,12 @@ data Api routes = Api teamFeatureStatusConferenceCallingGet :: routes :- FeatureStatusGet 'TeamFeatureConferenceCalling, + teamFeatureStatusSelfDeletingMessagesGet :: + routes + :- FeatureStatusGet 'TeamFeatureSelfDeletingMessages, + teamFeatureStatusSelfDeletingMessagesPut :: + routes + :- FeatureStatusPut 'TeamFeatureSelfDeletingMessages, featureAllFeatureConfigsGet :: routes :- AllFeatureConfigsGet, @@ -681,7 +688,10 @@ data Api routes = Api :- FeatureConfigGet 'TeamFeatureClassifiedDomains, featureConfigConferenceCallingGet :: routes - :- FeatureConfigGet 'TeamFeatureConferenceCalling + :- FeatureConfigGet 'TeamFeatureConferenceCalling, + featureConfigSelfDeletingMessagesGet :: + routes + :- FeatureConfigGet 'TeamFeatureSelfDeletingMessages } deriving (Generic) diff --git a/libs/wire-api/src/Wire/API/Swagger.hs b/libs/wire-api/src/Wire/API/Swagger.hs index 17f75474c8..f746c3465c 100644 --- a/libs/wire-api/src/Wire/API/Swagger.hs +++ b/libs/wire-api/src/Wire/API/Swagger.hs @@ -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, diff --git a/libs/wire-api/src/Wire/API/Team/Feature.hs b/libs/wire-api/src/Wire/API/Team/Feature.hs index c943a4db1e..f466fe3102 100644 --- a/libs/wire-api/src/Wire/API/Team/Feature.hs +++ b/libs/wire-api/src/Wire/API/Team/Feature.hs @@ -22,6 +22,7 @@ module Wire.API.Team.Feature ( TeamFeatureName (..), TeamFeatureStatus, TeamFeatureAppLockConfig (..), + TeamFeatureSelfDeletingMessagesConfig (..), TeamFeatureClassifiedDomainsConfig (..), TeamFeatureStatusValue (..), FeatureHasNoConfig, @@ -33,6 +34,7 @@ module Wire.API.Team.Feature AllFeatureConfigs (..), defaultAppLockStatus, defaultClassifiedDomains, + defaultSelfDeletingMessagesStatus, -- * Swagger typeTeamFeatureName, @@ -41,6 +43,7 @@ module Wire.API.Team.Feature modelTeamFeatureStatusWithConfig, modelTeamFeatureAppLockConfig, modelTeamFeatureClassifiedDomainsConfig, + modelTeamFeatureSelfDeletingMessagesConfig, modelForTeamFeature, ) where @@ -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: @@ -119,6 +125,7 @@ data TeamFeatureName | TeamFeatureFileSharing | TeamFeatureClassifiedDomains | TeamFeatureConferenceCalling + | TeamFeatureSelfDeletingMessages deriving stock (Eq, Show, Ord, Generic, Enum, Bounded, Typeable) deriving (Arbitrary) via (GenericUniform TeamFeatureName) @@ -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 -> @@ -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 @@ -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 = @@ -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 @@ -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 @@ -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 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 97dc0e0aa3..6e09181e54 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 @@ -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, diff --git a/services/galley/galley.cabal b/services/galley/galley.cabal index 81c99088c3..36693c8b65 100644 --- a/services/galley/galley.cabal +++ b/services/galley/galley.cabal @@ -4,7 +4,7 @@ cabal-version: 1.12 -- -- see: https://github.com/sol/hpack -- --- hash: 8bf007e90cc28a7b92252e0fccfb998d850e30df040205e1bc7316b9008a0c9f +-- hash: 503d71d65ade51f149de711b6c0b958b0d7de6c3472c4831fc5549c20410b37a name: galley version: 0.83.0 @@ -385,6 +385,7 @@ executable galley-schema V51_FeatureFileSharing V52_FeatureConferenceCalling V53_AddRemoteConvStatus + V54_TeamFeatureSelfDeletingMessages Paths_galley hs-source-dirs: schema/src diff --git a/services/galley/schema/src/Main.hs b/services/galley/schema/src/Main.hs index c350df9f4d..369a464436 100644 --- a/services/galley/schema/src/Main.hs +++ b/services/galley/schema/src/Main.hs @@ -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 @@ -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) diff --git a/services/galley/schema/src/V54_TeamFeatureSelfDeletingMessages.hs b/services/galley/schema/src/V54_TeamFeatureSelfDeletingMessages.hs new file mode 100644 index 0000000000..35b2236e86 --- /dev/null +++ b/services/galley/schema/src/V54_TeamFeatureSelfDeletingMessages.hs @@ -0,0 +1,34 @@ +-- This file is part of the Wire Server implementation. +-- +-- Copyright (C) 2020 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 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 + ) + |] diff --git a/services/galley/src/Galley/API/Internal.hs b/services/galley/src/Galley/API/Internal.hs index 5cc940a457..b939cf2d36 100644 --- a/services/galley/src/Galley/API/Internal.hs +++ b/services/galley/src/Galley/API/Internal.hs @@ -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 :: @@ -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 diff --git a/services/galley/src/Galley/API/Public.hs b/services/galley/src/Galley/API/Public.hs index 900e4b052f..e421314271 100644 --- a/services/galley/src/Galley/API/Public.hs +++ b/services/galley/src/Galley/API/Public.hs @@ -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, @@ -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) () diff --git a/services/galley/src/Galley/API/Teams/Features.hs b/services/galley/src/Galley/API/Teams/Features.hs index b34917162d..cb14611e9a 100644 --- a/services/galley/src/Galley/API/Teams/Features.hs +++ b/services/galley/src/Galley/API/Teams/Features.hs @@ -38,6 +38,8 @@ module Galley.API.Teams.Features setFileSharingInternal, getConferenceCallingInternal, setConferenceCallingInternal, + getSelfDeletingMessagesInternal, + setSelfDeletingMessagesInternal, DoAuth (..), GetFeatureInternalParam, ) @@ -162,7 +164,8 @@ getAllFeatureConfigs zusr = do getStatus @'Public.TeamFeatureAppLock getAppLockInternal, getStatus @'Public.TeamFeatureFileSharing getFileSharingInternal, getStatus @'Public.TeamFeatureClassifiedDomains getClassifiedDomainsInternal, - getStatus @'Public.TeamFeatureConferenceCalling getConferenceCallingInternal + getStatus @'Public.TeamFeatureConferenceCalling getConferenceCallingInternal, + getStatus @'Public.TeamFeatureSelfDeletingMessages getSelfDeletingMessagesInternal ] getAllFeaturesH :: UserId ::: TeamId ::: JSON -> Galley r Response @@ -181,7 +184,8 @@ getAllFeatures uid tid = do getStatus @'Public.TeamFeatureAppLock getAppLockInternal, getStatus @'Public.TeamFeatureFileSharing getFileSharingInternal, getStatus @'Public.TeamFeatureClassifiedDomains getClassifiedDomainsInternal, - getStatus @'Public.TeamFeatureConferenceCalling getConferenceCallingInternal + getStatus @'Public.TeamFeatureConferenceCalling getConferenceCallingInternal, + getStatus @'Public.TeamFeatureSelfDeletingMessages getSelfDeletingMessagesInternal ] where getStatus :: @@ -404,6 +408,19 @@ setConferenceCallingInternal :: Galley r (Public.TeamFeatureStatus 'Public.TeamFeatureConferenceCalling) setConferenceCallingInternal = setFeatureStatusNoConfig @'Public.TeamFeatureConferenceCalling $ \_status _tid -> pure () +getSelfDeletingMessagesInternal :: GetFeatureInternalParam -> Galley r (Public.TeamFeatureStatus 'Public.TeamFeatureSelfDeletingMessages) +getSelfDeletingMessagesInternal = \case + Left _ -> pure Public.defaultSelfDeletingMessagesStatus + Right tid -> + TeamFeatures.getSelfDeletingMessagesStatus tid + <&> maybe Public.defaultSelfDeletingMessagesStatus id + +setSelfDeletingMessagesInternal :: + TeamId -> + Public.TeamFeatureStatus 'Public.TeamFeatureSelfDeletingMessages -> + Galley r (Public.TeamFeatureStatus 'Public.TeamFeatureSelfDeletingMessages) +setSelfDeletingMessagesInternal = TeamFeatures.setSelfDeletingMessagesStatus + pushFeatureConfigEvent :: Member GundeckAccess r => TeamId -> Event.Event -> Galley r () pushFeatureConfigEvent tid event = do memList <- Data.teamMembersForFanout tid diff --git a/services/galley/src/Galley/Data.hs b/services/galley/src/Galley/Data.hs index 4d2e02de64..44663d52ee 100644 --- a/services/galley/src/Galley/Data.hs +++ b/services/galley/src/Galley/Data.hs @@ -206,7 +206,7 @@ mkResultSet page = ResultSet (result page) typ | otherwise = ResultSetComplete schemaVersion :: Int32 -schemaVersion = 53 +schemaVersion = 54 -- | Insert a conversation code insertCode :: Code -> Galley r () diff --git a/services/galley/src/Galley/Data/TeamFeatures.hs b/services/galley/src/Galley/Data/TeamFeatures.hs index 93109ac314..3a4421177b 100644 --- a/services/galley/src/Galley/Data/TeamFeatures.hs +++ b/services/galley/src/Galley/Data/TeamFeatures.hs @@ -22,6 +22,8 @@ module Galley.Data.TeamFeatures setFeatureStatusNoConfig, getApplockFeatureStatus, setApplockFeatureStatus, + getSelfDeletingMessagesStatus, + setSelfDeletingMessagesStatus, HasStatusCol (..), ) where @@ -66,6 +68,8 @@ instance HasStatusCol 'TeamFeatureFileSharing where statusCol = "file_sharing" instance HasStatusCol 'TeamFeatureConferenceCalling where statusCol = "conference_calling" +instance HasStatusCol 'TeamFeatureSelfDeletingMessages where statusCol = "self_deleting_messages_status" + getFeatureStatusNoConfig :: forall (a :: Public.TeamFeatureName) m. ( MonadClient m, @@ -93,11 +97,11 @@ setFeatureStatusNoConfig :: m (TeamFeatureStatus a) setFeatureStatusNoConfig tid status = do let flag = Public.tfwoStatus status - retry x5 $ write update (params Quorum (flag, tid)) + retry x5 $ write insert (params Quorum (tid, flag)) pure status where - update :: PrepQuery W (TeamFeatureStatusValue, TeamId) () - update = fromString $ "update team_features set " <> statusCol @a <> " = ? where team_id = ?" + insert :: PrepQuery W (TeamId, TeamFeatureStatusValue) () + insert = fromString $ "insert into team_features (team_id, " <> statusCol @a <> ") values (?, ?)" getApplockFeatureStatus :: forall m. @@ -126,15 +130,51 @@ setApplockFeatureStatus tid status = do let statusValue = Public.tfwcStatus status enforce = Public.applockEnforceAppLock . Public.tfwcConfig $ status timeout = Public.applockInactivityTimeoutSecs . Public.tfwcConfig $ status - retry x5 $ write update (params Quorum (statusValue, enforce, timeout, tid)) + retry x5 $ write insert (params Quorum (tid, statusValue, enforce, timeout)) pure status where - update :: PrepQuery W (TeamFeatureStatusValue, Public.EnforceAppLock, Int32, TeamId) () - update = + insert :: PrepQuery W (TeamId, TeamFeatureStatusValue, Public.EnforceAppLock, Int32) () + insert = fromString $ - "update team_features set " + "insert into team_features (team_id, " <> statusCol @'Public.TeamFeatureAppLock - <> " = ?, " - <> "app_lock_enforce = ?, " - <> "app_lock_inactivity_timeout_secs = ? " - <> "where team_id = ?" + <> ", app_lock_enforce, app_lock_inactivity_timeout_secs) values (?, ?, ?, ?)" + +getSelfDeletingMessagesStatus :: + forall m. + (MonadClient m) => + TeamId -> + m (Maybe (TeamFeatureStatus 'Public.TeamFeatureSelfDeletingMessages)) +getSelfDeletingMessagesStatus tid = do + let q = query1 select (params Quorum (Identity tid)) + mTuple <- retry x1 q + pure $ + mTuple >>= \(mbStatusValue, mbTimeout) -> + TeamFeatureStatusWithConfig <$> mbStatusValue <*> (Public.TeamFeatureSelfDeletingMessagesConfig <$> mbTimeout) + where + select :: PrepQuery R (Identity TeamId) (Maybe TeamFeatureStatusValue, Maybe Int32) + select = + fromString $ + "select " + <> statusCol @'Public.TeamFeatureSelfDeletingMessages + <> ", self_deleting_messages_ttl " + <> "from team_features where team_id = ?" + +setSelfDeletingMessagesStatus :: + (MonadClient m) => + TeamId -> + TeamFeatureStatus 'Public.TeamFeatureSelfDeletingMessages -> + m (TeamFeatureStatus 'Public.TeamFeatureSelfDeletingMessages) +setSelfDeletingMessagesStatus tid status = do + let statusValue = Public.tfwcStatus status + timeout = Public.sdmEnforcedTimeoutSeconds . Public.tfwcConfig $ status + retry x5 $ write insert (params Quorum (tid, statusValue, timeout)) + pure status + where + insert :: PrepQuery W (TeamId, TeamFeatureStatusValue, Int32) () + insert = + fromString $ + "insert into team_features (team_id, " + <> statusCol @'Public.TeamFeatureSelfDeletingMessages + <> ", self_deleting_messages_ttl) " + <> "values (?, ?, ?)" diff --git a/services/galley/test/integration/API/Teams/Feature.hs b/services/galley/test/integration/API/Teams/Feature.hs index 2c2394f0c8..76c5f7b408 100644 --- a/services/galley/test/integration/API/Teams/Feature.hs +++ b/services/galley/test/integration/API/Teams/Feature.hs @@ -67,7 +67,8 @@ tests s = test s "Classified Domains (disabled)" testClassifiedDomainsDisabled, test s "All features" testAllFeatures, test s "Feature Configs / Team Features Consistency" testFeatureConfigConsistency, - test s "ConferenceCalling" $ testSimpleFlag @'Public.TeamFeatureConferenceCalling Public.TeamFeatureEnabled + test s "ConferenceCalling" $ testSimpleFlag @'Public.TeamFeatureConferenceCalling Public.TeamFeatureEnabled, + test s "SelfDeletingMessages" $ testSelfDeletingMessages ] testSSO :: TestM () @@ -377,6 +378,47 @@ testSimpleFlag defaultValue = do setFlagInternal defaultValue getFlag defaultValue +testSelfDeletingMessages :: TestM () +testSelfDeletingMessages = do + -- personal users + let setting :: TeamFeatureStatusValue -> Int32 -> Public.TeamFeatureStatus 'Public.TeamFeatureSelfDeletingMessages + setting stat tout = + Public.TeamFeatureStatusWithConfig @Public.TeamFeatureSelfDeletingMessagesConfig + stat + (Public.TeamFeatureSelfDeletingMessagesConfig tout) + + personalUser <- Util.randomUser + Util.getFeatureConfig Public.TeamFeatureSelfDeletingMessages personalUser + !!! responseJsonEither === const (Right $ setting TeamFeatureEnabled 0) + + -- team users + galley <- view tsGalley + (owner, tid, []) <- Util.createBindingTeamWithNMembers 0 + + let checkSet :: TeamFeatureStatusValue -> Int32 -> TestM () + checkSet stat tout = do + Util.putTeamFeatureFlagInternal @'Public.TeamFeatureSelfDeletingMessages + galley + tid + (setting stat tout) + + -- internal, public (/team/:tid/features), and team-agnostic (/feature-configs). + checkGet :: HasCallStack => TeamFeatureStatusValue -> Int32 -> TestM () + checkGet stat tout = do + let expected = setting stat tout + forM_ + [ Util.getTeamFeatureFlagInternal Public.TeamFeatureSelfDeletingMessages tid, + Util.getTeamFeatureFlagWithGalley Public.TeamFeatureSelfDeletingMessages galley owner tid, + Util.getFeatureConfig Public.TeamFeatureSelfDeletingMessages owner + ] + (!!! responseJsonEither === const (Right expected)) + + checkGet TeamFeatureEnabled 0 + checkSet TeamFeatureDisabled 0 + checkGet TeamFeatureDisabled 0 + checkSet TeamFeatureEnabled 30 + checkGet TeamFeatureEnabled 30 + -- | Call 'GET /teams/:tid/features' and 'GET /feature-configs', and check if all -- features are there. testAllFeatures :: TestM () @@ -411,7 +453,12 @@ testAllFeatures = do TeamFeatureEnabled (Public.TeamFeatureClassifiedDomainsConfig [Domain "example.com"]), toS TeamFeatureConferenceCalling - .= Public.TeamFeatureStatusNoConfig confCalling + .= Public.TeamFeatureStatusNoConfig confCalling, + toS TeamFeatureSelfDeletingMessages + .= ( Public.TeamFeatureStatusWithConfig @Public.TeamFeatureSelfDeletingMessagesConfig + TeamFeatureEnabled + (Public.TeamFeatureSelfDeletingMessagesConfig 0) + ) ] toS :: TeamFeatureName -> Text toS = TE.decodeUtf8 . toByteString'