fix: Resolve mTLS issue, update changelogs, bump version number

This commit is contained in:
Anton Stubenbord
2024-01-07 22:55:57 +01:00
parent 18d8d34bd9
commit c5db5dfdce
11 changed files with 89 additions and 140 deletions

View File

@@ -63,6 +63,7 @@ class ChangelogDialog extends StatelessWidget {
}
const _versionNumbers = {
"4053": "3.2.1",
"4043": "3.2.0",
"4033": "3.1.8",
"4023": "3.1.7",

View File

@@ -8,13 +8,27 @@ part 'client_certificate.g.dart';
@HiveType(typeId: HiveTypeIds.clientCertificate)
class ClientCertificate {
@HiveField(0)
Uint8List bytes;
final Uint8List bytes;
@HiveField(2, defaultValue: "cert.pfx")
final String filename;
@HiveField(1)
String? passphrase;
final String? passphrase;
ClientCertificate({
required this.bytes,
required this.filename,
this.passphrase,
});
ClientCertificate copyWith({
Uint8List? bytes,
String? filename,
String? passphrase,
}) {
return ClientCertificate(
bytes: bytes ?? this.bytes,
filename: filename ?? this.filename,
passphrase: passphrase ?? this.passphrase,
);
}
}

View File

@@ -1,25 +0,0 @@
import 'dart:typed_data';
class ClientCertificateFormModel {
static const bytesKey = 'bytes';
static const passphraseKey = 'passphrase';
final Uint8List bytes;
final String? passphrase;
ClientCertificateFormModel({
required this.bytes,
this.passphrase,
});
ClientCertificateFormModel copyWith({
Uint8List? bytes,
String? passphrase,
String? filePath,
}) {
return ClientCertificateFormModel(
bytes: bytes ?? this.bytes,
passphrase: passphrase ?? this.passphrase,
);
}
}

View File

@@ -12,7 +12,6 @@ import 'package:paperless_mobile/core/model/info_message_exception.dart';
import 'package:paperless_mobile/core/service/connectivity_status_service.dart';
import 'package:paperless_mobile/core/extensions/flutter_extensions.dart';
import 'package:paperless_mobile/features/login/model/client_certificate.dart';
import 'package:paperless_mobile/features/login/model/client_certificate_form_model.dart';
import 'package:paperless_mobile/features/login/model/login_form_credentials.dart';
import 'package:paperless_mobile/features/login/model/reachability_status.dart';
import 'package:paperless_mobile/features/login/view/widgets/form_fields/client_certificate_form_field.dart';
@@ -230,75 +229,14 @@ class _AddAccountPageState extends State<AddAccountPage> {
),
),
);
return Scaffold(
appBar: AppBar(
title: Text(widget.titleText),
),
bottomNavigationBar: BottomAppBar(
child: Row(
mainAxisAlignment: widget.bottomLeftButton != null
? MainAxisAlignment.spaceBetween
: MainAxisAlignment.end,
children: [
if (widget.bottomLeftButton != null) widget.bottomLeftButton!,
FilledButton(
child: Text(S.of(context)!.loginPageSignInTitle),
onPressed: _reachabilityStatus == ReachabilityStatus.reachable &&
!_isFormSubmitted
? _onSubmit
: null,
),
],
),
),
resizeToAvoidBottomInset: true,
body: AutofillGroup(
child: FormBuilder(
key: _formKey,
child: ListView(
children: [
ServerAddressFormField(
initialValue: widget.initialServerUrl,
onChanged: (address) {
_updateReachability(address);
},
).padded(),
ClientCertificateFormField(
initialBytes: widget.initialClientCertificate?.bytes,
initialPassphrase: widget.initialClientCertificate?.passphrase,
onChanged: (_) => _updateReachability(),
).padded(),
_buildStatusIndicator(),
if (_reachabilityStatus == ReachabilityStatus.reachable) ...[
UserCredentialsFormField(
formKey: _formKey,
initialUsername: widget.initialUsername,
initialPassword: widget.initialPassword,
onFieldsSubmitted: _onSubmit,
),
Text(
S.of(context)!.loginRequiredPermissionsHint,
style: Theme.of(context).textTheme.bodySmall?.apply(
color: Theme.of(context)
.colorScheme
.onBackground
.withOpacity(0.6),
),
).padded(16),
],
],
),
),
),
);
}
Future<ReachabilityStatus> _updateReachability([String? address]) async {
setState(() {
_isCheckingConnection = true;
});
final certForm =
_formKey.currentState?.getRawValue<ClientCertificateFormModel>(
final selectedCertificate =
_formKey.currentState?.getRawValue<ClientCertificate>(
ClientCertificateFormField.fkClientCertificate,
);
final status = await context
@@ -307,12 +245,7 @@ class _AddAccountPageState extends State<AddAccountPage> {
address ??
_formKey.currentState!
.getRawValue(ServerAddressFormField.fkServerAddress),
certForm != null
? ClientCertificate(
bytes: certForm.bytes,
passphrase: certForm.passphrase,
)
: null,
selectedCertificate,
);
setState(() {
_isCheckingConnection = false;
@@ -383,16 +316,10 @@ class _AddAccountPageState extends State<AddAccountPage> {
});
if (_formKey.currentState?.saveAndValidate() ?? false) {
final form = _formKey.currentState!.value;
ClientCertificate? clientCert;
final clientCertFormModel =
form[ClientCertificateFormField.fkClientCertificate]
as ClientCertificateFormModel?;
if (clientCertFormModel != null) {
clientCert = ClientCertificate(
bytes: clientCertFormModel.bytes,
passphrase: clientCertFormModel.passphrase,
);
}
as ClientCertificate?;
final credentials =
form[UserCredentialsFormField.fkCredentials] as LoginFormCredentials;
try {
@@ -401,7 +328,7 @@ class _AddAccountPageState extends State<AddAccountPage> {
credentials.username!,
credentials.password!,
form[ServerAddressFormField.fkServerAddress],
clientCert,
clientCertFormModel,
);
} on PaperlessApiException catch (error) {
showErrorMessage(context, error);

View File

@@ -5,7 +5,7 @@ import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:paperless_mobile/core/extensions/flutter_extensions.dart';
import 'package:paperless_mobile/features/login/model/client_certificate_form_model.dart';
import 'package:paperless_mobile/features/login/model/client_certificate.dart';
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
import 'package:paperless_mobile/helpers/message_helpers.dart';
import 'package:path/path.dart' as p;
@@ -15,14 +15,16 @@ class ClientCertificateFormField extends StatefulWidget {
static const fkClientCertificate = 'clientCertificate';
final String? initialPassphrase;
final String? initialFilename;
final Uint8List? initialBytes;
final ValueChanged<ClientCertificateFormModel?>? onChanged;
final ValueChanged<ClientCertificate?>? onChanged;
const ClientCertificateFormField({
super.key,
this.onChanged,
this.initialPassphrase,
this.initialBytes,
this.initialFilename,
});
@override
@@ -32,23 +34,23 @@ class ClientCertificateFormField extends StatefulWidget {
class _ClientCertificateFormFieldState extends State<ClientCertificateFormField>
with AutomaticKeepAliveClientMixin {
File? _selectedFile;
@override
Widget build(BuildContext context) {
super.build(context);
return FormBuilderField<ClientCertificateFormModel?>(
return FormBuilderField<ClientCertificate?>(
key: const ValueKey('login-client-cert'),
name: ClientCertificateFormField.fkClientCertificate,
onChanged: widget.onChanged,
initialValue: widget.initialBytes != null
? ClientCertificateFormModel(
? ClientCertificate(
bytes: widget.initialBytes!,
filename: widget.initialFilename!,
passphrase: widget.initialPassphrase,
)
: null,
builder: (field) {
final theme =
Theme.of(context).copyWith(dividerColor: Colors.transparent); //new
Theme.of(context).copyWith(dividerColor: Colors.transparent);
return Theme(
data: theme,
child: ExpansionTile(
@@ -74,11 +76,10 @@ class _ClientCertificateFormFieldState extends State<ClientCertificateFormField>
_buildSelectedFileText(field).paddedOnly(left: 8),
],
),
if (_selectedFile != null)
if (field.value?.filename != null)
IconButton(
icon: const Icon(Icons.close),
onPressed: () => setState(() {
_selectedFile = null;
field.didChange(null);
}),
)
@@ -103,7 +104,7 @@ class _ClientCertificateFormFieldState extends State<ClientCertificateFormField>
// : null,
// ),
// ),
if (_selectedFile != null) ...[
if (field.value?.filename != null) ...[
ObscuredInputTextFormField(
key: const ValueKey('login-client-cert-passphrase'),
initialValue: field.value?.passphrase,
@@ -124,7 +125,7 @@ class _ClientCertificateFormFieldState extends State<ClientCertificateFormField>
}
Future<void> _onSelectFile(
FormFieldState<ClientCertificateFormModel?> field,
FormFieldState<ClientCertificate?> field,
) async {
final result = await FilePicker.platform.pickFiles(
allowMultiple: false,
@@ -137,21 +138,18 @@ class _ClientCertificateFormFieldState extends State<ClientCertificateFormField>
showSnackBar(context, S.of(context)!.invalidCertificateFormat);
return;
}
File file = File(result.files.single.path!);
setState(() {
_selectedFile = file;
});
File file = File(path);
final bytes = await file.readAsBytes();
final changedValue = field.value?.copyWith(bytes: bytes) ??
ClientCertificateFormModel(bytes: bytes);
final changedValue = ClientCertificate(
bytes: bytes,
filename: p.basename(path),
);
field.didChange(changedValue);
}
Widget _buildSelectedFileText(
FormFieldState<ClientCertificateFormModel?> field) {
Widget _buildSelectedFileText(FormFieldState<ClientCertificate?> field) {
if (field.value == null) {
assert(_selectedFile == null);
return Text(
S.of(context)!.selectFile,
style: Theme.of(context).textTheme.labelMedium?.apply(
@@ -159,9 +157,8 @@ class _ClientCertificateFormFieldState extends State<ClientCertificateFormField>
),
);
} else {
assert(_selectedFile != null);
return Text(
_selectedFile!.path.split("/").last,
p.basename(field.value!.filename),
style: const TextStyle(
overflow: TextOverflow.ellipsis,
),