Skip to content
Closed
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
2 changes: 1 addition & 1 deletion changelog.d/1-api-changes/get-subconversation
Original file line number Diff line number Diff line change
@@ -1 +1 @@
Introduce a subconversation GET endpoint
Introduce a subconversation GET endpoint (#2869, #2993)
56 changes: 45 additions & 11 deletions libs/cassandra-util/src/Cassandra/Util.hs
Original file line number Diff line number Diff line change
Expand Up @@ -14,28 +14,28 @@
--
-- 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/>.
{-# LANGUAGE NumericUnderscores #-}

module Cassandra.Util
( writeTimeToUTC,
defInitCassandra,
Writetime,
( defInitCassandra,
Writetime (..),
writetimeToInt64,
)
where

import Cassandra (ClientState, Keyspace (Keyspace), init)
import Cassandra (ClientState, init)
import Cassandra.CQL
import Cassandra.Settings (defSettings, setContacts, setKeyspace, setLogger, setPortNumber)
import Data.Aeson
import Data.Fixed
import Data.Text (unpack)
import Data.Time (UTCTime)
import Data.Time.Clock.POSIX (posixSecondsToUTCTime)
import Data.Time (UTCTime, nominalDiffTimeToSeconds)
import Data.Time.Clock (secondsToNominalDiffTime)
import Data.Time.Clock.POSIX
import qualified Database.CQL.IO.Tinylog as CT
import Imports hiding (init)
import qualified System.Logger as Log

type Writetime a = Int64

writeTimeToUTC :: Writetime a -> UTCTime
writeTimeToUTC = posixSecondsToUTCTime . fromIntegral . (`div` 1000000)

defInitCassandra :: Text -> Text -> Word16 -> Log.Logger -> IO ClientState
defInitCassandra ks h p lg =
init
Expand All @@ -44,3 +44,37 @@ defInitCassandra ks h p lg =
. setContacts (unpack h) []
. setKeyspace (Keyspace ks)
$ defSettings

-- | Read cassandra's writetimes https://docs.datastax.com/en/dse/5.1/cql/cql/cql_using/useWritetime.html
-- as UTCTime values without any loss of precision
newtype Writetime a = Writetime {writetimeToUTC :: UTCTime}

instance Cql (Writetime a) where
ctype = Tagged BigIntColumn
toCql = CqlBigInt . writetimeToInt64
fromCql (CqlBigInt n) =
pure
. Writetime
. posixSecondsToUTCTime
. secondsToNominalDiffTime
. MkFixed
. (* 1_000_000)
. fromIntegral @Int64 @Integer
$ n
fromCql _ = Left "Writetime: bigint expected"

-- | This yields the same int as it is returned by WRITETIME()
writetimeToInt64 :: Writetime a -> Int64
writetimeToInt64 =
fromIntegral @Integer @Int64
. (`div` 1_000_000)
. unfixed
. nominalDiffTimeToSeconds
. utcTimeToPOSIXSeconds
. writetimeToUTC
where
unfixed :: Fixed a -> Integer
unfixed (MkFixed n) = n

instance ToJSON (Writetime a) where
toJSON = toJSON . writetimeToInt64
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import Data.Id
import Data.Misc
import Data.Qualified
import qualified Data.Set as Set
import Data.Time
import qualified Data.UUID as UUID
import Imports
import Wire.API.Conversation
Expand Down Expand Up @@ -84,5 +85,5 @@ testObject_ConversationCreated2 =
ccNonCreatorMembers = Set.fromList [],
ccMessageTimer = Nothing,
ccReceiptMode = Nothing,
ccProtocol = ProtocolMLS (ConversationMLSData (GroupId "group") (Epoch 3) MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519)
ccProtocol = ProtocolMLS (ConversationMLSData (GroupId "group") (Epoch 3) (Just (UTCTime (fromGregorian 2020 8 29) 0)) MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519)
}
9 changes: 9 additions & 0 deletions libs/wire-api/src/Wire/API/Conversation/Protocol.hs
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,13 @@ import Control.Arrow
import Control.Lens (makePrisms, (?~))
import Data.Aeson (FromJSON (..), ToJSON (..))
import Data.Schema
import Data.Time.Clock
import Imports
import Wire.API.Conversation.Action.Tag
import Wire.API.MLS.CipherSuite
import Wire.API.MLS.Epoch
import Wire.API.MLS.Group
import Wire.API.MLS.SubConversation
import Wire.Arbitrary

data ProtocolTag = ProtocolProteusTag | ProtocolMLSTag
Expand All @@ -52,6 +54,8 @@ data ConversationMLSData = ConversationMLSData
cnvmlsGroupId :: GroupId,
-- | The current epoch number of the corresponding MLS group.
cnvmlsEpoch :: Epoch,
-- | The time stamp of the epoch.
cnvmlsEpochTimestamp :: Maybe UTCTime,
-- | The cipher suite to be used in the MLS group.
cnvmlsCipherSuite :: CipherSuiteTag
}
Expand Down Expand Up @@ -126,6 +130,11 @@ mlsDataSchema =
"epoch"
(description ?~ "The epoch number of the corresponding MLS group")
schema
<*> cnvmlsEpochTimestamp
.= fieldWithDocModifier
"epoch_timestamp"
(description ?~ "The timestamp of the epoch number")
schemaEpochTimestamp
<*> cnvmlsCipherSuite
.= fieldWithDocModifier
"cipher_suite"
Expand Down
12 changes: 11 additions & 1 deletion libs/wire-api/src/Wire/API/MLS/SubConversation.hs
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,12 @@ import qualified Data.Aeson as A
import Data.ByteArray
import Data.ByteString.Conversion
import Data.Id
import Data.Json.Util
import Data.Qualified
import Data.Schema
import qualified Data.Swagger as S
import qualified Data.Text as T
import Data.Time.Clock
import Imports
import Servant (FromHttpApiData (..), ToHttpApiData (toQueryParam))
import Test.QuickCheck
Expand Down Expand Up @@ -77,6 +79,9 @@ data PublicSubConversation = PublicSubConversation
pscSubConvId :: SubConvId,
pscGroupId :: GroupId,
pscEpoch :: Epoch,
-- | It is 'Nothing' when the epoch is 0, and otherwise a timestamp when the
-- epoch was bumped, i.e., it is a timestamp of the most recent commit.
pscEpochTimestamp :: Maybe UTCTime,
pscCipherSuite :: CipherSuiteTag,
pscMembers :: [ClientIdentity]
}
Expand All @@ -87,15 +92,20 @@ instance ToSchema PublicSubConversation where
schema =
objectWithDocModifier
"PublicSubConversation"
(description ?~ "A MLS subconversation")
(description ?~ "An MLS subconversation")
$ PublicSubConversation
<$> pscParentConvId .= field "parent_qualified_id" schema
<*> pscSubConvId .= field "subconv_id" schema
<*> pscGroupId .= field "group_id" schema
<*> pscEpoch .= field "epoch" schema
<*> pscEpochTimestamp .= field "epoch_timestamp" schemaEpochTimestamp
<*> pscCipherSuite .= field "cipher_suite" schema
<*> pscMembers .= field "members" (array schema)

