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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/2-features/pr-2503
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Team feature API now includes endpoints to get and set the `searchVisibilityInbound` feature
11 changes: 5 additions & 6 deletions docs/legacy/reference/config-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,9 @@ IMPORTANT: If you switch this back to `disabled-permanently` from
that have created them while it was allowed. This may change in the
future.

### Team Feature teamSearchVisibility
### Team searchVisibility

The feature flag `teamSearchVisibility` affects the outbound search of user
The team flag `searchVisibility` affects the outbound search of user
searches. If it is set to `no-name-outside-team` for a team then all users of
that team will no longer be able to find users that are not part of their team
when searching. This also includes finding other users by by providing their
Expand All @@ -96,6 +96,9 @@ pull-down-menu "body":
"no-name-outside-team"
```

The team feature flag `teamSearchVisibility` determines whether it is allowed to change the `searchVisibility` setting or not.
The default is `disabled-by-default`. Note that whenever this feature setting is disabled the `searchVisibility` will be reset to `standard`.

The default setting that applies to all teams on the instance can be defined at configuration

```yaml
Expand All @@ -104,10 +107,6 @@ settings:
teamSearchVisibility: disabled-by-default # or enabled-by-default
```

where disabled is equivalent to `standard` and enabled is equivalent to `no-name-outside-team`. Individual teams may ovewrite the default setting.

On wire cloud the default setting is `standard`.

### TeamFeature searchVisibilityInbound

The team feature flag `searchVisibilityInbound` affects if the team's users are
Expand Down
22 changes: 11 additions & 11 deletions libs/galley-types/src/Galley/Types/Teams.hs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ module Galley.Types.Teams
unDefaults,
FeatureSSO (..),
FeatureLegalHold (..),
FeatureTeamSearchVisibility (..),
FeatureTeamSearchVisibilityAvailability (..),
notTeamMember,
findTeamMember,
isTeamMember,
Expand Down Expand Up @@ -140,7 +140,7 @@ newtype TeamCreationTime = TeamCreationTime
data FeatureFlags = FeatureFlags
{ _flagSSO :: !FeatureSSO,
_flagLegalHold :: !FeatureLegalHold,
_flagTeamSearchVisibility :: !FeatureTeamSearchVisibility,
_flagTeamSearchVisibility :: !FeatureTeamSearchVisibilityAvailability,
_flagAppLockDefaults :: !(Defaults (ImplicitLockStatus AppLockConfig)),
_flagClassifiedDomains :: !(ImplicitLockStatus ClassifiedDomainsConfig),
_flagFileSharing :: !(Defaults (WithStatus FileSharingConfig)),
Expand Down Expand Up @@ -180,9 +180,9 @@ data FeatureLegalHold
-- | Default value for all teams that have not enabled or disabled this feature explicitly.
-- See also 'Wire.API.Team.SearchVisibility.TeamSearchVisibilityEnabled',
-- 'Wire.API.Team.SearchVisibility.TeamSearchVisibility'.
data FeatureTeamSearchVisibility
= FeatureTeamSearchVisibilityEnabledByDefault
| FeatureTeamSearchVisibilityDisabledByDefault
data FeatureTeamSearchVisibilityAvailability
= FeatureTeamSearchVisibilityAvailableByDefault
| FeatureTeamSearchVisibilityUnavailableByDefault
deriving (Eq, Ord, Show, Enum, Bounded, Generic)

-- NOTE: This is used only in the config and thus YAML... camelcase
Expand Down Expand Up @@ -259,14 +259,14 @@ instance ToJSON FeatureLegalHold where
toJSON FeatureLegalHoldDisabledByDefault = String "disabled-by-default"
toJSON FeatureLegalHoldWhitelistTeamsAndImplicitConsent = String "whitelist-teams-and-implicit-consent"

instance FromJSON FeatureTeamSearchVisibility where
parseJSON (String "enabled-by-default") = pure FeatureTeamSearchVisibilityEnabledByDefault
parseJSON (String "disabled-by-default") = pure FeatureTeamSearchVisibilityDisabledByDefault
instance FromJSON FeatureTeamSearchVisibilityAvailability where
parseJSON (String "enabled-by-default") = pure FeatureTeamSearchVisibilityAvailableByDefault
parseJSON (String "disabled-by-default") = pure FeatureTeamSearchVisibilityUnavailableByDefault
parseJSON bad = fail $ "FeatureSearchVisibility: " <> cs (encode bad)

instance ToJSON FeatureTeamSearchVisibility where
toJSON FeatureTeamSearchVisibilityEnabledByDefault = String "enabled-by-default"
toJSON FeatureTeamSearchVisibilityDisabledByDefault = String "disabled-by-default"
instance ToJSON FeatureTeamSearchVisibilityAvailability where
toJSON FeatureTeamSearchVisibilityAvailableByDefault = String "enabled-by-default"
toJSON FeatureTeamSearchVisibilityUnavailableByDefault = String "disabled-by-default"

makeLenses ''TeamCreationTime
makeLenses ''FeatureFlags
Expand Down
2 changes: 2 additions & 0 deletions libs/wire-api/src/Wire/API/Routes/Public/Galley.hs
Original file line number Diff line number Diff line change
Expand Up @@ -1120,6 +1120,8 @@ type FeatureAPI =
:<|> FeatureStatusPut '() SndFactorPasswordChallengeConfig
:<|> FeatureStatusGet MLSConfig
:<|> FeatureStatusPut '() MLSConfig
:<|> FeatureStatusGet SearchVisibilityInboundConfig
:<|> FeatureStatusPut '() SearchVisibilityInboundConfig
:<|> AllFeatureConfigsUserGet
:<|> AllFeatureConfigsTeamGet
:<|> FeatureConfigDeprecatedGet "The usage of this endpoint was removed in iOS in version 3.101. It is not used by team management, or webapp, and is potentially used by the old Android client as of June 2022" LegalholdConfig
Expand Down
5 changes: 4 additions & 1 deletion libs/wire-api/src/Wire/API/Team/Feature.hs
Original file line number Diff line number Diff line change
Expand Up @@ -817,7 +817,8 @@ data AllFeatureConfigs = AllFeatureConfigs
afcSelfDeletingMessages :: WithStatus SelfDeletingMessagesConfig,
afcGuestLink :: WithStatus GuestLinksConfig,
afcSndFactorPasswordChallenge :: WithStatus SndFactorPasswordChallengeConfig,
afcMLS :: WithStatus MLSConfig
afcMLS :: WithStatus MLSConfig,
afcSearchVisibilityInboundConfig :: WithStatus SearchVisibilityInboundConfig
}
deriving stock (Eq, Show)
deriving (FromJSON, ToJSON, S.ToSchema) via (Schema AllFeatureConfigs)
Expand All @@ -839,6 +840,7 @@ instance ToSchema AllFeatureConfigs where
<*> afcGuestLink .= featureField
<*> afcSndFactorPasswordChallenge .= featureField
<*> afcMLS .= featureField
<*> afcSearchVisibilityInboundConfig .= featureField
where
featureField ::
forall cfg.
Expand All @@ -862,5 +864,6 @@ instance Arbitrary AllFeatureConfigs where
<*> arbitrary
<*> arbitrary
<*> arbitrary
<*> arbitrary

makeLenses ''ImplicitLockStatus
10 changes: 9 additions & 1 deletion services/galley/src/Galley/API/Internal.hs
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,9 @@ type IFeatureAPI =
-- MLSConfig
:<|> IFeatureStatusGet MLSConfig
:<|> IFeatureStatusPut '() MLSConfig
-- SearchVisibilityInboundConfig
:<|> IFeatureStatusGet SearchVisibilityInboundConfig
:<|> IFeatureStatusPut '() SearchVisibilityInboundConfig
-- all feature configs
:<|> Named
"feature-configs-internal"
Expand Down Expand Up @@ -337,6 +340,9 @@ type ITeamsAPIBase =
:<|> Named
"set-search-visibility-internal"
( CanThrow 'TeamSearchVisibilityNotEnabled
:> CanThrow OperationDenied
:> CanThrow 'NotATeamMember
:> CanThrow 'TeamNotFound
:> ReqBody '[Servant.JSON] TeamSearchVisibilityView
:> MultiVerb1 'PUT '[Servant.JSON] (RespondEmpty 204 "OK")
)
Expand Down Expand Up @@ -434,7 +440,7 @@ iTeamsAPI = mkAPI $ \tid -> hoistAPIHandler id (base tid)
<@> mkNamedAPI @"user-is-team-owner" (Teams.userIsTeamOwner tid)
<@> hoistAPISegment
( mkNamedAPI @"get-search-visibility-internal" (Teams.getSearchVisibilityInternal tid)
<@> mkNamedAPI @"set-search-visibility-internal" (Teams.setSearchVisibilityInternal @Cassandra tid)
<@> mkNamedAPI @"set-search-visibility-internal" (Teams.setSearchVisibilityInternal @Cassandra (featureEnabledForTeam @Cassandra @SearchVisibilityAvailableConfig) tid)
)

featureAPI :: API IFeatureAPI GalleyEffects
Expand Down Expand Up @@ -471,6 +477,8 @@ featureAPI =
<@> mkNamedAPI (getFeatureStatus @Cassandra DontDoAuth)
<@> mkNamedAPI (getFeatureStatus @Cassandra DontDoAuth)
<@> mkNamedAPI (\tid ws ttl -> setFeatureStatus @Cassandra ttl DontDoAuth tid ws)
<@> mkNamedAPI (getFeatureStatus @Cassandra DontDoAuth)
<@> mkNamedAPI (\tid ws ttl -> setFeatureStatus @Cassandra ttl DontDoAuth tid ws)
<@> mkNamedAPI (maybe (getAllFeatureConfigsForServer @Cassandra) (getAllFeatureConfigsForUser @Cassandra))

internalSitemap :: Routes a (Sem GalleyEffects) ()
Expand Down
4 changes: 3 additions & 1 deletion services/galley/src/Galley/API/Public/Servant.hs
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ servantSitemap =
<@> mkNamedAPI @'("get-deprecated", SearchVisibilityAvailableConfig) (getFeatureStatus @Cassandra . DoAuth)
<@> mkNamedAPI @'("put-deprecated", SearchVisibilityAvailableConfig) (setFeatureStatus @Cassandra Nothing . DoAuth)
<@> mkNamedAPI @"get-search-visibility" getSearchVisibility
<@> mkNamedAPI @"set-search-visibility" (setSearchVisibility @Cassandra)
<@> mkNamedAPI @"set-search-visibility" (setSearchVisibility @Cassandra (featureEnabledForTeam @Cassandra @SearchVisibilityAvailableConfig))
<@> mkNamedAPI @'("get", ValidateSAMLEmailsConfig) (getFeatureStatus @Cassandra . DoAuth)
<@> mkNamedAPI @'("get-deprecated", ValidateSAMLEmailsConfig) (getFeatureStatus @Cassandra . DoAuth)
<@> mkNamedAPI @'("get", DigitalSignaturesConfig) (getFeatureStatus @Cassandra . DoAuth)
Expand All @@ -138,6 +138,8 @@ servantSitemap =
<@> mkNamedAPI @'("put", SndFactorPasswordChallengeConfig) (setFeatureStatus @Cassandra Nothing . DoAuth)
<@> mkNamedAPI @'("get", MLSConfig) (getFeatureStatus @Cassandra . DoAuth)
<@> mkNamedAPI @'("put", MLSConfig) (setFeatureStatus @Cassandra Nothing . DoAuth)
<@> mkNamedAPI @'("get", SearchVisibilityInboundConfig) (getFeatureStatus @Cassandra . DoAuth)
<@> mkNamedAPI @'("put", SearchVisibilityInboundConfig) (setFeatureStatus @Cassandra Nothing . DoAuth)
<@> mkNamedAPI @"get-all-feature-configs-for-user" (getAllFeatureConfigsForUser @Cassandra)
<@> mkNamedAPI @"get-all-feature-configs-for-team" (getAllFeatureConfigsForTeam @Cassandra)
<@> mkNamedAPI @'("get-config", LegalholdConfig) (getFeatureStatusForUser @Cassandra)
Expand Down
29 changes: 6 additions & 23 deletions services/galley/src/Galley/API/Teams.hs
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,6 @@ import qualified Galley.Effects.Queue as E
import qualified Galley.Effects.SearchVisibilityStore as SearchVisibilityData
import qualified Galley.Effects.SparAccess as Spar
import Galley.Effects.TeamFeatureStore (FeaturePersistentConstraint)
import qualified Galley.Effects.TeamFeatureStore as TeamFeatures
import qualified Galley.Effects.TeamMemberStore as E
import qualified Galley.Effects.TeamStore as E
import qualified Galley.Intra.Journal as Journal
Expand Down Expand Up @@ -141,7 +140,6 @@ import Wire.API.Team.Conversation
import qualified Wire.API.Team.Conversation as Public
import Wire.API.Team.Export (TeamExportUser (..))
import Wire.API.Team.Feature
import qualified Wire.API.Team.Feature as Public
import Wire.API.Team.Member (HardTruncationLimit, ListType (ListComplete, ListTruncated), NewTeamMember, TeamMember, TeamMemberList, TeamMemberListOptPerms, TeamMemberOptPerms, hardTruncationLimit, invitation, nPermissions, nUserId, newTeamMemberList, ntmNewTeamMember, permissions, setOptionalPerms, setOptionalPermsMany, teamMemberListType, teamMembers, tmdAuthPassword, userId)
import qualified Wire.API.Team.Member as Public
import Wire.API.Team.Permission (Perm (..), Permissions (..), SPerm (..), copy, fullPermissions, self)
Expand Down Expand Up @@ -1101,14 +1099,15 @@ setSearchVisibility ::
r,
FeaturePersistentConstraint db SearchVisibilityAvailableConfig
) =>
(TeamId -> Sem r Bool) ->
Local UserId ->
TeamId ->
Public.TeamSearchVisibilityView ->
Sem r ()
setSearchVisibility luid tid req = do
setSearchVisibility availableForTeam luid tid req = do
zusrMembership <- E.getTeamMember tid (tUnqualified luid)
void $ permissionCheck ChangeTeamSearchVisibility zusrMembership
setSearchVisibilityInternal @db tid req
setSearchVisibilityInternal @db availableForTeam tid req

-- Internal -----------------------------------------------------------------

Expand Down Expand Up @@ -1380,22 +1379,6 @@ canUserJoinTeam tid = do
(TeamSize sizeBeforeJoin) <- E.getSize tid
ensureNotTooLargeForLegalHold @db tid (fromIntegral sizeBeforeJoin + 1)

getTeamSearchVisibilityAvailableInternal ::
forall db r.
(Members '[Input Opts, TeamFeatureStore db] r, FeaturePersistentConstraint db SearchVisibilityAvailableConfig) =>
TeamId ->
Sem r (WithStatus Public.SearchVisibilityAvailableConfig)
getTeamSearchVisibilityAvailableInternal tid = do
defConfig <- do
featureTeamSearchVisibility <- view (optSettings . setFeatureFlags . flagTeamSearchVisibility) <$> input
let status = case featureTeamSearchVisibility of
FeatureTeamSearchVisibilityEnabledByDefault -> Public.FeatureStatusEnabled
FeatureTeamSearchVisibilityDisabledByDefault -> Public.FeatureStatusDisabled
pure $ (defFeatureStatus @Public.SearchVisibilityAvailableConfig) {wsStatus = status}
mbConfig <- TeamFeatures.getFeatureConfig @db (Proxy @Public.SearchVisibilityAvailableConfig) tid
mbLockStatus <- TeamFeatures.getFeatureLockStatus @db (Proxy @Public.SearchVisibilityAvailableConfig) tid
pure $ computeFeatureConfigForTeamUser mbConfig mbLockStatus defConfig

-- | Modify and get visibility type for a team (internal, no user permission checks)
getSearchVisibilityInternal ::
Member SearchVisibilityStore r =>
Expand All @@ -1416,12 +1399,12 @@ setSearchVisibilityInternal ::
r,
FeaturePersistentConstraint db SearchVisibilityAvailableConfig
) =>
(TeamId -> Sem r Bool) ->
TeamId ->
TeamSearchVisibilityView ->
Sem r ()
setSearchVisibilityInternal tid (TeamSearchVisibilityView searchVisibility) = do
fs <- getTeamSearchVisibilityAvailableInternal @db tid
unless (Public.wsStatus fs == Public.FeatureStatusEnabled) $
setSearchVisibilityInternal availableForTeam tid (TeamSearchVisibilityView searchVisibility) = do
unlessM (availableForTeam tid) $
throwS @'TeamSearchVisibilityNotEnabled
SearchVisibilityData.setSearchVisibility tid searchVisibility

Expand Down
27 changes: 24 additions & 3 deletions services/galley/src/Galley/API/Teams/Features.hs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ module Galley.API.Teams.Features
SetFeatureConfig,
guardSecondFactorDisabled,
DoAuth (..),
featureEnabledForTeam,
)
where

Expand Down Expand Up @@ -174,7 +175,8 @@ type FeaturePersistentAllFeatures db =
FeaturePersistentConstraint db SelfDeletingMessagesConfig,
FeaturePersistentConstraint db GuestLinksConfig,
FeaturePersistentConstraint db SndFactorPasswordChallengeConfig,
FeaturePersistentConstraint db MLSConfig
FeaturePersistentConstraint db MLSConfig,
FeaturePersistentConstraint db SearchVisibilityInboundConfig
)

getFeatureStatus ::
Expand Down Expand Up @@ -377,6 +379,7 @@ getAllFeatureConfigsForServer =
<*> getConfigForServer @db @GuestLinksConfig
<*> getConfigForServer @db @SndFactorPasswordChallengeConfig
<*> getConfigForServer @db @MLSConfig
<*> getConfigForServer @db @SearchVisibilityInboundConfig

getAllFeatureConfigsUser ::
forall db r.
Expand Down Expand Up @@ -409,6 +412,7 @@ getAllFeatureConfigsUser uid =
<*> getConfigForUser @db @GuestLinksConfig uid
<*> getConfigForUser @db @SndFactorPasswordChallengeConfig uid
<*> getConfigForUser @db @MLSConfig uid
<*> getConfigForUser @db @SearchVisibilityInboundConfig uid

getAllFeatureConfigsTeam ::
forall db r.
Expand Down Expand Up @@ -440,6 +444,7 @@ getAllFeatureConfigsTeam tid =
<*> getConfigForTeam @db @GuestLinksConfig tid
<*> getConfigForTeam @db @SndFactorPasswordChallengeConfig tid
<*> getConfigForTeam @db @MLSConfig tid
<*> getConfigForTeam @db @SearchVisibilityInboundConfig tid

-- | Note: this is an internal function which doesn't cover all features, e.g. LegalholdConfig
genericGetConfigForTeam ::
Expand Down Expand Up @@ -580,8 +585,8 @@ instance GetFeatureConfig db SearchVisibilityAvailableConfig where
getConfigForServer = do
status <-
inputs (view (optSettings . setFeatureFlags . flagTeamSearchVisibility)) <&> \case
FeatureTeamSearchVisibilityEnabledByDefault -> FeatureStatusEnabled
FeatureTeamSearchVisibilityDisabledByDefault -> FeatureStatusDisabled
FeatureTeamSearchVisibilityAvailableByDefault -> FeatureStatusEnabled
FeatureTeamSearchVisibilityUnavailableByDefault -> FeatureStatusDisabled
pure $ defFeatureStatus {wsStatus = status}

instance SetFeatureConfig db SearchVisibilityAvailableConfig where
Expand Down Expand Up @@ -814,3 +819,19 @@ guardSecondFactorDisabled uid cid action = do
case wsStatus tf of
FeatureStatusDisabled -> action
FeatureStatusEnabled -> throwS @'AccessDenied

featureEnabledForTeam ::
forall db cfg r.
( GetFeatureConfig db cfg,
GetConfigForTeamConstraints db cfg r,
Members
'[ ErrorS OperationDenied,
ErrorS 'NotATeamMember,
ErrorS 'TeamNotFound,
TeamStore
]
r
) =>
TeamId ->
Sem r Bool
featureEnabledForTeam tid = (==) FeatureStatusEnabled . wsStatus <$> getFeatureStatus @db @cfg DontDoAuth tid
4 changes: 2 additions & 2 deletions services/galley/test/integration/API/Teams.hs
Original file line number Diff line number Diff line change
Expand Up @@ -477,12 +477,12 @@ testEnableTeamSearchVisibilityPerTeam = do
const 200 === statusCode
const (Just (TeamSearchVisibilityView vis)) === responseJsonUnsafe

Util.withCustomSearchFeature FeatureTeamSearchVisibilityEnabledByDefault $ do
Util.withCustomSearchFeature FeatureTeamSearchVisibilityAvailableByDefault $ do
g <- view tsGalley
check "Teams should start with Custom Search Visibility enabled" Public.FeatureStatusEnabled
putSearchVisibility g owner tid SearchVisibilityNoNameOutsideTeam !!! const 204 === statusCode
putSearchVisibility g owner tid SearchVisibilityStandard !!! const 204 === statusCode
Util.withCustomSearchFeature FeatureTeamSearchVisibilityDisabledByDefault $ do
Util.withCustomSearchFeature FeatureTeamSearchVisibilityUnavailableByDefault $ do
check "Teams should start with Custom Search Visibility disabled" Public.FeatureStatusDisabled
putSearchVisibilityCheckNotAllowed

Expand Down
Loading