LicenseStore.m 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. // _____ _
  2. // |_ _| |_ _ _ ___ ___ _ __ __ _
  3. // | | | ' \| '_/ -_) -_) ' \/ _` |_
  4. // |_| |_||_|_| \___\___|_|_|_\__,_(_)
  5. //
  6. // Threema iOS Client
  7. // Copyright (c) 2016-2020 Threema GmbH
  8. //
  9. // This program is free software: you can redistribute it and/or modify
  10. // it under the terms of the GNU Affero General Public License, version 3,
  11. // as published by the Free Software Foundation.
  12. //
  13. // This program is distributed in the hope that it will be useful,
  14. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. // GNU Affero General Public License for more details.
  17. //
  18. // You should have received a copy of the GNU Affero General Public License
  19. // along with this program. If not, see <https://www.gnu.org/licenses/>.
  20. #import "LicenseStore.h"
  21. #import "BundleUtil.h"
  22. #import "ServerAPIConnector.h"
  23. #import "Utils.h"
  24. #import "ThreemaError.h"
  25. #import "AppGroup.h"
  26. #import "MyIdentityStore.h"
  27. #import "NaClCrypto.h"
  28. #import "NSString+Hex.h"
  29. #import "ValidationLogger.h"
  30. #ifdef DEBUG
  31. static const DDLogLevel ddLogLevel = DDLogLevelVerbose;
  32. #else
  33. static const DDLogLevel ddLogLevel = DDLogLevelWarning;
  34. #endif
  35. #define WORK_APP_ID @"ch.threema.work.iapp"
  36. #define PERSISTENCE_KEY_LICENSE_USER @"Threema license username"
  37. #define PERSISTENCE_KEY_LICENSE_PASSWORD @"Threema license password"
  38. #define PERSISTENCE_KEY_DEVICE_ID @"Threema device ID"
  39. #define LICENSE_CHECK_INTERVAL_S 24*60*60
  40. #define DEVICE_ID_LENGTH 16
  41. static LicenseStore *singleton;
  42. @interface LicenseStore ()
  43. @property BOOL didCheckLicense;
  44. @property dispatch_semaphore_t sema;
  45. @end
  46. @implementation LicenseStore
  47. @synthesize licenseUsername = _licenseUsername;
  48. @synthesize licensePassword = _licensePassword;
  49. + (instancetype)sharedLicenseStore {
  50. if (singleton == nil) {
  51. static dispatch_once_t onceToken;
  52. dispatch_once(&onceToken, ^{
  53. singleton = [LicenseStore new];
  54. });
  55. }
  56. return singleton;
  57. }
  58. - (instancetype)init
  59. {
  60. self = [super init];
  61. if (self) {
  62. _didCheckLicense = NO;
  63. _sema = dispatch_semaphore_create(1);
  64. [self loadLicense];
  65. }
  66. return self;
  67. }
  68. + (BOOL)requiresLicenseKey {
  69. NSBundle *bundle = [BundleUtil mainBundle];
  70. if ([bundle.bundleIdentifier hasPrefix:WORK_APP_ID]) {
  71. return YES;
  72. }
  73. return NO;
  74. }
  75. - (BOOL)getRequiresLicenseKey {
  76. return [LicenseStore requiresLicenseKey];
  77. }
  78. - (BOOL)isWithinCheckInterval {
  79. NSDate *lastCheck = [MyIdentityStore sharedMyIdentityStore].licenseLastCheck;
  80. if (lastCheck == nil) {
  81. return NO;
  82. }
  83. NSTimeInterval time = [[NSDate date] timeIntervalSinceDate:lastCheck];
  84. if (time > LICENSE_CHECK_INTERVAL_S) {
  85. return NO;
  86. }
  87. return YES;
  88. }
  89. - (BOOL)isValid {
  90. if ([LicenseStore requiresLicenseKey]) {
  91. if (_didCheckLicense) {
  92. if ([self isWithinCheckInterval] == NO) {
  93. // force fresh license check
  94. _didCheckLicense = NO;
  95. [[ValidationLogger sharedValidationLogger] logString:@"License Check: force fresh license check"];
  96. return NO;
  97. }
  98. return YES;
  99. } else if ([AppGroup getCurrentType] == AppGroupTypeShareExtension && [self isWithinCheckInterval]) {
  100. // keep share extension valid for one week
  101. return YES;
  102. }
  103. else {
  104. [[ValidationLogger sharedValidationLogger] logString:@"License Check: it's not valid"];
  105. return NO;
  106. }
  107. }
  108. return YES;
  109. }
  110. - (void)performLicenseCheckWithCompletion:(void(^)(BOOL success))onCompletion {
  111. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
  112. dispatch_semaphore_wait(_sema, DISPATCH_TIME_FOREVER);
  113. if (_didCheckLicense) {
  114. dispatch_semaphore_signal(_sema);
  115. onCompletion(YES);
  116. return;
  117. }
  118. if (_licenseUsername.length < 1 || _licensePassword.length < 1) {
  119. dispatch_semaphore_signal(_sema);
  120. _errorMessage = @"License username/password too short";
  121. onCompletion(NO);
  122. return;
  123. }
  124. NSString *appId = [BundleUtil mainBundle].bundleIdentifier;
  125. NSString *version = [Utils getClientVersion];
  126. ServerAPIConnector *connector = [[ServerAPIConnector alloc] init];
  127. [connector validateLicenseUsername:_licenseUsername password:_licensePassword appId:appId version:version deviceId:[self deviceId] onCompletion:^(BOOL success, NSDictionary *info) {
  128. if (success) {
  129. [MyIdentityStore sharedMyIdentityStore].licenseLastCheck = [NSDate date];
  130. _didCheckLicense = YES;
  131. } else {
  132. [MyIdentityStore sharedMyIdentityStore].licenseLastCheck = nil;
  133. _didCheckLicense = NO;
  134. _errorMessage = info[@"error"];
  135. }
  136. [self performUpdateWorkInfo];
  137. onCompletion(success);
  138. dispatch_semaphore_signal(_sema);
  139. } onError:^(NSError *error) {
  140. _errorMessage = error.localizedDescription;
  141. _error = error;
  142. onCompletion(NO);
  143. dispatch_semaphore_signal(_sema);
  144. }];
  145. });
  146. }
  147. - (void)performUpdateWorkInfo {
  148. if (![LicenseStore requiresLicenseKey] || _licenseUsername.length < 1)
  149. return;
  150. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  151. ServerAPIConnector *connector = [[ServerAPIConnector alloc] init];
  152. [connector updateWorkInfoForStore:[MyIdentityStore sharedMyIdentityStore] licenseUsername:_licenseUsername password:_licensePassword onCompletion:^{
  153. DDLogInfo(@"Work info update completed");
  154. } onError:^(NSError *error) {
  155. DDLogWarn(@"Work info update failed: %@", error);
  156. }];
  157. });
  158. }
  159. - (void)setLicenseUsername:(NSString *)licenseUsername {
  160. if ([_licenseUsername isEqualToString:licenseUsername] == NO) {
  161. _licenseUsername = licenseUsername;
  162. _didCheckLicense = NO;
  163. [self saveLicense];
  164. }
  165. }
  166. - (NSString *)licenseUsername {
  167. return _licenseUsername;
  168. }
  169. - (void)setLicensePassword:(NSString *)licensePassword {
  170. if ([_licensePassword isEqualToString:licensePassword] == NO) {
  171. _licensePassword = licensePassword;
  172. _didCheckLicense = NO;
  173. [self saveLicense];
  174. }
  175. }
  176. - (NSString *)licensePassword {
  177. return _licensePassword;
  178. }
  179. - (void)deleteLicense {
  180. _didCheckLicense = NO;
  181. _licenseUsername = nil;
  182. _licensePassword = nil;
  183. [[AppGroup userDefaults] setValue:nil forKey:PERSISTENCE_KEY_LICENSE_USER];
  184. [[AppGroup userDefaults] setValue:nil forKey:PERSISTENCE_KEY_LICENSE_PASSWORD];
  185. [[AppGroup userDefaults] synchronize];
  186. }
  187. #pragma mark - private
  188. - (void)loadLicense {
  189. _licenseUsername = [[AppGroup userDefaults] stringForKey:PERSISTENCE_KEY_LICENSE_USER];
  190. _licensePassword = [[AppGroup userDefaults] stringForKey:PERSISTENCE_KEY_LICENSE_PASSWORD];
  191. }
  192. - (void)saveLicense {
  193. [[AppGroup userDefaults] setValue:_licenseUsername forKey:PERSISTENCE_KEY_LICENSE_USER];
  194. [[AppGroup userDefaults] setValue:_licensePassword forKey:PERSISTENCE_KEY_LICENSE_PASSWORD];
  195. [[AppGroup userDefaults] synchronize];
  196. }
  197. - (NSString*)deviceId {
  198. // Obtain device ID from user defaults. If it doesn't exist yet, generate a new random device ID.
  199. NSString *deviceId = [[AppGroup userDefaults] stringForKey:PERSISTENCE_KEY_DEVICE_ID];
  200. if (deviceId == nil) {
  201. deviceId = [NSString stringWithHexData:[[NaClCrypto sharedCrypto] randomBytes:DEVICE_ID_LENGTH]];
  202. [[AppGroup userDefaults] setValue:deviceId forKey:PERSISTENCE_KEY_DEVICE_ID];
  203. [[AppGroup userDefaults] synchronize];
  204. }
  205. return deviceId;
  206. }
  207. @end