Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / chrome / browser / ui / cocoa / ssl_client_certificate_selector_cocoa.mm
blob6e2703d34d4092a360d92cb0cae1b6cbc5c1ded9
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #import "chrome/browser/ui/cocoa/ssl_client_certificate_selector_cocoa.h"
7 #import <SecurityInterface/SFChooseIdentityPanel.h>
9 #include "base/logging.h"
10 #include "base/mac/foundation_util.h"
11 #include "base/strings/string_util.h"
12 #include "base/strings/sys_string_conversions.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "chrome/browser/ssl/ssl_client_auth_observer.h"
15 #import "chrome/browser/ui/cocoa/constrained_window/constrained_window_mac.h"
16 #include "chrome/grit/generated_resources.h"
17 #include "components/guest_view/browser/guest_view_base.h"
18 #include "components/web_modal/web_contents_modal_dialog_manager.h"
19 #include "content/public/browser/browser_thread.h"
20 #include "content/public/browser/client_certificate_delegate.h"
21 #include "content/public/browser/web_contents.h"
22 #include "net/cert/x509_certificate.h"
23 #include "net/cert/x509_util_mac.h"
24 #include "net/ssl/ssl_cert_request_info.h"
25 #include "ui/base/cocoa/window_size_constants.h"
26 #include "ui/base/l10n/l10n_util_mac.h"
28 using content::BrowserThread;
30 @interface SFChooseIdentityPanel (SystemPrivate)
31 // A system-private interface that dismisses a panel whose sheet was started by
32 // -beginSheetForWindow:modalDelegate:didEndSelector:contextInfo:identities:message:
33 // as though the user clicked the button identified by returnCode. Verified
34 // present in 10.5 through 10.8.
35 - (void)_dismissWithCode:(NSInteger)code;
36 @end
38 @interface SSLClientCertificateSelectorCocoa ()
39 - (void)onConstrainedWindowClosed;
40 @end
42 class SSLClientAuthObserverCocoaBridge : public SSLClientAuthObserver,
43                                          public ConstrainedWindowMacDelegate {
44  public:
45   SSLClientAuthObserverCocoaBridge(
46       const content::BrowserContext* browser_context,
47       net::SSLCertRequestInfo* cert_request_info,
48       scoped_ptr<content::ClientCertificateDelegate> delegate,
49       SSLClientCertificateSelectorCocoa* controller)
50       : SSLClientAuthObserver(browser_context,
51                               cert_request_info,
52                               delegate.Pass()),
53         controller_(controller) {}
55   // SSLClientAuthObserver implementation:
56   void OnCertSelectedByNotification() override {
57     [controller_ closeWebContentsModalDialog];
58   }
60   // ConstrainedWindowMacDelegate implementation:
61   void OnConstrainedWindowClosed(ConstrainedWindowMac* window) override {
62     // |onConstrainedWindowClosed| will delete the sheet which might be still
63     // in use higher up the call stack. Wait for the next cycle of the event
64     // loop to call this function.
65     [controller_ performSelector:@selector(onConstrainedWindowClosed)
66                       withObject:nil
67                       afterDelay:0];
68   }
70  private:
71   SSLClientCertificateSelectorCocoa* controller_;  // weak
74 namespace chrome {
76 void ShowSSLClientCertificateSelector(
77     content::WebContents* contents,
78     net::SSLCertRequestInfo* cert_request_info,
79     scoped_ptr<content::ClientCertificateDelegate> delegate) {
80   DCHECK_CURRENTLY_ON(BrowserThread::UI);
82   // Not all WebContentses can show modal dialogs.
83   //
84   // Use the top-level embedder if |contents| is a guest.
85   // GetTopLevelWebContents() will return |contents| otherwise.
86   // TODO(davidben): Move this hook to the WebContentsDelegate and only try to
87   // show a dialog in Browser's implementation. https://crbug.com/456255
88   if (web_modal::WebContentsModalDialogManager::FromWebContents(
89           guest_view::GuestViewBase::GetTopLevelWebContents(contents)) ==
90       nullptr)
91     return;
93   // The dialog manages its own lifetime.
94   SSLClientCertificateSelectorCocoa* selector =
95       [[SSLClientCertificateSelectorCocoa alloc]
96           initWithBrowserContext:contents->GetBrowserContext()
97                  certRequestInfo:cert_request_info
98                         delegate:delegate.Pass()];
99   [selector displayForWebContents:contents];
102 }  // namespace chrome
104 @implementation SSLClientCertificateSelectorCocoa
106 - (id)initWithBrowserContext:(const content::BrowserContext*)browserContext
107              certRequestInfo:(net::SSLCertRequestInfo*)certRequestInfo
108                     delegate:(scoped_ptr<content::ClientCertificateDelegate>)
109                                  delegate {
110   DCHECK(browserContext);
111   DCHECK(certRequestInfo);
112   if ((self = [super init])) {
113     observer_.reset(new SSLClientAuthObserverCocoaBridge(
114         browserContext, certRequestInfo, delegate.Pass(), self));
115   }
116   return self;
119 - (void)sheetDidEnd:(NSWindow*)parent
120          returnCode:(NSInteger)returnCode
121             context:(void*)context {
122   net::X509Certificate* cert = NULL;
123   if (returnCode == NSFileHandlingPanelOKButton) {
124     CFRange range = CFRangeMake(0, CFArrayGetCount(identities_));
125     CFIndex index =
126         CFArrayGetFirstIndexOfValue(identities_, range, [panel_ identity]);
127     if (index != -1)
128       cert = certificates_[index].get();
129     else
130       NOTREACHED();
131   }
133   if (!closePending_) {
134     // If |closePending_| is already set, |closeSheetWithAnimation:| was called
135     // already to cancel the selection rather than continue with no
136     // certificate. Otherwise, tell the backend which identity (or none) the
137     // user selected.
138     userResponded_ = YES;
139     observer_->CertificateSelected(cert);
141     constrainedWindow_->CloseWebContentsModalDialog();
142   }
145 - (void)displayForWebContents:(content::WebContents*)webContents {
146   // Create an array of CFIdentityRefs for the certificates:
147   size_t numCerts = observer_->cert_request_info()->client_certs.size();
148   identities_.reset(CFArrayCreateMutable(
149       kCFAllocatorDefault, numCerts, &kCFTypeArrayCallBacks));
150   for (size_t i = 0; i < numCerts; ++i) {
151     SecCertificateRef cert =
152         observer_->cert_request_info()->client_certs[i]->os_cert_handle();
153     SecIdentityRef identity;
154     if (SecIdentityCreateWithCertificate(NULL, cert, &identity) == noErr) {
155       CFArrayAppendValue(identities_, identity);
156       CFRelease(identity);
157       certificates_.push_back(observer_->cert_request_info()->client_certs[i]);
158     }
159   }
161   // Get the message to display:
162   NSString* message = l10n_util::GetNSStringF(
163       IDS_CLIENT_CERT_DIALOG_TEXT,
164       base::ASCIIToUTF16(
165           observer_->cert_request_info()->host_and_port.ToString()));
167   // Create and set up a system choose-identity panel.
168   panel_.reset([[SFChooseIdentityPanel alloc] init]);
169   [panel_ setInformativeText:message];
170   [panel_ setDefaultButtonTitle:l10n_util::GetNSString(IDS_OK)];
171   [panel_ setAlternateButtonTitle:l10n_util::GetNSString(IDS_CANCEL)];
172   SecPolicyRef sslPolicy;
173   if (net::x509_util::CreateSSLClientPolicy(&sslPolicy) == noErr) {
174     [panel_ setPolicies:(id)sslPolicy];
175     CFRelease(sslPolicy);
176   }
178   constrainedWindow_.reset(
179       new ConstrainedWindowMac(observer_.get(), webContents, self));
180   observer_->StartObserving();
183 - (void)closeWebContentsModalDialog {
184   DCHECK(constrainedWindow_);
185   constrainedWindow_->CloseWebContentsModalDialog();
188 - (NSWindow*)overlayWindow {
189   return overlayWindow_;
192 - (SFChooseIdentityPanel*)panel {
193   return panel_;
196 - (void)showSheetForWindow:(NSWindow*)window {
197   NSString* title = l10n_util::GetNSString(IDS_CLIENT_CERT_DIALOG_TITLE);
198   overlayWindow_.reset([window retain]);
199   [panel_ beginSheetForWindow:window
200                 modalDelegate:self
201                didEndSelector:@selector(sheetDidEnd:returnCode:context:)
202                   contextInfo:NULL
203                    identities:base::mac::CFToNSCast(identities_)
204                       message:title];
207 - (void)closeSheetWithAnimation:(BOOL)withAnimation {
208   if (!userResponded_) {
209     // If the sheet is closed by closing the tab rather than the user explicitly
210     // hitting Cancel, |closeSheetWithAnimation:| gets called before
211     // |sheetDidEnd:|. In this case, the selection should be canceled rather
212     // than continue with no certificate. The |returnCode| parameter to
213     // |sheetDidEnd:| is the same in both cases.
214     observer_->CancelCertificateSelection();
215   }
216   closePending_ = YES;
217   overlayWindow_.reset();
218   // Closing the sheet using -[NSApp endSheet:] doesn't work so use the private
219   // method.
220   [panel_ _dismissWithCode:NSFileHandlingPanelCancelButton];
223 - (void)hideSheet {
224   NSWindow* sheetWindow = [overlayWindow_ attachedSheet];
225   [sheetWindow setAlphaValue:0.0];
227   oldResizesSubviews_ = [[sheetWindow contentView] autoresizesSubviews];
228   [[sheetWindow contentView] setAutoresizesSubviews:NO];
230   oldSheetFrame_ = [sheetWindow frame];
231   NSRect overlayFrame = [overlayWindow_ frame];
232   oldSheetFrame_.origin.x -= NSMinX(overlayFrame);
233   oldSheetFrame_.origin.y -= NSMinY(overlayFrame);
234   [sheetWindow setFrame:ui::kWindowSizeDeterminedLater display:NO];
237 - (void)unhideSheet {
238   NSWindow* sheetWindow = [overlayWindow_ attachedSheet];
239   NSRect overlayFrame = [overlayWindow_ frame];
240   oldSheetFrame_.origin.x += NSMinX(overlayFrame);
241   oldSheetFrame_.origin.y += NSMinY(overlayFrame);
242   [sheetWindow setFrame:oldSheetFrame_ display:NO];
243   [[sheetWindow contentView] setAutoresizesSubviews:oldResizesSubviews_];
244   [[overlayWindow_ attachedSheet] setAlphaValue:1.0];
247 - (void)pulseSheet {
248   // NOOP
251 - (void)makeSheetKeyAndOrderFront {
252   [[overlayWindow_ attachedSheet] makeKeyAndOrderFront:nil];
255 - (void)updateSheetPosition {
256   // NOOP
259 - (NSWindow*)sheetWindow {
260   return panel_;
263 - (void)onConstrainedWindowClosed {
264   observer_->StopObserving();
265   panel_.reset();
266   constrainedWindow_.reset();
267   [self release];
270 @end