blob: bd248a078ab0ceabf3edf918e1be5f13cff02b7c [file] [log] [blame]
package org.eclipse.aether.repository;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
import java.io.Closeable;
import java.io.File;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.aether.RepositorySystemSession;
/**
* A glorified map of key value pairs holding (cleartext) authentication data. Authentication contexts are used
* internally when network operations need to access secured repositories or proxies. Each authentication context
* manages the credentials required to access a single host. Unlike {@link Authentication} callbacks which exist for a
* potentially long time like the duration of a repository system session, an authentication context has a supposedly
* short lifetime and should be {@link #close() closed} as soon as the corresponding network operation has finished:
*
* <pre>
* AuthenticationContext context = AuthenticationContext.forRepository( session, repository );
* try {
* // get credentials
* char[] password = context.get( AuthenticationContext.PASSWORD, char[].class );
* // perform network operation using retrieved credentials
* ...
* } finally {
* // erase confidential authentication data from heap memory
* AuthenticationContext.close( context );
* }
* </pre>
*
* The same authentication data can often be presented using different data types, e.g. a password can be presented
* using a character array or (less securely) using a string. For ease of use, an authentication context treats the
* following groups of data types as equivalent and converts values automatically during retrieval:
* <ul>
* <li>{@code String}, {@code char[]}</li>
* <li>{@code String}, {@code File}</li>
* </ul>
* An authentication context is thread-safe.
*/
public final class AuthenticationContext
implements Closeable
{
/**
* The key used to store the username. The corresponding authentication data should be of type {@link String}.
*/
public static final String USERNAME = "username";
/**
* The key used to store the password. The corresponding authentication data should be of type {@code char[]} or
* {@link String}.
*/
public static final String PASSWORD = "password";
/**
* The key used to store the NTLM domain. The corresponding authentication data should be of type {@link String}.
*/
public static final String NTLM_DOMAIN = "ntlm.domain";
/**
* The key used to store the NTML workstation. The corresponding authentication data should be of type
* {@link String}.
*/
public static final String NTLM_WORKSTATION = "ntlm.workstation";
/**
* The key used to store the pathname to a private key file. The corresponding authentication data should be of type
* {@link String} or {@link File}.
*/
public static final String PRIVATE_KEY_PATH = "privateKey.path";
/**
* The key used to store the passphrase protecting the private key. The corresponding authentication data should be
* of type {@code char[]} or {@link String}.
*/
public static final String PRIVATE_KEY_PASSPHRASE = "privateKey.passphrase";
/**
* The key used to store the acceptance policy for unknown host keys. The corresponding authentication data should
* be of type {@link Boolean}. When querying this authentication data, the extra data should provide
* {@link #HOST_KEY_REMOTE} and {@link #HOST_KEY_LOCAL}, e.g. to enable a well-founded decision of the user during
* an interactive prompt.
*/
public static final String HOST_KEY_ACCEPTANCE = "hostKey.acceptance";
/**
* The key used to store the fingerprint of the public key advertised by remote host. Note that this key is used to
* query the extra data passed to {@link #get(String, Map, Class)} when getting {@link #HOST_KEY_ACCEPTANCE}, not
* the authentication data in a context.
*/
public static final String HOST_KEY_REMOTE = "hostKey.remote";
/**
* The key used to store the fingerprint of the public key expected from remote host as recorded in a known hosts
* database. Note that this key is used to query the extra data passed to {@link #get(String, Map, Class)} when
* getting {@link #HOST_KEY_ACCEPTANCE}, not the authentication data in a context.
*/
public static final String HOST_KEY_LOCAL = "hostKey.local";
/**
* The key used to store the SSL context. The corresponding authentication data should be of type
* {@link javax.net.ssl.SSLContext}.
*/
public static final String SSL_CONTEXT = "ssl.context";
/**
* The key used to store the SSL hostname verifier. The corresponding authentication data should be of type
* {@link javax.net.ssl.HostnameVerifier}.
*/
public static final String SSL_HOSTNAME_VERIFIER = "ssl.hostnameVerifier";
private final RepositorySystemSession session;
private final RemoteRepository repository;
private final Proxy proxy;
private final Authentication auth;
private final Map<String, Object> authData;
private boolean fillingAuthData;
/**
* Gets an authentication context for the specified repository.
*
* @param session The repository system session during which the repository is accessed, must not be {@code null}.
* @param repository The repository for which to create an authentication context, must not be {@code null}.
* @return An authentication context for the repository or {@code null} if no authentication is configured for it.
*/
public static AuthenticationContext forRepository( RepositorySystemSession session, RemoteRepository repository )
{
return newInstance( session, repository, null, repository.getAuthentication() );
}
/**
* Gets an authentication context for the proxy of the specified repository.
*
* @param session The repository system session during which the repository is accessed, must not be {@code null}.
* @param repository The repository for whose proxy to create an authentication context, must not be {@code null}.
* @return An authentication context for the proxy or {@code null} if no proxy is set or no authentication is
* configured for it.
*/
public static AuthenticationContext forProxy( RepositorySystemSession session, RemoteRepository repository )
{
Proxy proxy = repository.getProxy();
return newInstance( session, repository, proxy, ( proxy != null ) ? proxy.getAuthentication() : null );
}
private static AuthenticationContext newInstance( RepositorySystemSession session, RemoteRepository repository,
Proxy proxy, Authentication auth )
{
if ( auth == null )
{
return null;
}
return new AuthenticationContext( session, repository, proxy, auth );
}
private AuthenticationContext( RepositorySystemSession session, RemoteRepository repository, Proxy proxy,
Authentication auth )
{
if ( session == null )
{
throw new IllegalArgumentException( "repository system session missing" );
}
this.session = session;
this.repository = repository;
this.proxy = proxy;
this.auth = auth;
authData = new HashMap<String, Object>();
}
/**
* Gets the repository system session during which the authentication happens.
*
* @return The repository system session, never {@code null}.
*/
public RepositorySystemSession getSession()
{
return session;
}
/**
* Gets the repository requiring authentication. If {@link #getProxy()} is not {@code null}, the data gathered by
* this authentication context does not apply to the repository's host but rather the proxy.
*
* @return The repository to be contacted, never {@code null}.
*/
public RemoteRepository getRepository()
{
return repository;
}
/**
* Gets the proxy (if any) to be authenticated with.
*
* @return The proxy or {@code null} if authenticating directly with the repository's host.
*/
public Proxy getProxy()
{
return proxy;
}
/**
* Gets the authentication data for the specified key.
*
* @param key The key whose authentication data should be retrieved, must not be {@code null}.
* @return The requested authentication data or {@code null} if none.
*/
public String get( String key )
{
return get( key, null, String.class );
}
/**
* Gets the authentication data for the specified key.
*
* @param <T> The data type of the authentication data.
* @param key The key whose authentication data should be retrieved, must not be {@code null}.
* @param type The expected type of the authentication data, must not be {@code null}.
* @return The requested authentication data or {@code null} if none or if the data doesn't match the expected type.
*/
public <T> T get( String key, Class<T> type )
{
return get( key, null, type );
}
/**
* Gets the authentication data for the specified key.
*
* @param <T> The data type of the authentication data.
* @param key The key whose authentication data should be retrieved, must not be {@code null}.
* @param data Any (read-only) extra data in form of key value pairs that might be useful when getting the
* authentication data, may be {@code null}.
* @param type The expected type of the authentication data, must not be {@code null}.
* @return The requested authentication data or {@code null} if none or if the data doesn't match the expected type.
*/
public <T> T get( String key, Map<String, String> data, Class<T> type )
{
if ( key == null )
{
throw new IllegalArgumentException( "authentication data key missing" );
}
Object value;
synchronized ( authData )
{
value = authData.get( key );
if ( value == null && !authData.containsKey( key ) && !fillingAuthData )
{
if ( auth != null )
{
try
{
fillingAuthData = true;
auth.fill( this, key, data );
}
finally
{
fillingAuthData = false;
}
value = authData.get( key );
}
if ( value == null )
{
authData.put( key, value );
}
}
}
return convert( value, type );
}
private <T> T convert( Object value, Class<T> type )
{
if ( !type.isInstance( value ) )
{
if ( String.class.equals( type ) )
{
if ( value instanceof File )
{
value = ( (File) value ).getPath();
}
else if ( value instanceof char[] )
{
value = new String( (char[]) value );
}
}
else if ( File.class.equals( type ) )
{
if ( value instanceof String )
{
value = new File( (String) value );
}
}
else if ( char[].class.equals( type ) )
{
if ( value instanceof String )
{
value = ( (String) value ).toCharArray();
}
}
}
if ( type.isInstance( value ) )
{
return type.cast( value );
}
return null;
}
/**
* Puts the specified authentication data into this context. This method should only be called from implementors of
* {@link Authentication#fill(AuthenticationContext, String, Map)}. Passed in character arrays are not cloned and
* become owned by this context, i.e. get erased when the context gets closed.
*
* @param key The key to associate the authentication data with, must not be {@code null}.
* @param value The (cleartext) authentication data to store, may be {@code null}.
*/
public void put( String key, Object value )
{
if ( key == null )
{
throw new IllegalArgumentException( "authentication data key missing" );
}
synchronized ( authData )
{
Object oldValue = authData.put( key, value );
if ( oldValue instanceof char[] )
{
Arrays.fill( (char[]) oldValue, '\0' );
}
}
}
/**
* Closes this authentication context and erases sensitive authentication data from heap memory. Closing an already
* closed context has no effect.
*/
public void close()
{
synchronized ( authData )
{
for ( Object value : authData.values() )
{
if ( value instanceof char[] )
{
Arrays.fill( (char[]) value, '\0' );
}
}
authData.clear();
}
}
/**
* Closes the specified authentication context. This is a convenience method doing a {@code null} check before
* calling {@link #close()} on the given context.
*
* @param context The authentication context to close, may be {@code null}.
*/
public static void close( AuthenticationContext context )
{
if ( context != null )
{
context.close();
}
}
}