Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

Quelle  ServerAPIConnector.m   Sprache: Unknown

 

Spracherkennung für: .m vermutete Sprache: Masm {Masm[50] ABAP[48] JAVA[44]} [Methode: maximale Elemente, drei Dimensionen]

#include <CommonCrypto/CommonDigest.h>

#import "ThreemaFramework/ThreemaFramework-Swift.h"
#import "ServerAPIConnector.h"
#import "ServerAPIRequest.h"
#import "NSString+Hex.h"
#import "NaClCrypto.h"
#import "ThreemaUtilityObjC.h"
#import "ThreemaError.h"
#import "UserSettings.h"
#import "MyIdentityStore.h"
#import "LicenseStore.h"
#import "AppGroup.h"
#import "ProtocolDefines.h"

@import FileUtility;

#ifdef DEBUG
  static const DDLogLevel ddLogLevel = DDLogLevelVerbose;
#else
  static const DDLogLevel ddLogLevel = DDLogLevelInfo;
#endif

/// Redacts personal info for logging, same format as `-[MDMSetup checkIsPersonalInfoAndLog:value:]`
static NSString *redactedPersonalInfo(NSString *value) {
    if (value == nil) {
        return @"(null)";
    }
    else if ([value length] == 0) {
        return @"[empty]";
    }
    else if ([value length] <= 2) {
        return [NSString stringWithFormat:@"%@***", [value substringToIndex:value.length - 1]];
    }
    else {
        return [NSString stringWithFormat:@"%@***", [value substringToIndex:2]];
    }
}

@implementation ServerAPIConnector

- (void)createIdentityWithStore:(id<MyIdentityStoreProtocol>)identityStore onCompletion:(void(^)(MyIdentityStore *store))onCompletion onError:(void(^)(NSError *error))onError {
    
    static NSString *apiPath = @"identity/create";
    
    NSMutableDictionary *request = [NSMutableDictionary dictionaryWithDictionary:@{
                              @"publicKey": [identityStore.publicKey base64EncodedStringWithOptions:0],
                              @"version": ThreemaUtility.clientVersion,
                              @"deviceId": [[[UIDevice currentDevice] identifierForVendor] UUIDString]
                              }];
    
    // Add App Store receipt if available (but not for Work)
    NSURL *receiptUrl = [[NSBundle mainBundle] appStoreReceiptURL];
    if (receiptUrl && [[FileUtility new] fileExistsAt:receiptUrl] && !TargetManagerObjC.isBusinessApp) {
        NSData *receiptData = [NSData dataWithContentsOfURL:receiptUrl];
        if (receiptData) {
            request[@"appStoreReceipt"] = [receiptData base64EncodedStringWithOptions:0];
        }
    }
    
    // Add Work license if available
    NSString *licenseUsername = [[LicenseStore sharedLicenseStore] licenseUsername];
    NSString *licensePassword = [[LicenseStore sharedLicenseStore] licensePassword];
    if (licenseUsername && licensePassword) {
        request[@"licenseUsername"] = licenseUsername;
        request[@"licensePassword"] = licensePassword;
    }
    
    [self sendSignedRequestPhase1:request toApiPath:apiPath onCompletion:^(NSDictionary *response) {
        [self sendSignedRequestPhase2:request toApiPath:apiPath phase1Response:response forStore:identityStore onCompletion:^(NSDictionary *response) {
            identityStore.identity = response[@"identity"];
            identityStore.serverGroup = response[@"serverGroup"];
            
            onCompletion(identityStore);
        } onError:onError];
    } onError:onError];
}

- (void)fetchIdentityInfo:(NSString*)identity onCompletion:(void(^)(NSData *publicKey, NSNumber *state, NSNumber *type, NSNumber *featureMask))onCompletion onError:(void(^)(NSError *error))onError {
    
    NSString *apiPath = [NSString stringWithFormat:@"identity/%@", identity];
    
    [ServerAPIRequest loadJSONFromAPIPath:apiPath withCachePolicy:NSURLRequestReloadIgnoringLocalCacheData onCompletion:^(id jsonObject) {
        NSData *publicKey = [[NSData alloc] initWithBase64EncodedString:jsonObject[@"publicKey"] options:0];
        NSNumber *state = [NSNumber numberWithInt:[jsonObject[@"state"] intValue]];
        NSNumber *type = [NSNumber numberWithInt:[jsonObject[@"type"] intValue]];
        NSNumber *featureMask = @0;
        if (![jsonObject[@"featureMask"] isEqual:[NSNull null]]) {
            featureMask = [NSNumber numberWithInt:[jsonObject[@"featureMask"] intValue]];
        }
        
        onCompletion(publicKey, state, type, featureMask);
    } onError:^(NSError *error) {
        onError(error);
    }];
}

