Merge pull request #64 in ITERATE/cyberduck from feature/windows/9074 to master
[cyberduck.git] / source / ch / cyberduck / ui / cocoa / PromptLoginController.java
bloba622088a57739b78eec7e0288aedab38921b196d
1 package ch.cyberduck.ui.cocoa;
3 /*
4 * Copyright (c) 2005 David Kocher. All rights reserved.
5 * http://cyberduck.ch/
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;
59 /**
60 * @version $Id$
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) {
77 this.parent = parent;
80 @Override
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) {
100 // Never show again.
101 preferences.setProperty(preference, true);
103 switch(option) {
104 case SheetCallback.CANCEL_OPTION:
105 throw new LoginCanceledException();
107 //Proceed nevertheless.
110 @Override
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) {
118 @Override
119 protected String getBundleName() {
120 return "Login";
123 @Override
124 public void awakeFromNib() {
125 this.update();
126 window.makeFirstResponder(usernameField);
127 super.awakeFromNib();
130 @Override
131 public void helpButtonClicked(NSButton sender) {
132 new DefaultProviderHelpService().help(bookmark.getProtocol());
135 @Outlet
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()));
143 @Outlet
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));
154 @Outlet
155 private NSTextField passwordLabel;
157 public void setPasswordLabel(NSTextField passwordLabel) {
158 this.passwordLabel = passwordLabel;
161 @Outlet
162 private NSTextField titleField;
164 public void setTitleField(NSTextField titleField) {
165 this.titleField = titleField;
166 this.updateField(this.titleField, LocaleFactory.localizedString(title, "Credentials"));
169 @Outlet
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,
178 this.usernameField);
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);
191 this.update();
194 @Outlet
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())) {
201 // For OAuth2
202 this.textField.setAttributedStringValue(HyperlinkAttributedStringFactory.create(reason));
203 this.textField.setAllowsEditingTextAttributes(true);
204 this.textField.setSelectable(true);
206 else {
207 this.updateField(this.textField, new StringAppender().append(reason).toString());
211 @Outlet
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,
220 this.passwordField);
223 public void passFieldTextDidChange(NSNotification notification) {
224 credentials.setPassword(passwordField.stringValue());
227 @Outlet
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);
243 @Outlet
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:"));
252 @Action
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());
264 this.update();
267 @Outlet
268 private NSTextField pkLabel;
270 public void setPkLabel(NSTextField pkLabel) {
271 this.pkLabel = pkLabel;
274 @Outlet
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:"));
283 @Action
284 public void pkCheckboxSelectionChanged(final NSButton sender) {
285 if(sender.state() == NSCell.NSOnState) {
286 select(this, new SheetCallback() {
287 @Override
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()));
293 update();
299 else {
300 credentials.setIdentity(null);
302 update();
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);
319 if(!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);
327 else {
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());
337 else {
338 this.pkCheckbox.setState(NSCell.NSOffState);
339 this.pkLabel.setStringValue(LocaleFactory.localizedString("No private key selected"));
340 this.pkLabel.setTextColor(NSColor.disabledControlTextColor());
344 @Override
345 protected boolean validateInput() {
346 credentials.setUsername(usernameField.stringValue());
347 credentials.setPassword(passwordField.stringValue());
348 return credentials.validate(bookmark.getProtocol(), options);
351 @Override
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());
361 sheet.beginSheet();
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() {
371 @Override
372 public void callback(final int returncode) {
376 if(null == selected) {
377 throw new LoginCanceledException();
379 return selected;
382 protected Local select(final WindowController parent, final SheetCallback callback) {
383 final SheetController sheet = new SheetController(parent) {
384 @Override
385 public void callback(final int returncode) {
386 callback.callback(returncode);
389 @Override
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);
401 @Override
402 public NSWindow window() {
403 return select;
406 @Override
407 public void invalidate() {
408 notificationCenter.removeObserver(this.id());
409 super.invalidate();
412 sheet.beginSheet();
413 if(sheet.returnCode() == SheetCallback.DEFAULT_OPTION) {
414 final NSObject selected = select.filenames().lastObject();
415 if(selected != null) {
416 return LocalFactory.get(selected.toString());
419 return null;