refactor: Follow up clean up bootstrap

This commit is contained in:
Christian Pauly 2020-12-10 12:49:00 +01:00
parent b563aec7bb
commit 6657e073a0
17 changed files with 783 additions and 106 deletions

View File

@ -29,19 +29,44 @@ import '../../famedlysdk.dart';
import '../../matrix_api/utils/logs.dart'; import '../../matrix_api/utils/logs.dart';
enum BootstrapState { enum BootstrapState {
loading, // loading /// Is loading.
askWipeSsss, // existing SSSS found, should we wipe it? loading,
askUseExistingSsss, // ask if an existing SSSS should be userDeviceKeys
askBadSsss, // SSSS is in a bad state, continue with potential dataloss? /// Existing SSSS found, should we wipe it?
askUnlockSsss, // Ask to unlock all the SSSS keys askWipeSsss,
askNewSsss, // Ask for new SSSS key / passphrase
openExistingSsss, // Open an existing SSSS key /// Ask if an existing SSSS should be userDeviceKeys
askWipeCrossSigning, // Ask if cross signing should be wiped askUseExistingSsss,
askSetupCrossSigning, // Ask if cross signing should be set up
askWipeOnlineKeyBackup, // Ask if online key backup should be wiped /// Ask to unlock all the SSSS keys
askSetupOnlineKeyBackup, // Ask if the online key backup should be set up askUnlockSsss,
error, // error
done, // done /// SSSS is in a bad state, continue with potential dataloss?
askBadSsss,
/// Ask for new SSSS key / passphrase
askNewSsss,
/// Open an existing SSSS key
openExistingSsss,
/// Ask if cross signing should be wiped
askWipeCrossSigning,
/// Ask if cross signing should be set up
askSetupCrossSigning,
/// Ask if online key backup should be wiped
askWipeOnlineKeyBackup,
/// Ask if the online key backup should be set up
askSetupOnlineKeyBackup,
/// An error has been occured.
error,
/// done
done,
} }
/// Bootstrapping SSSS and cross-signing /// Bootstrapping SSSS and cross-signing
@ -425,8 +450,9 @@ class Bootstrap {
} }
try { try {
// upload the keys! // upload the keys!
state = BootstrapState.loading;
await client.uiaRequestBackground( await client.uiaRequestBackground(
(Map<String, dynamic> auth) => client.uploadDeviceSigningKeys( (AuthenticationData auth) => client.uploadDeviceSigningKeys(
masterKey: masterKey, masterKey: masterKey,
selfSigningKey: selfSigningKey, selfSigningKey: selfSigningKey,
userSigningKey: userSigningKey, userSigningKey: userSigningKey,

View File

@ -66,6 +66,16 @@ export 'matrix_api/model/upload_key_signatures_response.dart';
export 'matrix_api/model/user_search_result.dart'; export 'matrix_api/model/user_search_result.dart';
export 'matrix_api/model/well_known_informations.dart'; export 'matrix_api/model/well_known_informations.dart';
export 'matrix_api/model/who_is_info.dart'; export 'matrix_api/model/who_is_info.dart';
export 'matrix_api/model/auth/authentication_data.dart';
export 'matrix_api/model/auth/authentication_identifier.dart';
export 'matrix_api/model/auth/authentication_password.dart';
export 'matrix_api/model/auth/authentication_phone_identifier.dart';
export 'matrix_api/model/auth/authentication_recaptcha.dart';
export 'matrix_api/model/auth/authentication_third_party_identifier.dart';
export 'matrix_api/model/auth/authentication_three_pid_creds.dart';
export 'matrix_api/model/auth/authentication_token.dart';
export 'matrix_api/model/auth/authentication_types.dart';
export 'matrix_api/model/auth/authentication_user_identifier.dart';
export 'matrix_api/model/events/secret_storage_default_key_content.dart'; export 'matrix_api/model/events/secret_storage_default_key_content.dart';
export 'matrix_api/model/events/secret_storage_key_content.dart'; export 'matrix_api/model/events/secret_storage_key_content.dart';
export 'matrix_api/model/events/tombstone_content.dart'; export 'matrix_api/model/events/tombstone_content.dart';

View File

@ -19,10 +19,12 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'package:famedlysdk/famedlysdk.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:mime/mime.dart'; import 'package:mime/mime.dart';
import 'package:moor/moor.dart'; import 'package:moor/moor.dart';
import 'model/auth/authentication_types.dart';
import 'model/device.dart'; import 'model/device.dart';
import 'model/event_context.dart'; import 'model/event_context.dart';
import 'model/events_sync_update.dart'; import 'model/events_sync_update.dart';
@ -247,7 +249,7 @@ class MatrixApi {
/// Authenticates the user, and issues an access token they can use to authorize themself in subsequent requests. /// Authenticates the user, and issues an access token they can use to authorize themself in subsequent requests.
/// https://matrix.org/docs/spec/client_server/r0.6.0#post-matrix-client-r0-login /// https://matrix.org/docs/spec/client_server/r0.6.0#post-matrix-client-r0-login
Future<LoginResponse> login({ Future<LoginResponse> login({
String type = 'm.login.password', String type = AuthenticationTypes.password,
String userIdentifierType, String userIdentifierType,
String user, String user,
String medium, String medium,
@ -256,7 +258,7 @@ class MatrixApi {
String token, String token,
String deviceId, String deviceId,
String initialDeviceDisplayName, String initialDeviceDisplayName,
Map<String, dynamic> auth, AuthenticationData auth,
}) async { }) async {
final response = await request(RequestType.POST, '/client/r0/login', data: { final response = await request(RequestType.POST, '/client/r0/login', data: {
'type': type, 'type': type,
@ -272,7 +274,7 @@ class MatrixApi {
if (deviceId != null) 'device_id': deviceId, if (deviceId != null) 'device_id': deviceId,
if (initialDeviceDisplayName != null) if (initialDeviceDisplayName != null)
'initial_device_display_name': initialDeviceDisplayName, 'initial_device_display_name': initialDeviceDisplayName,
if (auth != null) 'auth': auth, if (auth != null) 'auth': auth.toJson(),
}); });
return LoginResponse.fromJson(response); return LoginResponse.fromJson(response);
} }
@ -302,13 +304,25 @@ class MatrixApi {
return; return;
} }
/// Register for an account on this homeserver.
///
/// There are two kinds of user account:
///
/// user accounts. These accounts may use the full API described in this
/// specification.
/// guest accounts. These accounts may have limited permissions and may not
/// be supported by all servers.
///
/// If registration is successful, this endpoint will issue an access token
/// the client can use to authorize itself in subsequent requests.
/// https://matrix.org/docs/spec/client_server/r0.6.0#post-matrix-client-r0-register
Future<LoginResponse> register({ Future<LoginResponse> register({
String username, String username,
String password, String password,
String deviceId, String deviceId,
String initialDeviceDisplayName, String initialDeviceDisplayName,
bool inhibitLogin, bool inhibitLogin,
Map<String, dynamic> auth, AuthenticationData auth,
String kind, String kind,
}) async { }) async {
var action = '/client/r0/register'; var action = '/client/r0/register';
@ -320,7 +334,7 @@ class MatrixApi {
if (initialDeviceDisplayName != null) if (initialDeviceDisplayName != null)
'initial_device_display_name': initialDeviceDisplayName, 'initial_device_display_name': initialDeviceDisplayName,
if (inhibitLogin != null) 'inhibit_login': inhibitLogin, if (inhibitLogin != null) 'inhibit_login': inhibitLogin,
if (auth != null) 'auth': auth, if (auth != null) 'auth': auth.toJson(),
}); });
return LoginResponse.fromJson(response); return LoginResponse.fromJson(response);
} }
@ -382,11 +396,11 @@ class MatrixApi {
/// https://matrix.org/docs/spec/client_server/r0.6.0#post-matrix-client-r0-account-password /// https://matrix.org/docs/spec/client_server/r0.6.0#post-matrix-client-r0-account-password
Future<void> changePassword( Future<void> changePassword(
String newPassword, { String newPassword, {
Map<String, dynamic> auth, AuthenticationData auth,
}) async { }) async {
await request(RequestType.POST, '/client/r0/account/password', data: { await request(RequestType.POST, '/client/r0/account/password', data: {
'new_password': newPassword, 'new_password': newPassword,
if (auth != null) 'auth': auth, if (auth != null) 'auth': auth.toJson(),
}); });
return; return;
} }
@ -443,14 +457,15 @@ class MatrixApi {
return RequestTokenResponse.fromJson(response); return RequestTokenResponse.fromJson(response);
} }
/// https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-account-deactivate
Future<IdServerUnbindResult> deactivateAccount({ Future<IdServerUnbindResult> deactivateAccount({
String idServer, String idServer,
Map<String, dynamic> auth, AuthenticationData auth,
}) async { }) async {
final response = final response =
await request(RequestType.POST, '/client/r0/account/deactivate', data: { await request(RequestType.POST, '/client/r0/account/deactivate', data: {
if (idServer != null) 'id_server': idServer, if (idServer != null) 'id_server': idServer,
if (auth != null) 'auth': auth, if (auth != null) 'auth': auth.toJson(),
}); });
return IdServerUnbindResult.values.firstWhere( return IdServerUnbindResult.values.firstWhere(
@ -486,12 +501,12 @@ class MatrixApi {
Future<void> addThirdPartyIdentifier( Future<void> addThirdPartyIdentifier(
String clientSecret, String clientSecret,
String sid, { String sid, {
Map<String, dynamic> auth, AuthenticationData auth,
}) async { }) async {
await request(RequestType.POST, '/client/r0/account/3pid/add', data: { await request(RequestType.POST, '/client/r0/account/3pid/add', data: {
'sid': sid, 'sid': sid,
'client_secret': clientSecret, 'client_secret': clientSecret,
if (auth != null && auth.isNotEmpty) 'auth': auth, if (auth != null) 'auth': auth.toJson(),
}); });
return; return;
} }
@ -1381,12 +1396,11 @@ class MatrixApi {
/// Deletes the given device, and invalidates any access token associated with it. /// Deletes the given device, and invalidates any access token associated with it.
/// https://matrix.org/docs/spec/client_server/r0.6.1#delete-matrix-client-r0-devices-deviceid /// https://matrix.org/docs/spec/client_server/r0.6.1#delete-matrix-client-r0-devices-deviceid
Future<void> deleteDevice(String deviceId, Future<void> deleteDevice(String deviceId, {AuthenticationData auth}) async {
{Map<String, dynamic> auth}) async {
await request(RequestType.DELETE, await request(RequestType.DELETE,
'/client/r0/devices/${Uri.encodeComponent(deviceId)}', '/client/r0/devices/${Uri.encodeComponent(deviceId)}',
data: { data: {
if (auth != null) 'auth': auth, if (auth != null) 'auth': auth.toJson(),
}); });
return; return;
} }
@ -1394,10 +1408,10 @@ class MatrixApi {
/// Deletes the given devices, and invalidates any access token associated with them. /// Deletes the given devices, and invalidates any access token associated with them.
/// https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-delete-devices /// https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-delete-devices
Future<void> deleteDevices(List<String> deviceIds, Future<void> deleteDevices(List<String> deviceIds,
{Map<String, dynamic> auth}) async { {AuthenticationData auth}) async {
await request(RequestType.POST, '/client/r0/delete_devices', data: { await request(RequestType.POST, '/client/r0/delete_devices', data: {
'devices': deviceIds, 'devices': deviceIds,
if (auth != null) 'auth': auth, if (auth != null) 'auth': auth.toJson(),
}); });
return; return;
} }
@ -1470,7 +1484,7 @@ class MatrixApi {
MatrixCrossSigningKey masterKey, MatrixCrossSigningKey masterKey,
MatrixCrossSigningKey selfSigningKey, MatrixCrossSigningKey selfSigningKey,
MatrixCrossSigningKey userSigningKey, MatrixCrossSigningKey userSigningKey,
Map<String, dynamic> auth, AuthenticationData auth,
}) async { }) async {
await request( await request(
RequestType.POST, RequestType.POST,
@ -1479,7 +1493,7 @@ class MatrixApi {
if (masterKey != null) 'master_key': masterKey.toJson(), if (masterKey != null) 'master_key': masterKey.toJson(),
if (selfSigningKey != null) 'self_signing_key': selfSigningKey.toJson(), if (selfSigningKey != null) 'self_signing_key': selfSigningKey.toJson(),
if (userSigningKey != null) 'user_signing_key': userSigningKey.toJson(), if (userSigningKey != null) 'user_signing_key': userSigningKey.toJson(),
if (auth != null) 'auth': auth, if (auth != null) 'auth': auth.toJson(),
}, },
); );
} }

View File

@ -0,0 +1,36 @@
/*
* Famedly Matrix SDK
* Copyright (C) 2019, 2020 Famedly 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 <https://www.gnu.org/licenses/>.
*/
class AuthenticationData {
String type;
String session;
AuthenticationData({this.type, this.session});
AuthenticationData.fromJson(Map<String, dynamic> json) {
type = json['type'];
session = json['session'];
}
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
data['type'] = type;
data['session'] = session;
return data;
}
}

View File

@ -0,0 +1,33 @@
/*
* Famedly Matrix SDK
* Copyright (C) 2019, 2020 Famedly 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 <https://www.gnu.org/licenses/>.
*/
class AuthenticationIdentifier {
String type;
AuthenticationIdentifier({this.type});
AuthenticationIdentifier.fromJson(Map<String, dynamic> json) {
type = json['type'];
}
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
data['type'] = type;
return data;
}
}

View File

@ -0,0 +1,85 @@
/*
* Famedly Matrix SDK
* Copyright (C) 2019, 2020 Famedly 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 <https://www.gnu.org/licenses/>.
*/
import 'package:famedlysdk/matrix_api/model/auth/authentication_user_identifier.dart';
import 'authentication_data.dart';
import 'authentication_identifier.dart';
import 'authentication_phone_identifier.dart';
import 'authentication_third_party_identifier.dart';
import 'authentication_types.dart';
class AuthenticationPassword extends AuthenticationData {
String user;
String password;
/// You may want to cast this as [AuthenticationUserIdentifier] or other
/// Identifier classes extending AuthenticationIdentifier.
AuthenticationIdentifier identifier;
AuthenticationPassword(
{String session, this.password, this.user, this.identifier})
: super(
type: AuthenticationTypes.password,
session: session,
);
AuthenticationPassword.fromJson(Map<String, dynamic> json)
: super.fromJson(json) {
user = json['user'];
password = json['password'];
identifier = AuthenticationIdentifier.fromJson(json['identifier']);
switch (identifier.type) {
case AuthenticationIdentifierTypes.userId:
identifier = AuthenticationUserIdentifier.fromJson(json['identifier']);
break;
case AuthenticationIdentifierTypes.phone:
identifier = AuthenticationPhoneIdentifier.fromJson(json['identifier']);
break;
case AuthenticationIdentifierTypes.thirdParty:
identifier =
AuthenticationThirdPartyIdentifier.fromJson(json['identifier']);
break;
}
}
@override
Map<String, dynamic> toJson() {
final data = super.toJson();
if (user != null) data['user'] = user;
data['password'] = password;
switch (identifier.type) {
case AuthenticationIdentifierTypes.userId:
data['identifier'] =
(identifier as AuthenticationUserIdentifier).toJson();
break;
case AuthenticationIdentifierTypes.phone:
data['identifier'] =
(identifier as AuthenticationPhoneIdentifier).toJson();
break;
case AuthenticationIdentifierTypes.thirdParty:
data['identifier'] =
(identifier as AuthenticationThirdPartyIdentifier).toJson();
break;
default:
data['identifier'] = identifier.toJson();
break;
}
return data;
}
}

View File

@ -0,0 +1,42 @@
/*
* Famedly Matrix SDK
* Copyright (C) 2019, 2020 Famedly 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 <https://www.gnu.org/licenses/>.
*/
import 'authentication_identifier.dart';
import 'authentication_types.dart';
class AuthenticationPhoneIdentifier extends AuthenticationIdentifier {
String country;
String phone;
AuthenticationPhoneIdentifier({this.country, this.phone})
: super(type: AuthenticationIdentifierTypes.phone);
AuthenticationPhoneIdentifier.fromJson(Map<String, dynamic> json)
: super.fromJson(json) {
country = json['country'];
phone = json['phone'];
}
@override
Map<String, dynamic> toJson() {
final data = super.toJson();
data['country'] = country;
data['phone'] = phone;
return data;
}
}

View File

@ -0,0 +1,42 @@
/*
* Famedly Matrix SDK
* Copyright (C) 2019, 2020 Famedly 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 <https://www.gnu.org/licenses/>.
*/
import 'authentication_data.dart';
import 'authentication_types.dart';
class AuthenticationRecaptcha extends AuthenticationData {
String response;
AuthenticationRecaptcha({String session, this.response})
: super(
type: AuthenticationTypes.recaptcha,
session: session,
);
AuthenticationRecaptcha.fromJson(Map<String, dynamic> json)
: super.fromJson(json) {
response = json['response'];
}
@override
Map<String, dynamic> toJson() {
final data = super.toJson();
data['response'] = response;
return data;
}
}

View File

@ -0,0 +1,42 @@
/*
* Famedly Matrix SDK
* Copyright (C) 2019, 2020 Famedly 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 <https://www.gnu.org/licenses/>.
*/
import 'authentication_identifier.dart';
import 'authentication_types.dart';
class AuthenticationThirdPartyIdentifier extends AuthenticationIdentifier {
String medium;
String address;
AuthenticationThirdPartyIdentifier({this.medium, this.address})
: super(type: AuthenticationIdentifierTypes.thirdParty);
AuthenticationThirdPartyIdentifier.fromJson(Map<String, dynamic> json)
: super.fromJson(json) {
medium = json['medium'];
address = json['address'];
}
@override
Map<String, dynamic> toJson() {
final data = super.toJson();
data['medium'] = medium;
data['address'] = address;
return data;
}
}

View File

@ -0,0 +1,85 @@
/*
* Famedly Matrix SDK
* Copyright (C) 2019, 2020 Famedly 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 <https://www.gnu.org/licenses/>.
*/
import 'authentication_data.dart';
/// For email based identity:
/// https://matrix.org/docs/spec/client_server/r0.6.1#email-based-identity-homeserver
/// Or phone number based identity:
/// https://matrix.org/docs/spec/client_server/r0.6.1#phone-number-msisdn-based-identity-homeserver
class AuthenticationThreePidCreds extends AuthenticationData {
List<ThreepidCreds> threepidCreds;
AuthenticationThreePidCreds({String session, String type, this.threepidCreds})
: super(
type: type,
session: session,
);
AuthenticationThreePidCreds.fromJson(Map<String, dynamic> json)
: super.fromJson(json) {
if (json['threepidCreds'] != null) {
threepidCreds = (json['threepidCreds'] as List)
.map((item) => ThreepidCreds.fromJson(item))
.toList();
}
// This is so extremly stupid... kill it with fire!
if (json['threepid_creds'] != null) {
threepidCreds = (json['threepid_creds'] as List)
.map((item) => ThreepidCreds.fromJson(item))
.toList();
}
}
@override
Map<String, dynamic> toJson() {
final data = super.toJson();
data['threepidCreds'] = threepidCreds.map((t) => t.toJson()).toList();
// Help me! I'm prisoned in a developer factory against my will,
// where we are forced to work with json like this!!
data['threepid_creds'] = threepidCreds.map((t) => t.toJson()).toList();
return data;
}
}
class ThreepidCreds {
String sid;
String clientSecret;
String idServer;
String idAccessToken;
ThreepidCreds(
{this.sid, this.clientSecret, this.idServer, this.idAccessToken});
ThreepidCreds.fromJson(Map<String, dynamic> json) {
sid = json['sid'];
clientSecret = json['client_secret'];
idServer = json['id_server'];
idAccessToken = json['id_access_token'];
}
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
data['sid'] = sid;
data['client_secret'] = clientSecret;
data['id_server'] = idServer;
data['id_access_token'] = idAccessToken;
return data;
}
}

View File

@ -0,0 +1,45 @@
/*
* Famedly Matrix SDK
* Copyright (C) 2019, 2020 Famedly 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 <https://www.gnu.org/licenses/>.
*/
import 'authentication_data.dart';
import 'authentication_types.dart';
class AuthenticationToken extends AuthenticationData {
String token;
String txnId;
AuthenticationToken({String session, this.token, this.txnId})
: super(
type: AuthenticationTypes.token,
session: session,
);
AuthenticationToken.fromJson(Map<String, dynamic> json)
: super.fromJson(json) {
token = json['token'];
txnId = json['txn_id'];
}
@override
Map<String, dynamic> toJson() {
final data = super.toJson();
data['token'] = token;
data['txn_id'] = txnId;
return data;
}
}

View File

@ -0,0 +1,34 @@
/*
* Famedly Matrix SDK
* Copyright (C) 2019, 2020 Famedly 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 <https://www.gnu.org/licenses/>.
*/
abstract class AuthenticationTypes {
static const String password = 'm.login.password';
static const String recaptcha = 'm.login.recaptcha';
static const String token = 'm.login.token';
static const String oauth2 = 'm.login.oauth2';
static const String sso = 'm.login.sso';
static const String emailIdentity = 'm.login.email.identity';
static const String msisdn = 'm.login.msisdn';
static const String dummy = 'm.login.dummy';
}
abstract class AuthenticationIdentifierTypes {
static const String userId = 'm.id.user';
static const String thirdParty = 'm.id.thirdparty';
static const String phone = 'm.id.phone';
}

View File

@ -0,0 +1,39 @@
/*
* Famedly Matrix SDK
* Copyright (C) 2019, 2020 Famedly 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 <https://www.gnu.org/licenses/>.
*/
import 'authentication_identifier.dart';
import 'authentication_types.dart';
class AuthenticationUserIdentifier extends AuthenticationIdentifier {
String user;
AuthenticationUserIdentifier({this.user})
: super(type: AuthenticationIdentifierTypes.userId);
AuthenticationUserIdentifier.fromJson(Map<String, dynamic> json)
: super.fromJson(json) {
user = json['user'];
}
@override
Map<String, dynamic> toJson() {
final data = super.toJson();
data['user'] = user;
return data;
}
}

View File

@ -309,7 +309,7 @@ class Client extends MatrixApi {
String deviceId, String deviceId,
String initialDeviceDisplayName, String initialDeviceDisplayName,
bool inhibitLogin, bool inhibitLogin,
Map<String, dynamic> auth, AuthenticationData auth,
String kind, String kind,
}) async { }) async {
final response = await super.register( final response = await super.register(
@ -342,8 +342,8 @@ class Client extends MatrixApi {
/// You have to call [checkHomeserver] first to set a homeserver. /// You have to call [checkHomeserver] first to set a homeserver.
@override @override
Future<LoginResponse> login({ Future<LoginResponse> login({
String type = 'm.login.password', String type = AuthenticationTypes.password,
String userIdentifierType = 'm.id.user', String userIdentifierType = AuthenticationIdentifierTypes.userId,
String user, String user,
String medium, String medium,
String address, String address,
@ -351,7 +351,7 @@ class Client extends MatrixApi {
String token, String token,
String deviceId, String deviceId,
String initialDeviceDisplayName, String initialDeviceDisplayName,
Map<String, dynamic> auth, AuthenticationData auth,
}) async { }) async {
final loginResp = await super.login( final loginResp = await super.login(
type: type, type: type,
@ -412,15 +412,15 @@ class Client extends MatrixApi {
/// Run any request and react on user interactive authentication flows here. /// Run any request and react on user interactive authentication flows here.
Future<T> uiaRequestBackground<T>( Future<T> uiaRequestBackground<T>(
Future<T> Function(Map<String, dynamic> auth) request) { Future<T> Function(AuthenticationData auth) request) {
final completer = Completer<T>(); final completer = Completer<T>();
UiaRequest uia; UiaRequest uia;
uia = UiaRequest( uia = UiaRequest(
request: request, request: request,
onDone: () { onUpdate: (state) {
if (uia.done) { if (state == UiaRequestState.done) {
completer.complete(uia.result); completer.complete(uia.result);
} else if (uia.fail) { } else if (state == UiaRequestState.fail) {
completer.completeError(uia.error); completer.completeError(uia.error);
} }
}, },
@ -538,7 +538,7 @@ class Client extends MatrixApi {
: null; : null;
static const Set<String> supportedVersions = {'r0.5.0', 'r0.6.0'}; static const Set<String> supportedVersions = {'r0.5.0', 'r0.6.0'};
static const Set<String> supportedLoginTypes = {'m.login.password'}; static const Set<String> supportedLoginTypes = {AuthenticationTypes.password};
static const String syncFilters = static const String syncFilters =
'{"room":{"state":{"lazy_load_members":true}}}'; '{"room":{"state":{"lazy_load_members":true}}}';
static const String messagesFilters = '{"lazy_load_members":true}'; static const String messagesFilters = '{"lazy_load_members":true}';
@ -1603,14 +1603,14 @@ sort order of ${prevState.sortOrder}. This should never happen...''');
/// Changes the password. You should either set oldPasswort or another authentication flow. /// Changes the password. You should either set oldPasswort or another authentication flow.
@override @override
Future<void> changePassword(String newPassword, Future<void> changePassword(String newPassword,
{String oldPassword, Map<String, dynamic> auth}) async { {String oldPassword, AuthenticationData auth}) async {
try { try {
if (oldPassword != null) { if (oldPassword != null) {
auth = { auth = AuthenticationPassword(
'type': 'm.login.password', user: userID,
'user': userID, identifier: AuthenticationUserIdentifier(user: userID),
'password': oldPassword, password: oldPassword,
}; );
} }
await super.changePassword(newPassword, auth: auth); await super.changePassword(newPassword, auth: auth);
} on MatrixException catch (matrixException) { } on MatrixException catch (matrixException) {
@ -1619,7 +1619,7 @@ sort order of ${prevState.sortOrder}. This should never happen...''');
} }
if (matrixException.authenticationFlows.length != 1 || if (matrixException.authenticationFlows.length != 1 ||
!matrixException.authenticationFlows.first.stages !matrixException.authenticationFlows.first.stages
.contains('m.login.password')) { .contains(AuthenticationTypes.password)) {
rethrow; rethrow;
} }
if (oldPassword == null) { if (oldPassword == null) {
@ -1627,13 +1627,12 @@ sort order of ${prevState.sortOrder}. This should never happen...''');
} }
return changePassword( return changePassword(
newPassword, newPassword,
auth: { auth: AuthenticationPassword(
'type': 'm.login.password', user: userID,
'user': userID, identifier: AuthenticationUserIdentifier(user: userID),
'identifier': {'type': 'm.id.user', 'user': userID}, password: oldPassword,
'password': oldPassword, session: matrixException.session,
'session': matrixException.session, ),
},
); );
} catch (_) { } catch (_) {
rethrow; rethrow;

View File

@ -18,30 +18,48 @@
import '../../famedlysdk.dart'; import '../../famedlysdk.dart';
enum UiaRequestState {
/// The request is done
done,
/// The request has failed
fail,
/// The request is currently loading
loading,
/// The request is waiting for user interaction
waitForUser,
}
/// Wrapper to handle User interactive authentication requests /// Wrapper to handle User interactive authentication requests
class UiaRequest<T> { class UiaRequest<T> {
void Function() onUpdate; void Function(UiaRequestState state) onUpdate;
void Function() onDone; final Future<T> Function(AuthenticationData auth) request;
final Future<T> Function(Map<String, dynamic> auth) request;
String session; String session;
bool done = false; UiaRequestState _state = UiaRequestState.loading;
bool fail = false;
T result; T result;
Exception error; Exception error;
Set<String> nextStages = <String>{}; Set<String> nextStages = <String>{};
Map<String, dynamic> params = <String, dynamic>{}; Map<String, dynamic> params = <String, dynamic>{};
UiaRequest({this.onUpdate, this.request, this.onDone}) {
run(); UiaRequestState get state => _state;
set state(UiaRequestState newState) {
if (_state == newState) return;
_state = newState;
onUpdate?.call(newState);
} }
Future<T> run([Map<String, dynamic> auth]) async { UiaRequest({this.onUpdate, this.request}) {
_run();
}
Future<T> _run([AuthenticationData auth]) async {
state = UiaRequestState.loading;
try { try {
auth ??= <String, dynamic>{}; auth ??= AuthenticationData(session: session);
if (session != null) {
auth['session'] = session;
}
final res = await request(auth); final res = await request(auth);
done = true; state = UiaRequestState.done;
result = res; result = res;
return res; return res;
} on MatrixException catch (err) { } on MatrixException catch (err) {
@ -59,23 +77,16 @@ class UiaRequest<T> {
return null; return null;
} catch (err) { } catch (err) {
error = err is Exception ? err : Exception(err); error = err is Exception ? err : Exception(err);
fail = true; state = UiaRequestState.fail;
return null; return null;
} finally { } finally {
if (onUpdate != null) { if (state == UiaRequestState.loading) {
onUpdate(); state = UiaRequestState.waitForUser;
}
if ((fail || done) && onDone != null) {
onDone();
} }
} }
} }
Future<T> completeStage(String type, [Map<String, dynamic> auth]) async { Future<T> completeStage(AuthenticationData auth) => _run(auth);
auth ??= <String, dynamic>{};
auth['type'] = type;
return await run(auth);
}
Set<String> getNextStages( Set<String> getNextStages(
List<AuthenticationFlow> flows, List<String> completed) { List<AuthenticationFlow> flows, List<String> completed) {

View File

@ -204,11 +204,14 @@ void main() {
test('changePassword', () async { test('changePassword', () async {
matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting');
matrixApi.accessToken = '1234'; matrixApi.accessToken = '1234';
await matrixApi.changePassword('1234', auth: { await matrixApi.changePassword(
'type': 'example.type.foo', '1234',
'session': 'xxxxx', auth: AuthenticationData.fromJson({
'example_credential': 'verypoorsharedsecret' 'type': 'example.type.foo',
}); 'session': 'xxxxx',
'example_credential': 'verypoorsharedsecret'
}),
);
matrixApi.homeserver = matrixApi.accessToken = null; matrixApi.homeserver = matrixApi.accessToken = null;
}); });
test('resetPasswordUsingEmail', () async { test('resetPasswordUsingEmail', () async {
@ -241,12 +244,14 @@ void main() {
test('deactivateAccount', () async { test('deactivateAccount', () async {
matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting');
matrixApi.accessToken = '1234'; matrixApi.accessToken = '1234';
final response = await matrixApi final response = await matrixApi.deactivateAccount(
.deactivateAccount(idServer: 'https://example.com', auth: { idServer: 'https://example.com',
'type': 'example.type.foo', auth: AuthenticationData.fromJson({
'session': 'xxxxx', 'type': 'example.type.foo',
'example_credential': 'verypoorsharedsecret' 'session': 'xxxxx',
}); 'example_credential': 'verypoorsharedsecret'
}),
);
expect(response, IdServerUnbindResult.success); expect(response, IdServerUnbindResult.success);
matrixApi.homeserver = matrixApi.accessToken = null; matrixApi.homeserver = matrixApi.accessToken = null;
}); });
@ -267,7 +272,8 @@ void main() {
test('addThirdPartyIdentifier', () async { test('addThirdPartyIdentifier', () async {
matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting');
matrixApi.accessToken = '1234'; matrixApi.accessToken = '1234';
await matrixApi.addThirdPartyIdentifier('1234', '1234', auth: {}); await matrixApi.addThirdPartyIdentifier('1234', '1234',
auth: AuthenticationData.fromJson({'type': 'm.login.dummy'}));
matrixApi.homeserver = matrixApi.accessToken = null; matrixApi.homeserver = matrixApi.accessToken = null;
}); });
test('bindThirdPartyIdentifier', () async { test('bindThirdPartyIdentifier', () async {
@ -1041,7 +1047,8 @@ void main() {
matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting');
matrixApi.accessToken = '1234'; matrixApi.accessToken = '1234';
await matrixApi.deleteDevice('QBUAZIFURK', auth: {}); await matrixApi.deleteDevice('QBUAZIFURK',
auth: AuthenticationData.fromJson({}));
matrixApi.homeserver = matrixApi.accessToken = null; matrixApi.homeserver = matrixApi.accessToken = null;
}); });
@ -1049,7 +1056,8 @@ void main() {
matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting');
matrixApi.accessToken = '1234'; matrixApi.accessToken = '1234';
await matrixApi.deleteDevices(['QBUAZIFURK'], auth: {}); await matrixApi
.deleteDevices(['QBUAZIFURK'], auth: AuthenticationData.fromJson({}));
matrixApi.homeserver = matrixApi.accessToken = null; matrixApi.homeserver = matrixApi.accessToken = null;
}); });
@ -1785,5 +1793,122 @@ void main() {
['/client/unstable/room_keys/keys?version=5']({}), ['/client/unstable/room_keys/keys?version=5']({}),
ret.toJson()); ret.toJson());
}); });
test('AuthenticationData', () {
final json = {'session': '1234', 'type': 'm.login.dummy'};
expect(AuthenticationData.fromJson(json).toJson(), json);
expect(
AuthenticationData(session: '1234', type: 'm.login.dummy').toJson(),
json);
});
test('AuthenticationRecaptcha', () {
final json = {
'session': '1234',
'type': 'm.login.recaptcha',
'response': 'a',
};
expect(AuthenticationRecaptcha.fromJson(json).toJson(), json);
expect(AuthenticationRecaptcha(session: '1234', response: 'a').toJson(),
json);
});
test('AuthenticationToken', () {
final json = {
'session': '1234',
'type': 'm.login.token',
'token': 'a',
'txn_id': '1'
};
expect(AuthenticationToken.fromJson(json).toJson(), json);
expect(
AuthenticationToken(session: '1234', token: 'a', txnId: '1').toJson(),
json);
});
test('AuthenticationThreePidCreds', () {
final json = {
'type': 'm.login.email.identity',
'threepidCreds': [
{
'sid': '1',
'client_secret': 'a',
'id_server': 'matrix.org',
'id_access_token': 'a',
},
],
'threepid_creds': [
{
'sid': '1',
'client_secret': 'a',
'id_server': 'matrix.org',
'id_access_token': 'a',
},
],
'session': '1',
};
expect(AuthenticationThreePidCreds.fromJson(json).toJson(), json);
expect(
AuthenticationThreePidCreds(
session: '1',
type: AuthenticationTypes.emailIdentity,
threepidCreds: [
ThreepidCreds(
sid: '1',
clientSecret: 'a',
idServer: 'matrix.org',
idAccessToken: 'a',
),
]).toJson(),
json);
});
test('AuthenticationIdentifier', () {
final json = {'type': 'm.id.user'};
expect(AuthenticationIdentifier.fromJson(json).toJson(), json);
expect(AuthenticationIdentifier(type: 'm.id.user').toJson(), json);
});
test('AuthenticationPassword', () {
final json = {
'type': 'm.login.password',
'identifier': {'type': 'm.id.user', 'user': 'a'},
'password': 'a',
'session': '1',
'user': 'a',
};
expect(AuthenticationPassword.fromJson(json).toJson(), json);
expect(
AuthenticationPassword(
session: '1',
password: 'a',
user: 'a',
identifier: AuthenticationUserIdentifier(user: 'a'),
).toJson(),
json);
json['identifier'] = {
'type': 'm.id.thirdparty',
'medium': 'a',
'address': 'a',
};
expect(AuthenticationPassword.fromJson(json).toJson(), json);
expect(
AuthenticationPassword(
session: '1',
password: 'a',
user: 'a',
identifier:
AuthenticationThirdPartyIdentifier(medium: 'a', address: 'a'),
).toJson(),
json);
json['identifier'] = {
'type': 'm.id.phone',
'country': 'a',
'phone': 'a',
};
expect(AuthenticationPassword.fromJson(json).toJson(), json);
expect(
AuthenticationPassword(
session: '1',
password: 'a',
user: 'a',
identifier: AuthenticationPhoneIdentifier(country: 'a', phone: 'a'),
).toJson(),
json);
});
}); });
} }

View File

@ -29,14 +29,14 @@ void main() {
var finished = false; var finished = false;
final request = UiaRequest( final request = UiaRequest(
request: (auth) async { request: (auth) async {
if (auth['session'] != null && auth['session'] != 'foxies') { if (auth.session != null && auth.session != 'foxies') {
throw MatrixException.fromJson(<String, dynamic>{}); throw MatrixException.fromJson(<String, dynamic>{});
} }
if (auth['type'] == 'stage1') { if (auth.type == 'stage1') {
if (completed.isEmpty) { if (completed.isEmpty) {
completed.add('stage1'); completed.add('stage1');
} }
} else if (auth['type'] == 'stage2') { } else if (auth.type == 'stage2') {
if (completed.length == 1 && completed[0] == 'stage1') { if (completed.length == 1 && completed[0] == 'stage1') {
// okay, we are done! // okay, we are done!
return 'FOXIES ARE FLOOOOOFY!!!!!'; return 'FOXIES ARE FLOOOOOFY!!!!!';
@ -54,24 +54,29 @@ void main() {
}; };
throw MatrixException.fromJson(res); throw MatrixException.fromJson(res);
}, },
onUpdate: () => updated++, onUpdate: (state) {
onDone: () => finished = true, if (state == UiaRequestState.done) {
finished = true;
}
updated++;
},
); );
await Future.delayed(Duration(milliseconds: 50)); await Future.delayed(Duration(milliseconds: 50));
expect(request.nextStages.contains('stage1'), true); expect(request.nextStages.contains('stage1'), true);
expect(request.nextStages.length, 1); expect(request.nextStages.length, 1);
expect(updated, 1); expect(updated, 1);
expect(finished, false); expect(finished, false);
await request.completeStage('stage1'); await request.completeStage(AuthenticationData(type: 'stage1'));
expect(request.nextStages.contains('stage2'), true); expect(request.nextStages.contains('stage2'), true);
expect(request.nextStages.length, 1); expect(request.nextStages.length, 1);
expect(updated, 2); expect(updated, 3);
expect(finished, false); expect(finished, false);
final res = await request.completeStage('stage2'); final res =
await request.completeStage(AuthenticationData(type: 'stage2'));
expect(res, 'FOXIES ARE FLOOOOOFY!!!!!'); expect(res, 'FOXIES ARE FLOOOOOFY!!!!!');
expect(request.result, 'FOXIES ARE FLOOOOOFY!!!!!'); expect(request.result, 'FOXIES ARE FLOOOOOFY!!!!!');
expect(request.done, true); expect(request.state, UiaRequestState.done);
expect(updated, 3); expect(updated, 5);
expect(finished, true); expect(finished, true);
}); });
test('it should throw errors', () async { test('it should throw errors', () async {
@ -81,11 +86,15 @@ void main() {
request: (auth) async { request: (auth) async {
throw Exception('nope'); throw Exception('nope');
}, },
onUpdate: () => updated = true, onUpdate: (state) {
onDone: () => finished = true, if (state == UiaRequestState.fail) {
finished = true;
}
updated = true;
},
); );
await Future.delayed(Duration(milliseconds: 50)); await Future.delayed(Duration(milliseconds: 50));
expect(request.fail, true); expect(request.state, UiaRequestState.fail);
expect(updated, true); expect(updated, true);
expect(finished, true); expect(finished, true);
expect(request.error.toString(), Exception('nope').toString()); expect(request.error.toString(), Exception('nope').toString());