001/*
002 * oauth2-oidc-sdk
003 *
004 * Copyright 2012-2016, Connect2id Ltd and contributors.
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use
007 * this file except in compliance with the License. You may obtain a copy of the
008 * License at
009 *
010 *    http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software distributed
013 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
014 * CONDITIONS OF ANY KIND, either express or implied. See the License for the
015 * specific language governing permissions and limitations under the License.
016 */
017
018package com.nimbusds.openid.connect.sdk;
019
020
021import java.util.Collections;
022import java.util.HashSet;
023import java.util.Set;
024
025import com.nimbusds.oauth2.sdk.ErrorObject;
026import com.nimbusds.oauth2.sdk.ErrorResponse;
027import com.nimbusds.oauth2.sdk.ParseException;
028import com.nimbusds.oauth2.sdk.http.CommonContentTypes;
029import com.nimbusds.oauth2.sdk.http.HTTPResponse;
030import com.nimbusds.oauth2.sdk.token.BearerTokenError;
031import net.jcip.annotations.Immutable;
032import org.apache.commons.lang3.StringUtils;
033
034
035/**
036 * UserInfo error response.
037 *
038 * <p>Standard OAuth 2.0 Bearer Token errors:
039 *
040 * <ul>
041 *     <li>{@link com.nimbusds.oauth2.sdk.token.BearerTokenError#MISSING_TOKEN}
042 *     <li>{@link com.nimbusds.oauth2.sdk.token.BearerTokenError#INVALID_REQUEST}
043 *     <li>{@link com.nimbusds.oauth2.sdk.token.BearerTokenError#INVALID_TOKEN}
044 *     <li>{@link com.nimbusds.oauth2.sdk.token.BearerTokenError#INSUFFICIENT_SCOPE}
045 * </ul>
046 *
047 * <p>Example HTTP response:
048 *
049 * <pre>
050 * HTTP/1.1 401 Unauthorized
051 * WWW-Authenticate: Bearer realm="example.com",
052 *                   error="invalid_token",
053 *                   error_description="The access token expired"
054 * </pre>
055 *
056 * <p>Related specifications:
057 *
058 * <ul>
059 *     <li>OpenID Connect Core 1.0, section 5.3.3.
060 *     <li>OAuth 2.0 Bearer Token Usage (RFC 6750), section 3.1.
061 * </ul>
062 */
063@Immutable
064public class UserInfoErrorResponse
065        extends UserInfoResponse
066        implements ErrorResponse {
067
068
069        /**
070         * Gets the standard errors for a UserInfo error response.
071         *
072         * @return The standard errors, as a read-only set.
073         */
074        public static Set<BearerTokenError> getStandardErrors() {
075                
076                Set<BearerTokenError> 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
082                return Collections.unmodifiableSet(stdErrors);
083        }
084
085
086        /**
087         * The underlying error.
088         */
089        private final ErrorObject error;
090
091
092        /**
093         * Creates a new UserInfo error response. No OAuth 2.0 bearer token
094         * error / general error object is specified.
095         */
096        private UserInfoErrorResponse() {
097
098                error = null;
099        }
100        
101
102        /**
103         * Creates a new UserInfo error response indicating a bearer token
104         * error.
105         *
106         * @param error The OAuth 2.0 bearer token error. Should match one of 
107         *              the {@link #getStandardErrors standard errors} for a 
108         *              UserInfo error response. Must not be {@code null}.
109         */
110        public UserInfoErrorResponse(final BearerTokenError error) {
111
112                this((ErrorObject) error);
113        }
114        
115        
116        /**
117         * Creates a new UserInfo error response indicating a general error.
118         *
119         * @param error The error. Must not be {@code null}.
120         */
121        public UserInfoErrorResponse(final ErrorObject error) {
122                
123                if (error == null)
124                        throw new IllegalArgumentException("The error must not be null");
125                
126                this.error = error;
127        }
128
129
130        @Override
131        public boolean indicatesSuccess() {
132
133                return false;
134        }
135
136
137        @Override
138        public ErrorObject getErrorObject() {
139
140                return error;
141        }
142
143
144        /**
145         * Returns the HTTP response for this UserInfo error response.
146         *
147         * <p>Example HTTP response:
148         *
149         * <pre>
150         * HTTP/1.1 401 Unauthorized
151         * WWW-Authenticate: Bearer realm="example.com",
152         *                   error="invalid_token",
153         *                   error_description="The access token expired"
154         * </pre>
155         *
156         * @return The HTTP response matching this UserInfo error response.
157         */
158        @Override
159        public HTTPResponse toHTTPResponse() {
160
161                HTTPResponse httpResponse;
162
163                if (error != null && error.getHTTPStatusCode() > 0) {
164                        httpResponse = new HTTPResponse(error.getHTTPStatusCode());
165                } else {
166                        httpResponse = new HTTPResponse(HTTPResponse.SC_BAD_REQUEST);
167                }
168
169                // Add the WWW-Authenticate header
170                if (error instanceof BearerTokenError) {
171                        httpResponse.setWWWAuthenticate(((BearerTokenError) error).toWWWAuthenticateHeader());
172                } else if (error != null){
173                        httpResponse.setContentType(CommonContentTypes.APPLICATION_JSON);
174                        httpResponse.setContent(error.toJSONObject().toJSONString());
175                }
176
177                return httpResponse;
178        }
179
180
181        /**
182         * Parses a UserInfo error response from the specified HTTP response
183         * {@code WWW-Authenticate} header.
184         *
185         * @param wwwAuth The {@code WWW-Authenticate} header value to parse. 
186         *                Must not be {@code null}.
187         *
188         * @throws ParseException If the {@code WWW-Authenticate} header value 
189         *                        couldn't be parsed to a UserInfo error 
190         *                        response.
191         */
192        public static UserInfoErrorResponse parse(final String wwwAuth)
193                throws ParseException {
194
195                BearerTokenError error = BearerTokenError.parse(wwwAuth);
196
197                return new UserInfoErrorResponse(error);
198        }
199        
200        
201        /**
202         * Parses a UserInfo error response from the specified HTTP response.
203         *
204         * <p>Note: The HTTP status code is not checked for matching the error
205         * code semantics.
206         *
207         * @param httpResponse The HTTP response to parse. Its status code must
208         *                     not be 200 (OK). Must not be {@code null}.
209         *
210         * @throws ParseException If the HTTP response couldn't be parsed to a 
211         *                        UserInfo error response.
212         */
213        public static UserInfoErrorResponse parse(final HTTPResponse httpResponse)
214                throws ParseException {
215                
216                httpResponse.ensureStatusCodeNotOK();
217
218                String wwwAuth = httpResponse.getWWWAuthenticate();
219                
220                if (StringUtils.isNotBlank(wwwAuth)) {
221                        // Bearer token error?
222                        return parse(wwwAuth);
223                }
224                
225                // Other error?
226                return new UserInfoErrorResponse(ErrorObject.parse(httpResponse));
227        }
228}