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