- (void)fetchBulkIdentityInfo:(NSArray*)identities onCompletion:(void(^)(NSArray *identities, NSArray *publicKeys, NSArray *featureMasks, NSArray *states, NSArray *types))onCompletion onError:(void(^)(NSError *error))onError {
    
    NSDictionary *req = [NSDictionary dictionaryWithObjectsAndKeys:identities, @"identities", nil];
    
    [ServerAPIRequest postJSONToAPIPath:@"identity/fetch_bulk" data:req onCompletion:^(id jsonObject) {
        DDLogVerbose(@"Bulk fetch of ID status success: %@", jsonObject);

        NSMutableArray *responseIdentities = [[NSMutableArray alloc] init];
        NSMutableArray *responsePublicKeys = [[NSMutableArray alloc] init];
        NSMutableArray *responseFeatureMasks = [[NSMutableArray alloc] init];
        NSMutableArray *responseStates = [[NSMutableArray alloc] init];
        NSMutableArray *responseTypes = [[NSMutableArray alloc] init];

        int index = 0;
        NSUInteger count = [jsonObject[@"identities"] count];
        while(index < count) {
            id item = jsonObject[@"identities"][index];
            
            [responseIdentities addObject:item[@"identity"]];
            [responsePublicKeys addObject:[[NSData alloc] initWithBase64EncodedString:item[@"publicKey"] options:0]];
            if ([item[@"featureMask"] isEqual: [NSNull null]]) {
                [responseFeatureMasks addObject:@0];
            } else {
                [responseFeatureMasks addObject:item[@"featureMask"]];
            }
            [responseStates addObject:item[@"state"]];
            [responseTypes addObject:item[@"type"]];

            index++;
        }
        
        onCompletion(responseIdentities, responsePublicKeys, responseFeatureMasks, responseStates, responseTypes);
    } onError:^(NSError *error) {
        DDLogError(@"Bulk fetch of ID status failed: %@", error);
        onError(error);
    }];
}

- (void)fetchPrivateIdentityInfo:(id<MyIdentityStoreProtocol>)identityStore onCompletion:(void(^)(NSString *serverGroup, NSString *email, NSString *mobileNo))onCompletion onError:(void(^)(NSError *error))onError {
    
    if (identityStore.identity == nil) {
        onError([ThreemaError threemaError:@"store has no valid identity"]);
        return;
    }
    
    NSDictionary *request = @{
        @"identity": identityStore.identity,
        @"appVariant": TargetManagerObjC.isBusinessApp ? @"work" : @"consumer"
    };

    [self sendSignedRequest:request toApiPath:@"identity/fetch_priv" forStore:identityStore onCompletion:^(id jsonObject) {
        onCompletion(jsonObject[@"serverGroup"], jsonObject[@"email"], jsonObject[@"mobileNo"]);
    } onError:onError];
}

- (void)fetchWorkIdentitiesInfo:(NSArray *)identities onCompletion:(void(^)(NSArray *foundIdentities))onCompletion onError:(void(^)(NSError *error))onError {
    if (identities == nil) {
        onError([ThreemaError threemaError:@"Identity is nil"]);
        return;
    }

    NSDictionary *request = @{
        @"username": [[LicenseStore sharedLicenseStore] licenseUsername],
        @"password": [[LicenseStore sharedLicenseStore] licensePassword],
        @"contacts": identities
    };
    [ServerAPIRequest postJSONToWorkAPIPath:@"identities" data:request onCompletion:^(id jsonObject) {
        onCompletion(jsonObject[@"contacts"]);
    } onError:^(NSError *error) {
        onError(error);
    }];
}

- (void)updateMyIdentityStore:(id<MyIdentityStoreProtocol>)identityStore onCompletion:(void(^)(void))onCompletion onError:(void(^)(NSError* error))onError {
    [self fetchPrivateIdentityInfo:identityStore onCompletion:^(NSString *serverGroup, NSString *email, NSString *mobileNo) {
        DDLogVerbose(@"Got identity info: serverGroup %@, email %@, mobileNo %@", serverGroup, email, mobileNo);
        
        identityStore.serverGroup = serverGroup;
        
        if (email != nil) {
            identityStore.linkedEmail = email;
            identityStore.linkEmailPending = NO;
        }
        
        if (mobileNo != nil) {
            identityStore.linkedMobileNo = mobileNo;
            identityStore.linkMobileNoPending = NO;
        }
        
        onCompletion();
    } onError:^(NSError *error) {
        onError(error);
    }];
}

- (void)linkEmailWithStore:(id<MyIdentityStoreProtocol>)identityStore email:(NSString*)email onCompletion:(void(^)(BOOL linked))onCompletion onError:(void(^)(NSError *error))onError {
    
    static NSString *apiPath = @"identity/link_email";
    
    if (identityStore.identity == nil) {
        onError([ThreemaError threemaError:@"store has no valid identity"]);
        return;
    }
    
    if (email == nil) {
        onError([ThreemaError threemaError:@"link email with store: email missing"]);
        return;
    }
    
    NSDictionary *request = @{
                              @"identity": identityStore.identity,
                              @"email": email,
                              @"lang": [self preferredLanguage]
                              };
    
    [self sendSignedRequestPhase1:request toApiPath:apiPath onCompletion:^(NSDictionary *response) {
        if ([response[@"linked"] boolValue]) {
            /* Already linked - update address with user provided value, as it is possible that we currently
               only have ***@*** from the server after an ID restore */
            ProfileStore *profileStore = [ProfileStore new];
            [profileStore syncAndSaveWithEmail:email completionHandler:^(NSError * _Nullable error) {
                if (error == nil) {
                    identityStore.linkEmailPending = NO;
                    onCompletion(YES);
                }
                else {
                    onError(error);
                }
            }];
            return;
        }
        
        [self sendSignedRequestPhase2:request toApiPath:apiPath phase1Response:response forStore:identityStore onCompletion:^(NSDictionary *response) {
            ProfileStore *profileStore = [ProfileStore new];
            [profileStore syncAndSaveWithEmail:email.length != 0 ? email : nil completionHandler:^(NSError * _Nullable error) {
                if (error == nil) {
                    if (email.length == 0) {
                        /* unlink */
                        identityStore.linkEmailPending = NO;
                    } else {
                        identityStore.linkEmailPending = YES;
                    }
                    onCompletion(NO);
                }
                else {
                    onError(error);
                }
            }];
        } onError:onError];
    } onError:onError];
}

