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                        switch (s) {
095                                case "none":
096                                        return NONE;
097                                case "login":
098                                        return LOGIN;
099                                case "consent":
100                                        return CONSENT;
101                                case "select_account":
102                                        return SELECT_ACCOUNT;
103                                default:
104                                        throw new ParseException("Unknown prompt type: " + s);
105                        }
106                }
107        }
108        
109        
110        /**
111         * Creates a new empty prompt.
112         */
113        public Prompt() {
114        
115                // Nothing to do
116        }
117
118
119        /**
120         * Creates a new prompt with the specified types.
121         *
122         * @param type The prompt types.
123         */
124        public Prompt(final Type ... type) {
125
126                addAll(Arrays.asList(type));
127        }
128
129
130        /**
131         * Creates a new prompt with the specified type values.
132         *
133         * @param values The prompt type values.
134         *
135         * @throws java.lang.IllegalArgumentException If the type value is
136         *                                            invalid.
137         */
138        public Prompt(final String ... values) {
139
140                for (String v: values) {
141
142                        try {
143                                add(Type.parse(v));
144
145                        } catch (ParseException e) {
146
147                                throw new IllegalArgumentException(e.getMessage(), e);
148                        }
149                }
150        }
151        
152        
153        /**
154         * Checks if the prompt is valid. This is done by examining the prompt
155         * for a conflicting {@link Type#NONE} value.
156         *
157         * @return {@code true} if this prompt if valid, else {@code false}.
158         */
159        public boolean isValid() {
160
161                return !(size() > 1 && contains(Type.NONE));
162        }
163        
164        
165        /**
166         * Returns the string list representation of this prompt.
167         * 
168         * @return The string list representation.
169         */
170        public List<String> toStringList() {
171                
172                List<String> list = new ArrayList<>(this.size());
173                
174                for (Type t: this)
175                        list.add(t.toString());
176                
177                return list;
178        }
179        
180        
181        /**
182         * Returns the string representation of this prompt. The values are 
183         * delimited by space.
184         *
185         * <p>Example:
186         *
187         * <pre>
188         * login consent
189         * </pre>
190         *
191         * @return The string representation.
192         */
193        @Override
194        public String toString() {
195        
196                StringBuilder sb = new StringBuilder();
197        
198                Iterator<Type> it = super.iterator();
199                
200                while (it.hasNext()) {
201                
202                        sb.append(it.next().toString());
203                        
204                        if (it.hasNext())
205                                sb.append(" ");
206                }
207        
208                return sb.toString();
209        }
210        
211        
212        /**
213         * Parses a prompt from the specified string list.
214         * 
215         * @param collection The string list to parse, with one or more
216         *                   non-conflicting prompt types. May be {@code null}.
217         *
218         * @return The prompt, {@code null} if the parsed string list was
219         *         {@code null} or empty.
220         * 
221         * @throws ParseException If the string list couldn't be parsed to a
222         *                        valid prompt.
223         */
224        public static Prompt parse(final Collection<String> collection)
225                throws ParseException {
226                
227                if (collection == null)
228                        return null;
229                
230                Prompt prompt = new Prompt();
231                
232                for (String s: collection)
233                        prompt.add(Prompt.Type.parse(s));
234                
235                if (! prompt.isValid())
236                        throw new ParseException("Invalid prompt: " + collection);
237                
238                return prompt;  
239        }
240        
241        
242        /**
243         * Parses a prompt from the specified string.
244         *
245         * @param s The string to parse, with one or more non-conflicting space
246         *          delimited prompt types. May be {@code null}.
247         *
248         * @return The prompt, {@code null} if the parsed string was 
249         *         {@code null} or empty.
250         *
251         * @throws ParseException If the string couldn't be parsed to a valid
252         *                        prompt.
253         */
254        public static Prompt parse(final String s)
255                throws ParseException {
256        
257                if (StringUtils.isBlank(s))
258                        return null;
259        
260                Prompt prompt = new Prompt();
261                
262                StringTokenizer st = new StringTokenizer(s, " ");
263
264                while (st.hasMoreTokens())
265                        prompt.add(Prompt.Type.parse(st.nextToken()));
266                
267                if (! prompt.isValid())
268                        throw new ParseException("Invalid prompt: " + s);
269                
270                return prompt;
271        }
272}