Skip to content

Commit bb78ab3

Browse files
apolworkAnton Poluboiarynov
and
Anton Poluboiarynov
authored
MSUE-220: Stores first ifv to keychain (#157)
* MSUE-220: Stores first ifv to keychain * MSUE-220: Adds unit-tests for storing device_ifv to keychain --------- Co-authored-by: Anton Poluboiarynov <[email protected]>
1 parent 7a81923 commit bb78ab3

10 files changed

+317
-0
lines changed

Sift.xcodeproj/project.pbxproj

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77
objects = {
88

99
/* Begin PBXBuildFile section */
10+
0541A85D2D3E3A5F00076F57 /* SiftKeychain.h in Headers */ = {isa = PBXBuildFile; fileRef = 0541A85B2D3E3A5F00076F57 /* SiftKeychain.h */; settings = {ATTRIBUTES = (Private, ); }; };
11+
0541A85E2D3E3A5F00076F57 /* SiftKeychain.m in Sources */ = {isa = PBXBuildFile; fileRef = 0541A85C2D3E3A5F00076F57 /* SiftKeychain.m */; };
12+
0541A8602D3E3BAB00076F57 /* SiftKeychainTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 0541A85F2D3E3BAB00076F57 /* SiftKeychainTests.m */; };
13+
0541A8632D3E3D9400076F57 /* XCTestCase+Swizzling.m in Sources */ = {isa = PBXBuildFile; fileRef = 0541A8622D3E3D9400076F57 /* XCTestCase+Swizzling.m */; };
1014
1E409D2425E173B20066C922 /* NSData+GZIP.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E409D2225E173B10066C922 /* NSData+GZIP.m */; };
1115
1E409D2525E173B20066C922 /* NSData+GZIP.h in Headers */ = {isa = PBXBuildFile; fileRef = 1E409D2325E173B20066C922 /* NSData+GZIP.h */; };
1216
5E819393268E1A47007FED87 /* TaskManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 5E819391268E1A47007FED87 /* TaskManager.h */; };
@@ -63,6 +67,12 @@
6367
/* End PBXContainerItemProxy section */
6468

