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.Collections; 026import java.util.LinkedHashMap; 027import java.util.List; 028import java.util.Map; 029 030import net.jcip.annotations.Immutable; 031 032import com.nimbusds.common.contenttype.ContentType; 033import com.nimbusds.jwt.JWT; 034import com.nimbusds.jwt.JWTParser; 035import com.nimbusds.jwt.PlainJWT; 036import com.nimbusds.oauth2.sdk.AbstractRequest; 037import com.nimbusds.oauth2.sdk.ParseException; 038import com.nimbusds.oauth2.sdk.SerializeException; 039import com.nimbusds.oauth2.sdk.http.HTTPRequest; 040import com.nimbusds.oauth2.sdk.util.MultivaluedMapUtils; 041import com.nimbusds.oauth2.sdk.util.URIUtils; 042import com.nimbusds.oauth2.sdk.util.URLUtils; 043 044 045/** 046 * Back-channel logout request initiated by an OpenID provider (OP). 047 * 048 * <p>Example HTTP request: 049 * 050 * <pre> 051 * POST /backchannel_logout HTTP/1.1 052 * Host: rp.example.org 053 * Content-Type: application/x-www-form-urlencoded 054 * 055 * logout_token=eyJhbGci ... .eyJpc3Mi ... .T3BlbklE ... 056 * </pre> 057 * 058 * <p>Related specifications: 059 * 060 * <ul> 061 * <li>OpenID Connect Back-Channel Logout 1.0, section 2.5 (draft 04). 062 * </ul> 063 */ 064@Immutable 065public class BackChannelLogoutRequest extends AbstractRequest { 066 067 068 /** 069 * The logout token. 070 */ 071 private final JWT logoutToken; 072 073 074 /** 075 * Creates a new back-channel logout request. 076 * 077 * @param uri The back-channel logout URI. May be {@code null} 078 * if the {@link #toHTTPRequest} method will not be 079 * used. 080 * @param logoutToken The logout token. Must be signed, or signed and 081 * encrypted. Must not be {@code null}. 082 */ 083 public BackChannelLogoutRequest(final URI uri, 084 final JWT logoutToken) { 085 086 super(uri); 087 088 if (logoutToken == null) { 089 throw new IllegalArgumentException("The logout token must not be null"); 090 } 091 092 if (logoutToken instanceof PlainJWT) { 093 throw new IllegalArgumentException("The logout token must not be unsecured (plain)"); 094 } 095 096 this.logoutToken = logoutToken; 097 } 098 099 100 /** 101 * Returns the logout token. 102 * 103 * @return The logout token. 104 */ 105 public JWT getLogoutToken() { 106 107 return logoutToken; 108 } 109 110 111 /** 112 * Returns the parameters for this back-channel logout request. 113 * 114 * <p>Example parameters: 115 * 116 * <pre> 117 * logout_token=eyJhbGci ... .eyJpc3Mi ... .T3BlbklE ... 118 * </pre> 119 * 120 * @return The parameters. 121 */ 122 public Map<String,List<String>> toParameters() { 123 124 Map <String,List<String>> params = new LinkedHashMap<>(); 125 126 try { 127 params.put("logout_token", Collections.singletonList(logoutToken.serialize())); 128 } catch (IllegalStateException e) { 129 throw new SerializeException("Couldn't serialize logout token: " + e.getMessage(), e); 130 } 131 132 return params; 133 } 134 135 136 @Override 137 public HTTPRequest toHTTPRequest() { 138 139 if (getEndpointURI() == null) 140 throw new SerializeException("The endpoint URI is not specified"); 141 142 HTTPRequest httpRequest; 143 144 URL endpointURL; 145 146 try { 147 endpointURL = getEndpointURI().toURL(); 148 149 } catch (MalformedURLException e) { 150 151 throw new SerializeException(e.getMessage(), e); 152 } 153 154 httpRequest = new HTTPRequest(HTTPRequest.Method.POST, endpointURL); 155 httpRequest.setEntityContentType(ContentType.APPLICATION_URLENCODED); 156 httpRequest.setQuery(URLUtils.serializeParameters(toParameters())); 157 158 return httpRequest; 159 } 160 161 162 /** 163 * Parses a back-channel logout request from the specified request body 164 * parameters. 165 * 166 * <p>Example parameters: 167 * 168 * <pre> 169 * logout_token = eyJhbGci ... .eyJpc3Mi ... .T3BlbklE ... 170 * </pre> 171 * 172 * @param params The parameters. Must not be {@code null}. 173 * 174 * @return The back-channel logout request. 175 * 176 * @throws ParseException If the parameters couldn't be parsed to a 177 * back-channel logout request. 178 */ 179 public static BackChannelLogoutRequest parse(final Map<String,List<String>> params) 180 throws ParseException { 181 182 return parse(null, params); 183 } 184 185 186 /** 187 * Parses a back-channel logout request from the specified URI and 188 * request body parameters. 189 * 190 * <p>Example parameters: 191 * 192 * <pre> 193 * logout_token = eyJhbGci ... .eyJpc3Mi ... .T3BlbklE ... 194 * </pre> 195 * 196 * @param uri The back-channel logout URI. May be {@code null} if 197 * the {@link #toHTTPRequest()} method will not be used. 198 * @param params The parameters. Must not be {@code null}. 199 * 200 * @return The back-channel logout request. 201 * 202 * @throws ParseException If the parameters couldn't be parsed to a 203 * back-channel logout request. 204 */ 205 public static BackChannelLogoutRequest parse(final URI uri, Map<String,List<String>> params) 206 throws ParseException { 207 208 String logoutTokenString = MultivaluedMapUtils.getFirstValue(params, "logout_token"); 209 210 if (logoutTokenString == null) { 211 throw new ParseException("Missing logout_token parameter"); 212 } 213 214 JWT logoutToken; 215 216 try { 217 logoutToken = JWTParser.parse(logoutTokenString); 218 } catch (java.text.ParseException e) { 219 throw new ParseException("Invalid logout token: " + e.getMessage(), e); 220 } 221 222 try { 223 return new BackChannelLogoutRequest(uri, logoutToken); 224 } catch (IllegalArgumentException e) { 225 throw new ParseException(e.getMessage(), e); 226 } 227 } 228 229 230 /** 231 * Parses a back-channel logout request from the specified HTTP request. 232 * 233 * <p>Example HTTP request (POST): 234 * 235 * <pre> 236 * POST /backchannel_logout HTTP/1.1 237 * Host: rp.example.org 238 * Content-Type: application/x-www-form-urlencoded 239 * 240 * logout_token=eyJhbGci ... .eyJpc3Mi ... .T3BlbklE ... 241 * </pre> 242 * 243 * @param httpRequest The HTTP request. Must not be {@code null}. 244 * 245 * @return The back-channel logout request. 246 * 247 * @throws ParseException If the HTTP request couldn't be parsed to a 248 * back-channel logout request. 249 */ 250 public static BackChannelLogoutRequest parse(final HTTPRequest httpRequest) 251 throws ParseException { 252 253 if (! HTTPRequest.Method.POST.equals(httpRequest.getMethod())) { 254 throw new ParseException("HTTP POST required"); 255 } 256 257 // Lenient on content-type 258 259 String query = httpRequest.getQuery(); 260 261 if (query == null) 262 throw new ParseException("Missing URI query string"); 263 264 Map<String,List<String>> params = URLUtils.parseParameters(query); 265 266 try { 267 return parse(URIUtils.getBaseURI(httpRequest.getURL().toURI()), params); 268 269 } catch (URISyntaxException e) { 270 271 throw new ParseException(e.getMessage(), e); 272 } 273 } 274}