Browse Source

neon: make LoginQrcode and Account a Credentials

pull/291/head
Nikolas Rimikis 2 years ago
parent
commit
7e1c083e68
No known key found for this signature in database
GPG Key ID: 85ED1DE9786A4FF2
  1. 2
      packages/neon/neon/lib/models.dart
  2. 123
      packages/neon/neon/lib/src/models/account.dart
  3. 6
      packages/neon/neon/lib/src/pages/login_qrcode.dart
  4. 5
      packages/neon/neon/lib/src/router.dart
  5. 47
      packages/neon/neon/lib/src/utils/login_qrcode.dart
  6. 25
      packages/neon/neon/test/account_test.dart

2
packages/neon/neon/lib/models.dart

@ -1,3 +1,3 @@
export 'package:neon/src/models/account.dart';
export 'package:neon/src/models/account.dart' hide Credentials, LoginQrcode;
export 'package:neon/src/models/app_implementation.dart';
export 'package:neon/src/models/notifications_interface.dart';

123
packages/neon/neon/lib/src/models/account.dart

@ -2,15 +2,29 @@ import 'dart:convert';
import 'package:collection/collection.dart';
import 'package:crypto/crypto.dart';
import 'package:flutter/foundation.dart';
import 'package:json_annotation/json_annotation.dart';
import 'package:meta/meta.dart';
import 'package:nextcloud/nextcloud.dart';
part 'account.g.dart';
/// Credentials interface
@internal
@immutable
abstract interface class Credentials {
/// Url of the server
abstract final String serverURL;
/// Username
abstract final String username;
/// Password
abstract final String? password;
}
@JsonSerializable()
@immutable
class Account {
class Account implements Credentials {
Account({
required this.serverURL,
required this.loginName,
@ -29,9 +43,12 @@ class Account {
factory Account.fromJson(final Map<String, dynamic> json) => _$AccountFromJson(json);
Map<String, dynamic> toJson() => _$AccountToJson(this);
@override
final String serverURL;
final String loginName;
@override
final String username;
@override
final String? password;
final String? userAgent;
@ -76,3 +93,105 @@ extension AccountFind on Iterable<Account> {
Account? tryFind(final String? accountID) => firstWhereOrNull((final account) => account.id == accountID);
Account find(final String accountID) => firstWhere((final account) => account.id == accountID);
}
/// Qrcode Login credentials.
///
/// The Credentials as provided by the server when manually creating an app
/// password.
@internal
@immutable
class LoginQrcode implements Credentials {
/// Creates a new LoginQrcode object.
@visibleForTesting
const LoginQrcode({
required this.serverURL,
required this.username,
required this.password,
});
@override
final String serverURL;
@override
final String username;
@override
final String password;
/// Pattern matching the full Qrcode content.
static final _loginQrcodeUrlRegex = RegExp(r'^nc://login/user:(.*)&password:(.*)&server:(.*)$');
/// Pattern matching the path part of the Qrcode.
///
/// This is used when launching the app through an intent.
static final _loginQrcodePathRegex = RegExp(r'^/user:(.*)&password:(.*)&server:(.*)$');
/// Creates a new `LoginQrcode` object by parsing a url string.
///
/// If the [url] string is not valid as a LoginQrcode a [FormatException] is
/// thrown.
///
/// Example:
/// ```dart
/// final loginQrcode =
/// LoginQrcode.parse('nc://login/user:JohnDoe&password:super_secret&server:example.com');
/// print(loginQrcode.serverURL); // JohnDoe
/// print(loginQrcode.username); // super_secret
/// print(loginQrcode.password); // example.com
///
/// LoginQrcode.parse('::Not valid LoginQrcode::'); // Throws FormatException.
/// ```
static LoginQrcode parse(final String url) {
for (final regex in [_loginQrcodeUrlRegex, _loginQrcodePathRegex]) {
final matches = regex.allMatches(url);
if (matches.isEmpty) {
continue;
}
final match = matches.single;
if (match.groupCount != 3) {
continue;
}
return LoginQrcode(
serverURL: match.group(3)!,
username: match.group(1)!,
password: match.group(2)!,
);
}
throw const FormatException();
}
/// Creates a new `LoginQrcode` object by parsing a url string.
///
/// Returns `null` if the [url] string is not valid as a LoginQrcode.
///
/// Example:
/// ```dart
/// final loginQrcode =
/// LoginQrcode.parse('nc://login/user:JohnDoe&password:super_secret&server:example.com');
/// print(loginQrcode.serverURL); // JohnDoe
/// print(loginQrcode.username); // super_secret
/// print(loginQrcode.password); // example.com
///
/// final notLoginQrcode = LoginQrcode.tryParse('::Not valid LoginQrcode::');
/// print(notLoginQrcode); // null
/// ```
static LoginQrcode? tryParse(final String url) {
try {
return parse(url);
} on FormatException {
return null;
}
}
@override
bool operator ==(final Object other) =>
other is LoginQrcode && other.serverURL == serverURL && other.username == username && other.password == password;
@override
int get hashCode => Object.hashAll([
serverURL,
username,
password,
]);
}

6
packages/neon/neon/lib/src/pages/login_qrcode.dart

@ -1,8 +1,8 @@
import 'package:flutter/material.dart';
import 'package:flutter_zxing/flutter_zxing.dart';
import 'package:neon/src/models/account.dart';
import 'package:neon/src/router.dart';
import 'package:neon/src/utils/exceptions.dart';
import 'package:neon/src/utils/login_qrcode.dart';
import 'package:neon/src/widgets/exception.dart';
class LoginQrcodePage extends StatefulWidget {
@ -41,8 +41,8 @@ class _LoginQrcodePageState extends State<LoginQrcodePage> {
}
LoginCheckServerStatusRoute.withCredentials(
serverUrl: match.server,
loginName: match.user,
serverUrl: match.serverURL,
loginName: match.username,
password: match.password,
).pushReplacement(context);
} catch (e, s) {

5
packages/neon/neon/lib/src/router.dart

@ -17,7 +17,6 @@ import 'package:neon/src/pages/login_flow.dart';
import 'package:neon/src/pages/login_qrcode.dart';
import 'package:neon/src/pages/nextcloud_app_settings.dart';
import 'package:neon/src/pages/settings.dart';
import 'package:neon/src/utils/login_qrcode.dart';
import 'package:neon/src/utils/stream_listenable.dart';
import 'package:provider/provider.dart';
@ -36,8 +35,8 @@ class AppRouter extends GoRouter {
final loginQrcode = LoginQrcode.tryParse(state.location);
if (loginQrcode != null) {
return LoginCheckServerStatusRoute.withCredentials(
serverUrl: loginQrcode.server,
loginName: loginQrcode.user,
serverUrl: loginQrcode.serverURL,
loginName: loginQrcode.username,
password: loginQrcode.password,
).location;
}

47
packages/neon/neon/lib/src/utils/login_qrcode.dart

@ -1,47 +0,0 @@
import 'package:meta/meta.dart';
@internal
class LoginQrcode {
LoginQrcode({
required this.server,
required this.user,
required this.password,
});
static final _loginQrcodeUrlRegex = RegExp(r'^nc://login/user:(.*)&password:(.*)&server:(.*)$');
static final _loginQrcodePathRegex = RegExp(r'^/user:(.*)&password:(.*)&server:(.*)$');
static LoginQrcode parse(final String url) {
for (final regex in [_loginQrcodeUrlRegex, _loginQrcodePathRegex]) {
final matches = regex.allMatches(url);
if (matches.isEmpty) {
continue;
}
final match = matches.single;
if (match.groupCount != 3) {
continue;
}
return LoginQrcode(
server: match.group(3)!,
user: match.group(1)!,
password: match.group(2)!,
);
}
throw const FormatException();
}
static LoginQrcode? tryParse(final String url) {
try {
return parse(url);
} on FormatException {
return null;
}
}
final String server;
final String user;
final String password;
}

25
packages/neon/neon/test/account_test.dart

@ -0,0 +1,25 @@
import 'package:neon/src/models/account.dart';
import 'package:test/test.dart';
void main() {
const qrCodePath = '/user:JohnDoe&password:super_secret&server:example.com';
const qrCode = 'nc://login$qrCodePath';
const invalidUrl = '::Not valid LoginQrcode::';
const credentials = LoginQrcode(
serverURL: 'example.com',
username: 'JohnDoe',
password: 'super_secret',
);
group('LoginQrcode', () {
test('parse', () {
expect(LoginQrcode.tryParse(qrCode), equals(credentials));
expect(LoginQrcode.tryParse(qrCodePath), equals(credentials));
expect(LoginQrcode.tryParse(invalidUrl), null);
});
test('equality', () {
expect(credentials, equals(credentials));
});
});
}
Loading…
Cancel
Save