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 HostPasswordStore keychain
66 = PasswordStoreFactory
.get();
68 private Preferences preferences
69 = PreferencesFactory
.get();
71 private WindowController parent
;
73 public PromptLoginController(final WindowController parent
) {
78 public void warn(final Protocol protocol
, final String title
, final String message
,
79 final String continueButton
, final String disconnectButton
, final String preference
)
80 throws LoginCanceledException
{
81 if(log
.isDebugEnabled()) {
82 log
.debug(String
.format("Display insecure connection alert for %s", protocol
));
84 final NSAlert alert
= NSAlert
.alert(title
, message
,
85 continueButton
, // Default Button
86 null, // Alternate button
87 disconnectButton
// Other
89 alert
.setShowsHelp(true);
90 alert
.setShowsSuppressionButton(true);
91 alert
.suppressionButton().setTitle(LocaleFactory
.localizedString("Don't show again", "Credentials"));
92 alert
.setAlertStyle(NSAlert
.NSWarningAlertStyle
);
93 final StringBuilder site
= new StringBuilder(preferences
.getProperty("website.help"));
94 site
.append("/").append(protocol
.getScheme().name());
95 int option
= parent
.alert(alert
, site
.toString());
96 if(alert
.suppressionButton().state() == NSCell
.NSOnState
) {
98 preferences
.setProperty(preference
, true);
101 case SheetCallback
.CANCEL_OPTION
:
102 throw new LoginCanceledException();
104 //Proceed nevertheless.
108 public void prompt(final Host bookmark
, final Credentials credentials
,
109 final String title
, final String reason
,
110 final LoginOptions options
) throws LoginCanceledException
{
111 if(log
.isDebugEnabled()) {
112 log
.debug(String
.format("Prompt for credentials for %s", bookmark
));
114 final SheetController sheet
= new SheetController(parent
) {
116 protected String
getBundleName() {
121 public void awakeFromNib() {
123 this.window().makeFirstResponder(usernameField
);
124 super.awakeFromNib();
128 public void helpButtonClicked(NSButton sender
) {
129 new DefaultProviderHelpService().help(bookmark
.getProtocol());
133 protected NSImageView iconView
;
135 public void setIconView(NSImageView iconView
) {
136 this.iconView
= iconView
;
137 this.iconView
.setImage(IconCacheFactory
.<NSImage
>get().iconNamed(bookmark
.getProtocol().disk()));
141 private NSTextField usernameLabel
;
143 public void setUsernameLabel(NSTextField usernameLabel
) {
144 this.usernameLabel
= usernameLabel
;
145 this.usernameLabel
.setAttributedStringValue(NSAttributedString
.attributedStringWithAttributes(
146 StringUtils
.isNotBlank(credentials
.getUsernamePlaceholder()) ? String
.format("%s:",
147 credentials
.getUsernamePlaceholder()) : StringUtils
.EMPTY
,
148 TRUNCATE_MIDDLE_ATTRIBUTES
));
152 private NSTextField passwordLabel
;
154 public void setPasswordLabel(NSTextField passwordLabel
) {
155 this.passwordLabel
= passwordLabel
;
159 private NSTextField titleField
;
161 public void setTitleField(NSTextField titleField
) {
162 this.titleField
= titleField
;
163 this.updateField(this.titleField
, LocaleFactory
.localizedString(title
, "Credentials"));
167 private NSTextField usernameField
;
169 public void setUsernameField(NSTextField usernameField
) {
170 this.usernameField
= usernameField
;
171 this.updateField(this.usernameField
, credentials
.getUsername());
172 NSNotificationCenter
.defaultCenter().addObserver(this.id(),
173 Foundation
.selector("userFieldTextDidChange:"),
174 NSControl
.NSControlTextDidChangeNotification
,
178 public void userFieldTextDidChange(NSNotification notification
) {
179 credentials
.setUsername(usernameField
.stringValue());
180 if(StringUtils
.isNotBlank(credentials
.getUsername())) {
181 final String password
= keychain
.getPassword(bookmark
.getProtocol().getScheme(), bookmark
.getPort(),
182 bookmark
.getHostname(), credentials
.getUsername());
183 if(StringUtils
.isNotBlank(password
)) {
184 passwordField
.setStringValue(password
);
185 this.passFieldTextDidChange(notification
);
192 private NSTextField textField
;
194 public void setTextField(NSTextField textField
) {
195 this.textField
= textField
;
196 this.textField
.setSelectable(true);
197 if(reason
.startsWith(Scheme
.http
.name())) {
199 this.textField
.setAttributedStringValue(HyperlinkAttributedStringFactory
.create(reason
));
200 this.textField
.setAllowsEditingTextAttributes(true);
201 this.textField
.setSelectable(true);
204 this.updateField(this.textField
, new StringAppender().append(reason
).toString());
209 private NSSecureTextField passwordField
;
211 public void setPasswordField(NSSecureTextField passwordField
) {
212 this.passwordField
= passwordField
;
213 this.updateField(this.passwordField
, credentials
.getPassword());
214 NSNotificationCenter
.defaultCenter().addObserver(this.id(),
215 Foundation
.selector("passFieldTextDidChange:"),
216 NSControl
.NSControlTextDidChangeNotification
,
220 public void passFieldTextDidChange(NSNotification notification
) {
221 credentials
.setPassword(passwordField
.stringValue());
225 private NSButton keychainCheckbox
;
227 public void setKeychainCheckbox(NSButton keychainCheckbox
) {
228 this.keychainCheckbox
= keychainCheckbox
;
229 this.keychainCheckbox
.setTarget(this.id());
230 this.keychainCheckbox
.setAction(Foundation
.selector("keychainCheckboxClicked:"));
231 this.keychainCheckbox
.setState(preferences
.getBoolean("connection.login.useKeychain")
232 && preferences
.getBoolean("connection.login.addKeychain") ? NSCell
.NSOnState
: NSCell
.NSOffState
);
235 public void keychainCheckboxClicked(final NSButton sender
) {
236 final boolean enabled
= sender
.state() == NSCell
.NSOnState
;
237 preferences
.setProperty("connection.login.addKeychain", enabled
);
241 private NSButton anonymousCheckbox
;
243 public void setAnonymousCheckbox(NSButton anonymousCheckbox
) {
244 this.anonymousCheckbox
= anonymousCheckbox
;
245 this.anonymousCheckbox
.setTarget(this.id());
246 this.anonymousCheckbox
.setAction(Foundation
.selector("anonymousCheckboxClicked:"));
250 public void anonymousCheckboxClicked(final NSButton sender
) {
251 if(sender
.state() == NSCell
.NSOnState
) {
252 credentials
.setUsername(preferences
.getProperty("connection.login.anon.name"));
253 credentials
.setPassword(preferences
.getProperty("connection.login.anon.pass"));
255 if(sender
.state() == NSCell
.NSOffState
) {
256 credentials
.setUsername(preferences
.getProperty("connection.login.name"));
257 credentials
.setPassword(null);
259 this.updateField(this.usernameField
, credentials
.getUsername());
260 this.updateField(this.passwordField
, credentials
.getPassword());
265 private NSTextField pkLabel
;
267 public void setPkLabel(NSTextField pkLabel
) {
268 this.pkLabel
= pkLabel
;
272 private NSButton pkCheckbox
;
274 public void setPkCheckbox(NSButton pkCheckbox
) {
275 this.pkCheckbox
= pkCheckbox
;
276 this.pkCheckbox
.setTarget(this.id());
277 this.pkCheckbox
.setAction(Foundation
.selector("pkCheckboxSelectionChanged:"));
281 public void pkCheckboxSelectionChanged(final NSButton sender
) {
282 if(sender
.state() == NSCell
.NSOnState
) {
284 select(this, new SheetCallback() {
286 public void callback(final int returncode
) {
287 if(returncode
== SheetCallback
.DEFAULT_OPTION
) {
288 final NSObject selected
= select
.filenames().lastObject();
289 if(selected
!= null) {
290 credentials
.setIdentity(LocalFactory
.get(selected
.toString()));
297 catch(LoginCanceledException e
) {
302 credentials
.setIdentity(null);
307 private void update() {
308 this.usernameField
.setEnabled(options
.user
&& !credentials
.isAnonymousLogin());
309 this.usernameField
.cell().setPlaceholderString(credentials
.getUsernamePlaceholder());
311 this.passwordField
.setEnabled(options
.password
&& !credentials
.isAnonymousLogin());
312 this.passwordField
.cell().setPlaceholderString(credentials
.getPasswordPlaceholder());
314 this.passwordLabel
.setAttributedStringValue(NSAttributedString
.attributedStringWithAttributes(
315 StringUtils
.isNotBlank(credentials
.getPasswordPlaceholder()) ? String
.format("%s:",
316 credentials
.getPasswordPlaceholder()) : StringUtils
.EMPTY
,
317 TRUNCATE_MIDDLE_ATTRIBUTES
));
319 boolean enable
= options
.keychain
&& !credentials
.isAnonymousLogin();
320 this.keychainCheckbox
.setEnabled(enable
);
322 this.keychainCheckbox
.setState(NSCell
.NSOffState
);
325 this.anonymousCheckbox
.setEnabled(options
.anonymous
);
326 if(options
.anonymous
&& credentials
.isAnonymousLogin()) {
327 this.anonymousCheckbox
.setState(NSCell
.NSOnState
);
330 this.anonymousCheckbox
.setState(NSCell
.NSOffState
);
332 this.pkCheckbox
.setEnabled(options
.publickey
);
333 if(options
.publickey
&& credentials
.isPublicKeyAuthentication()) {
334 this.pkCheckbox
.setState(NSCell
.NSOnState
);
335 this.updateField(this.pkLabel
, credentials
.getIdentity().getAbbreviatedPath(),
336 TRUNCATE_MIDDLE_ATTRIBUTES
);
337 this.pkLabel
.setTextColor(NSColor
.textColor());
340 this.pkCheckbox
.setState(NSCell
.NSOffState
);
341 this.pkLabel
.setStringValue(LocaleFactory
.localizedString("No private key selected"));
342 this.pkLabel
.setTextColor(NSColor
.disabledControlTextColor());
347 protected boolean validateInput() {
348 credentials
.setUsername(usernameField
.stringValue());
349 credentials
.setPassword(passwordField
.stringValue());
350 return credentials
.validate(bookmark
.getProtocol(), options
);
354 public void callback(final int returncode
) {
355 if(returncode
== SheetCallback
.DEFAULT_OPTION
) {
356 this.window().endEditingFor(null);
357 credentials
.setSaved(keychainCheckbox
.state() == NSCell
.NSOnState
);
358 credentials
.setUsername(usernameField
.stringValue());
359 credentials
.setPassword(passwordField
.stringValue());
364 if(sheet
.returnCode() == SheetCallback
.CANCEL_OPTION
) {
365 throw new LoginCanceledException();
369 private NSOpenPanel select
;
371 public Local
select(final Local identity
) throws LoginCanceledException
{
372 return this.select(parent
, new SheetCallback() {
374 public void callback(final int returncode
) {
380 protected Local
select(final WindowController parent
, final SheetCallback callback
) throws LoginCanceledException
{
381 final SheetController sheet
= new SheetController(parent
) {
383 public void callback(final int returncode
) {
384 callback
.callback(returncode
);
388 protected void beginSheetImpl() {
389 select
= NSOpenPanel
.openPanel();
390 select
.setCanChooseDirectories(false);
391 select
.setCanChooseFiles(true);
392 select
.setAllowsMultipleSelection(false);
393 select
.setMessage(LocaleFactory
.localizedString("Select the private key in PEM or PuTTY format", "Credentials"));
394 select
.setPrompt(LocaleFactory
.localizedString("Choose"));
395 select
.beginSheetForDirectory(LocalFactory
.get("~/.ssh").getAbsolute(),
396 null, parent
.window(), this.id(), Foundation
.selector("sheetDidClose:returnCode:contextInfo:"), null);
400 public NSWindow
window() {
405 if(sheet
.returnCode() == SheetCallback
.DEFAULT_OPTION
) {
406 final NSObject selected
= select
.filenames().lastObject();
407 if(selected
!= null) {
408 return LocalFactory
.get(selected
.toString());
411 throw new LoginCanceledException();