001 package com.nimbusds.oauth2.sdk;
002
003
004 import java.net.MalformedURLException;
005 import java.net.URL;
006
007 import java.util.Collections;
008 import java.util.HashMap;
009 import java.util.HashSet;
010 import java.util.Map;
011 import java.util.Set;
012
013 import net.jcip.annotations.Immutable;
014
015 import com.nimbusds.oauth2.sdk.id.State;
016
017 import com.nimbusds.oauth2.sdk.http.HTTPResponse;
018
019 import com.nimbusds.oauth2.sdk.util.StringUtils;
020 import com.nimbusds.oauth2.sdk.util.URLUtils;
021
022
023 /**
024 * Authorisation error response. This class is immutable.
025 *
026 * <p>Standard authorisation errors:
027 *
028 * <ul>
029 * <li>{@link OAuth2Error#INVALID_REQUEST}
030 * <li>{@link OAuth2Error#UNAUTHORIZED_CLIENT}
031 * <li>{@link OAuth2Error#ACCESS_DENIED}
032 * <li>{@link OAuth2Error#UNSUPPORTED_RESPONSE_TYPE}
033 * <li>{@link OAuth2Error#INVALID_SCOPE}
034 * <li>{@link OAuth2Error#SERVER_ERROR}
035 * <li>{@link OAuth2Error#TEMPORARILY_UNAVAILABLE}
036 * </ul>
037 *
038 * <p>Example HTTP response:
039 *
040 * <pre>
041 * HTTP/1.1 302 Found
042 * Location: https://client.example.com/cb?
043 * error=invalid_request
044 * &error_description=the%20request%20is%20not%20valid%20or%20malformed
045 * &state=af0ifjsldkj
046 * </pre>
047 *
048 * <p>Related specifications:
049 *
050 * <ul>
051 * <li>OAuth 2.0 (RFC 6749), sections 4.1.2.1 and 4.2.2.1.
052 * </ul>
053 *
054 * @author Vladimir Dzhuvinov
055 * @version $version$ (2013-01-30)
056 */
057 @Immutable
058 public class AuthorizationErrorResponse
059 extends AuthorizationResponse
060 implements ErrorResponse {
061
062
063 /**
064 * The standard OAuth 2.0 errors for an Authorisation error response.
065 */
066 private static Set<ErrorObject> stdErrors = new HashSet<ErrorObject>();
067
068
069 static {
070 stdErrors.add(OAuth2Error.INVALID_REQUEST);
071 stdErrors.add(OAuth2Error.UNAUTHORIZED_CLIENT);
072 stdErrors.add(OAuth2Error.ACCESS_DENIED);
073 stdErrors.add(OAuth2Error.UNSUPPORTED_RESPONSE_TYPE);
074 stdErrors.add(OAuth2Error.INVALID_SCOPE);
075 stdErrors.add(OAuth2Error.SERVER_ERROR);
076 stdErrors.add(OAuth2Error.TEMPORARILY_UNAVAILABLE);
077 }
078
079
080 /**
081 * Gets the standard OAuth 2.0 errors for an Authorisation error
082 * response.
083 *
084 * @return The standard errors, as a read-only set.
085 */
086 public static Set<ErrorObject> getStandardErrors() {
087
088 return Collections.unmodifiableSet(stdErrors);
089 }
090
091
092 /**
093 * The error.
094 */
095 private final ErrorObject error;
096
097
098 /**
099 * The response type set, used to determine redirect URL composition.
100 * If unknown {@code null}.
101 */
102 private final ResponseTypeSet rts;
103
104
105 /**
106 * Creates a new authorisation error response.
107 *
108 * @param redirectURI The base redirect URI. Must not be {@code null}.
109 * @param error The error. Should match one of the
110 * {@link #getStandardErrors standard errors} for an
111 * authorisation error response. Must not be
112 * {@code null}.
113 * @param rts The response type set, used to determine the
114 * redirect URL composition. If unknown
115 * {@code null}.
116 * @param state The state, {@code null} if not requested.
117 */
118 public AuthorizationErrorResponse(final URL redirectURI,
119 final ErrorObject error,
120 final ResponseTypeSet rts,
121 final State state) {
122
123 super(redirectURI, state);
124
125 if (error == null)
126 throw new IllegalArgumentException("The error must not be null");
127
128 this.error = error;
129
130 this.rts = rts;
131 }
132
133
134 @Override
135 public ErrorObject getErrorObject() {
136
137 return error;
138 }
139
140
141 /**
142 * Gets the response type set.
143 *
144 * @return The response type set, {@code null} if not specified.
145 */
146 public ResponseTypeSet getResponseTypeSet() {
147
148 return rts;
149 }
150
151
152 @Override
153 public Map<String,String> toParameters() {
154
155 Map<String,String> params = new HashMap<String,String>();
156
157 params.put("error", error.getCode());
158
159 if (error.getDescription() != null)
160 params.put("error_description", error.getDescription());
161
162 if (error.getURI() != null)
163 params.put("error_uri", error.getURI().toString());
164
165 if (getState() != null)
166 params.put("state", getState().getValue());
167
168 return params;
169 }
170
171
172 @Override
173 public URL toURI()
174 throws SerializeException {
175
176 StringBuilder sb = new StringBuilder(getRedirectURI().toString());
177
178 if (rts == null || rts.contains(ResponseType.TOKEN))
179 sb.append("#");
180 else
181 sb.append("?");
182
183 sb.append(URLUtils.serializeParameters(toParameters()));
184
185 try {
186 return new URL(sb.toString());
187
188 } catch (MalformedURLException e) {
189
190 throw new SerializeException("Couldn't serialize redirect URL: " + e.getMessage(), e);
191 }
192 }
193
194
195 /**
196 * Parses an authorisation error response from the specified redirect
197 * URI and parameters.
198 *
199 * @param redirectURI The base redirect URI. Must not be {@code null}.
200 * @param params The response parameters to parse. Must not be
201 * {@code null}.
202 *
203 * @return The authorisation error response.
204 *
205 * @throws ParseException If the parameters couldn't be parsed to an
206 * authorisation error response.
207 */
208 public static AuthorizationErrorResponse parse(final URL redirectURI,
209 final Map<String,String> params)
210 throws ParseException {
211
212 // Parse the error
213 if (StringUtils.isUndefined(params.get("error")))
214 throw new ParseException("Missing error code");
215
216 // Parse error code
217 String errorCode = params.get("error");
218
219 String errorDescription = params.get("error_description");
220
221 String errorURIString = params.get("error_uri");
222
223 URL errorURI = null;
224
225 if (errorURIString != null) {
226
227 try {
228 errorURI = new URL(errorURIString);
229
230 } catch (MalformedURLException e) {
231
232 throw new ParseException("Invalid error URI: " + errorURIString, e);
233 }
234 }
235
236
237 ErrorObject error = new ErrorObject(errorCode, errorDescription, HTTPResponse.SC_FOUND, errorURI);
238
239
240 // State
241 State state = State.parse(params.get("state"));
242
243 return new AuthorizationErrorResponse(redirectURI, error, null, state);
244 }
245
246
247 /**
248 * Parses an authorisation error response from the specified URI.
249 *
250 * <p>Example URI:
251 *
252 * <pre>
253 * https://client.example.com/cb?
254 * error=invalid_request
255 * &error_description=the%20request%20is%20not%20valid%20or%20malformed
256 * &state=af0ifjsldkj
257 * </pre>
258 *
259 * @param uri The URI to parse. Can be absolute or relative. Must not
260 * be {@code null}.
261 *
262 * @return The authorisation error response.
263 *
264 * @throws ParseException If the URI couldn't be parsed to an
265 * authorisation error response.
266 */
267 public static AuthorizationErrorResponse parse(final URL uri)
268 throws ParseException {
269
270 Map<String,String> params = null;
271
272 if (uri.getRef() != null)
273 params = URLUtils.parseParameters(uri.getRef());
274
275 else if (uri.getQuery() != null)
276 params = URLUtils.parseParameters(uri.getQuery());
277
278 else
279 throw new ParseException("Missing URL fragment or query string");
280
281
282 return parse(URLUtils.getBaseURL(uri), params);
283 }
284
285
286 /**
287 * Parses an authorisation error response from the specified HTTP
288 * response.
289 *
290 * <p>Example HTTP response:
291 *
292 * <pre>
293 * HTTP/1.1 302 Found
294 * Location: https://client.example.com/cb?
295 * error=invalid_request
296 * &error_description=the%20request%20is%20not%20valid%20or%20malformed
297 * &state=af0ifjsldkj
298 * </pre>
299 *
300 * @param httpResponse The HTTP response to parse. Must not be
301 * {@code null}.
302 *
303 * @return The authorisation error response.
304 *
305 * @throws ParseException If the HTTP response couldn't be parsed to an
306 * authorisation error response.
307 */
308 public static AuthorizationErrorResponse parse(final HTTPResponse httpResponse)
309 throws ParseException {
310
311 if (httpResponse.getStatusCode() != HTTPResponse.SC_FOUND)
312 throw new ParseException("Unexpected HTTP status code, must be 302 (Found): " +
313 httpResponse.getStatusCode());
314
315 URL location = httpResponse.getLocation();
316
317 if (location == null)
318 throw new ParseException("Missing redirect URL / HTTP Location header");
319
320 return parse(location);
321 }
322 }