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

@@ -0,0 +1,2 @@
- Beheben eines Problems mit mTLS
- Kleinere Fehlerbehebungen

View File

@@ -0,0 +1,2 @@
- Fix issue with mTLS
- Some minor bug fixes

View File

@@ -2,13 +2,29 @@ import 'package:dio/dio.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
abstract class PaperlessApiFactory { abstract class PaperlessApiFactory {
PaperlessDocumentsApi createDocumentsApi(Dio dio, {required int apiVersion}); PaperlessDocumentsApi createDocumentsApi(
PaperlessSavedViewsApi createSavedViewsApi(Dio dio, Dio dio, {
{required int apiVersion}); required int apiVersion,
PaperlessLabelsApi createLabelsApi(Dio dio, {required int apiVersion}); });
PaperlessServerStatsApi createServerStatsApi(Dio dio, PaperlessSavedViewsApi createSavedViewsApi(
{required int apiVersion}); Dio dio, {
PaperlessTasksApi createTasksApi(Dio dio, {required int apiVersion}); required int apiVersion,
});
PaperlessLabelsApi createLabelsApi(
Dio dio, {
required int apiVersion,
});
PaperlessServerStatsApi createServerStatsApi(
Dio dio, {
required int apiVersion,
});
PaperlessTasksApi createTasksApi(
Dio dio, {
required int apiVersion,
});
PaperlessAuthenticationApi createAuthenticationApi(Dio dio); PaperlessAuthenticationApi createAuthenticationApi(Dio dio);
PaperlessUserApi createUserApi(Dio dio, {required int apiVersion}); PaperlessUserApi createUserApi(
Dio dio, {
required int apiVersion,
});
} }

View File

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

View File

