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