- (NSString *)preferredLanguage {
    NSString *lang = [[[NSBundle mainBundle] preferredLocalizations] objectAtIndex:0];
    if (lang) {
        return lang;
    } else {
        return @"en_US";
    }
}

- (void)checkLinkEmailStatus:(id<MyIdentityStoreProtocol>)identityStore email:(NSString*)email onCompletion:(void(^)(BOOL linked))onCompletion onError:(void(^)(NSError *error))onError {

    if (identityStore.identity == nil) {
        onError([ThreemaError threemaError:@"store has no valid identity"]);
        return;
    }
    
    if (email == nil) {
        onError([ThreemaError threemaError:@"check link email status: email missing"]);
        return;
    }
    
    NSDictionary *request = @{
                              @"identity": identityStore.identity,
                              @"email": email
                              };
    
    [self sendSignedRequestPhase1:request toApiPath:@"identity/link_email" onCompletion:^(NSDictionary *response) {
        BOOL linked = [response[@"linked"] boolValue];
        if (linked)
            [MyIdentityStore sharedMyIdentityStore].linkEmailPending = NO;

        onCompletion(linked);
    } onError:onError];
}

- (void)linkMobileNoWithStore:(id<MyIdentityStoreProtocol>)identityStore mobileNo:(NSString*)mobileNo onCompletion:(void(^)(BOOL linked))onCompletion onError:(void(^)(NSError *error))onError {
    
    if (identityStore.identity == nil) {
        onError([ThreemaError threemaError:@"store has no valid identity"]);
        return;
    }
    
    if (mobileNo == nil) {
        onError([ThreemaError threemaError:@"link mobile: mobileNo missing"]);
        return;
    }
    
    static NSString *apiPath = @"identity/link_mobileno";
    
    NSString *urlScheme = @"threema";
    if (TargetManagerObjC.isBusinessApp) {
        urlScheme = @"threemawork";
    }
    
    NSDictionary *request = @{
                              @"identity": identityStore.identity,
                              @"mobileNo": mobileNo,
                              @"lang": [self preferredLanguage],
                              @"urlScheme": urlScheme
                              };
    
    [self sendSignedRequestPhase1:request toApiPath:apiPath onCompletion:^(NSDictionary *response) {
        if ([response[@"linked"] boolValue]) {
            /* already linked */
            ProfileStore *profileStore = [ProfileStore new];
            [profileStore syncAndSaveWithMobileNo:mobileNo completionHandler:^(NSError * _Nullable error) {
                if (error == nil) {
                    identityStore.linkMobileNoPending = NO;
                    identityStore.linkMobileNoVerificationId = nil;
                    onCompletion(YES);
                }
                else {
                    onError(error);
                }
            }];
            return;
        }
        
        [self sendSignedRequestPhase2:request toApiPath:apiPath phase1Response:response forStore:identityStore onCompletion:^(NSDictionary *response) {
            ProfileStore *profileStore = [ProfileStore new];
            [profileStore syncAndSaveWithMobileNo:mobileNo.length != 0 ? mobileNo : nil completionHandler:^(NSError * _Nullable error) {
                if (error == nil) {
                    if (mobileNo.length == 0) {
                        /* unlink */
                        identityStore.linkMobileNoPending = NO;
                        identityStore.linkMobileNoVerificationId = nil;
                    } else {
                        identityStore.linkMobileNoPending = YES;
                        identityStore.linkMobileNoVerificationId = response[@"verificationId"];
                        identityStore.linkMobileNoStartDate = [NSDate date];
                    }
                    onCompletion(NO);
                }
                else {
                    onError(error);
                }
            }];
        } onError:onError];
    } onError:onError];
}

- (void)linkMobileNoWithStore:(id<MyIdentityStoreProtocol>)identityStore code:(NSString*)code onCompletion:(void(^)(BOOL linked))onCompletion onError:(void(^)(NSError *error))onError {
    
    if (!identityStore.linkMobileNoPending || identityStore.linkMobileNoVerificationId == nil) {
        DDLogWarn(@"No mobileNo verification pending");
        onError([ThreemaError threemaError:@"No verification pending"]);
        return;
    }
    
    NSDictionary *req = @{
                          @"verificationId": identityStore.linkMobileNoVerificationId,
                          @"code": code
                          };
    
    [ServerAPIRequest postJSONToAPIPath:@"identity/link_mobileno" data:req onCompletion:^(id jsonObject) {
        DDLogVerbose(@"link mobileNo phase 3 request success: %@", jsonObject);
        
        identityStore.linkMobileNoPending = NO;
        identityStore.linkMobileNoVerificationId = nil;
        [[NSNotificationCenter defaultCenter] postNotificationName:@"ThreemaIdentityLinkedWithMobileNo" object:nil];

        onCompletion(YES);
    } onError:^(NSError *error) {
        DDLogError(@"link mobileNo phase 3 request failed: %@", error);
        onError(error);
    }];
}

