/ SharedWebCredentialViewService / SWCViewController.m
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 systemImageNamed:@"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