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.
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 Core 1.0, section 5.3.3.
044 *     <li>OAuth 2.0 Bearer Token Usage (RFC 6750), section 3.1.
045 * </ul>
046 */
047@Immutable
048public class UserInfoErrorResponse
049        extends UserInfoResponse
050        implements ErrorResponse {
051
052
053        /**
054         * Gets the standard errors for a UserInfo error response.
055         *
056         * @return The standard errors, as a read-only set.
057         */
058        public static Set<BearerTokenError> getStandardErrors() {
059                
060                Set<BearerTokenError> stdErrors = new HashSet<>();
061                stdErrors.add(BearerTokenError.MISSING_TOKEN);
062                stdErrors.add(BearerTokenError.INVALID_REQUEST);
063                stdErrors.add(BearerTokenError.INVALID_TOKEN);
064                stdErrors.add(BearerTokenError.INSUFFICIENT_SCOPE);
065
066                return Collections.unmodifiableSet(stdErrors);
067        }
068
069
070        /**
071         * The underlying bearer token error.
072         */
073        private final BearerTokenError error;
074
075
076        /**
077         * Creates a new UserInfo error response. No OAuth 2.0 bearer token
078         * error is specified.
079         */
080        private UserInfoErrorResponse() {
081
082                error = null;
083        }
084        
085
086        /**
087         * Creates a new UserInfo error response.
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                if (error == null)
096                        throw new IllegalArgumentException("The error must not be null");
097
098                this.error = error;
099        }
100
101
102        @Override
103        public boolean indicatesSuccess() {
104
105                return false;
106        }
107
108
109        @Override
110        public ErrorObject getErrorObject() {
111
112                return error;
113        }
114
115
116        /**
117         * Returns the HTTP response for this UserInfo error response.
118         *
119         * <p>Example HTTP response:
120         *
121         * <pre>
122         * HTTP/1.1 401 Unauthorized
123         * WWW-Authenticate: Bearer realm="example.com",
124         *                   error="invalid_token",
125         *                   error_description="The access token expired"
126         * </pre>
127         *
128         * @return The HTTP response matching this UserInfo error response.
129         */
130        @Override
131        public HTTPResponse toHTTPResponse() {
132
133                HTTPResponse httpResponse;
134
135                if (error != null && error.getHTTPStatusCode() > 0)
136                        httpResponse = new HTTPResponse(error.getHTTPStatusCode());
137                else
138                        httpResponse = new HTTPResponse(HTTPResponse.SC_BAD_REQUEST);
139
140                // Add the WWW-Authenticate header
141                if (error != null)
142                        httpResponse.setWWWAuthenticate(error.toWWWAuthenticateHeader());
143
144                return httpResponse;
145        }
146
147
148        /**
149         * Parses a UserInfo error response from the specified HTTP response
150         * {@code WWW-Authenticate} header.
151         *
152         * @param wwwAuth The {@code WWW-Authenticate} header value to parse. 
153         *                Must not be {@code null}.
154         *
155         * @throws ParseException If the {@code WWW-Authenticate} header value 
156         *                        couldn't be parsed to a UserInfo error 
157         *                        response.
158         */
159        public static UserInfoErrorResponse parse(final String wwwAuth)
160                throws ParseException {
161
162                BearerTokenError error = BearerTokenError.parse(wwwAuth);
163
164                return new UserInfoErrorResponse(error);
165        }
166        
167        
168        /**
169         * Parses a UserInfo error response from the specified HTTP response.
170         *
171         * <p>Note: The HTTP status code is not checked for matching the error
172         * code semantics.
173         *
174         * @param httpResponse The HTTP response to parse. Its status code must
175         *                     not be 200 (OK). Must not be {@code null}.
176         *
177         * @throws ParseException If the HTTP response couldn't be parsed to a 
178         *                        UserInfo error response.
179         */
180        public static UserInfoErrorResponse parse(final HTTPResponse httpResponse)
181                throws ParseException {
182                
183                httpResponse.ensureStatusCodeNotOK();
184
185                String wwwAuth = httpResponse.getWWWAuthenticate();
186                
187                if (StringUtils.isNotBlank(wwwAuth))
188                        return parse(wwwAuth);
189
190                return new UserInfoErrorResponse();
191        }
192}