schemaEpochTimestamp :: ValueSchema NamedSwaggerDoc (Maybe UTCTime)
schemaEpochTimestamp =
named "Epoch Timestamp" . nullable . unnamed $ utcTimeSchema

data ConvOrSubTag = ConvTag | SubConvTag
deriving (Eq, Enum, Bounded)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import Data.Id (Id (Id))
import Data.Misc
import Data.Qualified
import qualified Data.Set as Set
import Data.Time.Calendar
import Data.Time.Clock
import qualified Data.UUID as UUID
import Imports
import Wire.API.Conversation
Expand Down Expand Up @@ -128,5 +130,15 @@ conv2 =
},
cmOthers = []
},
cnvProtocol = ProtocolMLS (ConversationMLSData (GroupId "test_group") (Epoch 42) MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519)
cnvProtocol =
ProtocolMLS
( ConversationMLSData
(GroupId "test_group")
(Epoch 42)
(Just timestamp)
MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519
)
}
where
timestamp :: UTCTime
timestamp = UTCTime (fromGregorian 2023 1 17) (secondsToDiffTime 42)
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ where
import Data.Domain
import Data.Id
import Data.Qualified
import Data.Time.Calendar
import Data.Time.Clock
import qualified Data.UUID as UUID
import Imports
import Wire.API.MLS.CipherSuite
Expand Down Expand Up @@ -55,8 +57,14 @@ testObject_PublicSubConversation_1 =
subConvId1
(GroupId "test_group")
(Epoch 5)
(Just (UTCTime day fromMidnight))
MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519
[]
where
fromMidnight :: DiffTime
fromMidnight = 42
day :: Day
day = fromGregorian 2023 1 17

