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