1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one 3 * or more contributor license agreements. See the NOTICE file 4 * distributed with this work for additional information 5 * regarding copyright ownership. The ASF licenses this file 6 * to you under the Apache License, Version 2.0 (the 7 * "License"); you may not use this file except in compliance 8 * with the License. You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, 13 * software distributed under the License is distributed on an 14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 * KIND, either express or implied. See the License for the 16 * specific language governing permissions and limitations 17 * under the License. 18 */ 19 package org.eclipse.aether.repository; 20 21 import java.io.Closeable; 22 import java.io.File; 23 import java.util.Arrays; 24 import java.util.HashMap; 25 import java.util.Map; 26 27 import org.eclipse.aether.RepositorySystemSession; 28 29 import static java.util.Objects.requireNonNull; 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 implements Closeable { 61 62 /** 63 * The key used to store the username. The corresponding authentication data should be of type {@link String}. 64 */ 65 public static final String USERNAME = "username"; 66 67 /** 68 * The key used to store the password. The corresponding authentication data should be of type {@code char[]} or 69 * {@link String}. 70 */ 71 public static final String PASSWORD = "password"; 72 73 /** 74 * The key used to store the NTLM domain. The corresponding authentication data should be of type {@link String}. 75 */ 76 public static final String NTLM_DOMAIN = "ntlm.domain"; 77 78 /** 79 * The key used to store the NTML workstation. The corresponding authentication data should be of type 80 * {@link String}. 81 */ 82 public static final String NTLM_WORKSTATION = "ntlm.workstation"; 83 84 /** 85 * The key used to store the pathname to a private key file. The corresponding authentication data should be of type 86 * {@link String} or {@link File}. 87 */ 88 public static final String PRIVATE_KEY_PATH = "privateKey.path"; 89 90 /** 91 * The key used to store the passphrase protecting the private key. The corresponding authentication data should be 92 * of type {@code char[]} or {@link String}. 93 */ 94 public static final String PRIVATE_KEY_PASSPHRASE = "privateKey.passphrase"; 95 96 /** 97 * The key used to store the acceptance policy for unknown host keys. The corresponding authentication data should 98 * be of type {@link Boolean}. When querying this authentication data, the extra data should provide 99 * {@link #HOST_KEY_REMOTE} and {@link #HOST_KEY_LOCAL}, e.g. to enable a well-founded decision of the user during 100 * an interactive prompt. 101 */ 102 public static final String HOST_KEY_ACCEPTANCE = "hostKey.acceptance"; 103 104 /** 105 * The key used to store the fingerprint of the public key advertised by remote host. Note that this key is used to 106 * query the extra data passed to {@link #get(String, Map, Class)} when getting {@link #HOST_KEY_ACCEPTANCE}, not 107 * the authentication data in a context. 108 */ 109 public static final String HOST_KEY_REMOTE = "hostKey.remote"; 110 111 /** 112 * The key used to store the fingerprint of the public key expected from remote host as recorded in a known hosts 113 * database. Note that this key is used to query the extra data passed to {@link #get(String, Map, Class)} when 114 * getting {@link #HOST_KEY_ACCEPTANCE}, not the authentication data in a context. 115 */ 116 public static final String HOST_KEY_LOCAL = "hostKey.local"; 117 118 /** 119 * The key used to store the SSL context. The corresponding authentication data should be of type 120 * {@link javax.net.ssl.SSLContext}. 121 */ 122 public static final String SSL_CONTEXT = "ssl.context"; 123 124 /** 125 * The key used to store the SSL hostname verifier. The corresponding authentication data should be of type 126 * {@link javax.net.ssl.HostnameVerifier}. 127 */ 128 public static final String SSL_HOSTNAME_VERIFIER = "ssl.hostnameVerifier"; 129 130 private final RepositorySystemSession session; 131 132 private final RemoteRepository repository; 133 134 private final Proxy proxy; 135 136 private final Authentication auth; 137 138 private final Map<String, Object> authData; 139 140 private boolean fillingAuthData; 141 142 /** 143 * Gets an authentication context for the specified repository. 144 * 145 * @param session The repository system session during which the repository is accessed, may be {@code null}. 146 * @param repository The repository for which to create an authentication context, must not be {@code null}. 147 * @return An authentication context for the repository or {@code null} if no authentication is configured for it. 148 */ 149 public static AuthenticationContext forRepository(RepositorySystemSession session, RemoteRepository repository) { 150 return newInstance(session, repository, null, repository.getAuthentication()); 151 } 152 153 /** 154 * Gets an authentication context for the proxy of the specified repository. 155 * 156 * @param session The repository system session during which the repository is accessed, may be {@code null}. 157 * @param repository The repository for whose proxy to create an authentication context, must not be {@code null}. 158 * @return An authentication context for the proxy or {@code null} if no proxy is set or no authentication is 159 * configured for it. 160 */ 161 public static AuthenticationContext forProxy(RepositorySystemSession session, RemoteRepository repository) { 162 Proxy proxy = repository.getProxy(); 163 return newInstance(session, repository, proxy, (proxy != null) ? proxy.getAuthentication() : null); 164 } 165 166 private static AuthenticationContext newInstance( 167 RepositorySystemSession session, RemoteRepository repository, Proxy proxy, Authentication auth) { 168 if (auth == null) { 169 return null; 170 } 171 return new AuthenticationContext(session, repository, proxy, auth); 172 } 173 174 private AuthenticationContext( 175 RepositorySystemSession session, RemoteRepository repository, Proxy proxy, Authentication auth) { 176 this.session = session; 177 this.repository = requireNonNull(repository, "null repository"); 178 this.proxy = proxy; 179 this.auth = auth; 180 authData = new HashMap<>(); 181 } 182 183 /** 184 * Gets the repository system session during which the authentication happens (if within session). 185 * 186 * @return The repository system session, may be {@code null} if context is created outside of session. 187 */ 188 public RepositorySystemSession getSession() { 189 return session; 190 } 191 192 /** 193 * Gets the repository requiring authentication. If {@link #getProxy()} is not {@code null}, the data gathered by 194 * this authentication context does not apply to the repository's host but rather the proxy. 195 * 196 * @return The repository to be contacted, never {@code null}. 197 */ 198 public RemoteRepository getRepository() { 199 return repository; 200 } 201 202 /** 203 * Gets the proxy (if any) to be authenticated with. 204 * 205 * @return The proxy or {@code null} if authenticating directly with the repository's host. 206 */ 207 public Proxy getProxy() { 208 return proxy; 209 } 210 211 /** 212 * Gets the authentication data for the specified key. 213 * 214 * @param key The key whose authentication data should be retrieved, must not be {@code null}. 215 * @return The requested authentication data or {@code null} if none. 216 */ 217 public String get(String key) { 218 return get(key, null, String.class); 219 } 220 221 /** 222 * Gets the authentication data for the specified key. 223 * 224 * @param <T> The data type of the authentication data. 225 * @param key The key whose authentication data should be retrieved, must not be {@code null}. 226 * @param type The expected type of the authentication data, must not be {@code null}. 227 * @return The requested authentication data or {@code null} if none or if the data doesn't match the expected type. 228 */ 229 public <T> T get(String key, Class<T> type) { 230 return get(key, null, type); 231 } 232 233 /** 234 * Gets the authentication data for the specified key. 235 * 236 * @param <T> The data type of the authentication data. 237 * @param key The key whose authentication data should be retrieved, must not be {@code null}. 238 * @param data Any (read-only) extra data in form of key value pairs that might be useful when getting the 239 * authentication data, may 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, Map<String, String> data, Class<T> type) { 244 requireNonNull(key, "authentication key cannot be null"); 245 if (key.isEmpty()) { 246 throw new IllegalArgumentException("authentication key cannot be empty"); 247 } 248 249 Object value; 250 synchronized (authData) { 251 value = authData.get(key); 252 if (value == null && !authData.containsKey(key) && !fillingAuthData) { 253 if (auth != null) { 254 try { 255 fillingAuthData = true; 256 auth.fill(this, key, data); 257 } finally { 258 fillingAuthData = false; 259 } 260 value = authData.get(key); 261 } 262 if (value == null) { 263 authData.put(key, null); 264 } 265 } 266 } 267 268 return convert(value, type); 269 } 270 271 private <T> T convert(Object value, Class<T> type) { 272 if (!type.isInstance(value)) { 273 if (String.class.equals(type)) { 274 if (value instanceof File) { 275 value = ((File) value).getPath(); 276 } else if (value instanceof char[]) { 277 value = new String((char[]) value); 278 } 279 } else if (File.class.equals(type)) { 280 if (value instanceof String) { 281 value = new File((String) value); 282 } 283 } else if (char[].class.equals(type)) { 284 if (value instanceof String) { 285 value = ((String) value).toCharArray(); 286 } 287 } 288 } 289 290 if (type.isInstance(value)) { 291 return type.cast(value); 292 } 293 294 return null; 295 } 296 297 /** 298 * Puts the specified authentication data into this context. This method should only be called from implementors of 299 * {@link Authentication#fill(AuthenticationContext, String, Map)}. Passed in character arrays are not cloned and 300 * become owned by this context, i.e. get erased when the context gets closed. 301 * 302 * @param key The key to associate the authentication data with, must not be {@code null}. 303 * @param value The (cleartext) authentication data to store, may be {@code null}. 304 */ 305 public void put(String key, Object value) { 306 requireNonNull(key, "authentication key cannot be null"); 307 if (key.isEmpty()) { 308 throw new IllegalArgumentException("authentication key cannot be empty"); 309 } 310 311 synchronized (authData) { 312 Object oldValue = authData.put(key, value); 313 if (oldValue instanceof char[]) { 314 Arrays.fill((char[]) oldValue, '\0'); 315 } 316 } 317 } 318 319 /** 320 * Closes this authentication context and erases sensitive authentication data from heap memory. Closing an already 321 * closed context has no effect. 322 */ 323 public void close() { 324 synchronized (authData) { 325 for (Object value : authData.values()) { 326 if (value instanceof char[]) { 327 Arrays.fill((char[]) value, '\0'); 328 } 329 } 330 authData.clear(); 331 } 332 } 333 334 /** 335 * Closes the specified authentication context. This is a convenience method doing a {@code null} check before 336 * calling {@link #close()} on the given context. 337 * 338 * @param context The authentication context to close, may be {@code null}. 339 */ 340 public static void close(AuthenticationContext context) { 341 if (context != null) { 342 context.close(); 343 } 344 } 345 }