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