/*******************************************************************************
* Copyright (c) 2015 Red Hat, Inc.
* Distributed under license by Red Hat, Inc. All rights reserved.
* This program is made available under the terms of the
* Eclipse Public License v1.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
******************************************************************************/
package org.jboss.tools.foundation.core.credentials.internal;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExtension;
import org.eclipse.core.runtime.IExtensionPoint;
import org.eclipse.core.runtime.IExtensionRegistry;
import org.eclipse.core.runtime.InvalidRegistryObjectException;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.preferences.ConfigurationScope;
import org.eclipse.core.runtime.preferences.IEclipsePreferences;
import org.eclipse.core.runtime.preferences.InstanceScope;
import org.eclipse.equinox.security.storage.ISecurePreferences;
import org.eclipse.equinox.security.storage.SecurePreferencesFactory;
import org.eclipse.equinox.security.storage.StorageException;
import org.jboss.tools.foundation.core.credentials.CredentialService;
import org.jboss.tools.foundation.core.credentials.ICredentialDomain;
import org.jboss.tools.foundation.core.credentials.ICredentialListener;
import org.jboss.tools.foundation.core.credentials.ICredentialsModel;
import org.jboss.tools.foundation.core.credentials.ICredentialsPrompter;
import org.jboss.tools.foundation.core.credentials.UsernameChangedException;
import org.jboss.tools.foundation.core.internal.FoundationCorePlugin;
import org.osgi.service.prefs.BackingStoreException;
import org.osgi.service.prefs.Preferences;
public class CredentialsModel implements ICredentialsModel {
private static CredentialsModel instance = new CredentialsModel();
// A preference key where we store the string
static final String CREDENTIAL_BASE_KEY = "org.jboss.tools.foundation.core.credentials.CredentialsModel";
/*
* Internal event types
*/
private static final int DOMAIN_ADDED = 1;
private static final int DOMAIN_REMOVED = 2;
private static final int CREDENTIAL_ADDED = 3;
private static final int CREDENTIAL_REMOVED = 4;
private static final int CREDENTIAL_CHANGED = 5;
private static final int DEFAULT_CREDENTIAL_CHANGED = 6;
public static CredentialsModel getDefault() {
return instance;
}
private IEclipsePreferences prefs;
private HashMap<String, ICredentialDomain> map;
private ArrayList<ICredentialListener> listeners;
public CredentialsModel() {
loadModel();
}
private void loadModel() {
map = new HashMap<String, ICredentialDomain>();
listeners = new ArrayList<>();
try {
ICredentialDomain[] domains = loadDomainsFromPreferences();
for( int i = 0; i < domains.length; i++ ) {
map.put(domains[i].getId(), domains[i]);
}
// Static domains that must always be present. For now, hard-coded, but maybe
// Can be contributed via ext-pt later.
if( !map.containsKey(CredentialService.REDHAT_ACCESS)) {
map.put(CredentialService.REDHAT_ACCESS, new CredentialDomain(CredentialService.REDHAT_ACCESS, CredentialService.REDHAT_ACCESS, false));
}
if( !map.containsKey(CredentialService.JBOSS_ORG)) {
map.put(CredentialService.JBOSS_ORG, new CredentialDomain(CredentialService.JBOSS_ORG, CredentialService.JBOSS_ORG, false));
}
} catch(BackingStoreException bse) {
// TODO log
bse.printStackTrace();
}
}
/**
* Fire the events to listeners
* @param type
* @param domain
* @param user
*/
private void fireEvent(int type, ICredentialDomain domain, String user) {
Iterator<ICredentialListener> it = listeners.iterator();
while(it.hasNext()) {
switch(type) {
case DOMAIN_ADDED:
it.next().domainAdded(domain);
break;
case DOMAIN_REMOVED:
it.next().domainRemoved(domain);
break;
case CREDENTIAL_ADDED:
it.next().credentialAdded(domain, user);
break;
case CREDENTIAL_REMOVED:
it.next().credentialRemoved(domain, user);
break;
case CREDENTIAL_CHANGED:
it.next().credentialChanged(domain, user);
break;
case DEFAULT_CREDENTIAL_CHANGED:
it.next().defaultUsernameChanged(domain, user);
break;
}
}
}
public void addCredentials(ICredentialDomain domain, String user, String pass) {
addCredentials(domain, user, false, pass);
}
public void addPromptedCredentials(ICredentialDomain domain, String user) {
addCredentials(domain, user, true, null);
}
private void addCredentials(ICredentialDomain domain, String user, boolean prompt, String password) {
CredentialDomain cd = (CredentialDomain)domain;
boolean existed = cd.userExists(user);
String preDefault = cd.getDefaultUsername();
if( !prompt )
((CredentialDomain)domain).addCredentials(user, password);
else
((CredentialDomain)domain).addPromptedCredentials(user);
String postDefault = cd.getDefaultUsername();
// fire credential added or changed
if( !existed )
fireEvent(CREDENTIAL_ADDED, domain, user);
else
fireEvent(CREDENTIAL_CHANGED, domain, user);
if( !isEqual(preDefault, postDefault)) {
fireEvent(DEFAULT_CREDENTIAL_CHANGED, domain, user);
}
}
public boolean credentialRequiresPrompt(ICredentialDomain domain, String user) {
return ((CredentialDomain)domain).userRequiresPrompt(user);
}
public void removeCredentials(ICredentialDomain domain, String user) {
CredentialDomain cd = (CredentialDomain)domain;
String preDefault = cd.getDefaultUsername();
((CredentialDomain)domain).removeCredential(user);
String postDefault = cd.getDefaultUsername();
fireEvent(CREDENTIAL_REMOVED, domain, user);
if( !isEqual(preDefault, postDefault)) {
fireEvent(DEFAULT_CREDENTIAL_CHANGED, domain, user);
}
}
private boolean isEqual(String one, String two) {
if( one == null ) {
return two == null;
} else {
return one.equals(two);
}
}
public ICredentialDomain addDomain(String id, String name, boolean removable) {
if( !map.containsKey(id)) {
ICredentialDomain d = new CredentialDomain(id, name, removable);
map.put(d.getId(), d);
fireEvent(DOMAIN_ADDED, d, null);
return d;
}
return null;
}
public ICredentialDomain[] getDomains() {
ArrayList<ICredentialDomain> result = new ArrayList<ICredentialDomain>(map.values());
Collections.sort(result, new Comparator<ICredentialDomain>() {
public int compare(ICredentialDomain o1, ICredentialDomain o2) {
return o1.getName().compareTo(o2.getName());
}
});
return (ICredentialDomain[]) result.toArray(new ICredentialDomain[result.size()]);
}
public ICredentialDomain getDomain(String id) {
return map.get(id);
}
public void removeDomain(ICredentialDomain domain) {
if( domain != null && map.containsKey(domain.getId())) {
map.remove(domain.getId());
fireEvent(DOMAIN_REMOVED, domain, null);
}
}
@Override
public void setDefaultCredential(ICredentialDomain domain, String user) throws IllegalArgumentException {
String original = ((CredentialDomain)domain).getDefaultUsername();
if( user != null && !user.equals(original)) {
((CredentialDomain)domain).setDefaultUsername(user);
fireEvent(DEFAULT_CREDENTIAL_CHANGED, domain, user);
}
}
private ICredentialDomain[] loadDomainsFromPreferences() throws BackingStoreException {
ArrayList<ICredentialDomain> domains = new ArrayList<ICredentialDomain>();
IEclipsePreferences root = InstanceScope.INSTANCE.getNode(FoundationCorePlugin.PLUGIN_ID);
Preferences credentialRoot = root.node(CREDENTIAL_BASE_KEY);
String[] childNodes = credentialRoot.childrenNames();
for( int i = 0; i < childNodes.length; i++ ) {
Preferences domain = credentialRoot.node(childNodes[i]);
ICredentialDomain cd = new CredentialDomain(domain);
domains.add(cd);
}
return (ICredentialDomain[]) domains.toArray(new ICredentialDomain[domains.size()]);
}
@Override
public void saveModel() {
save();
}
@Override
public boolean save() {
try {
ISecurePreferences secureRoot = SecurePreferencesFactory.getDefault();
ISecurePreferences secureCredentialRoot = secureRoot.node(CREDENTIAL_BASE_KEY);
IEclipsePreferences root = InstanceScope.INSTANCE.getNode(FoundationCorePlugin.PLUGIN_ID);
Preferences credentialRoot = root.node(CREDENTIAL_BASE_KEY);
ArrayList<ICredentialDomain> domains = new ArrayList<ICredentialDomain>(map.values());
Iterator<ICredentialDomain> it = domains.iterator();
while(it.hasNext()) {
ICredentialDomain d = it.next();
ISecurePreferences secureDomainNode = secureCredentialRoot.node(d.getId());
Preferences domainNode = credentialRoot.node(d.getId());
((CredentialDomain)d).saveToPreferences(domainNode, secureDomainNode);
}
// Check for any removed domains
String[] childrenNodes = credentialRoot.childrenNames();
for( int i = 0; i < childrenNodes.length; i++ ) {
if( getDomain(childrenNodes[i]) == null) {
// Domain was deleted, delete the preference node
credentialRoot.node(childrenNodes[i]).removeNode();
}
}
credentialRoot.flush();
secureCredentialRoot.flush();
} catch(StorageException se) {
if(se.getErrorCode() == StorageException.NO_PASSWORD) {
return false;
}
FoundationCorePlugin.pluginLog().logError("Error saving credentials in secure storage", se);
} catch(IOException ioe) {
FoundationCorePlugin.pluginLog().logError("Error saving credentials in secure storage", ioe);
} catch(BackingStoreException bse) {
FoundationCorePlugin.pluginLog().logError("Error saving credentials in secure storage", bse);
}
return true;
}
IEclipsePreferences getPreferences() {
if (prefs == null) {
prefs = ConfigurationScope.INSTANCE.getNode(FoundationCorePlugin.PLUGIN_ID);
}
return prefs;
}
@Override
public void addCredentialListener(ICredentialListener listener) {
listeners.add(listener);
}
@Override
public void removeCredentialListener(ICredentialListener listener) {
listeners.remove(listener);
}
String promptForCredentials(ICredentialDomain domain, String user, boolean canChangeUser) throws UsernameChangedException {
ICredentialsPrompter passwordProvider = createPasswordPrompt();
passwordProvider.init(domain, user, canChangeUser);
passwordProvider.prompt();
String retUser = passwordProvider.getUsername();
String retPass = passwordProvider.getPassword();
if( retUser == null || retPass == null || retUser.isEmpty() || retPass.isEmpty()) {
return null;
}
boolean save = passwordProvider.saveChanges();
if( save ) {
// Update the credentials
addCredentials(domain, retUser, retPass);
save();
}
if(!user.equals(retUser)) {
throw new UsernameChangedException(domain, user, retUser, retPass, passwordProvider.saveChanges());
}
return retPass;
}
String promptForCredentials(ICredentialDomain domain, String user) throws UsernameChangedException {
return promptForCredentials(domain, user, true);
}
String promptForPassword(ICredentialDomain domain, String user) {
try {
return promptForCredentials(domain, user, true);
} catch(UsernameChangedException uce) {
// Should *never* happen
FoundationCorePlugin.pluginLog().logError("Error: username has changed when not allowed", uce);
return uce.getPassword();
}
}
private static final String CREDENTIAL_PROMPTER_EXT_PT = "org.jboss.tools.foundation.core.credentialPrompter";
private ICredentialsPrompter createPasswordPrompt() {
IExtension[] extensions = findExtension(CREDENTIAL_PROMPTER_EXT_PT);
if( extensions.length > 1 ) {
FoundationCorePlugin.pluginLog().logError("Multiple credential prompters found for extension point " + CREDENTIAL_PROMPTER_EXT_PT);
}
for (int i = 0; i < extensions.length; i++) {
IConfigurationElement elements[] = extensions[i].getConfigurationElements();
for (int j = 0; j < elements.length; j++) {
if( elements.length > 1 ) {
FoundationCorePlugin.pluginLog().logError("Multiple credential prompters found for extension point " + CREDENTIAL_PROMPTER_EXT_PT);
}
try {
return (ICredentialsPrompter) elements[j].createExecutableExtension("class");
} catch (InvalidRegistryObjectException e) {
FoundationCorePlugin.pluginLog().logError("Unable to load a credential prompter for extension point " + CREDENTIAL_PROMPTER_EXT_PT);
} catch (CoreException e) {
FoundationCorePlugin.pluginLog().logError("Unable to load a credential prompter for extension point " + CREDENTIAL_PROMPTER_EXT_PT);
}
}
}
return null;
}
private static IExtension[] findExtension(String extensionId) {
IExtensionRegistry registry = Platform.getExtensionRegistry();
IExtensionPoint extensionPoint = registry
.getExtensionPoint(extensionId);
return extensionPoint.getExtensions();
}
}