- (void)linkMobileNoRequestCallWithStore:(id<MyIdentityStoreProtocol>)identityStore onCompletion:(void(^)(void))onCompletion onError:(void(^)(NSError *error))onError {
    
    if (!identityStore.linkMobileNoPending || identityStore.linkMobileNoVerificationId == nil) {
        DDLogWarn(@"No mobileNo verification pending");
        onError([ThreemaError threemaError:@"No verification pending"]);
        return;
    }
    
    NSDictionary *req = @{
                          @"verificationId": identityStore.linkMobileNoVerificationId
                          };
    
    [ServerAPIRequest postJSONToAPIPath:@"identity/link_mobileno_call" data:req onCompletion:^(id jsonObject) {
        DDLogVerbose(@"link mobileNo call request success: %@", jsonObject);
        onCompletion();
    } onError:^(NSError *error) {
        DDLogError(@"link mobileNo call request failed: %@", error);
        onError(error);
    }];
}

- (void)obtainMatchTokenForIdentity:(id<MyIdentityStoreProtocol>)identityStore forceRefresh:(BOOL)forceRefresh onCompletion:(void(^)(NSString *matchToken))onCompletion onError:(void(^)(NSError *error))onError {

    if (!identityStore.isValidIdentity || !AppSetup.isIdentityProvisioned) {
        DDLogNotice(@"Skip obtaining token, because my identity is invalid or not provisioned.");
        onCompletion(nil);
        return;
    }
    
    // Cached match token?
    NSUserDefaults *defaults = [AppGroup userDefaults];
    NSString *matchToken = [defaults objectForKey:@"MatchToken"];
    if (!forceRefresh && matchToken) {
        onCompletion(matchToken);
        return;
    }
    
    NSDictionary *request = @{
        @"identity": identityStore.identity
    };

    [self sendSignedRequest:request toApiPath:@"identity/match_token" forStore:identityStore onCompletion:^(id jsonObject) {
        if (jsonObject[@"matchToken"]) {
            [defaults setObject:jsonObject[@"matchToken"] forKey:@"MatchToken"];
            [defaults synchronize];
            onCompletion(jsonObject[@"matchToken"]);
        } else {
            onError([ThreemaError threemaError:jsonObject[@"error"]]);
        }
    } onError:onError];
}

- (void)matchIdentitiesWithEmailHashes:(NSArray*)emailHashes mobileNoHashes:(NSArray*)mobileNoHashes includeInactive:(BOOL)includeInactive onCompletion:(void(^)(NSArray *identities, int checkInterval))onCompletion onError:(void(^)(NSError *error))onError {
    
    [self obtainMatchTokenForIdentity:[MyIdentityStore sharedMyIdentityStore] forceRefresh:NO onCompletion:^(NSString *matchToken) {
        DDLogInfo(@"Match token: %@", matchToken);
        
        [self matchIdentitiesWithEmailHashes:emailHashes mobileNoHashes:mobileNoHashes includeInactive:includeInactive matchToken:matchToken onCompletion:onCompletion onError:^(NSError *error) {
            // Match token may be invalid/expired, refresh and try again
            [self obtainMatchTokenForIdentity:[MyIdentityStore sharedMyIdentityStore] forceRefresh:YES onCompletion:^(NSString *matchToken) {
                [self matchIdentitiesWithEmailHashes:emailHashes mobileNoHashes:mobileNoHashes includeInactive:includeInactive matchToken:matchToken onCompletion:onCompletion onError:onError];
            } onError:onError];
        }];
    } onError:^(NSError *error) {
        DDLogError(@"Cannot obtain match token: %@", error);
        onError(error);
    }];
}

- (void)matchIdentitiesWithEmailHashes:(NSArray*)emailHashes mobileNoHashes:(NSArray*)mobileNoHashes includeInactive:(BOOL)includeInactive matchToken:(NSString*)matchToken onCompletion:(void(^)(NSArray *identities, int checkInterval))onCompletion onError:(void(^)(NSError *error))onError {
    
    NSMutableDictionary *req = [NSMutableDictionary dictionary];
    req[@"emailHashes"] = emailHashes;
    req[@"mobileNoHashes"] = mobileNoHashes;
    if (matchToken != nil) {
        req[@"matchToken"] = matchToken;
    }
    if (includeInactive) {
        req[@"includeInactive"] = @YES;
    }
    
    [ServerAPIRequest postJSONToAPIPath:@"identity/match" data:req onCompletion:^(id jsonObject) {
        DDLogVerbose(@"Match identities request success: %@", jsonObject);
        onCompletion([jsonObject objectForKey:@"identities"], ((NSNumber*)jsonObject[@"checkInterval"]).intValue);
    } onError:^(NSError *error) {
        DDLogError(@"Match identities request failed: %@", error);
        onError(error);
    }];
}