6569
/* Begin PBXFileReference section */
70+
0541A85B2D3E3A5F00076F57 /* SiftKeychain.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SiftKeychain.h; sourceTree = "<group>"; };
71+
0541A85C2D3E3A5F00076F57 /* SiftKeychain.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SiftKeychain.m; sourceTree = "<group>"; };
72+
0541A85F2D3E3BAB00076F57 /* SiftKeychainTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SiftKeychainTests.m; sourceTree = "<group>"; };
73+
0541A8612D3E3D9400076F57 /* XCTestCase+Swizzling.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "XCTestCase+Swizzling.h"; sourceTree = "<group>"; };
74+
0541A8622D3E3D9400076F57 /* XCTestCase+Swizzling.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "XCTestCase+Swizzling.m"; sourceTree = "<group>"; };
75+
0541A8642D3E49D500076F57 /* SiftKeychain+Testing.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SiftKeychain+Testing.h"; sourceTree = "<group>"; };
6676
1E409D2225E173B10066C922 /* NSData+GZIP.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSData+GZIP.m"; sourceTree = "<group>"; };
6777
1E409D2325E173B20066C922 /* NSData+GZIP.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSData+GZIP.h"; sourceTree = "<group>"; };
6878
5E819391268E1A47007FED87 /* TaskManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TaskManager.h; sourceTree = "<group>"; };
@@ -169,6 +179,8 @@
169179
8B5874F42552FE0C00CF8A02 /* SiftIosAppStateCollector+Private.h */,
170180
713D85981D627E860011D5FE /* SiftIosDeviceProperties.h */,
171181
713D85991D627E9F0011D5FE /* SiftIosDeviceProperties.m */,
182+
0541A85B2D3E3A5F00076F57 /* SiftKeychain.h */,
183+
0541A85C2D3E3A5F00076F57 /* SiftKeychain.m */,
172184
7168EE321C864A4100A88245 /* SiftIosDevicePropertiesCollector.h */,
173185
7168EE331C864A4100A88245 /* SiftIosDevicePropertiesCollector.m */,
174186
7168EE1D1C7E7A6800A88245 /* SiftQueue.h */,
@@ -204,6 +216,10 @@
204216
7168EE401C88C91900A88245 /* SiftUploaderTests.m */,
205217
713AF4231DA456740061B688 /* SiftUtilsTests.m */,
206218
7168EE101C7B977500A88245 /* SiftTests.m */,
219+
0541A85F2D3E3BAB00076F57 /* SiftKeychainTests.m */,
220+
0541A8642D3E49D500076F57 /* SiftKeychain+Testing.h */,
221+
0541A8612D3E3D9400076F57 /* XCTestCase+Swizzling.h */,
222+
0541A8622D3E3D9400076F57 /* XCTestCase+Swizzling.m */,
207223
);
208224
path = SiftTests;
209225
sourceTree = "<group>";
@@ -225,6 +241,7 @@
225241
1E409D2525E173B20066C922 /* NSData+GZIP.h in Headers */,
226242
71B1B5971D8C700C00EA9B17 /* SiftIosAppStateCollector.h in Headers */,
227243
716BEB8B1C7D336B009C3706 /* SiftQueueConfig.h in Headers */,
244+
0541A85D2D3E3A5F00076F57 /* SiftKeychain.h in Headers */,
228245
7168EE1E1C7E7A6800A88245 /* SiftQueue.h in Headers */,
229246
7168EE051C7B977500A88245 /* Sift.h in Headers */,
230247
713D859B1D627EA60011D5FE /* SiftIosDeviceProperties.h in Headers */,
@@ -338,6 +355,7 @@
338355
5E819394268E1A47007FED87 /* TaskManager.m in Sources */,
339356
716BEB931C7D3695009C3706 /* Sift.m in Sources */,
340357
71E15CF91D95C92300A0305C /* SiftCircularBuffer.m in Sources */,
358+
0541A85E2D3E3A5F00076F57 /* SiftKeychain.m in Sources */,
341359
7168EE281C7FB80C00A88245 /* SiftUploader.m in Sources */,
342360
71E15CFD1D95D15900A0305C /* SiftTokenBucket.m in Sources */,
343361
713D859A1D627E9F0011D5FE /* SiftIosDeviceProperties.m in Sources */,
@@ -363,6 +381,8 @@
363381
7168EE1C1C7E61E900A88245 /* SiftEventTests.m in Sources */,
364382
713D859D1D6CF69E0011D5FE /* SiftIosDevicePropertiesTests.m in Sources */,
365383
7168EE241C7F99AF00A88245 /* SiftQueueTests.m in Sources */,
384+
0541A8632D3E3D9400076F57 /* XCTestCase+Swizzling.m in Sources */,
385+
0541A8602D3E3BAB00076F57 /* SiftKeychainTests.m in Sources */,
366386
7168EE421C88C91900A88245 /* SiftUploaderTests.m in Sources */,
367387
71B1B5911D8B31ED00EA9B17 /* SiftIosAppStateTests.m in Sources */,
368388
);

Sift/SiftIosDeviceProperties.m

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#endif
77
@import Foundation;
88
@import UIKit;
9+
@import Security;
910

1011
#include <sys/sysctl.h>
1112
#include <sys/stat.h>
@@ -14,6 +15,7 @@
1415

1516
#import "SiftCompatibility.h"
1617
#import "SiftDebug.h"
18+
#import "SiftKeychain.h"
1719
#import "Sift.h"
1820

1921
#import "SiftIosDeviceProperties.h"
@@ -51,6 +53,11 @@
5153
[iosDeviceProperties setValue:ifv.UUIDString forKey:@"device_ifv"];
5254
}
5355

56+
NSString *storedIFVString = [SiftKeychain processDeviceIFV:ifv.UUIDString];
57+
if (storedIFVString) {
58+
[iosDeviceProperties setValue:storedIFVString forKey:@"initial_device_ifv"];
59+
}
60+
5461
#if !TARGET_OS_MACCATALYST
5562
CTTelephonyNetworkInfo *networkInfo = [CTTelephonyNetworkInfo new];
5663

Sift/SiftKeychain.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
//
2+
// SiftKeychain.h
3+
// Sift
4+
//
5+
// Created by Anton Poluboiarynov on 20.01.2025.
6+
// Copyright © 2025 Sift Science. All rights reserved.
7+
//
8+
9+
#import <Foundation/Foundation.h>
10+
11+
@interface SiftKeychain : NSObject
12+
+ (NSString *)processDeviceIFV:(NSString *)ifv;
13+
@end

