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