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.oauth2.sdk.AbstractRequest;
031import com.nimbusds.oauth2.sdk.ParseException;
032import com.nimbusds.oauth2.sdk.SerializeException;
033import com.nimbusds.oauth2.sdk.http.HTTPRequest;
034import com.nimbusds.oauth2.sdk.id.State;
035import com.nimbusds.oauth2.sdk.util.URIUtils;
036import com.nimbusds.oauth2.sdk.util.URLUtils;
037import net.jcip.annotations.Immutable;
038import org.apache.commons.lang3.StringUtils;
039
040
041/**
042 * Logout request initiated by an OpenID relying party (RP).
043 *
044 * <p>Example HTTP request:
045 *
046 * <pre>
047 * https://server.example.com/op/logout?
048 * id_token_hint=eyJhbGciOiJSUzI1NiJ9.eyJpc3Mi...
049 * &amp;post_logout_redirect_uri=https%3A%2F%2Fclient.example.org%2Fpost-logout
050 * &amp;state=af0ifjsldkj
051 * </pre>
052 *
053 * <p>Related specifications:
054 *
055 * <ul>
056 *     <li>OpenID Connect Session Management 1.0, section 5.
057 * </ul>
058 */
059@Immutable
060public class LogoutRequest extends AbstractRequest {
061
062
063        /**
064         * The ID token hint (recommended).
065         */
066        private final JWT idTokenHint;
067
068
069        /**
070         * The optional post-logout redirection URI.
071         */
072        private final URI postLogoutRedirectURI;
073
074
075        /**
076         * The optional state parameter.
077         */
078        private final State state;
079
080
081        /**
082         * Creates a new OpenID Connect logout request.
083         *
084         * @param uri                   The URI of the end-session endpoint.
085         *                              May be {@code null} if the
086         *                              {@link #toHTTPRequest} method will not
087         *                              be used.
088         * @param idTokenHint           The ID token hint (recommended),
089         *                              {@code null} if not specified.
090         * @param postLogoutRedirectURI The optional post-logout redirection
091         *                              URI, {@code null} if not specified.
092         * @param state                 The optional state parameter for the
093         *                              post-logout redirection URI,
094         *                              {@code null} if not specified.
095         */
096        public LogoutRequest(final URI uri,
097                             final JWT idTokenHint,
098                             final URI postLogoutRedirectURI,
099                             final State state) {
100
101                super(uri);
102
103                this.idTokenHint = idTokenHint;
104
105                this.postLogoutRedirectURI = postLogoutRedirectURI;
106
107                if (postLogoutRedirectURI == null && state != null) {
108                        throw new IllegalArgumentException("The state parameter required a post-logout redirection URI");
109                }
110
111                this.state = state;
112        }
113
114
115        /**
116         * Creates a new OpenID Connect logout request without a post-logout
117         * redirection.
118         *
119         * @param uri         The URI of the end-session endpoint. May be
120         *                    {@code null} if the {@link #toHTTPRequest} method
121         *                    will not be used.
122         * @param idTokenHint The ID token hint (recommended), {@code null} if
123         *                    not specified.
124         */
125        public LogoutRequest(final URI uri,
126                             final JWT idTokenHint) {
127
128                this(uri, idTokenHint, null, null);
129        }
130        
131        
132        /**
133         * Creates a new OpenID Connect logout request without a post-logout
134         * redirection.
135         *
136         * @param uri The URI of the end-session endpoint. May be {@code null}
137         *            if the {@link #toHTTPRequest} method will not be used.
138         */
139        public LogoutRequest(final URI uri) {
140                
141                this(uri, null, null, null);
142        }
143
144
145        /**
146         * Returns the ID token hint.
147         *
148         * @return The ID token hint, {@code null} if not specified.
149         */
150        public JWT getIDTokenHint() {
151
152                return idTokenHint;
153        }
154
155
156        /**
157         * Return the post-logout redirection URI.
158         *
159         * @return The post-logout redirection URI, {@code null} if not
160         *         specified.
161         */
162        public URI getPostLogoutRedirectionURI() {
163
164                return postLogoutRedirectURI;
165        }
166
167
168        /**
169         * Returns the state parameter for a post-logout redirection URI.
170         *
171         * @return The state parameter, {@code null} if not specified.
172         */
173        public State getState() {
174
175                return state;
176        }
177
178        /**
179         * Returns the parameters for this logout request.
180         *
181         * <p>Example parameters:
182         *
183         * <pre>
184         * id_token_hint = eyJhbGciOiJSUzI1NiJ9.eyJpc3Mi...
185         * post_logout_redirect_uri = https://client.example.com/post-logout
186         * state = af0ifjsldkj
187         * </pre>
188         *
189         * @return The parameters.
190         */
191        public Map<String,String> toParameters() {
192
193                Map <String,String> params = new LinkedHashMap<>();
194                
195                if (idTokenHint != null) {
196                        try {
197                                params.put("id_token_hint", idTokenHint.serialize());
198                        } catch (IllegalStateException e) {
199                                throw new SerializeException("Couldn't serialize ID token: " + e.getMessage(), e);
200                        }
201                }
202
203                if (postLogoutRedirectURI != null) {
204                        params.put("post_logout_redirect_uri", postLogoutRedirectURI.toString());
205                }
206
207                if (state != null) {
208                        params.put("state", state.getValue());
209                }
210
211                return params;
212        }
213
214
215        /**
216         * Returns the URI query string for this logout request.
217         *
218         * <p>Note that the '?' character preceding the query string in an URI
219         * is not included in the returned string.
220         *
221         * <p>Example URI query string:
222         *
223         * <pre>
224         * id_token_hint = eyJhbGciOiJSUzI1NiJ9.eyJpc3Mi...
225         * &amp;post_logout_redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fpost-logout
226         * &amp;state=af0ifjsldkj
227         * </pre>
228         *
229         * @return The URI query string.
230         */
231        public String toQueryString() {
232
233                return URLUtils.serializeParameters(toParameters());
234        }
235
236
237        /**
238         * Returns the complete URI representation for this logout request,
239         * consisting of the {@link #getEndpointURI end-session endpoint URI}
240         * with the {@link #toQueryString query string} appended.
241         *
242         * <p>Example URI:
243         *
244         * <pre>
245         * https://server.example.com/logout?
246         * id_token_hint = eyJhbGciOiJSUzI1NiJ9.eyJpc3Mi...
247         * &amp;post_logout_redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fpost-logout
248         * &amp;state=af0ifjsldkj
249         * </pre>
250         *
251         * @return The URI representation.
252         */
253        public URI toURI() {
254
255                if (getEndpointURI() == null)
256                        throw new SerializeException("The end-session endpoint URI is not specified");
257                
258                String query = toQueryString();
259                
260                if (StringUtils.isNotBlank(query)) {
261                        query = '?' + query;
262                }
263                
264                try {
265                        return new URI(getEndpointURI() + query);
266                } catch (URISyntaxException e) {
267                        throw new SerializeException("Couldn't append query string: " + e.getMessage(), e);
268                }
269        }
270
271
272        @Override
273        public HTTPRequest toHTTPRequest() {
274
275                if (getEndpointURI() == null)
276                        throw new SerializeException("The endpoint URI is not specified");
277
278                HTTPRequest httpRequest;
279
280                URL endpointURL;
281
282                try {
283                        endpointURL = getEndpointURI().toURL();
284
285                } catch (MalformedURLException e) {
286
287                        throw new SerializeException(e.getMessage(), e);
288                }
289
290                httpRequest = new HTTPRequest(HTTPRequest.Method.GET, endpointURL);
291
292                httpRequest.setQuery(toQueryString());
293
294                return httpRequest;
295        }
296
297
298        /**
299         * Parses a logout request from the specified parameters.
300         *
301         * <p>Example parameters:
302         *
303         * <pre>
304         * id_token_hint = eyJhbGciOiJSUzI1NiJ9.eyJpc3Mi...
305         * post_logout_redirect_uri = https://client.example.com/post-logout
306         * state = af0ifjsldkj
307         * </pre>
308         *
309         * @param params The parameters, empty map if none. Must not be
310         *               {@code null}.
311         *
312         * @return The logout request.
313         *
314         * @throws ParseException If the parameters couldn't be parsed to a
315         *                        logout request.
316         */
317        public static LogoutRequest parse(final Map<String,String> params)
318                throws ParseException {
319
320                return parse(null, params);
321        }
322
323
324        /**
325         * Parses a logout request from the specified parameters.
326         *
327         * <p>Example parameters:
328         *
329         * <pre>
330         * id_token_hint = eyJhbGciOiJSUzI1NiJ9.eyJpc3Mi...
331         * post_logout_redirect_uri = https://client.example.com/post-logout
332         * state = af0ifjsldkj
333         * </pre>
334         *
335         * @param uri    The URI of the end-session endpoint. May be
336         *               {@code null} if the {@link #toHTTPRequest()} method
337         *               will not be used.
338         * @param params The parameters, empty map if none. Must not be
339         *               {@code null}.
340         *
341         * @return The logout request.
342         *
343         * @throws ParseException If the parameters couldn't be parsed to a
344         *                        logout request.
345         */
346        public static LogoutRequest parse(final URI uri, final Map<String,String> params)
347                throws ParseException {
348
349                String v = params.get("id_token_hint");
350
351                JWT idTokenHint = null;
352                
353                if (StringUtils.isNotBlank(v)) {
354                        
355                        try {
356                                idTokenHint = JWTParser.parse(v);
357                        } catch (java.text.ParseException e) {
358                                throw new ParseException("Invalid ID token hint: " + e.getMessage(), e);
359                        }
360                }
361
362                v = params.get("post_logout_redirect_uri");
363
364                URI postLogoutRedirectURI = null;
365
366                if (StringUtils.isNotBlank(v)) {
367
368                        try {
369                                postLogoutRedirectURI = new URI(v);
370                        } catch (URISyntaxException e) {
371                                throw new ParseException("Invalid \"post_logout_redirect_uri\" parameter: " + e.getMessage(),  e);
372                        }
373                }
374
375                State state = null;
376
377                v = params.get("state");
378
379                if (postLogoutRedirectURI != null && StringUtils.isNotBlank(v)) {
380                        state = new State(v);
381                }
382
383                return new LogoutRequest(uri, idTokenHint, postLogoutRedirectURI, state);
384        }
385
386
387        /**
388         * Parses a logout request from the specified URI query string.
389         *
390         * <p>Example URI query string:
391         *
392         * <pre>
393         * id_token_hint = eyJhbGciOiJSUzI1NiJ9.eyJpc3Mi...
394         * &amp;post_logout_redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fpost-logout
395         * &amp;state=af0ifjsldkj
396         * </pre>
397         *
398         * @param query The URI query string, {@code null} if none.
399         *
400         * @return The logout request.
401         *
402         * @throws ParseException If the query string couldn't be parsed to a
403         *                        logout request.
404         */
405        public static LogoutRequest parse(final String query)
406                throws ParseException {
407
408                return parse(null, URLUtils.parseParameters(query));
409        }
410
411
412        /**
413         * Parses a logout request from the specified URI query string.
414         *
415         * <p>Example URI query string:
416         *
417         * <pre>
418         * id_token_hint = eyJhbGciOiJSUzI1NiJ9.eyJpc3Mi...
419         * &amp;post_logout_redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fpost-logout
420         * &amp;state=af0ifjsldkj
421         * </pre>
422         *
423         * @param uri   The URI of the end-session endpoint. May be
424         *              {@code null} if the {@link #toHTTPRequest()} method
425         *              will not be used.
426         * @param query The URI query string, {@code null} if none.
427         *
428         * @return The logout request.
429         *
430         * @throws ParseException If the query string couldn't be parsed to a
431         *                        logout request.
432         */
433        public static LogoutRequest parse(final URI uri, final String query)
434                throws ParseException {
435
436                return parse(uri, URLUtils.parseParameters(query));
437        }
438
439
440        /**
441         * Parses a logout request from the specified URI.
442         *
443         * <p>Example URI:
444         *
445         * <pre>
446         * https://server.example.com/logout?
447         * id_token_hint = eyJhbGciOiJSUzI1NiJ9.eyJpc3Mi...
448         * &amp;post_logout_redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fpost-logout
449         * &amp;state=af0ifjsldkj
450         * </pre>
451         *
452         * @param uri The URI. Must not be {@code null}.
453         *
454         * @return The logout request.
455         *
456         * @throws ParseException If the URI couldn't be parsed to a logout
457         *                        request.
458         */
459        public static LogoutRequest parse(final URI uri)
460                throws ParseException {
461
462                return parse(URIUtils.getBaseURI(uri), URLUtils.parseParameters(uri.getRawQuery()));
463        }
464
465
466        /**
467         * Parses a logout request from the specified HTTP request.
468         *
469         * <p>Example HTTP request (GET):
470         *
471         * <pre>
472         * https://server.example.com/logout?
473         * id_token_hint = eyJhbGciOiJSUzI1NiJ9.eyJpc3Mi...
474         * &amp;post_logout_redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fpost-logout
475         * &amp;state=af0ifjsldkj
476         * </pre>
477         *
478         * @param httpRequest The HTTP request. Must not be {@code null}.
479         *
480         * @return The logout request.
481         *
482         * @throws ParseException If the HTTP request couldn't be parsed to a
483         *                        logout request.
484         */
485        public static LogoutRequest parse(final HTTPRequest httpRequest)
486                throws ParseException {
487
488                String query = httpRequest.getQuery();
489
490                if (query == null)
491                        throw new ParseException("Missing URI query string");
492
493                try {
494                        return parse(URIUtils.getBaseURI(httpRequest.getURL().toURI()), query);
495
496                } catch (URISyntaxException e) {
497
498                        throw new ParseException(e.getMessage(), e);
499                }
500        }
501}