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 * OpenID Connect logout request initiated by the 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 authorisation 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. Must not be {@code null}. 310 * 311 * @return The logout request. 312 * 313 * @throws ParseException If the parameters couldn't be parsed to a 314 * logout request. 315 */ 316 public static LogoutRequest parse(final Map<String,String> params) 317 throws ParseException { 318 319 return parse(null, params); 320 } 321 322 323 /** 324 * Parses a logout request from the specified parameters. 325 * 326 * <p>Example parameters: 327 * 328 * <pre> 329 * id_token_hint = eyJhbGciOiJSUzI1NiJ9.eyJpc3Mi... 330 * post_logout_redirect_uri = https://client.example.com/post-logout 331 * state = af0ifjsldkj 332 * </pre> 333 * 334 * @param uri The URI of the end-session endpoint. May be 335 * {@code null} if the {@link #toHTTPRequest()} method 336 * will not be used. 337 * @param params The parameters. Must not be {@code null}. 338 * 339 * @return The logout request. 340 * 341 * @throws ParseException If the parameters couldn't be parsed to a 342 * logout request. 343 */ 344 public static LogoutRequest parse(final URI uri, final Map<String,String> params) 345 throws ParseException { 346 347 String v = params.get("id_token_hint"); 348 349 JWT idTokenHint = null; 350 351 if (StringUtils.isNotBlank(v)) { 352 353 try { 354 idTokenHint = JWTParser.parse(v); 355 } catch (java.text.ParseException e) { 356 throw new ParseException("Invalid ID token hint: " + e.getMessage(), e); 357 } 358 } 359 360 v = params.get("post_logout_redirect_uri"); 361 362 URI postLogoutRedirectURI = null; 363 364 if (StringUtils.isNotBlank(v)) { 365 366 try { 367 postLogoutRedirectURI = new URI(v); 368 } catch (URISyntaxException e) { 369 throw new ParseException("Invalid \"post_logout_redirect_uri\" parameter: " + e.getMessage(), e); 370 } 371 } 372 373 State state = null; 374 375 v = params.get("state"); 376 377 if (postLogoutRedirectURI != null && StringUtils.isNotBlank(v)) { 378 state = new State(v); 379 } 380 381 return new LogoutRequest(uri, idTokenHint, postLogoutRedirectURI, state); 382 } 383 384 385 /** 386 * Parses a logout request from the specified URI query string. 387 * 388 * <p>Example URI query string: 389 * 390 * <pre> 391 * id_token_hint = eyJhbGciOiJSUzI1NiJ9.eyJpc3Mi... 392 * &post_logout_redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fpost-logout 393 * &state=af0ifjsldkj 394 * </pre> 395 * 396 * @param query The URI query string. Must not be {@code null}. 397 * 398 * @return The logout request. 399 * 400 * @throws ParseException If the query string couldn't be parsed to a 401 * logout request. 402 */ 403 public static LogoutRequest parse(final String query) 404 throws ParseException { 405 406 return parse(null, URLUtils.parseParameters(query)); 407 } 408 409 410 /** 411 * Parses a logout request from the specified URI query string. 412 * 413 * <p>Example URI query string: 414 * 415 * <pre> 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 of the end-session endpoint. May be 422 * {@code null} if the {@link #toHTTPRequest()} method 423 * will not be used. 424 * @param query The URI query string. Must not be {@code null}. 425 * 426 * @return The logout request. 427 * 428 * @throws ParseException If the query string couldn't be parsed to a 429 * logout request. 430 */ 431 public static LogoutRequest parse(final URI uri, final String query) 432 throws ParseException { 433 434 return parse(uri, URLUtils.parseParameters(query)); 435 } 436 437 438 /** 439 * Parses a logout request from the specified URI. 440 * 441 * <p>Example URI: 442 * 443 * <pre> 444 * https://server.example.com/logout? 445 * id_token_hint = eyJhbGciOiJSUzI1NiJ9.eyJpc3Mi... 446 * &post_logout_redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fpost-logout 447 * &state=af0ifjsldkj 448 * </pre> 449 * 450 * @param uri The URI. Must not be {@code null}. 451 * 452 * @return The logout request. 453 * 454 * @throws ParseException If the URI couldn't be parsed to a logout 455 * request. 456 */ 457 public static LogoutRequest parse(final URI uri) 458 throws ParseException { 459 460 return parse(URIUtils.getBaseURI(uri), URLUtils.parseParameters(uri.getRawQuery())); 461 } 462 463 464 /** 465 * Parses a logout request from the specified HTTP request. 466 * 467 * <p>Example HTTP request (GET): 468 * 469 * <pre> 470 * https://server.example.com/logout? 471 * id_token_hint = eyJhbGciOiJSUzI1NiJ9.eyJpc3Mi... 472 * &post_logout_redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fpost-logout 473 * &state=af0ifjsldkj 474 * </pre> 475 * 476 * @param httpRequest The HTTP request. Must not be {@code null}. 477 * 478 * @return The logout request. 479 * 480 * @throws ParseException If the HTTP request couldn't be parsed to a 481 * logout request. 482 */ 483 public static LogoutRequest parse(final HTTPRequest httpRequest) 484 throws ParseException { 485 486 String query = httpRequest.getQuery(); 487 488 if (query == null) 489 throw new ParseException("Missing URI query string"); 490 491 try { 492 return parse(URIUtils.getBaseURI(httpRequest.getURL().toURI()), query); 493 494 } catch (URISyntaxException e) { 495 496 throw new ParseException(e.getMessage(), e); 497 } 498 } 499}