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