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. 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-14), 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
068public 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;
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;
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}