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 * OpenID Connect logout request initiated by the 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 authorisation 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. Must not be {@code null}.
310         *
311         * @return The logout request.
312         *
313         * @throws ParseException If the parameters couldn't be parsed to a
314         *                        logout request.
315         */
316        public static LogoutRequest parse(final Map<String,String> params)
317                throws ParseException {
318
319                return parse(null, params);
320        }
321
322
323        /**
324         * Parses a logout request from the specified parameters.
325         *
326         * <p>Example parameters:
327         *
328         * <pre>
329         * id_token_hint = eyJhbGciOiJSUzI1NiJ9.eyJpc3Mi...
330         * post_logout_redirect_uri = https://client.example.com/post-logout
331         * state = af0ifjsldkj
332         * </pre>
333         *
334         * @param uri    The URI of the end-session endpoint. May be
335         *               {@code null} if the {@link #toHTTPRequest()} method
336         *               will not be used.
337         * @param params The parameters. Must not be {@code null}.
338         *
339         * @return The logout request.
340         *
341         * @throws ParseException If the parameters couldn't be parsed to a
342         *                        logout request.
343         */
344        public static LogoutRequest parse(final URI uri, final Map<String,String> params)
345                throws ParseException {
346
347                String v = params.get("id_token_hint");
348
349                JWT idTokenHint = null;
350                
351                if (StringUtils.isNotBlank(v)) {
352                        
353                        try {
354                                idTokenHint = JWTParser.parse(v);
355                        } catch (java.text.ParseException e) {
356                                throw new ParseException("Invalid ID token hint: " + e.getMessage(), e);
357                        }
358                }
359
360                v = params.get("post_logout_redirect_uri");
361
362                URI postLogoutRedirectURI = null;
363
364                if (StringUtils.isNotBlank(v)) {
365
366                        try {
367                                postLogoutRedirectURI = new URI(v);
368                        } catch (URISyntaxException e) {
369                                throw new ParseException("Invalid \"post_logout_redirect_uri\" parameter: " + e.getMessage(),  e);
370                        }
371                }
372
373                State state = null;
374
375                v = params.get("state");
376
377                if (postLogoutRedirectURI != null && StringUtils.isNotBlank(v)) {
378                        state = new State(v);
379                }
380
381                return new LogoutRequest(uri, idTokenHint, postLogoutRedirectURI, state);
382        }
383
384
385        /**
386         * Parses a logout request from the specified URI query string.
387         *
388         * <p>Example URI query string:
389         *
390         * <pre>
391         * id_token_hint = eyJhbGciOiJSUzI1NiJ9.eyJpc3Mi...
392         * &amp;post_logout_redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fpost-logout
393         * &amp;state=af0ifjsldkj
394         * </pre>
395         *
396         * @param query The URI query string. Must not be {@code null}.
397         *
398         * @return The logout request.
399         *
400         * @throws ParseException If the query string couldn't be parsed to a
401         *                        logout request.
402         */
403        public static LogoutRequest parse(final String query)
404                throws ParseException {
405
406                return parse(null, URLUtils.parseParameters(query));
407        }
408
409
410        /**
411         * Parses a logout request from the specified URI query string.
412         *
413         * <p>Example URI query string:
414         *
415         * <pre>
416         * id_token_hint = eyJhbGciOiJSUzI1NiJ9.eyJpc3Mi...
417         * &amp;post_logout_redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fpost-logout
418         * &amp;state=af0ifjsldkj
419         * </pre>
420         *
421         * @param uri   The URI of the end-session endpoint. May be
422         *              {@code null} if the {@link #toHTTPRequest()} method
423         *              will not be used.
424         * @param query The URI query string. Must not be {@code null}.
425         *
426         * @return The logout request.
427         *
428         * @throws ParseException If the query string couldn't be parsed to a
429         *                        logout request.
430         */
431        public static LogoutRequest parse(final URI uri, final String query)
432                throws ParseException {
433
434                return parse(uri, URLUtils.parseParameters(query));
435        }
436
437
438        /**
439         * Parses a logout request from the specified URI.
440         *
441         * <p>Example URI:
442         *
443         * <pre>
444         * https://server.example.com/logout?
445         * id_token_hint = eyJhbGciOiJSUzI1NiJ9.eyJpc3Mi...
446         * &amp;post_logout_redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fpost-logout
447         * &amp;state=af0ifjsldkj
448         * </pre>
449         *
450         * @param uri The URI. Must not be {@code null}.
451         *
452         * @return The logout request.
453         *
454         * @throws ParseException If the URI couldn't be parsed to a logout
455         *                        request.
456         */
457        public static LogoutRequest parse(final URI uri)
458                throws ParseException {
459
460                return parse(URIUtils.getBaseURI(uri), URLUtils.parseParameters(uri.getRawQuery()));
461        }
462
463
464        /**
465         * Parses a logout request from the specified HTTP request.
466         *
467         * <p>Example HTTP request (GET):
468         *
469         * <pre>
470         * https://server.example.com/logout?
471         * id_token_hint = eyJhbGciOiJSUzI1NiJ9.eyJpc3Mi...
472         * &amp;post_logout_redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fpost-logout
473         * &amp;state=af0ifjsldkj
474         * </pre>
475         *
476         * @param httpRequest The HTTP request. Must not be {@code null}.
477         *
478         * @return The logout request.
479         *
480         * @throws ParseException If the HTTP request couldn't be parsed to a
481         *                        logout request.
482         */
483        public static LogoutRequest parse(final HTTPRequest httpRequest)
484                throws ParseException {
485
486                String query = httpRequest.getQuery();
487
488                if (query == null)
489                        throw new ParseException("Missing URI query string");
490
491                try {
492                        return parse(URIUtils.getBaseURI(httpRequest.getURL().toURI()), query);
493
494                } catch (URISyntaxException e) {
495
496                        throw new ParseException(e.getMessage(), e);
497                }
498        }
499}