- (void)setFeatureMask:(NSNumber *)featureMask forStore:(id<MyIdentityStoreProtocol>)identityStore onCompletion:(void(^)(void))onCompletion onError:(void(^)(NSError *error))onError {
    if (identityStore.identity == nil) {
        onError([ThreemaError threemaError:@"store has no valid identity"]);
        return;
    }
    
    NSString *apiPath = @"identity/set_featuremask";
    NSDictionary *req = @{
                          @"identity": identityStore.identity,
                          @"featureMask": featureMask
                          };
    
    [self sendSignedRequest:req toApiPath:apiPath forStore:identityStore onCompletion:^(NSDictionary *response) {
        if ([response[@"success"] boolValue])
            onCompletion();
        else
            onError([ThreemaError threemaError:response[@"error"]]);
    } onError:onError];
}

- (void)getFeatureMasksForIdentities:(NSArray*)identities onCompletion:(void(^)(NSArray* featureMasks))onCompletion onError:(void(^)(NSError *error))onError {
    NSDictionary *req = [NSDictionary dictionaryWithObjectsAndKeys:identities, @"identities", nil];
    
    [ServerAPIRequest postJSONToAPIPath:@"identity/check_featuremask" data:req onCompletion:^(id jsonObject) {
        DDLogVerbose(@"Check featureMask success: %@", jsonObject);
        NSArray *featureMaskStrings = jsonObject[@"featureMasks"];
        
        NSMutableArray *featureMaskNumbers = [NSMutableArray arrayWithCapacity: [featureMaskStrings count]];
        for (NSString *maskString in featureMaskStrings) {
            if ([maskString isEqual: [NSNull null]]) {
                NSNumber *number = [NSNumber numberWithInt: 0];
                [featureMaskNumbers addObject: number];
            } else {
                NSNumber *number = [NSNumber numberWithInt: maskString.intValue];
                [featureMaskNumbers addObject: number];
            }
        }
        
        onCompletion(featureMaskNumbers);
    } onError:^(NSError *error) {
        DDLogError(@"Check featureMask failed: %@", error);
        onError(error);
    }];
}

- (void)checkRevocationPasswordForStore:(id<MyIdentityStoreProtocol>)identityStore onCompletion:(void(^)(BOOL revocationPasswordSet, NSDate *lastChanged))onCompletion onError:(void(^)(NSError *error))onError {
    
    if (identityStore.identity == nil) {
        onError([ThreemaError threemaError:@"store has no valid identity"]);
        return;
    }

    NSDictionary *request = @{
                              @"identity": identityStore.identity
                              };
    
    [self sendSignedRequest:request toApiPath:@"identity/check_revocation_key" forStore:identityStore onCompletion:^(NSDictionary *response) {
        BOOL revocationPasswordSet = [response[@"revocationKeySet"] boolValue];
        NSDate *lastChanged = nil;
        if (revocationPasswordSet)
            lastChanged = [ThreemaUtilityObjC parseISO8601DateString:response[@"lastChanged"]];
        onCompletion(revocationPasswordSet, lastChanged);
    } onError:onError];
}

- (void)setRevocationPassword:(NSString*)revocationPassword forStore:(id<MyIdentityStoreProtocol>)identityStore onCompletion:(void(^)(void))onCompletion onError:(void(^)(NSError *error))onError {
    
    if (identityStore.identity == nil) {
        onError([ThreemaError threemaError:@"store has no valid identity"]);
        return;
    }
    
    /* key = first 32 bits of SHA-256 hash of password string */
    NSData *revocationPasswordData = [revocationPassword dataUsingEncoding:NSUTF8StringEncoding];
    unsigned char digest[CC_SHA256_DIGEST_LENGTH];
    CC_SHA256(revocationPasswordData.bytes, (CC_LONG)revocationPasswordData.length, digest);
    NSData *revocationKey = [NSData dataWithBytes:digest length:4];
    
    NSDictionary *request = @{
                              @"identity": identityStore.identity,
                              @"revocationKey": [revocationKey base64EncodedStringWithOptions:0]
                              };
    
    [self sendSignedRequest:request toApiPath:@"identity/set_revocation_key" forStore:identityStore onCompletion:^(id jsonObject) {
        if ([jsonObject[@"success"] boolValue])
            onCompletion();
        else
            onError([ThreemaError threemaError:jsonObject[@"error"]]);
    } onError:onError];
}

- (void)revokeID:(id<MyIdentityStoreProtocol>)identityStore onCompletion:(void(^)(void))onCompletion onError:(void(^)(NSError *error))onError {
    
    if (identityStore.identity == nil) {
        onError([ThreemaError threemaError:@"store has no valid identity"]);
        return;
    }
    
    NSDictionary *request = @{
                              @"identity": identityStore.identity,
                              @"lang": [self preferredLanguage],
                              };
    
    [self sendSignedRequest:request toApiPath:@"identity/revoke" forStore:identityStore onCompletion:^(id jsonObject) {
        if ([jsonObject[@"success"] boolValue])
            onCompletion();
        else
            onError([ThreemaError threemaError:jsonObject[@"error"]]);
    } onError:onError];
}

