mirror of
https://github.com/Xevion/paperless-mobile.git
synced 2025-12-09 16:07:57 -06:00
fix: Resolve mTLS issue, update changelogs, bump version number
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user