@@ -8,13 +8,27 @@ part 'client_certificate.g.dart';
@HiveType(typeId: HiveTypeIds.clientCertificate) @HiveType(typeId: HiveTypeIds.clientCertificate)
class ClientCertificate { class ClientCertificate {
@HiveField(0) @HiveField(0)
Uint8List bytes; final Uint8List bytes;
@HiveField(2, defaultValue: "cert.pfx")
final String filename;
@HiveField(1) @HiveField(1)
String? passphrase; final String? passphrase;
ClientCertificate({ ClientCertificate({
required this.bytes, required this.bytes,
required this.filename,
this.passphrase, 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/service/connectivity_status_service.dart';
import 'package:paperless_mobile/core/extensions/flutter_extensions.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.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/login_form_credentials.dart';
import 'package:paperless_mobile/features/login/model/reachability_status.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'; 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 { Future<ReachabilityStatus> _updateReachability([String? address]) async {
setState(() { setState(() {
_isCheckingConnection = true; _isCheckingConnection = true;
}); });
final certForm = final selectedCertificate =
_formKey.currentState?.getRawValue<ClientCertificateFormModel>( _formKey.currentState?.getRawValue<ClientCertificate>(
ClientCertificateFormField.fkClientCertificate, ClientCertificateFormField.fkClientCertificate,
); );
final status = await context final status = await context
@@ -307,12 +245,7 @@ class _AddAccountPageState extends State<AddAccountPage> {
address ?? address ??
_formKey.currentState! _formKey.currentState!
.getRawValue(ServerAddressFormField.fkServerAddress), .getRawValue(ServerAddressFormField.fkServerAddress),
certForm != null selectedCertificate,
? ClientCertificate(
bytes: certForm.bytes,
passphrase: certForm.passphrase,
)
: null,
); );
setState(() { setState(() {
_isCheckingConnection = false; _isCheckingConnection = false;
@@ -383,16 +316,10 @@ class _AddAccountPageState extends State<AddAccountPage> {
}); });
if (_formKey.currentState?.saveAndValidate() ?? false) { if (_formKey.currentState?.saveAndValidate() ?? false) {
final form = _formKey.currentState!.value; final form = _formKey.currentState!.value;
ClientCertificate? clientCert;
final clientCertFormModel = final clientCertFormModel =
form[ClientCertificateFormField.fkClientCertificate] form[ClientCertificateFormField.fkClientCertificate]
as ClientCertificateFormModel?; as ClientCertificate?;
if (clientCertFormModel != null) {
clientCert = ClientCertificate(
bytes: clientCertFormModel.bytes,
passphrase: clientCertFormModel.passphrase,
);
}
final credentials = final credentials =
form[UserCredentialsFormField.fkCredentials] as LoginFormCredentials; form[UserCredentialsFormField.fkCredentials] as LoginFormCredentials;
try { try {
@@ -401,7 +328,7 @@ class _AddAccountPageState extends State<AddAccountPage> {
credentials.username!, credentials.username!,
credentials.password!, credentials.password!,
form[ServerAddressFormField.fkServerAddress], form[ServerAddressFormField.fkServerAddress],
clientCert, clientCertFormModel,
); );
} on PaperlessApiException catch (error) { } on PaperlessApiException catch (error) {
showErrorMessage(context, 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/material.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart'; import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:paperless_mobile/core/extensions/flutter_extensions.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/generated/l10n/app_localizations.dart';
import 'package:paperless_mobile/helpers/message_helpers.dart'; import 'package:paperless_mobile/helpers/message_helpers.dart';
import 'package:path/path.dart' as p; import 'package:path/path.dart' as p;
@@ -15,14 +15,16 @@ class ClientCertificateFormField extends StatefulWidget {
static const fkClientCertificate = 'clientCertificate'; static const fkClientCertificate = 'clientCertificate';
final String? initialPassphrase; final String? initialPassphrase;
final String? initialFilename;
final Uint8List? initialBytes; final Uint8List? initialBytes;
final ValueChanged<ClientCertificateFormModel?>? onChanged; final ValueChanged<ClientCertificate?>? onChanged;
const ClientCertificateFormField({ const ClientCertificateFormField({
super.key, super.key,
this.onChanged, this.onChanged,
this.initialPassphrase, this.initialPassphrase,
this.initialBytes, this.initialBytes,
this.initialFilename,
}); });
@override @override
@@ -32,23 +34,23 @@ class ClientCertificateFormField extends StatefulWidget {
class _ClientCertificateFormFieldState extends State<ClientCertificateFormField> class _ClientCertificateFormFieldState extends State<ClientCertificateFormField>
with AutomaticKeepAliveClientMixin { with AutomaticKeepAliveClientMixin {
File? _selectedFile;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
super.build(context); super.build(context);
return FormBuilderField<ClientCertificateFormModel?>( return FormBuilderField<ClientCertificate?>(
key: const ValueKey('login-client-cert'), key: const ValueKey('login-client-cert'),
name: ClientCertificateFormField.fkClientCertificate, name: ClientCertificateFormField.fkClientCertificate,
onChanged: widget.onChanged, onChanged: widget.onChanged,
initialValue: widget.initialBytes != null initialValue: widget.initialBytes != null
? ClientCertificateFormModel( ? ClientCertificate(
bytes: widget.initialBytes!, bytes: widget.initialBytes!,
filename: widget.initialFilename!,
passphrase: widget.initialPassphrase, passphrase: widget.initialPassphrase,
) )
: null, : null,
builder: (field) { builder: (field) {
final theme = final theme =
Theme.of(context).copyWith(dividerColor: Colors.transparent); //new Theme.of(context).copyWith(dividerColor: Colors.transparent);
return Theme( return Theme(
data: theme, data: theme,
child: ExpansionTile( child: ExpansionTile(
@@ -74,11 +76,10 @@ class _ClientCertificateFormFieldState extends State<ClientCertificateFormField>
_buildSelectedFileText(field).paddedOnly(left: 8), _buildSelectedFileText(field).paddedOnly(left: 8),
], ],
), ),
if (_selectedFile != null) if (field.value?.filename != null)
IconButton( IconButton(
icon: const Icon(Icons.close), icon: const Icon(Icons.close),
onPressed: () => setState(() { onPressed: () => setState(() {
_selectedFile = null;
field.didChange(null); field.didChange(null);
}), }),
) )
@@ -103,7 +104,7 @@ class _ClientCertificateFormFieldState extends State<ClientCertificateFormField>
// : null, // : null,
// ), // ),
// ), // ),
if (_selectedFile != null) ...[ if (field.value?.filename != null) ...[
ObscuredInputTextFormField( ObscuredInputTextFormField(
key: const ValueKey('login-client-cert-passphrase'), key: const ValueKey('login-client-cert-passphrase'),
initialValue: field.value?.passphrase, initialValue: field.value?.passphrase,
@@ -124,7 +125,7 @@ class _ClientCertificateFormFieldState extends State<ClientCertificateFormField>
} }
Future<void> _onSelectFile( Future<void> _onSelectFile(
FormFieldState<ClientCertificateFormModel?> field, FormFieldState<ClientCertificate?> field,
) async { ) async {
final result = await FilePicker.platform.pickFiles( final result = await FilePicker.platform.pickFiles(
allowMultiple: false, allowMultiple: false,
@@ -137,21 +138,18 @@ class _ClientCertificateFormFieldState extends State<ClientCertificateFormField>
showSnackBar(context, S.of(context)!.invalidCertificateFormat); showSnackBar(context, S.of(context)!.invalidCertificateFormat);
return; return;
} }
File file = File(result.files.single.path!); File file = File(path);
setState(() {
_selectedFile = file;
});
final bytes = await file.readAsBytes(); final bytes = await file.readAsBytes();
final changedValue = field.value?.copyWith(bytes: bytes) ?? final changedValue = ClientCertificate(
ClientCertificateFormModel(bytes: bytes); bytes: bytes,
filename: p.basename(path),
);
field.didChange(changedValue); field.didChange(changedValue);
} }
Widget _buildSelectedFileText( Widget _buildSelectedFileText(FormFieldState<ClientCertificate?> field) {
FormFieldState<ClientCertificateFormModel?> field) {
if (field.value == null) { if (field.value == null) {
assert(_selectedFile == null);
return Text( return Text(
S.of(context)!.selectFile, S.of(context)!.selectFile,
style: Theme.of(context).textTheme.labelMedium?.apply( style: Theme.of(context).textTheme.labelMedium?.apply(
@@ -159,9 +157,8 @@ class _ClientCertificateFormFieldState extends State<ClientCertificateFormField>
), ),
); );
} else { } else {
assert(_selectedFile != null);
return Text( return Text(
_selectedFile!.path.split("/").last, p.basename(field.value!.filename),
style: const TextStyle( style: const TextStyle(
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),

View File

@@ -7,7 +7,7 @@ part 'custom_field_model.g.dart';
@JsonSerializable() @JsonSerializable()
class CustomFieldModel with EquatableMixin { class CustomFieldModel with EquatableMixin {
final int? id; final int? id;
final String name; final String? name;
final CustomFieldDataType dataType; final CustomFieldDataType dataType;
CustomFieldModel({ CustomFieldModel({
@@ -24,3 +24,18 @@ class CustomFieldModel with EquatableMixin {
Map<String, dynamic> toJson() => _$CustomFieldModelToJson(this); Map<String, dynamic> toJson() => _$CustomFieldModelToJson(this);
} }
/// An instance of the [CustomFieldModel].
@JsonSerializable()
class CustomFieldInstance {
final int? id;
final dynamic value;
const CustomFieldInstance({
this.id,
this.value,
});
factory CustomFieldInstance.fromJson(Map<String, dynamic> json) =>
_$CustomFieldInstanceFromJson(json);
}

View File

@@ -52,7 +52,7 @@ class DocumentModel extends Equatable {
/// Only present if full_perms=true /// Only present if full_perms=true
final Permissions? permissions; final Permissions? permissions;
final Iterable<CustomFieldModel> customFields; final Iterable<CustomFieldInstance> customFields;
const DocumentModel({ const DocumentModel({
required this.id, required this.id,
@@ -98,7 +98,7 @@ class DocumentModel extends Equatable {
bool? userCanChange, bool? userCanChange,
Iterable<NoteModel>? notes, Iterable<NoteModel>? notes,
Permissions? permissions, Permissions? permissions,
Iterable<CustomFieldModel>? customFields, Iterable<CustomFieldInstance>? customFields,
}) { }) {
return DocumentModel( return DocumentModel(
id: id, id: id,

View File

@@ -15,7 +15,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at # Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 3.2.0+404 version: 3.2.1+405
environment: environment:
sdk: ">=3.1.0 <4.0.0" sdk: ">=3.1.0 <4.0.0"