testObject_PublicSubConversation_2 :: PublicSubConversation
testObject_PublicSubConversation_2 =
Expand All @@ -65,6 +73,7 @@ testObject_PublicSubConversation_2 =
subConvId2
(GroupId "test_group_2")
(Epoch 0)
Nothing
MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519
[mkClientIdentity user cid]
where
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
"cipher_suite": 1,
"creator": "00000000-0000-0000-0000-000200000001",
"epoch": 42,
"epoch_timestamp": "2023-01-17T00:00:42Z",
"group_id": "dGVzdF9ncm91cA==",
"id": "00000000-0000-0000-0000-000000000002",
"last_event": "0.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
"cipher_suite": 1,
"creator": "00000000-0000-0000-0000-000200000001",
"epoch": 42,
"epoch_timestamp": "2023-01-17T00:00:42Z",
"group_id": "dGVzdF9ncm91cA==",
"id": "00000000-0000-0000-0000-000000000002",
"last_event": "0.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"cipher_suite": 1,
"epoch": 5,
"epoch_timestamp": "2023-01-17T00:00:42Z",
"group_id": "dGVzdF9ncm91cA==",
"members": [],
"parent_qualified_id": {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"cipher_suite": 1,
"epoch": 0,
"epoch_timestamp": null,
"group_id": "dGVzdF9ncm91cF8y",
"members": [
{
Expand Down
33 changes: 21 additions & 12 deletions services/brig/src/Brig/User/Search/Index.hs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ import Brig.Types.Intra
import Brig.Types.Search (SearchVisibilityInbound, defaultSearchVisibilityInbound, searchVisibilityInboundFromFeatureStatus)
import Brig.User.Search.Index.Types as Types
import qualified Cassandra as C
import Cassandra.Util
import Control.Lens hiding ((#), (.=))
import Control.Monad.Catch (MonadCatch, MonadMask, MonadThrow, throwM, try)
import Control.Monad.Except
Expand All @@ -73,7 +74,6 @@ import Data.ByteString.Builder (Builder, toLazyByteString)
import Data.ByteString.Conversion (toByteString')
import qualified Data.ByteString.Conversion as Bytes
import qualified Data.ByteString.Lazy as BL
import Data.Fixed (Fixed (MkFixed))
import Data.Handle (Handle)
import Data.Id
import qualified Data.Map as Map
Expand All @@ -85,8 +85,6 @@ import Data.Text.Encoding (decodeUtf8, encodeUtf8)
import qualified Data.Text.Lazy as LT
import Data.Text.Lazy.Builder.Int (decimal)
import Data.Text.Lens hiding (text)
import Data.Time (UTCTime, secondsToNominalDiffTime)
import Data.Time.Clock.POSIX (posixSecondsToUTCTime)
import qualified Data.UUID as UUID
import qualified Database.Bloodhound as ES
import Imports hiding (log, searchable)
Expand Down Expand Up @@ -775,12 +773,6 @@ scanForIndex num = do

type Activated = Bool

type Writetime a = Int64

-- Note: Writetime is in microseconds (e-6) https://docs.datastax.com/en/dse/5.1/cql/cql/cql_using/useWritetime.html
writeTimeToUTC :: Writetime a -> UTCTime
writeTimeToUTC = posixSecondsToUTCTime . secondsToNominalDiffTime . MkFixed . (* 1_000_000) . fromIntegral @Int64 @Integer

type ReindexRow =
( UserId,
Maybe TeamId,
Expand Down Expand Up @@ -837,7 +829,20 @@ reindexRowToIndexUser
)
searchVisInbound =
do
iu <- mkIndexUser u <$> version [Just tName, tStatus, tHandle, tEmail, Just tColour, Just tActivated, tService, tManagedBy, tSsoId, tEmailUnvalidated]
iu <-
mkIndexUser u
<$> version
[ Just (v tName),
v <$> tStatus,
v <$> tHandle,
v <$> tEmail,
Just (v tColour),
Just (v tActivated),
v <$> tService,
v <$> tManagedBy,
v <$> tSsoId,
v <$> tEmailUnvalidated
]
pure $
if shouldIndex
then
Expand All @@ -850,7 +855,7 @@ reindexRowToIndexUser
. set iuAccountStatus status
. set iuSAMLIdP (idpUrl =<< ssoId)
. set iuManagedBy managedBy
. set iuCreatedAt (Just (writeTimeToUTC tActivated))
. set iuCreatedAt (Just (writetimeToUTC tActivated))
. set iuSearchVisibilityInbound (Just searchVisInbound)
. set iuScimExternalId (join $ User.scimExternalId <$> managedBy <*> ssoId)
. set iuSso (sso =<< ssoId)
Expand All @@ -861,8 +866,12 @@ reindexRowToIndexUser
-- It's mostly empty, but having the status here might be useful in the future.
& set iuAccountStatus status
where
version :: [Maybe (Writetime Name)] -> m IndexVersion
v :: Writetime a -> Int64
v = writetimeToInt64

version :: [Maybe Int64] -> m IndexVersion
version = mkIndexVersion . getMax . mconcat . fmap Max . catMaybes

shouldIndex =
( case status of
Nothing -> True
Expand Down
23 changes: 22 additions & 1 deletion services/brig/test/integration/API/Internal.hs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
--
-- 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/>.
{-# LANGUAGE NumericUnderscores #-}

module API.Internal
( tests,
Expand All @@ -28,7 +29,9 @@ import Bilge.Assert
import Brig.Data.User (lookupFeatureConferenceCalling, lookupStatus, userExists)
import qualified Brig.Options as Opt
import Brig.Types.Intra
import qualified Cassandra as C
import qualified Cassandra as Cass
import Cassandra.Util
import Control.Exception (ErrorCall (ErrorCall), throwIO)
import Control.Lens ((^.), (^?!))
import Control.Monad.Catch
Expand Down Expand Up @@ -77,7 +80,8 @@ tests opts mgr db brig brigep gundeck galley = do
test mgr "get,get" $ testKpcGetGet brig,
test mgr "put,put" $ testKpcPutPut brig,
test mgr "add key package ref" $ testAddKeyPackageRef brig
]
],
test mgr "writetimeToInt64" $ testWritetimeRepresentation opts mgr db brig brigep galley
]

testSuspendUser :: forall m. TestConstraints m => Cass.ClientState -> Brig -> m ()
Expand Down Expand Up @@ -370,3 +374,20 @@ getFeatureConfig galley uid = do
getAllFeatureConfigs :: (MonadIO m, MonadHttp m, HasCallStack) => (Request -> Request) -> UserId -> m ResponseLBS
getAllFeatureConfigs galley uid = do
get $ galley . paths ["feature-configs"] . zUser uid

testWritetimeRepresentation :: forall m. TestConstraints m => Opt.Opts -> Manager -> Cass.ClientState -> Brig -> Endpoint -> Galley -> m ()
testWritetimeRepresentation _ _mgr db brig _brigep _galley = do
quid <- userQualifiedId <$> randomUser brig
let uid = qUnqualified quid

ref <- fromJust <$> (runIdentity <$$> Cass.runClient db (C.query1 q1 (C.params C.LocalQuorum (Identity uid))))

wt <- fromJust <$> (runIdentity <$$> Cass.runClient db (C.query1 q2 (C.params C.LocalQuorum (Identity uid))))

liftIO $ assertEqual "ts representaiton do not match" ref (writetimeToInt64 wt)
where
q1 :: C.PrepQuery C.R (Identity UserId) (Identity Int64)
q1 = "SELECT WRITETIME(status) from user where id = ?"

q2 :: C.PrepQuery C.R (Identity UserId) (Identity (Writetime ()))
q2 = "SELECT WRITETIME(status) from user where id = ?"
Loading