feat(@desktop/general): (macos) Keychain manager added
LocalAuthentication class - used to authenticate OS' logged user (using Touch Id) Keychain class - able to store/read/remove item from the Keychain KeychainManager class - manages the flow of storing/reading/removing an item from the Keychain using own sync/async methods This change is required as part of the feature issue-2675
This commit is contained in:
parent
4d10692572
commit
5dc926f665
|
@ -1027,6 +1027,19 @@ DOS_API void dos_qsettings_delete(DosQSettings* vptr);
|
||||||
|
|
||||||
#pragma endregion
|
#pragma endregion
|
||||||
|
|
||||||
|
#pragma region KeychainManager exposed methods
|
||||||
|
|
||||||
|
DOS_API DosKeychainManager* dos_keychainmanager_create(const char* service,
|
||||||
|
const char* authenticationReason);
|
||||||
|
DOS_API char* dos_keychainmanager_read_data_sync(DosKeychainManager* vptr,
|
||||||
|
const char* key);
|
||||||
|
DOS_API void dos_keychainmanager_read_data_async(DosKeychainManager* vptr,
|
||||||
|
const char* key);
|
||||||
|
DOS_API void dos_keychainmanager_store_data_async(DosKeychainManager* vptr,
|
||||||
|
const char* key, const char* data);
|
||||||
|
DOS_API void dos_keychainmanager_delete_data_async(DosKeychainManager* vptr,
|
||||||
|
const char* key);
|
||||||
|
DOS_API void dos_keychainmanager_delete(DosKeychainManager* vptr);
|
||||||
|
|
||||||
#pragma endregion
|
#pragma endregion
|
||||||
|
|
||||||
|
|
|
@ -107,6 +107,8 @@ typedef void DosEvent;
|
||||||
/// A pointer to a os notification object which is actualy a QObject
|
/// A pointer to a os notification object which is actualy a QObject
|
||||||
typedef DosQObject DosOSNotification;
|
typedef DosQObject DosOSNotification;
|
||||||
|
|
||||||
|
/// A pointer to a keychain manager object which is actualy a QObject
|
||||||
|
typedef DosQObject DosKeychainManager;
|
||||||
|
|
||||||
|
|
||||||
/// A pixmap callback to be supplied to an image provider
|
/// A pixmap callback to be supplied to an image provider
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
#ifndef KEYCHAIN_H
|
||||||
|
#define KEYCHAIN_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
|
||||||
|
namespace Status
|
||||||
|
{
|
||||||
|
class Keychain : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
enum Error {
|
||||||
|
NoError=0,
|
||||||
|
EntryNotFound,
|
||||||
|
CouldNotDeleteEntry,
|
||||||
|
AccessDeniedByUser,
|
||||||
|
AccessDenied,
|
||||||
|
NoBackendAvailable,
|
||||||
|
NotImplemented,
|
||||||
|
OtherError
|
||||||
|
};
|
||||||
|
|
||||||
|
Keychain(const QString& service, QObject *parent = nullptr);
|
||||||
|
|
||||||
|
void readItem(const QString& key);
|
||||||
|
void writeItem(const QString& key, const QString& data);
|
||||||
|
void deleteItem(const QString& key);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void success(QString data);
|
||||||
|
void error(int error, const QString& errorString);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QString m_service;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,96 @@
|
||||||
|
#ifndef KEYCHAIN_MANAGER_H
|
||||||
|
#define KEYCHAIN_MANAGER_H
|
||||||
|
|
||||||
|
#include "Keychain.h"
|
||||||
|
#include "LocalAuthentication.h"
|
||||||
|
|
||||||
|
namespace Status
|
||||||
|
{
|
||||||
|
class KeychainManager : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Constructor defining name of the service for storing in the Keychain
|
||||||
|
* and the reason for requesting authorisation via touch id.
|
||||||
|
*
|
||||||
|
* @param service Service name used in Keychain.
|
||||||
|
* @param authenticationReason Reason for requestion touch id authorization.
|
||||||
|
*/
|
||||||
|
KeychainManager(const QString& service,
|
||||||
|
const QString& authenticationReason, QObject* parent = nullptr);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Synchronously reads @data stored in the Keychain under the @key and
|
||||||
|
* returns stored @data. In case of any error an empty string will be
|
||||||
|
* returned and error signal will be emitted.
|
||||||
|
*
|
||||||
|
* @param key Key which is stored in the Keychain.
|
||||||
|
*/
|
||||||
|
QString readDataSync(const QString& key) const;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Asynchronously reads @data stored in the Keychain under the @key.
|
||||||
|
* Onces it's read success signal will be emitted containing read data,
|
||||||
|
* otherwise error signal will be emitted.
|
||||||
|
*
|
||||||
|
* @param key Key which is stored in the Keychain.
|
||||||
|
*/
|
||||||
|
void readDataAsync(const QString& key);
|
||||||
|
/*!
|
||||||
|
* Asynchronously stores @data under the @key in the Keychain.
|
||||||
|
* Onces @data is stored success signal will be emitted, otherwise error
|
||||||
|
* signal will be emitted.
|
||||||
|
*
|
||||||
|
* @param key Key which is stored in the Keychain.
|
||||||
|
* @param data Data which is stored in the Keychain.
|
||||||
|
*/
|
||||||
|
void storeDataAsync(const QString& key, const QString& data);
|
||||||
|
/*!
|
||||||
|
* Asynchronously deletes @data stored in the Keychain under the @key.
|
||||||
|
* Onces it's deleted success signal will be emitted, otherwise error
|
||||||
|
* signal will be emitted.
|
||||||
|
*
|
||||||
|
* @param key Key which is stored in the Keychain.
|
||||||
|
*/
|
||||||
|
void deleteDataAsync(const QString& key);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
/*!
|
||||||
|
* Notifies that action was performed successfully and in case of asyc
|
||||||
|
* read contains read @data, in other cases @data param is empty.
|
||||||
|
*
|
||||||
|
* @param data Data read from the Keychain.
|
||||||
|
*/
|
||||||
|
void success(QString data);
|
||||||
|
/*!
|
||||||
|
* Notifies that an error with @error code and @errorString description
|
||||||
|
* occured.
|
||||||
|
*
|
||||||
|
* @param type Determins origin of the error ("authentication" or "keychain")
|
||||||
|
* @param code Error code.
|
||||||
|
* @param errorString Error description.
|
||||||
|
*/
|
||||||
|
void error(QString type, int code, const QString& errorString);
|
||||||
|
|
||||||
|
#ifdef Q_OS_MACOS
|
||||||
|
private:
|
||||||
|
void process(const std::function<void()> action);
|
||||||
|
|
||||||
|
QString readDataSyncMacOs(const QString& key) const;
|
||||||
|
void readDataAsyncMacOs(const QString& key);
|
||||||
|
void storeDataAsyncMacOs(const QString& key, const QString& data);
|
||||||
|
void deleteDataAsyncMacOs(const QString& key);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QString m_authenticationReason;
|
||||||
|
std::unique_ptr<LocalAuthentication> m_localAuth;
|
||||||
|
std::unique_ptr<Keychain> m_keychain;
|
||||||
|
QMetaObject::Connection m_actionConnection;
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,33 @@
|
||||||
|
#ifndef LOCAL_AUTHENTICATION_H
|
||||||
|
#define LOCAL_AUTHENTICATION_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
|
||||||
|
namespace Status
|
||||||
|
{
|
||||||
|
class LocalAuthentication : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
enum Error {
|
||||||
|
Domain=0,
|
||||||
|
AppCanceled,
|
||||||
|
SystemCanceled,
|
||||||
|
UserCanceled,
|
||||||
|
TouchIdNotAvailable,
|
||||||
|
TouchIdNotConfigured,
|
||||||
|
WrongCredentials,
|
||||||
|
OtherError
|
||||||
|
};
|
||||||
|
|
||||||
|
void runAuthentication(const QString& authenticationReason);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void success();
|
||||||
|
void error(int error, const QString& errorString);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -66,6 +66,7 @@
|
||||||
#include "DOtherSide/Status/DockShowAppEvent.h"
|
#include "DOtherSide/Status/DockShowAppEvent.h"
|
||||||
#include "DOtherSide/Status/OSThemeEvent.h"
|
#include "DOtherSide/Status/OSThemeEvent.h"
|
||||||
#include "DOtherSide/Status/OSNotification.h"
|
#include "DOtherSide/Status/OSNotification.h"
|
||||||
|
#include "DOtherSide/Status/KeychainManager.h"
|
||||||
#include "DOtherSide/DosSpellchecker.h"
|
#include "DOtherSide/DosSpellchecker.h"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
@ -1459,6 +1460,60 @@ void dos_qsettings_delete(DosQSettings* vptr)
|
||||||
}
|
}
|
||||||
#pragma endregion
|
#pragma endregion
|
||||||
|
|
||||||
|
#pragma region KeychainManager
|
||||||
|
DosKeychainManager* dos_keychainmanager_create(const char* service,
|
||||||
|
const char* authenticationReason)
|
||||||
|
{
|
||||||
|
return new Status::KeychainManager(QString(service), QString(authenticationReason));
|
||||||
|
}
|
||||||
|
|
||||||
|
char* dos_keychainmanager_read_data_sync(DosKeychainManager* vptr,
|
||||||
|
const char* key)
|
||||||
|
{
|
||||||
|
auto obj = static_cast<Status::KeychainManager*>(vptr);
|
||||||
|
if(obj)
|
||||||
|
{
|
||||||
|
return convert_to_cstring(obj->readDataSync(QString(key)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return convert_to_cstring(QString());
|
||||||
|
}
|
||||||
|
|
||||||
|
void dos_keychainmanager_read_data_async(DosKeychainManager* vptr,
|
||||||
|
const char* key)
|
||||||
|
{
|
||||||
|
auto obj = static_cast<Status::KeychainManager*>(vptr);
|
||||||
|
if(obj)
|
||||||
|
obj->readDataAsync(QString(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
void dos_keychainmanager_store_data_async(DosKeychainManager* vptr,
|
||||||
|
const char* key, const char* data)
|
||||||
|
{
|
||||||
|
auto obj = static_cast<Status::KeychainManager*>(vptr);
|
||||||
|
if(obj)
|
||||||
|
{
|
||||||
|
obj->storeDataAsync(QString(key), QString(data));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void dos_keychainmanager_delete_data_async(DosKeychainManager* vptr,
|
||||||
|
const char* key)
|
||||||
|
{
|
||||||
|
auto obj = static_cast<Status::KeychainManager*>(vptr);
|
||||||
|
if(obj)
|
||||||
|
obj->deleteDataAsync(QString(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
void dos_keychainmanager_delete(DosKeychainManager* vptr)
|
||||||
|
{
|
||||||
|
auto qobject = static_cast<QObject*>(vptr);
|
||||||
|
if(qobject)
|
||||||
|
qobject->deleteLater();
|
||||||
|
|
||||||
|
}
|
||||||
|
#pragma endregion
|
||||||
|
|
||||||
char* dos_to_local_file(const char* fileUrl)
|
char* dos_to_local_file(const char* fileUrl)
|
||||||
{
|
{
|
||||||
return convert_to_cstring(QUrl(QString::fromUtf8(fileUrl)).toLocalFile());
|
return convert_to_cstring(QUrl(QString::fromUtf8(fileUrl)).toLocalFile());
|
||||||
|
@ -1467,4 +1522,4 @@ char* dos_to_local_file(const char* fileUrl)
|
||||||
char* dos_from_local_file(const char* filePath)
|
char* dos_from_local_file(const char* filePath)
|
||||||
{
|
{
|
||||||
return convert_to_cstring(QUrl::fromLocalFile(QString::fromUtf8(filePath)).toString());
|
return convert_to_cstring(QUrl::fromLocalFile(QString::fromUtf8(filePath)).toString());
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,162 @@
|
||||||
|
#include "DOtherSide/Status/Keychain.h"
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
#import <Security/Security.h>
|
||||||
|
|
||||||
|
using namespace Status;
|
||||||
|
|
||||||
|
struct ErrorDescription
|
||||||
|
{
|
||||||
|
Keychain::Error code;
|
||||||
|
QString message;
|
||||||
|
|
||||||
|
ErrorDescription(Keychain::Error code, const QString &message)
|
||||||
|
: code(code)
|
||||||
|
, message(message)
|
||||||
|
{}
|
||||||
|
|
||||||
|
static ErrorDescription fromStatus(OSStatus status)
|
||||||
|
{
|
||||||
|
switch(status) {
|
||||||
|
case errSecSuccess:
|
||||||
|
return ErrorDescription(Keychain::NoError,
|
||||||
|
"No error");
|
||||||
|
case errSecItemNotFound:
|
||||||
|
return ErrorDescription(Keychain::EntryNotFound,
|
||||||
|
"The specified item could not be found in the keychain");
|
||||||
|
case errSecUserCanceled:
|
||||||
|
return ErrorDescription(Keychain::AccessDeniedByUser,
|
||||||
|
"User canceled the operation");
|
||||||
|
case errSecInteractionNotAllowed:
|
||||||
|
return ErrorDescription(Keychain::AccessDenied,
|
||||||
|
"User interaction is not allowed");
|
||||||
|
case errSecNotAvailable:
|
||||||
|
return ErrorDescription(Keychain::AccessDenied,
|
||||||
|
"No keychain is available. You may need to restart your computer");
|
||||||
|
case errSecAuthFailed:
|
||||||
|
return ErrorDescription(Keychain::AccessDenied,
|
||||||
|
"The user name or passphrase you entered is not correct");
|
||||||
|
case errSecVerifyFailed:
|
||||||
|
return ErrorDescription(Keychain::AccessDenied,
|
||||||
|
"A cryptographic verification failure has occurred");
|
||||||
|
case errSecUnimplemented:
|
||||||
|
return ErrorDescription(Keychain::NotImplemented,
|
||||||
|
"Function or operation not implemented");
|
||||||
|
case errSecIO:
|
||||||
|
return ErrorDescription(Keychain::OtherError,
|
||||||
|
"I/O error");
|
||||||
|
case errSecOpWr:
|
||||||
|
return ErrorDescription(Keychain::OtherError,
|
||||||
|
"Already open with with write permission");
|
||||||
|
case errSecParam:
|
||||||
|
return ErrorDescription(Keychain::OtherError,
|
||||||
|
"Invalid parameters passed to a function");
|
||||||
|
case errSecAllocate:
|
||||||
|
return ErrorDescription(Keychain::OtherError,
|
||||||
|
"Failed to allocate memory");
|
||||||
|
case errSecBadReq:
|
||||||
|
return ErrorDescription(Keychain::OtherError,
|
||||||
|
"Bad parameter or invalid state for operation");
|
||||||
|
case errSecInternalComponent:
|
||||||
|
return ErrorDescription(Keychain::OtherError,
|
||||||
|
"An internal component failed");
|
||||||
|
case errSecDuplicateItem:
|
||||||
|
return ErrorDescription(Keychain::OtherError,
|
||||||
|
"The specified item already exists in the keychain");
|
||||||
|
case errSecDecode:
|
||||||
|
return ErrorDescription(Keychain::OtherError,
|
||||||
|
"Unable to decode the provided data");
|
||||||
|
}
|
||||||
|
|
||||||
|
return ErrorDescription(Keychain::OtherError, "Unknown error");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Keychain::Keychain(const QString& service, QObject *parent)
|
||||||
|
: QObject(parent)
|
||||||
|
, m_service(service)
|
||||||
|
{}
|
||||||
|
|
||||||
|
void Keychain::readItem(const QString& key)
|
||||||
|
{
|
||||||
|
NSDictionary *const query = @{
|
||||||
|
(__bridge id) kSecClass: (__bridge id) kSecClassGenericPassword,
|
||||||
|
(__bridge id) kSecAttrService: (__bridge NSString *) m_service.toCFString(),
|
||||||
|
(__bridge id) kSecAttrAccount: (__bridge NSString *) key.toCFString(),
|
||||||
|
(__bridge id) kSecReturnData: @YES,
|
||||||
|
};
|
||||||
|
|
||||||
|
CFTypeRef dataRef = nil;
|
||||||
|
const OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef) query, &dataRef);
|
||||||
|
|
||||||
|
if (status == errSecSuccess) {
|
||||||
|
QByteArray data;
|
||||||
|
if (dataRef)
|
||||||
|
data = QByteArray::fromCFData((CFDataRef) dataRef);
|
||||||
|
|
||||||
|
emit success(QString::fromUtf8(data));
|
||||||
|
} else {
|
||||||
|
const ErrorDescription ed = ErrorDescription::fromStatus(status);
|
||||||
|
emit error(ed.code,
|
||||||
|
QString("Could not retrieve private key from keystore: %1").arg(ed.message));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dataRef)
|
||||||
|
[dataRef release];
|
||||||
|
}
|
||||||
|
|
||||||
|
void Keychain::writeItem(const QString& key, const QString& data)
|
||||||
|
{
|
||||||
|
NSDictionary *const query = @{
|
||||||
|
(__bridge id) kSecClass: (__bridge id) kSecClassGenericPassword,
|
||||||
|
(__bridge id) kSecAttrService: (__bridge NSString *) m_service.toCFString(),
|
||||||
|
(__bridge id) kSecAttrAccount: (__bridge NSString *) key.toCFString(),
|
||||||
|
};
|
||||||
|
|
||||||
|
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef) query, nil);
|
||||||
|
|
||||||
|
QByteArray baData = data.toUtf8();
|
||||||
|
if (status == errSecSuccess) {
|
||||||
|
NSDictionary *const update = @{
|
||||||
|
(__bridge id) kSecValueData: (__bridge NSData *) baData.toCFData(),
|
||||||
|
};
|
||||||
|
|
||||||
|
status = SecItemUpdate((__bridge CFDictionaryRef) query, (__bridge CFDictionaryRef) update);
|
||||||
|
} else {
|
||||||
|
NSDictionary *const insert = @{
|
||||||
|
(__bridge id) kSecClass: (__bridge id) kSecClassGenericPassword,
|
||||||
|
(__bridge id) kSecAttrService: (__bridge NSString *) m_service.toCFString(),
|
||||||
|
(__bridge id) kSecAttrAccount: (__bridge NSString *) key.toCFString(),
|
||||||
|
(__bridge id) kSecValueData: (__bridge NSData *) baData.toCFData(),
|
||||||
|
};
|
||||||
|
|
||||||
|
status = SecItemAdd((__bridge CFDictionaryRef) insert, nil);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status == errSecSuccess) {
|
||||||
|
emit success(QString());
|
||||||
|
} else {
|
||||||
|
const ErrorDescription ed = ErrorDescription::fromStatus(status);
|
||||||
|
emit error(ed.code,
|
||||||
|
QString("Could not store data in settings: %1").arg(ed.message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Keychain::deleteItem(const QString& key)
|
||||||
|
{
|
||||||
|
const NSDictionary *const query = @{
|
||||||
|
(__bridge id) kSecClass: (__bridge id) kSecClassGenericPassword,
|
||||||
|
(__bridge id) kSecAttrService: (__bridge NSString *) m_service.toCFString(),
|
||||||
|
(__bridge id) kSecAttrAccount: (__bridge NSString *) key.toCFString(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const OSStatus status = SecItemDelete((__bridge CFDictionaryRef) query);
|
||||||
|
|
||||||
|
if (status == errSecSuccess) {
|
||||||
|
emit success(QString());
|
||||||
|
} else {
|
||||||
|
const ErrorDescription ed = ErrorDescription::fromStatus(status);
|
||||||
|
emit error(ed.code,
|
||||||
|
QString("Could not remove private key from keystore: %1").arg(ed.message));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
#include "DOtherSide/Status/KeychainManager.h"
|
||||||
|
|
||||||
|
using namespace Status;
|
||||||
|
|
||||||
|
KeychainManager::KeychainManager(const QString& service,
|
||||||
|
const QString& authenticationReason, QObject* parent)
|
||||||
|
: QObject(parent)
|
||||||
|
{
|
||||||
|
#ifdef Q_OS_MACOS
|
||||||
|
m_authenticationReason = authenticationReason;
|
||||||
|
m_localAuth = std::unique_ptr<LocalAuthentication>(new LocalAuthentication());
|
||||||
|
m_keychain = std::unique_ptr<Keychain>(new Keychain(service));
|
||||||
|
|
||||||
|
connect(m_localAuth.get(), &LocalAuthentication::error, [this](int code, const QString& errorString){
|
||||||
|
emit error("authentication", code, errorString);
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(m_keychain.get(), &Keychain::success, this, &KeychainManager::success);
|
||||||
|
connect(m_keychain.get(), &Keychain::error, [this](int code, const QString& errorString){
|
||||||
|
emit error("keychain", code, errorString);
|
||||||
|
});
|
||||||
|
#else
|
||||||
|
// Marked params unused until we need them for Win/Linux.
|
||||||
|
Q_UNUSED(authenticationReason);
|
||||||
|
Q_UNUSED(service);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
QString KeychainManager::readDataSync(const QString& key) const
|
||||||
|
{
|
||||||
|
#ifdef Q_OS_MACOS
|
||||||
|
return readDataSyncMacOs(key);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
|
|
||||||
|
void KeychainManager::readDataAsync(const QString& key)
|
||||||
|
{
|
||||||
|
#ifdef Q_OS_MACOS
|
||||||
|
readDataAsyncMacOs(key);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void KeychainManager::storeDataAsync(const QString& key, const QString& data)
|
||||||
|
{
|
||||||
|
#ifdef Q_OS_MACOS
|
||||||
|
storeDataAsyncMacOs(key, data);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void KeychainManager::deleteDataAsync(const QString& key)
|
||||||
|
{
|
||||||
|
#ifdef Q_OS_MACOS
|
||||||
|
deleteDataAsyncMacOs(key);
|
||||||
|
#endif
|
||||||
|
}
|
|
@ -0,0 +1,75 @@
|
||||||
|
#include "DOtherSide/Status/KeychainManager.h"
|
||||||
|
|
||||||
|
#include <QEventLoop>
|
||||||
|
using namespace Status;
|
||||||
|
|
||||||
|
QString KeychainManager::readDataSyncMacOs(const QString& key) const
|
||||||
|
{
|
||||||
|
QString storedData;
|
||||||
|
QEventLoop loop;
|
||||||
|
|
||||||
|
auto onAuthenticationSuccess = [this, &key](){
|
||||||
|
m_keychain->readItem(key);
|
||||||
|
};
|
||||||
|
|
||||||
|
auto onReadItemSuccess = [&loop, &storedData](QString data){
|
||||||
|
storedData = data;
|
||||||
|
loop.quit();
|
||||||
|
};
|
||||||
|
|
||||||
|
connect(m_localAuth.get(), &LocalAuthentication::success, onAuthenticationSuccess);
|
||||||
|
|
||||||
|
connect(m_keychain.get(), &Keychain::success, onReadItemSuccess);
|
||||||
|
|
||||||
|
connect(m_localAuth.get(), &LocalAuthentication::error, [this, &loop](int code, const QString& errorString){
|
||||||
|
Q_UNUSED(code)
|
||||||
|
Q_UNUSED(errorString)
|
||||||
|
loop.quit();
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(m_keychain.get(), &Keychain::error, [this, &loop](int code, const QString& errorString){
|
||||||
|
Q_UNUSED(code)
|
||||||
|
Q_UNUSED(errorString)
|
||||||
|
loop.quit();
|
||||||
|
});
|
||||||
|
|
||||||
|
m_localAuth->runAuthentication(m_authenticationReason);
|
||||||
|
loop.exec();
|
||||||
|
|
||||||
|
return storedData;
|
||||||
|
}
|
||||||
|
|
||||||
|
void KeychainManager::readDataAsyncMacOs(const QString& key)
|
||||||
|
{
|
||||||
|
auto readAction = [this, key](){
|
||||||
|
m_keychain->readItem(key);
|
||||||
|
};
|
||||||
|
|
||||||
|
process(readAction);
|
||||||
|
}
|
||||||
|
|
||||||
|
void KeychainManager::storeDataAsyncMacOs(const QString& key, const QString& data)
|
||||||
|
{
|
||||||
|
auto writeAction = [this, key, data](){
|
||||||
|
m_keychain->writeItem(key, data);
|
||||||
|
};
|
||||||
|
|
||||||
|
process(writeAction);
|
||||||
|
}
|
||||||
|
|
||||||
|
void KeychainManager::deleteDataAsyncMacOs(const QString& key)
|
||||||
|
{
|
||||||
|
auto deleteAction = [this, key](){
|
||||||
|
m_keychain->deleteItem(key);
|
||||||
|
};
|
||||||
|
|
||||||
|
process(deleteAction);
|
||||||
|
}
|
||||||
|
|
||||||
|
void KeychainManager::process(const std::function<void()> action)
|
||||||
|
{
|
||||||
|
disconnect(m_actionConnection);
|
||||||
|
m_actionConnection = connect(m_localAuth.get(), &LocalAuthentication::success, action);
|
||||||
|
|
||||||
|
m_localAuth->runAuthentication(m_authenticationReason);
|
||||||
|
}
|
|
@ -0,0 +1,152 @@
|
||||||
|
#include "DOtherSide/Status/LocalAuthentication.h"
|
||||||
|
|
||||||
|
#include <AvailabilityMacros.h>
|
||||||
|
#import <LocalAuthentication/LocalAuthentication.h>
|
||||||
|
|
||||||
|
using namespace Status;
|
||||||
|
|
||||||
|
struct ErrorDescription
|
||||||
|
{
|
||||||
|
LocalAuthentication::Error code;
|
||||||
|
QString message;
|
||||||
|
|
||||||
|
ErrorDescription(LocalAuthentication::Error code, const QString &message)
|
||||||
|
: code(code)
|
||||||
|
, message(message)
|
||||||
|
{}
|
||||||
|
|
||||||
|
static ErrorDescription fromLAError(NSInteger err)
|
||||||
|
{
|
||||||
|
NSProcessInfo *pInfo = [NSProcessInfo processInfo];
|
||||||
|
NSOperatingSystemVersion info = [pInfo operatingSystemVersion];
|
||||||
|
|
||||||
|
#ifdef __MAC_OS_X_VERSION_MAX_ALLOWED
|
||||||
|
#if defined MAC_OS_VERSION_11_2 \
|
||||||
|
&& MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_VERSION_11_2
|
||||||
|
switch(err) {
|
||||||
|
case LAErrorBiometryDisconnected: /* MacOs 11.2+ */
|
||||||
|
return ErrorDescription(LocalAuthentication::OtherError,
|
||||||
|
"The device supports biometry only using a removable accessory, but the paired accessory isn’t connected");
|
||||||
|
case LAErrorBiometryNotPaired: /* MacOs 11.2+ */
|
||||||
|
return ErrorDescription(LocalAuthentication::OtherError,
|
||||||
|
"The device supports biometry only using a removable accessory, but no accessory is paired");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined MAC_OS_X_VERSION_10_15 \
|
||||||
|
&& MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_15
|
||||||
|
switch(err) {
|
||||||
|
case LAErrorWatchNotAvailable: /* MacOs 10.15+ */
|
||||||
|
return ErrorDescription(LocalAuthentication::OtherError,
|
||||||
|
"An attempt to authenticate with Apple Watch failed");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined MAC_OS_X_VERSION_10_13 \
|
||||||
|
&& MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_13
|
||||||
|
switch(err) {
|
||||||
|
case LAErrorBiometryLockout: /* MacOs 10.13+ */
|
||||||
|
return ErrorDescription(LocalAuthentication::OtherError,
|
||||||
|
"Biometry is locked because there were too many failed attempts");
|
||||||
|
case LAErrorBiometryNotAvailable: /* MacOs 10.13+ */
|
||||||
|
return ErrorDescription(LocalAuthentication::TouchIdNotAvailable,
|
||||||
|
"Biometry is not available on the device");
|
||||||
|
case LAErrorBiometryNotEnrolled: /* MacOs 10.13+ */
|
||||||
|
return ErrorDescription(LocalAuthentication::TouchIdNotConfigured,
|
||||||
|
"The user has no enrolled biometric identities");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined MAC_OS_X_VERSION_10_11 \
|
||||||
|
&& defined MAC_OS_X_VERSION_10_13 \
|
||||||
|
&& MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_11 \
|
||||||
|
&& MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_13
|
||||||
|
switch(err) {
|
||||||
|
case LAErrorTouchIDLockout: /* MacOs 10.11 - 10.13 */
|
||||||
|
return ErrorDescription(LocalAuthentication::OtherError,
|
||||||
|
"Touch ID is locked because there were too many failed attempts");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined MAC_OS_X_VERSION_10_10 \
|
||||||
|
&& defined MAC_OS_X_VERSION_10_13 \
|
||||||
|
&& MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_10 \
|
||||||
|
&& MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_13
|
||||||
|
switch(err) {
|
||||||
|
case LAErrorTouchIDNotAvailable: /* MacOs 10.10 - 10.13 */
|
||||||
|
return ErrorDescription(LocalAuthentication::TouchIdNotAvailable,
|
||||||
|
"Touch ID is not available on the device");
|
||||||
|
case LAErrorTouchIDNotEnrolled: /* MacOs 10.10 - 10.13 */
|
||||||
|
return ErrorDescription(LocalAuthentication::TouchIdNotConfigured,
|
||||||
|
"The user has no enrolled Touch ID fingers");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined MAC_OS_X_VERSION_10_11 \
|
||||||
|
&& MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_11
|
||||||
|
switch(err) {
|
||||||
|
case LAErrorInvalidContext: /* MacOs 10.11+ */
|
||||||
|
return ErrorDescription(LocalAuthentication::OtherError,
|
||||||
|
"The context was previously invalidated");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined MAC_OS_X_VERSION_10_10 \
|
||||||
|
&& MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_10
|
||||||
|
switch(err) {
|
||||||
|
case LAErrorSystemCancel: /* MacOs 10.10+ */
|
||||||
|
return ErrorDescription(LocalAuthentication::SystemCanceled,
|
||||||
|
"The system canceled authentication");
|
||||||
|
case LAErrorUserCancel: /* MacOs 10.10+ */
|
||||||
|
return ErrorDescription(LocalAuthentication::UserCanceled,
|
||||||
|
"The user tapped the cancel button in the authentication dialog");
|
||||||
|
case LAErrorAuthenticationFailed: /* MacOs 10.10+ */
|
||||||
|
return ErrorDescription(LocalAuthentication::WrongCredentials,
|
||||||
|
"The user failed to provide valid credentials");
|
||||||
|
case LAErrorNotInteractive: /* MacOs 10.10+ */
|
||||||
|
return ErrorDescription(LocalAuthentication::OtherError,
|
||||||
|
"Displaying the required authentication user interface is forbidden");
|
||||||
|
case LAErrorPasscodeNotSet: /* MacOs 10.10+ */
|
||||||
|
return ErrorDescription(LocalAuthentication::OtherError,
|
||||||
|
"A passcode isn’t set on the device");
|
||||||
|
case LAErrorUserFallback: /* MacOs 10.10+ */
|
||||||
|
return ErrorDescription(LocalAuthentication::OtherError,
|
||||||
|
"The user tapped the fallback button in the authentication dialog, but no fallback is available for the authentication policy");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return ErrorDescription(LocalAuthentication::OtherError, "Unknown error");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void LocalAuthentication::runAuthentication(const QString& authenticationReason)
|
||||||
|
{
|
||||||
|
LAContext *laContext = [[LAContext alloc] init];
|
||||||
|
NSError *authError = nil;
|
||||||
|
NSString *localizedReasonString = authenticationReason.toNSString();
|
||||||
|
|
||||||
|
if ([laContext canEvaluatePolicy:LAPolicyDeviceOwnerAuthentication error:&authError])
|
||||||
|
{
|
||||||
|
[laContext evaluatePolicy:LAPolicyDeviceOwnerAuthentication
|
||||||
|
localizedReason:localizedReasonString
|
||||||
|
reply:^(BOOL authenticated, NSError *err)
|
||||||
|
{
|
||||||
|
if (authenticated)
|
||||||
|
{
|
||||||
|
emit success();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const ErrorDescription ed = ErrorDescription::fromLAError([err code]);
|
||||||
|
emit error(ed.code, QString("User did not authenticate successfully: %1").arg(ed.message));
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const ErrorDescription ed = ErrorDescription::fromLAError([authError code]);
|
||||||
|
emit error(ed.code, QString("Could not evaluate policy: %1").arg(ed.message));
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
#include "DOtherSide/StatusNotification/OSNotification.h"
|
#include "DOtherSide/Status/OSNotification.h"
|
||||||
|
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
#include <shellapi.h>
|
#include <shellapi.h>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#include "DOtherSide/StatusNotification/OSNotification.h"
|
#include "DOtherSide/Status/OSNotification.h"
|
||||||
|
|
||||||
#ifdef Q_OS_MACOS
|
#ifdef Q_OS_MACOS
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue