From: Mathieu Baudier Date: Tue, 28 Apr 2015 08:46:37 +0000 (+0000) Subject: Move auth related widgets to CMS X-Git-Tag: argeo-commons-2.1.30~265 X-Git-Url: https://git.argeo.org/?a=commitdiff_plain;h=bd811d9fad35f55aa1ab3afbc835ff24367ffbbe;p=lgpl%2Fargeo-commons.git Move auth related widgets to CMS git-svn-id: https://svn.argeo.org/commons/trunk@8080 4cfe0d0a-d680-48aa-b62c-e0a02a3f76cc --- diff --git a/org.argeo.cms/src/org/argeo/cms/widgets/auth/AbstractLoginDialog.java b/org.argeo.cms/src/org/argeo/cms/widgets/auth/AbstractLoginDialog.java new file mode 100644 index 000000000..a9ed1c7ea --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/widgets/auth/AbstractLoginDialog.java @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2007-2012 Argeo GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.argeo.cms.widgets.auth; + +import java.io.IOException; +import java.util.Arrays; + +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.NameCallback; +import javax.security.auth.callback.PasswordCallback; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.argeo.cms.internal.kernel.Activator; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.jface.dialogs.IDialogConstants; +import org.eclipse.jface.dialogs.TrayDialog; +import org.eclipse.jface.operation.IRunnableWithProgress; +import org.eclipse.jface.operation.ModalContext; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Shell; + +/** Base for login dialogs */ +public abstract class AbstractLoginDialog extends TrayDialog implements + CallbackHandler { + private static final long serialVersionUID = -8046708963512717709L; + + private final static Log log = LogFactory.getLog(AbstractLoginDialog.class); + + private Thread modalContextThread = null; + boolean processCallbacks = false; + boolean isCancelled = false; + Callback[] callbackArray; + + protected final Callback[] getCallbacks() { + return this.callbackArray; + } + + public abstract void internalHandle(); + + public boolean isCancelled() { + return isCancelled; + } + + protected AbstractLoginDialog(Shell parentShell) { + super(parentShell); + } + + /* + * (non-Javadoc) + * + * @see + * javax.security.auth.callback.CallbackHandler#handle(javax.security.auth + * .callback.Callback[]) + */ + public void handle(final Callback[] callbacks) throws IOException { + // clean previous usage + if (processCallbacks) { + // this handler was already used + processCallbacks = false; + } + + if (modalContextThread != null) { + try { + modalContextThread.join(1000); + } catch (InterruptedException e) { + // silent + } + modalContextThread = null; + } + + // initialize + this.callbackArray = callbacks; + final Display display = Display.getDefault(); + display.syncExec(new Runnable() { + + public void run() { + isCancelled = false; + setBlockOnOpen(false); + open(); + + final Button okButton = getButton(IDialogConstants.OK_ID); + okButton.setText("Login"); + okButton.addSelectionListener(new SelectionListener() { + private static final long serialVersionUID = -200281625679096775L; + + public void widgetSelected(final SelectionEvent event) { + processCallbacks = true; + } + + public void widgetDefaultSelected(final SelectionEvent event) { + // nothing to do + } + }); + final Button cancel = getButton(IDialogConstants.CANCEL_ID); + cancel.addSelectionListener(new SelectionListener() { + private static final long serialVersionUID = -3826030278084915815L; + + public void widgetSelected(final SelectionEvent event) { + isCancelled = true; + processCallbacks = true; + } + + public void widgetDefaultSelected(final SelectionEvent event) { + // nothing to do + } + }); + } + }); + try { + ModalContext.setAllowReadAndDispatch(true); // Works for now. + ModalContext.run(new IRunnableWithProgress() { + + public void run(final IProgressMonitor monitor) { + modalContextThread = Thread.currentThread(); + // Wait here until OK or cancel is pressed, then let it rip. + // The event + // listener + // is responsible for closing the dialog (in the + // loginSucceeded + // event). + while (!processCallbacks && (modalContextThread != null) + && (modalContextThread == Thread.currentThread()) + && Activator.getBundleContext() != null) { + // Note: SecurityUiPlugin.getDefault() != null is false + // when the OSGi runtime is shut down + try { + Thread.sleep(100); + // if (display.isDisposed()) { + // log.warn("Display is disposed, killing login dialog thread"); + // throw new ThreadDeath(); + // } + } catch (final Exception e) { + // do nothing + } + } + processCallbacks = false; + // Call the adapter to handle the callbacks + if (!isCancelled()) + internalHandle(); + else + // clear callbacks are when cancelling + for (Callback callback : callbacks) + if (callback instanceof PasswordCallback) { + char[] arr = ((PasswordCallback) callback) + .getPassword(); + if (arr != null) { + Arrays.fill(arr, '*'); + ((PasswordCallback) callback) + .setPassword(null); + } + } else if (callback instanceof NameCallback) + ((NameCallback) callback).setName(null); + } + }, true, new NullProgressMonitor(), Display.getDefault()); + } catch (ThreadDeath e) { + isCancelled = true; + log.debug("Thread " + Thread.currentThread().getId() + " died"); + throw e; + } catch (Exception e) { + isCancelled = true; + IOException ioe = new IOException( + "Unexpected issue in login dialog, see root cause for more details"); + ioe.initCause(e); + throw ioe; + } finally { + // so that the modal thread dies + processCallbacks = true; + // try { + // // wait for the modal context thread to gracefully exit + // modalContextThread.join(); + // } catch (InterruptedException ie) { + // // silent + // } + modalContextThread = null; + } + } + + protected void configureShell(Shell shell) { + super.configureShell(shell); + shell.setText("Authentication"); + } +} diff --git a/org.argeo.cms/src/org/argeo/cms/widgets/auth/CompositeCallbackHandler.java b/org.argeo.cms/src/org/argeo/cms/widgets/auth/CompositeCallbackHandler.java new file mode 100644 index 000000000..30f8308e3 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/widgets/auth/CompositeCallbackHandler.java @@ -0,0 +1,291 @@ +package org.argeo.cms.widgets.auth; + +import java.io.IOException; +import java.util.Arrays; + +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.NameCallback; +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.callback.TextOutputCallback; +import javax.security.auth.callback.UnsupportedCallbackException; + +import org.argeo.util.LocaleCallback; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.KeyEvent; +import org.eclipse.swt.events.KeyListener; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Combo; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Text; + +/** + * A composite that can populate itself based on {@link Callback}s. It can be + * used directly as a {@link CallbackHandler} or be used by one by calling the + * {@link #createCallbackHandlers(Callback[])}. + *

+ * Supported standard {@link Callback}s are:
+ *

+ *

+ *

+ * Supported Argeo {@link Callback}s are:
+ *

+ *

+ */ +public class CompositeCallbackHandler extends Composite implements + CallbackHandler { + private static final long serialVersionUID = -928223893722723777L; + + private boolean wasUsedAlready = false; + private boolean isSubmitted = false; + private boolean isCanceled = false; + + public CompositeCallbackHandler(Composite parent, int style) { + super(parent, style); + } + + @Override + public synchronized void handle(final Callback[] callbacks) + throws IOException, UnsupportedCallbackException { + // reset + if (wasUsedAlready && !isSubmitted() && !isCanceled()) { + cancel(); + for (Control control : getChildren()) + control.dispose(); + isSubmitted = false; + isCanceled = false; + } + + for (Callback callback : callbacks) + checkCallbackSupported(callback); + // create controls synchronously in the UI thread + getDisplay().syncExec(new Runnable() { + + @Override + public void run() { + createCallbackHandlers(callbacks); + } + }); + + if (!wasUsedAlready) + wasUsedAlready = true; + + while (!isSubmitted() && !isCanceled()) { + try { + wait(1000l); + } catch (InterruptedException e) { + // silent + } + } + + cleanCallbacksAfterCancel(callbacks); + } + + public void checkCallbackSupported(Callback callback) + throws UnsupportedCallbackException { + if (callback instanceof TextOutputCallback + || callback instanceof NameCallback + || callback instanceof PasswordCallback + || callback instanceof LocaleCallback) { + return; + } else { + throw new UnsupportedCallbackException(callback); + } + } + + /** + * Set writable callbacks to null if the handle is canceled (check is done + * by the method) + */ + public void cleanCallbacksAfterCancel(Callback[] callbacks) { + if (isCanceled()) { + for (Callback callback : callbacks) { + if (callback instanceof NameCallback) { + ((NameCallback) callback).setName(null); + } else if (callback instanceof PasswordCallback) { + PasswordCallback pCallback = (PasswordCallback) callback; + char[] arr = pCallback.getPassword(); + if (arr != null) { + Arrays.fill(arr, '*'); + pCallback.setPassword(null); + } + } + } + } + } + + public void createCallbackHandlers(Callback[] callbacks) { + Composite composite = this; + for (int i = 0; i < callbacks.length; i++) { + Callback callback = callbacks[i]; + if (callback instanceof TextOutputCallback) { + createLabelTextoutputHandler(composite, + (TextOutputCallback) callback); + } else if (callback instanceof NameCallback) { + createNameHandler(composite, (NameCallback) callback); + } else if (callback instanceof PasswordCallback) { + createPasswordHandler(composite, (PasswordCallback) callback); + } else if (callback instanceof LocaleCallback) { + createLocaleHandler(composite, (LocaleCallback) callback); + } + } + } + + protected Text createNameHandler(Composite composite, + final NameCallback callback) { + Label label = new Label(composite, SWT.NONE); + label.setText(callback.getPrompt()); + final Text text = new Text(composite, SWT.SINGLE | SWT.LEAD + | SWT.BORDER); + if (callback.getDefaultName() != null) { + // set default value, if provided + text.setText(callback.getDefaultName()); + callback.setName(callback.getDefaultName()); + } + text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + text.addModifyListener(new ModifyListener() { + private static final long serialVersionUID = 7300032545287292973L; + + public void modifyText(ModifyEvent event) { + callback.setName(text.getText()); + } + }); + text.addSelectionListener(new SelectionListener() { + private static final long serialVersionUID = 1820530045857665111L; + + @Override + public void widgetSelected(SelectionEvent e) { + } + + @Override + public void widgetDefaultSelected(SelectionEvent e) { + submit(); + } + }); + + text.addKeyListener(new KeyListener() { + private static final long serialVersionUID = -8698107785092095713L; + + @Override + public void keyReleased(KeyEvent e) { + } + + @Override + public void keyPressed(KeyEvent e) { + } + }); + return text; + } + + protected Text createPasswordHandler(Composite composite, + final PasswordCallback callback) { + Label label = new Label(composite, SWT.NONE); + label.setText(callback.getPrompt()); + final Text passwordText = new Text(composite, SWT.SINGLE | SWT.LEAD + | SWT.PASSWORD | SWT.BORDER); + passwordText + .setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + passwordText.addModifyListener(new ModifyListener() { + private static final long serialVersionUID = -7099363995047686732L; + + public void modifyText(ModifyEvent event) { + callback.setPassword(passwordText.getTextChars()); + } + }); + passwordText.addSelectionListener(new SelectionListener() { + private static final long serialVersionUID = 1820530045857665111L; + + @Override + public void widgetSelected(SelectionEvent e) { + } + + @Override + public void widgetDefaultSelected(SelectionEvent e) { + submit(); + } + }); + return passwordText; + } + + protected Combo createLocaleHandler(Composite composite, + final LocaleCallback callback) { + String[] labels = callback.getSupportedLocalesLabels(); + if (labels.length == 0) + return null; + Label label = new Label(composite, SWT.NONE); + label.setText(callback.getPrompt()); + + final Combo combo = new Combo(composite, SWT.READ_ONLY); + combo.setItems(labels); + combo.select(callback.getDefaultIndex()); + combo.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + combo.addSelectionListener(new SelectionListener() { + private static final long serialVersionUID = 38678989091946277L; + + @Override + public void widgetSelected(SelectionEvent e) { + callback.setSelectedIndex(combo.getSelectionIndex()); + } + + @Override + public void widgetDefaultSelected(SelectionEvent e) { + } + }); + return combo; + } + + protected Label createLabelTextoutputHandler(Composite composite, + final TextOutputCallback callback) { + Label label = new Label(composite, SWT.NONE); + label.setText(callback.getMessage()); + GridData data = new GridData(SWT.FILL, SWT.FILL, true, true); + data.horizontalSpan = 2; + label.setLayoutData(data); + return label; + // TODO: find a way to pass this information + // int messageType = callback.getMessageType(); + // int dialogMessageType = IMessageProvider.NONE; + // switch (messageType) { + // case TextOutputCallback.INFORMATION: + // dialogMessageType = IMessageProvider.INFORMATION; + // break; + // case TextOutputCallback.WARNING: + // dialogMessageType = IMessageProvider.WARNING; + // break; + // case TextOutputCallback.ERROR: + // dialogMessageType = IMessageProvider.ERROR; + // break; + // } + // setMessage(callback.getMessage(), dialogMessageType); + } + + synchronized boolean isSubmitted() { + return isSubmitted; + } + + synchronized boolean isCanceled() { + return isCanceled; + } + + protected synchronized void submit() { + isSubmitted = true; + notifyAll(); + } + + protected synchronized void cancel() { + isCanceled = true; + notifyAll(); + } +} diff --git a/org.argeo.cms/src/org/argeo/cms/widgets/auth/DefaultLoginDialog.java b/org.argeo.cms/src/org/argeo/cms/widgets/auth/DefaultLoginDialog.java new file mode 100644 index 000000000..b8de34b7c --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/widgets/auth/DefaultLoginDialog.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2007-2012 Argeo GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.argeo.cms.widgets.auth; + +import javax.security.auth.callback.CallbackHandler; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Shell; + +/** Default authentication dialog, to be used as {@link CallbackHandler}. */ +public class DefaultLoginDialog extends AbstractLoginDialog { + private static final long serialVersionUID = -8551827590693035734L; + + public DefaultLoginDialog() { + this(Display.getCurrent().getActiveShell()); + } + + public DefaultLoginDialog(Shell parentShell) { + super(parentShell); + } + + protected Point getInitialSize() { + return new Point(350, 180); + } + + @Override + protected Control createContents(Composite parent) { + Control control = super.createContents(parent); + parent.pack(); + + // Move the dialog to the center of the top level shell. + Rectangle shellBounds; + if (Display.getCurrent().getActiveShell() != null) // RCP + shellBounds = Display.getCurrent().getActiveShell().getBounds(); + else + shellBounds = Display.getCurrent().getBounds();// RAP + Point dialogSize = parent.getSize(); + int x = shellBounds.x + (shellBounds.width - dialogSize.x) / 2; + int y = shellBounds.y + (shellBounds.height - dialogSize.y) / 2; + parent.setLocation(x, y); + return control; + } + + protected Control createDialogArea(Composite parent) { + Composite dialogarea = (Composite) super.createDialogArea(parent); + CompositeCallbackHandler composite = new CompositeCallbackHandler( + dialogarea, SWT.NONE); + composite.setLayout(new GridLayout(2, false)); + composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + composite.createCallbackHandlers(getCallbacks()); + return composite; + } + + public void internalHandle() { + } +} diff --git a/org.argeo.security.ui.rap/src/org/argeo/security/ui/rap/SecureEntryPoint.java b/org.argeo.security.ui.rap/src/org/argeo/security/ui/rap/SecureEntryPoint.java index b79d60d26..3f0f66469 100644 --- a/org.argeo.security.ui.rap/src/org/argeo/security/ui/rap/SecureEntryPoint.java +++ b/org.argeo.security.ui.rap/src/org/argeo/security/ui/rap/SecureEntryPoint.java @@ -22,16 +22,14 @@ import javax.security.auth.callback.CallbackHandler; import javax.security.auth.login.CredentialNotFoundException; import javax.security.auth.login.LoginContext; import javax.security.auth.login.LoginException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpSession; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.argeo.ArgeoException; import org.argeo.cms.KernelHeader; import org.argeo.cms.auth.ArgeoLoginContext; +import org.argeo.cms.widgets.auth.DefaultLoginDialog; import org.argeo.eclipse.ui.dialogs.ErrorFeedback; -import org.argeo.security.ui.auth.DefaultLoginDialog; import org.argeo.util.LocaleUtils; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.rap.rwt.RWT; @@ -40,7 +38,6 @@ import org.eclipse.swt.widgets.Display; import org.eclipse.ui.PlatformUI; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; /** @@ -78,13 +75,13 @@ public class SecureEntryPoint implements EntryPoint { RWT.getRequest().getSession().setMaxInactiveInterval(loginTimeout); // Try to load security context thanks to the session processing filter -// HttpServletRequest httpRequest = RWT.getRequest(); -// HttpSession httpSession = httpRequest.getSession(); -// Object contextFromSessionObject = httpSession -// .getAttribute(SPRING_SECURITY_CONTEXT_KEY); -// if (contextFromSessionObject != null) -// SecurityContextHolder -// .setContext((SecurityContext) contextFromSessionObject); + // HttpServletRequest httpRequest = RWT.getRequest(); + // HttpSession httpSession = httpRequest.getSession(); + // Object contextFromSessionObject = httpSession + // .getAttribute(SPRING_SECURITY_CONTEXT_KEY); + // if (contextFromSessionObject != null) + // SecurityContextHolder + // .setContext((SecurityContext) contextFromSessionObject); final Display display = PlatformUI.createDisplay(); Subject subject = new Subject(); @@ -106,9 +103,10 @@ public class SecureEntryPoint implements EntryPoint { throw new ArgeoException("Login succeeded but no auth");// fatal // add security context to session -// if (httpSession.getAttribute(SPRING_SECURITY_CONTEXT_KEY) == null) -// httpSession.setAttribute(SPRING_SECURITY_CONTEXT_KEY, -// SecurityContextHolder.getContext()); + // if (httpSession.getAttribute(SPRING_SECURITY_CONTEXT_KEY) == + // null) + // httpSession.setAttribute(SPRING_SECURITY_CONTEXT_KEY, + // SecurityContextHolder.getContext()); // add thread locale to RWT session if (log.isTraceEnabled()) diff --git a/org.argeo.security.ui/pom.xml b/org.argeo.security.ui/pom.xml index 9ca839e8f..4c6cb5183 100644 --- a/org.argeo.security.ui/pom.xml +++ b/org.argeo.security.ui/pom.xml @@ -11,6 +11,11 @@ Commons CMS Workbench jar + + org.argeo.commons + org.argeo.cms + 2.1.22-SNAPSHOT + org.argeo.commons org.argeo.security.core diff --git a/org.argeo.security.ui/src/org/argeo/security/ui/SecurityUiPlugin.java b/org.argeo.security.ui/src/org/argeo/security/ui/SecurityUiPlugin.java index a4190d69a..e5987938b 100644 --- a/org.argeo.security.ui/src/org/argeo/security/ui/SecurityUiPlugin.java +++ b/org.argeo.security.ui/src/org/argeo/security/ui/SecurityUiPlugin.java @@ -22,7 +22,7 @@ import javax.security.auth.callback.CallbackHandler; import javax.security.auth.callback.UnsupportedCallbackException; import org.argeo.ArgeoException; -import org.argeo.security.ui.auth.DefaultLoginDialog; +import org.argeo.cms.widgets.auth.DefaultLoginDialog; import org.eclipse.swt.widgets.Display; import org.osgi.framework.BundleActivator; import org.osgi.framework.BundleContext; diff --git a/org.argeo.security.ui/src/org/argeo/security/ui/auth/AbstractLoginDialog.java b/org.argeo.security.ui/src/org/argeo/security/ui/auth/AbstractLoginDialog.java deleted file mode 100644 index 107697e75..000000000 --- a/org.argeo.security.ui/src/org/argeo/security/ui/auth/AbstractLoginDialog.java +++ /dev/null @@ -1,201 +0,0 @@ -/* - * Copyright (C) 2007-2012 Argeo GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.argeo.security.ui.auth; - -import java.io.IOException; -import java.util.Arrays; - -import javax.security.auth.callback.Callback; -import javax.security.auth.callback.CallbackHandler; -import javax.security.auth.callback.NameCallback; -import javax.security.auth.callback.PasswordCallback; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.argeo.security.ui.SecurityUiPlugin; -import org.eclipse.core.runtime.IProgressMonitor; -import org.eclipse.core.runtime.NullProgressMonitor; -import org.eclipse.jface.dialogs.IDialogConstants; -import org.eclipse.jface.dialogs.TrayDialog; -import org.eclipse.jface.operation.IRunnableWithProgress; -import org.eclipse.jface.operation.ModalContext; -import org.eclipse.swt.events.SelectionEvent; -import org.eclipse.swt.events.SelectionListener; -import org.eclipse.swt.widgets.Button; -import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.Shell; - -/** Base for login dialogs */ -public abstract class AbstractLoginDialog extends TrayDialog implements - CallbackHandler { - private static final long serialVersionUID = -8046708963512717709L; - - private final static Log log = LogFactory.getLog(AbstractLoginDialog.class); - - private Thread modalContextThread = null; - boolean processCallbacks = false; - boolean isCancelled = false; - Callback[] callbackArray; - - protected final Callback[] getCallbacks() { - return this.callbackArray; - } - - public abstract void internalHandle(); - - public boolean isCancelled() { - return isCancelled; - } - - protected AbstractLoginDialog(Shell parentShell) { - super(parentShell); - } - - /* - * (non-Javadoc) - * - * @see - * javax.security.auth.callback.CallbackHandler#handle(javax.security.auth - * .callback.Callback[]) - */ - public void handle(final Callback[] callbacks) throws IOException { - // clean previous usage - if (processCallbacks) { - // this handler was already used - processCallbacks = false; - } - - if (modalContextThread != null) { - try { - modalContextThread.join(1000); - } catch (InterruptedException e) { - // silent - } - modalContextThread = null; - } - - // initialize - this.callbackArray = callbacks; - final Display display = Display.getDefault(); - display.syncExec(new Runnable() { - - public void run() { - isCancelled = false; - setBlockOnOpen(false); - open(); - - final Button okButton = getButton(IDialogConstants.OK_ID); - okButton.setText("Login"); - okButton.addSelectionListener(new SelectionListener() { - private static final long serialVersionUID = -200281625679096775L; - - public void widgetSelected(final SelectionEvent event) { - processCallbacks = true; - } - - public void widgetDefaultSelected(final SelectionEvent event) { - // nothing to do - } - }); - final Button cancel = getButton(IDialogConstants.CANCEL_ID); - cancel.addSelectionListener(new SelectionListener() { - private static final long serialVersionUID = -3826030278084915815L; - - public void widgetSelected(final SelectionEvent event) { - isCancelled = true; - processCallbacks = true; - } - - public void widgetDefaultSelected(final SelectionEvent event) { - // nothing to do - } - }); - } - }); - try { - ModalContext.setAllowReadAndDispatch(true); // Works for now. - ModalContext.run(new IRunnableWithProgress() { - - public void run(final IProgressMonitor monitor) { - modalContextThread = Thread.currentThread(); - // Wait here until OK or cancel is pressed, then let it rip. - // The event - // listener - // is responsible for closing the dialog (in the - // loginSucceeded - // event). - while (!processCallbacks && (modalContextThread != null) - && (modalContextThread == Thread.currentThread()) - && SecurityUiPlugin.getBundleContext() != null) { - // Note: SecurityUiPlugin.getDefault() != null is false - // when the OSGi runtime is shut down - try { - Thread.sleep(100); - // if (display.isDisposed()) { - // log.warn("Display is disposed, killing login dialog thread"); - // throw new ThreadDeath(); - // } - } catch (final Exception e) { - // do nothing - } - } - processCallbacks = false; - // Call the adapter to handle the callbacks - if (!isCancelled()) - internalHandle(); - else - // clear callbacks are when cancelling - for (Callback callback : callbacks) - if (callback instanceof PasswordCallback) { - char[] arr = ((PasswordCallback) callback) - .getPassword(); - if (arr != null) { - Arrays.fill(arr, '*'); - ((PasswordCallback) callback) - .setPassword(null); - } - } else if (callback instanceof NameCallback) - ((NameCallback) callback).setName(null); - } - }, true, new NullProgressMonitor(), Display.getDefault()); - } catch (ThreadDeath e) { - isCancelled = true; - log.debug("Thread " + Thread.currentThread().getId() + " died"); - throw e; - } catch (Exception e) { - isCancelled = true; - IOException ioe = new IOException( - "Unexpected issue in login dialog, see root cause for more details"); - ioe.initCause(e); - throw ioe; - } finally { - // so that the modal thread dies - processCallbacks = true; - // try { - // // wait for the modal context thread to gracefully exit - // modalContextThread.join(); - // } catch (InterruptedException ie) { - // // silent - // } - modalContextThread = null; - } - } - - protected void configureShell(Shell shell) { - super.configureShell(shell); - shell.setText("Authentication"); - } -} diff --git a/org.argeo.security.ui/src/org/argeo/security/ui/auth/CompositeCallbackHandler.java b/org.argeo.security.ui/src/org/argeo/security/ui/auth/CompositeCallbackHandler.java deleted file mode 100644 index a2fc83162..000000000 --- a/org.argeo.security.ui/src/org/argeo/security/ui/auth/CompositeCallbackHandler.java +++ /dev/null @@ -1,291 +0,0 @@ -package org.argeo.security.ui.auth; - -import java.io.IOException; -import java.util.Arrays; - -import javax.security.auth.callback.Callback; -import javax.security.auth.callback.CallbackHandler; -import javax.security.auth.callback.NameCallback; -import javax.security.auth.callback.PasswordCallback; -import javax.security.auth.callback.TextOutputCallback; -import javax.security.auth.callback.UnsupportedCallbackException; - -import org.argeo.util.LocaleCallback; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.KeyEvent; -import org.eclipse.swt.events.KeyListener; -import org.eclipse.swt.events.ModifyEvent; -import org.eclipse.swt.events.ModifyListener; -import org.eclipse.swt.events.SelectionEvent; -import org.eclipse.swt.events.SelectionListener; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.widgets.Combo; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Text; - -/** - * A composite that can populate itself based on {@link Callback}s. It can be - * used directly as a {@link CallbackHandler} or be used by one by calling the - * {@link #createCallbackHandlers(Callback[])}. - *

- * Supported standard {@link Callback}s are:
- *

    - *
  • {@link PasswordCallback}
  • - *
  • {@link NameCallback}
  • - *
  • {@link TextOutputCallback}
  • - *
- *

- *

- * Supported Argeo {@link Callback}s are:
- *

    - *
  • {@link LocaleCallback}
  • - *
- *

- */ -public class CompositeCallbackHandler extends Composite implements - CallbackHandler { - private static final long serialVersionUID = -928223893722723777L; - - private boolean wasUsedAlready = false; - private boolean isSubmitted = false; - private boolean isCanceled = false; - - public CompositeCallbackHandler(Composite parent, int style) { - super(parent, style); - } - - @Override - public synchronized void handle(final Callback[] callbacks) - throws IOException, UnsupportedCallbackException { - // reset - if (wasUsedAlready && !isSubmitted() && !isCanceled()) { - cancel(); - for (Control control : getChildren()) - control.dispose(); - isSubmitted = false; - isCanceled = false; - } - - for (Callback callback : callbacks) - checkCallbackSupported(callback); - // create controls synchronously in the UI thread - getDisplay().syncExec(new Runnable() { - - @Override - public void run() { - createCallbackHandlers(callbacks); - } - }); - - if (!wasUsedAlready) - wasUsedAlready = true; - - while (!isSubmitted() && !isCanceled()) { - try { - wait(1000l); - } catch (InterruptedException e) { - // silent - } - } - - cleanCallbacksAfterCancel(callbacks); - } - - public void checkCallbackSupported(Callback callback) - throws UnsupportedCallbackException { - if (callback instanceof TextOutputCallback - || callback instanceof NameCallback - || callback instanceof PasswordCallback - || callback instanceof LocaleCallback) { - return; - } else { - throw new UnsupportedCallbackException(callback); - } - } - - /** - * Set writable callbacks to null if the handle is canceled (check is done - * by the method) - */ - public void cleanCallbacksAfterCancel(Callback[] callbacks) { - if (isCanceled()) { - for (Callback callback : callbacks) { - if (callback instanceof NameCallback) { - ((NameCallback) callback).setName(null); - } else if (callback instanceof PasswordCallback) { - PasswordCallback pCallback = (PasswordCallback) callback; - char[] arr = pCallback.getPassword(); - if (arr != null) { - Arrays.fill(arr, '*'); - pCallback.setPassword(null); - } - } - } - } - } - - public void createCallbackHandlers(Callback[] callbacks) { - Composite composite = this; - for (int i = 0; i < callbacks.length; i++) { - Callback callback = callbacks[i]; - if (callback instanceof TextOutputCallback) { - createLabelTextoutputHandler(composite, - (TextOutputCallback) callback); - } else if (callback instanceof NameCallback) { - createNameHandler(composite, (NameCallback) callback); - } else if (callback instanceof PasswordCallback) { - createPasswordHandler(composite, (PasswordCallback) callback); - } else if (callback instanceof LocaleCallback) { - createLocaleHandler(composite, (LocaleCallback) callback); - } - } - } - - protected Text createNameHandler(Composite composite, - final NameCallback callback) { - Label label = new Label(composite, SWT.NONE); - label.setText(callback.getPrompt()); - final Text text = new Text(composite, SWT.SINGLE | SWT.LEAD - | SWT.BORDER); - if (callback.getDefaultName() != null) { - // set default value, if provided - text.setText(callback.getDefaultName()); - callback.setName(callback.getDefaultName()); - } - text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - text.addModifyListener(new ModifyListener() { - private static final long serialVersionUID = 7300032545287292973L; - - public void modifyText(ModifyEvent event) { - callback.setName(text.getText()); - } - }); - text.addSelectionListener(new SelectionListener() { - private static final long serialVersionUID = 1820530045857665111L; - - @Override - public void widgetSelected(SelectionEvent e) { - } - - @Override - public void widgetDefaultSelected(SelectionEvent e) { - submit(); - } - }); - - text.addKeyListener(new KeyListener() { - private static final long serialVersionUID = -8698107785092095713L; - - @Override - public void keyReleased(KeyEvent e) { - } - - @Override - public void keyPressed(KeyEvent e) { - } - }); - return text; - } - - protected Text createPasswordHandler(Composite composite, - final PasswordCallback callback) { - Label label = new Label(composite, SWT.NONE); - label.setText(callback.getPrompt()); - final Text passwordText = new Text(composite, SWT.SINGLE | SWT.LEAD - | SWT.PASSWORD | SWT.BORDER); - passwordText - .setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - passwordText.addModifyListener(new ModifyListener() { - private static final long serialVersionUID = -7099363995047686732L; - - public void modifyText(ModifyEvent event) { - callback.setPassword(passwordText.getTextChars()); - } - }); - passwordText.addSelectionListener(new SelectionListener() { - private static final long serialVersionUID = 1820530045857665111L; - - @Override - public void widgetSelected(SelectionEvent e) { - } - - @Override - public void widgetDefaultSelected(SelectionEvent e) { - submit(); - } - }); - return passwordText; - } - - protected Combo createLocaleHandler(Composite composite, - final LocaleCallback callback) { - String[] labels = callback.getSupportedLocalesLabels(); - if (labels.length == 0) - return null; - Label label = new Label(composite, SWT.NONE); - label.setText(callback.getPrompt()); - - final Combo combo = new Combo(composite, SWT.READ_ONLY); - combo.setItems(labels); - combo.select(callback.getDefaultIndex()); - combo.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - combo.addSelectionListener(new SelectionListener() { - private static final long serialVersionUID = 38678989091946277L; - - @Override - public void widgetSelected(SelectionEvent e) { - callback.setSelectedIndex(combo.getSelectionIndex()); - } - - @Override - public void widgetDefaultSelected(SelectionEvent e) { - } - }); - return combo; - } - - protected Label createLabelTextoutputHandler(Composite composite, - final TextOutputCallback callback) { - Label label = new Label(composite, SWT.NONE); - label.setText(callback.getMessage()); - GridData data = new GridData(SWT.FILL, SWT.FILL, true, true); - data.horizontalSpan = 2; - label.setLayoutData(data); - return label; - // TODO: find a way to pass this information - // int messageType = callback.getMessageType(); - // int dialogMessageType = IMessageProvider.NONE; - // switch (messageType) { - // case TextOutputCallback.INFORMATION: - // dialogMessageType = IMessageProvider.INFORMATION; - // break; - // case TextOutputCallback.WARNING: - // dialogMessageType = IMessageProvider.WARNING; - // break; - // case TextOutputCallback.ERROR: - // dialogMessageType = IMessageProvider.ERROR; - // break; - // } - // setMessage(callback.getMessage(), dialogMessageType); - } - - synchronized boolean isSubmitted() { - return isSubmitted; - } - - synchronized boolean isCanceled() { - return isCanceled; - } - - protected synchronized void submit() { - isSubmitted = true; - notifyAll(); - } - - protected synchronized void cancel() { - isCanceled = true; - notifyAll(); - } -} diff --git a/org.argeo.security.ui/src/org/argeo/security/ui/auth/DefaultLoginDialog.java b/org.argeo.security.ui/src/org/argeo/security/ui/auth/DefaultLoginDialog.java deleted file mode 100644 index 85f90bd3f..000000000 --- a/org.argeo.security.ui/src/org/argeo/security/ui/auth/DefaultLoginDialog.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (C) 2007-2012 Argeo GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.argeo.security.ui.auth; - -import javax.security.auth.callback.CallbackHandler; - -import org.argeo.security.ui.SecurityUiPlugin; -import org.eclipse.swt.SWT; -import org.eclipse.swt.graphics.Point; -import org.eclipse.swt.graphics.Rectangle; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.Shell; - -/** Default authentication dialog, to be used as {@link CallbackHandler}. */ -public class DefaultLoginDialog extends AbstractLoginDialog { - private static final long serialVersionUID = -8551827590693035734L; - - public DefaultLoginDialog() { - this(SecurityUiPlugin.display.get().getActiveShell()); - } - - public DefaultLoginDialog(Shell parentShell) { - super(parentShell); - } - - protected Point getInitialSize() { - return new Point(350, 180); - } - - @Override - protected Control createContents(Composite parent) { - Control control = super.createContents(parent); - parent.pack(); - - // Move the dialog to the center of the top level shell. - Rectangle shellBounds; - if (Display.getCurrent().getActiveShell() != null) // RCP - shellBounds = Display.getCurrent().getActiveShell().getBounds(); - else - shellBounds = Display.getCurrent().getBounds();// RAP - Point dialogSize = parent.getSize(); - int x = shellBounds.x + (shellBounds.width - dialogSize.x) / 2; - int y = shellBounds.y + (shellBounds.height - dialogSize.y) / 2; - parent.setLocation(x, y); - return control; - } - - protected Control createDialogArea(Composite parent) { - Composite dialogarea = (Composite) super.createDialogArea(parent); - CompositeCallbackHandler composite = new CompositeCallbackHandler( - dialogarea, SWT.NONE); - composite.setLayout(new GridLayout(2, false)); - composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); - composite.createCallbackHandlers(getCallbacks()); - return composite; - } - - public void internalHandle() { - } -}