- (void)checkStatusOfIdentities:(NSArray*)identities onCompletion:(void(^)(NSArray* states, NSArray* types, NSArray* featureMasks, int checkInterval))onCompletion onError:(void(^)(NSError *error))onError {
    NSDictionary *req = [NSDictionary dictionaryWithObjectsAndKeys:identities, @"identities", nil];
    
    [ServerAPIRequest postJSONToAPIPath:@"identity/check" data:req onCompletion:^(id jsonObject) {
        DDLogVerbose(@"Check ID status success: %@", jsonObject);
        onCompletion(jsonObject[@"states"], jsonObject[@"types"], jsonObject[@"featureMasks"], [jsonObject[@"checkInterval"] intValue]);
    } onError:^(NSError *error) {
        DDLogError(@"Check ID status failed: %@", error);
        onError(error);
    }];
}

- (void)revokeIdForStore:(id<MyIdentityStoreProtocol>)identityStore onCompletion:(void(^)(void))onCompletion onError:(void(^)(NSError *error))onError {
    
    if (identityStore.identity == nil) {
        onError([ThreemaError threemaError:@"store has no valid identity"]);
        return;
    }
    
    NSDictionary *request = @{
                              @"identity": identityStore.identity,
                              @"lang": [self preferredLanguage]
                              };
    
    [self sendSignedRequest:request toApiPath:@"identity/revoke" forStore:identityStore onCompletion:^(id jsonObject) {
        if ([jsonObject[@"success"] boolValue])
            onCompletion();
        else
            onError([ThreemaError threemaError:jsonObject[@"error"]]);
    } onError:onError];
}

- (void)validateLicenseUsername:(NSString*)licenseUsername password:(NSString*)licensePassword appId:(NSString*)appId version:(NSString*)version deviceId:(NSString*)deviceId onCompletion:(void(^)(BOOL success, NSDictionary *info))onCompletion onError:(void(^)(NSError *error))onError {
    
    static NSString *apiPath = @"check_license";
    
    NSDictionary *request = @{
                              @"licenseUsername": licenseUsername,
                              @"licensePassword": licensePassword,
                              @"appId": appId,
                              @"version": version,
                              @"deviceId": deviceId
                              };
    
    [self sendSignedRequestPhase1:request toApiPath:apiPath onCompletion:^(NSDictionary *response) {
        BOOL success = [response[@"success"] boolValue];
        onCompletion(success, response);
    } onError:onError];
}

- (void)updateWorkInfoForStore:(id<MyIdentityStoreProtocol>)identityStore licenseUsername:(NSString*)licenseUsername password:(NSString*)licensePassword force:(BOOL)force onCompletion:(void(^)(BOOL sent))onCompletion onError:(void(^)(NSError *error))onError {
    
    if (identityStore.identity == nil) {
        onError([ThreemaError threemaError:@"store has no valid identity"]);
        return;
    }
    
    NSMutableDictionary *request = [@{
        @"identity": identityStore.identity,
        @"licenseUsername": licenseUsername,
        @"licensePassword": licensePassword,
        @"publicNickname": (identityStore.pushFromName != nil  && identityStore.pushFromName.length > 0 ? identityStore.pushFromName : identityStore.identity),
        @"version": ThreemaUtility.clientVersionWithMDM
    } mutableCopy];
    
    if (identityStore.firstName != nil)
        request[@"firstName"] = identityStore.firstName;
    if (identityStore.lastName != nil)
        request[@"lastName"] = identityStore.lastName;
    if (identityStore.csi != nil)
        request[@"csi"] = identityStore.csi;
    if (identityStore.category != nil)
        request[@"category"] = identityStore.category;
    
    // For backwards compatibility we have to send this values always
    // Otherwise it will not remove the values if there are empty or nil
    if (identityStore.jobTitle != nil) {
        request[@"jobTitle"] = identityStore.jobTitle;
    } else {
        request[@"jobTitle"] = @"";
    }
    
    if (identityStore.department != nil) {
        request[@"department"] = identityStore.department;
    } else {
        request[@"department"] = @"";
    }
    
    NSData *requestData = [NSJSONSerialization dataWithJSONObject:request options:NSJSONWritingPrettyPrinted error:nil];
    unsigned char digest[CC_SHA256_DIGEST_LENGTH];
    CC_SHA256(requestData.bytes, (CC_LONG)requestData.length, digest);
    NSData *requestHash = [NSData dataWithBytes:digest length:sizeof(digest)];

    if ((force == NO && [requestHash isEqualToData:identityStore.lastWorkUpdateRequestHash] && ![identityStore sendUpdateWorkInfoStatus])
        || [AppGroup getCurrentType] != AppGroupTypeApp) {
        // request hasn't changed since last update and it's the same date
        // or it's a extension
        onCompletion(false);
        return;
    }
    
    DDLogWarn(@"Send update work info with\nfirstName: %@\nlastName: %@\ncsi: %@\njobTitle: %@\ndepartment: %@\ncategory: %@", redactedPersonalInfo(identityStore.firstName), redactedPersonalInfo(identityStore.lastName), redactedPersonalInfo(identityStore.csi), redactedPersonalInfo(identityStore.jobTitle), redactedPersonalInfo(identityStore.department), redactedPersonalInfo(identityStore.category));
    
    [self sendSignedRequest:request toApiPath:@"identity/update_work_info" forStore:identityStore onCompletion:^(id jsonObject) {
        if ([jsonObject[@"success"] boolValue]) {
            identityStore.lastWorkUpdateRequestHash = requestHash;
            [identityStore setLastWorkUpdateDate:[NSDate date]];
            onCompletion(true);
        } else {
            onError([ThreemaError threemaError:jsonObject[@"error"]]);
        }
    } onError:onError];
}

