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     * &amp;error_description=the%20request%20is%20not%20valid%20or%20malformed
045     * &amp;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             * &amp;error_description=the%20request%20is%20not%20valid%20or%20malformed
256             * &amp;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             * &amp;error_description=the%20request%20is%20not%20valid%20or%20malformed
297             * &amp;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    }