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