- (void)searchInDirectory:(NSString *)searchString categories:(NSArray *)categories page:(int)page forLicenseStore:(id<LicenseStoreProtocol> )licenseStore forMyIdentityStore:(id<MyIdentityStoreProtocol>)identityStore onCompletion:(void(^)(NSArray *contacts, NSDictionary *paging))onCompletion onError:(void(^)(NSError *error))onError {
    NSString *sortOrder = [[UserSettings sharedUserSettings] sortOrderFirstName] ? @"firstName" : @"lastName";

    NSMutableDictionary *req = [[NSMutableDictionary alloc] initWithDictionary:@{@"username": licenseStore.licenseUsername,
                          @"password": licenseStore.licensePassword,
                          @"identity": identityStore.identity,
                          @"query": searchString,
                          @"sort": @{@"by": sortOrder, @"asc": @true},
                          @"page": [NSNumber numberWithInt:page]
                          }];
    
    if (categories.count > 0) {
        [req setValue:categories forKey:@"categories"];
    }
    
    [ServerAPIRequest postJSONToWorkAPIPath:@"directory" data:req onCompletion:^(id jsonObject) {
        DDLogVerbose(@"Work directory search success: %@", jsonObject);
        NSArray *contactsArray = jsonObject[@"contacts"];
        NSDictionary *paging = jsonObject[@"paging"];
        onCompletion(contactsArray, paging);
    } onError:^(NSError *error) {
        DDLogError(@"Check featureMask failed: %@", error);
        onError(error);
    }];
}

- (void)obtainTurnServersWithStore:(id<MyIdentityStoreProtocol>)identityStore onCompletion:(void(^)(NSDictionary *response))onCompletion onError:(void(^)(NSError *error))onError {
    
    if (identityStore.identity == nil) {
        onError([ThreemaError threemaError:@"No identity"]);
        return;
    }
    
    NSDictionary *request = @{
        @"identity": identityStore.identity,
        @"type": @"voip"
    };

    [self sendSignedRequest:request toApiPath:@"identity/turn_cred" forStore:identityStore onCompletion:^(id jsonObject) {
        if (jsonObject[@"turnUrls"]) {
            onCompletion(jsonObject);
        } else {
            onError([ThreemaError threemaError:jsonObject[@"error"]]);
        }
    } onError:onError];
}

- (void)obtainSFUCredentials:(id<MyIdentityStoreProtocol>)identityStore onCompletion:(void(^)(NSDictionary *response))onCompletion onError:(void(^)(NSError *error))onError {
    
    if (identityStore.identity == nil) {
        onError([ThreemaError threemaError:@"No identity"]);
        return;
    }
    
    NSDictionary *request = @{
        @"identity": identityStore.identity,
    };

    [self sendSignedRequest:request toApiPath:@"identity/sfu_cred" forStore:identityStore onCompletion:^(id jsonObject) {
        if (jsonObject[@"sfuToken"]) {
            onCompletion(jsonObject);
        } else {
            onError([ThreemaError threemaError:jsonObject[@"error"]]);
        }
    } onError:onError];
}

- (void)obtainAuthTokenOnCompletion:(void(^)(NSString *authToken))onCompletion onError:(void(^)(NSError *error))onError {
    [ServerAPIRequest loadJSONFromAPIPath:@"auth_token" withCachePolicy:NSURLRequestReloadIgnoringCacheData onCompletion:^(id jsonObject) {
        if (jsonObject[@"authToken"]) {
            onCompletion(jsonObject[@"authToken"]);
        } else {
            onError([ThreemaError threemaError:jsonObject[@"error"]]);
        }
    } onError:onError];
}

- (void)reportSpamForIdentity:(nonnull NSString *)peerIdentity andNickname:(nullable NSString *)peerNickname withStore:(nonnull id<MyIdentityStoreProtocol>)identityStore onCompletion:(void(^_Nonnull)(void))onCompletion onError:(void(^_Nonnull)( NSError * _Nullable error))onError {
     if (identityStore.identity == nil) {
         onError([ThreemaError threemaError:@"No identity"]);
         return;
     }
     
     NSMutableDictionary *request = [[NSMutableDictionary alloc] initWithDictionary: @{
        @"reporter_identity": identityStore.identity,
        @"peer_identity": peerIdentity,
     }];
     
     // Send an explicit null (not an empty string and not an omitted key) when no nickname was set
     if (peerNickname != nil && peerNickname.length > 0) {
         [request setObject:peerNickname forKey:@"peer_nickname"];
     } else {
         [request setObject:[NSNull null] forKey:@"peer_nickname"];
     }

     [self sendSignedRequest:request toApiPath:@"identity/report_identity" forStore:identityStore onCompletion:^(NSDictionary *jsonObject) {
         if ([self isReportSuccessResponse:jsonObject]) {
             onCompletion();
         }
         else {
             onError([self reportErrorFromResponse:jsonObject]);
         }
     } onError:onError];
}

