]> git.saurik.com Git - apple/security.git/blob - SharedWebCredentialViewService/SWCViewController.m
Security-59306.140.5.tar.gz
[apple/security.git] / SharedWebCredentialViewService / SWCViewController.m
1 //
2 // SWCViewController.m
3 // SharedWebCredentialViewService
4 //
5 // Copyright (c) 2014 Apple Inc. All Rights Reserved.
6 //
7
8 #import <Foundation/NSXPCConnection.h>
9 #import "SWCViewController.h"
10 #import <UIKit/UIViewController_Private.h>
11 #import <UIKit/UIFont_Private.h>
12 #import <UIKit/UIImage_SymbolImagePrivate.h>
13 #import <UIKit/UIAlertController_Private.h>
14 #import <UIKit/UITableViewCell_Private.h>
15
16 #include <bsm/libbsm.h>
17 #include <ipc/securityd_client.h>
18
19 #include "SharedWebCredential/swcagent_client.h"
20
21 #import "SWCViewController.h"
22
23 const NSString* SWC_PASSWORD_KEY = @"spwd";
24 const NSString* SWC_ACCOUNT_KEY = @"acct";
25 const NSString* SWC_SERVER_KEY = @"srvr";
26
27 //
28 // SWCDictionaryAdditions
29 //
30
31 @interface NSDictionary (SWCDictionaryAdditions)
32 - (NSComparisonResult) compareCredentialDictionaryAscending:(NSDictionary *)other;
33 @end
34
35 @implementation NSDictionary (SWCDictionaryAdditions)
36 - (NSComparisonResult)compareCredentialDictionaryAscending:(NSDictionary *)other
37 {
38 NSComparisonResult result;
39 NSString *str1 = [self objectForKey:SWC_ACCOUNT_KEY], *str2 = [other objectForKey:SWC_ACCOUNT_KEY];
40 if (!str1) str1 = @"";
41 if (!str2) str2 = @"";
42
43 // primary sort by account name
44 result = [str1 localizedCaseInsensitiveCompare:str2];
45 if (result == NSOrderedSame) {
46 // secondary sort by domain name
47 NSString *str3 = [self objectForKey:SWC_SERVER_KEY], *str4 = [other objectForKey:SWC_SERVER_KEY];
48 if (!str3) str3 = @"";
49 if (!str4) str4 = @"";
50
51 result = [str3 localizedCaseInsensitiveCompare:str4];
52 }
53
54 return result;
55 }
56 @end
57
58
59 //
60 // SWCItemCell
61 //
62 @interface SWCItemCell : UITableViewCell
63 {
64 NSDictionary *_dict;
65 BOOL _isTicked;
66 UIView *_bottomLine;
67 UIView *_bottomLineSelected;
68 UIView *_topLine;
69 UIView *_topLineSelected;
70 BOOL _showSeparator;
71 BOOL _showTopSeparator;
72 }
73
74 - (id)initWithDictionary:(NSDictionary *)dict;
75 @property (nonatomic, readonly) id userInfo;
76 @property (nonatomic, assign) BOOL showSeparator;
77 @end
78
79 @implementation SWCItemCell
80
81 @synthesize showSeparator = _showSeparator;
82
83 - (id)initWithDictionary:(NSDictionary *)dict
84 {
85 if ((self = [super initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:nil]))
86 {
87 _dict = dict;
88
89 self.selectionStyle = UITableViewCellSelectionStyleNone;
90
91 self.backgroundColor = [UIColor systemBackgroundColor];
92
93 self.textLabel.textColor = [UIColor labelColor];
94 self.textLabel.textAlignment = NSTextAlignmentLeft;
95 self.textLabel.adjustsFontSizeToFitWidth = YES;
96 self.textLabel.baselineAdjustment = UIBaselineAdjustmentAlignCenters;
97
98 NSString *title = [dict objectForKey:SWC_ACCOUNT_KEY];
99 self.textLabel.text = title ? title : NSLocalizedString(@"--", nil);
100
101 self.detailTextLabel.textColor = [UIColor secondaryLabelColor];
102 self.detailTextLabel.textAlignment = NSTextAlignmentLeft;
103 self.detailTextLabel.adjustsFontSizeToFitWidth = YES;
104 self.detailTextLabel.baselineAdjustment = UIBaselineAdjustmentAlignCenters;
105
106 NSString *subtitle = [dict objectForKey:SWC_SERVER_KEY];
107 self.detailTextLabel.text = subtitle ? subtitle : NSLocalizedString(@"--", nil);
108
109 self.backgroundView = [[UIView alloc] init];
110 self.backgroundView.backgroundColor = [UIColor systemBackgroundColor];
111
112 self.imageView.image = [UIImage symbolImageNamed:@"checkmark"];
113 self.imageView.hidden = YES;
114 }
115
116 return self;
117 }
118
119 - (void)setTicked: (BOOL) selected
120 {
121 _isTicked = selected;
122 }
123
124 - (void)layoutSubviews
125 {
126
127 if (_bottomLine) {
128 CGFloat scale = [[UIScreen mainScreen] scale];
129 [_bottomLine setFrame:CGRectMake(0, self.frame.size.height - (1 / scale), self.frame.size.width, 1 / scale)];
130 }
131
132 if (_bottomLineSelected) {
133 CGFloat scale = [[UIScreen mainScreen] scale];
134 [_bottomLineSelected setFrame:CGRectMake(0, self.frame.size.height - (1 / scale), self.frame.size.width, 1 / scale)];
135 }
136
137 if (_topLine) {
138 CGFloat scale = [[UIScreen mainScreen] scale];
139 [_topLine setFrame:CGRectMake(0, 0, self.frame.size.width, 1 / scale)];
140 }
141
142 if (_topLineSelected) {
143 CGFloat scale = [[UIScreen mainScreen] scale];
144 [_topLineSelected setFrame:CGRectMake(0, 0, self.frame.size.width, 1 / scale)];
145 }
146
147 if (_isTicked)
148 {
149 self.imageView.hidden = NO;
150 } else {
151 self.imageView.hidden = YES;
152 }
153
154 [super layoutSubviews];
155
156 }
157
158 - (void)setShowSeparator:(BOOL)showSeparator {
159 if (_showSeparator != showSeparator) {
160 _showSeparator = showSeparator;
161
162 if (_showSeparator) {
163 if (!_bottomLine) {
164 CGRect rectZero = CGRectMake(0, 0, 0, 0);
165 _bottomLine = [[UIView alloc] initWithFrame:rectZero];
166 _bottomLine.backgroundColor = [UIColor separatorColor];
167 [self.backgroundView addSubview:_bottomLine];
168 }
169 if (!_bottomLineSelected) {
170 CGRect rectZero = CGRectMake(0, 0, 0, 0);
171 _bottomLineSelected = [[UIView alloc] initWithFrame:rectZero];
172 _bottomLineSelected.backgroundColor = [UIColor separatorColor];
173 [self.selectedBackgroundView addSubview: _bottomLineSelected];
174 }
175
176 } else {
177 if (_bottomLine) {
178 [_bottomLine removeFromSuperview];
179 _bottomLine = nil;
180 }
181 if (_bottomLineSelected) {
182 [_bottomLineSelected removeFromSuperview];
183 _bottomLineSelected = nil;
184 }
185 }
186 }
187 }
188
189 - (void)setShowTopSeparator:(BOOL)showTopSeparator {
190 if (_showTopSeparator != showTopSeparator) {
191 _showTopSeparator = showTopSeparator;
192
193 if (_showTopSeparator) {
194 if (!_topLine) {
195 CGRect rectZero = CGRectMake(0, 0, 0, 0);
196 _topLine = [[UIView alloc] initWithFrame:rectZero];
197 _topLine.backgroundColor = [UIColor separatorColor];
198 [self.backgroundView addSubview:_topLine];
199 }
200 if (!_topLineSelected) {
201 CGRect rectZero = CGRectMake(0, 0, 0, 0);
202 _topLineSelected = [[UIView alloc] initWithFrame:rectZero];
203 _topLineSelected.backgroundColor = [UIColor separatorColor];
204 [self.selectedBackgroundView addSubview: _topLineSelected];
205 }
206
207 } else {
208 if (_topLine) {
209 [_topLine removeFromSuperview];
210 _topLine = nil;
211 }
212 if (_topLineSelected) {
213 [_topLineSelected removeFromSuperview];
214 _topLineSelected = nil;
215 }
216 }
217 }
218 }
219
220 @end
221
222
223 //
224 // SWCViewController
225 //
226
227 @interface SWCViewController ()
228 {
229 NSMutableArray *_credentials; // array of NSDictionary
230 UILabel *_topLabel;
231 UILabel *_middleLabel;
232 UITableView *_table;
233 NSDictionary *_selectedDict;
234 NSIndexPath *_selectedCell;
235 }
236
237 @end
238
239 @implementation SWCViewController
240
241 - (NSDictionary *)selectedItem
242 {
243 return _selectedDict;
244 }
245
246 - (void)setCredentials:(NSArray *)inArray
247 {
248 NSMutableArray *credentials = [[NSMutableArray alloc] initWithArray:inArray];
249 [credentials sortUsingSelector:@selector(compareCredentialDictionaryAscending:)];
250 _credentials = credentials;
251 if (_table)
252 [_table reloadData];
253 }
254
255 - (void)_enableTable
256 {
257 [_table setUserInteractionEnabled:YES];
258 }
259
260 - (UITableView *)tableView
261 {
262 if (_table == nil) {
263 _table = [[UITableView alloc] init];
264 [_table setTranslatesAutoresizingMaskIntoConstraints:NO];
265 [_table setAutoresizingMask:UIViewAutoresizingNone];
266 [_table setBackgroundColor:[UIColor systemBackgroundColor]];
267 [_table setSeparatorStyle:UITableViewCellSeparatorStyleNone];
268 }
269 [_table sizeToFit];
270
271 return (UITableView *)_table;
272 }
273
274 -(void)loadView
275 {
276
277 UIView* view = [[UIView alloc] init];
278
279 UITableView* table = [self tableView];
280 [table setDelegate: self];
281 [table setDataSource: self];
282
283 [view addSubview: table];
284
285 CFErrorRef error = NULL;
286 audit_token_t auditToken = {};
287 memset(&auditToken, 0, sizeof(auditToken));
288 CFArrayRef credentialList = swca_copy_pairs(swca_copy_pairs_request_id, &auditToken, &error);
289 if (error) {
290 NSLog(@"Unable to get accounts: %@", [(__bridge NSError*)error localizedDescription]);
291 }
292
293 [self setCredentials:(__bridge NSArray*)credentialList];
294 if (credentialList) {
295 CFRelease(credentialList);
296 }
297
298 table.frame = CGRectMake(0.0, 0.0, 300.0, 45.0);
299 [table layoutIfNeeded]; // so autolayout can do its thing and then we can measure a cell
300 UITableViewCell* cell = table.visibleCells.firstObject;
301 CGFloat heightForOneRow = cell ? CGRectGetHeight(cell.frame) : 45.0;
302
303 NSDictionary* views = NSDictionaryOfVariableBindings(table);
304
305 if ([_credentials count] > 2)
306 {
307
308 CGFloat manyCredentialsHeight = CGFloatMax((heightForOneRow * 2) + 30.0, 120.0);
309 NSDictionary *metrics = @{@"height":@(manyCredentialsHeight)};
310 [view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[table]-|" options:0 metrics:metrics views:views]];
311 [view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[table(height)]-|" options:0 metrics:metrics views:views]];
312
313 CGFloat scale = [[UIScreen mainScreen] scale];
314 table.layer.borderWidth = 1.0 / scale;
315 table.layer.borderColor = [UIColor opaqueSeparatorColor].CGColor;
316
317 [self setPreferredContentSize:CGSizeMake(0, manyCredentialsHeight + 20.0)];
318
319 } else if ([_credentials count] == 2) {
320
321 NSDictionary *metrics = @{@"height":@(heightForOneRow * 2)};
322 [view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[table]|" options:0 metrics:metrics views:views]];
323 [view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[table(height)]-|" options:0 metrics:metrics views:views]];
324
325 [self setPreferredContentSize:CGSizeMake(0, heightForOneRow * 2)];
326 [table setScrollEnabled: NO];
327
328 } else { // [_credentials count] == 1
329
330 NSDictionary *metrics = @{@"height":@(heightForOneRow)};
331 [view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[table]|" options:0 metrics:metrics views:views]];
332 [view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[table(height)]|" options:0 metrics:metrics views:views]];
333
334 [self setPreferredContentSize:CGSizeMake(0, heightForOneRow)];
335 [table setScrollEnabled: NO];
336
337 }
338
339 [self setView:view];
340 }
341
342 -(void)viewWillAppear:(BOOL)animated
343 {
344
345 // Select the first cell by default
346
347 NSDictionary *dict = [_credentials objectAtIndex: 0];
348 _selectedDict = dict;
349 _selectedCell = [NSIndexPath indexPathForItem:0 inSection: 0];
350 SWCItemCell *cell = (SWCItemCell *)[_table cellForRowAtIndexPath: _selectedCell];
351 [cell setTicked: YES];
352 [cell layoutSubviews];
353 [_table selectRowAtIndexPath: _selectedCell animated: NO scrollPosition: UITableViewScrollPositionTop];
354
355 CFErrorRef error = NULL;
356 audit_token_t auditToken = {};
357 memset(&auditToken, 0, sizeof(auditToken));
358 bool result = swca_set_selection(swca_set_selection_request_id,
359 &auditToken, (__bridge CFDictionaryRef)dict, &error);
360 if (!result) {
361 NSLog(@"Unable to select item: %@", [(__bridge NSError*)error localizedDescription]);
362 }
363
364 [super viewWillAppear:animated];
365 }
366
367
368 //
369 // UITableView delegate methods
370 //
371
372 - (NSInteger)tableView:(UITableView *)table numberOfRowsInSection:(NSInteger)section
373 {
374 return [_credentials count];
375 }
376
377 - (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
378 {
379 NSUInteger row = indexPath.row;
380
381 NSDictionary *dict = [_credentials objectAtIndex:row];
382 _selectedDict = dict;
383
384 CFErrorRef error = NULL;
385 audit_token_t auditToken = {};
386 memset(&auditToken, 0, sizeof(auditToken));
387 bool result = swca_set_selection(swca_set_selection_request_id,
388 &auditToken, (__bridge CFDictionaryRef)dict, &error);
389 if (!result) {
390 NSLog(@"Unable to select item: %@", [(__bridge NSError*)error localizedDescription]);
391 }
392
393 _selectedCell = indexPath;
394 SWCItemCell *cell = (SWCItemCell *)[tableView cellForRowAtIndexPath: indexPath];
395 [cell setTicked: YES];
396 [cell layoutSubviews];
397 }
398
399 - (void) tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath
400 {
401 SWCItemCell *cell = (SWCItemCell *)[tableView cellForRowAtIndexPath: indexPath];
402 [cell setTicked: NO];
403 [cell layoutSubviews];
404
405 }
406
407 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
408 {
409
410 NSDictionary *dict = [_credentials objectAtIndex:[indexPath row]];
411 SWCItemCell *cell = [[SWCItemCell alloc] initWithDictionary:dict];
412
413 // show separator on top cell if there's only or or two items
414 if ([_credentials count] <= 2) {
415 cell.showTopSeparator = YES;
416 } else {
417 cell.showSeparator = YES;
418
419 if (indexPath.row == 0)
420 {
421 cell.showTopSeparator = YES;
422 }
423
424 }
425
426 if (_selectedCell == indexPath)
427 {
428 [cell setTicked: YES];
429 } else {
430 [cell setTicked: NO];
431 }
432
433 return cell;
434 }
435
436
437 @end