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