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