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.oauth2.sdk;
019
020
021import java.util.Arrays;
022import java.util.Collection;
023import java.util.HashSet;
024import java.util.StringTokenizer;
025
026import net.jcip.annotations.Immutable;
027import net.jcip.annotations.NotThreadSafe;
028
029import com.nimbusds.oauth2.sdk.id.Identifier;
030import com.nimbusds.oauth2.sdk.util.StringUtils;
031import com.nimbusds.openid.connect.sdk.OIDCResponseTypeValue;
032
033
034/**
035 * Authorisation response type.
036 *
037 * <p>Example response type implying an authorisation code flow:
038 *
039 * <pre>
040 * ResponseType rt = ResponseType.CODE;
041 * </pre>
042 *
043 * <p>Example response type from OpenID Connect specifying an ID token and an
044 * access token (implies implicit flow):
045 *
046 * <pre>
047 * ResponseType rt = ResponseType.IDTOKEN_TOKEN);
048 * </pre>
049 *
050 * <p>The following helper methods can be used to find out the implied OAuth
051 * 2.0 protocol flow for a response type:
052 *
053 * <ul>
054 *     <li>{@link #impliesImplicitFlow}
055 *     <li>{@link #impliesCodeFlow}
056 * </ul>
057 *
058 * <p>Related specifications:
059 *
060 * <ul>
061 *     <li>OAuth 2.0 (RFC 6749), sections 3.1.1 and 4.1.1.
062 *     <li>OAuth 2.0 Multiple Response Type Encoding Practices.
063 * </ul>
064 */
065@NotThreadSafe
066public class ResponseType extends HashSet<ResponseType.Value> {
067        
068        
069        /**
070         * Constant for {@code response_type=code}.
071         */
072        public static final ResponseType CODE = new ResponseType(true, Value.CODE);
073        
074        
075        /**
076         * Constant for {@code response_type=token}.
077         */
078        public static final ResponseType TOKEN = new ResponseType(true, Value.TOKEN);
079        
080        
081        /**
082         * Constant for {@code response_type=id_token token}.
083         */
084        public static final ResponseType IDTOKEN_TOKEN = new ResponseType(true, OIDCResponseTypeValue.ID_TOKEN, Value.TOKEN);
085        
086        
087        /**
088         * Constant for {@code response_type=id_token}.
089         */
090        public static final ResponseType IDTOKEN = new ResponseType(true, OIDCResponseTypeValue.ID_TOKEN);
091        
092        
093        /**
094         * Constant for {@code response_type=code id_token}.
095         */
096        public static final ResponseType CODE_IDTOKEN = new ResponseType(true, Value.CODE, OIDCResponseTypeValue.ID_TOKEN);
097        
098        
099        /**
100         * Constant for {@code response_type=code token}.
101         */
102        public static final ResponseType CODE_TOKEN = new ResponseType(true, Value.CODE, Value.TOKEN);
103        
104        
105        /**
106         * Constant for {@code response_type=code id_token token}.
107         */
108        public static final ResponseType CODE_IDTOKEN_TOKEN = new ResponseType(true, Value.CODE, OIDCResponseTypeValue.ID_TOKEN, Value.TOKEN);
109        
110        
111        private static final long serialVersionUID = 1351973244616920112L;
112        
113        
114        /**
115         * Authorisation response type value.
116         */
117        @Immutable
118        public static final class Value extends Identifier {
119
120                /**
121                 * Authorisation code.
122                 */
123                public static final Value CODE = new Value("code");
124
125                
126                /**
127                 * Access token, with optional refresh token.
128                 */
129                public static final Value TOKEN = new Value("token");
130                
131                
132                private static final long serialVersionUID = 5339971450891463852L;
133                
134                
135                /**
136                 * Creates a new response type value.
137                 *
138                 * @param value The response type value. Must not be
139                 *              {@code null} or empty string.
140                 */
141                public Value(final String value) {
142
143                        super(value);
144                }
145                
146                
147                @Override
148                public boolean equals(final Object object) {
149
150                        return object instanceof Value &&
151                               this.toString().equals(object.toString());
152                }
153        }
154
155        
156        /**
157         *  Gets the default response type.
158         * 
159         * @return The default response type, consisting of the value
160         *         {@link ResponseType.Value#CODE}.
161         */
162        public static ResponseType getDefault() {
163                
164                return ResponseType.CODE;
165        }
166        
167        
168        /**
169         * If {@code true} flags the response type as unmodifiable.
170         */
171        private final boolean unmodifiable;
172
173        
174        /**
175         * Creates a new empty response type.
176         */
177        public ResponseType() {
178                super();
179                unmodifiable = false;
180        }
181
182
183        /**
184         * Creates a new response type with the specified string values.
185         *
186         * @param values The string values. Must not be {@code null}.
187         */
188        public ResponseType(final String ... values) {
189
190                for (String v: values) {
191                        add(new Value(v));
192                }
193                
194                unmodifiable = false;
195        }
196
197
198        /**
199         * Creates a new response type with the specified values.
200         *
201         * @param values The values. Must not be {@code null}.
202         */
203        public ResponseType(final Value ... values) {
204                this(false, values);
205        }
206
207
208        /**
209         * Creates a new response type with the specified values.
210         *
211         * @param unmodifiable If {@code true} flags the response type as
212         *                     unmodifiable.
213         * @param values       The values. Must not be {@code null}.
214         */
215        private ResponseType(final boolean unmodifiable, final Value ... values) {
216                super(Arrays.asList(values));
217                this.unmodifiable = unmodifiable;
218        }
219        
220        
221        /**
222         * Parses a set of authorisation response types.
223         *
224         * @param s Space-delimited list of one or more authorisation response 
225         *          types.
226         *
227         * @return The authorisation response types set.
228         *
229         * @throws ParseException If the parsed string is {@code null} or 
230         *                        empty.
231         */
232        public static ResponseType parse(final String s)
233                throws ParseException {
234        
235                if (StringUtils.isBlank(s))
236                        throw new ParseException("Null or empty response type string");
237        
238                ResponseType rt = new ResponseType();
239                
240                StringTokenizer st = new StringTokenizer(s, " ");
241
242                while (st.hasMoreTokens())
243                        rt.add(new ResponseType.Value(st.nextToken()));
244                
245                return rt;
246        }
247        
248        
249        /**
250         * Returns {@code true} if this response type implies an authorisation
251         * code flow.
252         *
253         * <p>Code flow response_type values: code
254         *
255         * @return {@code true} if a code flow is implied, else {@code false}.
256         */
257        public boolean impliesCodeFlow() {
258                
259                return this.equals(new ResponseType(Value.CODE));
260        }
261        
262        
263        /**
264         * Returns {@code true} if this response type implies an implicit flow.
265         *
266         * <p>Implicit flow response_type values: token, id_token token,
267         * id_token
268         *
269         * @return {@code true} if an implicit flow is implied, else 
270         *         {@code false}.
271         */
272        public boolean impliesImplicitFlow() {
273        
274                return
275                        this.equals(new ResponseType(Value.TOKEN)) ||
276                        this.equals(new ResponseType(OIDCResponseTypeValue.ID_TOKEN, Value.TOKEN)) ||
277                        this.equals(new ResponseType(OIDCResponseTypeValue.ID_TOKEN));
278        }
279        
280        
281        /**
282         * Returns {@code true} if this response type implies an OpenID Connect
283         * hybrid flow.
284         *
285         * <p>Hybrid flow response_type values: code id_token, code token,
286         * code id_token token
287         *
288         * @return {@code true} if a hybrid flow is implied, else
289         *         {@code false}.
290         */
291        public boolean impliesHybridFlow() {
292        
293                return
294                        this.equals(new ResponseType(Value.CODE, OIDCResponseTypeValue.ID_TOKEN)) ||
295                        this.equals(new ResponseType(Value.CODE, Value.TOKEN)) ||
296                        this.equals(new ResponseType(Value.CODE, OIDCResponseTypeValue.ID_TOKEN, Value.TOKEN));
297        }
298
299
300        /**
301         * Checks if this response type contains the specified string value.
302         *
303         * @param value The string value. Must not be {@code null}.
304         *
305         * @return {@code true} if the value is contained, else {@code false}.
306         */
307        public boolean contains(final String value) {
308
309                return contains(new Value(value));
310        }
311        
312        
313        /**
314         * Returns the string representation of this  authorisation response 
315         * type.
316         *
317         * <p>Example serialised response types:
318         *
319         * <pre>
320         * code
321         * token
322         * id_token
323         * id_token token
324         * code token
325         * code id_token
326         * code id_token token
327         * </pre>
328         *
329         * @return Space delimited string representing the authorisation 
330         *         response type.
331         */
332        @Override
333        public String toString() {
334        
335                StringBuilder sb = new StringBuilder();
336
337                for (ResponseType.Value v: this) {
338
339                        if (sb.length() > 0)
340                                sb.append(' ');
341
342                        sb.append(v.getValue());
343                }
344
345                return sb.toString();
346        }
347        
348        
349        @Override
350        public boolean add(Value value) {
351                if (unmodifiable) {
352                        throw new UnsupportedOperationException();
353                }
354                return super.add(value);
355        }
356        
357        
358        @Override
359        public boolean remove(Object o) {
360                if (unmodifiable) {
361                        throw new UnsupportedOperationException();
362                }
363                return super.remove(o);
364        }
365        
366        
367        @Override
368        public void clear() {
369                if (unmodifiable) {
370                        throw new UnsupportedOperationException();
371                }
372                super.clear();
373        }
374        
375        
376        @Override
377        public boolean removeAll(Collection<?> c) {
378                if (unmodifiable) {
379                        throw new UnsupportedOperationException();
380                }
381                return super.removeAll(c);
382        }
383        
384        
385        @Override
386        public boolean addAll(Collection<? extends Value> c) {
387                if (unmodifiable) {
388                        throw new UnsupportedOperationException();
389                }
390                return super.addAll(c);
391        }
392        
393        
394        @Override
395        public boolean retainAll(Collection<?> c) {
396                if (unmodifiable) {
397                        throw new UnsupportedOperationException();
398                }
399                return super.retainAll(c);
400        }
401}