001package com.nimbusds.common.oauth2;
002
003
004import java.io.IOException;
005import javax.servlet.http.HttpServletRequest;
006import javax.servlet.http.HttpServletResponse;
007import javax.ws.rs.WebApplicationException;
008import javax.ws.rs.core.MediaType;
009import javax.ws.rs.core.Response;
010
011import org.apache.commons.lang3.StringUtils;
012
013import net.minidev.json.JSONObject;
014
015import com.nimbusds.oauth2.sdk.ParseException;
016import com.nimbusds.oauth2.sdk.token.BearerAccessToken;
017import com.nimbusds.oauth2.sdk.token.BearerTokenError;
018
019
020/**
021 * Basic access token validator. Supports servlet-based and JAX-RS based web
022 * applications.
023 */
024public class BasicAccessTokenValidator {
025
026
027        /**
028         * Error response: Missing OAuth 2.0 Bearer access token.
029         */
030        public static final ErrorResponse MISSING_BEARER_TOKEN;
031
032
033        /**
034         * Error response: Invalid OAuth 2.0 Bearer access token.
035         */
036        public static final ErrorResponse INVALID_BEARER_TOKEN;
037
038
039        /**
040         * Error response: Web API disabled.
041         */
042        public static final ErrorResponse WEB_API_DISABLED;
043
044
045        static {
046                JSONObject o = new JSONObject();
047                o.put("error", "missing_token");
048                o.put("error_description", "Unauthorized: Missing Bearer access token");
049                MISSING_BEARER_TOKEN = new ErrorResponse(
050                        BearerTokenError.MISSING_TOKEN.getHTTPStatusCode(),
051                        BearerTokenError.MISSING_TOKEN.toWWWAuthenticateHeader(),
052                        o.toJSONString());
053
054                o = new JSONObject();
055                o.put("error", BearerTokenError.INVALID_TOKEN.getCode());
056                o.put("error_description", "Unauthorized: Invalid Bearer access token");
057                INVALID_BEARER_TOKEN = new ErrorResponse(
058                        BearerTokenError.INVALID_TOKEN.getHTTPStatusCode(),
059                        BearerTokenError.INVALID_TOKEN.toWWWAuthenticateHeader(),
060                        o.toJSONString());
061
062                o = new JSONObject();
063                o.put("error", "web_api_disabled");
064                o.put("error_description", "Forbidden: Web API disabled");
065                WEB_API_DISABLED = new ErrorResponse(403, null, o.toJSONString());
066        }
067
068
069        /**
070         * Bearer token error response.
071         */
072        public static class ErrorResponse {
073
074
075                /**
076                 * The HTTP status code.
077                 */
078                private final int statusCode;
079
080
081                /**
082                 * Optional HTTP response {@code WWW-Authenticate} header.
083                 */
084                private final String wwwAuthHeader;
085
086
087                /**
088                 * The HTTP body.
089                 */
090                private final String body;
091
092
093                /**
094                 * Creates a new bearer token error response.
095                 *
096                 * @param statusCode    The HTTP status code.
097                 * @param wwwAuthHeader The HTTP response
098                 *                      {@code WWW-Authenticate} header,
099                 *                      {@code null} if none.
100                 * @param body          The HTTP body (application/json).
101                 */
102                public ErrorResponse(final int statusCode,
103                                     final String wwwAuthHeader,
104                                     final String body) {
105
106                        this.statusCode = statusCode;
107                        this.wwwAuthHeader = wwwAuthHeader;
108                        this.body = body;
109                }
110
111
112                /**
113                 * Returns a web application exception for this error response.
114                 *
115                 * @return The web application exception.
116                 */
117                public WebApplicationException toWebAppException() {
118
119                        Response.ResponseBuilder builder = Response.status(statusCode);
120
121                        if (wwwAuthHeader != null) {
122                                builder.header("WWW-Authenticate", wwwAuthHeader);
123                        }
124
125                        return new WebApplicationException(
126                                builder.entity(body).type(MediaType.APPLICATION_JSON).build());
127                }
128
129
130                /**
131                 * Applies this error response to the specified HTTP servlet
132                 * response.
133                 *
134                 * @param servletResponse The HTTP servlet response. Must not
135                 *                        be {@code null}.
136                 *
137                 * @throws IOException If the error response couldn't be
138                 *                     written.
139                 */
140                public void apply(final HttpServletResponse servletResponse)
141                        throws IOException {
142
143                        servletResponse.setStatus(statusCode);
144
145                        if (wwwAuthHeader != null) {
146                                servletResponse.setHeader("WWW-Authenticate", wwwAuthHeader);
147                        }
148
149                        if (body != null) {
150                                servletResponse.setContentType("application/json");
151                                servletResponse.getWriter().print(body);
152                        }
153                }
154        }
155
156
157        /**
158         * The access token, {@code null} access to the web API is disabled.
159         */
160        private final BearerAccessToken accessToken;
161
162
163        /**
164         * Creates a new basic access token validator.
165         *
166         * @param accessToken The Bearer access token. If {@code null} access
167         *                    to the web API will be disabled.
168         */
169        public BasicAccessTokenValidator(final BearerAccessToken accessToken) {
170
171                this.accessToken = accessToken;
172        }
173
174
175        /**
176         * Returns the Bearer access token.
177         *
178         * @return The Bearer access token, {@code null} access to the web API
179         *         is disabled.
180         */
181        public BearerAccessToken getAccessToken() {
182
183                return accessToken;
184        }
185
186
187        /**
188         * Validates a bearer access token passed in the specified HTTP
189         * Authorization header value.
190         *
191         * @param authzHeader The HTTP Authorization header value, {@code null}
192         *                    if not specified.
193         *
194         * @throws WebApplicationException If the header value is {@code null},
195         *                                 the web API is disabled, or the
196         *                                 Bearer access token is missing or
197         *                                 invalid.
198         */
199        public void validateBearerAccessToken(final String authzHeader)
200                throws WebApplicationException {
201
202                if (StringUtils.isBlank(authzHeader)) {
203                        throw MISSING_BEARER_TOKEN.toWebAppException();
204                }
205
206                BearerAccessToken receivedToken;
207
208                try {
209                        receivedToken = BearerAccessToken.parse(authzHeader);
210
211                } catch (ParseException e) {
212                        throw MISSING_BEARER_TOKEN.toWebAppException();
213                }
214
215                // Web API disabled?
216                if (accessToken == null) {
217                        throw WEB_API_DISABLED.toWebAppException();
218                }
219
220                // Check receivedToken
221                if (! receivedToken.equals(accessToken)) {
222                        throw INVALID_BEARER_TOKEN.toWebAppException();
223                }
224        }
225
226
227        /**
228         * Validates a bearer access token passed in the specified HTTP servlet
229         * request.
230         *
231         * @param servletRequest  The HTTP servlet request. Must not be
232         *                        {@code null}.
233         * @param servletResponse The HTTP servlet response. Must not be
234         *                        {@code null}.
235         *
236         * @return {@code true} if the bearer access token was successfully
237         *         validated, {@code false}.
238         *
239         * @throws IOException If the response couldn't be written.
240         */
241        public boolean validateBearerAccessToken(final HttpServletRequest servletRequest,
242                                                 final HttpServletResponse servletResponse)
243                throws IOException {
244
245                String authzHeader = servletRequest.getHeader("Authorization");
246
247                if (StringUtils.isBlank(authzHeader)) {
248                        MISSING_BEARER_TOKEN.apply(servletResponse);
249                        return false;
250                }
251
252                BearerAccessToken receivedToken;
253
254                try {
255                        receivedToken = BearerAccessToken.parse(authzHeader);
256
257                } catch (ParseException e) {
258                        MISSING_BEARER_TOKEN.apply(servletResponse);
259                        return false;
260                }
261
262                // Web API disabled?
263                if (accessToken == null) {
264                        WEB_API_DISABLED.apply(servletResponse);
265                        return false;
266                }
267
268                // Check receivedToken
269                if (! receivedToken.equals(accessToken)) {
270                        INVALID_BEARER_TOKEN.apply(servletResponse);
271                        return false;
272                }
273
274                return true; // success
275        }
276}