Skip to content

Commit 733bb88

Browse files
committed
login: Reject earlier (at get-server-settings) when server is too old
There's a planned change to make zulipMergeBase required in GetServerSettingsResult (see #904). When we do that, we'd otherwise just treat a missing zulipMergeBase on an ancient server the same way we treat any malformed response. Better to check if the server is an ancient one we don't support, just like when the /register response is malformed.
1 parent 06f1b87 commit 733bb88

File tree

3 files changed

+92
-4
lines changed

3 files changed

+92
-4
lines changed

lib/model/server_support.dart

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,24 @@
11
import '../api/core.dart';
22
import '../api/exception.dart';
33
import '../api/model/initial_snapshot.dart';
4+
import '../api/route/realm.dart';
45
import 'database.dart';
56

67
/// The fields 'zulip_version', 'zulip_merge_base', and 'zulip_feature_level'
7-
/// from a /register response.
8+
/// from a /server_settings or /register response.
89
class ZulipVersionData {
910
const ZulipVersionData({
1011
required this.zulipVersion,
1112
required this.zulipMergeBase,
1213
required this.zulipFeatureLevel,
1314
});
1415

16+
factory ZulipVersionData.fromServerSettings(GetServerSettingsResult serverSettings) =>
17+
ZulipVersionData(
18+
zulipVersion: serverSettings.zulipVersion,
19+
zulipMergeBase: serverSettings.zulipMergeBase,
20+
zulipFeatureLevel: serverSettings.zulipFeatureLevel);
21+
1522
factory ZulipVersionData.fromInitialSnapshot(InitialSnapshot initialSnapshot) =>
1623
ZulipVersionData(
1724
zulipVersion: initialSnapshot.zulipVersion,
@@ -21,6 +28,8 @@ class ZulipVersionData {
2128
/// Make a [ZulipVersionData] from a [MalformedServerResponseException],
2229
/// if the body was readable/valid JSON and contained the data, else null.
2330
///
31+
/// May be used for the /server_settings or the /register response.
32+
///
2433
/// If there's a zulip_version but no zulip_feature_level,
2534
/// we infer it's indeed a Zulip server,
2635
/// just an ancient one before feature levels were introduced in Zulip 3.0,

lib/widgets/login.dart

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import 'package:flutter/material.dart';
55
import 'package:flutter/services.dart';
66
import 'package:url_launcher/url_launcher.dart';
77

8+
import '../api/core.dart';
89
import '../api/exception.dart';
910
import '../api/model/web_auth.dart';
1011
import '../api/route/account.dart';
@@ -13,6 +14,7 @@ import '../api/route/users.dart';
1314
import '../generated/l10n/zulip_localizations.dart';
1415
import '../log.dart';
1516
import '../model/binding.dart';
17+
import '../model/server_support.dart';
1618
import '../model/store.dart';
1719
import 'dialog.dart';
1820
import 'home.dart';
@@ -180,17 +182,40 @@ class _AddAccountPageState extends State<AddAccountPage> {
180182
final connection = globalStore.apiConnection(realmUrl: url!, zulipFeatureLevel: null);
181183
try {
182184
serverSettings = await getServerSettings(connection);
185+
final zulipVersionData = ZulipVersionData.fromServerSettings(serverSettings);
186+
if (zulipVersionData.isUnsupported) {
187+
throw ServerVersionUnsupportedException(zulipVersionData);
188+
}
189+
} on MalformedServerResponseException catch (e) {
190+
final zulipVersionData = ZulipVersionData.fromMalformedServerResponseException(e);
191+
if (zulipVersionData != null && zulipVersionData.isUnsupported) {
192+
throw ServerVersionUnsupportedException(zulipVersionData);
193+
}
194+
rethrow;
183195
} finally {
184196
connection.close();
185197
}
186198
} catch (e) {
187199
if (!context.mounted) return;
188200

189-
// TODO(#105) give more helpful feedback; see `fetchServerSettings`
190-
// in zulip-mobile's src/message/fetchActions.js.
201+
String? message;
202+
Uri? learnMoreButtonUrl;
203+
switch (e) {
204+
case ServerVersionUnsupportedException(:final data):
205+
message = zulipLocalizations.errorServerVersionUnsupportedMessage(
206+
url.toString(),
207+
data.zulipVersion,
208+
kMinSupportedZulipVersion);
209+
learnMoreButtonUrl = kServerSupportDocUrl;
210+
default:
211+
// TODO(#105) give more helpful feedback; see `fetchServerSettings`
212+
// in zulip-mobile's src/message/fetchActions.js.
213+
message = zulipLocalizations.errorLoginCouldNotConnect(url.toString());
214+
}
191215
showErrorDialog(context: context,
192216
title: zulipLocalizations.errorCouldNotConnectTitle,
193-
message: zulipLocalizations.errorLoginCouldNotConnect(url.toString()));
217+
message: message,
218+
learnMoreButtonUrl: learnMoreButtonUrl);
194219
return;
195220
}
196221
if (!context.mounted) return;

test/widgets/login_test.dart

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import 'package:flutter/material.dart';
55
import 'package:flutter/services.dart';
66
import 'package:flutter_test/flutter_test.dart';
77
import 'package:http/http.dart' as http;
8+
import 'package:zulip/api/core.dart';
89
import 'package:zulip/api/model/web_auth.dart';
910
import 'package:zulip/api/route/account.dart';
1011
import 'package:zulip/api/route/realm.dart';
@@ -120,6 +121,59 @@ void main() {
120121
check(takePushedRoutes()).single.isA<WidgetRoute>().page.isA<LoginPage>()
121122
.serverSettings.realmUrl.equals(serverSettings.realmUrl);
122123
});
124+
125+
testWidgets('Server too old, well-formed response', (tester) async {
126+
await prepare(tester);
127+
128+
final serverSettings = eg.serverSettings(
129+
zulipFeatureLevel: 1, zulipVersion: '3.0');
130+
131+
await attempt(tester, serverSettings.realmUrl, serverSettings.toJson());
132+
checkErrorDialog(tester,
133+
expectedTitle: 'Could not connect',
134+
expectedMessage: '${serverSettings.realmUrl} is running Zulip Server 3.0, which is unsupported. The minimum supported version is Zulip Server $kMinSupportedZulipVersion.');
135+
// i.e., not the login route
136+
check(takePushedRoutes()).single.isA<DialogRoute<void>>();
137+
});
138+
139+
testWidgets('Server too old, malformed response', (tester) async {
140+
await prepare(tester);
141+
142+
final serverSettings = eg.serverSettings(
143+
zulipFeatureLevel: 1, zulipVersion: '3.0');
144+
final serverSettingsMalformedJson =
145+
serverSettings.toJson()..['push_notifications_enabled'] = 'abcd';
146+
check(() => GetServerSettingsResult.fromJson(serverSettingsMalformedJson))
147+
.throws<void>();
148+
149+
await attempt(tester, serverSettings.realmUrl, serverSettingsMalformedJson);
150+
checkErrorDialog(tester,
151+
expectedTitle: 'Could not connect',
152+
expectedMessage: '${serverSettings.realmUrl} is running Zulip Server 3.0, which is unsupported. The minimum supported version is Zulip Server $kMinSupportedZulipVersion.');
153+
// i.e., not the login route
154+
check(takePushedRoutes()).single.isA<DialogRoute<void>>();
155+
});
156+
157+
testWidgets('Malformed response, server not too old', (tester) async {
158+
await prepare(tester);
159+
160+
final serverSettings = eg.serverSettings(
161+
zulipVersion: eg.recentZulipVersion,
162+
zulipFeatureLevel: eg.recentZulipFeatureLevel);
163+
final serverSettingsMalformedJson =
164+
serverSettings.toJson()..['push_notifications_enabled'] = 'abcd';
165+
check(() => GetServerSettingsResult.fromJson(serverSettingsMalformedJson))
166+
.throws<void>();
167+
168+
await attempt(tester, serverSettings.realmUrl, serverSettingsMalformedJson);
169+
checkErrorDialog(tester,
170+
expectedTitle: 'Could not connect',
171+
expectedMessage: 'Failed to connect to server:\n${serverSettings.realmUrl}');
172+
// i.e., not the login route
173+
check(takePushedRoutes()).single.isA<DialogRoute<void>>();
174+
});
175+
176+
// TODO other errors
123177
});
124178

125179
group('LoginPage', () {

0 commit comments

Comments
 (0)