Sift/SiftKeychain.m

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
//
2+
// SiftKeychain.m
3+
// Sift
4+
//
5+
// Created by Anton Poluboiarynov on 20.01.2025.
6+
// Copyright © 2025 Sift Science. All rights reserved.
7+
//
8+
9+
#import "SiftKeychain.h"
10+
@import Security;
11+
12+
static NSString* kSiftVendorIFVKeychainKey = @"com.sift.initial_device_ifv";
13+
14+
@implementation SiftKeychain
15+
16+
+ (NSString *)processDeviceIFV:(NSString *)ifv {
17+
NSString *storedIFVString = [self getStoredIFVString];
18+
if (storedIFVString == nil && ifv == nil) {
19+
return nil;
20+
}
21+
22+
if (storedIFVString == nil) {
23+
[self storeIFVString:ifv];
24+
return ifv;
25+
}
26+
27+
return storedIFVString;
28+
}
29+
30+
+ (NSString *)getStoredIFVString {
31+
NSDictionary *query = @{
32+
(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
33+
(__bridge id)kSecAttrAccount: kSiftVendorIFVKeychainKey,
34+
(__bridge id)kSecReturnData: (__bridge id)kCFBooleanTrue,
35+
(__bridge id)kSecMatchLimit: (__bridge id)kSecMatchLimitOne
36+
};
37+
38+
CFTypeRef result = NULL;
39+
SecItemCopyMatching((__bridge CFDictionaryRef)query, &result);
40+
41+
NSString *storedIFVString = nil;
42+
if (result) {
43+
NSData *data = (__bridge_transfer NSData *)result;
44+
storedIFVString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
45+
}
46+
return storedIFVString;
47+
}
48+
49+
+ (void)storeIFVString:(NSString *)ifv {
50+
NSData *data = [ifv dataUsingEncoding:NSUTF8StringEncoding];
51+
NSDictionary *query = @{
52+
(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
53+
(__bridge id)kSecAttrAccount: kSiftVendorIFVKeychainKey,
54+
(__bridge id)kSecValueData: data
55+
};
56+
57+
SecItemDelete((__bridge CFDictionaryRef)query);
58+
SecItemAdd((__bridge CFDictionaryRef)query, NULL);
59+
}
60+
61+
@end

SiftTests/SiftEventTests.m

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ - (void)testCollect {
6666
@"evidence_dylds_present": NSArray.class,
6767
@"evidence_files_present": NSArray.class,
6868
@"sdk_version": NSString.class,
69+
@"initial_device_ifv": NSString.class,
6970
};
7071

7172
for (NSString *name in entryTypes) {

SiftTests/SiftIosDevicePropertiesTests.m

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Copyright (c) 2016 Sift Science. All rights reserved.
22

33
@import XCTest;
4+
#import "XCTestCase+Swizzling.h"
5+
#import "SiftKeychain+Testing.h"
46

57
#import "SiftDebug.h"
68

@@ -12,6 +14,20 @@ @interface SiftIosDevicePropertiesTests : XCTestCase
1214

1315
@implementation SiftIosDevicePropertiesTests
1416

17+
- (void)setup {
18+
Method storeIFV = class_getClassMethod([SiftKeychain class], @selector(storeIFVString:));
19+
Method mockStoreIFV = class_getClassMethod([self class], @selector(mockStoreDeviceIFV));
20+
21+
[self swizzleMethod:storeIFV withMethod:mockStoreIFV];
22+
}
23+
24+
- (void)tearDown {
25+
Method storeIFV = class_getClassMethod([SiftKeychain class], @selector(storeIFVString:));
26+
Method mockStoreIFV = class_getClassMethod([self class], @selector(mockStoreDeviceIFV));
27+
28+
[self swizzleMethod:storeIFV withMethod:mockStoreIFV];
29+
}
30+
1531
- (void)testCollect {
1632
NSDictionary *actual = SFCollectIosDeviceProperties();
1733
SF_DEBUG(@"Collect device properties: %@", actual);
@@ -109,6 +125,52 @@ - (void)testMacCatalyst {
109125
#endif
110126
}
111127

128+
- (void)testProcessDeviceIFV_nilDeviceIFV {
129+
Method getStoredDeviceIFV = class_getClassMethod([SiftKeychain class], @selector(getStoredIFVString));
130+
Method mockGetStoredDeviceIFV = class_getClassMethod([self class], @selector(mockNilStoredDeviceIFV));
131+
Method deviceIdentifier = class_getInstanceMethod([UIDevice class], @selector(identifierForVendor));
132+
Method mockDeviceIdentifier = class_getClassMethod([self class], @selector(mockNilDeviceIdentifier));
133+
134+
[self swizzleMethod:getStoredDeviceIFV withMethod:mockGetStoredDeviceIFV];
135+
[self swizzleMethod:deviceIdentifier withMethod:mockDeviceIdentifier];
136+
137+
NSString *actual = SFCollectIosDeviceProperties()[@"initial_device_ifv"];
138+
139+
XCTAssertNil(actual);
140+
141+
[self swizzleMethod:mockGetStoredDeviceIFV withMethod:getStoredDeviceIFV];
142+
[self swizzleMethod:mockDeviceIdentifier withMethod:deviceIdentifier];
143+
}
144+
145+
- (void)testProcessDeviceIFV_nilStoredDeviceIFV {
146+
Method getStoredDeviceIFV = class_getClassMethod([SiftKeychain class], @selector(getStoredIFVString));
147+
Method mockGetStoredDeviceIFV = class_getClassMethod([self class], @selector(mockNilStoredDeviceIFV));
148+
149+
[self swizzleMethod:getStoredDeviceIFV withMethod:mockGetStoredDeviceIFV];
150+
151+
NSString *deviceIFV = [[UIDevice currentDevice] identifierForVendor].UUIDString;
152+
NSString *actual = SFCollectIosDeviceProperties()[@"initial_device_ifv"];
153+
154+
XCTAssertEqualObjects(actual, deviceIFV);
155+
156+
[self swizzleMethod:mockGetStoredDeviceIFV withMethod:getStoredDeviceIFV];
157+
}
158+
159+
- (void)testProcessDeviceIFV_changedDeviceIFV {
160+
Method getStoredDeviceIFV = class_getClassMethod([SiftKeychain class], @selector(getStoredIFVString));
161+
Method mockGetStoredDeviceIFV = class_getClassMethod([self class], @selector(mockChangedStoredDeviceIFV));
162+
163+
[self swizzleMethod:getStoredDeviceIFV withMethod:mockGetStoredDeviceIFV];
164+
165+
NSString *actual = SFCollectIosDeviceProperties()[@"initial_device_ifv"];
166+
167+
XCTAssertEqual(actual, @"CHANGED-DEVICE-IFV");
168+
169+
[self swizzleMethod:mockGetStoredDeviceIFV withMethod:getStoredDeviceIFV];
170+
}
171+
172+
// MARK: Helpers
173+
112174
- (NSDictionary *)generateRandomProperties {
113175
NSMutableDictionary *properties = SFMakeEmptyIosDeviceProperties();
114176
[properties setValue:[self generateRandomString] forKey:@"app_name"];
@@ -137,4 +199,19 @@ - (NSArray *)generateRandomArray {
137199
return strings;
138200
}
139201

202+
// MARK: Mocks
203+
204+
+ (NSString *)mockNilDeviceIdentifier {
205+
return nil;
206+
}
207+
208+
+ (NSString *)mockNilStoredDeviceIFV {
209+
return nil;
210+
}
211+
212+
+ (NSString *)mockChangedStoredDeviceIFV {
213+
return @"CHANGED-DEVICE-IFV";
214+
}
215+
216+
+ (void)mockStoreDeviceIFV {}
140217
@end

SiftTests/SiftKeychain+Testing.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
//
2+
// SiftKeychain+Testing.h
3+
// Sift
4+
//
5+
// Created by Anton Poluboiarynov on 20.01.2025.
6+
// Copyright © 2025 Sift Science. All rights reserved.
7+
//
8+
9+
#import "SiftKeychain.h"
10+
11+
@interface SiftKeychain ()
12+
+ (NSString *)getStoredIFVString;
13+
+ (void)storeIFVString:(NSString *)ifv;
14+
@end

SiftTests/SiftKeychainTests.m

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
//
2+
// SiftKeychainTests.m
3+
// SiftTests
4+
//
5+
// Created by Anton Poluboiarynov on 20.01.2025.
6+
// Copyright © 2025 Sift Science. All rights reserved.
7+
//
8+
9+
#import <XCTest/XCTest.h>
10+
#import "XCTestCase+Swizzling.h"
11+
#import "SiftKeychain+Testing.h"
12+
13+
@interface SiftKeychainTests : XCTestCase
14+
15+
@end
16+
17+
@implementation SiftKeychainTests
18+
19+
- (void)setup {
20+
Method storeIFV = class_getClassMethod([SiftKeychain class], @selector(storeIFVString:));
21+
Method mockStoreIFV = class_getClassMethod([SiftKeychainTests class], @selector(mockStoreDeviceIFV));
22+
23+
[self swizzleMethod:storeIFV withMethod:mockStoreIFV];
24+
}
25+
26+
- (void)tearDown {
27+
Method storeIFV = class_getClassMethod([SiftKeychain class], @selector(storeIFVString:));
28+
Method mockStoreIFV = class_getClassMethod([SiftKeychainTests class], @selector(mockStoreDeviceIFV));
29+
30+
[self swizzleMethod:storeIFV withMethod:mockStoreIFV];
31+
}
32+
33+
- (void)testProcessDeviceIFV_nilDeviceIFV {
34+
Method getStoredDeviceIFV = class_getClassMethod([SiftKeychain class], @selector(getStoredIFVString));
35+
Method mockGetStoredDeviceIFV = class_getClassMethod([SiftKeychainTests class], @selector(mockNilStoredDeviceIFV));
36+
37+
[self swizzleMethod:getStoredDeviceIFV withMethod:mockGetStoredDeviceIFV];
38+
39+
NSString *actual = [SiftKeychain processDeviceIFV:nil];
40+
41+
XCTAssertNil(actual);
42+
43+
[self swizzleMethod:mockGetStoredDeviceIFV withMethod:getStoredDeviceIFV];
44+
}
45+
46+
- (void)testProcessDeviceIFV_nilStoredDeviceIFV {
47+
Method getStoredDeviceIFV = class_getClassMethod([SiftKeychain class], @selector(getStoredIFVString));
48+
Method mockGetStoredDeviceIFV = class_getClassMethod([SiftKeychainTests class], @selector(mockNilStoredDeviceIFV));
49+
50+
[self swizzleMethod:getStoredDeviceIFV withMethod:mockGetStoredDeviceIFV];
51+
52+
NSString *deviceIFV = @"DEVICE-IFV";
53+
NSString *actual = [SiftKeychain processDeviceIFV:deviceIFV];
54+
55+
XCTAssertEqual(actual, deviceIFV);
56+
57+
[self swizzleMethod:mockGetStoredDeviceIFV withMethod:getStoredDeviceIFV];
58+
}
59+
60+
- (void)testProcessDeviceIFV_changedDeviceIFV {
61+
Method getStoredDeviceIFV = class_getClassMethod([SiftKeychain class], @selector(getStoredIFVString));
62+
Method mockGetStoredDeviceIFV = class_getClassMethod([SiftKeychainTests class], @selector(mockChangedStoredDeviceIFV));
63+
64+
[self swizzleMethod:getStoredDeviceIFV withMethod:mockGetStoredDeviceIFV];
65+
66+
NSString *deviceIFV = @"DEVICE-IFV";
67+
NSString *actual = [SiftKeychain processDeviceIFV:deviceIFV];
68+
69+
XCTAssertEqual(actual, @"CHANGED-DEVICE-IFV");
70+
71+
[self swizzleMethod:mockGetStoredDeviceIFV withMethod:getStoredDeviceIFV];
72+
}
73+
74+
// MARK: Mocks
75+
76+
+ (NSString *)mockNilStoredDeviceIFV {
77+
return nil;
78+
}
79+
80+
+ (NSString *)mockChangedStoredDeviceIFV {
81+
return @"CHANGED-DEVICE-IFV";
82+
}
83+
84+
+ (void)mockStoreDeviceIFV {}
85+
86+
@end

0 commit comments

Comments
 (0)