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