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