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}