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