Skip to content

Commit

Permalink
Merge branch 'develop' into darwin
Browse files Browse the repository at this point in the history
  • Loading branch information
za-creature committed Dec 9, 2024
2 parents 2efa33d + c29bcb6 commit e825b5b
Show file tree
Hide file tree
Showing 62 changed files with 2,609 additions and 983 deletions.
36 changes: 29 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ on:

jobs:
build_and_test:
name: build and test on ${{ matrix.name }}
name: build and/or test on ${{ matrix.name }}
runs-on: ${{ matrix.os }}

strategy:
matrix:
name: [android, linux, windows]
name: [android, linux-test, linux-build, windows]
include:
- name: android
os: ubuntu-24.04
Expand All @@ -25,15 +25,31 @@ jobs:
abi: arm64-v8a
build-args: --apk
artifact-files: ouisync*.apk
test: true
build: true

- name: linux
- name: linux-test
os: ubuntu-24.04
env:
OUISYNC_LIB: ouisync/target/debug/libouisync_ffi.so
# TODO: enable analysis
# analyze: true
build-args: --deb-gui --deb-cli
artifact-files: ouisync*.deb
test: true
build: false

# For GLIBC and GLIBCXX compatibility we build on not-the-most-recent Ubuntu version
- name: linux-build
os: ubuntu-22.04
env:
OUISYNC_LIB: ouisync/target/debug/libouisync_ffi.so
# TODO: enable analysis
# analyze: true
build-args: --deb-gui --deb-cli
artifact-files: ouisync*.deb
test: false
build: true

- name: windows
os: windows-latest
Expand All @@ -43,6 +59,8 @@ jobs:
OUISYNC_LIB: ouisync\target\debug\ouisync_ffi.dll
build-args: --exe
artifact-files: ouisync*.exe
test: true
build: true

env: ${{ matrix.env }}

Expand Down Expand Up @@ -72,7 +90,7 @@ jobs:
libsecret-1-dev \
ninja-build \
imagemagick
if: matrix.name == 'linux'
if: matrix.name == 'linux-test' || matrix.name == 'linux-build'

- name: Install dependencies (android)
# Note that libfuse-dev is not required to build the Android app, but
Expand Down Expand Up @@ -119,10 +137,12 @@ jobs:
- name: Build Ouisync library for tests
working-directory: ouisync
run: cargo build --package ouisync-ffi --lib
if: matrix.test

- name: Run tests
run:
flutter test
if: matrix.test

- name: Setup Sentry DSN for artifact build (different from the production releases)
run: |
Expand All @@ -131,11 +151,11 @@ jobs:
shell: bash
env:
NIGHTLY_SENTRY_DSN : ${{ secrets.NIGHTLY_SENTRY_DSN }}
if: env.NIGHTLY_SENTRY_DSN != null
if: matrix.build && env.NIGHTLY_SENTRY_DSN != null

- name: Setup secrets for artifact signing (different from the production releases)
run: |
dir=secrets/nightly/android
dir=secrets/android
mkdir -p $dir
echo $NIGHTLY_KEYSTORE_JKS_HEX > $dir/keystore.jks.hex
# Use `keytool` to generate the `keystore.jks` keystore.
Expand All @@ -152,7 +172,7 @@ jobs:
env:
NIGHTLY_KEYSTORE_JKS_HEX : ${{ secrets.NIGHTLY_KEYSTORE_JKS_HEX }}
NIGHTLY_KEYSTORE_PASSWORD : ${{ secrets.NIGHTLY_KEYSTORE_PASSWORD }}
if: env.NIGHTLY_KEYSTORE_JKS_HEX != null && env.NIGHTLY_KEYSTORE_PASSWORD != null
if: matrix.build && env.NIGHTLY_KEYSTORE_JKS_HEX != null && env.NIGHTLY_KEYSTORE_PASSWORD != null

- name: Build artifacts
run: |
Expand All @@ -165,10 +185,12 @@ jobs:
dart run util/release.dart --flavor=$flavor $keystore $sentry ${{ matrix.build-args }}
shell: bash
if: matrix.build

- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
# Name of the produced zip file
name: ${{ matrix.name }}
path: releases/release_*/${{ matrix.artifact-files }}
if: matrix.build
14 changes: 10 additions & 4 deletions android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -161,13 +161,19 @@ def loadKeystorePropertiesFile(String keystorePropertiesPath) {
}
def properties = new Properties()

def proFile = rootProject.file(keystorePropertiesPath)
def propFile = rootProject.file(keystorePropertiesPath)

if (!proFile.exists()) {
return null
if (!propFile.exists()) {
throw new GradleException("The keystore properties file does not exist: ${keystorePropertiesPath}");
}

proFile.withInputStream { properties.load(it) }
propFile.withInputStream { properties.load(it) }

def storeFile = file(properties['storeFile']);

if (!storeFile.exists()) {
throw new GradleException("The store file does not exist: ${storeFile.getPath()}");
}

def config = new SigningConfig(
keyAlias: properties['keyAlias'],
Expand Down
4 changes: 2 additions & 2 deletions lib/app/app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -288,8 +288,8 @@ class ErrorScreen extends StatelessWidget {
// resulting in black screens. This class should help us find those race conditions.
class _AppNavigatorObserver extends NavigatorObserver {
final int _maxHistoryLength = 16;
List<_RouteHistoryEntry> _stackHistory = [];
Loggy<AppLogger> _logger;
final List<_RouteHistoryEntry> _stackHistory = [];
final Loggy<AppLogger> _logger;

_AppNavigatorObserver(this._logger);

Expand Down
72 changes: 44 additions & 28 deletions lib/app/cubits/repo.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,9 @@ import 'package:stream_transform/stream_transform.dart';

import '../../generated/l10n.dart';
import '../models/models.dart';
import '../utils/master_key.dart';
import '../utils/mounter.dart';
import '../utils/repo_path.dart' as repo_path;
import '../utils/utils.dart';
import 'cubits.dart';
import 'utils.dart';

class RepoState extends Equatable {
final bool isLoading;
Expand Down Expand Up @@ -111,6 +108,7 @@ class RepoCubit extends Cubit<RepoState> with CubitActions, AppLogger {
final NavigationCubit _navigation;
final EntryBottomSheetCubit _bottomSheet;
final Repository _repo;
final Session _session;
final Cipher _pathCipher;
final CacheServers _cacheServers;
final Mounter _mounter;
Expand All @@ -120,6 +118,7 @@ class RepoCubit extends Cubit<RepoState> with CubitActions, AppLogger {
this._navigation,
this._bottomSheet,
this._repo,
this._session,
this._pathCipher,
this._cacheServers,
this._mounter,
Expand All @@ -131,6 +130,7 @@ class RepoCubit extends Cubit<RepoState> with CubitActions, AppLogger {
static Future<RepoCubit> create({
required NativeChannels nativeChannels,
required Repository repo,
required Session session,
required RepoLocation location,
required NavigationCubit navigation,
required EntryBottomSheetCubit bottomSheet,
Expand Down Expand Up @@ -169,6 +169,7 @@ class RepoCubit extends Cubit<RepoState> with CubitActions, AppLogger {
navigation,
bottomSheet,
repo,
session,
pathCipher,
cacheServers,
mounter,
Expand All @@ -184,6 +185,7 @@ class RepoCubit extends Cubit<RepoState> with CubitActions, AppLogger {
return cubit;
}

Session get session => _session;
RepoLocation get location => state.location;
String get name => state.location.name;
AccessMode get accessMode => state.accessMode;
Expand Down Expand Up @@ -289,6 +291,12 @@ class RepoCubit extends Cubit<RepoState> with CubitActions, AppLogger {
emitUnlessClosed(state.copyWith(accessMode: accessMode));
}

Future<void> resetCredentials(ShareToken token) async {
await _repo.resetCredentials(token);
final accessMode = await _repo.accessMode;
emitUnlessClosed(state.copyWith(accessMode: accessMode));
}

Future<void> mount() async {
if (_mounter.mountPoint == null) {
// Mounting not supported.
Expand Down Expand Up @@ -485,22 +493,34 @@ class RepoCubit extends Cubit<RepoState> with CubitActions, AppLogger {
return content;
}

/// Returns which access mode does the given password provide.
Future<AccessMode> getPasswordAccessMode(String password) async {
/// Returns which access mode does the given secret provide.
/// TODO: It should be possible to add API which does not temporarily unlock
/// the repository.
Future<AccessMode> getSecretAccessMode(LocalSecret secret) async {
final credentials = await _repo.credentials;

try {
await _repo.setAccessMode(AccessMode.blind);
await _repo.setAccessMode(
AccessMode.write,
secret: LocalPassword(password),
secret: secret,
);
return await _repo.accessMode;
} finally {
await _repo.setCredentials(credentials);
}
}

Future<Access> getAccessOf(LocalSecret secret) async {
final accessMode = await getSecretAccessMode(secret);

return switch (accessMode) {
AccessMode.blind => BlindAccess(),
AccessMode.read => ReadAccess(secret),
AccessMode.write => WriteAccess(secret),
};
}

Future<void> setAuthMode(AuthMode authMode) async {
emitUnlessClosed(state.copyWith(isLoading: true));
await _repo.setAuthMode(authMode);
Expand Down Expand Up @@ -552,29 +572,25 @@ class RepoCubit extends Cubit<RepoState> with CubitActions, AppLogger {
}
}

/// Returns null if the authMode is AuthModeBlindOrManual or if decryption fails.
/// TODO: If decryption fails, we should throw and catch that above to inform
/// the user about the fact.
Future<LocalSecret?> getLocalSecret(MasterKey masterKey) async {
final authMode = state.authMode;

try {
switch (authMode) {
case AuthModeBlindOrManual():
return null;
case AuthModePasswordStoredOnDevice(encryptedPassword: final encrypted):
final decrypted = await masterKey.decrypt(encrypted);
if (decrypted == null) throw AuthModeDecryptFailed();
return LocalPassword(decrypted);
case AuthModeKeyStoredOnDevice(encryptedKey: final encrypted):
final decrypted = await masterKey.decryptBytes(encrypted);
if (decrypted == null) throw AuthModeDecryptFailed();
return LocalSecretKey(decrypted);
}
} catch (e) {
loggy.error("Failed to decrypt local secret: $e");
return null;
Future<void> setAccess({
AccessChange? read,
AccessChange? write,
}) async {
await _repo.setAccess(read: read, write: write);

// Operation succeeded (did not throw), so we can set `state.accessMode`
// based on `read` and `write`.
AccessMode newAccessMode;

if (read is DisableAccess && write is DisableAccess) {
newAccessMode = AccessMode.blind;
} else if (write is DisableAccess) {
newAccessMode = AccessMode.read;
} else /* `write` or both (`read` and `write`) are `EnableAccess` */ {
newAccessMode = AccessMode.write;
}

emitUnlessClosed(state.copyWith(accessMode: newAccessMode));
}

/// Unlocks the repository using the secret. The access mode the repository ends up in depends on
Expand Down
Loading

0 comments on commit e825b5b

Please sign in to comment.