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 switch (s) { 095 case "none": 096 return NONE; 097 case "login": 098 return LOGIN; 099 case "consent": 100 return CONSENT; 101 case "select_account": 102 return SELECT_ACCOUNT; 103 default: 104 throw new ParseException("Unknown prompt type: " + s); 105 } 106 } 107 } 108 109 110 /** 111 * Creates a new empty prompt. 112 */ 113 public Prompt() { 114 115 // Nothing to do 116 } 117 118 119 /** 120 * Creates a new prompt with the specified types. 121 * 122 * @param type The prompt types. 123 */ 124 public Prompt(final Type ... type) { 125 126 addAll(Arrays.asList(type)); 127 } 128 129 130 /** 131 * Creates a new prompt with the specified type values. 132 * 133 * @param values The prompt type values. 134 * 135 * @throws java.lang.IllegalArgumentException If the type value is 136 * invalid. 137 */ 138 public Prompt(final String ... values) { 139 140 for (String v: values) { 141 142 try { 143 add(Type.parse(v)); 144 145 } catch (ParseException e) { 146 147 throw new IllegalArgumentException(e.getMessage(), e); 148 } 149 } 150 } 151 152 153 /** 154 * Checks if the prompt is valid. This is done by examining the prompt 155 * for a conflicting {@link Type#NONE} value. 156 * 157 * @return {@code true} if this prompt if valid, else {@code false}. 158 */ 159 public boolean isValid() { 160 161 return !(size() > 1 && contains(Type.NONE)); 162 } 163 164 165 /** 166 * Returns the string list representation of this prompt. 167 * 168 * @return The string list representation. 169 */ 170 public List<String> toStringList() { 171 172 List<String> list = new ArrayList<>(this.size()); 173 174 for (Type t: this) 175 list.add(t.toString()); 176 177 return list; 178 } 179 180 181 /** 182 * Returns the string representation of this prompt. The values are 183 * delimited by space. 184 * 185 * <p>Example: 186 * 187 * <pre> 188 * login consent 189 * </pre> 190 * 191 * @return The string representation. 192 */ 193 @Override 194 public String toString() { 195 196 StringBuilder sb = new StringBuilder(); 197 198 Iterator<Type> it = super.iterator(); 199 200 while (it.hasNext()) { 201 202 sb.append(it.next().toString()); 203 204 if (it.hasNext()) 205 sb.append(" "); 206 } 207 208 return sb.toString(); 209 } 210 211 212 /** 213 * Parses a prompt from the specified string list. 214 * 215 * @param collection The string list to parse, with one or more 216 * non-conflicting prompt types. May be {@code null}. 217 * 218 * @return The prompt, {@code null} if the parsed string list was 219 * {@code null} or empty. 220 * 221 * @throws ParseException If the string list couldn't be parsed to a 222 * valid prompt. 223 */ 224 public static Prompt parse(final Collection<String> collection) 225 throws ParseException { 226 227 if (collection == null) 228 return null; 229 230 Prompt prompt = new Prompt(); 231 232 for (String s: collection) 233 prompt.add(Prompt.Type.parse(s)); 234 235 if (! prompt.isValid()) 236 throw new ParseException("Invalid prompt: " + collection); 237 238 return prompt; 239 } 240 241 242 /** 243 * Parses a prompt from the specified string. 244 * 245 * @param s The string to parse, with one or more non-conflicting space 246 * delimited prompt types. May be {@code null}. 247 * 248 * @return The prompt, {@code null} if the parsed string was 249 * {@code null} or empty. 250 * 251 * @throws ParseException If the string couldn't be parsed to a valid 252 * prompt. 253 */ 254 public static Prompt parse(final String s) 255 throws ParseException { 256 257 if (StringUtils.isBlank(s)) 258 return null; 259 260 Prompt prompt = new Prompt(); 261 262 StringTokenizer st = new StringTokenizer(s, " "); 263 264 while (st.hasMoreTokens()) 265 prompt.add(Prompt.Type.parse(st.nextToken())); 266 267 if (! prompt.isValid()) 268 throw new ParseException("Invalid prompt: " + s); 269 270 return prompt; 271 } 272}