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 (RFC 7591), section
063 *         3.2.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 boolean indicatesSuccess() {
118
119                return false;
120        }
121
122
123        @Override
124        public ErrorObject getErrorObject() {
125
126                return error;
127        }
128
129
130        /**
131         * Returns the HTTP response for this client registration error 
132         * response.
133         *
134         * <p>Example HTTP response:
135         *
136         * <pre>
137         * HTTP/1.1 400 Bad Request
138         * Content-Type: application/json
139         * Cache-Control: no-store
140         * Pragma: no-cache
141         *
142         * {
143         *  "error":"invalid_redirect_uri",
144         *  "error_description":"The redirection URI of http://sketchy.example.com is not allowed for this server."
145         * }
146         * </pre>
147         *
148         * @return The HTTP response.
149         */
150        @Override
151        public HTTPResponse toHTTPResponse() {
152
153                HTTPResponse httpResponse;
154
155                if (error.getHTTPStatusCode() > 0) {
156                        httpResponse = new HTTPResponse(error.getHTTPStatusCode());
157                } else {
158                        httpResponse = new HTTPResponse(HTTPResponse.SC_BAD_REQUEST);
159                }
160
161                // Add the WWW-Authenticate header
162                if (error instanceof BearerTokenError) {
163
164                        BearerTokenError bte = (BearerTokenError)error;
165
166                        httpResponse.setWWWAuthenticate(bte.toWWWAuthenticateHeader());
167
168                } else {
169                        JSONObject jsonObject = new JSONObject();
170
171                        if (error.getCode() != null)
172                                jsonObject.put("error", error.getCode());
173
174                        if (error.getDescription() != null)
175                                jsonObject.put("error_description", error.getDescription());
176
177                        httpResponse.setContentType(CommonContentTypes.APPLICATION_JSON);
178
179                        httpResponse.setContent(jsonObject.toString());
180                }
181                
182                httpResponse.setCacheControl("no-store");
183                httpResponse.setPragma("no-cache");
184
185                return httpResponse;
186        }
187
188
189        /**
190         * Parses a client registration error response from the specified HTTP 
191         * response.
192         *
193         * <p>Note: The HTTP status code is not checked for matching the error
194         * code semantics.
195         *
196         * @param httpResponse The HTTP response to parse. Its status code must
197         *                     not be 200 (OK). Must not be {@code null}.
198         *
199         * @throws ParseException If the HTTP response couldn't be parsed to a
200         *                        client registration error response.
201         */
202        public static ClientRegistrationErrorResponse parse(final HTTPResponse httpResponse)
203                throws ParseException {
204                
205                httpResponse.ensureStatusCodeNotOK();
206
207                ErrorObject error;
208
209                String wwwAuth = httpResponse.getWWWAuthenticate();
210                
211                if (StringUtils.isNotBlank(wwwAuth)) {
212
213                        error = BearerTokenError.parse(wwwAuth);
214
215                } else {
216                        
217                        String code = null;
218                        String description = null;
219                        
220                        if (CommonContentTypes.APPLICATION_JSON.match(httpResponse.getContentType())) {
221                                
222                                JSONObject jsonObject = httpResponse.getContentAsJSONObject();
223
224                                code = JSONObjectUtils.getString(jsonObject, "error");
225
226                                if (jsonObject.containsKey("error_description"))
227                                        description = JSONObjectUtils.getString(jsonObject, "error_description");
228                        }
229                        
230                        error = new ErrorObject(code, description, httpResponse.getStatusCode());
231                }
232
233                return new ClientRegistrationErrorResponse(error);
234        }
235}