1 package ch
.cyberduck
.ui
.cocoa
;
4 * Copyright (c) 2005 David Kocher. All rights reserved.
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * Bug fixes, suggestions and comments should be sent to:
18 * dkocher@cyberduck.ch
21 import ch
.cyberduck
.binding
.application
.NSAlert
;
22 import ch
.cyberduck
.binding
.application
.NSButton
;
23 import ch
.cyberduck
.binding
.application
.NSCell
;
24 import ch
.cyberduck
.binding
.application
.NSColor
;
25 import ch
.cyberduck
.binding
.application
.NSControl
;
26 import ch
.cyberduck
.binding
.application
.NSImage
;
27 import ch
.cyberduck
.binding
.application
.NSImageView
;
28 import ch
.cyberduck
.binding
.application
.NSOpenPanel
;
29 import ch
.cyberduck
.binding
.application
.NSSecureTextField
;
30 import ch
.cyberduck
.binding
.application
.NSTextField
;
31 import ch
.cyberduck
.binding
.application
.NSWindow
;
32 import ch
.cyberduck
.binding
.application
.SheetCallback
;
33 import ch
.cyberduck
.binding
.foundation
.NSAttributedString
;
34 import ch
.cyberduck
.binding
.foundation
.NSNotification
;
35 import ch
.cyberduck
.binding
.foundation
.NSNotificationCenter
;
36 import ch
.cyberduck
.binding
.foundation
.NSObject
;
37 import ch
.cyberduck
.core
.Credentials
;
38 import ch
.cyberduck
.core
.DefaultProviderHelpService
;
39 import ch
.cyberduck
.core
.Host
;
40 import ch
.cyberduck
.core
.HostPasswordStore
;
41 import ch
.cyberduck
.core
.Local
;
42 import ch
.cyberduck
.core
.LocalFactory
;
43 import ch
.cyberduck
.core
.LocaleFactory
;
44 import ch
.cyberduck
.core
.LoginCallback
;
45 import ch
.cyberduck
.core
.LoginOptions
;
46 import ch
.cyberduck
.core
.PasswordStoreFactory
;
47 import ch
.cyberduck
.core
.Protocol
;
48 import ch
.cyberduck
.core
.Scheme
;
49 import ch
.cyberduck
.core
.StringAppender
;
50 import ch
.cyberduck
.core
.exception
.LoginCanceledException
;
51 import ch
.cyberduck
.core
.preferences
.Preferences
;
52 import ch
.cyberduck
.core
.preferences
.PreferencesFactory
;
53 import ch
.cyberduck
.core
.resources
.IconCacheFactory
;
55 import org
.apache
.commons
.lang3
.StringUtils
;
56 import org
.apache
.log4j
.Logger
;
57 import org
.rococoa
.Foundation
;
62 public final class PromptLoginController
implements LoginCallback
{
63 private static final Logger log
= Logger
.getLogger(PromptLoginController
.class);
65 private final NSNotificationCenter notificationCenter
66 = NSNotificationCenter
.defaultCenter();
68 private HostPasswordStore keychain
69 = PasswordStoreFactory
.get();
71 private Preferences preferences
72 = PreferencesFactory
.get();
74 private WindowController parent
;
76 public PromptLoginController(final WindowController parent
) {
81 public void warn(final Protocol protocol
, final String title
, final String message
,
82 final String continueButton
, final String disconnectButton
, final String preference
)
83 throws LoginCanceledException
{
84 if(log
.isDebugEnabled()) {
85 log
.debug(String
.format("Display insecure connection alert for %s", protocol
));
87 final NSAlert alert
= NSAlert
.alert(title
, message
,
88 continueButton
, // Default Button
89 null, // Alternate button
90 disconnectButton
// Other
92 alert
.setShowsHelp(true);
93 alert
.setShowsSuppressionButton(true);
94 alert
.suppressionButton().setTitle(LocaleFactory
.localizedString("Don't show again", "Credentials"));
95 alert
.setAlertStyle(NSAlert
.NSWarningAlertStyle
);
96 final StringBuilder site
= new StringBuilder(preferences
.getProperty("website.help"));
97 site
.append("/").append(protocol
.getScheme().name());
98 int option
= parent
.alert(alert
, site
.toString());
99 if(alert
.suppressionButton().state() == NSCell
.NSOnState
) {
101 preferences
.setProperty(preference
, true);
104 case SheetCallback
.CANCEL_OPTION
:
105 throw new LoginCanceledException();
107 //Proceed nevertheless.
111 public void prompt(final Host bookmark
, final Credentials credentials
,
112 final String title
, final String reason
,
113 final LoginOptions options
) throws LoginCanceledException
{
114 if(log
.isDebugEnabled()) {
115 log
.debug(String
.format("Prompt for credentials for %s", bookmark
));
117 final SheetController sheet
= new SheetController(parent
) {
119 protected String
getBundleName() {
124 public void awakeFromNib() {
126 window
.makeFirstResponder(usernameField
);
127 super.awakeFromNib();
131 public void helpButtonClicked(NSButton sender
) {
132 new DefaultProviderHelpService().help(bookmark
.getProtocol());
136 protected NSImageView iconView
;
138 public void setIconView(NSImageView iconView
) {
139 this.iconView
= iconView
;
140 this.iconView
.setImage(IconCacheFactory
.<NSImage
>get().iconNamed(bookmark
.getProtocol().disk()));
144 private NSTextField usernameLabel
;
146 public void setUsernameLabel(NSTextField usernameLabel
) {
147 this.usernameLabel
= usernameLabel
;
148 this.usernameLabel
.setAttributedStringValue(NSAttributedString
.attributedStringWithAttributes(
149 StringUtils
.isNotBlank(credentials
.getUsernamePlaceholder()) ? String
.format("%s:",
150 credentials
.getUsernamePlaceholder()) : StringUtils
.EMPTY
,
151 TRUNCATE_MIDDLE_ATTRIBUTES
));
155 private NSTextField passwordLabel
;
157 public void setPasswordLabel(NSTextField passwordLabel
) {
158 this.passwordLabel
= passwordLabel
;
162 private NSTextField titleField
;
164 public void setTitleField(NSTextField titleField
) {
165 this.titleField
= titleField
;
166 this.updateField(this.titleField
, LocaleFactory
.localizedString(title
, "Credentials"));
170 private NSTextField usernameField
;
172 public void setUsernameField(NSTextField usernameField
) {
173 this.usernameField
= usernameField
;
174 this.updateField(this.usernameField
, credentials
.getUsername());
175 notificationCenter
.addObserver(this.id(),
176 Foundation
.selector("userFieldTextDidChange:"),
177 NSControl
.NSControlTextDidChangeNotification
,
181 public void userFieldTextDidChange(NSNotification notification
) {
182 credentials
.setUsername(usernameField
.stringValue());
183 if(StringUtils
.isNotBlank(credentials
.getUsername())) {
184 final String password
= keychain
.getPassword(bookmark
.getProtocol().getScheme(), bookmark
.getPort(),
185 bookmark
.getHostname(), credentials
.getUsername());
186 if(StringUtils
.isNotBlank(password
)) {
187 passwordField
.setStringValue(password
);
188 this.passFieldTextDidChange(notification
);
195 private NSTextField textField
;
197 public void setTextField(NSTextField textField
) {
198 this.textField
= textField
;
199 this.textField
.setSelectable(true);
200 if(reason
.startsWith(Scheme
.http
.name())) {
202 this.textField
.setAttributedStringValue(HyperlinkAttributedStringFactory
.create(reason
));
203 this.textField
.setAllowsEditingTextAttributes(true);
204 this.textField
.setSelectable(true);
207 this.updateField(this.textField
, new StringAppender().append(reason
).toString());
212 private NSSecureTextField passwordField
;
214 public void setPasswordField(NSSecureTextField passwordField
) {
215 this.passwordField
= passwordField
;
216 this.updateField(this.passwordField
, credentials
.getPassword());
217 notificationCenter
.addObserver(this.id(),
218 Foundation
.selector("passFieldTextDidChange:"),
219 NSControl
.NSControlTextDidChangeNotification
,
223 public void passFieldTextDidChange(NSNotification notification
) {
224 credentials
.setPassword(passwordField
.stringValue());
228 private NSButton keychainCheckbox
;
230 public void setKeychainCheckbox(NSButton keychainCheckbox
) {
231 this.keychainCheckbox
= keychainCheckbox
;
232 this.keychainCheckbox
.setTarget(this.id());
233 this.keychainCheckbox
.setAction(Foundation
.selector("keychainCheckboxClicked:"));
234 this.keychainCheckbox
.setState(preferences
.getBoolean("connection.login.useKeychain")
235 && preferences
.getBoolean("connection.login.addKeychain") ? NSCell
.NSOnState
: NSCell
.NSOffState
);
238 public void keychainCheckboxClicked(final NSButton sender
) {
239 final boolean enabled
= sender
.state() == NSCell
.NSOnState
;
240 preferences
.setProperty("connection.login.addKeychain", enabled
);
244 private NSButton anonymousCheckbox
;
246 public void setAnonymousCheckbox(NSButton anonymousCheckbox
) {
247 this.anonymousCheckbox
= anonymousCheckbox
;
248 this.anonymousCheckbox
.setTarget(this.id());
249 this.anonymousCheckbox
.setAction(Foundation
.selector("anonymousCheckboxClicked:"));
253 public void anonymousCheckboxClicked(final NSButton sender
) {
254 if(sender
.state() == NSCell
.NSOnState
) {
255 credentials
.setUsername(preferences
.getProperty("connection.login.anon.name"));
256 credentials
.setPassword(preferences
.getProperty("connection.login.anon.pass"));
258 if(sender
.state() == NSCell
.NSOffState
) {
259 credentials
.setUsername(preferences
.getProperty("connection.login.name"));
260 credentials
.setPassword(null);
262 this.updateField(this.usernameField
, credentials
.getUsername());
263 this.updateField(this.passwordField
, credentials
.getPassword());
268 private NSTextField pkLabel
;
270 public void setPkLabel(NSTextField pkLabel
) {
271 this.pkLabel
= pkLabel
;
275 private NSButton pkCheckbox
;
277 public void setPkCheckbox(NSButton pkCheckbox
) {
278 this.pkCheckbox
= pkCheckbox
;
279 this.pkCheckbox
.setTarget(this.id());
280 this.pkCheckbox
.setAction(Foundation
.selector("pkCheckboxSelectionChanged:"));
284 public void pkCheckboxSelectionChanged(final NSButton sender
) {
285 if(sender
.state() == NSCell
.NSOnState
) {
286 select(this, new SheetCallback() {
288 public void callback(final int returncode
) {
289 if(returncode
== SheetCallback
.DEFAULT_OPTION
) {
290 final NSObject selected
= select
.filenames().lastObject();
291 if(selected
!= null) {
292 credentials
.setIdentity(LocalFactory
.get(selected
.toString()));
300 credentials
.setIdentity(null);
305 private void update() {
306 this.usernameField
.setEnabled(options
.user
&& !credentials
.isAnonymousLogin());
307 this.usernameField
.cell().setPlaceholderString(credentials
.getUsernamePlaceholder());
309 this.passwordField
.setEnabled(options
.password
&& !credentials
.isAnonymousLogin());
310 this.passwordField
.cell().setPlaceholderString(credentials
.getPasswordPlaceholder());
312 this.passwordLabel
.setAttributedStringValue(NSAttributedString
.attributedStringWithAttributes(
313 StringUtils
.isNotBlank(credentials
.getPasswordPlaceholder()) ? String
.format("%s:",
314 credentials
.getPasswordPlaceholder()) : StringUtils
.EMPTY
,
315 TRUNCATE_MIDDLE_ATTRIBUTES
));
317 boolean enable
= options
.keychain
&& !credentials
.isAnonymousLogin();
318 this.keychainCheckbox
.setEnabled(enable
);
320 this.keychainCheckbox
.setState(NSCell
.NSOffState
);
323 this.anonymousCheckbox
.setEnabled(options
.anonymous
);
324 if(options
.anonymous
&& credentials
.isAnonymousLogin()) {
325 this.anonymousCheckbox
.setState(NSCell
.NSOnState
);
328 this.anonymousCheckbox
.setState(NSCell
.NSOffState
);
330 this.pkCheckbox
.setEnabled(options
.publickey
);
331 if(options
.publickey
&& credentials
.isPublicKeyAuthentication()) {
332 this.pkCheckbox
.setState(NSCell
.NSOnState
);
333 this.updateField(this.pkLabel
, credentials
.getIdentity().getAbbreviatedPath(),
334 TRUNCATE_MIDDLE_ATTRIBUTES
);
335 this.pkLabel
.setTextColor(NSColor
.textColor());
338 this.pkCheckbox
.setState(NSCell
.NSOffState
);
339 this.pkLabel
.setStringValue(LocaleFactory
.localizedString("No private key selected"));
340 this.pkLabel
.setTextColor(NSColor
.disabledControlTextColor());
345 protected boolean validateInput() {
346 credentials
.setUsername(usernameField
.stringValue());
347 credentials
.setPassword(passwordField
.stringValue());
348 return credentials
.validate(bookmark
.getProtocol(), options
);
352 public void callback(final int returncode
) {
353 if(returncode
== SheetCallback
.DEFAULT_OPTION
) {
354 this.window().endEditingFor(null);
355 credentials
.setSaved(keychainCheckbox
.state() == NSCell
.NSOnState
);
356 credentials
.setUsername(usernameField
.stringValue());
357 credentials
.setPassword(passwordField
.stringValue());
362 if(sheet
.returnCode() == SheetCallback
.CANCEL_OPTION
) {
363 throw new LoginCanceledException();
367 private NSOpenPanel select
;
369 public Local
select(final Local identity
) throws LoginCanceledException
{
370 final Local selected
= this.select(parent
, new SheetCallback() {
372 public void callback(final int returncode
) {
376 if(null == selected
) {
377 throw new LoginCanceledException();
382 protected Local
select(final WindowController parent
, final SheetCallback callback
) {
383 final SheetController sheet
= new SheetController(parent
) {
385 public void callback(final int returncode
) {
386 callback
.callback(returncode
);
390 public void beginSheet(final NSWindow window
) {
391 select
= NSOpenPanel
.openPanel();
392 select
.setCanChooseDirectories(false);
393 select
.setCanChooseFiles(true);
394 select
.setAllowsMultipleSelection(false);
395 select
.setMessage(LocaleFactory
.localizedString("Select the private key in PEM or PuTTY format", "Credentials"));
396 select
.setPrompt(LocaleFactory
.localizedString("Choose"));
397 select
.beginSheetForDirectory(LocalFactory
.get("~/.ssh").getAbsolute(),
398 null, parent
.window(), this.id(), Foundation
.selector("sheetDidClose:returnCode:contextInfo:"), null);
402 public NSWindow
window() {
407 public void invalidate() {
408 notificationCenter
.removeObserver(this.id());
413 if(sheet
.returnCode() == SheetCallback
.DEFAULT_OPTION
) {
414 final NSObject selected
= select
.filenames().lastObject();
415 if(selected
!= null) {
416 return LocalFactory
.get(selected
.toString());