Signal-iOS/SignalServiceKit/src/Network/API/Requests/OWSRequestFactory.m
Max Radermacher 6dede44db6
Remove unused CDSv1 code
It’s been replaced by CDSv2.
2023-02-22 14:43:09 -08:00

916 lines
38 KiB
Objective-C

//
// Copyright 2018 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
#import "OWSRequestFactory.h"
#import "NSData+keyVersionByte.h"
#import "OWS2FAManager.h"
#import "OWSDevice.h"
#import "OWSIdentityManager.h"
#import "ProfileManagerProtocol.h"
#import "SSKEnvironment.h"
#import "SignedPrekeyRecord.h"
#import "TSAccountManager.h"
#import "TSRequest.h"
#import <Curve25519Kit/Curve25519.h>
#import <SignalCoreKit/Cryptography.h>
#import <SignalCoreKit/NSData+OWS.h>
#import <SignalServiceKit/SignalServiceKit-Swift.h>
NS_ASSUME_NONNULL_BEGIN
NSString *const OWSRequestKey_AuthKey = @"AuthKey";
static NSString *_Nullable queryParamForIdentity(OWSIdentity identity)
{
switch (identity) {
case OWSIdentityACI:
return nil;
case OWSIdentityPNI:
return @"identity=pni";
}
}
@implementation OWSRequestFactory
+ (TSRequest *)disable2FARequest
{
return [TSRequest requestWithUrl:[NSURL URLWithString:self.textSecure2FAAPI] method:@"DELETE" parameters:@{}];
}
+ (TSRequest *)acknowledgeMessageDeliveryRequestWithServerGuid:(NSString *)serverGuid
{
OWSAssertDebug(serverGuid.length > 0);
NSString *path = [NSString stringWithFormat:@"v1/messages/uuid/%@", serverGuid];
return [TSRequest requestWithUrl:[NSURL URLWithString:path] method:@"DELETE" parameters:@{}];
}
+ (TSRequest *)deleteDeviceRequestWithDevice:(OWSDevice *)device
{
OWSAssertDebug(device);
NSString *path = [NSString stringWithFormat:self.textSecureDevicesAPIFormat, @(device.deviceId)];
return [TSRequest requestWithUrl:[NSURL URLWithString:path] method:@"DELETE" parameters:@{}];
}
+ (TSRequest *)getDevicesRequest
{
NSString *path = [NSString stringWithFormat:self.textSecureDevicesAPIFormat, @""];
return [TSRequest requestWithUrl:[NSURL URLWithString:path] method:@"GET" parameters:@{}];
}
+ (TSRequest *)getMessagesRequest
{
TSRequest *request = [TSRequest requestWithUrl:[NSURL URLWithString:@"v1/messages"] method:@"GET" parameters:@{}];
[StoryManager appendStoryHeadersToRequest:request];
request.shouldMarkDeregisteredOn401 = YES;
return request;
}
+ (TSRequest *)getUnversionedProfileRequestWithAddress:(SignalServiceAddress *)address
udAccessKey:(nullable SMKUDAccessKey *)udAccessKey
{
OWSAssertDebug(address.isValid);
OWSAssertDebug(address.uuid != nil);
NSString *path = [NSString stringWithFormat:@"v1/profile/%@", address.uuidString];
TSRequest *request = [TSRequest requestWithUrl:[NSURL URLWithString:path] method:@"GET" parameters:@{}];
if (udAccessKey != nil) {
[self useUDAuthWithRequest:request accessKey:udAccessKey];
}
return request;
}
+ (TSRequest *)getVersionedProfileRequestWithServiceId:(ServiceIdObjC *)serviceId
profileKeyVersion:(nullable NSString *)profileKeyVersion
credentialRequest:(nullable NSData *)credentialRequest
udAccessKey:(nullable SMKUDAccessKey *)udAccessKey
{
NSString *uuidParam = serviceId.uuidValue.UUIDString.lowercaseString;
NSString *_Nullable profileKeyVersionParam = profileKeyVersion.lowercaseString;
NSString *_Nullable credentialRequestParam = credentialRequest.hexadecimalString.lowercaseString;
// GET /v1/profile/{uuid}/{version}/{profile_key_credential_request}
NSString *path;
if (profileKeyVersion.length > 0 && credentialRequest.length > 0) {
path = [NSString stringWithFormat:@"v1/profile/%@/%@/%@?credentialType=expiringProfileKey",
uuidParam,
profileKeyVersionParam,
credentialRequestParam];
} else if (profileKeyVersion.length > 0) {
path = [NSString stringWithFormat:@"v1/profile/%@/%@", uuidParam, profileKeyVersionParam];
} else {
path = [NSString stringWithFormat:@"v1/profile/%@", uuidParam];
}
TSRequest *request = [TSRequest requestWithUrl:[NSURL URLWithString:path] method:@"GET" parameters:@{}];
if (udAccessKey != nil) {
[self useUDAuthWithRequest:request accessKey:udAccessKey];
}
return request;
}
+ (TSRequest *)turnServerInfoRequest
{
return [TSRequest requestWithUrl:[NSURL URLWithString:@"v1/accounts/turn"] method:@"GET" parameters:@{}];
}
+ (TSRequest *)allocAttachmentRequestV2
{
return [TSRequest requestWithUrl:[NSURL URLWithString:@"v2/attachments/form/upload"] method:@"GET" parameters:@{}];
}
+ (TSRequest *)allocAttachmentRequestV3
{
return [TSRequest requestWithUrl:[NSURL URLWithString:@"v3/attachments/form/upload"] method:@"GET" parameters:@{}];
}
+ (TSRequest *)availablePreKeysCountRequestForIdentity:(OWSIdentity)identity
{
NSString *path = self.textSecureKeysAPI;
NSString *queryParam = queryParamForIdentity(identity);
if (queryParam != nil) {
path = [path stringByAppendingFormat:@"?%@", queryParam];
}
return [TSRequest requestWithUrl:[NSURL URLWithString:path] method:@"GET" parameters:@{}];
}
+ (TSRequest *)contactsIntersectionRequestWithHashesArray:(NSArray<NSString *> *)hashes
{
OWSAssertDebug(hashes.count > 0);
NSString *path = [NSString stringWithFormat:@"%@/%@", self.textSecureDirectoryAPI, @"tokens"];
return [TSRequest requestWithUrl:[NSURL URLWithString:path]
method:@"PUT"
parameters:@{
@"contacts" : hashes,
}];
}
+ (TSRequest *)currentSignedPreKeyRequest
{
NSString *path = self.textSecureSignedKeysAPI;
return [TSRequest requestWithUrl:[NSURL URLWithString:path] method:@"GET" parameters:@{}];
}
+ (TSRequest *)profileAvatarUploadFormRequest
{
NSString *path = self.textSecureProfileAvatarFormAPI;
return [TSRequest requestWithUrl:[NSURL URLWithString:path] method:@"GET" parameters:@{}];
}
+ (TSRequest *)recipientPreKeyRequestWithServiceId:(ServiceIdObjC *)serviceId
deviceId:(NSString *)deviceId
udAccessKey:(nullable SMKUDAccessKey *)udAccessKey
{
OWSAssertDebug(deviceId.length > 0);
NSString *path =
[NSString stringWithFormat:@"%@/%@/%@", self.textSecureKeysAPI, serviceId.uuidValue.UUIDString, deviceId];
TSRequest *request = [TSRequest requestWithUrl:[NSURL URLWithString:path] method:@"GET" parameters:@{}];
if (udAccessKey != nil) {
[self useUDAuthWithRequest:request accessKey:udAccessKey];
}
return request;
}
+ (TSRequest *)registerForPushRequestWithPushIdentifier:(NSString *)identifier
voipIdentifier:(nullable NSString *)voipId
{
OWSAssertDebug(identifier.length > 0);
NSString *path = [NSString stringWithFormat:@"%@/%@", self.textSecureAccountsAPI, @"apn"];
NSMutableDictionary *parameters = [@{ @"apnRegistrationId" : identifier } mutableCopy];
if (voipId.length > 0) {
parameters[@"voipRegistrationId"] = voipId;
} else {
OWSAssertDebug(SSKFeatureFlags.notificationServiceExtension);
}
return [TSRequest requestWithUrl:[NSURL URLWithString:path] method:@"PUT" parameters:parameters];
}
+ (TSRequest *)updatePrimaryDeviceAttributesRequest
{
// If you are updating capabilities for a secondary device, use `updateSecondaryDeviceCapabilities` instead
OWSAssertDebug(self.tsAccountManager.isPrimaryDevice);
NSString *authKey = self.tsAccountManager.storedServerAuthToken;
OWSAssertDebug(authKey.length > 0);
NSString *_Nullable pin = [self.ows2FAManager pinCode];
BOOL isManualMessageFetchEnabled = self.tsAccountManager.isManualMessageFetchEnabled;
NSDictionary<NSString *, id> *accountAttributes = [self accountAttributesWithAuthKey:authKey
pin:pin
encryptedDeviceName:nil
isManualMessageFetchEnabled:isManualMessageFetchEnabled
isSecondaryDevice:NO];
return [TSRequest requestWithUrl:[NSURL URLWithString:self.textSecureAttributesAPI]
method:@"PUT"
parameters:accountAttributes];
}
+ (TSRequest *)accountWhoAmIRequest
{
return [TSRequest requestWithUrl:[NSURL URLWithString:@"v1/accounts/whoami"] method:@"GET" parameters:@{}];
}
+ (TSRequest *)unregisterAccountRequest
{
NSString *path = [NSString stringWithFormat:@"%@/%@", self.textSecureAccountsAPI, @"me"];
return [TSRequest requestWithUrl:[NSURL URLWithString:path] method:@"DELETE" parameters:@{}];
}
+ (TSRequest *)requestVerificationCodeRequestWithE164:(NSString *)e164
preauthChallenge:(nullable NSString *)preauthChallenge
captchaToken:(nullable NSString *)captchaToken
transport:(TSVerificationTransport)transport
{
OWSAssertDebug(e164.length > 0);
NSMutableArray<NSURLQueryItem *> *queryItems = [NSMutableArray new];
[queryItems addObject:[NSURLQueryItem queryItemWithName:@"client" value:@"ios"]];
if (captchaToken.length > 0) {
[queryItems addObject:[NSURLQueryItem queryItemWithName:@"captcha" value:captchaToken]];
}
if (preauthChallenge.length > 0) {
[queryItems addObject:[NSURLQueryItem queryItemWithName:@"challenge" value:preauthChallenge]];
}
NSString *path = [NSString
stringWithFormat:@"%@/%@/code/%@", self.textSecureAccountsAPI, [self stringForTransport:transport], e164];
NSURLComponents *components = [[NSURLComponents alloc] initWithString:path];
components.queryItems = queryItems;
TSRequest *request = [TSRequest requestWithUrl:components.URL method:@"GET" parameters:@{}];
request.shouldHaveAuthorizationHeaders = NO;
if (transport == TSVerificationTransportVoice) {
NSString *_Nullable localizationHeader = [self voiceCodeLocalizationHeader];
if (localizationHeader.length > 0) {
[request setValue:localizationHeader forHTTPHeaderField:@"Accept-Language"];
}
}
return request;
}
+ (nullable NSString *)voiceCodeLocalizationHeader
{
NSLocale *locale = [NSLocale currentLocale];
NSString *_Nullable languageCode = [locale objectForKey:NSLocaleLanguageCode];
NSString *_Nullable countryCode = [locale objectForKey:NSLocaleCountryCode];
if (!languageCode) {
return nil;
}
OWSAssertDebug([languageCode rangeOfString:@"-"].location == NSNotFound);
if (!countryCode) {
// In the absence of a country code, just send a language code.
return languageCode;
}
OWSAssertDebug(languageCode.length == 2);
OWSAssertDebug(countryCode.length == 2);
return [NSString stringWithFormat:@"%@-%@", languageCode, countryCode];
}
+ (NSString *)stringForTransport:(TSVerificationTransport)transport
{
switch (transport) {
case TSVerificationTransportSMS:
return @"sms";
case TSVerificationTransportVoice:
return @"voice";
}
}
+ (TSRequest *)verifyPrimaryDeviceRequestWithVerificationCode:(NSString *)verificationCode
phoneNumber:(NSString *)phoneNumber
authKey:(NSString *)authKey
pin:(nullable NSString *)pin
checkForAvailableTransfer:(BOOL)checkForAvailableTransfer
{
OWSAssertDebug(verificationCode.length > 0);
OWSAssertDebug(phoneNumber.length > 0);
OWSAssertDebug(authKey.length > 0);
NSString *path = [NSString stringWithFormat:@"%@/code/%@", self.textSecureAccountsAPI, verificationCode];
if (checkForAvailableTransfer) {
path = [path stringByAppendingString:@"?transfer=true"];
}
BOOL isManualMessageFetchEnabled = self.tsAccountManager.isManualMessageFetchEnabled;
NSMutableDictionary<NSString *, id> *accountAttributes =
[[self accountAttributesWithAuthKey:authKey
pin:pin
encryptedDeviceName:nil
isManualMessageFetchEnabled:isManualMessageFetchEnabled
isSecondaryDevice:NO] mutableCopy];
[accountAttributes removeObjectForKey:OWSRequestKey_AuthKey];
TSRequest *request =
[TSRequest requestWithUrl:[NSURL URLWithString:path] method:@"PUT" parameters:accountAttributes];
// The "verify code" request handles auth differently.
request.authUsername = phoneNumber;
request.authPassword = authKey;
return request;
}
+ (TSRequest *)verifySecondaryDeviceRequestWithVerificationCode:(NSString *)verificationCode
phoneNumber:(NSString *)phoneNumber
authKey:(NSString *)authKey
encryptedDeviceName:(NSData *)encryptedDeviceName
{
OWSAssertDebug(verificationCode.length > 0);
OWSAssertDebug(phoneNumber.length > 0);
OWSAssertDebug(authKey.length > 0);
OWSAssertDebug(encryptedDeviceName.length > 0);
NSString *path = [NSString stringWithFormat:@"v1/devices/%@", verificationCode];
NSMutableDictionary<NSString *, id> *accountAttributes = [[self accountAttributesWithAuthKey:authKey
pin:nil
encryptedDeviceName:encryptedDeviceName
isManualMessageFetchEnabled:YES
isSecondaryDevice:YES] mutableCopy];
[accountAttributes removeObjectForKey:OWSRequestKey_AuthKey];
TSRequest *request = [TSRequest requestWithUrl:[NSURL URLWithString:path]
method:@"PUT"
parameters:accountAttributes];
// The "verify code" request handles auth differently.
request.authUsername = phoneNumber;
request.authPassword = authKey;
return request;
}
+ (TSRequest *)currencyConversionRequest NS_SWIFT_NAME(currencyConversionRequest())
{
return [TSRequest requestWithUrl:[NSURL URLWithString:@"v1/payments/conversions"] method:@"GET" parameters:@{}];
}
+ (NSDictionary<NSString *, id> *)accountAttributesWithAuthKey:(NSString *)authKey
pin:(nullable NSString *)pin
encryptedDeviceName:(nullable NSData *)encryptedDeviceName
isManualMessageFetchEnabled:(BOOL)isManualMessageFetchEnabled
isSecondaryDevice:(BOOL)isSecondaryDevice
{
OWSAssertDebug(authKey.length > 0);
__block uint32_t registrationId;
__block uint32_t pniRegistrationId;
DatabaseStorageWrite(self.databaseStorage, ^(SDSAnyWriteTransaction *transaction) {
registrationId = [self.tsAccountManager getOrGenerateRegistrationIdWithTransaction:transaction];
pniRegistrationId = [self.tsAccountManager getOrGeneratePniRegistrationIdWithTransaction:transaction];
});
OWSAES256Key *profileKey = [self.profileManager localProfileKey];
NSError *error;
SMKUDAccessKey *_Nullable udAccessKey = [[SMKUDAccessKey alloc] initWithProfileKey:profileKey.keyData error:&error];
if (error || udAccessKey.keyData.length < 1) {
// Crash app if UD cannot be enabled.
OWSFail(@"Could not determine UD access key: %@.", error);
}
BOOL allowUnrestrictedUD = [self.udManager shouldAllowUnrestrictedAccessLocal] && udAccessKey != nil;
// We no longer include the signalingKey.
NSMutableDictionary *accountAttributes = [@{
OWSRequestKey_AuthKey : authKey,
@"voice" : @(YES), // all Signal-iOS clients support voice
@"video" : @(YES), // all Signal-iOS clients support WebRTC-based voice and video calls.
@"fetchesMessages" : @(isManualMessageFetchEnabled), // devices that don't support push must tell the server
// they fetch messages manually
@"registrationId" : [NSString stringWithFormat:@"%i", registrationId],
@"pniRegistrationId" : [NSString stringWithFormat:@"%i", pniRegistrationId],
@"unidentifiedAccessKey" : udAccessKey.keyData.base64EncodedString,
@"unrestrictedUnidentifiedAccess" : @(allowUnrestrictedUD),
} mutableCopy];
NSString *_Nullable registrationLockToken = [KeyBackupServiceObjcBridge deriveRegistrationLockToken];
if (registrationLockToken.length > 0 && OWS2FAManager.shared.isRegistrationLockV2Enabled) {
accountAttributes[@"registrationLock"] = registrationLockToken;
} else if (pin.length > 0 && self.ows2FAManager.mode != OWS2FAMode_V2) {
accountAttributes[@"pin"] = pin;
}
if (encryptedDeviceName.length > 0) {
accountAttributes[@"name"] = encryptedDeviceName.base64EncodedString;
}
if (SSKFeatureFlags.phoneNumberDiscoverability) {
accountAttributes[@"discoverableByPhoneNumber"] = @([self.tsAccountManager isDiscoverableByPhoneNumber]);
}
accountAttributes[@"capabilities"] = [self deviceCapabilitiesWithIsSecondaryDevice:isSecondaryDevice];
return [accountAttributes copy];
}
+ (TSRequest *)updateSecondaryDeviceCapabilitiesRequest
{
// If you are updating capabilities for a primary device, use `updateAccountAttributes` instead
OWSAssertDebug(!self.tsAccountManager.isPrimaryDevice);
return [TSRequest requestWithUrl:[NSURL URLWithString:@"v1/devices/capabilities"]
method:@"PUT"
parameters:[self deviceCapabilitiesWithIsSecondaryDevice:YES]];
}
+ (NSDictionary<NSString *, NSNumber *> *)deviceCapabilitiesForLocalDevice
{
// tsAccountManager.isPrimaryDevice only has a valid value for registered
// devices.
OWSAssertDebug(self.tsAccountManager.isRegisteredAndReady);
BOOL isSecondaryDevice = !self.tsAccountManager.isPrimaryDevice;
return [self deviceCapabilitiesWithIsSecondaryDevice:isSecondaryDevice];
}
+ (NSDictionary<NSString *, NSNumber *> *)deviceCapabilitiesWithIsSecondaryDevice:(BOOL)isSecondaryDevice
{
NSMutableDictionary<NSString *, NSNumber *> *capabilities = [NSMutableDictionary new];
capabilities[@"gv2"] = @(YES);
capabilities[@"gv2-2"] = @(YES);
capabilities[@"gv2-3"] = @(YES);
capabilities[@"transfer"] = @(YES);
capabilities[@"announcementGroup"] = @(YES);
capabilities[@"senderKey"] = @(YES);
if (RemoteConfig.stories || isSecondaryDevice) {
capabilities[@"stories"] = @(YES);
}
if (RemoteConfig.canReceiveGiftBadges) {
capabilities[@"giftBadges"] = @(YES);
}
// If the storage service requires (or will require) secondary devices
// to have a capability in order to be linked, we might need to always
// set that capability here if isSecondaryDevice is true.
if (KeyBackupServiceObjcBridge.hasBackedUpMasterKey) {
capabilities[@"storage"] = @(YES);
}
capabilities[@"changeNumber"] = @(YES);
OWSLogInfo(@"local device capabilities: %@", capabilities);
return [capabilities copy];
}
+ (TSRequest *)submitMessageRequestWithServiceId:(ServiceIdObjC *)serviceId
messages:(NSArray<DeviceMessage *> *)messages
timestamp:(uint64_t)timestamp
udAccessKey:(nullable SMKUDAccessKey *)udAccessKey
isOnline:(BOOL)isOnline
isUrgent:(BOOL)isUrgent
isStory:(BOOL)isStory
{
// NOTE: messages may be empty; See comments in OWSDeviceManager.
OWSAssertDebug(timestamp > 0);
NSString *path = [self.textSecureMessagesAPI stringByAppendingString:serviceId.uuidValue.UUIDString];
path = [path stringByAppendingFormat:@"?story=%@", isStory ? @"true" : @"false"];
// Returns the per-account-message parameters used when submitting a message to
// the Signal Web Service.
// See
// <https://github.com/signalapp/Signal-Server/blob/65da844d70369cb8b44966cfb2d2eb9b925a6ba4/service/src/main/java/org/whispersystems/textsecuregcm/entities/IncomingMessageList.java>.
NSDictionary *parameters = @{
@"messages" : [messages map:^id _Nonnull(DeviceMessage *_Nonnull item) { return [item requestParameters]; }],
@"timestamp" : @(timestamp),
@"online" : @(isOnline),
@"urgent" : @(isUrgent)
};
TSRequest *request = [TSRequest requestWithUrl:[NSURL URLWithString:path] method:@"PUT" parameters:parameters];
if (udAccessKey != nil) {
[self useUDAuthWithRequest:request accessKey:udAccessKey];
}
return request;
}
+ (TSRequest *)submitMultiRecipientMessageRequestWithCiphertext:(NSData *)ciphertext
compositeUDAccessKey:(SMKUDAccessKey *)udAccessKey
timestamp:(uint64_t)timestamp
isOnline:(BOOL)isOnline
isUrgent:(BOOL)isUrgent
isStory:(BOOL)isStory
{
OWSAssertDebug(ciphertext);
OWSAssertDebug(udAccessKey);
OWSAssertDebug(timestamp > 0);
// We build the URL by hand instead of passing the query parameters into the query parameters
// AFNetworking won't handle both query parameters and an httpBody (which we need here)
NSURLComponents *components = [[NSURLComponents alloc] initWithString:self.textSecureMultiRecipientMessageAPI];
components.queryItems = @[
[NSURLQueryItem queryItemWithName:@"ts" value:[@(timestamp) stringValue]],
[NSURLQueryItem queryItemWithName:@"online" value:isOnline ? @"true" : @"false"],
[NSURLQueryItem queryItemWithName:@"urgent" value:isUrgent ? @"true" : @"false"],
[NSURLQueryItem queryItemWithName:@"story" value:isStory ? @"true" : @"false"],
];
NSURL *url = [components URL];
TSRequest *request = [TSRequest requestWithUrl:url method:@"PUT" parameters:nil];
[request setValue:kSenderKeySendRequestBodyContentType forHTTPHeaderField:@"Content-Type"];
if (udAccessKey != nil) {
[self useUDAuthWithRequest:request accessKey:udAccessKey];
}
request.HTTPBody = [ciphertext copy];
return request;
}
+ (TSRequest *)registerSignedPrekeyRequestForIdentity:(OWSIdentity)identity
signedPreKey:(SignedPreKeyRecord *)signedPreKey
{
OWSAssertDebug(signedPreKey);
NSString *path = self.textSecureSignedKeysAPI;
NSString *queryParam = queryParamForIdentity(identity);
if (queryParam != nil) {
path = [path stringByAppendingFormat:@"?%@", queryParam];
}
return [TSRequest requestWithUrl:[NSURL URLWithString:path]
method:@"PUT"
parameters:[self signedPreKeyRequestParameters:signedPreKey]];
}
+ (TSRequest *)registerPrekeysRequestForIdentity:(OWSIdentity)identity
prekeyArray:(NSArray *)prekeys
identityKey:(NSData *)identityKeyPublic
signedPreKey:(SignedPreKeyRecord *)signedPreKey
{
OWSAssertDebug(prekeys.count > 0);
OWSAssertDebug(identityKeyPublic.length > 0);
OWSAssertDebug(signedPreKey);
NSString *path = self.textSecureKeysAPI;
NSString *queryParam = queryParamForIdentity(identity);
if (queryParam != nil) {
path = [path stringByAppendingFormat:@"?%@", queryParam];
}
NSString *publicIdentityKey = [[identityKeyPublic prependKeyType] base64EncodedStringWithOptions:0];
NSMutableArray *serializedPrekeyList = [NSMutableArray array];
for (PreKeyRecord *preKey in prekeys) {
[serializedPrekeyList addObject:[self preKeyRequestParameters:preKey]];
}
return [TSRequest requestWithUrl:[NSURL URLWithString:path]
method:@"PUT"
parameters:@{
@"preKeys" : serializedPrekeyList,
@"signedPreKey" : [self signedPreKeyRequestParameters:signedPreKey],
@"identityKey" : publicIdentityKey
}];
}
#pragma mark - Storage Service
+ (TSRequest *)storageAuthRequest
{
return [TSRequest requestWithUrl:[NSURL URLWithString:@"v1/storage/auth"] method:@"GET" parameters:@{}];
}
#pragma mark - Remote Attestation
+ (TSRequest *)remoteAttestationAuthRequestForKeyBackup
{
return [TSRequest requestWithUrl:[NSURL URLWithString:@"v1/backup/auth"] method:@"GET" parameters:@{}];
}
+ (TSRequest *)remoteAttestationAuthRequestForCDSI
{
return [TSRequest requestWithUrl:[NSURL URLWithString:@"v2/directory/auth"] method:@"GET" parameters:@{}];
}
#pragma mark - KBS
+ (TSRequest *)kbsEnclaveTokenRequestWithEnclaveName:(NSString *)enclaveName
authUsername:(NSString *)authUsername
authPassword:(NSString *)authPassword
cookies:(NSArray<NSHTTPCookie *> *)cookies
{
NSString *path = [NSString stringWithFormat:@"v1/token/%@", enclaveName];
TSRequest *request = [TSRequest requestWithUrl:[NSURL URLWithString:path] method:@"GET" parameters:@{}];
request.authUsername = authUsername;
request.authPassword = authPassword;
// Set the cookie header.
// OWSURLSession disables default cookie handling for all requests.
OWSAssertDebug(request.allHTTPHeaderFields.count == 0);
[request setAllHTTPHeaderFields:[NSHTTPCookie requestHeaderFieldsWithCookies:cookies]];
return request;
}
+ (TSRequest *)kbsEnclaveRequestWithRequestId:(NSData *)requestId
data:(NSData *)data
cryptIv:(NSData *)cryptIv
cryptMac:(NSData *)cryptMac
enclaveName:(NSString *)enclaveName
authUsername:(NSString *)authUsername
authPassword:(NSString *)authPassword
cookies:(NSArray<NSHTTPCookie *> *)cookies
requestType:(NSString *)requestType
{
NSString *path = [NSString stringWithFormat:@"v1/backup/%@", enclaveName];
TSRequest *request = [TSRequest requestWithUrl:[NSURL URLWithString:path]
method:@"PUT"
parameters:@{
@"requestId" : requestId.base64EncodedString,
@"data" : data.base64EncodedString,
@"iv" : cryptIv.base64EncodedString,
@"mac" : cryptMac.base64EncodedString,
@"type" : requestType
}];
request.authUsername = authUsername;
request.authPassword = authPassword;
// Set the cookie header.
// OWSURLSession disables default cookie handling for all requests.
OWSAssertDebug(request.allHTTPHeaderFields.count == 0);
[request setAllHTTPHeaderFields:[NSHTTPCookie requestHeaderFieldsWithCookies:cookies]];
return request;
}
#pragma mark - UD
+ (TSRequest *)udSenderCertificateRequestWithUuidOnly:(BOOL)uuidOnly
{
NSString *path = @"v1/certificate/delivery?includeUuid=true";
if (uuidOnly) {
path = [path stringByAppendingString:@"&includeE164=false"];
}
return [TSRequest requestWithUrl:[NSURL URLWithString:path] method:@"GET" parameters:@{}];
}
+ (void)useUDAuthWithRequest:(TSRequest *)request accessKey:(SMKUDAccessKey *)udAccessKey
{
OWSAssertDebug(request);
OWSAssertDebug(udAccessKey);
// Suppress normal auth headers.
request.shouldHaveAuthorizationHeaders = NO;
// Add UD auth header.
[request setValue:[udAccessKey.keyData base64EncodedString] forHTTPHeaderField:@"Unidentified-Access-Key"];
request.isUDRequest = YES;
}
#pragma mark - Profiles
+ (TSRequest *)profileNameSetRequestWithEncryptedPaddedName:(NSData *)encryptedPaddedName
{
const NSUInteger kEncodedNameLength = 108;
NSString *base64EncodedName = [encryptedPaddedName base64EncodedString];
NSString *urlEncodedName;
// name length must match exactly
if (base64EncodedName.length == kEncodedNameLength) {
urlEncodedName = base64EncodedName.encodeURIComponent;
} else {
// if name length doesn't match exactly, use a blank name.
// Since names are required, the server will reject this with HTTP405,
// which is desirable - we want this request to fail rather than upload
// a broken name.
OWSFailDebug(@"Couldn't encode name.");
OWSAssertDebug(encryptedPaddedName == nil);
urlEncodedName = @"";
}
NSString *urlString = [NSString stringWithFormat:@"v1/profile/name/%@", urlEncodedName];
NSURL *url = [NSURL URLWithString:urlString];
TSRequest *request = [[TSRequest alloc] initWithURL:url];
request.HTTPMethod = @"PUT";
return request;
}
#pragma mark - Versioned Profiles
+ (TSRequest *)versionedProfileSetRequestWithName:(nullable ProfileValue *)name
bio:(nullable ProfileValue *)bio
bioEmoji:(nullable ProfileValue *)bioEmoji
hasAvatar:(BOOL)hasAvatar
paymentAddress:(nullable ProfileValue *)paymentAddress
visibleBadgeIds:(NSArray<NSString *> *)visibleBadgeIds
version:(NSString *)version
commitment:(NSData *)commitment
{
OWSAssertDebug(version.length > 0);
OWSAssertDebug(commitment.length > 0);
NSString *base64EncodedCommitment = [commitment base64EncodedString];
NSMutableDictionary<NSString *, NSObject *> *parameters = [@{
@"version" : version,
@"avatar" : @(hasAvatar),
@"commitment" : base64EncodedCommitment,
} mutableCopy];
if (name != nil) {
OWSAssertDebug(name.hasValidBase64Length);
parameters[@"name"] = name.encryptedBase64;
}
if (bio != nil) {
OWSAssertDebug(bio.hasValidBase64Length);
parameters[@"about"] = bio.encryptedBase64;
}
if (bioEmoji != nil) {
OWSAssertDebug(bioEmoji.hasValidBase64Length);
parameters[@"aboutEmoji"] = bioEmoji.encryptedBase64;
}
if (paymentAddress != nil) {
OWSAssertDebug(paymentAddress.hasValidBase64Length);
parameters[@"paymentAddress"] = paymentAddress.encryptedBase64;
}
parameters[@"badgeIds"] = [visibleBadgeIds copy];
NSURL *url = [NSURL URLWithString:self.textSecureVersionedProfileAPI];
return [TSRequest requestWithUrl:url
method:@"PUT"
parameters:parameters];
}
#pragma mark - Remote Config
+ (TSRequest *)getRemoteConfigRequest
{
NSURL *url = [NSURL URLWithString:@"/v1/config/"];
return [TSRequest requestWithUrl:url method:@"GET" parameters:@{}];
}
#pragma mark - Groups v2
+ (TSRequest *)groupAuthenticationCredentialRequestWithFromRedemptionSeconds:(uint64_t)fromRedemptionSeconds
toRedemptionSeconds:(uint64_t)toRedemptionSeconds
{
OWSAssertDebug(fromRedemptionSeconds > 0);
OWSAssertDebug(toRedemptionSeconds > 0);
NSString *path =
[NSString stringWithFormat:@"/v1/certificate/auth/group?redemptionStartSeconds=%llu&redemptionEndSeconds=%llu",
(unsigned long long)fromRedemptionSeconds,
(unsigned long long)toRedemptionSeconds];
return [TSRequest requestWithUrl:[NSURL URLWithString:path] method:@"GET" parameters:@{}];
}
#pragma mark - Payments
+ (TSRequest *)paymentsAuthenticationCredentialRequest
{
NSString *path = @"/v1/payments/auth";
return [TSRequest requestWithUrl:[NSURL URLWithString:path] method:@"GET" parameters:@{}];
}
#pragma mark - Spam
+ (TSRequest *)pushChallengeRequest
{
return [TSRequest requestWithUrl:[NSURL URLWithString:@"/v1/challenge/push"] method:@"POST" parameters:@{}];
}
+ (TSRequest *)pushChallengeResponseWithToken:(NSString *)challengeToken
{
return [TSRequest requestWithUrl:[NSURL URLWithString:@"/v1/challenge"]
method:@"PUT"
parameters:@{ @"type" : @"rateLimitPushChallenge", @"challenge" : challengeToken }];
}
+ (TSRequest *)recaptchChallengeResponseWithToken:(NSString *)serverToken captchaToken:(NSString *)captchaToken
{
return [TSRequest requestWithUrl:[NSURL URLWithString:@"/v1/challenge"]
method:@"PUT"
parameters:@{ @"type" : @"recaptcha", @"token" : serverToken, @"captcha" : captchaToken }];
}
#pragma mark - Subscriptions
+ (TSRequest *)subscriptionCreateStripePaymentMethodRequest:(NSString *)base64SubscriberID
{
TSRequest *request = [TSRequest
requestWithUrl:[NSURL URLWithString:[NSString stringWithFormat:@"/v1/subscription/%@/create_payment_method",
base64SubscriberID]]
method:@"POST"
parameters:@{}];
request.shouldHaveAuthorizationHeaders = NO;
request.shouldRedactUrlInLogs = YES;
return request;
}
+ (TSRequest *)subscriptionCreatePaypalPaymentMethodRequest:(NSString *)base64SubscriberID
returnUrl:(NSURL *)returnUrl
cancelUrl:(NSURL *)cancelUrl
{
TSRequest *request = [TSRequest
requestWithUrl:[NSURL
URLWithString:[NSString stringWithFormat:@"/v1/subscription/%@/create_payment_method/paypal",
base64SubscriberID]]
method:@"POST"
parameters:@{ @"returnUrl" : returnUrl.absoluteString, @"cancelUrl" : cancelUrl.absoluteString }];
request.shouldHaveAuthorizationHeaders = NO;
request.shouldRedactUrlInLogs = YES;
return request;
}
+ (TSRequest *)subscriptionSetDefaultPaymentMethodRequest:(NSString *)base64SubscriberID
processor:(NSString *)processor
paymentID:(NSString *)paymentID
{
TSRequest *request = [TSRequest
requestWithUrl:[NSURL
URLWithString:[NSString stringWithFormat:@"/v1/subscription/%@/default_payment_method/%@/%@",
base64SubscriberID,
processor,
paymentID]]
method:@"POST"
parameters:@{}];
request.shouldHaveAuthorizationHeaders = NO;
request.shouldRedactUrlInLogs = YES;
return request;
}
+ (TSRequest *)subscriptionSetSubscriptionLevelRequest:(NSString *)base64SubscriberID level:(NSString *)level currency:(NSString *)currency idempotencyKey:(NSString *)idempotencyKey {
TSRequest *request = [TSRequest requestWithUrl:[NSURL URLWithString:[NSString stringWithFormat:@"/v1/subscription/%@/level/%@/%@/%@", base64SubscriberID, level, currency, idempotencyKey]]
method:@"PUT"
parameters:@{}];
request.shouldHaveAuthorizationHeaders = NO;
request.shouldRedactUrlInLogs = YES;
return request;
}
+ (TSRequest *)subscriptionReceiptCredentialsRequest:(NSString *)base64SubscriberID
request:(NSString *)base64ReceiptCredentialRequest
{
TSRequest *request = [TSRequest requestWithUrl:[NSURL URLWithString:[NSString stringWithFormat:@"/v1/subscription/%@/receipt_credentials", base64SubscriberID]]
method:@"POST"
parameters:@{@"receiptCredentialRequest" : base64ReceiptCredentialRequest}];
request.shouldHaveAuthorizationHeaders = NO;
request.shouldRedactUrlInLogs = YES;
return request;
}
+ (TSRequest *)subscriptionRedeemReceiptCredential:(NSString *)base64ReceiptCredentialPresentation
{
TSRequest *request = [TSRequest requestWithUrl:[NSURL URLWithString:@"/v1/donation/redeem-receipt"]
method:@"POST"
parameters:@{
@"receiptCredentialPresentation" : base64ReceiptCredentialPresentation,
@"visible" : @(self.subscriptionManager.displayBadgesOnProfile),
@"primary" : @(NO)
}];
return request;
}
+ (TSRequest *)subscriptionGetCurrentSubscriptionLevelRequest:(NSString *)base64SubscriberID
{
TSRequest *request = [TSRequest
requestWithUrl:[NSURL URLWithString:[NSString stringWithFormat:@"/v1/subscription/%@", base64SubscriberID]]
method:@"GET"
parameters:@{}];
request.shouldRedactUrlInLogs = YES;
request.shouldHaveAuthorizationHeaders = NO;
return request;
}
+ (TSRequest *)boostReceiptCredentialsWithPaymentIntentId:(NSString *)paymentIntentId
andRequest:(NSString *)base64ReceiptCredentialRequest
forPaymentProcessor:(NSString *)processor
{
TSRequest *request = [TSRequest requestWithUrl:[NSURL URLWithString:@"/v1/subscription/boost/receipt_credentials"]
method:@"POST"
parameters:@{
@"paymentIntentId" : paymentIntentId,
@"receiptCredentialRequest" : base64ReceiptCredentialRequest,
@"processor" : processor
}];
request.shouldHaveAuthorizationHeaders = NO;
return request;
}
@end
NS_ASSUME_NONNULL_END