001package com.nimbusds.openid.connect.sdk; 002 003 004import java.net.MalformedURLException; 005import java.net.URI; 006import java.net.URISyntaxException; 007import java.net.URL; 008import java.util.LinkedHashMap; 009import java.util.Map; 010 011import net.jcip.annotations.Immutable; 012 013import org.apache.commons.lang3.StringUtils; 014 015import com.nimbusds.jwt.JWT; 016import com.nimbusds.jwt.JWTParser; 017 018import com.nimbusds.oauth2.sdk.AbstractRequest; 019import com.nimbusds.oauth2.sdk.ParseException; 020import com.nimbusds.oauth2.sdk.SerializeException; 021import com.nimbusds.oauth2.sdk.http.HTTPRequest; 022import com.nimbusds.oauth2.sdk.id.State; 023import com.nimbusds.oauth2.sdk.util.URIUtils; 024import com.nimbusds.oauth2.sdk.util.URLUtils; 025 026 027/** 028 * OpenID Connect logout request initiated by the relying party (RP). 029 * 030 * <p>Example HTTP request: 031 * 032 * <pre> 033 * https://server.example.com/op/logout? 034 * id_token_hint=eyJhbGciOiJSUzI1NiJ9.eyJpc3Mi... 035 * &post_logout_redirect_uri=https%3A%2F%2Fclient.example.org%2Fpost-logout 036 * &state=af0ifjsldkj 037 * </pre> 038 * 039 * <p>Related specifications: 040 * 041 * <ul> 042 * <li>OpenID Connect Session Management 1.0, section 5. 043 * </ul> 044 */ 045@Immutable 046public class LogoutRequest extends AbstractRequest { 047 048 049 /** 050 * The required ID token hint. 051 */ 052 private final JWT idTokenHint; 053 054 055 /** 056 * The optional post-logout redirection URI. 057 */ 058 private final URI postLogoutRedirectURI; 059 060 061 /** 062 * The optional state parameter. 063 */ 064 private final State state; 065 066 067 /** 068 * Creates a new OpenID Connect logout request. 069 * 070 * @param uri The URI of the end-session endpoint. 071 * May be {@code null} if the 072 * {@link #toHTTPRequest} method will not 073 * be used. 074 * @param idTokenHint The ID token hint. Must not be 075 * {@code null}. 076 * @param postLogoutRedirectURI The optional post-logout redirection 077 * URI, {@code null} if not specified. 078 * @param state The optional state parameter for a 079 * post-logout redirection URI, 080 * {@code null} if not specified. 081 */ 082 public LogoutRequest(final URI uri, 083 final JWT idTokenHint, 084 final URI postLogoutRedirectURI, 085 final State state) { 086 087 super(uri); 088 089 if (idTokenHint == null) { 090 throw new IllegalArgumentException("The ID token hint must not be null"); 091 } 092 093 this.idTokenHint = idTokenHint; 094 095 this.postLogoutRedirectURI = postLogoutRedirectURI; 096 097 if (postLogoutRedirectURI == null && state != null) { 098 throw new IllegalArgumentException("The state parameter required a post-logout redirection URI"); 099 } 100 101 this.state = state; 102 } 103 104 105 /** 106 * Creates a new OpenID Connect logout request with a post-logout 107 * redirection. 108 * 109 * @param uri The URI of the end-session endpoint. May be 110 * {@code null} if the {@link #toHTTPRequest} method 111 * will not be used. 112 * @param idTokenHint The ID token hint. Must not be {@code null}. 113 */ 114 public LogoutRequest(final URI uri, 115 final JWT idTokenHint) { 116 117 this(uri, idTokenHint, null, null); 118 } 119 120 121 /** 122 * Returns the ID token hint. 123 * 124 * @return The ID token hint. 125 */ 126 public JWT getIDTokenHint() { 127 128 return idTokenHint; 129 } 130 131 132 /** 133 * Return the post-logout redirection URI. 134 * 135 * @return The post-logout redirection URI, {@code null} if not 136 * specified. 137 */ 138 public URI getPostLogoutRedirectionURI() { 139 140 return postLogoutRedirectURI; 141 } 142 143 144 /** 145 * Returns the state parameter for a post-logout redirection URI. 146 * 147 * @return The state parameter, {@code null} if not specified. 148 */ 149 public State getState() { 150 151 return state; 152 } 153 154 /** 155 * Returns the parameters for this authorisation request. 156 * 157 * <p>Example parameters: 158 * 159 * <pre> 160 * id_token_hint = eyJhbGciOiJSUzI1NiJ9.eyJpc3Mi... 161 * post_logout_redirect_uri = https://client.example.com/post-logout 162 * state = af0ifjsldkj 163 * </pre> 164 * 165 * @return The parameters. 166 */ 167 public Map<String,String> toParameters() { 168 169 Map <String,String> params = new LinkedHashMap<>(); 170 171 try { 172 params.put("id_token_hint", idTokenHint.serialize()); 173 } catch (IllegalStateException e) { 174 throw new SerializeException("Couldn't serialize ID token: " + e.getMessage(), e); 175 } 176 177 if (postLogoutRedirectURI != null) { 178 params.put("post_logout_redirect_uri", postLogoutRedirectURI.toString()); 179 } 180 181 if (state != null) { 182 params.put("state", state.getValue()); 183 } 184 185 return params; 186 } 187 188 189 /** 190 * Returns the URI query string for this logout request. 191 * 192 * <p>Note that the '?' character preceding the query string in an URI 193 * is not included in the returned string. 194 * 195 * <p>Example URI query string: 196 * 197 * <pre> 198 * id_token_hint = eyJhbGciOiJSUzI1NiJ9.eyJpc3Mi... 199 * &post_logout_redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fpost-logout 200 * &state=af0ifjsldkj 201 * </pre> 202 * 203 * @return The URI query string. 204 */ 205 public String toQueryString() { 206 207 return URLUtils.serializeParameters(toParameters()); 208 } 209 210 211 /** 212 * Returns the complete URI representation for this logout request, 213 * consisting of the {@link #getEndpointURI end-session endpoint URI} 214 * with the {@link #toQueryString query string} appended. 215 * 216 * <p>Example URI: 217 * 218 * <pre> 219 * https://server.example.com/logout? 220 * id_token_hint = eyJhbGciOiJSUzI1NiJ9.eyJpc3Mi... 221 * &post_logout_redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fpost-logout 222 * &state=af0ifjsldkj 223 * </pre> 224 * 225 * @return The URI representation. 226 */ 227 public URI toURI() { 228 229 if (getEndpointURI() == null) 230 throw new SerializeException("The end-session endpoint URI is not specified"); 231 232 StringBuilder sb = new StringBuilder(getEndpointURI().toString()); 233 sb.append('?'); 234 sb.append(toQueryString()); 235 try { 236 return new URI(sb.toString()); 237 } catch (URISyntaxException e) { 238 throw new SerializeException("Couldn't append query string: " + e.getMessage(), e); 239 } 240 } 241 242 243 @Override 244 public HTTPRequest toHTTPRequest() { 245 246 if (getEndpointURI() == null) 247 throw new SerializeException("The endpoint URI is not specified"); 248 249 HTTPRequest httpRequest; 250 251 URL endpointURL; 252 253 try { 254 endpointURL = getEndpointURI().toURL(); 255 256 } catch (MalformedURLException e) { 257 258 throw new SerializeException(e.getMessage(), e); 259 } 260 261 httpRequest = new HTTPRequest(HTTPRequest.Method.GET, endpointURL); 262 263 httpRequest.setQuery(toQueryString()); 264 265 return httpRequest; 266 } 267 268 269 /** 270 * Parses a logout request from the specified parameters. 271 * 272 * <p>Example parameters: 273 * 274 * <pre> 275 * id_token_hint = eyJhbGciOiJSUzI1NiJ9.eyJpc3Mi... 276 * post_logout_redirect_uri = https://client.example.com/post-logout 277 * state = af0ifjsldkj 278 * </pre> 279 * 280 * @param params The parameters. Must not be {@code null}. 281 * 282 * @return The logout request. 283 * 284 * @throws ParseException If the parameters couldn't be parsed to a 285 * logout request. 286 */ 287 public static LogoutRequest parse(final Map<String,String> params) 288 throws ParseException { 289 290 return parse(null, params); 291 } 292 293 294 /** 295 * Parses a logout request from the specified parameters. 296 * 297 * <p>Example parameters: 298 * 299 * <pre> 300 * id_token_hint = eyJhbGciOiJSUzI1NiJ9.eyJpc3Mi... 301 * post_logout_redirect_uri = https://client.example.com/post-logout 302 * state = af0ifjsldkj 303 * </pre> 304 * 305 * @param uri The URI of the end-session endpoint. May be 306 * {@code null} if the {@link #toHTTPRequest()} method 307 * will not be used. 308 * @param params The parameters. Must not be {@code null}. 309 * 310 * @return The logout request. 311 * 312 * @throws ParseException If the parameters couldn't be parsed to a 313 * logout request. 314 */ 315 public static LogoutRequest parse(final URI uri, final Map<String,String> params) 316 throws ParseException { 317 318 String v = params.get("id_token_hint"); 319 320 if (StringUtils.isBlank(v)) 321 throw new ParseException("Missing \"id_token_hint\" parameter"); 322 323 JWT idTokenHint; 324 325 try { 326 idTokenHint = JWTParser.parse(v); 327 } catch (java.text.ParseException e) { 328 throw new ParseException("Invalid ID token hint: " + e.getMessage(), e); 329 } 330 331 v = params.get("post_logout_redirect_uri"); 332 333 URI postLogoutRedirectURI = null; 334 335 if (StringUtils.isNotBlank(v)) { 336 337 try { 338 postLogoutRedirectURI = new URI(v); 339 } catch (URISyntaxException e) { 340 throw new ParseException("Invalid \"post_logout_redirect_uri\" parameter: " + e.getMessage(), e); 341 } 342 } 343 344 State state = null; 345 346 v = params.get("state"); 347 348 if (postLogoutRedirectURI != null && StringUtils.isNotBlank(v)) { 349 state = new State(v); 350 } 351 352 return new LogoutRequest(uri, idTokenHint, postLogoutRedirectURI, state); 353 } 354 355 356 /** 357 * Parses a logout request from the specified URI query string. 358 * 359 * <p>Example URI query string: 360 * 361 * <pre> 362 * id_token_hint = eyJhbGciOiJSUzI1NiJ9.eyJpc3Mi... 363 * &post_logout_redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fpost-logout 364 * &state=af0ifjsldkj 365 * </pre> 366 * 367 * @param query The URI query string. Must not be {@code null}. 368 * 369 * @return The logout request. 370 * 371 * @throws ParseException If the query string couldn't be parsed to a 372 * logout request. 373 */ 374 public static LogoutRequest parse(final String query) 375 throws ParseException { 376 377 return parse(null, URLUtils.parseParameters(query)); 378 } 379 380 381 /** 382 * Parses a logout request from the specified URI query string. 383 * 384 * <p>Example URI query string: 385 * 386 * <pre> 387 * id_token_hint = eyJhbGciOiJSUzI1NiJ9.eyJpc3Mi... 388 * &post_logout_redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fpost-logout 389 * &state=af0ifjsldkj 390 * </pre> 391 * 392 * @param uri The URI of the end-session endpoint. May be 393 * {@code null} if the {@link #toHTTPRequest()} method 394 * will not be used. 395 * @param query The URI query string. Must not be {@code null}. 396 * 397 * @return The logout request. 398 * 399 * @throws ParseException If the query string couldn't be parsed to a 400 * logout request. 401 */ 402 public static LogoutRequest parse(final URI uri, final String query) 403 throws ParseException { 404 405 return parse(uri, URLUtils.parseParameters(query)); 406 } 407 408 409 /** 410 * Parses a logout request from the specified URI. 411 * 412 * <p>Example URI: 413 * 414 * <pre> 415 * https://server.example.com/logout? 416 * id_token_hint = eyJhbGciOiJSUzI1NiJ9.eyJpc3Mi... 417 * &post_logout_redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fpost-logout 418 * &state=af0ifjsldkj 419 * </pre> 420 * 421 * @param uri The URI. Must not be {@code null}. 422 * 423 * @return The logout request. 424 * 425 * @throws ParseException If the URI couldn't be parsed to a logout 426 * request. 427 */ 428 public static LogoutRequest parse(final URI uri) 429 throws ParseException { 430 431 return parse(URIUtils.getBaseURI(uri), URLUtils.parseParameters(uri.getRawQuery())); 432 } 433 434 435 /** 436 * Parses a logout request from the specified HTTP request. 437 * 438 * <p>Example HTTP request (GET): 439 * 440 * <pre> 441 * https://server.example.com/logout? 442 * id_token_hint = eyJhbGciOiJSUzI1NiJ9.eyJpc3Mi... 443 * &post_logout_redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fpost-logout 444 * &state=af0ifjsldkj 445 * </pre> 446 * 447 * @param httpRequest The HTTP request. Must not be {@code null}. 448 * 449 * @return The logout request. 450 * 451 * @throws ParseException If the HTTP request couldn't be parsed to a 452 * logout request. 453 */ 454 public static LogoutRequest parse(final HTTPRequest httpRequest) 455 throws ParseException { 456 457 String query = httpRequest.getQuery(); 458 459 if (query == null) 460 throw new ParseException("Missing URI query string"); 461 462 try { 463 return parse(URIUtils.getBaseURI(httpRequest.getURL().toURI()), query); 464 465 } catch (URISyntaxException e) { 466 467 throw new ParseException(e.getMessage(), e); 468 } 469 } 470}