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 *             <li>{@link RegistrationError#INVALID_SOFTWARE_STATEMENT}
041 *             <li>{@link RegistrationError#UNAPPROVED_SOFTWARE_STATEMENT}
042 *         </ul>
043 * </ul>
044 *
045 * <p>Example HTTP response:
046 *
047 * <pre>
048 * HTTP/1.1 400 Bad Request
049 * Content-Type: application/json
050 * Cache-Control: no-store
051 * Pragma: no-cache
052 *
053 * {
054 *  "error":"invalid_redirect_uri",
055 *  "error_description":"The redirection URI of http://sketchy.example.com is not allowed for this server."
056 * }
057 * </pre>
058 *
059 * <p>Related specifications:
060 *
061 * <ul>
062 *     <li>OAuth 2.0 Dynamic Client Registration Protocol 
063 *         (draft-ietf-oauth-dyn-reg-20), section 4.2.
064 *     <li>OAuth 2.0 Bearer Token Usage (RFC 6750), section 3.1.
065 * </ul>
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<>();
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                stdErrors.add(RegistrationError.INVALID_SOFTWARE_STATEMENT);
088                stdErrors.add(RegistrationError.UNAPPROVED_SOFTWARE_STATEMENT);
089
090                return Collections.unmodifiableSet(stdErrors);
091        }
092
093
094        /**
095         * The underlying error.
096         */
097        private final ErrorObject error;
098
099
100        /**
101         * Creates a new client registration error response.
102         *
103         * @param error The error. Should match one of the 
104         *              {@link #getStandardErrors standard errors} for a client
105         *              registration error response. Must not be {@code null}.
106         */
107        public ClientRegistrationErrorResponse(final ErrorObject error) {
108
109                if (error == null)
110                        throw new IllegalArgumentException("The error must not be null");
111
112                this.error = error;
113        }
114
115
116        @Override
117        public ErrorObject getErrorObject() {
118
119                return error;
120        }
121
122
123        /**
124         * Returns the HTTP response for this client registration error 
125         * response.
126         *
127         * <p>Example HTTP response:
128         *
129         * <pre>
130         * HTTP/1.1 400 Bad Request
131         * Content-Type: application/json
132         * Cache-Control: no-store
133         * Pragma: no-cache
134         *
135         * {
136         *  "error":"invalid_redirect_uri",
137         *  "error_description":"The redirection URI of http://sketchy.example.com is not allowed for this server."
138         * }
139         * </pre>
140         *
141         * @return The HTTP response.
142         */
143        @Override
144        public HTTPResponse toHTTPResponse() {
145
146                HTTPResponse httpResponse;
147
148                if (error.getHTTPStatusCode() > 0) {
149                        httpResponse = new HTTPResponse(error.getHTTPStatusCode());
150                } else {
151                        httpResponse = new HTTPResponse(HTTPResponse.SC_BAD_REQUEST);
152                }
153
154                // Add the WWW-Authenticate header
155                if (error instanceof BearerTokenError) {
156
157                        BearerTokenError bte = (BearerTokenError)error;
158
159                        httpResponse.setWWWAuthenticate(bte.toWWWAuthenticateHeader());
160
161                } else {
162                        JSONObject jsonObject = new JSONObject();
163
164                        if (error.getCode() != null)
165                                jsonObject.put("error", error.getCode());
166
167                        if (error.getDescription() != null)
168                                jsonObject.put("error_description", error.getDescription());
169
170                        httpResponse.setContentType(CommonContentTypes.APPLICATION_JSON);
171
172                        httpResponse.setContent(jsonObject.toString());
173                }
174                
175                httpResponse.setCacheControl("no-store");
176                httpResponse.setPragma("no-cache");
177
178                return httpResponse;
179        }
180
181
182        /**
183         * Parses a client registration error response from the specified HTTP 
184         * response.
185         *
186         * <p>Note: The HTTP status code is not checked for matching the error
187         * code semantics.
188         *
189         * @param httpResponse The HTTP response to parse. Its status code must
190         *                     not be 200 (OK). Must not be {@code null}.
191         *
192         * @throws ParseException If the HTTP response couldn't be parsed to a
193         *                        client registration error response.
194         */
195        public static ClientRegistrationErrorResponse parse(final HTTPResponse httpResponse)
196                throws ParseException {
197                
198                httpResponse.ensureStatusCodeNotOK();
199
200                ErrorObject error;
201
202                String wwwAuth = httpResponse.getWWWAuthenticate();
203                
204                if (StringUtils.isNotBlank(wwwAuth)) {
205
206                        error = BearerTokenError.parse(wwwAuth);
207
208                } else {
209                        
210                        String code = null;
211                        String description = null;
212                        
213                        if (CommonContentTypes.APPLICATION_JSON.match(httpResponse.getContentType())) {
214                                
215                                JSONObject jsonObject = httpResponse.getContentAsJSONObject();
216
217                                code = JSONObjectUtils.getString(jsonObject, "error");
218
219                                if (jsonObject.containsKey("error_description"))
220                                        description = JSONObjectUtils.getString(jsonObject, "error_description");
221                        }
222                        
223                        error = new ErrorObject(code, description, httpResponse.getStatusCode());
224                }
225
226                return new ClientRegistrationErrorResponse(error);
227        }
228}