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