From 7cf393095282077b0bbae3046826b67f4f20a8eb Mon Sep 17 00:00:00 2001 From: Ruben Beltran del Rio Date: Wed, 10 Sep 2025 16:12:55 +0200 Subject: Add auth key + complete task action --- QSThingsPlugin/Actions/CompleteTaskAction.h | 12 ++++ QSThingsPlugin/Actions/CompleteTaskAction.m | 41 +++++++++++ .../Actions/SetThingsAuthenticationKeyAction.h | 11 +++ .../Actions/SetThingsAuthenticationKeyAction.m | 21 ++++++ QSThingsPlugin/Helpers/KeychainHelper.h | 20 ++++++ QSThingsPlugin/Helpers/KeychainHelper.m | 83 ++++++++++++++++++++++ QSThingsPlugin/Parsers/QSThingsAreasParser.m | 4 +- QSThingsPlugin/QSThingsPluginSource.m | 3 +- QSThingsPlugin/QSThingsPluginSource.xib | 50 ------------- QSThingsPlugin/Types/Constants.h | 4 ++ QSThingsPlugin/Types/Constants.m | 4 ++ 11 files changed, 199 insertions(+), 54 deletions(-) create mode 100644 QSThingsPlugin/Actions/CompleteTaskAction.h create mode 100644 QSThingsPlugin/Actions/CompleteTaskAction.m create mode 100644 QSThingsPlugin/Actions/SetThingsAuthenticationKeyAction.h create mode 100644 QSThingsPlugin/Actions/SetThingsAuthenticationKeyAction.m create mode 100644 QSThingsPlugin/Helpers/KeychainHelper.h create mode 100644 QSThingsPlugin/Helpers/KeychainHelper.m delete mode 100644 QSThingsPlugin/QSThingsPluginSource.xib (limited to 'QSThingsPlugin') diff --git a/QSThingsPlugin/Actions/CompleteTaskAction.h b/QSThingsPlugin/Actions/CompleteTaskAction.h new file mode 100644 index 0000000..19609b9 --- /dev/null +++ b/QSThingsPlugin/Actions/CompleteTaskAction.h @@ -0,0 +1,12 @@ +#import "Constants.h" +#import "KeychainHelper.h" +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface CompleteTaskAction : QSActionProvider + +@end + +NS_ASSUME_NONNULL_END diff --git a/QSThingsPlugin/Actions/CompleteTaskAction.m b/QSThingsPlugin/Actions/CompleteTaskAction.m new file mode 100644 index 0000000..9673840 --- /dev/null +++ b/QSThingsPlugin/Actions/CompleteTaskAction.m @@ -0,0 +1,41 @@ +#import "CompleteTaskAction.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation CompleteTaskAction + +- (QSObject *)completeTask:(QSObject *)directObject +{ + NSSet *validTypes = [NSSet setWithObjects:kProjectType, kTaskType, nil]; + NSString *authenticationKey = [KeychainHelper authenticationKey]; + + if (!authenticationKey) { + NSLog(@"There is no authentication key."); + } + + if (authenticationKey && [validTypes containsObject:directObject.primaryType]) { + NSDictionary *contents = [directObject objectForType:directObject.primaryType]; + NSString *path = @"update"; + if (directObject.primaryType == kProjectType) { + path = @"update-project"; + } + if (contents) { + NSString *uuid = [contents objectForKey:@"uuid"]; + if (uuid) { + NSString *urlString = [NSString stringWithFormat:@"things:///%@?id=%@&auth-token=%@&completed=true", path, uuid, authenticationKey]; + NSURL *url = [NSURL URLWithString:urlString]; + + [[NSWorkspace sharedWorkspace] openURL:url]; + } else { + NSLog(@"Things object did not have a uuid."); + } + } else { + NSLog(@"Could not get the contents of the Things object."); + } + } + return directObject; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/QSThingsPlugin/Actions/SetThingsAuthenticationKeyAction.h b/QSThingsPlugin/Actions/SetThingsAuthenticationKeyAction.h new file mode 100644 index 0000000..376d6d4 --- /dev/null +++ b/QSThingsPlugin/Actions/SetThingsAuthenticationKeyAction.h @@ -0,0 +1,11 @@ +#import "KeychainHelper.h" +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface SetThingsAuthenticationKeyAction : QSActionProvider + +@end + +NS_ASSUME_NONNULL_END diff --git a/QSThingsPlugin/Actions/SetThingsAuthenticationKeyAction.m b/QSThingsPlugin/Actions/SetThingsAuthenticationKeyAction.m new file mode 100644 index 0000000..b9e7031 --- /dev/null +++ b/QSThingsPlugin/Actions/SetThingsAuthenticationKeyAction.m @@ -0,0 +1,21 @@ +#import "SetThingsAuthenticationKeyAction.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation SetThingsAuthenticationKeyAction + +- (QSObject *)setAuthenticationKey:(QSObject *)directObject +{ + + NSString *contents = [directObject displayName]; + if (contents) { + [KeychainHelper setAuthenticationKey:contents]; + } else { + [KeychainHelper deleteAuthenticationKey]; + } + return directObject; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/QSThingsPlugin/Helpers/KeychainHelper.h b/QSThingsPlugin/Helpers/KeychainHelper.h new file mode 100644 index 0000000..d935bec --- /dev/null +++ b/QSThingsPlugin/Helpers/KeychainHelper.h @@ -0,0 +1,20 @@ +// +// KeychainHelper.h +// Things Plugin +// +// Created by Ruben Beltran del Rio on 2025-09-10. +// + +#import "Constants.h" +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface KeychainHelper : NSObject ++ (NSString *)authenticationKey; ++ (OSStatus)setAuthenticationKey:(NSString *)authenticationKey; ++ (OSStatus)deleteAuthenticationKey; +@end + +NS_ASSUME_NONNULL_END diff --git a/QSThingsPlugin/Helpers/KeychainHelper.m b/QSThingsPlugin/Helpers/KeychainHelper.m new file mode 100644 index 0000000..9ce90f7 --- /dev/null +++ b/QSThingsPlugin/Helpers/KeychainHelper.m @@ -0,0 +1,83 @@ +// +// KeychainHelper.m +// Things Plugin +// +// Created by Ruben Beltran del Rio on 2025-09-10. +// + +#import "KeychainHelper.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation KeychainHelper + ++ (NSString *)authenticationKey { + const char *service = [kKeychainService UTF8String]; + const char *account = [kAuthenticationKeyKeychainKey UTF8String]; + + UInt32 authenticationKeyLength = 0; + void *authenticationKeyData = NULL; + + OSStatus status = SecKeychainFindGenericPassword( + NULL, (UInt32)strlen(service), service, (UInt32)strlen(account), account, + &authenticationKeyLength, &authenticationKeyData, NULL); + + if (status == errSecSuccess && authenticationKeyData != NULL) { + NSString *authenticationKey = [[NSString alloc] initWithBytes:authenticationKeyData + length:authenticationKeyLength + encoding:NSUTF8StringEncoding]; + SecKeychainItemFreeContent(NULL, authenticationKeyData); + return authenticationKey; + } + + return nil; +} + ++ (OSStatus)setAuthenticationKey:(NSString *)authenticationKey { + const char *service = [kKeychainService UTF8String]; + const char *account = [kAuthenticationKeyKeychainKey UTF8String]; + const char *authenticationKeyCString = [authenticationKey UTF8String]; + + // First try to find existing item + SecKeychainItemRef item = NULL; + OSStatus findStatus = SecKeychainFindGenericPassword( + NULL, (UInt32)strlen(service), service, (UInt32)strlen(account), account, + NULL, NULL, &item); + + OSStatus status; + if (findStatus == errSecSuccess) { + // Update existing item + status = SecKeychainItemModifyAttributesAndData( + item, NULL, (UInt32)strlen(authenticationKeyCString), authenticationKeyCString); + CFRelease(item); + } else { + // Create new item + status = SecKeychainAddGenericPassword( + NULL, (UInt32)strlen(service), service, (UInt32)strlen(account), + account, (UInt32)strlen(authenticationKeyCString), authenticationKeyCString, NULL); + } + + return status; +} + ++ (OSStatus) deleteAuthenticationKey { + const char *service = [kKeychainService UTF8String]; + const char *account = [kAuthenticationKeyKeychainKey UTF8String]; + + SecKeychainItemRef item = NULL; + OSStatus findStatus = SecKeychainFindGenericPassword( + NULL, (UInt32)strlen(service), service, (UInt32)strlen(account), account, + NULL, NULL, &item); + + if (findStatus == errSecSuccess) { + OSStatus deleteStatus = SecKeychainItemDelete(item); + CFRelease(item); + return deleteStatus; + } + + return findStatus; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/QSThingsPlugin/Parsers/QSThingsAreasParser.m b/QSThingsPlugin/Parsers/QSThingsAreasParser.m index d4f9b5f..f147603 100644 --- a/QSThingsPlugin/Parsers/QSThingsAreasParser.m +++ b/QSThingsPlugin/Parsers/QSThingsAreasParser.m @@ -21,9 +21,9 @@ QSObject *newObject = [QSObject makeObjectWithIdentifier:[dictionary objectForKey:@"uuid"]]; - [newObject setObject:dictionary forType:kProjectType]; + [newObject setObject:dictionary forType:kAreaType]; [newObject setName:[dictionary objectForKey:@"title"]]; - [newObject setPrimaryType:kProjectType]; + [newObject setPrimaryType:kAreaType]; [objects addObject:newObject]; }]; diff --git a/QSThingsPlugin/QSThingsPluginSource.m b/QSThingsPlugin/QSThingsPluginSource.m index 08984ef..f73b5ca 100644 --- a/QSThingsPlugin/QSThingsPluginSource.m +++ b/QSThingsPlugin/QSThingsPluginSource.m @@ -45,8 +45,7 @@ #pragma mark - Object Handler Methods - (void)setQuickIconForObject:(QSObject *)object { - [object - setIcon:[QSResourceManager imageNamed:@"com.culturedcode.ThingsMac"]]; + [object setIcon:[QSResourceManager imageNamed:@"com.culturedcode.ThingsMac"]]; } - (BOOL)objectHasChildren:(QSObject *)object { diff --git a/QSThingsPlugin/QSThingsPluginSource.xib b/QSThingsPlugin/QSThingsPluginSource.xib deleted file mode 100644 index ac6254e..0000000 --- a/QSThingsPlugin/QSThingsPluginSource.xib +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - info.username - info.site - info.includeTags - info.host - - - - - - - - diff --git a/QSThingsPlugin/Types/Constants.h b/QSThingsPlugin/Types/Constants.h index e365a8f..044dcde 100644 --- a/QSThingsPlugin/Types/Constants.h +++ b/QSThingsPlugin/Types/Constants.h @@ -5,3 +5,7 @@ extern NSString *const kProjectType; extern NSString *const kAreaType; extern NSString *const kTagType; extern NSString *const kTaskType; + +#pragma mark - Keychain +extern NSString *const kKeychainService; +extern NSString *const kAuthenticationKeyKeychainKey; diff --git a/QSThingsPlugin/Types/Constants.m b/QSThingsPlugin/Types/Constants.m index 4abf9f7..e6158a2 100644 --- a/QSThingsPlugin/Types/Constants.m +++ b/QSThingsPlugin/Types/Constants.m @@ -5,3 +5,7 @@ NSString *const kProjectType = @"things.project"; NSString *const kAreaType = @"things.area"; NSString *const kTagType = @"things.tag"; NSString *const kTaskType = @"things.task"; + +#pragma mark - Keychain +NSString *const kKeychainService = @"QSThingsPlugin"; +NSString *const kAuthenticationKeyKeychainKey = @"authenticationKey"; -- cgit