mirror of
https://github.com/Yubico/yubioath-flutter.git
synced 2024-11-23 00:57:26 +03:00
Merge PR #852.
This commit is contained in:
commit
71382634b5
2
.github/workflows/android.yaml
vendored
2
.github/workflows/android.yaml
vendored
@ -17,7 +17,7 @@ jobs:
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: 'stable'
|
||||
flutter-version: '3.3.7'
|
||||
flutter-version: '3.3.9'
|
||||
- run: |
|
||||
flutter config
|
||||
flutter --version
|
||||
|
2
.github/workflows/linux.yml
vendored
2
.github/workflows/linux.yml
vendored
@ -78,7 +78,7 @@ jobs:
|
||||
- uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: 'stable'
|
||||
flutter-version: '3.3.7'
|
||||
flutter-version: '3.3.9'
|
||||
- run: flutter config --enable-linux-desktop
|
||||
- run: flutter --version
|
||||
|
||||
|
8
.github/workflows/macos.yml
vendored
8
.github/workflows/macos.yml
vendored
@ -9,6 +9,9 @@ jobs:
|
||||
env:
|
||||
PYVER: 3.11
|
||||
|
||||
env:
|
||||
MACOSX_DEPLOYMENT_TARGET: "10.15"
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
@ -48,7 +51,7 @@ jobs:
|
||||
with:
|
||||
channel: 'stable'
|
||||
architecture: 'x64'
|
||||
flutter-version: '3.3.7'
|
||||
flutter-version: '3.3.9'
|
||||
- run: flutter config --enable-macos-desktop
|
||||
- run: flutter --version
|
||||
|
||||
@ -78,12 +81,13 @@ jobs:
|
||||
export REF=$(echo ${GITHUB_REF} | cut -d '/' -f 3)
|
||||
mkdir deploy
|
||||
mv yubioath-desktop.dmg deploy
|
||||
mv build/macos/Build/Products/Release/"Yubico Authenticator.app" deploy
|
||||
tar -czf deploy/yubioath-desktop-${REF}.app.tar.gz -C build/macos/Build/Products/Release "Yubico Authenticator.app"
|
||||
mv create-dmg.sh deploy
|
||||
mv resources/icons/dmg-background.png deploy
|
||||
mv macos/helper.entitlements deploy
|
||||
mv macos/helper-sandbox.entitlements deploy
|
||||
mv macos/Runner/Release.entitlements deploy
|
||||
mv macos/release-macos.sh deploy
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
|
2
.github/workflows/windows.yml
vendored
2
.github/workflows/windows.yml
vendored
@ -49,7 +49,7 @@ jobs:
|
||||
- uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: 'stable'
|
||||
flutter-version: '3.3.7'
|
||||
flutter-version: '3.3.9'
|
||||
- run: flutter config --enable-windows-desktop
|
||||
- run: flutter --version
|
||||
|
||||
|
7
NEWS
7
NEWS
@ -1,3 +1,10 @@
|
||||
* Version 6.0.2 (released 2022-11-28)
|
||||
** Android: Fix USB connectivity issues after returning from sleep.
|
||||
** MacOS: Fix helper subprocess compatibility on older MacOS versions.
|
||||
** Better indicate if there is an error starting the helper subprocess.
|
||||
** OATH: Better indicate error for too long issuer/name when scanning a QR code.
|
||||
** OATH: Sorting of credential names is now case-insensitive.
|
||||
|
||||
* Version 6.0.1 (released 2022-11-17)
|
||||
** Android: Fix issues of YubiKey NEO NFC connectivity on certain phones
|
||||
|
||||
|
@ -444,9 +444,22 @@ class OathManager(
|
||||
private suspend fun requestRefresh() =
|
||||
appViewModel.connectedYubiKey.value?.let { usbYubiKeyDevice ->
|
||||
useOathSessionUsb(usbYubiKeyDevice) { session ->
|
||||
oathViewModel.updateCredentials(
|
||||
calculateOathCodes(session).model(session.deviceId)
|
||||
)
|
||||
try {
|
||||
oathViewModel.updateCredentials(
|
||||
calculateOathCodes(session).model(session.deviceId)
|
||||
)
|
||||
} catch(apduException: ApduException) {
|
||||
if (apduException.sw == SW.SECURITY_CONDITION_NOT_SATISFIED) {
|
||||
Log.d(TAG, "Handled oath credential refresh on locked session.")
|
||||
oathViewModel.setSessionState(session.model(keyManager.isRemembered(session.deviceId)))
|
||||
} else {
|
||||
Log.e(
|
||||
TAG,
|
||||
"Unexpected sw when refreshing oath credentials",
|
||||
apduException.message
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,8 +6,8 @@ VSVersionInfo(
|
||||
ffi=FixedFileInfo(
|
||||
# filevers and prodvers should be always a tuple with four items: (1, 2, 3, 4)
|
||||
# Set not needed items to zero 0.
|
||||
filevers=(6, 0, 2, 0),
|
||||
prodvers=(6, 0, 2, 0),
|
||||
filevers=(6, 0, 3, 0),
|
||||
prodvers=(6, 0, 3, 0),
|
||||
# Contains a bitmask that specifies the valid bits 'flags'r
|
||||
mask=0x3f,
|
||||
# Contains a bitmask that specifies the Boolean attributes of the file.
|
||||
@ -31,11 +31,11 @@ VSVersionInfo(
|
||||
'040904b0',
|
||||
[StringStruct('CompanyName', 'Yubico'),
|
||||
StringStruct('FileDescription', 'Yubico Authenticator Helper'),
|
||||
StringStruct('FileVersion', '6.0.2-dev.1'),
|
||||
StringStruct('FileVersion', '6.0.3-dev.0'),
|
||||
StringStruct('LegalCopyright', 'Copyright (c) 2022 Yubico AB'),
|
||||
StringStruct('OriginalFilename', 'authenticator-helper.exe'),
|
||||
StringStruct('ProductName', 'Yubico Authenticator'),
|
||||
StringStruct('ProductVersion', '6.0.2-dev.1')])
|
||||
StringStruct('ProductVersion', '6.0.3-dev.0')])
|
||||
]),
|
||||
VarFileInfo([VarStruct('Translation', [1033, 1200])])
|
||||
]
|
||||
|
@ -27,6 +27,21 @@ import 'models.dart';
|
||||
|
||||
final _log = Logger('app.state');
|
||||
|
||||
// When non-null, an unrecoverable error preventing the app from functioning has occurred.
|
||||
final applicationError =
|
||||
StateNotifierProvider<ApplicationErrorNotifier, String?>(
|
||||
(ref) => ApplicationErrorNotifier(),
|
||||
);
|
||||
|
||||
class ApplicationErrorNotifier extends StateNotifier<String?> {
|
||||
ApplicationErrorNotifier() : super(null);
|
||||
|
||||
void setApplicationError(String? error) {
|
||||
_log.debug('Set ApplicationError to $error');
|
||||
state = error;
|
||||
}
|
||||
}
|
||||
|
||||
// Override this to alter the set of supported apps.
|
||||
final supportedAppsProvider =
|
||||
Provider<List<Application>>((ref) => Application.values);
|
||||
@ -42,8 +57,7 @@ final supportedThemesProvider = StateProvider<List<ThemeMode>>(
|
||||
|
||||
final themeModeProvider = StateNotifierProvider<ThemeModeNotifier, ThemeMode>(
|
||||
(ref) => ThemeModeNotifier(
|
||||
ref.watch(prefProvider),
|
||||
ref.read(supportedThemesProvider)),
|
||||
ref.watch(prefProvider), ref.read(supportedThemesProvider)),
|
||||
);
|
||||
|
||||
class ThemeModeNotifier extends StateNotifier<ThemeMode> {
|
||||
|
@ -16,6 +16,8 @@
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:yubico_authenticator/cancellation_exception.dart';
|
||||
import 'package:yubico_authenticator/core/state.dart';
|
||||
|
||||
@ -23,12 +25,16 @@ import '../../fido/views/fido_screen.dart';
|
||||
import '../../oath/models.dart';
|
||||
import '../../oath/views/add_account_page.dart';
|
||||
import '../../oath/views/oath_screen.dart';
|
||||
import '../../version.dart';
|
||||
import '../logging.dart';
|
||||
import '../message.dart';
|
||||
import '../models.dart';
|
||||
import '../state.dart';
|
||||
import 'device_error_screen.dart';
|
||||
import 'message_page.dart';
|
||||
|
||||
final _log = Logger('app.views.main_page');
|
||||
|
||||
class MainPage extends ConsumerWidget {
|
||||
const MainPage({super.key});
|
||||
|
||||
@ -40,6 +46,35 @@ class MainPage extends ConsumerWidget {
|
||||
next?.call(context);
|
||||
},
|
||||
);
|
||||
|
||||
final appError = ref.watch(applicationError);
|
||||
if (appError != null) {
|
||||
return MessagePage(
|
||||
header: 'An unrecoverable error has occured',
|
||||
message: appError,
|
||||
actions: [
|
||||
ActionChip(
|
||||
avatar: const Icon(Icons.copy),
|
||||
label: Text(AppLocalizations.of(context)!.general_copy_log),
|
||||
onPressed: () async {
|
||||
_log.info('Copying log to clipboard ($version)...');
|
||||
final logs = await ref.read(logLevelProvider.notifier).getLogs();
|
||||
var clipboard = ref.read(clipboardProvider);
|
||||
await clipboard.setText(logs.join('\n'));
|
||||
if (!clipboard.platformGivesFeedback()) {
|
||||
await ref.read(withContextProvider)(
|
||||
(context) async {
|
||||
showMessage(context,
|
||||
AppLocalizations.of(context)!.general_log_copied);
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// If the current device changes, we need to pop any open dialogs.
|
||||
ref.listen<AsyncValue<YubiKeyData>>(currentDeviceDataProvider, (_, __) {
|
||||
Navigator.of(context).popUntil((route) {
|
||||
|
@ -102,7 +102,11 @@ Future<Widget> initialize(List<String> argv) async {
|
||||
final rpc = RpcSession(exe!);
|
||||
await rpc.initialize();
|
||||
_log.info('Helper process started', exe);
|
||||
rpc.setLogLevel(Logger.root.level);
|
||||
|
||||
// Set the initial logging level. As this is the first message to the RPC,
|
||||
// it also serves to check that the Helper is functioning correctly.
|
||||
// The future will be awaited further down.
|
||||
final initRpcLogFuture = rpc.setLogLevel(Logger.root.level);
|
||||
|
||||
_initLicenses();
|
||||
|
||||
@ -131,7 +135,8 @@ Future<Widget> initialize(List<String> argv) async {
|
||||
fingerprintProvider.overrideWithProvider(desktopFingerprintProvider),
|
||||
credentialProvider.overrideWithProvider(desktopCredentialProvider),
|
||||
clipboardProvider.overrideWithProvider(desktopClipboardProvider),
|
||||
supportedThemesProvider.overrideWithProvider(desktopSupportedThemesProvider)
|
||||
supportedThemesProvider
|
||||
.overrideWithProvider(desktopSupportedThemesProvider)
|
||||
],
|
||||
child: YubicoAuthenticatorApp(
|
||||
page: Consumer(
|
||||
@ -141,6 +146,17 @@ Future<Widget> initialize(List<String> argv) async {
|
||||
rpc.setLogLevel(level);
|
||||
});
|
||||
|
||||
// Ensure the initial log level was successfully set within 5s, or
|
||||
// assume the Helper isn't functional.
|
||||
initRpcLogFuture.timeout(const Duration(seconds: 5)).onError(
|
||||
(error, stackTrace) {
|
||||
_log.error('Helper is not responsive.');
|
||||
ref
|
||||
.read(applicationError.notifier)
|
||||
.setApplicationError('Helper subprocess failed to start');
|
||||
},
|
||||
);
|
||||
|
||||
return const MainPage();
|
||||
}),
|
||||
),
|
||||
@ -175,10 +191,12 @@ void _initLogging(List<String> argv) {
|
||||
|
||||
void _initLicenses() async {
|
||||
LicenseRegistry.addLicense(() async* {
|
||||
final python = await rootBundle.loadString('assets/licenses/raw/python.txt');
|
||||
final python =
|
||||
await rootBundle.loadString('assets/licenses/raw/python.txt');
|
||||
yield LicenseEntryWithLineBreaks(['Python'], python);
|
||||
|
||||
final zxingcpp = await rootBundle.loadString('assets/licenses/raw/apache-2.0.txt');
|
||||
final zxingcpp =
|
||||
await rootBundle.loadString('assets/licenses/raw/apache-2.0.txt');
|
||||
yield LicenseEntryWithLineBreaks(['zxing-cpp'], zxingcpp);
|
||||
|
||||
final helper = await rootBundle.loadStructuredData<List>(
|
||||
|
@ -121,7 +121,7 @@ class RpcSession {
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
_log.error(e.toString(), entry);
|
||||
_log.error(entry);
|
||||
}
|
||||
}
|
||||
|
||||
@ -235,7 +235,7 @@ class RpcSession {
|
||||
return request.completer.future;
|
||||
}
|
||||
|
||||
void setLogLevel(Level level) async {
|
||||
Future<void> setLogLevel(Level level) async {
|
||||
final name = Levels.LEVELS
|
||||
.firstWhere((e) => level.value <= e.value, orElse: () => Level.OFF)
|
||||
.name
|
||||
|
@ -195,7 +195,8 @@ class FilteredCredentialsNotifier extends StateNotifier<List<OathPair>> {
|
||||
.where((pair) => pair.credential.issuer != '_hidden')
|
||||
.toList()
|
||||
..sort((a, b) {
|
||||
String searchKey(OathCredential c) => (c.issuer ?? '') + c.name;
|
||||
String searchKey(OathCredential c) =>
|
||||
((c.issuer ?? '') + c.name).toLowerCase();
|
||||
return searchKey(a.credential).compareTo(searchKey(b.credential));
|
||||
}),
|
||||
);
|
||||
|
@ -261,23 +261,28 @@ class _OathAddAccountPageState extends ConsumerState<OathAddAccountPage> {
|
||||
}
|
||||
|
||||
final period = int.tryParse(_periodController.text) ?? -1;
|
||||
final issuerText = _issuerController.text.trim();
|
||||
final nameText = _accountController.text.trim();
|
||||
final remaining = getRemainingKeySpace(
|
||||
oathType: _oathType,
|
||||
period: period,
|
||||
issuer: _issuerController.text.trim(),
|
||||
name: _accountController.text.trim(),
|
||||
issuer: issuerText,
|
||||
name: nameText,
|
||||
);
|
||||
final issuerRemaining = remaining.first;
|
||||
final nameRemaining = remaining.second;
|
||||
|
||||
final issuerMaxLength = max(issuerRemaining, 1);
|
||||
final nameMaxLength = max(nameRemaining, 1);
|
||||
|
||||
final secret = _secretController.text.replaceAll(' ', '');
|
||||
final secretLengthValid = secret.length * 5 % 8 < 5;
|
||||
|
||||
// is this credentials name/issuer pair different from all other?
|
||||
final isUnique = _credentials
|
||||
?.where((element) =>
|
||||
element.name == _accountController.text.trim() &&
|
||||
(element.issuer ?? '') == _issuerController.text.trim())
|
||||
element.name == nameText &&
|
||||
(element.issuer ?? '') == issuerText)
|
||||
.isEmpty ??
|
||||
true;
|
||||
final issuerNoColon = !_issuerController.text.contains(':');
|
||||
@ -285,7 +290,7 @@ class _OathAddAccountPageState extends ConsumerState<OathAddAccountPage> {
|
||||
final isLocked = oathState?.locked ?? false;
|
||||
|
||||
final isValid = !isLocked &&
|
||||
_accountController.text.trim().isNotEmpty &&
|
||||
nameText.isNotEmpty &&
|
||||
secret.isNotEmpty &&
|
||||
isUnique &&
|
||||
issuerNoColon &&
|
||||
@ -311,11 +316,9 @@ class _OathAddAccountPageState extends ConsumerState<OathAddAccountPage> {
|
||||
|
||||
void submit() async {
|
||||
if (secretLengthValid) {
|
||||
final issuer = _issuerController.text.trim();
|
||||
|
||||
final cred = CredentialData(
|
||||
issuer: issuer.isEmpty ? null : issuer,
|
||||
name: _accountController.text.trim(),
|
||||
issuer: issuerText.isEmpty ? null : issuerText,
|
||||
name: nameText,
|
||||
secret: secret,
|
||||
oathType: _oathType,
|
||||
hashAlgorithm: _hashAlgorithm,
|
||||
@ -389,12 +392,11 @@ class _OathAddAccountPageState extends ConsumerState<OathAddAccountPage> {
|
||||
controller: _issuerController,
|
||||
autofocus: widget.credentialData == null,
|
||||
enabled: issuerRemaining > 0,
|
||||
maxLength: max(issuerRemaining, 1),
|
||||
maxLength: issuerMaxLength,
|
||||
inputFormatters: [
|
||||
limitBytesLength(issuerRemaining),
|
||||
],
|
||||
buildCounter:
|
||||
buildByteCounterFor(_issuerController.text.trim()),
|
||||
buildCounter: buildByteCounterFor(issuerText),
|
||||
decoration: InputDecoration(
|
||||
border: const OutlineInputBorder(),
|
||||
labelText:
|
||||
@ -402,10 +404,12 @@ class _OathAddAccountPageState extends ConsumerState<OathAddAccountPage> {
|
||||
helperText:
|
||||
'', // Prevents dialog resizing when disabled
|
||||
prefixIcon: const Icon(Icons.business_outlined),
|
||||
errorText: issuerNoColon
|
||||
? null
|
||||
: AppLocalizations.of(context)!
|
||||
.oath_invalid_character_issuer,
|
||||
errorText: (byteLength(issuerText) > issuerMaxLength)
|
||||
? '' // needs empty string to render as error
|
||||
: issuerNoColon
|
||||
? null
|
||||
: AppLocalizations.of(context)!
|
||||
.oath_invalid_character_issuer,
|
||||
),
|
||||
textInputAction: TextInputAction.next,
|
||||
onChanged: (value) {
|
||||
@ -420,9 +424,8 @@ class _OathAddAccountPageState extends ConsumerState<OathAddAccountPage> {
|
||||
TextField(
|
||||
key: keys.nameField,
|
||||
controller: _accountController,
|
||||
maxLength: max(nameRemaining, 1),
|
||||
buildCounter:
|
||||
buildByteCounterFor(_accountController.text.trim()),
|
||||
maxLength: nameMaxLength,
|
||||
buildCounter: buildByteCounterFor(nameText),
|
||||
inputFormatters: [limitBytesLength(nameRemaining)],
|
||||
decoration: InputDecoration(
|
||||
border: const OutlineInputBorder(),
|
||||
@ -431,9 +434,12 @@ class _OathAddAccountPageState extends ConsumerState<OathAddAccountPage> {
|
||||
AppLocalizations.of(context)!.oath_account_name,
|
||||
helperText:
|
||||
'', // Prevents dialog resizing when disabled
|
||||
errorText: isUnique
|
||||
? null
|
||||
: AppLocalizations.of(context)!.oath_duplicate_name,
|
||||
errorText: (byteLength(nameText) > nameMaxLength)
|
||||
? '' // needs empty string to render as error
|
||||
: isUnique
|
||||
? null
|
||||
: AppLocalizations.of(context)!
|
||||
.oath_duplicate_name,
|
||||
),
|
||||
textInputAction: TextInputAction.next,
|
||||
onChanged: (value) {
|
||||
|
@ -1,5 +1,5 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// This file is generated by running ./set-version.py <version> <build>
|
||||
|
||||
const String version = '6.0.2-dev.1';
|
||||
const int build = 60004;
|
||||
const String version = '6.0.3-dev.0';
|
||||
const int build = 60009;
|
||||
|
@ -28,10 +28,18 @@ int byteLength(String value) => utf8.encode(value).length;
|
||||
/// used rather than number of characters. [currentValue] should always match
|
||||
/// the input text value to measure.
|
||||
InputCounterWidgetBuilder buildByteCounterFor(String currentValue) =>
|
||||
(context, {required currentLength, required isFocused, maxLength}) => Text(
|
||||
maxLength != null ? '${byteLength(currentValue)}/$maxLength' : '',
|
||||
style: Theme.of(context).textTheme.caption,
|
||||
);
|
||||
(context, {required currentLength, required isFocused, maxLength}) {
|
||||
final theme = Theme.of(context);
|
||||
final caption = theme.textTheme.caption;
|
||||
final style = (byteLength(currentValue) <= (maxLength ?? 0))
|
||||
? caption
|
||||
: caption?.copyWith(color: theme.errorColor);
|
||||
return Text(
|
||||
maxLength != null ? '${byteLength(currentValue)}/$maxLength' : '',
|
||||
style: style,
|
||||
semanticsLabel: 'Character count',
|
||||
);
|
||||
};
|
||||
|
||||
/// Limits the input in length based on the byte length when encoded.
|
||||
/// This is generally used together with [buildByteCounterFor].
|
||||
|
@ -407,7 +407,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.11;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.15;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = macosx;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
@ -429,6 +429,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.15;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.yubico.yubioath;
|
||||
PRODUCT_NAME = "Yubico Authenticator";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
@ -488,7 +489,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.11;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.15;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = macosx;
|
||||
@ -535,7 +536,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.11;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.15;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = macosx;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
@ -557,6 +558,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.15;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.yubico.yubioath;
|
||||
PRODUCT_NAME = "Yubico Authenticator";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
@ -579,6 +581,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.15;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.yubico.yubioath;
|
||||
PRODUCT_NAME = "Yubico Authenticator";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
|
81
macos/release-macos.sh
Normal file
81
macos/release-macos.sh
Normal file
@ -0,0 +1,81 @@
|
||||
#!/bin/sh
|
||||
|
||||
if [ -z "$1" ]
|
||||
then
|
||||
echo "No username given"
|
||||
exit
|
||||
fi
|
||||
|
||||
if [ -z "$2" ]
|
||||
then
|
||||
echo "No password given"
|
||||
exit
|
||||
fi
|
||||
|
||||
if ! command -v create-dmg &> /dev/null
|
||||
then
|
||||
echo "create-dmg could not be found"
|
||||
exit
|
||||
fi
|
||||
|
||||
|
||||
echo "# Extract .app from .tar.gz"
|
||||
tar -xzvf yubioath-desktop*.tar.gz
|
||||
|
||||
echo "# Sign the main binaries, with the entitlements"
|
||||
codesign -f --timestamp --options runtime --entitlements helper.entitlements --sign 'Application' Yubico\ Authenticator.app/Contents/Resources/helper/authenticator-helper
|
||||
codesign -f --timestamp --options runtime --entitlements helper.entitlements --sign 'Application' Yubico\ Authenticator.app/Contents/Resources/helper-arm64/authenticator-helper
|
||||
|
||||
echo "# Sign the dylib and so files, without entitlements"
|
||||
cd Yubico\ Authenticator.app/
|
||||
codesign -f --timestamp --options runtime --sign 'Application' $(find Contents/Resources/helper/ -name "*.dylib" -o -name "*.so")
|
||||
codesign -f --timestamp --options runtime --sign 'Application' $(find Contents/Resources/helper-arm64/ -name "*.dylib" -o -name "*.so")
|
||||
cd ..
|
||||
|
||||
echo "# Sign the Python binary (if it exists), without entitlements"
|
||||
codesign -f --timestamp --options runtime --sign 'Application' Yubico\ Authenticator.app/Contents/Resources/helper-arm64/Python
|
||||
codesign -f --timestamp --options runtime --sign 'Application' Yubico\ Authenticator.app/Contents/Resources/helper/Python
|
||||
|
||||
echo "# Sign the GUI"
|
||||
codesign -f --timestamp --options runtime --sign 'Application' --entitlements Release.entitlements --deep "Yubico Authenticator.app"
|
||||
|
||||
echo "# Compress the .app to .zip and notarize"
|
||||
ditto -c -k --sequesterRsrc --keepParent "Yubico Authenticator.app" "Yubico Authenticator.zip"
|
||||
RES=$(xcrun altool -t osx -f "Yubico Authenticator.zip" --primary-bundle-id com.yubico.authenticator --notarize-app -u $1 -p $2)
|
||||
echo ${RES}
|
||||
ERRORS=${RES:0:9}
|
||||
if [ "$ERRORS" != "No errors" ]; then
|
||||
echo "Error uploading for notarization"
|
||||
exit
|
||||
fi
|
||||
UUID=${RES#*=}
|
||||
STATUS=$(xcrun altool --notarization-info $UUID -u $1 -p $2)
|
||||
|
||||
while true
|
||||
do
|
||||
if [[ "$STATUS" == *"in progress"* ]]; then
|
||||
echo "Notarization still in progress. Sleep 30s."
|
||||
sleep 30
|
||||
echo "Retrieving status again."
|
||||
STATUS=$(xcrun altool --notarization-info $UUID -u $1 -p $2)
|
||||
else
|
||||
echo "Status changed."
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
echo "${STATUS}"
|
||||
|
||||
if [[ "$STATUS" == *"success"* ]]; then
|
||||
echo "Notarization successfull. Staple the .app"
|
||||
xcrun stapler staple -v "Yubico Authenticator.app"
|
||||
|
||||
echo "# Create dmg"
|
||||
rm yubioath-desktop.dmg # Remove old .dmg
|
||||
mkdir source_folder
|
||||
mv "Yubico Authenticator.app" source_folder
|
||||
sh create-dmg.sh
|
||||
echo "# .dmg created. Everything should be ready for release!"
|
||||
fi
|
||||
|
||||
echo "# End of script"
|
@ -18,7 +18,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
||||
|
||||
# This field is updated by running ./set-version.py <version>
|
||||
# DO NOT MANUALLY EDIT THIS!
|
||||
version: 6.0.2-dev.1+60004
|
||||
version: 6.0.3-dev.0+60009
|
||||
|
||||
environment:
|
||||
sdk: ">=2.17.0 <3.0.0"
|
||||
|
@ -1,4 +1,4 @@
|
||||
$version="6.0.2-dev.1"
|
||||
$version="6.0.3-dev.0"
|
||||
|
||||
echo "Renaming the Actions folder and moving it"
|
||||
mv yubioath-desktop-* release
|
||||
|
Loading…
Reference in New Issue
Block a user