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.util.*;
022
023import net.jcip.annotations.NotThreadSafe;
024
025import com.nimbusds.oauth2.sdk.ParseException;
026import com.nimbusds.oauth2.sdk.util.StringUtils;
027
028
029/**
030 * Prompts for end-user re-authentication and consent.
031 *
032 * <p>Related specifications:
033 *
034 * <ul>
035 *     <li>OpenID Connect Core 1.0, section 3.1.2.1.
036 *     <li>Initiating User Registration via OpenID Connect (draft 04)
037 * </ul>
038 */
039@NotThreadSafe
040public class Prompt extends LinkedHashSet<Prompt.Type> {
041        
042        
043        private static final long serialVersionUID = -3672900533669609699L;
044        
045        
046        /**
047         * Enumeration of the prompt types.
048         */
049        public enum Type {
050        
051        
052                /** 
053                 * The authorisation server must not display any authentication 
054                 * or consent UI pages. An error is returned if the end user is 
055                 * not already authenticated or the client does not have 
056                 * pre-configured consent for the requested {@code scope}. This 
057                 * can be used as a method to check for existing authentication 
058                 * and / or consent.
059                 */
060                NONE,
061
062
063                /**
064                 * The authorisation server must prompt the end-user for 
065                 * re-authentication.
066                 */
067                LOGIN,
068
069
070                /**
071                 * The authorisation server must prompt the end-user for 
072                 * consent before returning information to the client.
073                 */
074                CONSENT,
075
076
077                /**
078                 * The authorisation server must prompt the end-user to select
079                 * a user account. This allows a user who has multiple accounts 
080                 * at the authorisation server to select amongst the multiple 
081                 * accounts that they may have current sessions for.
082                 */
083                SELECT_ACCOUNT,
084                
085                
086                /**
087                 * The client desires the OpenID provider to present the
088                 * end-user with an account creation user interface instead of
089                 * the normal login flow. Care must be taken if combining this
090                 * value with other prompt values. Mutually exclusive
091                 * conditions can arise so it is RECOMMENDED that create not be
092                 * present with any other values.
093                 */
094                CREATE;
095                
096                
097                /**
098                 * Returns the string identifier of this prompt type.
099                 *
100                 * @return The string identifier.
101                 */
102                @Override
103                public String toString() {
104                
105                        return super.toString().toLowerCase();
106                }
107                
108                
109                /**
110                 * Parses a prompt type.
111                 *
112                 * @param s The string to parse.
113                 *
114                 * @return The prompt type.
115                 *
116                 * @throws ParseException If the parsed string is {@code null} 
117                 *                        or doesn't match a prompt type.
118                 */
119                public static Type parse(final String s)
120                        throws ParseException {
121
122                        if (StringUtils.isBlank(s))
123                                throw new ParseException("Null or empty prompt type string");
124                        
125                        if ("none".equals(s)) {
126                                return NONE;
127                        } else if ("login".equals(s)) {
128                                return LOGIN;
129                        } else if ("consent".equals(s)) {
130                                return CONSENT;
131                        } else if ("select_account".equals(s)) {
132                                return SELECT_ACCOUNT;
133                        } else if ("create".equals(s)) {
134                                return CREATE;
135                        } else {
136                                throw new ParseException("Unknown prompt type: " + s);
137                        }
138                }
139        }
140        
141        
142        /**
143         * Creates a new empty prompt.
144         */
145        public Prompt() {
146        
147                // Nothing to do
148        }
149
150
151        /**
152         * Creates a new prompt with the specified types.
153         *
154         * @param type The prompt types.
155         */
156        public Prompt(final Type ... type) {
157
158                addAll(Arrays.asList(type));
159        }
160
161
162        /**
163         * Creates a new prompt with the specified type values.
164         *
165         * @param values The prompt type values.
166         *
167         * @throws java.lang.IllegalArgumentException If the type value is
168         *                                            invalid.
169         */
170        public Prompt(final String ... values) {
171
172                for (String v: values) {
173
174                        try {
175                                add(Type.parse(v));
176
177                        } catch (ParseException e) {
178
179                                throw new IllegalArgumentException(e.getMessage(), e);
180                        }
181                }
182        }
183        
184        
185        /**
186         * Checks if the prompt is valid. This is done by examining the prompt
187         * for a conflicting {@link Type#NONE} value.
188         *
189         * @return {@code true} if this prompt if valid, else {@code false}.
190         */
191        public boolean isValid() {
192
193                return !(size() > 1 && contains(Type.NONE));
194        }
195        
196        
197        /**
198         * Returns the string list representation of this prompt.
199         * 
200         * @return The string list representation.
201         */
202        public List<String> toStringList() {
203                
204                List<String> list = new ArrayList<>(this.size());
205                
206                for (Type t: this)
207                        list.add(t.toString());
208                
209                return list;
210        }
211        
212        
213        /**
214         * Returns the string representation of this prompt. The values are 
215         * delimited by space.
216         *
217         * <p>Example:
218         *
219         * <pre>
220         * login consent
221         * </pre>
222         *
223         * @return The string representation.
224         */
225        @Override
226        public String toString() {
227        
228                StringBuilder sb = new StringBuilder();
229        
230                Iterator<Type> it = super.iterator();
231                
232                while (it.hasNext()) {
233                
234                        sb.append(it.next().toString());
235                        
236                        if (it.hasNext())
237                                sb.append(" ");
238                }
239        
240                return sb.toString();
241        }
242        
243        
244        /**
245         * Parses a prompt from the specified string list.
246         * 
247         * @param collection The string list to parse, with one or more
248         *                   non-conflicting prompt types. May be {@code null}.
249         *
250         * @return The prompt, {@code null} if the parsed string list was
251         *         {@code null} or empty.
252         * 
253         * @throws ParseException If the string list couldn't be parsed to a
254         *                        valid prompt.
255         */
256        public static Prompt parse(final Collection<String> collection)
257                throws ParseException {
258                
259                if (collection == null)
260                        return null;
261                
262                Prompt prompt = new Prompt();
263                
264                for (String s: collection)
265                        prompt.add(Prompt.Type.parse(s));
266                
267                if (! prompt.isValid())
268                        throw new ParseException("Invalid prompt: " + collection);
269                
270                return prompt;  
271        }
272        
273        
274        /**
275         * Parses a prompt from the specified string.
276         *
277         * @param s The string to parse, with one or more non-conflicting space
278         *          delimited prompt types. May be {@code null}.
279         *
280         * @return The prompt, {@code null} if the parsed string was 
281         *         {@code null} or empty.
282         *
283         * @throws ParseException If the string couldn't be parsed to a valid
284         *                        prompt.
285         */
286        public static Prompt parse(final String s)
287                throws ParseException {
288        
289                if (StringUtils.isBlank(s))
290                        return null;
291        
292                Prompt prompt = new Prompt();
293                
294                StringTokenizer st = new StringTokenizer(s, " ");
295
296                while (st.hasMoreTokens())
297                        prompt.add(Prompt.Type.parse(st.nextToken()));
298                
299                if (! prompt.isValid())
300                        throw new ParseException("Invalid prompt: " + s);
301                
302                return prompt;
303        }
304}