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}