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