001    package com.nimbusds.oauth2.sdk.client;
002    
003    
004    import java.util.Collections;
005    import java.util.HashSet;
006    import java.util.Set;
007    
008    import net.jcip.annotations.Immutable;
009    
010    import org.apache.commons.lang3.StringUtils;
011    
012    import net.minidev.json.JSONObject;
013    
014    import com.nimbusds.oauth2.sdk.ErrorObject;
015    import com.nimbusds.oauth2.sdk.ErrorResponse;
016    import com.nimbusds.oauth2.sdk.ParseException;
017    import com.nimbusds.oauth2.sdk.http.CommonContentTypes;
018    import com.nimbusds.oauth2.sdk.http.HTTPResponse;
019    import com.nimbusds.oauth2.sdk.token.BearerTokenError;
020    import com.nimbusds.oauth2.sdk.util.JSONObjectUtils;
021    
022    
023    /**
024     * Client registration error response. This class is immutable.
025     *
026     * <p>Standard errors:
027     *
028     * <ul>
029     *     <li>OAuth 2.0 Bearer Token errors:
030     *         <ul>
031     *             <li>{@link com.nimbusds.oauth2.sdk.token.BearerTokenError#MISSING_TOKEN}
032     *             <li>{@link com.nimbusds.oauth2.sdk.token.BearerTokenError#INVALID_REQUEST}
033     *             <li>{@link com.nimbusds.oauth2.sdk.token.BearerTokenError#INVALID_TOKEN}
034     *             <li>{@link com.nimbusds.oauth2.sdk.token.BearerTokenError#INSUFFICIENT_SCOPE}
035     *          </ul>
036     *     <li>OpenID Connect specific errors:
037     *         <ul>
038     *             <li>{@link RegistrationError#INVALID_REDIRECT_URI}
039     *             <li>{@link RegistrationError#INVALID_CLIENT_METADATA}
040     *         </ul>
041     * </ul>
042     *
043     * <p>Example HTTP response:
044     *
045     * <pre>
046     * HTTP/1.1 400 Bad Request
047     * Content-Type: application/json
048     * Cache-Control: no-store
049     * Pragma: no-cache
050     *
051     * {
052     *  "error":"invalid_redirect_uri",
053     *  "error_description":"The redirect URI of http://sketchy.example.com is not allowed for this server."
054     * }
055     * </pre>
056     *
057     * <p>Related specifications:
058     *
059     * <ul>
060     *     <li>OAuth 2.0 Dynamic Client Registration Protocol 
061     *         (draft-ietf-oauth-dyn-reg-12), section 5.2.
062     *     <li>OAuth 2.0 Bearer Token Usage (RFC 6750), section 3.1.
063     * </ul>
064     *
065     * @author Vladimir Dzhuvinov
066     */
067    @Immutable
068    public class ClientRegistrationErrorResponse 
069            extends ClientRegistrationResponse
070            implements ErrorResponse {
071    
072    
073            /**
074             * Gets the standard errors for a client registration error response.
075             *
076             * @return The standard errors, as a read-only set.
077             */
078            public static Set<ErrorObject> getStandardErrors() {
079                    
080                    Set<ErrorObject> stdErrors = new HashSet<ErrorObject>();
081                    stdErrors.add(BearerTokenError.MISSING_TOKEN);
082                    stdErrors.add(BearerTokenError.INVALID_REQUEST);
083                    stdErrors.add(BearerTokenError.INVALID_TOKEN);
084                    stdErrors.add(BearerTokenError.INSUFFICIENT_SCOPE);
085                    stdErrors.add(RegistrationError.INVALID_REDIRECT_URI);
086                    stdErrors.add(RegistrationError.INVALID_CLIENT_METADATA);
087    
088                    return Collections.unmodifiableSet(stdErrors);
089            }
090    
091    
092            /**
093             * The underlying error.
094             */
095            private final ErrorObject error;
096    
097    
098            /**
099             * Creates a new client registration error response.
100             *
101             * @param error The error. Should match one of the 
102             *              {@link #getStandardErrors standard errors} for a client
103             *              registration error response. Must not be {@code null}.
104             */
105            public ClientRegistrationErrorResponse(final ErrorObject error) {
106    
107                    if (error == null)
108                            throw new IllegalArgumentException("The error must not be null");
109    
110                    this.error = error;
111            }
112    
113    
114            @Override
115            public ErrorObject getErrorObject() {
116    
117                    return error;
118            }
119    
120    
121            /**
122             * Returns the HTTP response for this client registration error 
123             * response.
124             *
125             * <p>Example HTTP response:
126             *
127             * <pre>
128             * HTTP/1.1 400 Bad Request
129             * Content-Type: application/json
130             * Cache-Control: no-store
131             * Pragma: no-cache
132             *
133             * {
134             *  "error":"invalid_redirect_uri",
135             *  "error_description":"The redirect URI of http://sketchy.example.com is not allowed for this server."
136             * }
137             * </pre>
138             *
139             * @return The HTTP response.
140             */
141            @Override
142            public HTTPResponse toHTTPResponse() {
143    
144                    HTTPResponse httpResponse = null;
145    
146                    if (error.getHTTPStatusCode() > 0)
147                            httpResponse = new HTTPResponse(error.getHTTPStatusCode());
148                    else
149                            httpResponse = new HTTPResponse(HTTPResponse.SC_BAD_REQUEST);
150    
151                    // Add the WWW-Authenticate header
152                    if (error instanceof BearerTokenError) {
153    
154                            BearerTokenError bte = (BearerTokenError)error;
155    
156                            httpResponse.setWWWAuthenticate(bte.toWWWAuthenticateHeader());
157    
158                    } else {
159                            JSONObject jsonObject = new JSONObject();
160    
161                            if (error.getCode() != null)
162                                    jsonObject.put("error", error.getCode());
163    
164                            if (error.getDescription() != null)
165                                    jsonObject.put("error_description", error.getDescription());
166    
167                            httpResponse.setContentType(CommonContentTypes.APPLICATION_JSON);
168    
169                            httpResponse.setContent(jsonObject.toString());
170                    }
171                    
172                    httpResponse.setCacheControl("no-store");
173                    httpResponse.setPragma("no-cache");
174    
175                    return httpResponse;
176            }
177    
178    
179            /**
180             * Parses a client registration error response from the specified HTTP 
181             * response.
182             *
183             * <p>Note: The HTTP status code is not checked for matching the error
184             * code semantics.
185             *
186             * @param httpResponse The HTTP response to parse. Its status code must
187             *                     not be 200 (OK). Must not be {@code null}.
188             *
189             * @throws ParseException If the HTTP response couldn't be parsed to a
190             *                        client registration error response.
191             */
192            public static ClientRegistrationErrorResponse parse(final HTTPResponse httpResponse)
193                    throws ParseException {
194                    
195                    httpResponse.ensureStatusCodeNotOK();
196    
197                    ErrorObject error = null;
198    
199                    String wwwAuth = httpResponse.getWWWAuthenticate();
200                    
201                    if (StringUtils.isNotBlank(wwwAuth)) {
202    
203                            error = BearerTokenError.parse(wwwAuth);
204    
205                    } else {
206                            
207                            String code = null;
208                            String description = null;
209                            
210                            if (CommonContentTypes.APPLICATION_JSON == httpResponse.getContentType()) {
211                                    
212                                    JSONObject jsonObject = httpResponse.getContentAsJSONObject();
213    
214                                    code = JSONObjectUtils.getString(jsonObject, "error");
215    
216                                    if (jsonObject.containsKey("error_description"))
217                                            description = JSONObjectUtils.getString(jsonObject, "error_description");
218                            }
219                            
220                            error = new ErrorObject(code, description, httpResponse.getStatusCode());
221                    }
222    
223                    return new ClientRegistrationErrorResponse(error);
224            }
225    }