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 }