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