- (void)reportSpamForGroupWithGroupID:(uint64_t)groupID adminIdentity:(nonnull NSString *)groupAdminIdentity adminNickname:(nullable NSString *)groupAdminNickname memberCount:(NSInteger)memberCount withStore:(nonnull id<MyIdentityStoreProtocol>)identityStore onCompletion:(void(^_Nonnull)(void))onCompletion onError:(void(^_Nonnull)( NSError * _Nullable error))onError {
     if (identityStore.identity == nil) {
         onError([ThreemaError threemaError:@"No identity"]);
         return;
     }

     NSMutableDictionary *request = [[NSMutableDictionary alloc] initWithDictionary: @{
        @"reporter_identity": identityStore.identity,
        @"group_creator_identity": groupAdminIdentity,
        @"group_name": [NSNull null],
        @"group_id": [NSString stringWithFormat:@"%llu", groupID],
        @"group_member_count": @(memberCount),
     }];

     // Send an explicit null (not an empty string and not an omitted key) when no nickname was set
     if (groupAdminNickname != nil && groupAdminNickname.length > 0) {
         [request setObject:groupAdminNickname forKey:@"group_creator_nickname"];
     } else {
         [request setObject:[NSNull null] forKey:@"group_creator_nickname"];
     }

     [self sendSignedRequest:request toApiPath:@"identity/report_group" forStore:identityStore onCompletion:^(NSDictionary *jsonObject) {
         if ([self isReportSuccessResponse:jsonObject]) {
             onCompletion();
         }
         else {
             onError([self reportErrorFromResponse:jsonObject]);
         }
     } onError:onError];
}

#pragma mark - Private helpers

- (BOOL)isReportSuccessResponse:(NSDictionary *)jsonObject {
    id status = jsonObject[@"status"];
    return [status isKindOfClass:[NSString class]] && [status isEqualToString:@"success"];
}

- (NSError *)reportErrorFromResponse:(NSDictionary *)jsonObject {
    NSData *jsonData = [NSJSONSerialization isValidJSONObject:jsonObject] ? [NSJSONSerialization dataWithJSONObject:jsonObject options:0 error:nil] : nil;
    NSString *message = jsonData != nil ? [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding] : [jsonObject description];
    return [ThreemaError threemaError:message];
}

- (void)sendSignedRequest:(NSDictionary*)request toApiPath:(NSString*)apiPath forStore:(id<MyIdentityStoreProtocol>)identityStore onCompletion:(void(^)(NSDictionary *response))onCompletion onError:(void(^)(NSError *error))onError {
    [self sendSignedRequestPhase1:request toApiPath:apiPath onCompletion:^(NSDictionary *response) {
        [self sendSignedRequestPhase2:request toApiPath:apiPath phase1Response:response forStore:identityStore onCompletion:onCompletion onError:onError];
    } onError:onError];
}

- (void)sendSignedRequestPhase1:(NSDictionary*)request toApiPath:(NSString*)apiPath onCompletion:(void(^)(NSDictionary* response))onCompletion onError:(void(^)(NSError *error))onError {
    [ServerAPIRequest postJSONToAPIPath:apiPath data:request onCompletion:^(id jsonObject) {
        DDLogVerbose(@"Send API request %@ phase 1 success: %@", apiPath, jsonObject);
        onCompletion((NSDictionary*)jsonObject);
    } onError:^(NSError *error) {
        DDLogVerbose(@"Send API request %@ phase 1 failed: %@", apiPath, error);
        onError(error);
    }];
}

- (void)sendSignedRequestPhase2:(NSDictionary*)request toApiPath:(NSString*)apiPath phase1Response:(id)phase1Response forStore:(id<MyIdentityStoreProtocol>)identityStore onCompletion:(void(^)(NSDictionary *response))onCompletion onError:(void(^)(NSError *error))onError {
    
    NSDictionary *resp1 = (NSDictionary*)phase1Response;
    
    NSString *tokenStr = resp1[@"token"];
    NSData *token = [[NSData alloc] initWithBase64EncodedString:tokenStr options:0];
    NSData *tokenRespKeyPub = [[NSData alloc] initWithBase64EncodedString:resp1[@"tokenRespKeyPub"] options:0];
    
    /* Create authenticator for token */
    NSData *sharedSecret = [identityStore sharedSecretWithPublicKey:tokenRespKeyPub];
    NSData *responseKey = [[[ThreemaKDF alloc] initWithPersonal:@"3ma-csp"] deriveKeyWithSalt:@"dir" key:sharedSecret];
    NSData *response = [ThreemaKDF calculateMacWithKey:responseKey input:token];
    
    NSMutableDictionary *signedRequest = [NSMutableDictionary dictionaryWithDictionary:request];
    signedRequest[@"token"] = tokenStr;
    signedRequest[@"response"] = [response base64EncodedStringWithOptions:0];
    
    [ServerAPIRequest postJSONToAPIPath:apiPath data:signedRequest onCompletion:^(id jsonObject) {
        DDLogVerbose(@"Send API request %@ phase 2 success: %@", apiPath, jsonObject);
        onCompletion((NSDictionary*)jsonObject);
    } onError:^(NSError *error) {
        DDLogVerbose(@"Send API request %@ phase 2 failed: %@", apiPath, error);
        onError(error);
    }];
}

@end

¤ Dauer der Verarbeitung: 0.7 Sekunden  (vorverarbeitet am  2026-11-04) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover


Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Open Source Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Statistik
#Sources=1104845
#Domains=499266