Skip to content
This repository has been archived by the owner on Mar 6, 2024. It is now read-only.

Commit

Permalink
Encrypted guided LVM (#951)
Browse files Browse the repository at this point in the history
Allow selecting encryption in the "Advanced features" dialog, and show
the "Choose a security key" page when appropriate.

Ref: #33, #34
  • Loading branch information
jpnurmi committed Sep 22, 2022
1 parent 63fcd75 commit 3071e8b
Show file tree
Hide file tree
Showing 7 changed files with 248 additions and 51 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import 'package:ubuntu_desktop_installer/l10n.dart';
import 'package:ubuntu_desktop_installer/main.dart' as app;
import 'package:ubuntu_desktop_installer/pages.dart';
import 'package:ubuntu_desktop_installer/pages/connect_to_internet/connect_model.dart';
import 'package:ubuntu_desktop_installer/pages/installation_type/installation_type_model.dart';
import 'package:ubuntu_desktop_installer/pages/updates_other_software/updates_other_software_model.dart';
import 'package:ubuntu_desktop_installer/routes.dart';
import 'package:ubuntu_desktop_installer/services.dart';
Expand Down Expand Up @@ -93,6 +94,71 @@ void main() {
);
});

testWidgets('guided lvm + encryption', (tester) async {
const identity = IdentityData(
realname: 'User',
hostname: 'ubuntu',
username: 'user',
);

await app.main(<String>[]);
await tester.pumpAndSettle();

await testWelcomePage(tester);
await tester.pumpAndSettle();

await testTryOrInstallPage(tester, option: Option.installUbuntu);
await tester.pumpAndSettle();

await testKeyboardLayoutPage(tester);
await tester.pumpAndSettle();

await testConnectToInternetPage(tester, mode: ConnectMode.none);
await tester.pumpAndSettle();

await testUpdatesOtherSoftwarePage(tester);
await tester.pumpAndSettle();

await testInstallationTypePage(
tester,
type: InstallationType.erase,
advancedFeature: AdvancedFeature.lvm,
useEncryption: true,
);
await tester.pumpAndSettle();

await testChooseSecurityKeyPage(tester, securityKey: 'password');
await tester.pumpAndSettle();

await testWriteChangesToDiskPage(tester);
await tester.pumpAndSettle();

await testWhereAreYouPage(tester);
await tester.pump();

await testWhoAreYouPage(
tester,
identity: identity,
password: 'password',
);
await tester.pump();

await testChooseYourLookPage(tester);
await tester.pump();

await testInstallationSlidesPage(tester);
await tester.pumpAndSettle();

await testInstallationCompletePage(tester);
await tester.pumpAndSettle();

await verifyConfig(
identity: identity,
useLvm: true,
useEncryption: true,
);
});

