001package org.eclipse.aether.repository; 002 003/* 004 * Licensed to the Apache Software Foundation (ASF) under one 005 * or more contributor license agreements. See the NOTICE file 006 * distributed with this work for additional information 007 * regarding copyright ownership. The ASF licenses this file 008 * to you under the Apache License, Version 2.0 (the 009 * "License"); you may not use this file except in compliance 010 * with the License. You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, 015 * software distributed under the License is distributed on an 016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 017 * KIND, either express or implied. See the License for the 018 * specific language governing permissions and limitations 019 * under the License. 020 */ 021 022import java.io.Closeable; 023import java.io.File; 024import java.util.Arrays; 025import java.util.HashMap; 026import java.util.Map; 027import static java.util.Objects.requireNonNull; 028 029import org.eclipse.aether.RepositorySystemSession; 030 031/** 032 * A glorified map of key value pairs holding (cleartext) authentication data. Authentication contexts are used 033 * internally when network operations need to access secured repositories or proxies. Each authentication context 034 * manages the credentials required to access a single host. Unlike {@link Authentication} callbacks which exist for a 035 * potentially long time like the duration of a repository system session, an authentication context has a supposedly 036 * short lifetime and should be {@link #close() closed} as soon as the corresponding network operation has finished: 037 * 038 * <pre> 039 * AuthenticationContext context = AuthenticationContext.forRepository( session, repository ); 040 * try { 041 * // get credentials 042 * char[] password = context.get( AuthenticationContext.PASSWORD, char[].class ); 043 * // perform network operation using retrieved credentials 044 * ... 045 * } finally { 046 * // erase confidential authentication data from heap memory 047 * AuthenticationContext.close( context ); 048 * } 049 * </pre> 050 * 051 * The same authentication data can often be presented using different data types, e.g. a password can be presented 052 * using a character array or (less securely) using a string. For ease of use, an authentication context treats the 053 * following groups of data types as equivalent and converts values automatically during retrieval: 054 * <ul> 055 * <li>{@code String}, {@code char[]}</li> 056 * <li>{@code String}, {@code File}</li> 057 * </ul> 058 * An authentication context is thread-safe. 059 */ 060public final class AuthenticationContext 061 implements Closeable 062{ 063 064 /** 065 * The key used to store the username. The corresponding authentication data should be of type {@link String}. 066 */ 067 public static final String USERNAME = "username"; 068 069 /** 070 * The key used to store the password. The corresponding authentication data should be of type {@code char[]} or 071 * {@link String}. 072 */ 073 public static final String PASSWORD = "password"; 074 075 /** 076 * The key used to store the NTLM domain. The corresponding authentication data should be of type {@link String}. 077 */ 078 public static final String NTLM_DOMAIN = "ntlm.domain"; 079 080 /** 081 * The key used to store the NTML workstation. The corresponding authentication data should be of type 082 * {@link String}. 083 */ 084 public static final String NTLM_WORKSTATION = "ntlm.workstation"; 085 086 /** 087 * The key used to store the pathname to a private key file. The corresponding authentication data should be of type 088 * {@link String} or {@link File}. 089 */ 090 public static final String PRIVATE_KEY_PATH = "privateKey.path"; 091 092 /** 093 * The key used to store the passphrase protecting the private key. The corresponding authentication data should be 094 * of type {@code char[]} or {@link String}. 095 */ 096 public static final String PRIVATE_KEY_PASSPHRASE = "privateKey.passphrase"; 097 098 /** 099 * 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}