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