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}