diff --git a/cassandra-schema.cql b/cassandra-schema.cql index 5eb68efc44..81316299b9 100644 --- a/cassandra-schema.cql +++ b/cassandra-schema.cql @@ -128,6 +128,7 @@ CREATE TABLE galley_test.team_features ( app_lock_status int, conference_calling int, digital_signatures int, + expose_invitation_urls_to_team_admin int, file_sharing int, file_sharing_lock_status int, guest_links_lock_status int, diff --git a/changelog.d/2-features/registration-url-in-invitation b/changelog.d/2-features/registration-url-in-invitation new file mode 100644 index 0000000000..eb9669b1fc --- /dev/null +++ b/changelog.d/2-features/registration-url-in-invitation @@ -0,0 +1 @@ +Optionally add invitation urls to the body of `/teams/{tid}/invitations`. This allows further processing; e.g. to send those links with custom emails or distribute them as QR codes. See [docs](https://docs.wire.com/developer/reference/config-options.html#expose-invitation-urls-to-team-admin) for details and privacy implications. diff --git a/charts/galley/templates/configmap.yaml b/charts/galley/templates/configmap.yaml index 9a877ab26f..c5ce757ace 100644 --- a/charts/galley/templates/configmap.yaml +++ b/charts/galley/templates/configmap.yaml @@ -54,6 +54,9 @@ data: {{- if .settings.maxFanoutSize }} maxFanoutSize: {{ .settings.maxFanoutSize }} {{- end }} + {{- if .settings.exposeInvitationURLsTeamAllowlist }} + exposeInvitationURLsTeamAllowlist: {{ .settings.exposeInvitationURLsTeamAllowlist }} + {{- end }} conversationCodeURI: {{ .settings.conversationCodeURI | quote }} {{- if .settings.enableIndexedBillingTeamMembers }} enableIndexedBillingTeamMembers: {{ .settings.enableIndexedBillingTeamMembers }} @@ -92,15 +95,15 @@ data: {{- if .settings.featureFlags.appLock }} appLock: {{- toYaml .settings.featureFlags.appLock | nindent 10 }} - {{- end }} + {{- end }} {{- if .settings.featureFlags.conferenceCalling }} conferenceCalling: {{- toYaml .settings.featureFlags.conferenceCalling | nindent 10 }} - {{- end }} + {{- end }} {{- if .settings.featureFlags.selfDeletingMessages }} selfDeletingMessages: {{- toYaml .settings.featureFlags.selfDeletingMessages | nindent 10 }} - {{- end }} + {{- end }} {{- if .settings.featureFlags.conversationGuestLinks }} conversationGuestLinks: {{- toYaml .settings.featureFlags.conversationGuestLinks | nindent 10 }} diff --git a/charts/galley/values.yaml b/charts/galley/values.yaml index 2c6fa9a6c4..4ee17754e5 100644 --- a/charts/galley/values.yaml +++ b/charts/galley/values.yaml @@ -26,6 +26,7 @@ config: settings: httpPoolSize: 128 maxTeamSize: 10000 + exposeInvitationURLsTeamAllowlist: [] maxConvSize: 500 # Before making indexedBillingTeamMember true while upgrading, please # refer to notes here: https://github.com/wireapp/wire-server-deploy/releases/tag/v2020-05-15 @@ -79,7 +80,7 @@ config: validateSAMLemails: defaults: status: enabled - + aws: region: "eu-west-1" proxy: {} diff --git a/docs/src/developer/reference/config-options.md b/docs/src/developer/reference/config-options.md index 8a74338aa7..07ce0ec7ca 100644 --- a/docs/src/developer/reference/config-options.md +++ b/docs/src/developer/reference/config-options.md @@ -99,6 +99,53 @@ IMPORTANT: If you switch this back to `disabled-permanently` from that have created them while it was allowed. This may change in the future. +### Expose invitation URLs to team admin + +For further processing (e.g. sending custom emails or rendering the URLs as QR +codes), team invitation URLs can be made part of the result of +`GET /teams/{tid}/invitations`. + +```json +{ + "has_more": false, + "invitations": [ + { + "created_at": "2022-09-15T15:47:28.577Z", + "created_by": "375f56fe-7f12-4c0c-aed8-d48c0326d1fb", + "email": "foo@example.com", + "id": "4decf7f8-bdd4-43b3-aaf2-e912e2c0c46f", + "name": null, + "phone": null, + "role": "member", + "team": "51612209-3b61-49b0-8c55-d21ae65efc1a", + "url": "http://127.0.0.1:8080/register?team=51612209-3b61-49b0-8c55-d21ae65efc1a&team_code=RpxGkK_yjw8ZBegJuFQO0hha-2Tneajp" + } + ] +} +``` + +This can be a privacy issue as it allows the team admin to impersonate as +another team member. The feature is disabled by default. + +To activate this feature two steps are needed. First, the team id (tid) has to +be added to the list of teams for which this feature *can* be enabled +(`exposeInvitationURLsTeamAllowlist`). This is done in `galley`'s `values.yaml`: + +```yaml +settings: + exposeInvitationURLsTeamAllowlist: ["51612209-3b61-49b0-8c55-d21ae65efc1a", ...] +``` + +Then, the feature can be set for the team by enabling the +`exposeInvitationURLsToTeamAdmin` flag. This is done by making a `PUT` request +to `/teams/{tid}/features/exposeInvitationURLsToTeamAdmin` with the body: + +```json +{ + "status": "enabled" +} +``` + ### Team searchVisibility The team flag `searchVisibility` affects the outbound search of user 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 f80ff25aa3..de1bd25b34 100644 --- a/libs/wire-api/src/Wire/API/Routes/Public/Galley.hs +++ b/libs/wire-api/src/Wire/API/Routes/Public/Galley.hs @@ -1136,6 +1136,8 @@ type FeatureAPI = :<|> FeatureStatusPut '() SndFactorPasswordChallengeConfig :<|> FeatureStatusGet MLSConfig :<|> FeatureStatusPut '() MLSConfig + :<|> FeatureStatusGet ExposeInvitationURLsToTeamAdminConfig + :<|> FeatureStatusPut '() ExposeInvitationURLsToTeamAdminConfig :<|> FeatureStatusGet SearchVisibilityInboundConfig :<|> FeatureStatusPut '() SearchVisibilityInboundConfig :<|> AllFeatureConfigsUserGet diff --git a/libs/wire-api/src/Wire/API/Team/Feature.hs b/libs/wire-api/src/Wire/API/Team/Feature.hs index 1e2a33f803..41eb91997a 100644 --- a/libs/wire-api/src/Wire/API/Team/Feature.hs +++ b/libs/wire-api/src/Wire/API/Team/Feature.hs @@ -67,6 +67,7 @@ module Wire.API.Team.Feature DigitalSignaturesConfig (..), ConferenceCallingConfig (..), GuestLinksConfig (..), + ExposeInvitationURLsToTeamAdminConfig (..), SndFactorPasswordChallengeConfig (..), SearchVisibilityInboundConfig (..), ClassifiedDomainsConfig (..), @@ -579,6 +580,7 @@ allFeatureModels = withStatusNoLockModel @SndFactorPasswordChallengeConfig, withStatusNoLockModel @SearchVisibilityInboundConfig, withStatusNoLockModel @MLSConfig, + withStatusNoLockModel @ExposeInvitationURLsToTeamAdminConfig, withStatusModel @LegalholdConfig, withStatusModel @SSOConfig, withStatusModel @SearchVisibilityAvailableConfig, @@ -592,7 +594,8 @@ allFeatureModels = withStatusModel @GuestLinksConfig, withStatusModel @SndFactorPasswordChallengeConfig, withStatusModel @SearchVisibilityInboundConfig, - withStatusModel @MLSConfig + withStatusModel @MLSConfig, + withStatusModel @ExposeInvitationURLsToTeamAdminConfig ] <> catMaybes [ configModel @LegalholdConfig, @@ -608,7 +611,8 @@ allFeatureModels = configModel @GuestLinksConfig, configModel @SndFactorPasswordChallengeConfig, configModel @SearchVisibilityInboundConfig, - configModel @MLSConfig + configModel @MLSConfig, + configModel @ExposeInvitationURLsToTeamAdminConfig ] -------------------------------------------------------------------------------- @@ -939,6 +943,24 @@ instance IsFeatureConfig MLSConfig where Doc.property "allowedCipherSuites" (Doc.array Doc.int32') $ Doc.description "cipher suite numbers, See https://messaginglayersecurity.rocks/mls-protocol/draft-ietf-mls-protocol.html#table-5" Doc.property "defaultCipherSuite" Doc.int32' $ Doc.description "cipher suite number. See https://messaginglayersecurity.rocks/mls-protocol/draft-ietf-mls-protocol.html#table-5" +---------------------------------------------------------------------- +-- ExposeInvitationURLsToTeamAdminConfig + +data ExposeInvitationURLsToTeamAdminConfig = ExposeInvitationURLsToTeamAdminConfig + deriving stock (Show, Eq, Generic) + deriving (Arbitrary) via (GenericUniform ExposeInvitationURLsToTeamAdminConfig) + +instance IsFeatureConfig ExposeInvitationURLsToTeamAdminConfig where + type FeatureSymbol ExposeInvitationURLsToTeamAdminConfig = "exposeInvitationURLsToTeamAdmin" + defFeatureStatus = withStatus FeatureStatusDisabled LockStatusLocked ExposeInvitationURLsToTeamAdminConfig FeatureTTLUnlimited + objectSchema = pure ExposeInvitationURLsToTeamAdminConfig + +instance ToSchema ExposeInvitationURLsToTeamAdminConfig where + schema = object "ExposeInvitationURLsToTeamAdminConfig" objectSchema + +instance FeatureTrivialConfig ExposeInvitationURLsToTeamAdminConfig where + trivialConfig = ExposeInvitationURLsToTeamAdminConfig + ---------------------------------------------------------------------- -- FeatureStatus @@ -1007,7 +1029,8 @@ data AllFeatureConfigs = AllFeatureConfigs afcSelfDeletingMessages :: WithStatus SelfDeletingMessagesConfig, afcGuestLink :: WithStatus GuestLinksConfig, afcSndFactorPasswordChallenge :: WithStatus SndFactorPasswordChallengeConfig, - afcMLS :: WithStatus MLSConfig + afcMLS :: WithStatus MLSConfig, + afcExposeInvitationURLsToTeamAdmin :: WithStatus ExposeInvitationURLsToTeamAdminConfig } deriving stock (Eq, Show) deriving (FromJSON, ToJSON, S.ToSchema) via (Schema AllFeatureConfigs) @@ -1030,6 +1053,7 @@ instance ToSchema AllFeatureConfigs where <*> afcGuestLink .= featureField <*> afcSndFactorPasswordChallenge .= featureField <*> afcMLS .= featureField + <*> afcExposeInvitationURLsToTeamAdmin .= featureField where featureField :: forall cfg. @@ -1054,5 +1078,6 @@ instance Arbitrary AllFeatureConfigs where <*> arbitrary <*> arbitrary <*> arbitrary + <*> arbitrary makeLenses ''ImplicitLockStatus diff --git a/libs/wire-api/src/Wire/API/Team/Invitation.hs b/libs/wire-api/src/Wire/API/Team/Invitation.hs index 4476698097..efcc60de35 100644 --- a/libs/wire-api/src/Wire/API/Team/Invitation.hs +++ b/libs/wire-api/src/Wire/API/Team/Invitation.hs @@ -34,6 +34,7 @@ import Data.Id import Data.Json.Util import qualified Data.Swagger.Build.Api as Doc import Imports +import URI.ByteString import Wire.API.Team.Role (Role, defaultRole, typeRole) import Wire.API.User.Identity (Email, Phone) import Wire.API.User.Profile (Locale, Name) @@ -104,7 +105,8 @@ data Invitation = Invitation inCreatedBy :: Maybe UserId, inInviteeEmail :: Email, inInviteeName :: Maybe Name, - inInviteePhone :: Maybe Phone + inInviteePhone :: Maybe Phone, + inInviteeUrl :: Maybe (URIRef Absolute) } deriving stock (Eq, Show, Generic) deriving (Arbitrary) via (GenericUniform Invitation) @@ -134,6 +136,9 @@ modelTeamInvitation = Doc.defineModel "TeamInvitation" $ do Doc.property "phone" Doc.string' $ do Doc.description "Phone number of the invitee, in the E.164 format" Doc.optional + Doc.property "url" Doc.string' $ do + Doc.description "URL of the invitation link to be sent to the invitee" + Doc.optional instance ToJSON Invitation where toJSON i = @@ -145,7 +150,8 @@ instance ToJSON Invitation where "created_by" .= inCreatedBy i, "email" .= inInviteeEmail i, "name" .= inInviteeName i, - "phone" .= inInviteePhone i + "phone" .= inInviteePhone i, + "url" .= inInviteeUrl i ] instance FromJSON Invitation where @@ -160,6 +166,7 @@ instance FromJSON Invitation where <*> o .: "email" <*> o .:? "name" <*> o .:? "phone" + <*> o .:? "url" -------------------------------------------------------------------------------- -- InvitationList diff --git a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/InvitationList_team.hs b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/InvitationList_team.hs index 5a3d0eee55..2b94790051 100644 --- a/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/InvitationList_team.hs +++ b/libs/wire-api/test/golden/Test/Wire/API/Golden/Generated/InvitationList_team.hs @@ -19,10 +19,12 @@ module Test.Wire.API.Golden.Generated.InvitationList_team where +import Data.Either.Combinators import Data.Id (Id (Id)) import Data.Json.Util (readUTCTimeMillis) import qualified Data.UUID as UUID (fromString) import Imports (Bool (False, True), Maybe (Just, Nothing), fromJust) +import URI.ByteString (parseURI, strictURIParserOptions) import Wire.API.Team.Invitation ( Invitation ( Invitation, @@ -32,6 +34,7 @@ import Wire.API.Team.Invitation inInviteeEmail, inInviteeName, inInviteePhone, + inInviteeUrl, inRole, inTeam ), @@ -49,10 +52,10 @@ testObject_InvitationList_team_2 = InvitationList { ilInvitations = [ Invitation - { inTeam = (Id (fromJust (UUID.fromString "00000000-0000-0000-0000-000000000001"))), + { inTeam = Id (fromJust (UUID.fromString "00000000-0000-0000-0000-000000000001")), inRole = RoleOwner, - inInvitation = (Id (fromJust (UUID.fromString "00000001-0000-0001-0000-000000000000"))), - inCreatedAt = (fromJust (readUTCTimeMillis "1864-05-08T09:28:36.729Z")), + inInvitation = Id (fromJust (UUID.fromString "00000001-0000-0001-0000-000000000000")), + inCreatedAt = fromJust (readUTCTimeMillis "1864-05-08T09:28:36.729Z"), inCreatedBy = Just (Id (fromJust (UUID.fromString "00000001-0000-0001-0000-000000000000"))), inInviteeEmail = Email {emailLocal = "\153442", emailDomain = "w"}, inInviteeName = @@ -62,7 +65,8 @@ testObject_InvitationList_team_2 = "fuC9p\1098501A\163554\f\ENQ\SO\21027N\47326_?oCX.U\r\163744W\33096\58996\1038685\DC3\t[\37667\SYN/\8408A\145025\173325\DC4H\135001\STX\166880\EOT\165028o\DC3" } ), - inInviteePhone = Just (Phone {fromPhone = "+851333011"}) + inInviteePhone = Just (Phone {fromPhone = "+851333011"}), + inInviteeUrl = Just (fromRight' (parseURI strictURIParserOptions "https://example.com/inv14")) } ], ilHasMore = True @@ -76,10 +80,10 @@ testObject_InvitationList_team_4 = InvitationList { ilInvitations = [ Invitation - { inTeam = (Id (fromJust (UUID.fromString "00000000-0000-0001-0000-000000000000"))), + { inTeam = Id (fromJust (UUID.fromString "00000000-0000-0001-0000-000000000000")), inRole = RoleAdmin, - inInvitation = (Id (fromJust (UUID.fromString "00000001-0000-0001-0000-000100000000"))), - inCreatedAt = (fromJust (readUTCTimeMillis "1864-05-09T19:46:50.121Z")), + inInvitation = Id (fromJust (UUID.fromString "00000001-0000-0001-0000-000100000000")), + inCreatedAt = fromJust (readUTCTimeMillis "1864-05-09T19:46:50.121Z"), inCreatedBy = Just (Id (fromJust (UUID.fromString "00000001-0000-0001-0000-000000000000"))), inInviteeEmail = Email {emailLocal = "", emailDomain = ""}, inInviteeName = @@ -89,13 +93,14 @@ testObject_InvitationList_team_4 = "R6\133444\134053VQ\187682\SUB\SOH\180538\&0C\1088909\ESCR\185800\125002@\38857Z?\STX\169387\1067878e}\SOH\ETB\EOTm\184898\US]\986782\189015\1059374\986508\b\DC1zfw-5\120662\CAN\1064450 \EMe\DC4|\14426Vo{\1076439\DC3#\USS\45051&zz\160719\&9\142411,\SI\f\SOHp\1025840\DLE\163178\1060369.&\997544kZ\50431u\b\50764\1109279n:\1103691D$.Q" } ), - inInviteePhone = Just (Phone {fromPhone = "+60506387292"}) + inInviteePhone = Just (Phone {fromPhone = "+60506387292"}), + inInviteeUrl = Nothing }, Invitation - { inTeam = (Id (fromJust (UUID.fromString "00000000-0000-0001-0000-000100000000"))), + { inTeam = Id (fromJust (UUID.fromString "00000000-0000-0001-0000-000100000000")), inRole = RoleAdmin, - inInvitation = (Id (fromJust (UUID.fromString "00000001-0000-0000-0000-000100000000"))), - inCreatedAt = (fromJust (readUTCTimeMillis "1864-05-09T09:00:02.901Z")), + inInvitation = Id (fromJust (UUID.fromString "00000001-0000-0000-0000-000100000000")), + inCreatedAt = fromJust (readUTCTimeMillis "1864-05-09T09:00:02.901Z"), inCreatedBy = Just (Id (fromJust (UUID.fromString "00000000-0000-0001-0000-000000000000"))), inInviteeEmail = Email {emailLocal = "", emailDomain = ""}, inInviteeName = @@ -105,13 +110,14 @@ testObject_InvitationList_team_4 = "\DC2}q\CAN=SA\ETXx\t\ETX\\\v[\b)(\ESC]\135875Y\v@p\41515l\45065\157388\NUL\t\1100066\SOH1\DC1\ENQ\1021763\"i\29460\EM\b\ACK\SI\DC2v\ACK" } ), - inInviteePhone = Just (Phone {fromPhone = "+913945015"}) + inInviteePhone = Just (Phone {fromPhone = "+913945015"}), + inInviteeUrl = Nothing }, Invitation - { inTeam = (Id (fromJust (UUID.fromString "00000001-0000-0000-0000-000000000001"))), + { inTeam = Id (fromJust (UUID.fromString "00000001-0000-0000-0000-000000000001")), inRole = RoleMember, - inInvitation = (Id (fromJust (UUID.fromString "00000001-0000-0001-0000-000100000001"))), - inCreatedAt = (fromJust (readUTCTimeMillis "1864-05-09T11:10:31.203Z")), + inInvitation = Id (fromJust (UUID.fromString "00000001-0000-0001-0000-000100000001")), + inCreatedAt = fromJust (readUTCTimeMillis "1864-05-09T11:10:31.203Z"), inCreatedBy = Just (Id (fromJust (UUID.fromString "00000001-0000-0001-0000-000000000000"))), inInviteeEmail = Email {emailLocal = "", emailDomain = ""}, inInviteeName = @@ -121,13 +127,14 @@ testObject_InvitationList_team_4 = "\58076&\1059325Ec\NUL\16147}k\1036184l\172911\USJ\EM0^.+F\DEL\NUL\f$'`!\ETB[p\1041609}>E0y\96440#4I\a\66593jc\ESCgt\22473\1093208P\DC4!\1095909E93'Y$YL\46886b\r:,\181790\SO\153247y\ETX;\1064633\1099478z4z-D\1096755a\139100\&6\164829r\1033640\987906J\DLE\48134" } ), - inInviteePhone = Just (Phone {fromPhone = "+17046334"}) + inInviteePhone = Just (Phone {fromPhone = "+17046334"}), + inInviteeUrl = Nothing }, Invitation - { inTeam = (Id (fromJust (UUID.fromString "00000000-0000-0000-0000-000000000000"))), + { inTeam = Id (fromJust (UUID.fromString "00000000-0000-0000-0000-000000000000")), inRole = RoleOwner, - inInvitation = (Id (fromJust (UUID.fromString "00000001-0000-0000-0000-000000000000"))), - inCreatedAt = (fromJust (readUTCTimeMillis "1864-05-09T23:41:34.529Z")), + inInvitation = Id (fromJust (UUID.fromString "00000001-0000-0000-0000-000000000000")), + inCreatedAt = fromJust (readUTCTimeMillis "1864-05-09T23:41:34.529Z"), inCreatedBy = Just (Id (fromJust (UUID.fromString "00000001-0000-0001-0000-000100000000"))), inInviteeEmail = Email {emailLocal = "", emailDomain = ""}, inInviteeName = @@ -137,23 +144,25 @@ testObject_InvitationList_team_4 = "Ft*O1\b&\SO\CAN<\72219\1092619m\n\DC4\DC2; \ETX\988837\DC1\1059627\"k.T\1023249[[\FS\EOT{j`\GS\997342c\1066411{\SUB\GSQY\182805\t\NAKy\t\132339j\1036225W " } ), - inInviteePhone = Nothing + inInviteePhone = Nothing, + inInviteeUrl = Nothing }, Invitation - { inTeam = (Id (fromJust (UUID.fromString "00000001-0000-0000-0000-000100000000"))), + { inTeam = Id (fromJust (UUID.fromString "00000001-0000-0000-0000-000100000000")), inRole = RoleAdmin, - inInvitation = (Id (fromJust (UUID.fromString "00000001-0000-0000-0000-000000000000"))), - inCreatedAt = (fromJust (readUTCTimeMillis "1864-05-09T00:29:17.658Z")), + inInvitation = Id (fromJust (UUID.fromString "00000001-0000-0000-0000-000000000000")), + inCreatedAt = fromJust (readUTCTimeMillis "1864-05-09T00:29:17.658Z"), inCreatedBy = Just (Id (fromJust (UUID.fromString "00000000-0000-0000-0000-000000000001"))), inInviteeEmail = Email {emailLocal = "", emailDomain = ""}, inInviteeName = Nothing, - inInviteePhone = Just (Phone {fromPhone = "+918848647685283"}) + inInviteePhone = Just (Phone {fromPhone = "+918848647685283"}), + inInviteeUrl = Nothing }, Invitation - { inTeam = (Id (fromJust (UUID.fromString "00000001-0000-0000-0000-000100000001"))), + { inTeam = Id (fromJust (UUID.fromString "00000001-0000-0000-0000-000100000001")), inRole = RoleOwner, - inInvitation = (Id (fromJust (UUID.fromString "00000000-0000-0000-0000-000100000001"))), - inCreatedAt = (fromJust (readUTCTimeMillis "1864-05-09T13:34:37.117Z")), + inInvitation = Id (fromJust (UUID.fromString "00000000-0000-0000-0000-000100000001")), + inCreatedAt = fromJust (readUTCTimeMillis "1864-05-09T13:34:37.117Z"), inCreatedBy = Just (Id (fromJust (UUID.fromString "00000000-0000-0000-0000-000100000001"))), inInviteeEmail = Email {emailLocal = "", emailDomain = ""}, inInviteeName = @@ -163,13 +172,14 @@ testObject_InvitationList_team_4 = "Lo\r\1107113 @@ -31,62 +29,66 @@ import Wire.API.User.Profile (Name (Name, fromName)) testObject_Invitation_team_1 :: Invitation testObject_Invitation_team_1 = Invitation - { inTeam = (Id (fromJust (UUID.fromString "00000002-0000-0001-0000-000200000002"))), + { inTeam = Id (fromJust (UUID.fromString "00000002-0000-0001-0000-000200000002")), inRole = RoleAdmin, - inInvitation = (Id (fromJust (UUID.fromString "00000002-0000-0001-0000-000200000000"))), - inCreatedAt = (fromJust (readUTCTimeMillis "1864-05-11T20:13:15.856Z")), + inInvitation = Id (fromJust (UUID.fromString "00000002-0000-0001-0000-000200000000")), + inCreatedAt = fromJust (readUTCTimeMillis "1864-05-11T20:13:15.856Z"), inCreatedBy = Just (Id (fromJust (UUID.fromString "00000002-0000-0000-0000-000100000001"))), inInviteeEmail = Email {emailLocal = "\FS\58114Y", emailDomain = "7"}, inInviteeName = Nothing, - inInviteePhone = Just (Phone {fromPhone = "+54687000371"}) + inInviteePhone = Just (Phone {fromPhone = "+54687000371"}), + inInviteeUrl = Nothing } testObject_Invitation_team_2 :: Invitation testObject_Invitation_team_2 = Invitation - { inTeam = (Id (fromJust (UUID.fromString "00000000-0000-0001-0000-000000000000"))), + { inTeam = Id (fromJust (UUID.fromString "00000000-0000-0001-0000-000000000000")), inRole = RoleExternalPartner, - inInvitation = (Id (fromJust (UUID.fromString "00000002-0000-0001-0000-000100000002"))), - inCreatedAt = (fromJust (readUTCTimeMillis "1864-05-12T14:47:35.551Z")), + inInvitation = Id (fromJust (UUID.fromString "00000002-0000-0001-0000-000100000002")), + inCreatedAt = fromJust (readUTCTimeMillis "1864-05-12T14:47:35.551Z"), inCreatedBy = Just (Id (fromJust (UUID.fromString "00000002-0000-0001-0000-000200000001"))), inInviteeEmail = Email {emailLocal = "i", emailDomain = "m_:"}, inInviteeName = Just (Name {fromName = "\1067847} 2pGEW+\rT\171609p\174643\157218&\146145v0\b"}), - inInviteePhone = Nothing + inInviteePhone = Nothing, + inInviteeUrl = Nothing } testObject_Invitation_team_3 :: Invitation testObject_Invitation_team_3 = Invitation - { inTeam = (Id (fromJust (UUID.fromString "00000002-0000-0001-0000-000100000001"))), + { inTeam = Id (fromJust (UUID.fromString "00000002-0000-0001-0000-000100000001")), inRole = RoleExternalPartner, - inInvitation = (Id (fromJust (UUID.fromString "00000002-0000-0001-0000-000100000002"))), - inCreatedAt = (fromJust (readUTCTimeMillis "1864-05-08T22:07:35.846Z")), + inInvitation = Id (fromJust (UUID.fromString "00000002-0000-0001-0000-000100000002")), + inCreatedAt = fromJust (readUTCTimeMillis "1864-05-08T22:07:35.846Z"), inCreatedBy = Just (Id (fromJust (UUID.fromString "00000001-0000-0002-0000-000200000001"))), inInviteeEmail = Email {emailLocal = "", emailDomain = "\31189L"}, inInviteeName = Nothing, - inInviteePhone = Nothing + inInviteePhone = Nothing, + inInviteeUrl = Nothing } testObject_Invitation_team_4 :: Invitation testObject_Invitation_team_4 = Invitation - { inTeam = (Id (fromJust (UUID.fromString "00000000-0000-0000-0000-000100000000"))), + { inTeam = Id (fromJust (UUID.fromString "00000000-0000-0000-0000-000100000000")), inRole = RoleAdmin, - inInvitation = (Id (fromJust (UUID.fromString "00000001-0000-0001-0000-000000000001"))), - inCreatedAt = (fromJust (readUTCTimeMillis "1864-05-09T09:23:58.270Z")), + inInvitation = Id (fromJust (UUID.fromString "00000001-0000-0001-0000-000000000001")), + inCreatedAt = fromJust (readUTCTimeMillis "1864-05-09T09:23:58.270Z"), inCreatedBy = Just (Id (fromJust (UUID.fromString "00000000-0000-0000-0000-000200000001"))), inInviteeEmail = Email {emailLocal = "^", emailDomain = "e"}, inInviteeName = Nothing, - inInviteePhone = Nothing + inInviteePhone = Nothing, + inInviteeUrl = Nothing } testObject_Invitation_team_5 :: Invitation testObject_Invitation_team_5 = Invitation - { inTeam = (Id (fromJust (UUID.fromString "00000002-0000-0000-0000-000000000001"))), + { inTeam = Id (fromJust (UUID.fromString "00000002-0000-0000-0000-000000000001")), inRole = RoleOwner, - inInvitation = (Id (fromJust (UUID.fromString "00000000-0000-0002-0000-000000000002"))), - inCreatedAt = (fromJust (readUTCTimeMillis "1864-05-09T03:42:15.266Z")), + inInvitation = Id (fromJust (UUID.fromString "00000000-0000-0002-0000-000000000002")), + inCreatedAt = fromJust (readUTCTimeMillis "1864-05-09T03:42:15.266Z"), inCreatedBy = Nothing, inInviteeEmail = Email {emailLocal = "\SOHV", emailDomain = "f\1086249\43462"}, inInviteeName = @@ -96,16 +98,17 @@ testObject_Invitation_team_5 = "}G_\147658`X\1028823\131485\1014942L\"\1047959e6:E\DEL\51733\993223f-$\133906Z!s2p?#\tF 8\188400\165247\1023303\EOT\1087640*\1017476\SYN\DLE%Y\167940>\1111565\1042998\1027480g\"\1055088\SUB\SUB\180703\43419\EOTv\188258,\171408(\GSQT\150160;\1063450\ENQ\ETBB\1106414H\170195\\\1040638,Y" } ), - inInviteePhone = Just (Phone {fromPhone = "+45207005641274"}) + inInviteePhone = Just (Phone {fromPhone = "+45207005641274"}), + inInviteeUrl = Nothing } testObject_Invitation_team_6 :: Invitation testObject_Invitation_team_6 = Invitation - { inTeam = (Id (fromJust (UUID.fromString "00000001-0000-0000-0000-000000000001"))), + { inTeam = Id (fromJust (UUID.fromString "00000001-0000-0000-0000-000000000001")), inRole = RoleAdmin, - inInvitation = (Id (fromJust (UUID.fromString "00000001-0000-0002-0000-000100000000"))), - inCreatedAt = (fromJust (readUTCTimeMillis "1864-05-09T08:56:40.919Z")), + inInvitation = Id (fromJust (UUID.fromString "00000001-0000-0002-0000-000100000000")), + inCreatedAt = fromJust (readUTCTimeMillis "1864-05-09T08:56:40.919Z"), inCreatedBy = Just (Id (fromJust (UUID.fromString "00000001-0000-0001-0000-000200000000"))), inInviteeEmail = Email {emailLocal = "", emailDomain = "OC"}, inInviteeName = @@ -115,16 +118,17 @@ testObject_Invitation_team_6 = "O~\DC4U\RS?V3_\191280Slh\1072236Q1\1011443j|~M7\1092762\1097596\94632\DC1K\1078140Afs\178951lGV\1113159]`o\EMf\34020InvfDDy\\DI\163761\1091945\ETBB\159212F*X\SOH\SUB\50580\ETX\DLE<\ETX\SYNc\DEL\DLE,p\v*\1005720Vn\fI\70201xS\STXV\ESC$\EMu\1002390xl>\aZ\DC44e\DC4aZ" } ), - inInviteePhone = Just (Phone {fromPhone = "+75547625285"}) + inInviteePhone = Just (Phone {fromPhone = "+75547625285"}), + inInviteeUrl = Nothing } testObject_Invitation_team_7 :: Invitation testObject_Invitation_team_7 = Invitation - { inTeam = (Id (fromJust (UUID.fromString "00000000-0000-0001-0000-000200000001"))), + { inTeam = Id (fromJust (UUID.fromString "00000000-0000-0001-0000-000200000001")), inRole = RoleExternalPartner, - inInvitation = (Id (fromJust (UUID.fromString "00000000-0000-0000-0000-000000000002"))), - inCreatedAt = (fromJust (readUTCTimeMillis "1864-05-07T18:46:22.786Z")), + inInvitation = Id (fromJust (UUID.fromString "00000000-0000-0000-0000-000000000002")), + inCreatedAt = fromJust (readUTCTimeMillis "1864-05-07T18:46:22.786Z"), inCreatedBy = Just (Id (fromJust (UUID.fromString "00000000-0000-0002-0000-000100000000"))), inInviteeEmail = Email {emailLocal = "oj", emailDomain = ""}, inInviteeName = @@ -134,55 +138,59 @@ testObject_Invitation_team_7 = "\CAN.\110967\1085214\DLE\f\DLE\CAN\150564o;Yay:yY $\ETX<\879%@\USre>5L'R\DC3\178035oy#]c4!\99741U\54858\26279\1042232\1062242p_>f\SO\DEL\175240\1077738\995735_Vm\US}\STXPz\r\ENQK\SO+>\991648\NUL\153467?pu?r\ESC\SUB!?\168405;\6533S\18757\a\1071148\b\1023581\996567\17385\120022\b\SUB\FS\SIF%<\125113\SIh\ESC\ETX\SI\994739\USO\NULg_\151272\47274\1026399\EOT\1058084\1089771z~%IA'R\b\1011572Hv^\1043633wrjb\t\166747\ETX" } ), - inInviteePhone = Just (Phone {fromPhone = "+518729615781"}) + inInviteePhone = Just (Phone {fromPhone = "+518729615781"}), + inInviteeUrl = Nothing } testObject_Invitation_team_12 :: Invitation testObject_Invitation_team_12 = Invitation - { inTeam = (Id (fromJust (UUID.fromString "00000000-0000-0000-0000-000000000002"))), + { inTeam = Id (fromJust (UUID.fromString "00000000-0000-0000-0000-000000000002")), inRole = RoleAdmin, - inInvitation = (Id (fromJust (UUID.fromString "00000000-0000-0000-0000-000100000002"))), - inCreatedAt = (fromJust (readUTCTimeMillis "1864-05-12T22:47:35.829Z")), + inInvitation = Id (fromJust (UUID.fromString "00000000-0000-0000-0000-000100000002")), + inCreatedAt = fromJust (readUTCTimeMillis "1864-05-12T22:47:35.829Z"), inCreatedBy = Just (Id (fromJust (UUID.fromString "00000002-0000-0002-0000-000000000000"))), inInviteeEmail = Email {emailLocal = "\1016862\141073\RS", emailDomain = ""}, inInviteeName = @@ -211,42 +220,45 @@ testObject_Invitation_team_12 = "\DLEZ+wd^\67082\1073384\&1\STXYdXt>\1081020LSB7F9\\\135148\ENQ\n\987295\"\127009|\a\61724\157754\DEL'\ESCTygU\1106772R\52822\1071584O4\1035713E9\"\1016016\DC2Re\ENQD}\1051112\161959\1104733\bV\176894%98'\RS9\ACK4yP\83405\14400\345\aw\t\1098022\v\1078003xv/Yl\1005740\158703" } ), - inInviteePhone = Just (Phone {fromPhone = "+68945103783764"}) + inInviteePhone = Just (Phone {fromPhone = "+68945103783764"}), + inInviteeUrl = Nothing } testObject_Invitation_team_13 :: Invitation testObject_Invitation_team_13 = Invitation - { inTeam = (Id (fromJust (UUID.fromString "00000002-0000-0001-0000-000000000001"))), + { inTeam = Id (fromJust (UUID.fromString "00000002-0000-0001-0000-000000000001")), inRole = RoleMember, - inInvitation = (Id (fromJust (UUID.fromString "00000002-0000-0000-0000-000200000002"))), - inCreatedAt = (fromJust (readUTCTimeMillis "1864-05-08T01:18:31.982Z")), + inInvitation = Id (fromJust (UUID.fromString "00000002-0000-0000-0000-000200000002")), + inCreatedAt = fromJust (readUTCTimeMillis "1864-05-08T01:18:31.982Z"), inCreatedBy = Just (Id (fromJust (UUID.fromString "00000001-0000-0002-0000-000100000002"))), inInviteeEmail = Email {emailLocal = "", emailDomain = "\DELr"}, inInviteeName = Just (Name {fromName = "U"}), - inInviteePhone = Just (Phone {fromPhone = "+549940856897515"}) + inInviteePhone = Just (Phone {fromPhone = "+549940856897515"}), + inInviteeUrl = Nothing } testObject_Invitation_team_14 :: Invitation testObject_Invitation_team_14 = Invitation - { inTeam = (Id (fromJust (UUID.fromString "00000002-0000-0002-0000-000100000000"))), + { inTeam = Id (fromJust (UUID.fromString "00000002-0000-0002-0000-000100000000")), inRole = RoleOwner, - inInvitation = (Id (fromJust (UUID.fromString "00000001-0000-0000-0000-000200000002"))), - inCreatedAt = (fromJust (readUTCTimeMillis "1864-05-12T23:54:25.090Z")), + inInvitation = Id (fromJust (UUID.fromString "00000001-0000-0000-0000-000200000002")), + inCreatedAt = fromJust (readUTCTimeMillis "1864-05-12T23:54:25.090Z"), inCreatedBy = Just (Id (fromJust (UUID.fromString "00000002-0000-0002-0000-000200000000"))), inInviteeEmail = Email {emailLocal = "EI", emailDomain = "{"}, inInviteeName = Nothing, - inInviteePhone = Just (Phone {fromPhone = "+89058877371"}) + inInviteePhone = Just (Phone {fromPhone = "+89058877371"}), + inInviteeUrl = Nothing } testObject_Invitation_team_15 :: Invitation testObject_Invitation_team_15 = Invitation - { inTeam = (Id (fromJust (UUID.fromString "00000000-0000-0002-0000-000100000001"))), + { inTeam = Id (fromJust (UUID.fromString "00000000-0000-0002-0000-000100000001")), inRole = RoleOwner, - inInvitation = (Id (fromJust (UUID.fromString "00000001-0000-0001-0000-000200000001"))), - inCreatedAt = (fromJust (readUTCTimeMillis "1864-05-08T22:22:28.568Z")), + inInvitation = Id (fromJust (UUID.fromString "00000001-0000-0001-0000-000200000001")), + inCreatedAt = fromJust (readUTCTimeMillis "1864-05-08T22:22:28.568Z"), inCreatedBy = Nothing, inInviteeEmail = Email {emailLocal = ".", emailDomain = "\DEL"}, inInviteeName = @@ -256,29 +268,31 @@ testObject_Invitation_team_15 = "\71448\US&KIL\DC3\1086159![\n6\1111661HEj4E\12136UL\US>2\1070931_\nJ\53410Pv\SO\SIR\30897\&8\bmS\45510mE\ag\SYN\ENQ%\14545\f!\v\US\119306\ENQ\184817\1044744\SO83!j\73854\GS\1071331,\RS\CANF\1062795\1110535U\EMJb\DC1j\EMY\92304O\1007855" } ), - inInviteePhone = Just (Phone {fromPhone = "+57741900390998"}) + inInviteePhone = Just (Phone {fromPhone = "+57741900390998"}), + inInviteeUrl = Nothing } testObject_Invitation_team_16 :: Invitation testObject_Invitation_team_16 = Invitation - { inTeam = (Id (fromJust (UUID.fromString "00000001-0000-0001-0000-000100000002"))), + { inTeam = Id (fromJust (UUID.fromString "00000001-0000-0001-0000-000100000002")), inRole = RoleExternalPartner, - inInvitation = (Id (fromJust (UUID.fromString "00000001-0000-0002-0000-000200000001"))), - inCreatedAt = (fromJust (readUTCTimeMillis "1864-05-09T09:56:33.113Z")), + inInvitation = Id (fromJust (UUID.fromString "00000001-0000-0002-0000-000200000001")), + inCreatedAt = fromJust (readUTCTimeMillis "1864-05-09T09:56:33.113Z"), inCreatedBy = Just (Id (fromJust (UUID.fromString "00000001-0000-0000-0000-000100000001"))), inInviteeEmail = Email {emailLocal = "\\", emailDomain = "\"\DEL{"}, inInviteeName = Just (Name {fromName = "\GS\DC4Q;6/_f*7\1093966\SI+\1092810\41698\&9"}), - inInviteePhone = Nothing + inInviteePhone = Nothing, + inInviteeUrl = Nothing } testObject_Invitation_team_17 :: Invitation testObject_Invitation_team_17 = Invitation - { inTeam = (Id (fromJust (UUID.fromString "00000002-0000-0001-0000-000000000002"))), + { inTeam = Id (fromJust (UUID.fromString "00000002-0000-0001-0000-000000000002")), inRole = RoleAdmin, - inInvitation = (Id (fromJust (UUID.fromString "00000001-0000-0001-0000-000100000001"))), - inCreatedAt = (fromJust (readUTCTimeMillis "1864-05-08T06:30:23.239Z")), + inInvitation = Id (fromJust (UUID.fromString "00000001-0000-0001-0000-000100000001")), + inCreatedAt = fromJust (readUTCTimeMillis "1864-05-08T06:30:23.239Z"), inCreatedBy = Just (Id (fromJust (UUID.fromString "00000000-0000-0001-0000-000000000001"))), inInviteeEmail = Email {emailLocal = "", emailDomain = "\SOH[\97119"}, inInviteeName = @@ -288,16 +302,17 @@ testObject_Invitation_team_17 = "Z\ESC9E\DEL\NAK\37708\83413}(3m\97177\97764'\1072786.WY;\RS8?v-\1100720\DC2\1015859" } ), - inInviteePhone = Nothing + inInviteePhone = Nothing, + inInviteeUrl = Nothing } testObject_Invitation_team_19 :: Invitation testObject_Invitation_team_19 = Invitation - { inTeam = (Id (fromJust (UUID.fromString "00000000-0000-0000-0000-000200000000"))), + { inTeam = Id (fromJust (UUID.fromString "00000000-0000-0000-0000-000200000000")), inRole = RoleMember, - inInvitation = (Id (fromJust (UUID.fromString "00000001-0000-0002-0000-000000000001"))), - inCreatedAt = (fromJust (readUTCTimeMillis "1864-05-07T15:08:06.796Z")), + inInvitation = Id (fromJust (UUID.fromString "00000001-0000-0002-0000-000000000001")), + inCreatedAt = fromJust (readUTCTimeMillis "1864-05-07T15:08:06.796Z"), inCreatedBy = Nothing, inInviteeEmail = Email {emailLocal = "\1019726\96050\DEL", emailDomain = "(S\ETB"}, inInviteeName = @@ -326,18 +342,20 @@ testObject_Invitation_team_19 = "\38776r\111317\ETXQi\1000087\1097943\EM\170747\74323+\1067948Q?H=G-\RS;\1103719\SOq^K;a\1052250W\EM X\83384\1073320>M\980\26387jjbU-&\1040136v\NULy\181884\a|\SYNUfJCHjP\SO\1111555\27981DNA:~s" } ), - inInviteePhone = Just (Phone {fromPhone = "+05787228893"}) + inInviteePhone = Just (Phone {fromPhone = "+05787228893"}), + inInviteeUrl = Nothing } testObject_Invitation_team_20 :: Invitation testObject_Invitation_team_20 = Invitation - { inTeam = (Id (fromJust (UUID.fromString "00000001-0000-0000-0000-000000000000"))), + { inTeam = Id (fromJust (UUID.fromString "00000001-0000-0000-0000-000000000000")), inRole = RoleExternalPartner, - inInvitation = (Id (fromJust (UUID.fromString "00000002-0000-0001-0000-000000000001"))), - inCreatedAt = (fromJust (readUTCTimeMillis "1864-05-12T08:07:17.747Z")), + inInvitation = Id (fromJust (UUID.fromString "00000002-0000-0001-0000-000000000001")), + inCreatedAt = fromJust (readUTCTimeMillis "1864-05-12T08:07:17.747Z"), inCreatedBy = Just (Id (fromJust (UUID.fromString "00000000-0000-0001-0000-000100000001"))), inInviteeEmail = Email {emailLocal = "b", emailDomain = "u9T"}, inInviteeName = Nothing, - inInviteePhone = Just (Phone {fromPhone = "+27259486019"}) + inInviteePhone = Just (Phone {fromPhone = "+27259486019"}), + inInviteeUrl = Nothing } diff --git a/libs/wire-api/test/golden/testObject_InvitationList_team_10.json b/libs/wire-api/test/golden/testObject_InvitationList_team_10.json index f5a607418e..c06f56c3cf 100644 --- a/libs/wire-api/test/golden/testObject_InvitationList_team_10.json +++ b/libs/wire-api/test/golden/testObject_InvitationList_team_10.json @@ -9,7 +9,8 @@ "name": "P𥖧\u0006'e\u0010\u001d\"\u0011K󽗨Fcvm[\"Sc}U𑊒􂌨󿔟~!E􀖇\u000bV", "phone": null, "role": "member", - "team": "00000000-0000-0001-0000-000100000000" + "team": "00000000-0000-0001-0000-000100000000", + "url": null } ] } diff --git a/libs/wire-api/test/golden/testObject_InvitationList_team_11.json b/libs/wire-api/test/golden/testObject_InvitationList_team_11.json index 621efe7906..3b5e6bce7f 100644 --- a/libs/wire-api/test/golden/testObject_InvitationList_team_11.json +++ b/libs/wire-api/test/golden/testObject_InvitationList_team_11.json @@ -9,7 +9,8 @@ "name": "G\\,\u0000=ෝI-w󠀹}𠉭抳-92\u0013@\u0006\u001f\\F\u001a\"-r꒫6\u000fඬ\u001f*}c󼘹\u001f\u0007T8m@旅M\u0012#MIq\r4nW􍦐y\u0005Ud룫#𫶒5\n\u0002V]𨡀\"󶂃𩫘0:ﲼ𮭩+\u0001\u000bP󹎷X镟􅔧.\u0019N\"𬋻", "phone": "+872574694", "role": "admin", - "team": "00000000-0000-0001-0000-000100000000" + "team": "00000000-0000-0001-0000-000100000000", + "url" :null }, { "created_at": "1864-05-09T23:06:13.648Z", @@ -19,7 +20,8 @@ "name": "叕5q}B\u0001𦌜`イw\\X@󼶝𢼈7Mw,*z{𠚷&~", "phone": "+143031479742", "role": "partner", - "team": "00000000-0000-0001-0000-000000000001" + "team": "00000000-0000-0001-0000-000000000001", + "url" :null }, { "created_at": "1864-05-09T10:37:03.809Z", @@ -29,7 +31,8 @@ "name": "V􈫮\u0010qYヒCU\u000e􄕀fQJ\u0005ਓq+\u0007\u0016󱊸\u0011@𤠼`坟qh+𬾬A7𦄡Y \u0011Tㅎ1_􈩇#B<􂡁;a6o=", "phone": "+236346166386230", "role": "partner", - "team": "00000001-0000-0000-0000-000000000000" + "team": "00000001-0000-0000-0000-000000000000", + "url" :null }, { "created_at": "1864-05-09T04:46:03.504Z", @@ -39,7 +42,8 @@ "name": ",􃠾{ս\u000c𬕻Uh죙\t\u001b\u0004\u0001O@\u001a_\u0002D􎰥𦀛\u0016g}", "phone": "+80162248", "role": "admin", - "team": "00000001-0000-0001-0000-000100000001" + "team": "00000001-0000-0001-0000-000100000001", + "url" :null }, { "created_at": "1864-05-09T12:53:52.047Z", @@ -49,7 +53,8 @@ "name": null, "phone": null, "role": "owner", - "team": "00000000-0000-0001-0000-000100000001" + "team": "00000000-0000-0001-0000-000100000001", + "url" :null } ] } diff --git a/libs/wire-api/test/golden/testObject_InvitationList_team_16.json b/libs/wire-api/test/golden/testObject_InvitationList_team_16.json index fc14ac96bf..535fe0678e 100644 --- a/libs/wire-api/test/golden/testObject_InvitationList_team_16.json +++ b/libs/wire-api/test/golden/testObject_InvitationList_team_16.json @@ -9,7 +9,8 @@ "name": "E𝘆YM<󾪤j􆢆\r􇳗O󴟴MCU\u001eI󳊃m𔒷hG\u0012|:P􅛽Vj\u001c\u0000ffgG)K{􁇏7x5󱟰𪔘\n\u000clT􆊞", "phone": "+36515555", "role": "owner", - "team": "00000001-0000-0001-0000-000100000001" + "team": "00000001-0000-0001-0000-000100000001", + "url": null } ] } diff --git a/libs/wire-api/test/golden/testObject_InvitationList_team_17.json b/libs/wire-api/test/golden/testObject_InvitationList_team_17.json index c2c9ba044a..eba7991502 100644 --- a/libs/wire-api/test/golden/testObject_InvitationList_team_17.json +++ b/libs/wire-api/test/golden/testObject_InvitationList_team_17.json @@ -9,7 +9,8 @@ "name": null, "phone": null, "role": "partner", - "team": "00000001-0000-0000-0000-000100000000" + "team": "00000001-0000-0000-0000-000100000000", + "url": null } ] } diff --git a/libs/wire-api/test/golden/testObject_InvitationList_team_2.json b/libs/wire-api/test/golden/testObject_InvitationList_team_2.json index e2f2601fb1..076b78a0d4 100644 --- a/libs/wire-api/test/golden/testObject_InvitationList_team_2.json +++ b/libs/wire-api/test/golden/testObject_InvitationList_team_2.json @@ -9,7 +9,8 @@ "name": "fuC9p􌌅A𧻢\u000c\u0005\u000e刣N룞_?oCX.U\r𧾠W腈󽥝\u0013\t[錣\u0016/⃘A𣚁𪔍\u0014H𠽙\u0002𨯠\u0004𨒤o\u0013", "phone": "+851333011", "role": "owner", - "team": "00000000-0000-0000-0000-000000000001" + "team": "00000000-0000-0000-0000-000000000001", + "url": "https://example.com/inv14" } ] } diff --git a/libs/wire-api/test/golden/testObject_InvitationList_team_20.json b/libs/wire-api/test/golden/testObject_InvitationList_team_20.json index 1b50ca8071..26a5ab0134 100644 --- a/libs/wire-api/test/golden/testObject_InvitationList_team_20.json +++ b/libs/wire-api/test/golden/testObject_InvitationList_team_20.json @@ -9,7 +9,8 @@ "name": null, "phone": "+745177056001783", "role": "partner", - "team": "00000001-0000-0001-0000-000000000000" + "team": "00000001-0000-0001-0000-000000000000", + "url": null }, { "created_at": "1864-05-09T18:56:29.712Z", @@ -19,7 +20,8 @@ "name": "YPf╞:\u0005Ỉ&\u0018\u0011󽧛%ꦡk𪯋􅥏:Q\u0005F+\u0008b8Jh􌎓K\u0007\u001dY\u0004􃏡\u000f󽝰\u0016 􁗠6>I󾉩B$z?𤢾wECB\u001e𥼬덄\"W𗤞󲴂@\u001eg)\u0001m!-U􇧦󵜰o\u0006a\u0004𭂢;R􂪧kgT􍆈f\u0004\u001e\rp𓎎󿉊X/􄂲)\u00025.Ym󵳬n싟N\u0013𫅄]?'𠴺a4\"󳟾!i5\u001e\u001dC14", "phone": null, "role": "owner", - "team": "00000001-0000-0000-0000-000100000000" + "team": "00000001-0000-0000-0000-000100000000", + "url": null } ] } diff --git a/libs/wire-api/test/golden/testObject_InvitationList_team_4.json b/libs/wire-api/test/golden/testObject_InvitationList_team_4.json index e41e76da52..3063b4fdeb 100644 --- a/libs/wire-api/test/golden/testObject_InvitationList_team_4.json +++ b/libs/wire-api/test/golden/testObject_InvitationList_team_4.json @@ -9,7 +9,8 @@ "name": "R6𠥄𠮥VQ𭴢\u001a\u0001𬄺0C􉶍\u001bR𭗈𞡊@韉Z?\u0002𩖫􄭦e}\u0001\u0017\u0004m𭉂\u001f]󰺞𮉗􂨮󰶌\u0008\u0011zfw-5𝝖\u0018􃸂 \u0019e\u0014|㡚Vo{􆳗\u0013#\u001fS꿻&zz𧏏9𢱋,\u000f\u000c\u0001p󺜰\u0010𧵪􂸑.&󳢨kZ쓿u\u0008왌􎴟n:􍝋D$.Q", "phone": "+60506387292", "role": "admin", - "team": "00000000-0000-0001-0000-000000000000" + "team": "00000000-0000-0001-0000-000000000000", + "url": null }, { "created_at": "1864-05-09T09:00:02.901Z", @@ -19,7 +20,8 @@ "name": "\u0012}q\u0018=SA\u0003x\t\u0003\\\u000b[\u0008)(\u001b]𡋃Y\u000b@pꈫl뀉𦛌\u0000\t􌤢\u00011\u0011\u0005󹝃\"i猔\u0019\u0008\u0006\u000f\u0012v\u0006", "phone": "+913945015", "role": "admin", - "team": "00000000-0000-0001-0000-000100000000" + "team": "00000000-0000-0001-0000-000100000000", + "url": null }, { "created_at": "1864-05-09T11:10:31.203Z", @@ -29,7 +31,8 @@ "name": "&􂧽Ec\u0000㼓}k󼾘l𪍯\u001fJ\u00190^.+F\u0000\u000c$'`!\u0017[p󾓉}>E0y𗢸#4I\u0007𐐡jc\u001bgt埉􊹘P\u0014!􋣥E93'Y$YL뜦b\r:,𬘞\u000e𥚟y\u0003;􃺹􌛖z4z-D􋰳a𡽜6𨏝r󼖨󱌂J\u0010밆", "phone": "+17046334", "role": "member", - "team": "00000001-0000-0000-0000-000000000001" + "team": "00000001-0000-0000-0000-000000000001", + "url": null }, { "created_at": "1864-05-09T23:41:34.529Z", @@ -39,7 +42,8 @@ "name": "Ft*O1\u0008&\u000e\u0018<𑨛􊰋m\n\u0014\u0012; \u0003󱚥\u0011􂬫\"k.T󹴑[[\u001c\u0004{j`\u001d󳟞c􄖫{\u001a\u001dQY𬨕\t\u0015y\t𠓳j󼿁W ", "phone": null, "role": "owner", - "team": "00000000-0000-0000-0000-000000000000" + "team": "00000000-0000-0000-0000-000000000000", + "url": null }, { "created_at": "1864-05-09T00:29:17.658Z", @@ -49,7 +53,8 @@ "name": null, "phone": "+918848647685283", "role": "admin", - "team": "00000001-0000-0000-0000-000100000000" + "team": "00000001-0000-0000-0000-000100000000", + "url": null }, { "created_at": "1864-05-09T13:34:37.117Z", @@ -59,7 +64,8 @@ "name": "Lo\r􎒩B𗚰_v󰔢􆍶󻀬􊽦9\u0002vyQ🖰&W󻟑𠸘􇹬'􁔫:𤟗𡶘􏹠}-o󿜊le8Zp󺩐􋾙)nK\u00140⛟0DE\u0015K$io\u001e|Ip2ClnU𬖍", "phone": "+2239859474784", "role": "owner", - "team": "00000001-0000-0001-0000-000100000000" + "team": "00000001-0000-0001-0000-000100000000", + "url": null } ] } diff --git a/libs/wire-api/test/golden/testObject_InvitationList_team_6.json b/libs/wire-api/test/golden/testObject_InvitationList_team_6.json index 2285c8dc7a..03aa3d0485 100644 --- a/libs/wire-api/test/golden/testObject_InvitationList_team_6.json +++ b/libs/wire-api/test/golden/testObject_InvitationList_team_6.json @@ -9,7 +9,8 @@ "name": null, "phone": null, "role": "admin", - "team": "00000001-0000-0001-0000-000100000000" + "team": "00000001-0000-0001-0000-000100000000", + "url": null }, { "created_at": "1864-05-09T11:26:36.672Z", @@ -19,7 +20,8 @@ "name": null, "phone": "+85999765", "role": "admin", - "team": "00000000-0000-0000-0000-000100000000" + "team": "00000000-0000-0000-0000-000100000000", + "url": null }, { "created_at": "1864-05-09T00:31:56.241Z", @@ -29,7 +31,8 @@ "name": null, "phone": "+150835819626453", "role": "owner", - "team": "00000001-0000-0000-0000-000100000000" + "team": "00000001-0000-0000-0000-000100000000", + "url": null }, { "created_at": "1864-05-09T21:10:47.237Z", @@ -39,7 +42,8 @@ "name": "YBc\r웶8{\\\n􋸓+\u0008\u0016'<\u0004􈄿Z\u0007nOb􋨴􌸖𩮤}2o@v/", "phone": "+787465997389", "role": "member", - "team": "00000000-0000-0001-0000-000100000000" + "team": "00000000-0000-0001-0000-000100000000", + "url": null } ] } diff --git a/libs/wire-api/test/golden/testObject_Invitation_team_1.json b/libs/wire-api/test/golden/testObject_Invitation_team_1.json index 9d611aa22e..ee4489e209 100644 --- a/libs/wire-api/test/golden/testObject_Invitation_team_1.json +++ b/libs/wire-api/test/golden/testObject_Invitation_team_1.json @@ -6,5 +6,6 @@ "name": null, "phone": "+54687000371", "role": "admin", - "team": "00000002-0000-0001-0000-000200000002" + "team": "00000002-0000-0001-0000-000200000002", + "url": null } diff --git a/libs/wire-api/test/golden/testObject_Invitation_team_10.json b/libs/wire-api/test/golden/testObject_Invitation_team_10.json index 447daf009c..9c189f7c13 100644 --- a/libs/wire-api/test/golden/testObject_Invitation_team_10.json +++ b/libs/wire-api/test/golden/testObject_Invitation_team_10.json @@ -6,5 +6,6 @@ "name": null, "phone": "+957591063736", "role": "partner", - "team": "00000002-0000-0001-0000-000100000001" + "team": "00000002-0000-0001-0000-000100000001", + "url": null } diff --git a/libs/wire-api/test/golden/testObject_Invitation_team_11.json b/libs/wire-api/test/golden/testObject_Invitation_team_11.json index 09e6c67ff7..a1d4b2e572 100644 --- a/libs/wire-api/test/golden/testObject_Invitation_team_11.json +++ b/libs/wire-api/test/golden/testObject_Invitation_team_11.json @@ -6,5 +6,6 @@ "name": "􄘬,􍁨緌sC\nD\u001e󱫂*\u0011𧲍\u0011󲾁a󽌳𗿸{.熿𭒪빝𡨶9/ಇ<;$𭣘𠪹Z\u0005'󺠞!F􎉼󼪟n\"\n8\u001dH󼯢9𐪜z:d\u0010F𧕰y_w\ri轭!>󳓗䏩𝓖\u0008\u001a\u001c\u000fF%<𞢹\u000fh\u001b\u0003\u000f󲶳\u001fO\u0000g_𤻨뢪󺥟\u0004􂔤􊃫z~%IA'R\u0008󶽴Hv^󾲱wrjb\t𨭛\u0003", "phone": "+518729615781", "role": "admin", - "team": "00000001-0000-0001-0000-000100000000" + "team": "00000001-0000-0001-0000-000100000000", + "url": null } diff --git a/libs/wire-api/test/golden/testObject_Invitation_team_12.json b/libs/wire-api/test/golden/testObject_Invitation_team_12.json index 866b59a789..ece82b4d17 100644 --- a/libs/wire-api/test/golden/testObject_Invitation_team_12.json +++ b/libs/wire-api/test/golden/testObject_Invitation_team_12.json @@ -6,5 +6,6 @@ "name": "\u0010Z+wd^𐘊􆃨1\u0002YdXt>􇺼LSB7F9\\𠿬\u0005\n󱂟\"🀡|\u0007𦠺'\u001bTygU􎍔R칖􅧠O4󼷁E9\"󸃐\u0012Re\u0005D}􀧨𧢧􍭝\u0008V𫋾%98'\u001e9\u00064yP𔗍㡀ř\u0007w\t􌄦\u000b􇋳xv/Yl󵢬𦯯", "phone": "+68945103783764", "role": "admin", - "team": "00000000-0000-0000-0000-000000000002" + "team": "00000000-0000-0000-0000-000000000002", + "url": null } diff --git a/libs/wire-api/test/golden/testObject_Invitation_team_13.json b/libs/wire-api/test/golden/testObject_Invitation_team_13.json index 789b4a3297..f12163f667 100644 --- a/libs/wire-api/test/golden/testObject_Invitation_team_13.json +++ b/libs/wire-api/test/golden/testObject_Invitation_team_13.json @@ -6,5 +6,6 @@ "name": "U", "phone": "+549940856897515", "role": "member", - "team": "00000002-0000-0001-0000-000000000001" + "team": "00000002-0000-0001-0000-000000000001", + "url": null } diff --git a/libs/wire-api/test/golden/testObject_Invitation_team_14.json b/libs/wire-api/test/golden/testObject_Invitation_team_14.json index 1f1ac31cc4..7b5764a687 100644 --- a/libs/wire-api/test/golden/testObject_Invitation_team_14.json +++ b/libs/wire-api/test/golden/testObject_Invitation_team_14.json @@ -6,5 +6,6 @@ "name": null, "phone": "+89058877371", "role": "owner", - "team": "00000002-0000-0002-0000-000100000000" + "team": "00000002-0000-0002-0000-000100000000", + "url": null } diff --git a/libs/wire-api/test/golden/testObject_Invitation_team_15.json b/libs/wire-api/test/golden/testObject_Invitation_team_15.json index 8ec11e965f..7d5215c782 100644 --- a/libs/wire-api/test/golden/testObject_Invitation_team_15.json +++ b/libs/wire-api/test/golden/testObject_Invitation_team_15.json @@ -6,5 +6,6 @@ "name": "𑜘\u001f&KIL\u0013􉋏![\n6􏙭HEj4E⽨UL\u001f>2􅝓_\nJ킢Pv\u000e\u000fR碱8\u0008mS뇆mE\u0007g\u0016\u0005%㣑\u000c!\u000b\u001f𝈊\u0005𭇱󿄈\u000e83!j𒁾\u001d􅣣,\u001e\u0018F􃞋􏈇U\u0019Jb\u0011j\u0019Y𖢐O󶃯", "phone": "+57741900390998", "role": "owner", - "team": "00000000-0000-0002-0000-000100000001" + "team": "00000000-0000-0002-0000-000100000001", + "url": null } diff --git a/libs/wire-api/test/golden/testObject_Invitation_team_16.json b/libs/wire-api/test/golden/testObject_Invitation_team_16.json index 1ade470dd6..853aab3be7 100644 --- a/libs/wire-api/test/golden/testObject_Invitation_team_16.json +++ b/libs/wire-api/test/golden/testObject_Invitation_team_16.json @@ -6,5 +6,6 @@ "name": "\u001d\u0014Q;6/_f*7􋅎\u000f+􊳊ꋢ9", "phone": null, "role": "partner", - "team": "00000001-0000-0001-0000-000100000002" + "team": "00000001-0000-0001-0000-000100000002", + "url": null } diff --git a/libs/wire-api/test/golden/testObject_Invitation_team_17.json b/libs/wire-api/test/golden/testObject_Invitation_team_17.json index ffaa39fdfc..d7ae310a54 100644 --- a/libs/wire-api/test/golden/testObject_Invitation_team_17.json +++ b/libs/wire-api/test/golden/testObject_Invitation_team_17.json @@ -6,5 +6,6 @@ "name": "Z\u001b9E\u0015鍌𔗕}(3m𗮙𗷤'􅺒.WY;\u001e8?v-􌮰\u0012󸀳", "phone": null, "role": "admin", - "team": "00000000-0000-0001-0000-000100000000" + "team": "00000000-0000-0001-0000-000100000000", + "url": null } diff --git a/libs/wire-api/test/golden/testObject_Invitation_team_19.json b/libs/wire-api/test/golden/testObject_Invitation_team_19.json index 4f087c6be4..aaa9b35ce0 100644 --- a/libs/wire-api/test/golden/testObject_Invitation_team_19.json +++ b/libs/wire-api/test/golden/testObject_Invitation_team_19.json @@ -6,5 +6,6 @@ "name": "靸r𛋕\u0003Qi󴊗􌃗\u0019𩫻𒉓+􄮬Q?H=G-\u001e;􍝧\u000eq^K;a􀹚W\u0019 X𔖸􆂨>Mϔ朓jjbU-&󽼈v\u0000y𬙼\u0007|\u0016UfJCHjP\u000e􏘃浍DNA:~s", "phone": "+05787228893", "role": "member", - "team": "00000000-0000-0000-0000-000200000000" + "team": "00000000-0000-0000-0000-000200000000", + "url": null } diff --git a/libs/wire-api/test/golden/testObject_Invitation_team_2.json b/libs/wire-api/test/golden/testObject_Invitation_team_2.json index c5227405c9..393eaccd4f 100644 --- a/libs/wire-api/test/golden/testObject_Invitation_team_2.json +++ b/libs/wire-api/test/golden/testObject_Invitation_team_2.json @@ -6,5 +6,6 @@ "name": "􄭇} 2pGEW+\rT𩹙p𪨳𦘢&𣫡v0\u0008", "phone": null, "role": "partner", - "team": "00000000-0000-0001-0000-000000000000" + "team": "00000000-0000-0001-0000-000000000000", + "url": null } diff --git a/libs/wire-api/test/golden/testObject_Invitation_team_20.json b/libs/wire-api/test/golden/testObject_Invitation_team_20.json index 8a036b8aff..653fafc89e 100644 --- a/libs/wire-api/test/golden/testObject_Invitation_team_20.json +++ b/libs/wire-api/test/golden/testObject_Invitation_team_20.json @@ -6,5 +6,6 @@ "name": null, "phone": "+27259486019", "role": "partner", - "team": "00000001-0000-0000-0000-000000000000" + "team": "00000001-0000-0000-0000-000000000000", + "url": null } diff --git a/libs/wire-api/test/golden/testObject_Invitation_team_3.json b/libs/wire-api/test/golden/testObject_Invitation_team_3.json index 8111542049..6222659d12 100644 --- a/libs/wire-api/test/golden/testObject_Invitation_team_3.json +++ b/libs/wire-api/test/golden/testObject_Invitation_team_3.json @@ -6,5 +6,6 @@ "name": null, "phone": null, "role": "partner", - "team": "00000002-0000-0001-0000-000100000001" + "team": "00000002-0000-0001-0000-000100000001", + "url": null } diff --git a/libs/wire-api/test/golden/testObject_Invitation_team_4.json b/libs/wire-api/test/golden/testObject_Invitation_team_4.json index 76282c4654..8e8dedc4a4 100644 --- a/libs/wire-api/test/golden/testObject_Invitation_team_4.json +++ b/libs/wire-api/test/golden/testObject_Invitation_team_4.json @@ -6,5 +6,6 @@ "name": null, "phone": null, "role": "admin", - "team": "00000000-0000-0000-0000-000100000000" + "team": "00000000-0000-0000-0000-000100000000", + "url": null } diff --git a/libs/wire-api/test/golden/testObject_Invitation_team_5.json b/libs/wire-api/test/golden/testObject_Invitation_team_5.json index 44a5cd464d..ce4196efbb 100644 --- a/libs/wire-api/test/golden/testObject_Invitation_team_5.json +++ b/libs/wire-api/test/golden/testObject_Invitation_team_5.json @@ -6,5 +6,6 @@ "name": "}G_𤃊`X󻋗𠆝󷲞L\"󿶗e6:E쨕󲟇f-$𠬒Z!s2p?#\tF 8𭿰𨕿󹵇\u0004􉢘*󸚄\u0016\u0010%Y𩀄>􏘍󾨶󺶘g\"􁥰\u001a\u001a𬇟ꦛ\u0004v𭽢,𩶐(\u001dQT𤪐;􃨚\u0005\u0017B􎇮H𩣓\\󾃾,Y", "phone": "+45207005641274", "role": "owner", - "team": "00000002-0000-0000-0000-000000000001" + "team": "00000002-0000-0000-0000-000000000001", + "url": null } diff --git a/libs/wire-api/test/golden/testObject_Invitation_team_6.json b/libs/wire-api/test/golden/testObject_Invitation_team_6.json index c847dcf4a2..37e3f45bdc 100644 --- a/libs/wire-api/test/golden/testObject_Invitation_team_6.json +++ b/libs/wire-api/test/golden/testObject_Invitation_team_6.json @@ -6,5 +6,6 @@ "name": "O~\u0014U\u001e?V3_𮬰Slh􅱬Q1󶻳j|~M7􊲚􋽼𗆨\u0011K􇍼Afs𫬇lGV􏱇]`o\u0019f蓤InvfDDy\\DI𧾱􊥩\u0017B𦷬F*X\u0001\u001a얔\u0003\u0010<\u0003\u0016c\u0010,p\u000b*󵢘Vn\u000cI𑈹xS\u0002V\u001b$\u0019u󴮖xl>\u0007Z\u00144e\u0014aZ", "phone": "+75547625285", "role": "admin", - "team": "00000001-0000-0000-0000-000000000001" + "team": "00000001-0000-0000-0000-000000000001", + "url": null } diff --git a/libs/wire-api/test/golden/testObject_Invitation_team_7.json b/libs/wire-api/test/golden/testObject_Invitation_team_7.json index d4699fe74d..844522e716 100644 --- a/libs/wire-api/test/golden/testObject_Invitation_team_7.json +++ b/libs/wire-api/test/golden/testObject_Invitation_team_7.json @@ -6,5 +6,6 @@ "name": "\u0018.𛅷􈼞\u0010\u000c\u0010\u0018𤰤o;Yay:yY $\u0003<ͯ%@\u001fre>5L'R\u0013𫝳oy#]c4!𘖝U홊暧󾜸􃕢p_>f\u000e𪲈􇇪󳆗_Vm\u001f}\u0002Pz\r\u0005K\u000e+>󲆠\u0000𥝻?pu?r\u001b\u001a!?𩇕;ᦅS䥅\u0007􅠬\u0008󹹝=0.5 , currency-codes , directory + , either , filepath , hscim , imports diff --git a/services/brig/brig.cabal b/services/brig/brig.cabal index 392f90d30e..95f744d763 100644 --- a/services/brig/brig.cabal +++ b/services/brig/brig.cabal @@ -96,6 +96,7 @@ library Brig.Team.DB Brig.Team.Email Brig.Team.Template + Brig.Team.Types Brig.Team.Util Brig.Template Brig.Unique diff --git a/services/brig/src/Brig/API/Internal.hs b/services/brig/src/Brig/API/Internal.hs index 4a548f766d..d4d567f4ba 100644 --- a/services/brig/src/Brig/API/Internal.hs +++ b/services/brig/src/Brig/API/Internal.hs @@ -50,6 +50,7 @@ import Brig.Options hiding (internalEvents, sesQueue) import qualified Brig.Provider.API as Provider import qualified Brig.Team.API as Team import Brig.Team.DB (lookupInvitationByEmail) +import Brig.Team.Types (ShowOrHideInvitationUrl (..)) import Brig.Types.Connection import Brig.Types.Intra import Brig.Types.Team.LegalHold (LegalHoldClientRequest (..)) @@ -556,7 +557,7 @@ listActivatedAccounts elh includePendingInvitations = do case (accountStatus account, includePendingInvitations, emailIdentity ident) of (PendingInvitation, False, _) -> pure False (PendingInvitation, True, Just email) -> do - hasInvitation <- isJust <$> wrapClient (lookupInvitationByEmail email) + hasInvitation <- isJust <$> wrapClient (lookupInvitationByEmail HideInvitationUrl email) unless hasInvitation $ do -- user invited via scim should expire together with its invitation API.deleteUserNoVerify (userId . accountUser $ account) diff --git a/services/brig/src/Brig/API/User.hs b/services/brig/src/Brig/API/User.hs index ce19c9bdde..ea3932461f 100644 --- a/services/brig/src/Brig/API/User.hs +++ b/services/brig/src/Brig/API/User.hs @@ -125,6 +125,7 @@ import Brig.Options hiding (Timeout, internalEvents) import Brig.Password import qualified Brig.Queue as Queue import qualified Brig.Team.DB as Team +import Brig.Team.Types (ShowOrHideInvitationUrl (..)) import Brig.Types.Activation (ActivationPair) import Brig.Types.Connection import Brig.Types.Intra @@ -413,7 +414,7 @@ createUser new = do findTeamInvitation (Just e) c = lift (wrapClient $ Team.lookupInvitationInfo c) >>= \case Just ii -> do - inv <- lift . wrapClient $ Team.lookupInvitation (Team.iiTeam ii) (Team.iiInvId ii) + inv <- lift . wrapClient $ Team.lookupInvitation HideInvitationUrl (Team.iiTeam ii) (Team.iiInvId ii) case (inv, Team.inInviteeEmail <$> inv) of (Just invite, Just em) | e == userEmailKey em -> do diff --git a/services/brig/src/Brig/IO/Intra.hs b/services/brig/src/Brig/IO/Intra.hs index 51695d0a0f..c5c4b65042 100644 --- a/services/brig/src/Brig/IO/Intra.hs +++ b/services/brig/src/Brig/IO/Intra.hs @@ -59,6 +59,7 @@ module Brig.IO.Intra getTeamSearchVisibility, getAllFeatureConfigsForUser, getVerificationCodeEnabled, + getTeamExposeInvitationURLsToTeamAdmin, -- * Legalhold guardLegalhold, @@ -80,6 +81,7 @@ import qualified Brig.Data.Connection as Data import Brig.Federation.Client (notifyUserDeleted) import qualified Brig.IO.Journal as Journal import Brig.RPC +import Brig.Team.Types (ShowOrHideInvitationUrl (..)) import Brig.Types.User.Event import Brig.User.Search.Index (MonadIndexIO) import qualified Brig.User.Search.Index as Search @@ -1367,6 +1369,28 @@ getTeamSearchVisibility tid = paths ["i", "teams", toByteString' tid, "search-visibility"] . expect2xx +getTeamExposeInvitationURLsToTeamAdmin :: + ( MonadLogger m, + MonadReader Env m, + MonadIO m, + MonadMask m, + MonadHttp m, + HasRequestId m + ) => + TeamId -> + m ShowOrHideInvitationUrl +getTeamExposeInvitationURLsToTeamAdmin tid = do + debug $ remote "galley" . msg (val "Get expose invitation URLs to team admin settings") + response <- galleyRequest GET req + status <- wsStatus <$> decodeBody @(WithStatus ExposeInvitationURLsToTeamAdminConfig) "galley" response + case status of + FeatureStatusEnabled -> pure ShowInvitationUrl + FeatureStatusDisabled -> pure HideInvitationUrl + where + req = + paths ["i", "teams", toByteString' tid, "features", featureNameBS @ExposeInvitationURLsToTeamAdminConfig] + . expect2xx + getVerificationCodeEnabled :: ( MonadReader Env m, MonadIO m, diff --git a/services/brig/src/Brig/Team/API.hs b/services/brig/src/Brig/Team/API.hs index 74bda2dead..f00cd9ae1b 100644 --- a/services/brig/src/Brig/Team/API.hs +++ b/services/brig/src/Brig/Team/API.hs @@ -38,6 +38,7 @@ import Brig.Options (setMaxTeamSize, setTeamInvitationTimeout) import qualified Brig.Phone as Phone import qualified Brig.Team.DB as DB import Brig.Team.Email +import Brig.Team.Types (ShowOrHideInvitationUrl (..)) import Brig.Team.Util (ensurePermissionToAddUser, ensurePermissions) import Brig.Types.Intra (AccountStatus (..), NewUserScimInvitation (..), UserAccount (..)) import Brig.Types.Team (TeamSize) @@ -377,6 +378,7 @@ createInvitation' tid inviteeRole mbInviterUid fromEmail body = do let locale = irLocale body let inviteeName = irInviteeName body + showInvitationUrl <- lift $ wrapHttp $ Intra.getTeamExposeInvitationURLsToTeamAdmin tid lift $ do iid <- liftIO DB.mkInvitationId @@ -385,6 +387,7 @@ createInvitation' tid inviteeRole mbInviterUid fromEmail body = do (newInv, code) <- wrapClient $ DB.insertInvitation + showInvitationUrl iid tid inviteeRole @@ -412,7 +415,8 @@ listInvitationsH (_ ::: uid ::: tid ::: start ::: size) = do listInvitations :: UserId -> TeamId -> Maybe InvitationId -> Range 1 500 Int32 -> (Handler r) Public.InvitationList listInvitations uid tid start size = do ensurePermissions uid tid [AddTeamMember] - rs <- lift $ wrapClient $ DB.lookupInvitations tid start size + showInvitationUrl <- lift $ wrapHttp $ Intra.getTeamExposeInvitationURLsToTeamAdmin tid + rs <- lift $ wrapClient $ DB.lookupInvitations showInvitationUrl tid start size pure $! Public.InvitationList (DB.resultList rs) (DB.resultHasMore rs) getInvitationH :: JSON ::: UserId ::: TeamId ::: InvitationId -> (Handler r) Response @@ -425,7 +429,8 @@ getInvitationH (_ ::: uid ::: tid ::: iid) = do getInvitation :: UserId -> TeamId -> InvitationId -> (Handler r) (Maybe Public.Invitation) getInvitation uid tid iid = do ensurePermissions uid tid [AddTeamMember] - lift $ wrapClient $ DB.lookupInvitation tid iid + showInvitationUrl <- lift $ wrapHttp $ Intra.getTeamExposeInvitationURLsToTeamAdmin tid + lift $ wrapClient $ DB.lookupInvitation showInvitationUrl tid iid getInvitationByCodeH :: JSON ::: Public.InvitationCode -> (Handler r) Response getInvitationByCodeH (_ ::: c) = do @@ -433,7 +438,7 @@ getInvitationByCodeH (_ ::: c) = do getInvitationByCode :: Public.InvitationCode -> (Handler r) Public.Invitation getInvitationByCode c = do - inv <- lift . wrapClient $ DB.lookupInvitationByCode c + inv <- lift . wrapClient $ DB.lookupInvitationByCode HideInvitationUrl c maybe (throwStd $ errorToWai @'E.InvalidInvitationCode) pure inv headInvitationByEmailH :: JSON ::: Email -> (Handler r) Response @@ -453,7 +458,7 @@ getInvitationByEmailH (_ ::: email) = getInvitationByEmail :: Email -> (Handler r) Public.Invitation getInvitationByEmail email = do - inv <- lift $ wrapClient $ DB.lookupInvitationByEmail email + inv <- lift $ wrapClient $ DB.lookupInvitationByEmail HideInvitationUrl email maybe (throwStd (notFound "Invitation not found")) pure inv suspendTeamH :: JSON ::: TeamId -> (Handler r) Response diff --git a/services/brig/src/Brig/Team/DB.hs b/services/brig/src/Brig/Team/DB.hs index 0f6063c4d6..e52a511372 100644 --- a/services/brig/src/Brig/Team/DB.hs +++ b/services/brig/src/Brig/Team/DB.hs @@ -37,21 +37,29 @@ module Brig.Team.DB ) where +import Brig.App as App import Brig.Data.Instances () import Brig.Data.Types as T import Brig.Options +import Brig.Team.Template +import Brig.Team.Types (ShowOrHideInvitationUrl (..)) +import Brig.Template (renderTextWithBranding) import Cassandra as C +import Control.Lens (view) import Data.Conduit (runConduit, (.|)) import qualified Data.Conduit.List as C import Data.Id import Data.Json.Util (UTCTimeMillis, toUTCTimeMillis) import Data.Range -import Data.Text.Ascii (encodeBase64Url) +import Data.Text.Ascii (encodeBase64Url, toText) +import Data.Text.Encoding +import Data.Text.Lazy (toStrict) import Data.Time.Clock import Data.UUID.V4 import Imports import OpenSSL.Random (randBytes) import qualified System.Logger.Class as Log +import URI.ByteString import UnliftIO.Async (pooledMapConcurrentlyN_) import Wire.API.Team.Invitation import Wire.API.Team.Role @@ -76,7 +84,11 @@ data InvitationByEmail | InvitationByEmailMoreThanOne insertInvitation :: - MonadClient m => + ( Log.MonadLogger m, + MonadReader Env m, + MonadClient m + ) => + ShowOrHideInvitationUrl -> InvitationId -> TeamId -> Role -> @@ -88,9 +100,10 @@ insertInvitation :: -- | The timeout for the invitation code. Timeout -> m (Invitation, InvitationCode) -insertInvitation iid t role (toUTCTimeMillis -> now) minviter email inviteeName phone timeout = do +insertInvitation showUrl iid t role (toUTCTimeMillis -> now) minviter email inviteeName phone timeout = do code <- liftIO mkInvitationCode - let inv = Invitation t role iid now minviter email inviteeName phone + url <- mkInviteUrl showUrl t code + let inv = Invitation t role iid now minviter email inviteeName phone url retry x5 . batch $ do setType BatchLogged setConsistency LocalQuorum @@ -107,18 +120,33 @@ insertInvitation iid t role (toUTCTimeMillis -> now) minviter email inviteeName cqlInvitationByEmail :: PrepQuery W (Email, TeamId, InvitationId, InvitationCode, Int32) () cqlInvitationByEmail = "INSERT INTO team_invitation_email (email, team, invitation, code) VALUES (?, ?, ?, ?) USING TTL ?" -lookupInvitation :: MonadClient m => TeamId -> InvitationId -> m (Maybe Invitation) -lookupInvitation t r = - fmap toInvitation - <$> retry x1 (query1 cqlInvitation (params LocalQuorum (t, r))) +lookupInvitation :: + ( MonadClient m, + MonadReader Env m, + Log.MonadLogger m + ) => + ShowOrHideInvitationUrl -> + TeamId -> + InvitationId -> + m (Maybe Invitation) +lookupInvitation showUrl t r = do + inv <- retry x1 (query1 cqlInvitation (params LocalQuorum (t, r))) + traverse (toInvitation showUrl) inv where - cqlInvitation :: PrepQuery R (TeamId, InvitationId) (TeamId, Maybe Role, InvitationId, UTCTimeMillis, Maybe UserId, Email, Maybe Name, Maybe Phone) - cqlInvitation = "SELECT team, role, id, created_at, created_by, email, name, phone FROM team_invitation WHERE team = ? AND id = ?" + cqlInvitation :: PrepQuery R (TeamId, InvitationId) (TeamId, Maybe Role, InvitationId, UTCTimeMillis, Maybe UserId, Email, Maybe Name, Maybe Phone, InvitationCode) + cqlInvitation = "SELECT team, role, id, created_at, created_by, email, name, phone, code FROM team_invitation WHERE team = ? AND id = ?" -lookupInvitationByCode :: MonadClient m => InvitationCode -> m (Maybe Invitation) -lookupInvitationByCode i = +lookupInvitationByCode :: + ( Log.MonadLogger m, + MonadReader Env m, + MonadClient m + ) => + ShowOrHideInvitationUrl -> + InvitationCode -> + m (Maybe Invitation) +lookupInvitationByCode showUrl i = lookupInvitationInfo i >>= \case - Just InvitationInfo {..} -> lookupInvitation iiTeam iiInvId + Just InvitationInfo {..} -> lookupInvitation showUrl iiTeam iiInvId _ -> pure Nothing lookupInvitationCode :: MonadClient m => TeamId -> InvitationId -> m (Maybe InvitationCode) @@ -135,12 +163,21 @@ lookupInvitationCodeEmail t r = retry x1 (query1 cqlInvitationCodeEmail (params cqlInvitationCodeEmail :: PrepQuery R (TeamId, InvitationId) (InvitationCode, Email) cqlInvitationCodeEmail = "SELECT code, email FROM team_invitation WHERE team = ? AND id = ?" -lookupInvitations :: MonadClient m => TeamId -> Maybe InvitationId -> Range 1 500 Int32 -> m (ResultPage Invitation) -lookupInvitations team start (fromRange -> size) = do +lookupInvitations :: + ( Log.MonadLogger m, + MonadReader Env m, + MonadClient m + ) => + ShowOrHideInvitationUrl -> + TeamId -> + Maybe InvitationId -> + Range 1 500 Int32 -> + m (ResultPage Invitation) +lookupInvitations showUrl team start (fromRange -> size) = do page <- case start of Just ref -> retry x1 $ paginate cqlSelectFrom (paramsP LocalQuorum (team, ref) (size + 1)) Nothing -> retry x1 $ paginate cqlSelect (paramsP LocalQuorum (Identity team) (size + 1)) - pure $ toResult (hasMore page) $ map toInvitation (trim page) + toResult (hasMore page) <$> traverse (toInvitation showUrl) (trim page) where trim p = take (fromIntegral size) (result p) toResult more invs = @@ -149,10 +186,10 @@ lookupInvitations team start (fromRange -> size) = do { result = invs, hasMore = more } - cqlSelect :: PrepQuery R (Identity TeamId) (TeamId, Maybe Role, InvitationId, UTCTimeMillis, Maybe UserId, Email, Maybe Name, Maybe Phone) - cqlSelect = "SELECT team, role, id, created_at, created_by, email, name, phone FROM team_invitation WHERE team = ? ORDER BY id ASC" - cqlSelectFrom :: PrepQuery R (TeamId, InvitationId) (TeamId, Maybe Role, InvitationId, UTCTimeMillis, Maybe UserId, Email, Maybe Name, Maybe Phone) - cqlSelectFrom = "SELECT team, role, id, created_at, created_by, email, name, phone FROM team_invitation WHERE team = ? AND id > ? ORDER BY id ASC" + cqlSelect :: PrepQuery R (Identity TeamId) (TeamId, Maybe Role, InvitationId, UTCTimeMillis, Maybe UserId, Email, Maybe Name, Maybe Phone, InvitationCode) + cqlSelect = "SELECT team, role, id, created_at, created_by, email, name, phone, code FROM team_invitation WHERE team = ? ORDER BY id ASC" + cqlSelectFrom :: PrepQuery R (TeamId, InvitationId) (TeamId, Maybe Role, InvitationId, UTCTimeMillis, Maybe UserId, Email, Maybe Name, Maybe Phone, InvitationCode) + cqlSelectFrom = "SELECT team, role, id, created_at, created_by, email, name, phone, code FROM team_invitation WHERE team = ? AND id > ? ORDER BY id ASC" deleteInvitation :: MonadClient m => TeamId -> InvitationId -> m () deleteInvitation t i = do @@ -195,10 +232,17 @@ lookupInvitationInfo ic@(InvitationCode c) cqlInvitationInfo :: PrepQuery R (Identity InvitationCode) (TeamId, InvitationId) cqlInvitationInfo = "SELECT team, id FROM team_invitation_info WHERE code = ?" -lookupInvitationByEmail :: (Log.MonadLogger m, MonadClient m) => Email -> m (Maybe Invitation) -lookupInvitationByEmail e = +lookupInvitationByEmail :: + ( Log.MonadLogger m, + MonadReader Env m, + MonadClient m + ) => + ShowOrHideInvitationUrl -> + Email -> + m (Maybe Invitation) +lookupInvitationByEmail showUrl e = lookupInvitationInfoByEmail e >>= \case - InvitationByEmail InvitationInfo {..} -> lookupInvitation iiTeam iiInvId + InvitationByEmail InvitationInfo {..} -> lookupInvitation showUrl iiTeam iiInvId _ -> pure Nothing lookupInvitationInfoByEmail :: (Log.MonadLogger m, MonadClient m) => Email -> m InvitationByEmail @@ -230,6 +274,10 @@ countInvitations t = -- | brig used to not store the role, so for migration we allow this to be empty and fill in the -- default here. toInvitation :: + ( MonadReader Env m, + Log.MonadLogger m + ) => + ShowOrHideInvitationUrl -> ( TeamId, Maybe Role, InvitationId, @@ -237,8 +285,42 @@ toInvitation :: Maybe UserId, Email, Maybe Name, - Maybe Phone + Maybe Phone, + InvitationCode ) -> - Invitation -toInvitation (t, r, i, tm, minviter, e, inviteeName, p) = - Invitation t (fromMaybe defaultRole r) i tm minviter e inviteeName p + m Invitation +toInvitation showUrl (t, r, i, tm, minviter, e, inviteeName, p, code) = do + url <- mkInviteUrl showUrl t code + pure $ Invitation t (fromMaybe defaultRole r) i tm minviter e inviteeName p url + +mkInviteUrl :: + ( MonadReader Env m, + Log.MonadLogger m + ) => + ShowOrHideInvitationUrl -> + TeamId -> + InvitationCode -> + m (Maybe (URIRef Absolute)) +mkInviteUrl HideInvitationUrl _ _ = pure Nothing +mkInviteUrl ShowInvitationUrl team (InvitationCode c) = do + template <- invitationEmailUrl . invitationEmail . snd <$> teamTemplates Nothing + branding <- view App.templateBranding + let url = toStrict $ renderTextWithBranding template replace branding + parseHttpsUrl url + where + replace "team" = idToText team + replace "code" = toText c + replace x = x + + parseHttpsUrl :: Log.MonadLogger m => Text -> m (Maybe (URIRef Absolute)) + parseHttpsUrl url = + either (\e -> logError url e >> pure Nothing) (pure . Just) $ + parseURI laxURIParserOptions (encodeUtf8 url) + + logError :: (Log.MonadLogger m, Show e) => Text -> e -> m () + logError url e = + Log.err $ + Log.msg + (Log.val "Unable to create invitation url. Please check configuration.") + . Log.field "url" url + . Log.field "error" (show e) diff --git a/services/brig/src/Brig/Team/Types.hs b/services/brig/src/Brig/Team/Types.hs new file mode 100644 index 0000000000..e85bc4eb5b --- /dev/null +++ b/services/brig/src/Brig/Team/Types.hs @@ -0,0 +1,23 @@ +-- 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 Brig.Team.Types where + +import Imports + +data ShowOrHideInvitationUrl = ShowInvitationUrl | HideInvitationUrl + deriving (Eq, Show) diff --git a/services/brig/test/integration/API/User/Account.hs b/services/brig/test/integration/API/User/Account.hs index 07a4ef632c..eb579b4438 100644 --- a/services/brig/test/integration/API/User/Account.hs +++ b/services/brig/test/integration/API/User/Account.hs @@ -89,6 +89,7 @@ import Wire.API.Federation.API.Brig (UserDeletedConnectionsNotification (..)) import qualified Wire.API.Federation.API.Brig as FedBrig import Wire.API.Federation.API.Common (EmptyResponse (EmptyResponse)) import Wire.API.Internal.Notification +import Wire.API.Team.Feature (ExposeInvitationURLsToTeamAdminConfig (..), FeatureStatus (..), FeatureTTL' (..), LockStatus (LockStatusLocked), withStatus) import Wire.API.Team.Invitation (Invitation (inInvitation)) import Wire.API.Team.Permission hiding (self) import Wire.API.User @@ -1610,17 +1611,26 @@ testTooManyMembersForLegalhold opts brig = do responseJsonError =<< postInvitation brig tid owner invite +-- +-- 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 V74_ExposeInvitationsToTeamAdmin + ( migration, + ) +where + +import Cassandra.Schema +import Imports +import Text.RawString.QQ + +migration :: Migration +migration = Migration 74 "Add feature config for team feature exposing invitation URLs to team admins" $ do + schema' + [r| ALTER TABLE team_features ADD ( + expose_invitation_urls_to_team_admin int + ) + |] diff --git a/services/galley/src/Galley/API/Internal.hs b/services/galley/src/Galley/API/Internal.hs index 28c19f80d3..e7d9876ba4 100644 --- a/services/galley/src/Galley/API/Internal.hs +++ b/services/galley/src/Galley/API/Internal.hs @@ -192,6 +192,10 @@ type IFeatureAPI = :<|> IFeatureStatusGet MLSConfig :<|> IFeatureStatusPut '() MLSConfig :<|> IFeatureStatusPatch '() MLSConfig + -- ExposeInvitationURLsToTeamAdminConfig + :<|> IFeatureStatusGet ExposeInvitationURLsToTeamAdminConfig + :<|> IFeatureStatusPut '() ExposeInvitationURLsToTeamAdminConfig + :<|> IFeatureStatusPatch '() ExposeInvitationURLsToTeamAdminConfig -- SearchVisibilityInboundConfig :<|> IFeatureStatusGet SearchVisibilityInboundConfig :<|> IFeatureStatusPut '() SearchVisibilityInboundConfig @@ -530,6 +534,9 @@ featureAPI = <@> mkNamedAPI @'("iget", MLSConfig) (getFeatureStatus @Cassandra DontDoAuth) <@> mkNamedAPI @'("iput", MLSConfig) (setFeatureStatusInternal @Cassandra) <@> mkNamedAPI @'("ipatch", MLSConfig) (patchFeatureStatusInternal @Cassandra) + <@> mkNamedAPI @'("iget", ExposeInvitationURLsToTeamAdminConfig) (getFeatureStatus @Cassandra DontDoAuth) + <@> mkNamedAPI @'("iput", ExposeInvitationURLsToTeamAdminConfig) (setFeatureStatusInternal @Cassandra) + <@> mkNamedAPI @'("ipatch", ExposeInvitationURLsToTeamAdminConfig) (patchFeatureStatusInternal @Cassandra) <@> mkNamedAPI @'("iget", SearchVisibilityInboundConfig) (getFeatureStatus @Cassandra DontDoAuth) <@> mkNamedAPI @'("iput", SearchVisibilityInboundConfig) (setFeatureStatusInternal @Cassandra) <@> mkNamedAPI @'("ipatch", SearchVisibilityInboundConfig) (patchFeatureStatusInternal @Cassandra) diff --git a/services/galley/src/Galley/API/Public/Servant.hs b/services/galley/src/Galley/API/Public/Servant.hs index 9977aaae1a..baaa9cc414 100644 --- a/services/galley/src/Galley/API/Public/Servant.hs +++ b/services/galley/src/Galley/API/Public/Servant.hs @@ -111,6 +111,7 @@ servantSitemap = <@> mkNamedAPI @"get-team" getTeamH <@> mkNamedAPI @"delete-team" deleteTeam + features :: API FeatureAPI GalleyEffects features = mkNamedAPI @'("get", SSOConfig) (getFeatureStatus @Cassandra . DoAuth) <@> mkNamedAPI @'("get", LegalholdConfig) (getFeatureStatus @Cassandra . DoAuth) @@ -139,6 +140,8 @@ servantSitemap = <@> mkNamedAPI @'("put", SndFactorPasswordChallengeConfig) (setFeatureStatus @Cassandra . DoAuth) <@> mkNamedAPI @'("get", MLSConfig) (getFeatureStatus @Cassandra . DoAuth) <@> mkNamedAPI @'("put", MLSConfig) (setFeatureStatus @Cassandra . DoAuth) + <@> mkNamedAPI @'("get", ExposeInvitationURLsToTeamAdminConfig) (getFeatureStatus @Cassandra . DoAuth) + <@> mkNamedAPI @'("put", ExposeInvitationURLsToTeamAdminConfig) (setFeatureStatus @Cassandra . DoAuth) <@> mkNamedAPI @'("get", SearchVisibilityInboundConfig) (getFeatureStatus @Cassandra . DoAuth) <@> mkNamedAPI @'("put", SearchVisibilityInboundConfig) (setFeatureStatus @Cassandra . DoAuth) <@> mkNamedAPI @"get-all-feature-configs-for-user" (getAllFeatureConfigsForUser @Cassandra) diff --git a/services/galley/src/Galley/API/Teams/Features.hs b/services/galley/src/Galley/API/Teams/Features.hs index 57c93ebcaa..4556f935d7 100644 --- a/services/galley/src/Galley/API/Teams/Features.hs +++ b/services/galley/src/Galley/API/Teams/Features.hs @@ -179,7 +179,8 @@ type FeaturePersistentAllFeatures db = FeaturePersistentConstraint db GuestLinksConfig, FeaturePersistentConstraint db SndFactorPasswordChallengeConfig, FeaturePersistentConstraint db MLSConfig, - FeaturePersistentConstraint db SearchVisibilityInboundConfig + FeaturePersistentConstraint db SearchVisibilityInboundConfig, + FeaturePersistentConstraint db ExposeInvitationURLsToTeamAdminConfig ) getFeatureStatus :: @@ -440,6 +441,7 @@ getAllFeatureConfigsForServer = <*> getConfigForServer @db @GuestLinksConfig <*> getConfigForServer @db @SndFactorPasswordChallengeConfig <*> getConfigForServer @db @MLSConfig + <*> getConfigForServer @db @ExposeInvitationURLsToTeamAdminConfig getAllFeatureConfigsUser :: forall db r. @@ -473,6 +475,7 @@ getAllFeatureConfigsUser uid = <*> getConfigForUser @db @GuestLinksConfig uid <*> getConfigForUser @db @SndFactorPasswordChallengeConfig uid <*> getConfigForUser @db @MLSConfig uid + <*> getConfigForUser @db @ExposeInvitationURLsToTeamAdminConfig uid getAllFeatureConfigsTeam :: forall db r. @@ -505,6 +508,7 @@ getAllFeatureConfigsTeam tid = <*> getConfigForTeam @db @GuestLinksConfig tid <*> getConfigForTeam @db @SndFactorPasswordChallengeConfig tid <*> getConfigForTeam @db @MLSConfig tid + <*> getConfigForTeam @db @ExposeInvitationURLsToTeamAdminConfig tid -- | Note: this is an internal function which doesn't cover all features, e.g. LegalholdConfig genericGetConfigForTeam :: @@ -850,6 +854,44 @@ instance SetFeatureConfig db MLSConfig where setConfigForTeam tid wsnl = do persistAndPushEvent @db tid wsnl +instance GetFeatureConfig db ExposeInvitationURLsToTeamAdminConfig where + getConfigForServer = + -- we could look at the galley settings, but we don't have a team here, so there is not much else we can say. + pure $ + withStatus + FeatureStatusDisabled + LockStatusLocked + ExposeInvitationURLsToTeamAdminConfig + FeatureTTLUnlimited + + getConfigForTeam tid = do + allowList <- input <&> view (optSettings . setExposeInvitationURLsTeamAllowlist . to (fromMaybe [])) + mbOldStatus <- TeamFeatures.getFeatureConfig @db (Proxy @ExposeInvitationURLsToTeamAdminConfig) tid <&> fmap wssStatus + let teamAllowed = tid `elem` allowList + pure $ computeConfigForTeam teamAllowed (fromMaybe FeatureStatusDisabled mbOldStatus) + where + computeConfigForTeam :: Bool -> FeatureStatus -> WithStatus ExposeInvitationURLsToTeamAdminConfig + computeConfigForTeam teamAllowed teamDbStatus = + if teamAllowed + then makeConfig LockStatusUnlocked teamDbStatus + else makeConfig LockStatusLocked FeatureStatusDisabled + + makeConfig :: LockStatus -> FeatureStatus -> WithStatus ExposeInvitationURLsToTeamAdminConfig + makeConfig lockStatus status = + withStatus + status + lockStatus + ExposeInvitationURLsToTeamAdminConfig + FeatureTTLUnlimited + +instance SetFeatureConfig db ExposeInvitationURLsToTeamAdminConfig where + type SetConfigForTeamConstraints db ExposeInvitationURLsToTeamAdminConfig (r :: EffectRow) = (Member (ErrorS OperationDenied) r) + setConfigForTeam tid wsnl = do + lockStatus <- getConfigForTeam @db @ExposeInvitationURLsToTeamAdminConfig tid <&> wsLockStatus + case lockStatus of + LockStatusLocked -> throwS @OperationDenied + LockStatusUnlocked -> persistAndPushEvent @db tid wsnl + -- -- | 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 6bc6719a2b..275255cd59 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 = 73 +schemaVersion = 74 diff --git a/services/galley/src/Galley/Cassandra/TeamFeatures.hs b/services/galley/src/Galley/Cassandra/TeamFeatures.hs index eacdde5653..169280c51d 100644 --- a/services/galley/src/Galley/Cassandra/TeamFeatures.hs +++ b/services/galley/src/Galley/Cassandra/TeamFeatures.hs @@ -313,3 +313,7 @@ instance FeatureStatusCassandra MLSConfig where insert = "insert into team_features (team_id, mls_status, mls_default_protocol, \ \mls_protocol_toggle_users, mls_allowed_ciphersuites, mls_default_ciphersuite) values (?, ?, ?, ?, ?, ?)" + +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/src/Galley/Options.hs b/services/galley/src/Galley/Options.hs index 2d82241913..edb3850d29 100644 --- a/services/galley/src/Galley/Options.hs +++ b/services/galley/src/Galley/Options.hs @@ -22,6 +22,7 @@ module Galley.Options setHttpPoolSize, setMaxTeamSize, setMaxFanoutSize, + setExposeInvitationURLsTeamAllowlist, setMaxConvSize, setIntraListing, setConversationCodeURI, @@ -56,6 +57,7 @@ where import Control.Lens hiding (Level, (.=)) import Data.Aeson.TH (deriveFromJSON) import Data.Domain (Domain) +import Data.Id (TeamId) import Data.Misc import Data.Range import Galley.Keys @@ -76,6 +78,10 @@ data Settings = Settings -- This defaults to setMaxTeamSize and cannot be > HardTruncationLimit. Useful -- to tune mainly for testing purposes. _setMaxFanoutSize :: !(Maybe (Range 1 HardTruncationLimit Int32)), + -- | List of teams for which the invitation URL can be added to the list of all + -- invitations retrievable by team admins. See also: + -- 'ExposeInvitationURLsToTeamAdminConfig'. + _setExposeInvitationURLsTeamAllowlist :: !(Maybe [TeamId]), -- | Max number of members in a conversation. NOTE: This must be in sync with Brig _setMaxConvSize :: !Word16, -- | Whether to call Brig for device listing diff --git a/services/galley/test/integration/API/Teams/Feature.hs b/services/galley/test/integration/API/Teams/Feature.hs index 0660b3f836..365584c46f 100644 --- a/services/galley/test/integration/API/Teams/Feature.hs +++ b/services/galley/test/integration/API/Teams/Feature.hs @@ -18,15 +18,17 @@ module API.Teams.Feature (tests) where +import API.SQS (assertQueue, tActivate) import API.Util (HasGalley, getFeatureStatusMulti, withSettingsOverrides) import qualified API.Util as Util -import API.Util.TeamFeature (patchFeatureStatusInternal) +import API.Util.TeamFeature (patchFeatureStatusInternal, putTeamFeatureFlagWithGalley) import qualified API.Util.TeamFeature as Util import Bilge import Bilge.Assert import Brig.Types.Test.Arbitrary (Arbitrary (arbitrary)) import Cassandra as Cql -import Control.Lens (over, to, view) +import Control.Lens (over, to, view, (.~), (?~)) +import Control.Lens.Operators () import Control.Monad.Catch (MonadCatch) import Data.Aeson (FromJSON, ToJSON) import qualified Data.Aeson as Aeson @@ -40,7 +42,7 @@ import Data.Schema (ToSchema) import qualified Data.Set as Set import Data.Timeout (TimeoutUnit (Second), (#)) import GHC.TypeLits (KnownSymbol) -import Galley.Options (optSettings, setFeatureFlags) +import Galley.Options (optSettings, setExposeInvitationURLsTeamAllowlist, setFeatureFlags) import Galley.Types.Teams import Imports import Network.Wai.Utilities (label) @@ -56,7 +58,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 (FeatureStatus (..), FeatureTTL, FeatureTTL' (..), LockStatus (LockStatusUnlocked), MLSConfig (MLSConfig)) +import Wire.API.Team.Feature (ExposeInvitationURLsToTeamAdminConfig (..), FeatureStatus (..), FeatureTTL, FeatureTTL' (..), LockStatus (LockStatusUnlocked), MLSConfig (MLSConfig)) import qualified Wire.API.Team.Feature as Public tests :: IO TestSetup -> TestTree @@ -135,6 +137,12 @@ tests s = testPatch AssertLockStatusChange Public.FeatureStatusDisabled Public.SndFactorPasswordChallengeConfig, test s (unpack $ Public.featureNameBS @Public.SelfDeletingMessagesConfig) $ testPatch AssertLockStatusChange Public.FeatureStatusEnabled (Public.SelfDeletingMessagesConfig 0) + ], + testGroup + "ExposeInvitationURLsToTeamAdmin" + [ test s "can be set when TeamId is in allow list" testExposeInvitationURLsToTeamAdminTeamIdInAllowList, + test s "can not be set when allow list is empty" testExposeInvitationURLsToTeamAdminEmptyAllowList, + test s "server config takes precendece over team feature config" testExposeInvitationURLsToTeamAdminServerConfigTakesPrecedence ] ] @@ -442,7 +450,7 @@ testClassifiedDomainsDisabled = do liftIO $ Public.wsStatus result @?= Public.wssStatus expected' liftIO $ Public.wsConfig result @?= Public.wssConfig expected' - let classifiedDomainsDisabled = \opts -> + let classifiedDomainsDisabled opts = opts & over (optSettings . setFeatureFlags . flagClassifiedDomains) @@ -991,7 +999,8 @@ testAllFeatures = do 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.afcSearchVisibilityInboundConfig = Public.withStatus FeatureStatusDisabled Public.LockStatusUnlocked Public.SearchVisibilityInboundConfig Public.FeatureTTLUnlimited, + Public.afcExposeInvitationURLsToTeamAdmin = Public.withStatus FeatureStatusDisabled Public.LockStatusLocked Public.ExposeInvitationURLsToTeamAdminConfig Public.FeatureTTLUnlimited } testFeatureConfigConsistency :: TestM () @@ -1131,6 +1140,73 @@ testMLS = do wsAssertFeatureConfigUpdate @MLSConfig config3 LockStatusUnlocked getViaEndpoints config3 +testExposeInvitationURLsToTeamAdminTeamIdInAllowList :: TestM () +testExposeInvitationURLsToTeamAdminTeamIdInAllowList = do + owner <- Util.randomUser + tid <- Util.createBindingTeamInternal "foo" owner + assertQueue "create team" tActivate + void $ + withSettingsOverrides (\opts -> opts & optSettings . setExposeInvitationURLsTeamAllowlist ?~ [tid]) $ do + g <- view tsGalley + assertExposeInvitationURLsToTeamAdminConfigStatus owner tid FeatureStatusDisabled Public.LockStatusUnlocked + let enabled = Public.WithStatusNoLock Public.FeatureStatusEnabled ExposeInvitationURLsToTeamAdminConfig Public.FeatureTTLUnlimited + void $ + putTeamFeatureFlagWithGalley @ExposeInvitationURLsToTeamAdminConfig g owner tid enabled !!! do + const 200 === statusCode + assertExposeInvitationURLsToTeamAdminConfigStatus owner tid FeatureStatusEnabled Public.LockStatusUnlocked + +testExposeInvitationURLsToTeamAdminEmptyAllowList :: TestM () +testExposeInvitationURLsToTeamAdminEmptyAllowList = do + owner <- Util.randomUser + tid <- Util.createBindingTeamInternal "foo" owner + assertQueue "create team" tActivate + void $ + withSettingsOverrides (\opts -> opts & optSettings . setExposeInvitationURLsTeamAllowlist .~ Nothing) $ do + g <- view tsGalley + assertExposeInvitationURLsToTeamAdminConfigStatus owner tid FeatureStatusDisabled Public.LockStatusLocked + let enabled = Public.WithStatusNoLock Public.FeatureStatusEnabled ExposeInvitationURLsToTeamAdminConfig Public.FeatureTTLUnlimited + void $ + putTeamFeatureFlagWithGalley @ExposeInvitationURLsToTeamAdminConfig g owner tid enabled !!! do + const 409 === statusCode + assertExposeInvitationURLsToTeamAdminConfigStatus owner tid FeatureStatusDisabled Public.LockStatusLocked + +-- | Ensure that the server config takes precedence over a saved team config. +-- +-- In other words: When a team id is no longer in the +-- `setExposeInvitationURLsTeamAllowlist` the +-- `ExposeInvitationURLsToTeamAdminConfig` is always disabled (even tough it +-- might have been enabled before). +testExposeInvitationURLsToTeamAdminServerConfigTakesPrecedence :: TestM () +testExposeInvitationURLsToTeamAdminServerConfigTakesPrecedence = do + owner <- Util.randomUser + tid <- Util.createBindingTeamInternal "foo" owner + assertQueue "create team" tActivate + void $ + withSettingsOverrides (\opts -> opts & optSettings . setExposeInvitationURLsTeamAllowlist ?~ [tid]) $ do + g <- view tsGalley + assertExposeInvitationURLsToTeamAdminConfigStatus owner tid FeatureStatusDisabled Public.LockStatusUnlocked + let enabled = Public.WithStatusNoLock Public.FeatureStatusEnabled ExposeInvitationURLsToTeamAdminConfig Public.FeatureTTLUnlimited + void $ + putTeamFeatureFlagWithGalley @ExposeInvitationURLsToTeamAdminConfig g owner tid enabled !!! do + const 200 === statusCode + assertExposeInvitationURLsToTeamAdminConfigStatus owner tid FeatureStatusEnabled Public.LockStatusUnlocked + void $ + withSettingsOverrides (\opts -> opts & optSettings . setExposeInvitationURLsTeamAllowlist .~ Nothing) $ do + g <- view tsGalley + assertExposeInvitationURLsToTeamAdminConfigStatus owner tid FeatureStatusDisabled Public.LockStatusLocked + let enabled = Public.WithStatusNoLock Public.FeatureStatusEnabled ExposeInvitationURLsToTeamAdminConfig Public.FeatureTTLUnlimited + void $ + putTeamFeatureFlagWithGalley @ExposeInvitationURLsToTeamAdminConfig g owner tid enabled !!! do + const 409 === statusCode + assertExposeInvitationURLsToTeamAdminConfigStatus owner tid FeatureStatusDisabled Public.LockStatusLocked + +assertExposeInvitationURLsToTeamAdminConfigStatus :: UserId -> TeamId -> FeatureStatus -> LockStatus -> TestM () +assertExposeInvitationURLsToTeamAdminConfigStatus owner tid fStatus lStatus = do + g <- view tsGalley + Util.getTeamFeatureFlagWithGalley @ExposeInvitationURLsToTeamAdminConfig g owner tid !!! do + const 200 === statusCode + const (Right (Public.withStatus fStatus lStatus Public.ExposeInvitationURLsToTeamAdminConfig Public.FeatureTTLUnlimited)) === responseJsonEither + assertFlagForbidden :: HasCallStack => TestM ResponseLBS -> TestM () assertFlagForbidden res = do res !!! do