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