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.setBody(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}