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