001package com.nimbusds.openid.connect.sdk;
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 org.apache.commons.lang3.StringUtils;
016
017
018/**
019 * UserInfo error response.
020 *
021 * <p>Standard OAuth 2.0 Bearer Token errors:
022 *
023 * <ul>
024 *     <li>{@link com.nimbusds.oauth2.sdk.token.BearerTokenError#MISSING_TOKEN}
025 *     <li>{@link com.nimbusds.oauth2.sdk.token.BearerTokenError#INVALID_REQUEST}
026 *     <li>{@link com.nimbusds.oauth2.sdk.token.BearerTokenError#INVALID_TOKEN}
027 *     <li>{@link com.nimbusds.oauth2.sdk.token.BearerTokenError#INSUFFICIENT_SCOPE}
028 * </ul>
029 *
030 * <p>Example HTTP response:
031 *
032 * <pre>
033 * HTTP/1.1 401 Unauthorized
034 * WWW-Authenticate: Bearer realm="example.com",
035 *                   error="invalid_token",
036 *                   error_description="The access token expired"
037 * </pre>
038 *
039 * <p>Related specifications:
040 *
041 * <ul>
042 *     <li>OpenID Connect Core 1.0, section 5.3.3.
043 *     <li>OAuth 2.0 Bearer Token Usage (RFC 6750), section 3.1.
044 * </ul>
045 */
046@Immutable
047public class UserInfoErrorResponse
048        extends UserInfoResponse
049        implements ErrorResponse {
050
051
052        /**
053         * Gets the standard errors for a UserInfo error response.
054         *
055         * @return The standard errors, as a read-only set.
056         */
057        public static Set<BearerTokenError> getStandardErrors() {
058                
059                Set<BearerTokenError> stdErrors = new HashSet<>();
060                stdErrors.add(BearerTokenError.MISSING_TOKEN);
061                stdErrors.add(BearerTokenError.INVALID_REQUEST);
062                stdErrors.add(BearerTokenError.INVALID_TOKEN);
063                stdErrors.add(BearerTokenError.INSUFFICIENT_SCOPE);
064
065                return Collections.unmodifiableSet(stdErrors);
066        }
067
068
069        /**
070         * The underlying error.
071         */
072        private final ErrorObject error;
073
074
075        /**
076         * Creates a new UserInfo error response. No OAuth 2.0 bearer token
077         * error / general error object is specified.
078         */
079        private UserInfoErrorResponse() {
080
081                error = null;
082        }
083        
084
085        /**
086         * Creates a new UserInfo error response indicating a bearer token
087         * error.
088         *
089         * @param error The OAuth 2.0 bearer token error. Should match one of 
090         *              the {@link #getStandardErrors standard errors} for a 
091         *              UserInfo error response. Must not be {@code null}.
092         */
093        public UserInfoErrorResponse(final BearerTokenError error) {
094
095                this((ErrorObject) error);
096        }
097        
098        
099        /**
100         * Creates a new UserInfo error response indicating a general error.
101         *
102         * @param error The error. Must not be {@code null}.
103         */
104        public UserInfoErrorResponse(final ErrorObject error) {
105                
106                if (error == null)
107                        throw new IllegalArgumentException("The error must not be null");
108                
109                this.error = error;
110        }
111
112
113        @Override
114        public boolean indicatesSuccess() {
115
116                return false;
117        }
118
119
120        @Override
121        public ErrorObject getErrorObject() {
122
123                return error;
124        }
125
126
127        /**
128         * Returns the HTTP response for this UserInfo error response.
129         *
130         * <p>Example HTTP response:
131         *
132         * <pre>
133         * HTTP/1.1 401 Unauthorized
134         * WWW-Authenticate: Bearer realm="example.com",
135         *                   error="invalid_token",
136         *                   error_description="The access token expired"
137         * </pre>
138         *
139         * @return The HTTP response matching this UserInfo error response.
140         */
141        @Override
142        public HTTPResponse toHTTPResponse() {
143
144                HTTPResponse httpResponse;
145
146                if (error != null && error.getHTTPStatusCode() > 0) {
147                        httpResponse = new HTTPResponse(error.getHTTPStatusCode());
148                } else {
149                        httpResponse = new HTTPResponse(HTTPResponse.SC_BAD_REQUEST);
150                }
151
152                // Add the WWW-Authenticate header
153                if (error instanceof BearerTokenError) {
154                        httpResponse.setWWWAuthenticate(((BearerTokenError) error).toWWWAuthenticateHeader());
155                } else if (error != null){
156                        httpResponse.setContentType(CommonContentTypes.APPLICATION_JSON);
157                        httpResponse.setContent(error.toJSONObject().toJSONString());
158                }
159
160                return httpResponse;
161        }
162
163
164        /**
165         * Parses a UserInfo error response from the specified HTTP response
166         * {@code WWW-Authenticate} header.
167         *
168         * @param wwwAuth The {@code WWW-Authenticate} header value to parse. 
169         *                Must not be {@code null}.
170         *
171         * @throws ParseException If the {@code WWW-Authenticate} header value 
172         *                        couldn't be parsed to a UserInfo error 
173         *                        response.
174         */
175        public static UserInfoErrorResponse parse(final String wwwAuth)
176                throws ParseException {
177
178                BearerTokenError error = BearerTokenError.parse(wwwAuth);
179
180                return new UserInfoErrorResponse(error);
181        }
182        
183        
184        /**
185         * Parses a UserInfo error response from the specified HTTP response.
186         *
187         * <p>Note: The HTTP status code is not checked for matching the error
188         * code semantics.
189         *
190         * @param httpResponse The HTTP response to parse. Its status code must
191         *                     not be 200 (OK). Must not be {@code null}.
192         *
193         * @throws ParseException If the HTTP response couldn't be parsed to a 
194         *                        UserInfo error response.
195         */
196        public static UserInfoErrorResponse parse(final HTTPResponse httpResponse)
197                throws ParseException {
198                
199                httpResponse.ensureStatusCodeNotOK();
200
201                String wwwAuth = httpResponse.getWWWAuthenticate();
202                
203                if (StringUtils.isNotBlank(wwwAuth)) {
204                        // Bearer token error?
205                        return parse(wwwAuth);
206                }
207                
208                // Other error?
209                return new UserInfoErrorResponse(ErrorObject.parse(httpResponse));
210        }
211}