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