testWidgets('manual partitioning', (tester) async {
final storage = [
testDisk(
Expand Down Expand Up @@ -338,13 +404,53 @@ Future<void> testUpdatesOtherSoftwarePage(
Future<void> testInstallationTypePage(
WidgetTester tester, {
InstallationType? type,
AdvancedFeature? advancedFeature,
bool? useEncryption,
}) async {
await expectPage(
tester, InstallationTypePage, (lang) => lang.installationTypeTitle);

if (type != null) {
await tester.tapRadioButton<InstallationType>(type);
}
if (advancedFeature != null) {
await tester.tapButton(label: tester.lang.installationTypeAdvancedLabel);
await tester.pumpAndSettle();

await tester.tapRadioButton<AdvancedFeature>(advancedFeature);
await tester.pump();

if (useEncryption != null) {
await tester.toggleCheckbox(
label: tester.lang.installationTypeEncrypt('Ubuntu'),
value: true,
);
}

await tester.tapButton(label: tester.lang.okButtonText);
}

await tester.pumpAndSettle();

await tester.tapContinue();
}

Future<void> testChooseSecurityKeyPage(
WidgetTester tester, {
required String securityKey,
}) async {
await expectPage(
tester, ChooseSecurityKeyPage, (lang) => lang.chooseSecurityKeyTitle);

await tester.enterTextValue(
label: tester.lang.chooseSecurityKey,
value: securityKey,
);
await tester.enterTextValue(
label: tester.lang.confirmSecurityKey,
value: securityKey,
);

await tester.pumpAndSettle();

await tester.tapContinue();
Expand Down Expand Up @@ -556,6 +662,8 @@ Future<void> verifyConfig({
String? locale,
String? timezone,
List<Disk>? storage,
bool? useLvm,
bool? useEncryption,
}) async {
final path = p.join(
await subiquityPath,
Expand Down Expand Up @@ -596,9 +704,9 @@ Future<void> verifyConfig({
expect(actualTimezone, equals(timezone));
}

if (storage != null) {
final actualStorage = yaml['autoinstall']['storage']['config'] as YamlList;
final actualStorage = yaml['autoinstall']['storage']['config'] as YamlList;

if (storage != null) {
for (final disk in storage) {
final actualDisk = actualStorage.firstWhereOrNull(
(d) => d['type'] == 'disk' && d['path'] == disk.path);
Expand All @@ -617,4 +725,20 @@ Future<void> verifyConfig({
}
}
}

if (useLvm != null) {
expect(
actualStorage
.where((config) => config['type'] == 'lvm_volgroup')
.isNotEmpty,
useLvm);
}

if (useEncryption != null) {
expect(
actualStorage
.where((config) => config['type'] == 'dm_crypt')
.isNotEmpty,
useEncryption);
}
}
40 changes: 19 additions & 21 deletions packages/ubuntu_desktop_installer/lib/installer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -282,37 +282,20 @@ class _UbuntuDesktopInstallerWizard extends StatelessWidget {
),
Routes.installationType: WizardRoute(
builder: InstallationTypePage.create,
onNext: (settings) {
if (settings.arguments == InstallationType.manual) {
return Routes.allocateDiskSpace;
} else if (service.guidedTarget == null) {
if (settings.arguments == InstallationType.erase) {
return Routes.selectGuidedStorage;
} else if (settings.arguments == InstallationType.alongside) {
return Routes.installAlongside;
}
} else if (service.useEncryption) {
return Routes.chooseSecurityKey;
}
return Routes.writeChangesToDisk;
},
onNext: (settings) => _nextStorageRoute(service, settings.arguments),
),
Routes.installAlongside: WizardRoute(
builder: InstallAlongsidePage.create,
onReplace: (_) => Routes.allocateDiskSpace,
onNext: (_) => service.useEncryption
? Routes.chooseSecurityKey
: Routes.writeChangesToDisk,
onNext: (settings) => _nextStorageRoute(service, settings.arguments),
),
Routes.selectGuidedStorage: WizardRoute(
builder: SelectGuidedStoragePage.create,
onNext: (_) => service.useEncryption
? Routes.chooseSecurityKey
: Routes.writeChangesToDisk,
onNext: (settings) => _nextStorageRoute(service, settings.arguments),
),
Routes.chooseSecurityKey: WizardRoute(
builder: ChooseSecurityKeyPage.create,
onNext: (_) => Routes.writeChangesToDisk,
onNext: (settings) => _nextStorageRoute(service, settings.arguments),
),
Routes.allocateDiskSpace: const WizardRoute(
builder: AllocateDiskSpacePage.create,
Expand Down Expand Up @@ -349,6 +332,21 @@ class _UbuntuDesktopInstallerWizard extends StatelessWidget {
],
);
}

String? _nextStorageRoute(DiskStorageService service, dynamic arguments) {
if (arguments == InstallationType.manual) {
return Routes.allocateDiskSpace;
} else if (service.guidedTarget == null) {
if (arguments == InstallationType.erase) {
return Routes.selectGuidedStorage;
} else if (arguments == InstallationType.alongside) {
return Routes.installAlongside;
}
} else if (service.useEncryption && service.securityKey == null) {
return Routes.chooseSecurityKey;
}
return Routes.writeChangesToDisk;
}
}

class _UbuntuDesktopInstallerWizardObserver extends WizardObserver {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,18 +44,17 @@ Future<void> showAdvancedFeaturesDialog(
groupValue: advancedFeature.value,
onChanged: (v) => advancedFeature.value = v!,
),
// https://github.com/canonical/ubuntu-desktop-installer/issues/373
// Padding(
// padding: kContentIndentation,
// child: CheckButton(
// title: Text(lang.installationTypeEncrypt('Ubuntu')),
// subtitle: Text(lang.installationTypeEncrypt(flavor.name)),
// value: encryption.value,
// onChanged: model.advancedFeature == AdvancedFeature.lvm
// ? (v) => encryption.value = v!
// : null,
// ),
// ),
Padding(
padding: kContentIndentation,
child: CheckButton(
title: Text(lang.installationTypeEncrypt(flavor.name)),
subtitle: Text(lang.installationTypeEncryptInfo),
value: encryption.value,
onChanged: advancedFeature.value == AdvancedFeature.lvm
? (v) => encryption.value = v!
: null,
),
),
const SizedBox(height: kContentSpacing),
// https://github.com/canonical/ubuntu-desktop-installer/issues/373
// RadioButton<AdvancedFeature>(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,8 @@ class InstallationTypeModel extends SafeChangeNotifier {
/// if appropriate (single guided storage).
Future<void> save() async {
_diskService.useLvm = advancedFeature == AdvancedFeature.lvm;
_diskService.useEncryption =
encryption && advancedFeature == AdvancedFeature.lvm;

// automatically pre-select a guided storage target if possible
_diskService.guidedTarget = preselectTarget(installationType);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,11 +136,7 @@ class _InstallationTypePageState extends State<InstallationTypePage> {
child: Text(lang.installationTypeAdvancedLabel),
),
const SizedBox(width: kContentSpacing),
Text(model.advancedFeature == AdvancedFeature.lvm
? lang.installationTypeLVMSelected
: model.advancedFeature == AdvancedFeature.zfs
? lang.installationTypeZFSSelected
: lang.installationTypeNoneSelected),
Text(model.advancedFeature.localize(lang, model.encryption)),
],
),
),
Expand Down Expand Up @@ -170,6 +166,21 @@ class _InstallationTypePageState extends State<InstallationTypePage> {
}
}

extension _AdvancedFeatureL10n on AdvancedFeature {
String localize(AppLocalizations lang, bool encryption) {
switch (this) {
case AdvancedFeature.none:
return lang.installationTypeNoneSelected;
case AdvancedFeature.lvm:
return encryption
? lang.installationTypeLVMEncryptionSelected
: lang.installationTypeLVMSelected;
case AdvancedFeature.zfs:
return lang.installationTypeZFSSelected;
}
}
}

extension _OsProberList on List<OsProber> {
/// Whether the system has any OS installed multiple times.
bool get hasDuplicates =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,5 +77,41 @@ void main() {
await result;

verify(model.advancedFeature = AdvancedFeature.lvm).called(1);
verifyNever(model.encryption = true);
});

testWidgets('select encrypted lvm', (tester) async {
final model = MockInstallationTypeModel();
when(model.existingOS).thenReturn(null);
when(model.installationType).thenReturn(InstallationType.erase);
when(model.advancedFeature).thenReturn(AdvancedFeature.lvm);
when(model.encryption).thenReturn(false);
when(model.canInstallAlongside).thenReturn(false);

await tester.pumpWidget(
ChangeNotifierProvider<InstallationTypeModel>.value(
value: model,
child: tester.buildApp((_) => InstallationTypePage()),
),
);

final result = showAdvancedFeaturesDialog(
tester.element(find.byType(InstallationTypePage)), model);
await tester.pumpAndSettle();

await tester.tap(find.widgetWithText(RadioButton<AdvancedFeature>,
tester.lang.installationTypeLVM('Ubuntu')));
await tester.pump();

await tester.tap(find.widgetWithText(
CheckButton, tester.lang.installationTypeEncrypt('Ubuntu')));
await tester.pump();

await tester
.tap(find.widgetWithText(OutlinedButton, tester.lang.okButtonText));
await result;

verify(model.advancedFeature = AdvancedFeature.lvm).called(1);
verify(model.encryption = true).called(1);
});
}

0 comments on commit 3071e8b

Please sign in to comment.