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 }