View Javadoc
1   /*
2    * Copyright (C) 2007-2012 Argeo GmbH
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *         http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.argeo.cms.ui.widgets.auth;
17  
18  import java.io.IOException;
19  import java.util.Arrays;
20  
21  import javax.security.auth.callback.Callback;
22  import javax.security.auth.callback.CallbackHandler;
23  import javax.security.auth.callback.NameCallback;
24  import javax.security.auth.callback.PasswordCallback;
25  
26  import org.apache.commons.logging.Log;
27  import org.apache.commons.logging.LogFactory;
28  import org.eclipse.core.runtime.IProgressMonitor;
29  import org.eclipse.core.runtime.NullProgressMonitor;
30  import org.eclipse.jface.dialogs.IDialogConstants;
31  import org.eclipse.jface.dialogs.TrayDialog;
32  import org.eclipse.jface.operation.IRunnableWithProgress;
33  import org.eclipse.jface.operation.ModalContext;
34  import org.eclipse.swt.events.SelectionEvent;
35  import org.eclipse.swt.events.SelectionListener;
36  import org.eclipse.swt.widgets.Button;
37  import org.eclipse.swt.widgets.Display;
38  import org.eclipse.swt.widgets.Shell;
39  import org.osgi.framework.FrameworkUtil;
40  
41  /** Base for login dialogs */
42  public abstract class AbstractLoginDialog extends TrayDialog implements CallbackHandler {
43  	private static final long serialVersionUID = -8046708963512717709L;
44  
45  	private final static Log log = LogFactory.getLog(AbstractLoginDialog.class);
46  
47  	private Thread modalContextThread = null;
48  	boolean processCallbacks = false;
49  	boolean isCancelled = false;
50  	Callback[] callbackArray;
51  
52  	protected final Callback[] getCallbacks() {
53  		return this.callbackArray;
54  	}
55  
56  	public abstract void internalHandle();
57  
58  	public boolean isCancelled() {
59  		return isCancelled;
60  	}
61  
62  	protected AbstractLoginDialog(Shell parentShell) {
63  		super(parentShell);
64  	}
65  
66  	/*
67  	 * (non-Javadoc)
68  	 * 
69  	 * @see
70  	 * javax.security.auth.callback.CallbackHandler#handle(javax.security.auth
71  	 * .callback.Callback[])
72  	 */
73  	public void handle(final Callback[] callbacks) throws IOException {
74  		// clean previous usage
75  		if (processCallbacks) {
76  			// this handler was already used
77  			processCallbacks = false;
78  		}
79  
80  		if (modalContextThread != null) {
81  			try {
82  				modalContextThread.join(1000);
83  			} catch (InterruptedException e) {
84  				// silent
85  			}
86  			modalContextThread = null;
87  		}
88  
89  		// initialize
90  		this.callbackArray = callbacks;
91  		final Display display = Display.getDefault();
92  		display.syncExec(new Runnable() {
93  
94  			public void run() {
95  				isCancelled = false;
96  				setBlockOnOpen(false);
97  				open();
98  
99  				final Button okButton = getButton(IDialogConstants.OK_ID);
100 				okButton.setText("Login");
101 				okButton.addSelectionListener(new SelectionListener() {
102 					private static final long serialVersionUID = -200281625679096775L;
103 
104 					public void widgetSelected(final SelectionEvent event) {
105 						processCallbacks = true;
106 					}
107 
108 					public void widgetDefaultSelected(final SelectionEvent event) {
109 						// nothing to do
110 					}
111 				});
112 				final Button cancel = getButton(IDialogConstants.CANCEL_ID);
113 				cancel.addSelectionListener(new SelectionListener() {
114 					private static final long serialVersionUID = -3826030278084915815L;
115 
116 					public void widgetSelected(final SelectionEvent event) {
117 						isCancelled = true;
118 						processCallbacks = true;
119 					}
120 
121 					public void widgetDefaultSelected(final SelectionEvent event) {
122 						// nothing to do
123 					}
124 				});
125 			}
126 		});
127 		try {
128 			ModalContext.setAllowReadAndDispatch(true); // Works for now.
129 			ModalContext.run(new IRunnableWithProgress() {
130 
131 				public void run(final IProgressMonitor monitor) {
132 					modalContextThread = Thread.currentThread();
133 					// Wait here until OK or cancel is pressed, then let it rip.
134 					// The event
135 					// listener
136 					// is responsible for closing the dialog (in the
137 					// loginSucceeded
138 					// event).
139 					while (!processCallbacks && (modalContextThread != null)
140 							&& (modalContextThread == Thread.currentThread())
141 							&& FrameworkUtil.getBundle(AbstractLoginDialog.class).getBundleContext() != null) {
142 						// Note: SecurityUiPlugin.getDefault() != null is false
143 						// when the OSGi runtime is shut down
144 						try {
145 							Thread.sleep(100);
146 							// if (display.isDisposed()) {
147 							// log.warn("Display is disposed, killing login
148 							// dialog thread");
149 							// throw new ThreadDeath();
150 							// }
151 						} catch (final Exception e) {
152 							// do nothing
153 						}
154 					}
155 					processCallbacks = false;
156 					// Call the adapter to handle the callbacks
157 					if (!isCancelled())
158 						internalHandle();
159 					else
160 						// clear callbacks are when cancelling
161 						for (Callback callback : callbacks)
162 							if (callback instanceof PasswordCallback) {
163 								char[] arr = ((PasswordCallback) callback).getPassword();
164 								if (arr != null) {
165 									Arrays.fill(arr, '*');
166 									((PasswordCallback) callback).setPassword(null);
167 								}
168 							} else if (callback instanceof NameCallback)
169 								((NameCallback) callback).setName(null);
170 				}
171 			}, true, new NullProgressMonitor(), Display.getDefault());
172 		} catch (ThreadDeath e) {
173 			isCancelled = true;
174 			log.debug("Thread " + Thread.currentThread().getId() + " died");
175 			throw e;
176 		} catch (Exception e) {
177 			isCancelled = true;
178 			IOException ioe = new IOException("Unexpected issue in login dialog, see root cause for more details");
179 			ioe.initCause(e);
180 			throw ioe;
181 		} finally {
182 			// so that the modal thread dies
183 			processCallbacks = true;
184 			// try {
185 			// // wait for the modal context thread to gracefully exit
186 			// modalContextThread.join();
187 			// } catch (InterruptedException ie) {
188 			// // silent
189 			// }
190 			modalContextThread = null;
191 		}
192 	}
193 
194 	protected void configureShell(Shell shell) {
195 		super.configureShell(shell);
196 		shell.setText("Authentication");
197 	}
198 }