001package com.thetransactioncompany.jsonrpc2.server; 002 003 004import java.net.InetAddress; 005import java.net.URLConnection; 006import java.security.Principal; 007import java.security.cert.X509Certificate; 008 009import javax.net.ssl.HttpsURLConnection; 010import javax.servlet.http.HttpServletRequest; 011 012 013/** 014 * Context information about JSON-RPC 2.0 request and notification messages. 015 * This class is immutable. 016 * 017 * <ul> 018 * <li>The client's host name. 019 * <li>The client's IP address. 020 * <li>Whether the request / notification was transmitted securely (e.g. 021 * via HTTPS). 022 * <li>The client principal(s) (user), if authenticated. 023 * </ul> 024 * 025 * @author Vladimir Dzhuvinov 026 */ 027public class MessageContext { 028 029 030 /** 031 * The client hostname, {@code null} if none was specified. 032 */ 033 private String clientHostName = null; 034 035 036 /** 037 * The client IP address, {@code null} if none was specified. 038 */ 039 private String clientInetAddress = null; 040 041 042 /** 043 * Indicates whether the request was received over a secure channel 044 * (typically HTTPS). 045 */ 046 private boolean secure = false; 047 048 049 /** 050 * The authenticated client principals, {@code null} if none were 051 * specified. 052 */ 053 private Principal[] principals = null; 054 055 056 /** 057 * Minimal implementation of the {@link java.security.Principal} 058 * interface. 059 */ 060 public class BasicPrincipal implements Principal { 061 062 /** 063 * The principal name. 064 */ 065 private String name; 066 067 068 /** 069 * Creates a new principal. 070 * 071 * @param name The principal name, must not be {@code null} or 072 * empty string. 073 * 074 * @throws IllegalArgumentException On a {@code null} or empty 075 * principal name. 076 */ 077 public BasicPrincipal(final String name) { 078 079 if (name == null || name.trim().isEmpty()) 080 throw new IllegalArgumentException("The principal name must be defined"); 081 082 this.name = name; 083 } 084 085 086 /** 087 * Checks for equality. 088 * 089 * @param another The object to compare to. 090 */ 091 public boolean equals(final Object another) { 092 093 return another != null && 094 another instanceof Principal && 095 ((Principal)another).getName().equals(this.getName()); 096 } 097 098 099 /** 100 * Returns a hash code for this principal. 101 * 102 * @return The hash code. 103 */ 104 public int hashCode() { 105 106 return getName().hashCode(); 107 } 108 109 110 /** 111 * Returns the principal name. 112 * 113 * @return The principal name. 114 */ 115 public String getName() { 116 117 return name; 118 } 119 } 120 121 122 /** 123 * Creates a new JSON-RPC 2.0 request / notification context. 124 * 125 * @param clientHostName The client host name, {@code null} if 126 * unknown. 127 * @param clientInetAddress The client IP address, {@code null} if 128 * unknown. 129 * @param secure Specifies a request received over HTTPS. 130 * @param principalName Specifies the authenticated client principle 131 * name, {@code null} if unknown. The name must 132 * not be an empty or blank string. 133 */ 134 public MessageContext(final String clientHostName, 135 final String clientInetAddress, 136 final boolean secure, 137 final String principalName) { 138 139 this.clientHostName = clientHostName; 140 this.clientInetAddress = clientInetAddress; 141 this.secure = secure; 142 143 if (principalName != null) { 144 principals = new Principal[1]; 145 principals[0] = new BasicPrincipal(principalName); 146 } 147 } 148 149 150 /** 151 * Creates a new JSON-RPC 2.0 request / notification context. 152 * 153 * @param clientHostName The client host name, {@code null} if 154 * unknown. 155 * @param clientInetAddress The client IP address, {@code null} if 156 * unknown. 157 * @param secure Specifies a request received over HTTPS. 158 * @param principalNames Specifies the authenticated client principle 159 * names, {@code null} if unknown. The names 160 * must not be an empty or blank string. 161 */ 162 public MessageContext(final String clientHostName, 163 final String clientInetAddress, 164 final boolean secure, 165 final String[] principalNames) { 166 167 this.clientHostName = clientHostName; 168 this.clientInetAddress = clientInetAddress; 169 this.secure = secure; 170 171 if (principalNames != null) { 172 principals = new Principal[principalNames.length]; 173 174 for (int i=0; i < principals.length; i++) 175 principals[0] = new BasicPrincipal(principalNames[i]); 176 } 177 } 178 179 180 /** 181 * Creates a new JSON-RPC 2.0 request / notification context. No 182 * authenticated client principal is specified. 183 * 184 * @param clientHostName The client host name, {@code null} if 185 * unknown. 186 * @param clientInetAddress The client IP address, {@code null} if 187 * unknown. 188 * @param secure Specifies a request received over HTTPS. 189 */ 190 public MessageContext(final String clientHostName, 191 final String clientInetAddress, 192 final boolean secure) { 193 194 this.clientHostName = clientHostName; 195 this.clientInetAddress = clientInetAddress; 196 this.secure = secure; 197 } 198 199 200 /** 201 * Creates a new JSON-RPC 2.0 request / notification context. Indicates 202 * an insecure transport (plain HTTP) and no authenticated client 203 * principal. 204 * 205 * @param clientHostName The client host name, {@code null} if 206 * unknown. 207 * @param clientInetAddress The client IP address, {@code null} if 208 * unknown. 209 */ 210 public MessageContext(final String clientHostName, 211 final String clientInetAddress) { 212 213 this.clientHostName = clientHostName; 214 this.clientInetAddress = clientInetAddress; 215 this.secure = false; 216 } 217 218 219 /** 220 * Creates a new JSON-RPC 2.0 request / notification context. Indicates 221 * an insecure transport (plain HTTP) and no authenticated client 222 * principal. Not client host name / IP is specified. 223 */ 224 public MessageContext() { 225 226 this.secure = false; 227 } 228 229 230 /** 231 * Creates a new JSON-RPC 2.0 request / notification context from the 232 * specified HTTP request. 233 * 234 * @param httpRequest The HTTP request. 235 */ 236 public MessageContext(final HttpServletRequest httpRequest) { 237 238 clientInetAddress = httpRequest.getRemoteAddr(); 239 240 clientHostName = httpRequest.getRemoteHost(); 241 242 if (clientHostName != null && clientHostName.equals(clientInetAddress)) 243 clientHostName = null; // not resolved actually 244 245 secure = httpRequest.isSecure(); 246 247 X509Certificate[] certs = (X509Certificate[])httpRequest.getAttribute("javax.servlet.request.X509Certificate"); 248 249 if (certs != null && certs.length > 0) { 250 251 principals = new Principal[certs.length]; 252 253 for (int i=0; i < principals.length; i++) 254 principals[i] = certs[i].getSubjectX500Principal(); 255 } 256 } 257 258 259 /** 260 * Creates a new JSON-RPC 2.0 request / notification context from the 261 * specified URL connection. Use this constructor in cases when the 262 * HTTP server is the origin of the JSON-RPC 2.0 requests / 263 * notifications. If the IP address of the HTTP server cannot be 264 * resolved {@link #getClientInetAddress} will return {@code null}. 265 * 266 * @param connection The URL connection, must be established and not 267 * {@code null}. 268 */ 269 public MessageContext(final URLConnection connection) { 270 271 clientHostName = connection.getURL().getHost(); 272 273 InetAddress ip = null; 274 275 if (clientHostName != null) { 276 277 try { 278 ip = InetAddress.getByName(clientHostName); 279 280 } catch (Exception e) { 281 282 // UnknownHostException, SecurityException 283 // ignore 284 } 285 } 286 287 if (ip != null) 288 clientInetAddress = ip.getHostAddress(); 289 290 291 if (connection instanceof HttpsURLConnection) { 292 293 secure = true; 294 295 HttpsURLConnection httpsConnection = (HttpsURLConnection)connection; 296 297 Principal prn = null; 298 299 try { 300 prn = httpsConnection.getPeerPrincipal(); 301 302 } catch (Exception e) { 303 304 // SSLPeerUnverifiedException, IllegalStateException 305 // ignore 306 } 307 308 if (prn != null) { 309 310 principals = new Principal[1]; 311 principals[0] = prn; 312 } 313 } 314 } 315 316 317 /** 318 * Gets the host name of the client that sent the request / 319 * notification. 320 * 321 * @return The client host name, {@code null} if unknown. 322 */ 323 public String getClientHostName() { 324 325 return clientHostName; 326 } 327 328 329 /** 330 * Gets the IP address of the client that sent the request / 331 * notification. 332 * 333 * @return The client IP address, {@code null} if unknown. 334 */ 335 public String getClientInetAddress() { 336 337 return clientInetAddress; 338 } 339 340 341 /** 342 * Indicates whether the request / notification was received over a 343 * secure HTTPS connection. 344 * 345 * @return {@code true} If the request was received over HTTPS, 346 * {@code false} if it was received over plain HTTP. 347 */ 348 public boolean isSecure() { 349 350 return secure; 351 } 352 353 354 /** 355 * Returns the first authenticated client principal, {@code null} if 356 * none. 357 * 358 * @return The first client principal, {@code null} if none. 359 */ 360 public Principal getPrincipal() { 361 362 if (principals != null) 363 return principals[0]; 364 else 365 return null; 366 } 367 368 369 /** 370 * Returns the authenticated client principals, {@code null} if 371 * none. 372 * 373 * @return The client principals, {@code null} if none. 374 */ 375 public Principal[] getPrincipals() { 376 377 return principals; 378 } 379 380 381 /** 382 * Returns the first authenticated client principal name, {@code null} 383 * if none. 384 * 385 * @return The first client principal name, {@code null} if none. 386 */ 387 public String getPrincipalName() { 388 389 if (principals != null) 390 return principals[0].getName(); 391 else 392 return null; 393 } 394 395 396 /** 397 * Returns the authenticated client principal names, {@code null} 398 * if none. 399 * 400 * @return The client principal names, {@code null} if none. 401 */ 402 public String[] getPrincipalNames() { 403 404 String[] names = new String[principals.length]; 405 406 for (int i=0; i < names.length; i++) 407 names[i] = principals[i].getName(); 408 409 return names; 410 } 411 412 413 @Override 414 public String toString() { 415 416 String s = "[host=" + clientHostName + " hostIP=" + clientInetAddress + " secure=" + secure; 417 418 if (principals != null) { 419 420 int i = 0; 421 422 for (Principal p: principals) 423 s += " principal[" + (i++) + "]=" + p; 424 } 425 426 return s + "]"; 427 } 428}