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 }