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