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