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<String, Object>();
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, value );
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 }