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