View Javadoc
1   /*******************************************************************************
2    * Copyright (c) 2012, 2014 Sonatype, Inc.
3    * All rights reserved. This program and the accompanying materials
4    * are made available under the terms of the Eclipse Public License v1.0
5    * which accompanies this distribution, and is available at
6    * http://www.eclipse.org/legal/epl-v10.html
7    *
8    * Contributors:
9    *    Sonatype, Inc. - initial API and implementation
10   *******************************************************************************/
11  package org.eclipse.aether.repository;
12  
13  import java.io.Closeable;
14  import java.io.File;
15  import java.util.Arrays;
16  import java.util.HashMap;
17  import java.util.Map;
18  
19  import org.eclipse.aether.RepositorySystemSession;
20  
21  /**
22   * A glorified map of key value pairs holding (cleartext) authentication data. Authentication contexts are used
23   * internally when network operations need to access secured repositories or proxies. Each authentication context
24   * manages the credentials required to access a single host. Unlike {@link Authentication} callbacks which exist for a
25   * potentially long time like the duration of a repository system session, an authentication context has a supposedly
26   * short lifetime and should be {@link #close() closed} as soon as the corresponding network operation has finished:
27   * 
28   * <pre>
29   * AuthenticationContext context = AuthenticationContext.forRepository( session, repository );
30   * try {
31   *     // get credentials
32   *     char[] password = context.get( AuthenticationContext.PASSWORD, char[].class );
33   *     // perform network operation using retrieved credentials
34   *     ...
35   * } finally {
36   *     // erase confidential authentication data from heap memory
37   *     AuthenticationContext.close( context );
38   * }
39   * </pre>
40   * 
41   * The same authentication data can often be presented using different data types, e.g. a password can be presented
42   * using a character array or (less securely) using a string. For ease of use, an authentication context treats the
43   * following groups of data types as equivalent and converts values automatically during retrieval:
44   * <ul>
45   * <li>{@code String}, {@code char[]}</li>
46   * <li>{@code String}, {@code File}</li>
47   * </ul>
48   * An authentication context is thread-safe.
49   */
50  public final class AuthenticationContext
51      implements Closeable
52  {
53  
54      /**
55       * The key used to store the username. The corresponding authentication data should be of type {@link String}.
56       */
57      public static final String USERNAME = "username";
58  
59      /**
60       * The key used to store the password. The corresponding authentication data should be of type {@code char[]} or
61       * {@link String}.
62       */
63      public static final String PASSWORD = "password";
64  
65      /**
66       * The key used to store the NTLM domain. The corresponding authentication data should be of type {@link String}.
67       */
68      public static final String NTLM_DOMAIN = "ntlm.domain";
69  
70      /**
71       * The key used to store the NTML workstation. The corresponding authentication data should be of type
72       * {@link String}.
73       */
74      public static final String NTLM_WORKSTATION = "ntlm.workstation";
75  
76      /**
77       * The key used to store the pathname to a private key file. The corresponding authentication data should be of type
78       * {@link String} or {@link File}.
79       */
80      public static final String PRIVATE_KEY_PATH = "privateKey.path";
81  
82      /**
83       * The key used to store the passphrase protecting the private key. The corresponding authentication data should be
84       * of type {@code char[]} or {@link String}.
85       */
86      public static final String PRIVATE_KEY_PASSPHRASE = "privateKey.passphrase";
87  
88      /**
89       * The key used to store the acceptance policy for unknown host keys. The corresponding authentication data should
90       * be of type {@link Boolean}. When querying this authentication data, the extra data should provide
91       * {@link #HOST_KEY_REMOTE} and {@link #HOST_KEY_LOCAL}, e.g. to enable a well-founded decision of the user during
92       * an interactive prompt.
93       */
94      public static final String HOST_KEY_ACCEPTANCE = "hostKey.acceptance";
95  
96      /**
97       * The key used to store the fingerprint of the public key advertised by remote host. Note that this key is used to
98       * query the extra data passed to {@link #get(String, Map, Class)} when getting {@link #HOST_KEY_ACCEPTANCE}, not
99       * the authentication data in a context.
100      */
101     public static final String HOST_KEY_REMOTE = "hostKey.remote";
102 
103     /**
104      * The key used to store the fingerprint of the public key expected from remote host as recorded in a known hosts
105      * database. Note that this key is used to query the extra data passed to {@link #get(String, Map, Class)} when
106      * getting {@link #HOST_KEY_ACCEPTANCE}, not the authentication data in a context.
107      */
108     public static final String HOST_KEY_LOCAL = "hostKey.local";
109 
110     /**
111      * The key used to store the SSL context. The corresponding authentication data should be of type
112      * {@link javax.net.ssl.SSLContext}.
113      */
114     public static final String SSL_CONTEXT = "ssl.context";
115 
116     /**
117      * The key used to store the SSL hostname verifier. The corresponding authentication data should be of type
118      * {@link javax.net.ssl.HostnameVerifier}.
119      */
120     public static final String SSL_HOSTNAME_VERIFIER = "ssl.hostnameVerifier";
121 
122     private final RepositorySystemSession session;
123 
124     private final RemoteRepository repository;
125 
126     private final Proxy proxy;
127 
128     private final Authentication auth;
129 
130     private final Map<String, Object> authData;
131 
132     private boolean fillingAuthData;
133 
134     /**
135      * Gets an authentication context for the specified repository.
136      * 
137      * @param session The repository system session during which the repository is accessed, must not be {@code null}.
138      * @param repository The repository for which to create an authentication context, must not be {@code null}.
139      * @return An authentication context for the repository or {@code null} if no authentication is configured for it.
140      */
141     public static AuthenticationContext forRepository( RepositorySystemSession session, RemoteRepository repository )
142     {
143         return newInstance( session, repository, null, repository.getAuthentication() );
144     }
145 
146     /**
147      * Gets an authentication context for the proxy of the specified repository.
148      * 
149      * @param session The repository system session during which the repository is accessed, must not be {@code null}.
150      * @param repository The repository for whose proxy to create an authentication context, must not be {@code null}.
151      * @return An authentication context for the proxy or {@code null} if no proxy is set or no authentication is
152      *         configured for it.
153      */
154     public static AuthenticationContext forProxy( RepositorySystemSession session, RemoteRepository repository )
155     {
156         Proxy proxy = repository.getProxy();
157         return newInstance( session, repository, proxy, ( proxy != null ) ? proxy.getAuthentication() : null );
158     }
159 
160     private static AuthenticationContext newInstance( RepositorySystemSession session, RemoteRepository repository,
161                                                       Proxy proxy, Authentication auth )
162     {
163         if ( auth == null )
164         {
165             return null;
166         }
167         return new AuthenticationContext( session, repository, proxy, auth );
168     }
169 
170     private AuthenticationContext( RepositorySystemSession session, RemoteRepository repository, Proxy proxy,
171                                    Authentication auth )
172     {
173         if ( session == null )
174         {
175             throw new IllegalArgumentException( "repository system session missing" );
176         }
177         this.session = session;
178         this.repository = repository;
179         this.proxy = proxy;
180         this.auth = auth;
181         authData = new HashMap<String, Object>();
182     }
183 
184     /**
185      * Gets the repository system session during which the authentication happens.
186      * 
187      * @return The repository system session, never {@code null}.
188      */
189     public RepositorySystemSession getSession()
190     {
191         return session;
192     }
193 
194     /**
195      * Gets the repository requiring authentication. If {@link #getProxy()} is not {@code null}, the data gathered by
196      * this authentication context does not apply to the repository's host but rather the proxy.
197      * 
198      * @return The repository to be contacted, never {@code null}.
199      */
200     public RemoteRepository getRepository()
201     {
202         return repository;
203     }
204 
205     /**
206      * Gets the proxy (if any) to be authenticated with.
207      * 
208      * @return The proxy or {@code null} if authenticating directly with the repository's host.
209      */
210     public Proxy getProxy()
211     {
212         return proxy;
213     }
214 
215     /**
216      * Gets the authentication data for the specified key.
217      * 
218      * @param key The key whose authentication data should be retrieved, must not be {@code null}.
219      * @return The requested authentication data or {@code null} if none.
220      */
221     public String get( String key )
222     {
223         return get( key, null, String.class );
224     }
225 
226     /**
227      * Gets the authentication data for the specified key.
228      * 
229      * @param <T> The data type of the authentication data.
230      * @param key The key whose authentication data should be retrieved, must not be {@code null}.
231      * @param type The expected type of the authentication data, must not be {@code null}.
232      * @return The requested authentication data or {@code null} if none or if the data doesn't match the expected type.
233      */
234     public <T> T get( String key, Class<T> type )
235     {
236         return get( key, null, type );
237     }
238 
239     /**
240      * Gets the authentication data for the specified key.
241      * 
242      * @param <T> The data type of the authentication data.
243      * @param key The key whose authentication data should be retrieved, must not be {@code null}.
244      * @param data Any (read-only) extra data in form of key value pairs that might be useful when getting the
245      *            authentication data, may be {@code null}.
246      * @param type The expected type of the authentication data, must not be {@code null}.
247      * @return The requested authentication data or {@code null} if none or if the data doesn't match the expected type.
248      */
249     public <T> T get( String key, Map<String, String> data, Class<T> type )
250     {
251         if ( key == null )
252         {
253             throw new IllegalArgumentException( "authentication data key missing" );
254         }
255         Object value;
256         synchronized ( authData )
257         {
258             value = authData.get( key );
259             if ( value == null && !authData.containsKey( key ) && !fillingAuthData )
260             {
261                 if ( auth != null )
262                 {
263                     try
264                     {
265                         fillingAuthData = true;
266                         auth.fill( this, key, data );
267                     }
268                     finally
269                     {
270                         fillingAuthData = false;
271                     }
272                     value = authData.get( key );
273                 }
274                 if ( value == null )
275                 {
276                     authData.put( key, value );
277                 }
278             }
279         }
280 
281         return convert( value, type );
282     }
283 
284     private <T> T convert( Object value, Class<T> type )
285     {
286         if ( !type.isInstance( value ) )
287         {
288             if ( String.class.equals( type ) )
289             {
290                 if ( value instanceof File )
291                 {
292                     value = ( (File) value ).getPath();
293                 }
294                 else if ( value instanceof char[] )
295                 {
296                     value = new String( (char[]) value );
297                 }
298             }
299             else if ( File.class.equals( type ) )
300             {
301                 if ( value instanceof String )
302                 {
303                     value = new File( (String) value );
304                 }
305             }
306             else if ( char[].class.equals( type ) )
307             {
308                 if ( value instanceof String )
309                 {
310                     value = ( (String) value ).toCharArray();
311                 }
312             }
313         }
314 
315         if ( type.isInstance( value ) )
316         {
317             return type.cast( value );
318         }
319 
320         return null;
321     }
322 
323     /**
324      * Puts the specified authentication data into this context. This method should only be called from implementors of
325      * {@link Authentication#fill(AuthenticationContext, String, Map)}. Passed in character arrays are not cloned and
326      * become owned by this context, i.e. get erased when the context gets closed.
327      * 
328      * @param key The key to associate the authentication data with, must not be {@code null}.
329      * @param value The (cleartext) authentication data to store, may be {@code null}.
330      */
331     public void put( String key, Object value )
332     {
333         if ( key == null )
334         {
335             throw new IllegalArgumentException( "authentication data key missing" );
336         }
337         synchronized ( authData )
338         {
339             Object oldValue = authData.put( key, value );
340             if ( oldValue instanceof char[] )
341             {
342                 Arrays.fill( (char[]) oldValue, '\0' );
343             }
344         }
345     }
346 
347     /**
348      * Closes this authentication context and erases sensitive authentication data from heap memory. Closing an already
349      * closed context has no effect.
350      */
351     public void close()
352     {
353         synchronized ( authData )
354         {
355             for ( Object value : authData.values() )
356             {
357                 if ( value instanceof char[] )
358                 {
359                     Arrays.fill( (char[]) value, '\0' );
360                 }
361             }
362             authData.clear();
363         }
364     }
365 
366     /**
367      * Closes the specified authentication context. This is a convenience method doing a {@code null} check before
368      * calling {@link #close()} on the given context.
369      * 
370      * @param context The authentication context to close, may be {@code null}.
371      */
372     public static void close( AuthenticationContext context )
373     {
374         if ( context != null )
375         {
376             context.close();
377         }
378     }
379 
380 }