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