You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@taverna.apache.org by st...@apache.org on 2015/02/23 16:37:08 UTC

[08/49] incubator-taverna-server git commit: taverna-* module names

http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/2c71f9a9/taverna-server-webapp/src/main/java/org/taverna/server/master/worker/SecurityContextDelegate.java
----------------------------------------------------------------------
diff --git a/taverna-server-webapp/src/main/java/org/taverna/server/master/worker/SecurityContextDelegate.java b/taverna-server-webapp/src/main/java/org/taverna/server/master/worker/SecurityContextDelegate.java
new file mode 100644
index 0000000..ff14986
--- /dev/null
+++ b/taverna-server-webapp/src/main/java/org/taverna/server/master/worker/SecurityContextDelegate.java
@@ -0,0 +1,649 @@
+/*
+ * Copyright (C) 2010-2012 The University of Manchester
+ * 
+ * See the file "LICENSE" for license terms.
+ */
+package org.taverna.server.master.worker;
+
+import static java.lang.String.format;
+import static java.util.Arrays.fill;
+import static java.util.UUID.randomUUID;
+import static org.taverna.server.master.defaults.Default.CERTIFICATE_FIELD_NAMES;
+import static org.taverna.server.master.defaults.Default.CERTIFICATE_TYPE;
+import static org.taverna.server.master.defaults.Default.CREDENTIAL_FILE_SIZE_LIMIT;
+import static org.taverna.server.master.identity.WorkflowInternalAuthProvider.PREFIX;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.rmi.RemoteException;
+import java.security.GeneralSecurityException;
+import java.security.Key;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.annotation.Nullable;
+import javax.security.auth.x500.X500Principal;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.UriBuilder;
+import javax.xml.ws.handler.MessageContext;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContext;
+import org.taverna.server.localworker.remote.ImplementationException;
+import org.taverna.server.localworker.remote.RemoteSecurityContext;
+import org.taverna.server.master.common.Credential;
+import org.taverna.server.master.common.Trust;
+import org.taverna.server.master.exceptions.FilesystemAccessException;
+import org.taverna.server.master.exceptions.InvalidCredentialException;
+import org.taverna.server.master.exceptions.NoDirectoryEntryException;
+import org.taverna.server.master.interfaces.File;
+import org.taverna.server.master.interfaces.TavernaSecurityContext;
+import org.taverna.server.master.utils.UsernamePrincipal;
+
+/**
+ * Implementation of a security context.
+ * 
+ * @author Donal Fellows
+ */
+public abstract class SecurityContextDelegate implements TavernaSecurityContext {
+	Log log = LogFactory.getLog("Taverna.Server.Worker");
+	private final UsernamePrincipal owner;
+	private final List<Credential> credentials = new ArrayList<>();
+	private final List<Trust> trusted = new ArrayList<>();
+	private final RemoteRunDelegate run;
+	private final Object lock = new Object();
+	final SecurityContextFactory factory;
+
+	private transient Keystore keystore;
+	private transient Map<URI, String> uriToAliasMap;
+
+	/**
+	 * Initialise the context delegate.
+	 * 
+	 * @param run
+	 *            What workflow run is this for?
+	 * @param owner
+	 *            Who owns the workflow run?
+	 * @param factory
+	 *            What class built this object?
+	 */
+	protected SecurityContextDelegate(RemoteRunDelegate run,
+			UsernamePrincipal owner, SecurityContextFactory factory) {
+		this.run = run;
+		this.owner = owner;
+		this.factory = factory;
+	}
+
+	@Override
+	public SecurityContextFactory getFactory() {
+		return factory;
+	}
+
+	@Override
+	public UsernamePrincipal getOwner() {
+		return owner;
+	}
+
+	@Override
+	public Credential[] getCredentials() {
+		synchronized (lock) {
+			return credentials.toArray(new Credential[credentials.size()]);
+		}
+	}
+
+	/**
+	 * Get the human-readable name of a principal.
+	 * 
+	 * @param principal
+	 *            The principal being decoded.
+	 * @return A name.
+	 */
+	protected final String getPrincipalName(X500Principal principal) {
+		return factory.x500Utils.getName(principal, CERTIFICATE_FIELD_NAMES);
+	}
+
+	/**
+	 * Cause the current state to be flushed to the database.
+	 */
+	protected final void flushToDB() {
+		factory.db.flushToDisk(run);
+	}
+
+	@Override
+	public void addCredential(Credential toAdd) {
+		synchronized (lock) {
+			int idx = credentials.indexOf(toAdd);
+			if (idx != -1)
+				credentials.set(idx, toAdd);
+			else
+				credentials.add(toAdd);
+			flushToDB();
+		}
+	}
+
+	@Override
+	public void deleteCredential(Credential toDelete) {
+		synchronized (lock) {
+			credentials.remove(toDelete);
+			flushToDB();
+		}
+	}
+
+	@Override
+	public Trust[] getTrusted() {
+		synchronized (lock) {
+			return trusted.toArray(new Trust[trusted.size()]);
+		}
+	}
+
+	@Override
+	public void addTrusted(Trust toAdd) {
+		synchronized (lock) {
+			int idx = trusted.indexOf(toAdd);
+			if (idx != -1)
+				trusted.set(idx, toAdd);
+			else
+				trusted.add(toAdd);
+			flushToDB();
+		}
+	}
+
+	@Override
+	public void deleteTrusted(Trust toDelete) {
+		synchronized (lock) {
+			trusted.remove(toDelete);
+			flushToDB();
+		}
+	}
+
+	@Override
+	public abstract void validateCredential(Credential c)
+			throws InvalidCredentialException;
+
+	@Override
+	public void validateTrusted(Trust t) throws InvalidCredentialException {
+		InputStream contentsAsStream;
+		if (t.certificateBytes != null && t.certificateBytes.length > 0) {
+			contentsAsStream = new ByteArrayInputStream(t.certificateBytes);
+			t.certificateFile = null;
+		} else if (t.certificateFile == null
+				|| t.certificateFile.trim().isEmpty())
+			throw new InvalidCredentialException(
+					"absent or empty certificateFile");
+		else {
+			contentsAsStream = contents(t.certificateFile);
+			t.certificateBytes = null;
+		}
+		t.serverName = null;
+		if (t.fileType == null || t.fileType.trim().isEmpty())
+			t.fileType = CERTIFICATE_TYPE;
+		t.fileType = t.fileType.trim();
+		try {
+			t.loadedCertificates = CertificateFactory.getInstance(t.fileType)
+					.generateCertificates(contentsAsStream);
+			t.serverName = new ArrayList<>(t.loadedCertificates.size());
+			for (Certificate c : t.loadedCertificates)
+				t.serverName.add(getPrincipalName(((X509Certificate) c)
+						.getSubjectX500Principal()));
+		} catch (CertificateException e) {
+			throw new InvalidCredentialException(e);
+		} catch (ClassCastException e) {
+			// Do nothing; truncates the list of server names
+		}
+	}
+
+	@Override
+	public void initializeSecurityFromContext(SecurityContext securityContext)
+			throws Exception {
+		// This is how to get the info from Spring Security
+		Authentication auth = securityContext.getAuthentication();
+		if (auth == null)
+			return;
+		auth.getPrincipal();
+		// do nothing else in this implementation
+	}
+
+	@Override
+	public void initializeSecurityFromSOAPContext(MessageContext context) {
+		// do nothing in this implementation
+	}
+
+	@Override
+	public void initializeSecurityFromRESTContext(HttpHeaders context) {
+		// do nothing in this implementation
+	}
+
+	private UriBuilder getUB() {
+		return factory.uriSource.getRunUriBuilder(run);
+	}
+
+	private RunDatabaseDAO getDAO() {
+		return ((RunDatabase) factory.db).dao;
+	}
+
+	@Nullable
+	private List<X509Certificate> getCerts(URI uri) throws IOException,
+			GeneralSecurityException {
+		return factory.certFetcher.getTrustsForURI(uri);
+	}
+
+	private void installLocalPasswordCredential(List<Credential> credentials,
+			List<Trust> trusts) throws InvalidCredentialException, IOException,
+			GeneralSecurityException {
+		Credential.Password pw = new Credential.Password();
+		pw.id = "run:self";
+		pw.username = PREFIX + run.id;
+		pw.password = getDAO().getSecurityToken(run.id);
+		UriBuilder ub = getUB().segment("").fragment(factory.httpRealm);
+		pw.serviceURI = ub.build();
+		validateCredential(pw);
+		log.info("issuing self-referential credential for " + pw.serviceURI);
+		credentials.add(pw);
+		List<X509Certificate> myCerts = getCerts(pw.serviceURI);
+		if (myCerts != null && myCerts.size() > 0) {
+			Trust t = new Trust();
+			t.loadedCertificates = getCerts(pw.serviceURI);
+			trusts.add(t);
+		}
+	}
+
+	/**
+	 * Builds and transfers a keystore with suitable credentials to the back-end
+	 * workflow execution engine.
+	 * 
+	 * @throws GeneralSecurityException
+	 *             If the manipulation of the keystore, keys or certificates
+	 *             fails.
+	 * @throws IOException
+	 *             If there are problems building the data (should not happen).
+	 * @throws RemoteException
+	 *             If the conveyancing fails.
+	 */
+	@Override
+	public final void conveySecurity() throws GeneralSecurityException,
+			IOException, ImplementationException {
+		RemoteSecurityContext rc = run.run.getSecurityContext();
+
+		List<Trust> trusted = new ArrayList<>(this.trusted);
+		this.trusted.clear();
+		List<Credential> credentials = new ArrayList<>(this.credentials);
+		this.credentials.clear();
+
+		try {
+			installLocalPasswordCredential(credentials, trusted);
+		} catch (Exception e) {
+			log.warn("failed to construct local credential: "
+					+ "interaction service will fail", e);
+		}
+
+		char[] password = null;
+		try {
+			password = generateNewPassword();
+
+			log.info("constructing merged keystore");
+			Truststore truststore = new Truststore(password);
+			Keystore keystore = new Keystore(password);
+			Map<URI, String> uriToAliasMap = new HashMap<>();
+			int trustedCount = 0, keyCount = 0;
+
+			synchronized (lock) {
+				try {
+					for (Trust t : trusted) {
+						if (t == null || t.loadedCertificates == null)
+							continue;
+						for (Certificate cert : t.loadedCertificates)
+							if (cert != null) {
+								truststore.addCertificate(cert);
+								trustedCount++;
+							}
+					}
+
+					this.uriToAliasMap = uriToAliasMap;
+					this.keystore = keystore;
+					for (Credential c : credentials) {
+						addCredentialToKeystore(c);
+						keyCount++;
+					}
+				} finally {
+					this.uriToAliasMap = null;
+					this.keystore = null;
+					credentials.clear();
+					trusted.clear();
+					flushToDB();
+				}
+			}
+
+			byte[] trustbytes = null, keybytes = null;
+			try {
+				trustbytes = truststore.serialize();
+				keybytes = keystore.serialize();
+
+				// Now we've built the security information, ship it off...
+
+				log.info("transfering merged truststore with " + trustedCount
+						+ " entries");
+				rc.setTruststore(trustbytes);
+
+				log.info("transfering merged keystore with " + keyCount
+						+ " entries");
+				rc.setKeystore(keybytes);
+			} finally {
+				if (trustbytes != null)
+					fill(trustbytes, (byte) 0);
+				if (keybytes != null)
+					fill(keybytes, (byte) 0);
+			}
+			rc.setPassword(password);
+
+			log.info("transferring serviceURL->alias map with "
+					+ uriToAliasMap.size() + " entries");
+			rc.setUriToAliasMap(uriToAliasMap);
+		} finally {
+			if (password != null)
+				fill(password, ' ');
+		}
+
+		synchronized (lock) {
+			conveyExtraSecuritySettings(rc);
+		}
+	}
+
+	/**
+	 * Hook that allows additional information to be conveyed to the remote run.
+	 * 
+	 * @param remoteSecurityContext
+	 *            The remote resource that information would be passed to.
+	 * @throws IOException
+	 *             If anything goes wrong with the communication.
+	 */
+	protected void conveyExtraSecuritySettings(
+			RemoteSecurityContext remoteSecurityContext) throws IOException {
+		// Does nothing by default; overrideable
+	}
+
+	/**
+	 * @return A new password with a reasonable level of randomness.
+	 */
+	protected final char[] generateNewPassword() {
+		return randomUUID().toString().toCharArray();
+	}
+
+	/**
+	 * Adds a credential to the current keystore.
+	 * 
+	 * @param alias
+	 *            The alias to create within the keystore.
+	 * @param c
+	 *            The key-pair.
+	 * @throws KeyStoreException
+	 */
+	protected final void addKeypairToKeystore(String alias, Credential c)
+			throws KeyStoreException {
+		if (c.loadedKey == null)
+			throw new KeyStoreException("critical: credential was not verified");
+		if (uriToAliasMap.containsKey(c.serviceURI))
+			log.warn("duplicate URI in alias mapping: " + c.serviceURI);
+		keystore.addKey(alias, c.loadedKey, c.loadedTrustChain);
+		uriToAliasMap.put(c.serviceURI, alias);
+	}
+
+	/**
+	 * Adds a credential to the current keystore.
+	 * 
+	 * @param c
+	 *            The credential to add.
+	 * @throws KeyStoreException
+	 */
+	public abstract void addCredentialToKeystore(Credential c)
+			throws KeyStoreException;
+
+	/**
+	 * Read a file up to {@value #FILE_SIZE_LIMIT}kB in size.
+	 * 
+	 * @param name
+	 *            The path name of the file, relative to the context run's
+	 *            working directory.
+	 * @return A stream of the file's contents.
+	 * @throws InvalidCredentialException
+	 *             If anything goes wrong.
+	 */
+	final InputStream contents(String name) throws InvalidCredentialException {
+		try {
+			File f = (File) factory.fileUtils.getDirEntry(run, name);
+			long size = f.getSize();
+			if (size > CREDENTIAL_FILE_SIZE_LIMIT * 1024)
+				throw new InvalidCredentialException(CREDENTIAL_FILE_SIZE_LIMIT
+						+ "kB limit hit");
+			return new ByteArrayInputStream(f.getContents(0, (int) size));
+		} catch (NoDirectoryEntryException | FilesystemAccessException e) {
+			throw new InvalidCredentialException(e);
+		} catch (ClassCastException e) {
+			throw new InvalidCredentialException("not a file", e);
+		}
+	}
+
+	@Override
+	public Set<String> getPermittedDestroyers() {
+		return run.getDestroyers();
+	}
+
+	@Override
+	public void setPermittedDestroyers(Set<String> destroyers) {
+		run.setDestroyers(destroyers);
+	}
+
+	@Override
+	public Set<String> getPermittedUpdaters() {
+		return run.getWriters();
+	}
+
+	@Override
+	public void setPermittedUpdaters(Set<String> updaters) {
+		run.setWriters(updaters);
+	}
+
+	@Override
+	public Set<String> getPermittedReaders() {
+		return run.getReaders();
+	}
+
+	@Override
+	public void setPermittedReaders(Set<String> readers) {
+		run.setReaders(readers);
+	}
+
+	/**
+	 * Reinstall the credentials and the trust extracted from serialization to
+	 * the database.
+	 * 
+	 * @param credentials
+	 *            The credentials to reinstall.
+	 * @param trust
+	 *            The trusted certificates to reinstall.
+	 */
+	void setCredentialsAndTrust(Credential[] credentials, Trust[] trust) {
+		synchronized (lock) {
+			this.credentials.clear();
+			if (credentials != null)
+				for (Credential c : credentials)
+					try {
+						validateCredential(c);
+						this.credentials.add(c);
+					} catch (InvalidCredentialException e) {
+						log.warn("failed to revalidate credential: " + c, e);
+					}
+			this.trusted.clear();
+			if (trust != null)
+				for (Trust t : trust)
+					try {
+						validateTrusted(t);
+						this.trusted.add(t);
+					} catch (InvalidCredentialException e) {
+						log.warn("failed to revalidate trust assertion: " + t,
+								e);
+					}
+		}
+	}
+
+	static class SecurityStore {
+		private KeyStore ks;
+		private char[] password;
+
+		SecurityStore(char[] password) throws GeneralSecurityException {
+			this.password = password.clone();
+			ks = KeyStore.getInstance("UBER", "BC");
+			try {
+				ks.load(null, this.password);
+			} catch (IOException e) {
+				throw new GeneralSecurityException(
+						"problem initializing blank truststore", e);
+			}
+		}
+
+		final synchronized void setCertificate(String alias, Certificate c)
+				throws KeyStoreException {
+			if (ks == null)
+				throw new IllegalStateException("store already written");
+			ks.setCertificateEntry(alias, c);
+		}
+
+		final synchronized void setKey(String alias, Key key, Certificate[] trustChain)
+				throws KeyStoreException {
+			if (ks == null)
+				throw new IllegalStateException("store already written");
+			ks.setKeyEntry(alias, key, password, trustChain);
+		}
+
+		final synchronized byte[] serialize(boolean logIt)
+				throws GeneralSecurityException {
+			if (ks == null)
+				throw new IllegalStateException("store already written");
+			try (ByteArrayOutputStream stream = new ByteArrayOutputStream()) {
+				ks.store(stream, password);
+				if (logIt)
+					LogFactory.getLog("Taverna.Server.Worker").debug(
+							"serialized UBER/BC truststore (size: " + ks.size()
+									+ ") with password \""
+									+ new String(password) + "\"");
+				return stream.toByteArray();
+			} catch (IOException e) {
+				throw new GeneralSecurityException(
+						"problem serializing keystore", e);
+			} finally {
+				ks = null;
+				fill(password, ' ');
+			}
+		}
+
+		@Override
+		protected final void finalize() {
+			fill(password, ' ');
+			ks = null;
+		}
+	}
+
+	/**
+	 * A trust store that can only be added to or serialized. Only trusted
+	 * certificates can be placed in it.
+	 * 
+	 * @author Donal Fellows
+	 */
+	class Truststore extends SecurityStore {
+		Truststore(char[] password) throws GeneralSecurityException {
+			super(password);
+		}
+
+		/**
+		 * Add a trusted certificate to the truststore. No certificates can be
+		 * added after the truststore is serialized.
+		 * 
+		 * @param cert
+		 *            The certificate (typically belonging to a root CA) to add.
+		 * @throws KeyStoreException
+		 *             If anything goes wrong.
+		 */
+		public void addCertificate(Certificate cert) throws KeyStoreException {
+			X509Certificate c = (X509Certificate) cert;
+			String alias = format("trustedcert#%s#%s#%s",
+					getPrincipalName(c.getSubjectX500Principal()),
+					getPrincipalName(c.getIssuerX500Principal()),
+					factory.x500Utils.getSerial(c));
+			setCertificate(alias, c);
+			if (log.isDebugEnabled() && factory.logSecurityDetails)
+				log.debug("added cert with alias \"" + alias + "\" of type "
+						+ c.getClass().getCanonicalName());
+		}
+
+		/**
+		 * Get the byte serialization of this truststore. This can only be
+		 * fetched exactly once.
+		 * 
+		 * @return The serialization.
+		 * @throws GeneralSecurityException
+		 *             If anything goes wrong.
+		 */
+		public byte[] serialize() throws GeneralSecurityException {
+			return serialize(log.isDebugEnabled() && factory.logSecurityDetails);
+		}
+	}
+
+	/**
+	 * A key store that can only be added to or serialized. Only keys can be
+	 * placed in it.
+	 * 
+	 * @author Donal Fellows
+	 */
+	class Keystore extends SecurityStore {
+		Keystore(char[] password) throws GeneralSecurityException {
+			super(password);
+		}
+
+		/**
+		 * Add a key to the keystore. No keys can be added after the keystore is
+		 * serialized.
+		 * 
+		 * @param alias
+		 *            The alias of the key.
+		 * @param key
+		 *            The secret/private key to add.
+		 * @param trustChain
+		 *            The trusted certificate chain of the key. Should be
+		 *            <tt>null</tt> for secret keys.
+		 * @throws KeyStoreException
+		 *             If anything goes wrong.
+		 */
+		public void addKey(String alias, Key key, Certificate[] trustChain)
+				throws KeyStoreException {
+			setKey(alias, key, trustChain);
+			if (log.isDebugEnabled() && factory.logSecurityDetails)
+				log.debug("added key with alias \"" + alias + "\" of type "
+						+ key.getClass().getCanonicalName());
+		}
+
+		/**
+		 * Get the byte serialization of this keystore. This can only be fetched
+		 * exactly once.
+		 * 
+		 * @return The serialization.
+		 * @throws GeneralSecurityException
+		 *             If anything goes wrong.
+		 */
+		public byte[] serialize() throws GeneralSecurityException {
+			return serialize(log.isDebugEnabled() && factory.logSecurityDetails);
+		}
+	}
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/2c71f9a9/taverna-server-webapp/src/main/java/org/taverna/server/master/worker/SecurityContextDelegateImpl.java
----------------------------------------------------------------------
diff --git a/taverna-server-webapp/src/main/java/org/taverna/server/master/worker/SecurityContextDelegateImpl.java b/taverna-server-webapp/src/main/java/org/taverna/server/master/worker/SecurityContextDelegateImpl.java
new file mode 100644
index 0000000..d36d2da
--- /dev/null
+++ b/taverna-server-webapp/src/main/java/org/taverna/server/master/worker/SecurityContextDelegateImpl.java
@@ -0,0 +1,298 @@
+/*
+ * Copyright (C) 2010-2012 The University of Manchester
+ * 
+ * See the file "LICENSE" for license terms.
+ */
+package org.taverna.server.master.worker;
+
+import static java.lang.String.format;
+import static javax.xml.ws.handler.MessageContext.HTTP_REQUEST_HEADERS;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.Charset;
+import java.rmi.RemoteException;
+import java.security.Key;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.List;
+import java.util.Map;
+
+import javax.crypto.spec.SecretKeySpec;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.xml.ws.handler.MessageContext;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.taverna.server.localworker.remote.RemoteSecurityContext;
+import org.taverna.server.master.common.Credential;
+import org.taverna.server.master.exceptions.InvalidCredentialException;
+import org.taverna.server.master.utils.UsernamePrincipal;
+import org.taverna.server.master.utils.X500Utils;
+
+/**
+ * Factoring out of the part of the security context handling that actually
+ * deals with the different types of credentials.
+ * 
+ * @author Donal Fellows
+ */
+class SecurityContextDelegateImpl extends SecurityContextDelegate {
+	private static final char USERNAME_PASSWORD_SEPARATOR = '\u0000';
+	private static final String USERNAME_PASSWORD_KEY_ALGORITHM = "DUMMY";
+	/** What passwords are encoded as. */
+	private static final Charset UTF8 = Charset.forName("UTF-8");
+
+	private X500Utils x500Utils;
+
+	/**
+	 * Initialise the context delegate.
+	 * 
+	 * @param run
+	 *            What workflow run is this for?
+	 * @param owner
+	 *            Who owns the workflow run?
+	 * @param factory
+	 *            What class built this object?
+	 */
+	protected SecurityContextDelegateImpl(RemoteRunDelegate run,
+			UsernamePrincipal owner, SecurityContextFactory factory) {
+		super(run, owner, factory);
+		this.x500Utils = factory.x500Utils;
+	}
+
+	@Override
+	public void validateCredential(Credential c)
+			throws InvalidCredentialException {
+		try {
+			if (c instanceof Credential.Password)
+				validatePasswordCredential((Credential.Password) c);
+			else if (c instanceof Credential.KeyPair)
+				validateKeyCredential((Credential.KeyPair) c);
+			else
+				throw new InvalidCredentialException("unknown credential type");
+		} catch (InvalidCredentialException e) {
+			throw e;
+		} catch (Exception e) {
+			throw new InvalidCredentialException(e);
+		}
+	}
+
+	@Override
+	public void addCredentialToKeystore(Credential c) throws KeyStoreException {
+		try {
+			if (c instanceof Credential.Password)
+				addUserPassToKeystore((Credential.Password) c);
+			else if (c instanceof Credential.KeyPair)
+				addKeypairToKeystore((Credential.KeyPair) c);
+			else
+				throw new KeyStoreException("unknown credential type");
+		} catch (KeyStoreException e) {
+			throw e;
+		} catch (Exception e) {
+			throw new KeyStoreException(e);
+		}
+	}
+
+	// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+	/**
+	 * Tests whether the given username+password credential descriptor is valid.
+	 * If it is invalid, an exception will be thrown describing what the problem
+	 * is. Validation mainly consists of listing what the username is.
+	 * 
+	 * @param passwordDescriptor
+	 *            The credential descriptor to validate.
+	 * @throws InvalidCredentialException
+	 *             If the username is empty. NB: the password may be empty!
+	 *             That's legal (if unwise).
+	 */
+	protected void validatePasswordCredential(
+			Credential.Password passwordDescriptor)
+			throws InvalidCredentialException {
+		if (passwordDescriptor.username == null
+				|| passwordDescriptor.username.trim().isEmpty())
+			throw new InvalidCredentialException("absent or empty username");
+		if (passwordDescriptor.serviceURI == null)
+			throw new InvalidCredentialException("absent service URI");
+		String keyToSave = passwordDescriptor.username
+				+ USERNAME_PASSWORD_SEPARATOR + passwordDescriptor.password;
+		passwordDescriptor.loadedKey = encodeKey(keyToSave);
+		passwordDescriptor.loadedTrustChain = null;
+	}
+
+	private static Key encodeKey(String key) {
+		return new SecretKeySpec(key.getBytes(UTF8),
+				USERNAME_PASSWORD_KEY_ALGORITHM);
+	}
+
+	/**
+	 * Adds a username/password credential pair to the current keystore.
+	 * 
+	 * @param userpassCredential
+	 *            The username and password.
+	 * @throws KeyStoreException
+	 */
+	protected void addUserPassToKeystore(Credential.Password userpassCredential)
+			throws KeyStoreException {
+		String alias = format("password#%s",
+				userpassCredential.serviceURI.toASCIIString());
+		addKeypairToKeystore(alias, userpassCredential);
+	}
+
+	// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+	/**
+	 * Tests whether the given key-pair credential descriptor is valid. If it is
+	 * invalid, an exception will be thrown describing what the problem is.
+	 * 
+	 * @param keypairDescriptor
+	 *            The descriptor to validate.
+	 * @throws InvalidCredentialException
+	 *             If the descriptor is invalid
+	 * @throws KeyStoreException
+	 *             If we don't understand the keystore type or the contents of
+	 *             the keystore
+	 * @throws NoSuchAlgorithmException
+	 *             If the keystore is of a known type but we can't comprehend
+	 *             its security
+	 * @throws CertificateException
+	 *             If the keystore does not include enough information about the
+	 *             trust chain of the keypair
+	 * @throws UnrecoverableKeyException
+	 *             If we can't get the key out of the keystore
+	 * @throws IOException
+	 *             If we can't read the keystore for prosaic reasons (e.g., file
+	 *             absent)
+	 */
+	protected void validateKeyCredential(Credential.KeyPair keypairDescriptor)
+			throws InvalidCredentialException, KeyStoreException,
+			NoSuchAlgorithmException, CertificateException, IOException,
+			UnrecoverableKeyException {
+		if (keypairDescriptor.credentialName == null
+				|| keypairDescriptor.credentialName.trim().isEmpty())
+			throw new InvalidCredentialException(
+					"absent or empty credentialName");
+
+		InputStream contentsAsStream;
+		if (keypairDescriptor.credentialBytes != null
+				&& keypairDescriptor.credentialBytes.length > 0) {
+			contentsAsStream = new ByteArrayInputStream(
+					keypairDescriptor.credentialBytes);
+			keypairDescriptor.credentialFile = null;
+		} else if (keypairDescriptor.credentialFile == null
+				|| keypairDescriptor.credentialFile.trim().isEmpty())
+			throw new InvalidCredentialException(
+					"absent or empty credentialFile");
+		else {
+			contentsAsStream = contents(keypairDescriptor.credentialFile);
+			keypairDescriptor.credentialBytes = new byte[0];
+		}
+		if (keypairDescriptor.fileType == null
+				|| keypairDescriptor.fileType.trim().isEmpty())
+			keypairDescriptor.fileType = KeyStore.getDefaultType();
+		keypairDescriptor.fileType = keypairDescriptor.fileType.trim();
+
+		KeyStore ks = KeyStore.getInstance(keypairDescriptor.fileType);
+		char[] password = keypairDescriptor.unlockPassword.toCharArray();
+		ks.load(contentsAsStream, password);
+
+		try {
+			keypairDescriptor.loadedKey = ks.getKey(
+					keypairDescriptor.credentialName, password);
+		} catch (UnrecoverableKeyException ignored) {
+			keypairDescriptor.loadedKey = ks.getKey(
+					keypairDescriptor.credentialName, new char[0]);
+		}
+		if (keypairDescriptor.loadedKey == null)
+			throw new InvalidCredentialException(
+					"no such credential in key store");
+		keypairDescriptor.loadedTrustChain = ks
+				.getCertificateChain(keypairDescriptor.credentialName);
+		if (keypairDescriptor.loadedTrustChain == null
+				|| keypairDescriptor.loadedTrustChain.length == 0)
+			throw new InvalidCredentialException(
+					"could not establish trust chain for credential");
+	}
+
+	/**
+	 * Adds a key-pair to the current keystore.
+	 * 
+	 * @param c
+	 *            The key-pair.
+	 * @throws KeyStoreException
+	 */
+	protected void addKeypairToKeystore(Credential.KeyPair c)
+			throws KeyStoreException {
+		X509Certificate subjectCert = (X509Certificate) c.loadedTrustChain[0];
+		String alias = format("keypair#%s#%s#%s",
+				getPrincipalName(subjectCert.getSubjectX500Principal()),
+				getPrincipalName(subjectCert.getIssuerX500Principal()),
+				x500Utils.getSerial(subjectCert));
+		addKeypairToKeystore(alias, c);
+	}
+}
+
+/**
+ * Special subclass that adds support for HELIO project security tokens.
+ * 
+ * @author Donal Fellows
+ */
+class HelioSecurityContextDelegateImpl extends SecurityContextDelegateImpl {
+	/**
+	 * Initialise the context delegate.
+	 * 
+	 * @param run
+	 *            What workflow run is this for?
+	 * @param owner
+	 *            Who owns the workflow run?
+	 * @param factory
+	 *            What class built this object?
+	 */
+	protected HelioSecurityContextDelegateImpl(RemoteRunDelegate run,
+			UsernamePrincipal owner, SecurityContextFactory factory) {
+		super(run, owner, factory);
+	}
+
+	private Log log = LogFactory.getLog("Taverna.Server.Worker");
+	/** The name of the HTTP header holding the CIS token. */
+	private static final String HELIO_CIS_TOKEN = "X-Helio-CIS";
+	private transient String helioToken;
+
+	@Override
+	public void initializeSecurityFromSOAPContext(MessageContext context) {
+		// does nothing
+		@SuppressWarnings("unchecked")
+		Map<String, List<String>> headers = (Map<String, List<String>>) context
+				.get(HTTP_REQUEST_HEADERS);
+		if (factory.supportHelioToken && headers.containsKey(HELIO_CIS_TOKEN))
+			helioToken = headers.get(HELIO_CIS_TOKEN).get(0);
+	}
+
+	@Override
+	public void initializeSecurityFromRESTContext(HttpHeaders context) {
+		// does nothing
+		MultivaluedMap<String, String> headers = context.getRequestHeaders();
+		if (factory.supportHelioToken && headers.containsKey(HELIO_CIS_TOKEN))
+			helioToken = headers.get(HELIO_CIS_TOKEN).get(0);
+	}
+
+	@Override
+	protected void conveyExtraSecuritySettings(RemoteSecurityContext rc)
+			throws RemoteException {
+		try {
+			if (factory.supportHelioToken && helioToken != null) {
+				if (factory.logSecurityDetails)
+					log.info("transfering HELIO CIS token: " + helioToken);
+				rc.setHelioToken(helioToken);
+			}
+		} finally {
+			helioToken = null;
+		}
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/2c71f9a9/taverna-server-webapp/src/main/java/org/taverna/server/master/worker/SecurityContextFactory.java
----------------------------------------------------------------------
diff --git a/taverna-server-webapp/src/main/java/org/taverna/server/master/worker/SecurityContextFactory.java b/taverna-server-webapp/src/main/java/org/taverna/server/master/worker/SecurityContextFactory.java
new file mode 100644
index 0000000..cbccf34
--- /dev/null
+++ b/taverna-server-webapp/src/main/java/org/taverna/server/master/worker/SecurityContextFactory.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2011-2012 The University of Manchester
+ * 
+ * See the file "LICENSE" for license terms.
+ */
+package org.taverna.server.master.worker;
+
+import static java.security.Security.addProvider;
+import static java.security.Security.getProvider;
+import static java.security.Security.removeProvider;
+import static org.apache.commons.logging.LogFactory.getLog;
+import static org.bouncycastle.jce.provider.BouncyCastleProvider.PROVIDER_NAME;
+
+import java.io.Serializable;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
+
+import org.apache.commons.logging.Log;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.springframework.beans.factory.annotation.Required;
+import org.springframework.beans.factory.annotation.Value;
+import org.taverna.server.master.interfaces.TavernaRun;
+import org.taverna.server.master.interfaces.UriBuilderFactory;
+import org.taverna.server.master.utils.CertificateChainFetcher;
+import org.taverna.server.master.utils.FilenameUtils;
+import org.taverna.server.master.utils.UsernamePrincipal;
+import org.taverna.server.master.utils.X500Utils;
+
+/**
+ * Singleton factory. Really is a singleton (and is also very trivial); the
+ * singleton-ness is just about limiting the number of instances of this around
+ * even when lots of serialization is going on.
+ * 
+ * @see Serializable
+ * @author Donal Fellows
+ */
+public class SecurityContextFactory implements
+		org.taverna.server.master.interfaces.SecurityContextFactory {
+	private static final long serialVersionUID = 12345678987654321L;
+	private static SecurityContextFactory instance;
+	transient RunDBSupport db;
+	transient FilenameUtils fileUtils;
+	transient X500Utils x500Utils;
+	transient UriBuilderFactory uriSource;
+	transient CertificateChainFetcher certFetcher;
+	transient String httpRealm;
+	private transient PasswordIssuer passwordIssuer;
+	private transient BouncyCastleProvider provider;
+
+	/**
+	 * Whether to support HELIO CIS tokens.
+	 */
+	@Value("${helio.cis.enableTokenPassing}")
+	boolean supportHelioToken;
+
+	/**
+	 * Whether to log the details of security (passwords, etc).
+	 */
+	@Value("${log.security.details}")
+	boolean logSecurityDetails;
+
+	private Log log() {
+		return getLog("Taverna.Server.Worker.Security");
+	}
+
+	private void installAsInstance(SecurityContextFactory handle) {
+		instance = handle;
+	}
+
+	@PreDestroy
+	void removeAsSingleton() {
+		installAsInstance(null);
+		try {
+			if (provider != null)
+				removeProvider(provider.getName());
+		} catch (SecurityException e) {
+			log().warn(
+					"failed to remove BouncyCastle security provider; "
+							+ "might be OK if configured in environment", e);
+		}
+	}
+
+	@PostConstruct
+	void setAsSingleton() {
+		installAsInstance(this);
+		if (getProvider(PROVIDER_NAME) == null)
+			try {
+				provider = new BouncyCastleProvider();
+				if (addProvider(provider) == -1)
+					provider = null;
+			} catch (SecurityException e) {
+				log().warn(
+						"failed to install BouncyCastle security provider; "
+								+ "might be OK if already configured", e);
+				provider = null;
+			}
+	}
+
+	@Required
+	public void setRunDatabase(RunDBSupport db) {
+		this.db = db;
+	}
+
+	@Required
+	public void setCertificateFetcher(CertificateChainFetcher fetcher) {
+		this.certFetcher = fetcher;
+	}
+
+	@Required
+	public void setFilenameConverter(FilenameUtils fileUtils) {
+		this.fileUtils = fileUtils;
+	}
+
+	@Required
+	public void setX500Utils(X500Utils x500Utils) {
+		this.x500Utils = x500Utils;
+	}
+
+	@Required
+	public void setUriSource(UriBuilderFactory uriSource) {
+		this.uriSource = uriSource;
+	}
+
+	@Required
+	public void setHttpRealm(String realm) {
+		this.httpRealm = realm; //${http.realmName}
+	}
+
+	@Required
+	public void setPasswordIssuer(PasswordIssuer issuer) {
+		this.passwordIssuer = issuer;
+	}
+
+	@Override
+	public SecurityContextDelegate create(TavernaRun run,
+			UsernamePrincipal owner) throws Exception {
+		Log log = log();
+		if (log.isDebugEnabled())
+			log.debug("constructing security context delegate for " + owner);
+		RemoteRunDelegate rrd = (RemoteRunDelegate) run;
+		return new HelioSecurityContextDelegateImpl(rrd, owner, this);
+	}
+
+	private Object readResolve() {
+		if (instance == null)
+			installAsInstance(this);
+		return instance;
+	}
+
+	public String issueNewPassword() {
+		return passwordIssuer.issue();
+	}
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/2c71f9a9/taverna-server-webapp/src/main/java/org/taverna/server/master/worker/SimpleFormattedCompletionNotifier.java
----------------------------------------------------------------------
diff --git a/taverna-server-webapp/src/main/java/org/taverna/server/master/worker/SimpleFormattedCompletionNotifier.java b/taverna-server-webapp/src/main/java/org/taverna/server/master/worker/SimpleFormattedCompletionNotifier.java
new file mode 100644
index 0000000..793d291
--- /dev/null
+++ b/taverna-server-webapp/src/main/java/org/taverna/server/master/worker/SimpleFormattedCompletionNotifier.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2010-2011 The University of Manchester
+ * 
+ * See the file "LICENSE" for license terms.
+ */
+package org.taverna.server.master.worker;
+
+import static org.taverna.server.master.defaults.Default.NOTIFY_MESSAGE_FORMAT;
+
+import java.text.MessageFormat;
+
+import org.springframework.beans.factory.annotation.Required;
+
+/**
+ * Completion notifier that sends messages by email.
+ * 
+ * @author Donal Fellows
+ */
+public class SimpleFormattedCompletionNotifier implements CompletionNotifier {
+	@Required
+	public void setName(String name) {
+		this.name = name;
+	}
+
+	/**
+	 * @param subject
+	 *            The subject of the notification email.
+	 */
+	@Required
+	public void setSubject(String subject) {
+		this.subject = subject;
+	}
+
+	/**
+	 * @param messageFormat
+	 *            The template for the body of the message to send. Parameter #0
+	 *            will be substituted with the ID of the job, and parameter #1
+	 *            will be substituted with the exit code.
+	 */
+	public void setMessageFormat(String messageFormat) {
+		this.format = new MessageFormat(messageFormat);
+	}
+
+	private String name;
+	private String subject;
+	private MessageFormat format = new MessageFormat(NOTIFY_MESSAGE_FORMAT);
+
+	@Override
+	public String makeCompletionMessage(String name, RemoteRunDelegate run,
+			int code) {
+		return format.format(new Object[] { name, code });
+	}
+
+	@Override
+	public String makeMessageSubject(String name, RemoteRunDelegate run,
+			int code) {
+		return subject;
+	}
+
+	@Override
+	public String getName() {
+		return name;
+	}
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/2c71f9a9/taverna-server-webapp/src/main/java/org/taverna/server/master/worker/VelocityCompletionNotifier.java
----------------------------------------------------------------------
diff --git a/taverna-server-webapp/src/main/java/org/taverna/server/master/worker/VelocityCompletionNotifier.java b/taverna-server-webapp/src/main/java/org/taverna/server/master/worker/VelocityCompletionNotifier.java
new file mode 100644
index 0000000..cf67853
--- /dev/null
+++ b/taverna-server-webapp/src/main/java/org/taverna/server/master/worker/VelocityCompletionNotifier.java
@@ -0,0 +1,105 @@
+package org.taverna.server.master.worker;
+
+import java.io.StringWriter;
+
+import org.apache.velocity.Template;
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.app.VelocityEngine;
+import org.springframework.beans.factory.annotation.Required;
+import org.taverna.server.master.common.version.Version;
+import org.taverna.server.master.exceptions.NoListenerException;
+import org.taverna.server.master.interfaces.Listener;
+import org.taverna.server.master.interfaces.UriBuilderFactory;
+
+public class VelocityCompletionNotifier implements CompletionNotifier {
+	private String subject;
+	private VelocityEngine engine;
+	private Template template;
+	private String name;
+	private String templateName;
+	private UriBuilderFactory ubf;
+
+	@Override
+	public String getName() {
+		return name;
+	}
+
+	/**
+	 * @param subject
+	 *            The subject of the notification email.
+	 */
+	@Required
+	public void setSubject(String subject) {
+		this.subject = subject;
+	}
+
+	/**
+	 * @param engine
+	 *            The configured Apache Velocity engine.
+	 */
+	@Required
+	public void setVelocityEngine(VelocityEngine engine) {
+		this.engine = engine;
+	}
+
+	/**
+	 * @param uriBuilderFactory
+	 *            The configured URI builder factory.
+	 */
+	@Required
+	public void setUriBuilderFactory(UriBuilderFactory uriBuilderFactory) {
+		this.ubf = uriBuilderFactory;
+	}
+
+	/**
+	 * @param name
+	 *            The name of the template.
+	 */
+	@Required
+	public void setName(String name) {
+		this.name = name;
+		this.templateName = getClass().getName() + "_" + name + ".vtmpl";
+	}
+
+	private Template getTemplate() {
+		if (template == null)
+			synchronized(this) {
+				if (template == null)
+					template = engine.getTemplate(templateName);
+			}
+		return template;
+	}
+
+	@Override
+	public String makeCompletionMessage(String name, RemoteRunDelegate run,
+			int code) {
+		VelocityContext ctxt = new VelocityContext();
+		ctxt.put("id", name);
+		ctxt.put("uriBuilder", ubf.getRunUriBuilder(run));
+		ctxt.put("name", run.getName());
+		ctxt.put("creationTime", run.getCreationTimestamp());
+		ctxt.put("startTime", run.getStartTimestamp());
+		ctxt.put("finishTime", run.getFinishTimestamp());
+		ctxt.put("expiryTime", run.getExpiry());
+		ctxt.put("serverVersion", Version.JAVA);
+		for (Listener l : run.getListeners())
+			if (l.getName().equals("io")) {
+				for (String p : l.listProperties())
+					try {
+						ctxt.put("prop_" + p, l.getProperty(p));
+					} catch (NoListenerException e) {
+						// Ignore...
+					}
+				break;
+			}
+		StringWriter sw = new StringWriter();
+		getTemplate().merge(ctxt, sw);
+		return sw.toString();
+	}
+
+	@Override
+	public String makeMessageSubject(String name, RemoteRunDelegate run,
+			int code) {
+		return subject;
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/2c71f9a9/taverna-server-webapp/src/main/java/org/taverna/server/master/worker/WorkerModel.java
----------------------------------------------------------------------
diff --git a/taverna-server-webapp/src/main/java/org/taverna/server/master/worker/WorkerModel.java b/taverna-server-webapp/src/main/java/org/taverna/server/master/worker/WorkerModel.java
new file mode 100644
index 0000000..1abe617
--- /dev/null
+++ b/taverna-server-webapp/src/main/java/org/taverna/server/master/worker/WorkerModel.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2010-2013 The University of Manchester
+ * 
+ * See the file "LICENSE" for license terms.
+ */
+package org.taverna.server.master.worker;
+
+import java.net.URI;
+import java.util.List;
+
+import org.taverna.server.master.common.Status;
+
+/**
+ * Profile of the getters and setters in a worker system. Ensures that the
+ * persisted state matches the public view on the state model at least fairly
+ * closely.
+ * 
+ * @author Donal Fellows
+ */
+public interface WorkerModel extends PolicyLimits {
+
+	/**
+	 * @param defaultLifetime
+	 *            how long a workflow run should live by default, in minutes.
+	 */
+	public abstract void setDefaultLifetime(int defaultLifetime);
+
+	/**
+	 * @return how long a workflow run should live by default, in minutes.
+	 */
+	public abstract int getDefaultLifetime();
+
+	/**
+	 * @param maxRuns
+	 *            the maximum number of extant workflow runs
+	 */
+	public abstract void setMaxRuns(int maxRuns);
+
+	/**
+	 * @param factoryProcessNamePrefix
+	 *            the prefix used for factory processes in RMI
+	 */
+	public abstract void setFactoryProcessNamePrefix(
+			String factoryProcessNamePrefix);
+
+	/**
+	 * @return the prefix used for factory processes in RMI
+	 */
+	public abstract String getFactoryProcessNamePrefix();
+
+	/**
+	 * @param executeWorkflowScript
+	 *            the script to run to actually run a workflow
+	 */
+	public abstract void setExecuteWorkflowScript(String executeWorkflowScript);
+
+	/**
+	 * @return the script to run to actually run a workflow
+	 */
+	public abstract String getExecuteWorkflowScript();
+
+	/**
+	 * @param extraArgs
+	 *            the extra arguments to pass into the workflow runner
+	 */
+	public abstract void setExtraArgs(String[] extraArgs);
+
+	/**
+	 * @return the extra arguments to pass into the workflow runner
+	 */
+	public abstract String[] getExtraArgs();
+
+	/**
+	 * @param waitSeconds
+	 *            the number of seconds to wait for subprocesses to start
+	 */
+	public abstract void setWaitSeconds(int waitSeconds);
+
+	/**
+	 * @return the number of seconds to wait for subprocesses to start
+	 */
+	public abstract int getWaitSeconds();
+
+	/**
+	 * @param sleepMS
+	 *            milliseconds to wait between polling for a started
+	 *            subprocess's status
+	 */
+	public abstract void setSleepMS(int sleepMS);
+
+	/**
+	 * @return milliseconds to wait between polling for a started subprocess's
+	 *         status
+	 */
+	public abstract int getSleepMS();
+
+	/**
+	 * @param serverWorkerJar
+	 *            the full path name of the file system access worker
+	 *            subprocess's implementation JAR
+	 */
+	public abstract void setServerWorkerJar(String serverWorkerJar);
+
+	/**
+	 * @return the full path name of the file system access worker subprocess's
+	 *         implementation JAR
+	 */
+	public abstract String getServerWorkerJar();
+
+	/**
+	 * @param javaBinary
+	 *            the full path name to the Java binary to use
+	 */
+	public abstract void setJavaBinary(String javaBinary);
+
+	/**
+	 * @return the full path name to the Java binary to use
+	 */
+	public abstract String getJavaBinary();
+
+	/**
+	 * @param registryPort
+	 *            what port is the RMI registry on
+	 */
+	public abstract void setRegistryPort(int registryPort);
+
+	/**
+	 * @return what port is the RMI registry on
+	 */
+	public abstract int getRegistryPort();
+
+	/**
+	 * @param registryHost
+	 *            what host (network interface) is the RMI registry on
+	 */
+	public abstract void setRegistryHost(String registryHost);
+
+	/**
+	 * @return what host (network interface) is the RMI registry on
+	 */
+	public abstract String getRegistryHost();
+
+	/**
+	 * @param serverForkerJar
+	 *            the full path name of the impersonation engine's
+	 *            implementation JAR
+	 */
+	public abstract void setServerForkerJar(String serverForkerJar);
+
+	/**
+	 * @return the full path name of the impersonation engine's implementation
+	 *         JAR
+	 */
+	public abstract String getServerForkerJar();
+
+	/**
+	 * @param passwordFile
+	 *            the full path name of a file containing a password to use with
+	 *            sudo (or empty for none)
+	 */
+	public abstract void setPasswordFile(String passwordFile);
+
+	/**
+	 * @return the full path name of a file containing a password to use with
+	 *         sudo (or empty for none)
+	 */
+	public abstract String getPasswordFile();
+
+	/**
+	 * @param operatingLimit
+	 *            the maximum number of runs in the
+	 *            {@linkplain Status#Operating operating} state at once
+	 */
+	public abstract void setOperatingLimit(int operatingLimit);
+
+	@Override
+	void setPermittedWorkflowURIs(List<URI> permittedWorkflows);
+
+	/**
+	 * @return the full path name of the RMI registry subprocess's
+	 *         implementation JAR
+	 */
+	String getRegistryJar();
+
+	/**
+	 * @param rmiRegistryJar
+	 *            the full path name of the RMI registry subprocess's
+	 *            implementation JAR
+	 */
+	void setRegistryJar(String rmiRegistryJar);
+
+	/**
+	 * @return whether a run should generate provenance information by default
+	 */
+	boolean getGenerateProvenance();
+
+	/**
+	 * @param generateProvenance
+	 *            whether a run should generate provenance information by
+	 *            default
+	 */
+	void setGenerateProvenance(boolean generateProvenance);
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/2c71f9a9/taverna-server-webapp/src/main/java/org/taverna/server/master/worker/package-info.java
----------------------------------------------------------------------
diff --git a/taverna-server-webapp/src/main/java/org/taverna/server/master/worker/package-info.java b/taverna-server-webapp/src/main/java/org/taverna/server/master/worker/package-info.java
new file mode 100644
index 0000000..6007f88
--- /dev/null
+++ b/taverna-server-webapp/src/main/java/org/taverna/server/master/worker/package-info.java
@@ -0,0 +1,10 @@
+/*
+ * Copyright (C) 2013 The University of Manchester
+ * 
+ * See the file "LICENSE" for license terms.
+ */
+/**
+ * A Taverna Server back-end that works by forking off workflow executors.
+ */
+package org.taverna.server.master.worker;
+

http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/2c71f9a9/taverna-server-webapp/src/main/replacementscripts/executeworkflow.bat
----------------------------------------------------------------------
diff --git a/taverna-server-webapp/src/main/replacementscripts/executeworkflow.bat b/taverna-server-webapp/src/main/replacementscripts/executeworkflow.bat
new file mode 100644
index 0000000..c678855
--- /dev/null
+++ b/taverna-server-webapp/src/main/replacementscripts/executeworkflow.bat
@@ -0,0 +1,25 @@
+@ECHO OFF
+
+REM Taverna startup script
+
+REM distribution directory
+set TAVERNA_HOME=%~dp0
+
+REM 300 MB memory, 140 MB for classes
+set ARGS=-Xmx300m -XX:MaxPermSize=140m
+
+REM Taverna system properties
+set ARGS=%ARGS% "-Draven.profile=file:%TAVERNA_HOME%conf/current-profile.xml"
+set ARGS=%ARGS% -Djava.system.class.loader=net.sf.taverna.raven.prelauncher.BootstrapClassLoader 
+set ARGS=%ARGS% -Draven.launcher.app.main=net.sf.taverna.t2.commandline.CommandLineLauncher
+set ARGS=%ARGS% -Draven.launcher.show_splashscreen=false
+set ARGS=%ARGS% -Djava.awt.headless=true
+set ARGS=%ARGS% "-Dtaverna.startup=%TAVERNA_HOME%."
+IF NOT x%RAVEN_APPHOME%==x SET ARGS=%ARGS% "-Draven.launcher.app.home=%RAVEN_APPHOME%"
+IF NOT x%TAVERNA_RUN_ID%==x SET ARGS=%ARGS% "-Dtaverna.runid=%TAVERNA_RUN_ID%"
+IF NOT x%INTERACTION_HOST%==x SET ARGS=%ARGS% "-Dtaverna.interaction.host=%INTERACTION_HOST%"
+IF NOT x%INTERACTION_PORT%==x SET ARGS=%ARGS% "-Dtaverna.interaction.port=%INTERACTION_PORT%"
+IF NOT x%INTERACTION_WEBDAV%==x SET ARGS=%ARGS% "-Dtaverna.interaction.webdav_path=%INTERACTION_WEBDAV%"
+IF NOT x%INTERACTION_FEED%==x SET ARGS=%ARGS% "-Dtaverna.interaction.feed_path=%INTERACTION_FEED%"
+
+java %ARGS% -jar "%TAVERNA_HOME%lib\prelauncher-2.3.jar" %*

http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/2c71f9a9/taverna-server-webapp/src/main/replacementscripts/executeworkflow.sh
----------------------------------------------------------------------
diff --git a/taverna-server-webapp/src/main/replacementscripts/executeworkflow.sh b/taverna-server-webapp/src/main/replacementscripts/executeworkflow.sh
new file mode 100644
index 0000000..e9e1d36
--- /dev/null
+++ b/taverna-server-webapp/src/main/replacementscripts/executeworkflow.sh
@@ -0,0 +1,72 @@
+#!/bin/sh
+
+set -e
+
+# 300 MB memory, 140 MB for classes
+memlimit=-Xmx300m
+permsize=-XX:MaxPermSize=140m
+
+## Parse the command line to extract the pieces to move around to before or
+## after the JAR filename...
+pre=-Djava.awt.headless=true 
+post=
+for arg
+do
+    case $arg in
+	-JXmx*) memlimit=`echo $arg | sed 's/-JX/-X/'` ;;
+	-JXX:MaxPermSize=*) permsize=`echo $arg | sed 's/-JXX/-XX/'` ;;
+	-J*) pre="$pre `echo $arg | sed 's/-J/-/'`" ;;
+	-D*) pre="$pre $arg" ;;
+	*) post="$post \"$arg\"" ;;
+    esac
+done
+if test "xx" = "x${post}x"; then
+    echo "Missing arguments! Bug in argument processing?" >&2
+    exit 1
+fi
+eval set x $post
+shift
+
+## resolve links - $0 may be a symlink
+prog="$0"
+
+real_path() {
+    readlink -m "$1" 2>/dev/null || python -c 'import os,sys;print os.path.realpath(sys.argv[1])' "$1"
+}
+
+realprog=`real_path "$prog"`
+taverna_home=`dirname "$realprog"`
+javabin=java
+if test -x "$JAVA_HOME/bin/java"; then
+    javabin="$JAVA_HOME/bin/java"
+fi
+APPHOME_PROP= 
+if test x != "x$TAVERNA_APPHOME"; then
+    APPHOME_PROP="-Dtaverna.app.home=$TAVERNA_APPHOME"
+fi
+RUNID_PROP= 
+if test x != "x$TAVERNA_RUN_ID"; then
+    RUNID_PROP="-Dtaverna.runid=$TAVERNA_RUN_ID"
+fi
+INTERACTION_PROPS=-Dtaverna.interaction.ignore_requests=true
+if test x != "x$INTERACTION_HOST"; then
+    INTERACTION_PROPS="$INTERACTION_PROPS -Dtaverna.interaction.host=$INTERACTION_HOST"
+    INTERACTION_PROPS="$INTERACTION_PROPS -Dtaverna.interaction.port=$INTERACTION_PORT"
+    INTERACTION_PROPS="$INTERACTION_PROPS -Dtaverna.interaction.webdav_path=$INTERACTION_WEBDAV"
+    INTERACTION_PROPS="$INTERACTION_PROPS -Dtaverna.interaction.feed_path=$INTERACTION_FEED"
+    if test x != "x$INTERACTION_PUBLISH"; then
+    	INTERACTION_PROPS="$INTERACTION_PROPS -Dtaverna.interaction.publishAddressOverride=$INTERACTION_PUBLISH"
+    fi
+fi
+
+MainClass=net.sf.taverna.t2.commandline.CommandLineLauncher
+
+echo "pid:$$"
+exec "$javabin" $memlimit $permsize \
+  "-Dlog4j.configuration=file://$taverna_home/conf/log4j.properties " \
+  "-Djava.util.logging.config.file=$taverna_home/conf/logging.properties " \
+  "-Dtaverna.app.startup=$taverna_home" -Dtaverna.interaction.ignore_requests=true \
+  $APPHOME_PROP $RUNID_PROP $INTERACTION_PROPS -Djava.awt.headless=true \
+  -Dcom.sun.net.ssl.enableECC=false -Djsse.enableSNIExtension=false $pre \
+  -jar "$taverna_home/lib/taverna-command-line-0.1.1.jar" \
+  ${1+"$@"}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/2c71f9a9/taverna-server-webapp/src/main/resources/admin.html
----------------------------------------------------------------------
diff --git a/taverna-server-webapp/src/main/resources/admin.html b/taverna-server-webapp/src/main/resources/admin.html
new file mode 100644
index 0000000..a80a783
--- /dev/null
+++ b/taverna-server-webapp/src/main/resources/admin.html
@@ -0,0 +1,240 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+<title>Taverna Server ${project.version} Administration Interface</title>
+<link id="admin" href="admin" />
+<script type="text/javascript" src="admin/static/jquery-1.8.0.min.js"></script>
+<script type="text/javascript" src="admin/static/jquery-ui-1.8.23.custom.min.js"></script>
+<script type="text/javascript" src="admin/static/admin.js"></script>
+<link href="admin/static/jquery-ui-1.8.23.custom.css" rel="stylesheet" type="text/css" />
+
+</head>
+<body>
+<img height="70" style="float:left" src="admin/static/t2cogs.png">
+<h1>Taverna Server ${project.version} Administration Interface</h1>
+<br clear="left"/>
+<div id="body">
+<ul>
+  <li><a href="#t-global">Global Settings</a></li>
+  <li><a href="#t-users">Users</a></li>
+  <li><a href="#t-workflows">Workflows</a></li>
+  <li><a href="#t-usage">Usage Records</a></li>
+  <li><a href="#t-worker">Local Worker Configuration</a></li>
+</ul>
+  
+<div id="t-global">
+<label title="The number of invocations of the main interface webapp that have been done. Be aware that one service call can result in many invocations due to resource resolution." for="invokationCount">Invocation Count:</label>
+<span title="The number of invocations of the main interface webapp that have been done. Be aware that one service call can result in many invocations due to resource resolution." id="invokationCount">0</span>
+<br>
+<label title="The number of runs that currently exist." for="runCount">Run Count:</label>
+<span title="The number of runs that currently exist." id="runCount">0</span>
+<br>
+<label title="The number of runs that are currently operating." for="operatingCount">Operating Run Count:</label>
+<span title="The number of runs that are currently operating." id="operatingCount">0</span>
+<br>
+<label title="Whether workflow runs should create provenance traces by default. Users can explicitly override this." for="generateProvenance">Generate Provenance by Default</label>
+<input type="checkbox" id="generateProvenance" />
+<br>
+<label title="The time it took for the back-end engine to start up, in seconds. Should usually be short." for="startupTime">Back-End Startup Time (seconds):</label>
+<span title="The time it took for the back-end engine to start up, in seconds. Should usually be short." id="startupTime">0</span>
+<br>
+<label title="The exit code from the last time the back-end was shut down. Blank if the back end has never been shut down while the current webapp instance is running (i.e., since the last boot of the container)." for="lastExitCode">Back-End Last Exit Code:</label>
+<span title="The exit code from the last time the back-end was shut down. Blank if the back end has never been shut down while the current webapp instance is running (i.e., since the last boot of the container)." id="lastExitCode"></span>
+<p>
+<label title="Whether new workflow runs should be created. Disabling this does not prevent existing runs from executing." for="allowNew">Allow New Runs</label>
+<input type="checkbox" id="allowNew" />
+<label title="Whether to record the workflows being run by users. Very noisy due to length of workflow documents, occasionally useful." for="logWorkflows">Log Executed Workflows</label>
+<input type="checkbox" id="logWorkflows" />
+<label title="Whether to record exceptions generated by users in the code (as well as converting them to faults and error responses). Useful for debugging, but noisy." for="logFaults">Log User Exceptions</label>
+<input type="checkbox" id="logFaults" />
+<p>
+<label title="The maximum number of workflow runs that can exist at once, in any state." for="runLimit">Maximum Simultaneous Existing Workflow Runs</label>
+<input title="The maximum number of workflow runs that can exist at once, in any state." id="runLimit" size="3" />
+<br>
+<label title="The maximum number of workflow runs that can be executing at once." for="operatingLimit">Maximum Simultaneous Executing Workflow Runs</label>
+<input title="The maximum number of workflow runs that can be executing at once." id="operatingLimit" size="3" />
+<br>
+<label title="How long to allow a workflow to execute for by default (clients can change this), in minutes." for="defaultLifetime">Default Run Lifetime (minutes)</label>
+<input title="How long to allow a workflow to execute for by default (clients can change this), in minutes." id="defaultLifetime" size="7" />
+</div><!-- t-global -->
+
+<div id="t-users">
+<table id="userList">
+  <tr><th>Username<th>System Username</tr>
+</table>
+<h3>Add a user</h3>
+<table border=1>
+  <tr>
+    <td><label title="The user name to create." for="newUsername">Username</label>
+    <td><input title="The user name to create." size=12 id="newUsername" />
+  </tr>
+  <tr>
+    <td><label title="The password to use for the user." for="newPassword">Password</label>
+    <td><input title="The password to use for the user." size=12 id="newPassword" type="password"/>
+  </tr>
+  <tr>
+    <td><label title="The system account to run the user's workflows in; leave blank for the default." for="newSysID">System ID</label>
+    <td><input title="The system account to run the user's workflows in; leave blank for the default." size=12 id="newSysID" />
+  </tr>
+  <tr><td colspan=2>
+    <label title="Whether to allow this user to log in at all." for="newEnabled">Enabled</label>
+    <input type="checkbox" id="newEnabled" />
+    <label title="Whether the user has administrative privileges (can see all workflow runs, can access the administration page)." for="newAdmin">Admin</label>
+    <input type="checkbox" id="newAdmin" />
+  </td></tr>
+  <tr><td colspan=2>
+    <button id="makeNewUser">Create a new user</button>
+  </td></tr>
+</table>
+</div><!-- t-users -->
+
+<div id="t-workflows">
+<label title="Workflow URIs to limit execution to." for="workflows">Workflow URIs (one per line)</label>
+<br>
+<textarea title="Workflow URIs to limit execution to." rows="5" cols="60" id="workflows"></textarea>
+<p>
+<button id="saveWorkflows">Save</button> <button id="refreshWorkflows">Refresh</button> <button id="emptyWorkflows">Empty URIs list</button>
+</div>
+
+<div id="t-usage">
+Download <a href="#" id="ur">usage records</a> (warning: may be slow!)
+<p>
+<label title="The name of a file to write usage records to. Note that this file will end up containing many XML documents concatenated together; it is up to you to split them up as necessary. Each record is only written as it is generated; this does not produce historic data." for="usageRecordDumpFile">Usage Record Dump File</label>
+<input title="The name of a file to write usage records to. Note that this file will end up containing many XML documents concatenated together; it is up to you to split them up as necessary. Each record is only written as it is generated; this does not produce historic data." id="usageRecordDumpFile" size="50" />
+</div><!-- t-usage -->
+
+<div id="t-worker">
+  <div id="a-worker">
+
+    <h3><a href="#">Subprocess Implementation Control</a></h3>
+    <div>
+      <table>
+	<tr>
+	  <td> <label title="The full path of the Java executable to use. Normally set correct by default." for="javaBinary">Java Executable (for subprocesses):</label> </td>
+	  <td> <input title="The full path of the Java executable to use. Normally set correct by default." id="javaBinary" size="80" /> </td>
+	</tr>
+	<tr>
+	  <td> <label title="The full path of the secure subprocess fork engine to use. Normally set correct by default." for="serverForkerJar">Subprocess Factory JAR:</label> </td>
+	  <td> <input title="The full path of the secure subprocess fork engine to use. Normally set correct by default." id="serverForkerJar" size="80" /> </td>
+	</tr>
+	<tr>
+	  <td> <label title="The full path of a file containing the credentials to use with sudo. Leave blank to use a password-less connection (see documentation for how to configure)." for="runasPasswordFile">File with password for sudo:</label> </td>
+	  <td> <input title="The full path of a file containing the credentials to use with sudo. Leave blank to use a password-less connection (see documentation for how to configure)." id="runasPasswordFile" size="80" /> </td>
+	</tr>
+	<tr>
+	  <td> <label title="The full path of the user filesystem access and workflow initiation engine to use. Normally set correct by default." for="serverWorkerJar">User Filesystem Access JAR:</label> </td>
+	  <td> <input title="The full path of the user filesystem access and workflow initiation engine to use. Normally set correct by default." id="serverWorkerJar" size="80" /> </td>
+	</tr>
+	<tr>
+	  <td> <label title="The full path of the workflow engine executable. Normally set correctly by default." for="executeWorkflowScript">Workflow Engine Executable:</label> </td>
+	  <td> <input title="The full path of the workflow engine executable. Normally set correctly by default." id="executeWorkflowScript" size="80" /> </td>
+	</tr>
+      </table>
+    </div>
+
+    <h3><a href="#">Worker Registration Control</a></h3>
+    <div>
+      <label title="The machine hosting the RMI registry. WARNING: changing this will probably break your configuration! Contact the myGrid team for help before adjusting!" for="registryHost">Registry Host</label>
+      <input title="The machine hosting the RMI registry. WARNING: changing this will probably break your configuration! Contact the myGrid team for help before adjusting!" id="registryHost" size="20" />
+      <label title="The port number the RMI registry. WARNING: changing this will probably break your configuration! Contact the myGrid team for help before adjusting!" for="registryPort">Port</label>
+      <input title="The port number the RMI registry. WARNING: changing this will probably break your configuration! Contact the myGrid team for help before adjusting!" id="registryPort" size="5" />
+      <br>
+      <label title="The full path of the RMI registry implementation JAR file. WARNING: changing this will probably break your configuration! Contact the myGrid team for help before adjusting!" for="registryJar">RMI Registry JAR</label>
+      <input title="The full path of the RMI registry implementation JAR file. WARNING: changing this will probably break your configuration! Contact the myGrid team for help before adjusting!" id="registryJar" size="80" />
+      <br>
+      <label title="The time to wait (in seconds) for the back-end processes to boot and register themselves with the RMI registry. Busy machines may need a longer value here." for="registrationWaitSeconds">Time to wait for registration (seconds)</label>
+      <input title="The time to wait (in seconds) for the back-end processes to boot and register themselves with the RMI registry. Busy machines may need a longer value here." id="registrationWaitSeconds" size="5" />
+      <br>
+      <label title="How long to wait (in milliseconds) between probes to the registry to detect the registration of a back-end process." for="registrationPollMillis">Time to wait between polling to
+      detect registration (milliseconds)</label>
+      <input title="How long to wait (in milliseconds) between probes to the registry to detect the registration of a back-end process." id="registrationPollMillis" size="5" />
+    </div>
+
+    <h3><a href="#">System User/Factory ID Mapping</a></h3>
+    <div>
+      <table title="The mapping of system user IDs to factory identifiers (used in the RMI registry). Note that this is read-only." id="factoryProcessMapping" border="1">
+      </table>
+    </div>
+
+   	<h3><a href="#">Extra Workflow Engine Configuration</a></h3>
+   	<div>
+   		<h4>System Properties</h4>
+   		<table id="extraArguments-prop">
+   		<tr><td></td><td><button title="Add a system property to pass to the back-end engine." id="extra-prop-add">Add System Property</button></td></tr>
+   		</table>
+   		<h4>Environment Variables</h4>
+   		<table id="extraArguments-env">
+   		<tr><td></td><td><button title="Add an environment variable to pass to the back-end engine." id="extra-env-add">Add Environment Variable</button></td></tr>
+   		</table>
+   		<h4>Java Runtime Configuration</h4>
+   		<table id="extraArguments-runtime">
+   		<tr><td></td><td><button title="Add a Java runtime parameter (e.g., Xmx=400m to set the memory usage limit to 400MB) to pass to the back-end engine. Note the lack of a leading '-' character!" id="extra-run-add">Add Runtime Configuration</button></td></tr>
+   		</table>
+   	</div>
+
+  </div><!-- a-worker -->
+</div><!-- t-worker -->
+
+</div>
+
+<hr>
+<address>Donal Fellows / University of Manchester</address>
+
+<!-- DIALOG BOXES -->
+<div id="dialog-confirm" title="Delete user?" style="display: none">
+  <p>
+  <span class="ui-icon ui-icon-alert" style="float:left; margin:0 7px 20px 0;"></span>
+  This user will be permanently deleted from the system. Are you sure?
+  </p>
+</div>
+
+<div id="dialog-password" title="Change password?" style="display: none">
+  <p>
+  <span class="ui-icon ui-icon-alert" style="float:left; margin:0 7px 20px 0;"></span>
+  This will permanently change the user's password. Make sure you wish
+  to do this.
+  </p>
+  <p>
+  <input title="New password" id="change-password" type="password" size="12" />
+  <br>
+  Please repeat it to be sure...
+  <br>
+  <input title="New password (again)" id="change-password2" type="password" size="12" />
+  </p>
+</div>
+
+<div id="dialog-environment" title="Set environment variable?" style="display: none">
+  <p>
+  <span class="ui-icon ui-icon-alert" style="float:left; margin:0 7px 20px 0;"></span>
+  Set an environment variable to be passed to the workflow engine.
+  </p>
+  <p>
+  <input title="Environment variable name" id="env-key" size="15" /> =
+  <input title="Environment variable value" id="env-value" size="20" />
+  </p>
+</div>
+
+<div id="dialog-runtime" title="Set runtime configuration?" style="display: none">
+  <p>
+  <span class="ui-icon ui-icon-alert" style="float:left; margin:0 7px 20px 0;"></span>
+  Set a runtime parameter (e.g., -Xmx400m for a 400MB memory limit) for the Java runtime.
+  </p>
+  <p>
+  <input title="Java runtime configuration parameter" id="runtime-value" size="20" />
+  </p>
+</div>
+
+<div id="dialog-property" title="Set runtime property?" style="display: none">
+  <p>
+  <span class="ui-icon ui-icon-alert" style="float:left; margin:0 7px 20px 0;"></span>
+  Set a configuration property for the Java runtime.
+  </p>
+  <p>
+  <input title="System property name" id="prop-key" size="15" /> =
+  <input title="System property value" id="prop-value" size="20" />
+  </p>
+</div>
+
+</body>
+</html>

http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/2c71f9a9/taverna-server-webapp/src/main/resources/capabilities.properties
----------------------------------------------------------------------
diff --git a/taverna-server-webapp/src/main/resources/capabilities.properties b/taverna-server-webapp/src/main/resources/capabilities.properties
new file mode 100644
index 0000000..2b4844f
--- /dev/null
+++ b/taverna-server-webapp/src/main/resources/capabilities.properties
@@ -0,0 +1,38 @@
+# This is currently a hand-curated list. This sucks! 
+
+######## --- PLATFORM --- ########
+http\://ns.taverna.org.uk/2013/software/taverna = 2.5
+
+######## --- OUTPUTS/PROVENANCE --- ########
+http\://ns.taverna.org.uk/2013/bundle/run = 1.0
+http\://ns.taverna.org.uk/2013/provenance/prov = 1.0
+
+######## --- ACTIVITIES --- ########
+http\://ns.taverna.org.uk/2010/activity/nested-workflow = 1.5
+http\://ns.taverna.org.uk/2010/activity/apiconsumer = 1.5
+http\://ns.taverna.org.uk/2010/activity/beanshell = 1.5
+http\://ns.taverna.org.uk/2010/activity/localworker = 1.5
+http\://ns.taverna.org.uk/2010/activity/biomart = 1.5
+http\://ns.taverna.org.uk/2010/activity/biomoby/object = 1.5
+http\://ns.taverna.org.uk/2010/activity/biomoby/service = 1.5
+http\://ns.taverna.org.uk/2010/activity/rshell = 1.5
+http\://ns.taverna.org.uk/2010/activity/soaplab = 1.5
+http\://ns.taverna.org.uk/2010/activity/spreadsheet-import = 1.5
+http\://ns.taverna.org.uk/2010/activity/constant = 1.5
+http\://ns.taverna.org.uk/2010/activity/component = 1.5
+http\://ns.taverna.org.uk/2010/activity/wsdl = 1.5
+http\://ns.taverna.org.uk/2010/activity/wsdl/xml-splitter/in = 1.5
+http\://ns.taverna.org.uk/2010/activity/wsdl/xml-splitter/out = 1.5
+http\://ns.taverna.org.uk/2010/activity/tool = 1.5
+http\://ns.taverna.org.uk/2010/activity/rest = 1.5
+http\://ns.taverna.org.uk/2010/activity/xpath = 1.5
+http\://ns.taverna.org.uk/2010/activity/webdav = 1.5
+http\://ns.taverna.org.uk/2010/activity/interaction = 1.5
+
+######## --- DISPATCH LAYERS --- ########
+http\://ns.taverna.org.uk/2010/scufl2/taverna/dispatchlayer/ErrorBounce = 1.5
+http\://ns.taverna.org.uk/2010/scufl2/taverna/dispatchlayer/Failover = 1.5
+http\://ns.taverna.org.uk/2010/scufl2/taverna/dispatchlayer/Invoke = 1.5
+http\://ns.taverna.org.uk/2010/scufl2/taverna/dispatchlayer/Loop = 1.5
+http\://ns.taverna.org.uk/2010/scufl2/taverna/dispatchlayer/Parallelize = 1.5
+http\://ns.taverna.org.uk/2010/scufl2/taverna/dispatchlayer/Retry = 1.5
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/2c71f9a9/taverna-server-webapp/src/main/resources/log4j.properties
----------------------------------------------------------------------
diff --git a/taverna-server-webapp/src/main/resources/log4j.properties b/taverna-server-webapp/src/main/resources/log4j.properties
new file mode 100644
index 0000000..ea0ea12
--- /dev/null
+++ b/taverna-server-webapp/src/main/resources/log4j.properties
@@ -0,0 +1,39 @@
+log4j.rootLogger=info, R
+log4j.category.DataNucleus.Query=warn
+#log4j.category.DataNucleus.Datastore.Schema=debug
+#log4j.logger.org.springframework.security=DEBUG
+#log4j.category.Taverna=debug
+
+log4j.appender.R=org.apache.log4j.RollingFileAppender
+log4j.appender.R.File=${catalina.home}/logs/tavserv.out
+log4j.appender.R.MaxFileSize=10MB
+log4j.appender.R.MaxBackupIndex=30
+log4j.appender.R.layout=org.apache.log4j.PatternLayout
+log4j.appender.R.layout.ConversionPattern=%d{yyyyMMdd'T'HHmmss.SSS} %-5p %c{1} %C{1} - %m%n
+
+#log4j.category.Taverna=INFO, A1
+#log4j.category.Taverna.Server.LocalWorker.RunDB=INFO
+#log4j.category.Taverna.Server.Webapp=INFO
+#log4j.category.Taverna.Server.LocalWorker.Policy=INFO
+#log4j.category.Taverna.Server.LocalWorker.Security=INFO
+## Swallow Derby's messages
+#log4j.category.Derby=WARN, B2
+## Will you _shut up_, DataNucleus! <hits with rolled-up newspaper>
+#log4j.category.DataNucleus=WARN, B2
+##log4j.category.DataNucleus.SchemaTool=DEBUG
+##log4j.category.DataNucleus.Datastore.Schema=DEBUG
+##log4j.category.DataNucleus.Datastore.Native=DEBUG
+##log4j.logger.org.springframework.security=DEBUG, B2
+#log4j.category.org.springframework=INFO, B2
+#log4j.category.org.apache.cxf=INFO, B2
+#log4j.category.org.apache.cxf.jaxrs.utils.JAXRSUtils=INFO
+#log4j.category.eu.medsea=INFO, B2
+#log4j.category.org.apache.axiom=INFO, B2
+## Appender for Taverna Server components
+#log4j.appender.A1=org.apache.log4j.ConsoleAppender
+#log4j.appender.A1.layout=org.apache.log4j.PatternLayout
+#log4j.appender.A1.layout.ConversionPattern=%d{yyyyMMdd'T'HHmmss.SSS} %-5p %c{1} %C{1} - %m%n
+## Appender for Framework components
+#log4j.appender.B2=org.apache.log4j.ConsoleAppender
+#log4j.appender.B2.layout=org.apache.log4j.PatternLayout
+#log4j.appender.B2.layout.ConversionPattern=%d{yyyyMMdd'T'HHmmss.SSS} %-5p %c{1} - %m%n

http://git-wip-us.apache.org/repos/asf/incubator-taverna-server/blob/2c71f9a9/taverna-server-webapp/src/main/resources/security.policy
----------------------------------------------------------------------
diff --git a/taverna-server-webapp/src/main/resources/security.policy b/taverna-server-webapp/src/main/resources/security.policy
new file mode 100644
index 0000000..1ec4166
--- /dev/null
+++ b/taverna-server-webapp/src/main/resources/security.policy
@@ -0,0 +1,3 @@
+grant {
+   permission java.security.AllPermission "*:*";
+};