3
0
Fork 0
trifid_mobile/lib/screens/siteConfig/CAListScreen.dart

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

242 lines
6.8 KiB
Dart
Raw Normal View History

2020-07-27 20:43:58 +00:00
import 'dart:convert';
import 'package:flutter/cupertino.dart';
import 'package:flutter/services.dart';
import 'package:flutter_barcode_scanner/flutter_barcode_scanner.dart';
2020-07-27 20:43:58 +00:00
import 'package:mobile_nebula/components/FormPage.dart';
import 'package:mobile_nebula/components/config/ConfigButtonItem.dart';
import 'package:mobile_nebula/components/config/ConfigPageItem.dart';
import 'package:mobile_nebula/components/config/ConfigSection.dart';
import 'package:mobile_nebula/components/config/ConfigTextItem.dart';
import 'package:mobile_nebula/models/Certificate.dart';
import 'package:mobile_nebula/screens/siteConfig/CertificateDetailsScreen.dart';
import 'package:mobile_nebula/services/utils.dart';
//TODO: wire up the focus nodes, add a done/next/prev to the keyboard
//TODO: you left off at providing the signed cert back. You need to verify it has your public key in it. You likely want to present the cert details before they can save
//TODO: In addition you will want to think about re-generation while the site is still active (This means storing multiple keys in secure storage)
class CAListScreen extends StatefulWidget {
const CAListScreen({
Key? key,
required this.cas,
this.onSave,
}) : super(key: key);
2020-07-27 20:43:58 +00:00
final List<CertificateInfo> cas;
final ValueChanged<List<CertificateInfo>>? onSave;
2020-07-27 20:43:58 +00:00
@override
_CAListScreenState createState() => _CAListScreenState();
}
class _CAListScreenState extends State<CAListScreen> {
Map<String, CertificateInfo> cas = {};
bool changed = false;
var inputType = "paste";
final pasteController = TextEditingController();
static const platform = MethodChannel('net.defined.mobileNebula/NebulaVpnService');
var error = "";
@override
void initState() {
widget.cas.forEach((ca) {
cas[ca.cert.fingerprint] = ca;
});
super.initState();
}
@override
Widget build(BuildContext context) {
List<Widget> items = [];
final caItems = _buildCAs();
if (caItems.length > 0) {
items.add(ConfigSection(children: caItems));
}
items.addAll(_addCA());
return FormPage(
title: 'Certificate Authorities',
changed: changed,
onSave: () {
if (widget.onSave != null) {
Navigator.pop(context);
widget.onSave!(cas.values.map((ca) {
2020-07-27 20:43:58 +00:00
return ca;
}).toList());
}
},
child: Column(children: items));
}
List<Widget> _buildCAs() {
List<Widget> items = [];
cas.forEach((key, ca) {
items.add(ConfigPageItem(
content: Text(ca.cert.details.name),
onPressed: () {
Utils.openPage(context, (context) {
return CertificateDetailsScreen(
2021-05-03 20:16:00 +00:00
certInfo: ca,
2020-07-27 20:43:58 +00:00
onDelete: () {
setState(() {
changed = true;
cas.remove(key);
});
});
});
},
));
});
return items;
}
_addCAEntry(String ca, ValueChanged<String?> callback) async {
String? error;
2020-07-27 20:43:58 +00:00
//TODO: show an error popup
try {
var rawCerts = await platform.invokeMethod("nebula.parseCerts", <String, String>{"certs": ca});
var ignored = 0;
2020-07-27 20:43:58 +00:00
List<dynamic> certs = jsonDecode(rawCerts);
certs.forEach((rawCert) {
final info = CertificateInfo.fromJson(rawCert);
if (!info.cert.details.isCa) {
ignored++;
return;
}
2020-07-27 20:43:58 +00:00
cas[info.cert.fingerprint] = info;
});
if (ignored > 0) {
error = 'One or more certificates were ignored because they were not certificate authorities.';
}
2020-07-27 20:43:58 +00:00
changed = true;
} on PlatformException catch (err) {
//TODO: fix this message
error = err.details ?? err.message;
}
callback(error);
2020-07-27 20:43:58 +00:00
}
List<Widget> _addCA() {
List<Widget> items = [
Padding(
padding: EdgeInsets.fromLTRB(10, 25, 10, 0),
child: CupertinoSlidingSegmentedControl(
groupValue: inputType,
onValueChanged: (v) {
if (v != null) {
setState(() {
inputType = v;
});
}
2020-07-27 20:43:58 +00:00
},
children: {
'paste': Text('Copy/Paste'),
'file': Text('File'),
'qr': Text('QR Code'),
},
))
];
if (inputType == 'paste') {
items.addAll(_addPaste());
} else if (inputType == 'file') {
items.addAll(_addFile());
} else {
items.addAll(_addQr());
}
return items;
}
List<Widget> _addPaste() {
return [
ConfigSection(
children: [
ConfigTextItem(
placeholder: 'CA PEM contents',
controller: pasteController,
),
ConfigButtonItem(
content: Text('Load CA'),
onPressed: () {
_addCAEntry(pasteController.text, (err) {
print(err);
if (err != null) {
return Utils.popError(context, 'Failed to parse CA content', err);
}
pasteController.text = '';
setState(() {});
});
}),
],
)
];
}
List<Widget> _addFile() {
return [
ConfigSection(
children: [
ConfigButtonItem(
content: Text('Choose a file'),
onPressed: () async {
try {
2021-04-23 17:33:28 +00:00
final content = await Utils.pickFile(context);
if (content == null) {
return;
}
_addCAEntry(content, (err) {
if (err != null) {
Utils.popError(context, 'Error loading CA file', err);
} else {
setState(() {});
}
});
2020-07-27 20:43:58 +00:00
} catch (err) {
return Utils.popError(context, 'Failed to load CA file', err.toString());
}
})
],
)
];
}
List<Widget> _addQr() {
return [
ConfigSection(
children: [
ConfigButtonItem(
content: Text('Scan a QR code'),
onPressed: () async {
try {
var result = await FlutterBarcodeScanner.scanBarcode('#ff6666', 'Cancel', true, ScanMode.QR);
if (result != "") {
_addCAEntry(result, (err) {
if (err != null) {
Utils.popError(context, 'Error loading CA content', err);
} else {
setState(() {});
}
});
}
} catch (err) {
return Utils.popError(context, 'Error scanning QR code', err.toString());
2020-07-27 20:43:58 +00:00
}
})
],
)
];
}
}