Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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: 0 additions & 1 deletion changelog.d/1-api-changes/WPB-27393

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Moved the following endpoints from development version V17 to the new development version V18: `PUT /conversations/:domain/:cnv/members` (rejection of replacements that would leave a group adminless), `PUT /teams/:tid/features/preventAdminlessGroups` (duration-string request body), and `POST /register` (403 for SCIM-managed users changing their name). V17 behaves like V16 for these endpoints. API version V18 was created as a development version; V17 remains a development version until finalized. The changelog entry for the moved members endpoint is parked in `changelog.d/99-pending/`, which `mk-changelog.sh` and `mk-cleanup.sh` now skip.
1 change: 1 addition & 0 deletions changelog.d/99-pending/WPB-27393
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
V18 `PUT /conversations/{domain}/{conversation}/members` rejects replacements that would leave a regular group without an admin; V17 and older retain the legacy autopromotion behavior. (#5387)
4 changes: 4 additions & 0 deletions changelog.d/mk-changelog.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ get_pr_number() {

for d in "$DIR"/*; do
if [[ ! -d "$d" ]]; then continue; fi
# 99-pending: entries deferred out of the upcoming release. Since renaming a
# file breaks get_pr_number (git log does not follow renames), each parked
# entry must bake its PR number into its text, e.g. " (#1234)".
if [[ "$(basename "$d")" == "99-pending" ]]; then continue; fi

entries=("$d"/*[^~])

Expand Down
6 changes: 5 additions & 1 deletion changelog.d/mk-cleanup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,9 @@ shopt -s nullglob

DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

rm -f "$DIR"/*/*
for d in "$DIR"/*; do
[[ -d "$d" ]] || continue
if [[ "$(basename "$d")" == "99-pending" ]]; then continue; fi
rm -f "$d"/*
done
git add "$DIR"
2 changes: 1 addition & 1 deletion docs/src/developer/reference/config-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -425,7 +425,7 @@ Feature responses, including `GET /feature-configs`, `GET /teams/:tid/features`,

From a client's perspective, API versioning works like this:

- API version V17 and newer should send the duration fields to `PUT /teams/:tid/features/preventAdminlessGroups`.
- API version V18 and newer should send the duration fields to `PUT /teams/:tid/features/preventAdminlessGroups`.
- Feature responses include the duration fields for clients to read.

The lock status for individual teams can be changed via the internal API (`PUT /i/teams/:tid/features/preventAdminlessGroups/(un)?locked`).
Expand Down
16 changes: 8 additions & 8 deletions integration/test/Test/AdminlessGroups.hs
Original file line number Diff line number Diff line change
Expand Up @@ -201,9 +201,9 @@ testAdminlessReplaceMembers = do
members <- resp.json %. "members.others" & asList
shouldBeEmpty members

testVersion 17 $ \alice bob conv version -> do
testVersion 18 $ \alice bob conv version -> do
bobId <- bob %. "qualified_id"
-- V17 rejects a replacement that would remove the last admin while leaving
-- V18 rejects a replacement that would remove the last admin while leaving
-- only eligible non-admin members.
bindResponse (replaceMembers alice conv def {users = [bobId], version = Just version}) $ \resp -> do
resp.status `shouldMatchInt` 403
Expand Down Expand Up @@ -234,9 +234,9 @@ testAdminlessReplaceMembersAddsAdmin = do
bobId <- bob %. "qualified_id"
charlieId <- charlie %. "qualified_id"

-- V17 accepts replacing the existing admin when the same request adds a new
-- V18 accepts replacing the existing admin when the same request adds a new
-- admin, because the resulting conversation is not adminless.
bindResponse (replaceMembers alice conv def {users = [bobId, charlieId], role = Just "wire_admin", version = Just 17}) $ \resp -> do
bindResponse (replaceMembers alice conv def {users = [bobId, charlieId], role = Just "wire_admin", version = Just 18}) $ \resp -> do
resp.status `shouldMatchInt` 200

bindResponse (getConversation charlie conv) $ \resp -> do
Expand All @@ -253,10 +253,10 @@ testAdminlessReplaceMembersAddsEligibleMember = do
conv <- postConversation alice (defProteus {team = Just tid, qualifiedUsers = [], newUsersRole = "wire_member"}) >>= getJSON 201
bobId <- bob %. "qualified_id"

-- V17 rejects a replacement that removes the only admin even when the
-- V18 rejects a replacement that removes the only admin even when the
-- eligible member is added by the same request.
bindResponse
(replaceMembers alice conv def {users = [bobId], role = Just "wire_member", version = Just 17})
(replaceMembers alice conv def {users = [bobId], role = Just "wire_member", version = Just 18})
$ \resp -> do
resp.status `shouldMatchInt` 403
resp.json %. "label" `shouldMatch` "adminless-conversation"
Expand Down Expand Up @@ -315,7 +315,7 @@ testAdminlessSetupMemberUpdateAfterAdminLeaves = do
resp.status `shouldMatchInt` 200

withWebSockets [bob] $ \[wsBob] -> do
setTeamFeatureConfigVersioned (ExplicitVersion 17) alice tid "preventAdminlessGroups" (mkAdminlessFeature "enabled" "10s" []) >>= assertSuccess
setTeamFeatureConfigVersioned (ExplicitVersion 18) alice tid "preventAdminlessGroups" (mkAdminlessFeature "enabled" "10s" []) >>= assertSuccess

notif <- awaitMatchFor 20 isMemberUpdateNotif wsBob
notif %. "payload.0.qualified_conversation" `shouldMatch` objQidObject conv
Expand Down Expand Up @@ -350,7 +350,7 @@ testAdminlessSetupDeletesWithOriginAndRemoteMembers = do
conversationIds `shouldContain` [convQid]

withWebSockets [remoteUser] $ \[wsRemoteUser] -> do
setTeamFeatureConfigVersioned (ExplicitVersion 17) alice tid "preventAdminlessGroups" (mkAdminlessFeature "enabled" "1s" []) >>= assertSuccess
setTeamFeatureConfigVersioned (ExplicitVersion 18) alice tid "preventAdminlessGroups" (mkAdminlessFeature "enabled" "1s" []) >>= assertSuccess

deleteNotif <- awaitMatchFor 20 isConvDeleteNotif wsRemoteUser
deleteNotif %. "payload.0.qualified_from" `shouldMatch` objQidObject alice
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,12 @@ testPreventAdminlessGroupsPutV16AcceptsLegacyTimeoutFields = do
resp.json `shouldMatch` canonicalPreventAdminlessGroupsFeature
checkFeature "preventAdminlessGroups" owner tid canonicalPreventAdminlessGroupsFeature

testPreventAdminlessGroupsPutV17AcceptsDurationTimeoutFields :: (HasCallStack) => App ()
testPreventAdminlessGroupsPutV17AcceptsDurationTimeoutFields = do
testPreventAdminlessGroupsPutV18AcceptsDurationTimeoutFields :: (HasCallStack) => App ()
testPreventAdminlessGroupsPutV18AcceptsDurationTimeoutFields = do
(owner, tid, _) <- createTeam OwnDomain 0
bindResponse
( Public.setTeamFeatureConfigVersioned
(ExplicitVersion 17)
(ExplicitVersion 18)
owner
tid
"preventAdminlessGroups"
Expand Down
2 changes: 1 addition & 1 deletion integration/test/Test/Spar.hs
Original file line number Diff line number Diff line change
Expand Up @@ -1640,7 +1640,7 @@ testScimUserIsNotAllowedToChangeNameOnRegistering = do
resp.json %. "name" `shouldMatch` scimUserDisplayName

let newProfilename = "Takemiya Masaki"
registerUserWith OwnDomain email code newProfilename `bindResponse` \resp -> do
registerUserWithVersioned (ExplicitVersion 18) OwnDomain email code newProfilename `bindResponse` \resp -> do
resp.status `shouldMatchInt` 403
resp.json %. "label" `shouldMatch` "managed-by-scim"

Expand Down
2 changes: 1 addition & 1 deletion integration/test/Test/Swagger.hs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import Testlib.Prelude
import UnliftIO.Temporary

existingVersions :: Set Int
existingVersions = Set.fromList [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]
existingVersions = Set.fromList [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18]

internalApis :: Set String
internalApis = Set.fromList ["brig", "cannon", "cargohold", "cannon", "spar"]
Expand Down
6 changes: 3 additions & 3 deletions libs/wire-api/src/Wire/API/Routes/Features.hs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ module Wire.API.Routes.Features where

import Wire.API.Conversation.Role
import Wire.API.Error.Galley
import Wire.API.Routes.Version (Version (V17))
import Wire.API.Routes.Version (Version (V18))
import Wire.API.Team.Feature

type family FeatureErrors cfg where
Expand All @@ -42,6 +42,6 @@ type family FeatureAPIDesc cfg where
FeatureAPIDesc _ = ""

type family VersionedFeatureAPIDesc v cfg where
VersionedFeatureAPIDesc V17 PreventAdminlessGroupsConfig =
"<p>For API version 17, use duration strings for the timeout fields. The request body must have the following shape:</p><pre>{\n &quot;config&quot;: {\n &quot;deletionTimeoutDuration&quot;: &quot;7d&quot;,\n &quot;promotionStrategy&quot;: &quot;alphabetical&quot;,\n &quot;reminderTimeoutDurations&quot;: [\n &quot;2d&quot;,\n &quot;4d&quot;,\n &quot;6d&quot;\n ]\n },\n &quot;status&quot;: &quot;enabled&quot;\n}</pre><p>Older API versions use the legacy numeric fields <code>deletionTimeout</code> and <code>reminderTimeouts</code>.</p>"
VersionedFeatureAPIDesc V18 PreventAdminlessGroupsConfig =
"<p>For API version 18, use duration strings for the timeout fields. The request body must have the following shape:</p><pre>{\n &quot;config&quot;: {\n &quot;deletionTimeoutDuration&quot;: &quot;7d&quot;,\n &quot;promotionStrategy&quot;: &quot;alphabetical&quot;,\n &quot;reminderTimeoutDurations&quot;: [\n &quot;2d&quot;,\n &quot;4d&quot;,\n &quot;6d&quot;\n ]\n },\n &quot;status&quot;: &quot;enabled&quot;\n}</pre><p>Older API versions use the legacy numeric fields <code>deletionTimeout</code> and <code>reminderTimeouts</code>.</p>"
VersionedFeatureAPIDesc _ cfg = FeatureAPIDesc cfg
4 changes: 2 additions & 2 deletions libs/wire-api/src/Wire/API/Routes/Public/Brig.hs
Original file line number Diff line number Diff line change
Expand Up @@ -669,7 +669,7 @@ type AccountAPI =
Named
"register@v16"
( Summary "Register a new user."
:> Until 'V17
:> Until 'V18
:> Description
"If the environment where the registration takes \
\place is private and a registered email address \
Expand All @@ -682,7 +682,7 @@ type AccountAPI =
:<|> Named
"register"
( Summary "Register a new user."
:> From 'V17
:> From 'V18
:> Description
"If the environment where the registration takes \
\place is private and a registered email address \
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -965,7 +965,7 @@ type ConversationAPI =
\The roles of already existing members will not be changed \
\even if these members are included in the request body and their role differs from the role provided in this request."
:> From 'V13
:> Until 'V17
:> Until 'V18
:> CanThrow ('ActionDenied 'AddConversationMember)
:> CanThrow ('ActionDenied 'RemoveConversationMember)
:> CanThrow ('ActionDenied 'LeaveConversation)
Expand Down Expand Up @@ -996,7 +996,7 @@ type ConversationAPI =
\The given role in the request body will be applied to all added members. \
\The roles of already existing members will not be changed \
\even if these members are included in the request body and their role differs from the role provided in this request."
:> From 'V17
:> From 'V18
:> CanThrow ('ActionDenied 'AddConversationMember)
:> CanThrow ('ActionDenied 'RemoveConversationMember)
:> CanThrow ('ActionDenied 'LeaveConversation)
Expand Down
4 changes: 2 additions & 2 deletions libs/wire-api/src/Wire/API/Routes/Public/Galley/Feature.hs
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@ type FeatureAPI =
:<|> FeatureAPIGet DomainRegistrationConfig
:<|> FeatureAPIGetPut ChannelsConfig
:<|> FeatureAPIGet PreventAdminlessGroupsConfig
:<|> Until 'V17 ::> VersionedFeatureAPIPut "put-PreventAdminlessGroupsConfig@v16" V16 PreventAdminlessGroupsConfig
:<|> From 'V17 ::> VersionedFeatureAPIPut "put-PreventAdminlessGroupsConfig@v17" V17 PreventAdminlessGroupsConfig
:<|> Until 'V18 ::> VersionedFeatureAPIPut "put-PreventAdminlessGroupsConfig@v16" V16 PreventAdminlessGroupsConfig
:<|> From 'V18 ::> VersionedFeatureAPIPut "put-PreventAdminlessGroupsConfig@v18" V18 PreventAdminlessGroupsConfig
:<|> FeatureAPIGet CellsConfig
:<|> Until 'V14 ::> VersionedFeatureAPIPut "put-CellsConfig@v13" V13 CellsConfig
:<|> From 'V14 ::> FeatureAPIPut CellsConfig
Expand Down
6 changes: 5 additions & 1 deletion libs/wire-api/src/Wire/API/Routes/Version.hs
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ import Wire.Arbitrary (Arbitrary, GenericUniform (GenericUniform))
-- and 'developmentVersions' stay in sync; everything else here should keep working without
-- change. See also documentation in the *docs* directory.
-- https://docs.wire.com/developer/developer/api-versioning.html#version-bump-checklist
data Version = V0 | V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | V10 | V11 | V12 | V13 | V14 | V15 | V16 | V17
data Version = V0 | V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | V10 | V11 | V12 | V13 | V14 | V15 | V16 | V17 | V18
deriving stock (Eq, Ord, Bounded, Enum, Show, Generic)
deriving (FromJSON, ToJSON) via (Schema Version)
deriving (Arbitrary) via (GenericUniform Version)
Expand Down Expand Up @@ -142,6 +142,8 @@ instance RenderableSymbol V16 where renderSymbol = "V16"

instance RenderableSymbol V17 where renderSymbol = "V17"

instance RenderableSymbol V18 where renderSymbol = "V18"

-- | Manual enumeration of version integrals (the `<n>` in the constructor `V<n>`).
--
-- This is not the same as 'fromEnum': we will remove unsupported versions in the future,
Expand All @@ -167,6 +169,7 @@ versionInt V14 = 14
versionInt V15 = 15
versionInt V16 = 16
versionInt V17 = 17
versionInt V18 = 18

supportedVersions :: [Version]
supportedVersions = [minBound .. maxBound]
Expand Down Expand Up @@ -292,6 +295,7 @@ isDevelopmentVersion V14 = False
isDevelopmentVersion V15 = False
isDevelopmentVersion V16 = False
isDevelopmentVersion V17 = True
isDevelopmentVersion V18 = True

developmentVersions :: [Version]
developmentVersions = filter isDevelopmentVersion supportedVersions
Expand Down
4 changes: 2 additions & 2 deletions libs/wire-api/src/Wire/API/Team/Feature.hs
Original file line number Diff line number Diff line change
Expand Up @@ -1436,13 +1436,13 @@ instance ToSchema (Versioned V16 PreventAdminlessGroupsConfig) where
instance ToObjectSchema (Versioned V16 PreventAdminlessGroupsConfig) where

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this become V17 then?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice catch, updated!

objectSchema = field "config" schema

instance ToSchema (Versioned V17 PreventAdminlessGroupsConfig) where
instance ToSchema (Versioned V18 PreventAdminlessGroupsConfig) where
schema =
object $
Versioned
<$> unVersioned .= durationPreventAdminlessGroupsConfigObjectSchema

instance ToObjectSchema (Versioned V17 PreventAdminlessGroupsConfig) where
instance ToObjectSchema (Versioned V18 PreventAdminlessGroupsConfig) where
objectSchema = field "config" schema

oldPreventAdminlessGroupsConfigObjectSchema :: ObjectSchema SwaggerDoc PreventAdminlessGroupsConfig
Expand Down
16 changes: 16 additions & 0 deletions services/brig/src/Brig/API/Public.hs
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,22 @@ internalEndpointsSwaggerDocsAPIs =
--
-- Dual to `internalEndpointsSwaggerDocsAPI`.
versionedSwaggerDocsAPI :: Servant.Server VersionedSwaggerDocsAPI
versionedSwaggerDocsAPI (Just (VersionNumber V18)) =
swaggerSchemaUIServer $
( serviceSwagger @VersionAPITag @'V18
<> serviceSwagger @BrigAPITag @'V18
<> serviceSwagger @GalleyAPITag @'V18
<> serviceSwagger @SparAPITag @'V18
<> serviceSwagger @CargoholdAPITag @'V18
<> serviceSwagger @CannonAPITag @'V18
<> serviceSwagger @GundeckAPITag @'V18
<> serviceSwagger @ProxyAPITag @'V18
<> serviceSwagger @OAuthAPITag @'V18
)
& S.info . S.title .~ "Wire-Server API"
& S.info . S.description ?~ $((unTypeCode . embedText) =<< makeRelativeToProject "docs/swagger.md")
& S.servers .~ [S.Server ("/" <> toUrlPiece V18) Nothing mempty]
& cleanupSwagger
versionedSwaggerDocsAPI (Just (VersionNumber V17)) =
swaggerSchemaUIServer $
devVersionSwagger
Expand Down
2 changes: 1 addition & 1 deletion services/galley/src/Galley/API/Public/Feature.hs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ featureAPI =
<@> featureAPIGetPut
<@> mkNamedAPI @'("get", PreventAdminlessGroupsConfig) getFeature
<@> mkNamedAPI @"put-PreventAdminlessGroupsConfig@v16" setFeature
<@> mkNamedAPI @"put-PreventAdminlessGroupsConfig@v17" setFeature
<@> mkNamedAPI @"put-PreventAdminlessGroupsConfig@v18" setFeature
<@> mkNamedAPI @'("get", CellsConfig) getFeature
<@> mkNamedAPI @"put-CellsConfig@v13" setFeature
<@> mkNamedAPI @'("put", CellsConfig) setFeature
Expand Down
Loading