package org.jboss.resteasy.skeleton.key.as7; import org.jboss.resteasy.skeleton.key.RSATokenVerifier; import org.jboss.resteasy.skeleton.key.RealmConfiguration; import org.jboss.resteasy.skeleton.key.VerificationException; import org.jboss.resteasy.skeleton.key.as7.i18n.LogMessages; import org.jboss.resteasy.skeleton.key.as7.i18n.Messages; import org.jboss.resteasy.skeleton.key.representations.AccessTokenResponse; import org.jboss.resteasy.skeleton.key.representations.SkeletonKeyToken; import org.jboss.resteasy.util.BasicAuthHelper; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.ws.rs.client.Entity; import javax.ws.rs.core.Form; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriBuilder; import java.io.IOException; import java.util.UUID; import java.util.concurrent.atomic.AtomicLong; /** * * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @version $Revision: 1 $ */ public class ServletOAuthLogin { protected HttpServletRequest request; protected HttpServletResponse response; protected boolean codePresent; protected RealmConfiguration realmInfo; protected int redirectPort; protected String tokenString; protected SkeletonKeyToken token; public ServletOAuthLogin(RealmConfiguration realmInfo, HttpServletRequest request, HttpServletResponse response, int redirectPort) { this.request = request; this.response = response; this.realmInfo = realmInfo; this.redirectPort = redirectPort; } public String getTokenString() { return tokenString; } public SkeletonKeyToken getToken() { return token; } public RealmConfiguration getRealmInfo() { return realmInfo; } protected String getDefaultCookiePath() { String path = request.getContextPath(); if ("".equals(path) || path == null) path = "/"; return path; } protected String getRequestUrl() { return request.getRequestURL().toString(); } protected boolean isRequestSecure() { return request.isSecure(); } protected void sendError(int code) { try { response.sendError(code); } catch (IOException e) { throw new RuntimeException(e); } } protected void sendRedirect(String url) { try { response.sendRedirect(url); } catch (IOException e) { throw new RuntimeException(e); } } protected Cookie getCookie(String cookieName) { if (request.getCookies() == null) return null; for (Cookie cookie : request.getCookies()) { if (cookie.getName().equals(cookieName)) { return cookie; } } return null; } protected String getCookieValue(String cookieName) { Cookie cookie = getCookie(cookieName); if (cookie == null) return null; return cookie.getValue(); } protected String getQueryParamValue(String paramName) { String query = request.getQueryString(); if (query == null) return null; String[] params = query.split("&"); for (String param : params) { int eq = param.indexOf('='); if (eq == -1) continue; String name = param.substring(0, eq); if (!name.equals(paramName)) continue; return param.substring(eq + 1); } return null; } public String getError() { return getQueryParamValue("error"); } public String getCode() { return getQueryParamValue("code"); } protected void setCookie(String name, String value, String domain, String path, boolean secure) { Cookie cookie = new Cookie(name, value); if (domain != null) cookie.setDomain(domain); if (path != null) cookie.setPath(path); if (secure) cookie.setSecure(true); response.addCookie(cookie); } protected String getRedirectUri(String state) { String url = getRequestUrl(); if (!isRequestSecure() && realmInfo.isSslRequired()) { int port = redirectPort; if (port < 0) { // disabled? return null; } UriBuilder secureUrl = UriBuilder.fromUri(url).scheme("https").port(-1); if (port != 443) secureUrl.port(port); url = secureUrl.build().toString(); } return realmInfo.getAuthUrl().clone() .queryParam("client_id", realmInfo.getClientId()) .queryParam("redirect_uri", url) .queryParam("state", state) .queryParam("login", "true") .build().toString(); } protected static final AtomicLong counter = new AtomicLong(); protected String getStateCode() { return counter.getAndIncrement() + "/" + UUID.randomUUID().toString(); } public void loginRedirect() { String state = getStateCode(); String redirect = getRedirectUri(state); if (redirect == null) { sendError(Response.Status.FORBIDDEN.getStatusCode()); return; } setCookie(realmInfo.getStateCookieName(), state, null, getDefaultCookiePath(), realmInfo.isSslRequired()); sendRedirect(redirect); } public boolean checkStateCookie() { Cookie stateCookie = getCookie(realmInfo.getStateCookieName()); if (stateCookie == null) { sendError(400); LogMessages.LOGGER.warn(Messages.MESSAGES.noStateCookie()); return false; } // reset the cookie Cookie reset = new Cookie(stateCookie.getName(), stateCookie.getValue()); reset.setPath(stateCookie.getPath()); reset.setMaxAge(0); response.addCookie(reset); String stateCookieValue = getCookieValue(realmInfo.getStateCookieName()); // its ok to call request.getParameter() because this should be a redirect String state = request.getParameter("state"); if (state == null) { sendError(400); LogMessages.LOGGER.warn(Messages.MESSAGES.stateParameterWasNull()); return false; } if (!state.equals(stateCookieValue)) { sendError(400); LogMessages.LOGGER.warn(Messages.MESSAGES.stateParameterInvalid()); LogMessages.LOGGER.warn(Messages.MESSAGES.cookie(stateCookieValue)); LogMessages.LOGGER.warn(Messages.MESSAGES.queryParam(state)); return false; } return true; } /** * Start or continue the oauth login process. * * if code query parameter is not present, then browser is redirected to authUrl. The redirect URL will be * the URL of the current request. * * If code query parameter is present, then an access token is obtained by invoking a secure request to the codeUrl. * If the access token is obtained, the browser is again redirected to the current request URL, but any OAuth * protocol specific query parameters are removed. * * @return true if an access token was obtained */ public boolean resolveCode(String code) { // abort if not HTTPS if (realmInfo.isSslRequired() && !isRequestSecure()) { LogMessages.LOGGER.error(Messages.MESSAGES.sslIsRequired()); sendError(Response.Status.FORBIDDEN.getStatusCode()); return false; } if (!checkStateCookie()) return false; String client_id = realmInfo.getClientId(); String password = realmInfo.getCredentials().asMap().getFirst("password"); String authHeader = BasicAuthHelper.createHeader(client_id, password); String redirectUri = stripOauthParametersFromRedirect(); Form form = new Form(); form.param("grant_type", "authorization_code") .param("code", code) .param("redirect_uri", redirectUri); Response res = realmInfo.getCodeUrl().request().header(HttpHeaders.AUTHORIZATION, authHeader).post(Entity.form(form)); AccessTokenResponse tokenResponse; try { if (res.getStatus() != 200) { LogMessages.LOGGER.error(Messages.MESSAGES.failedToTurnCodeIntoToken()); sendError(Response.Status.FORBIDDEN.getStatusCode()); return false; } LogMessages.LOGGER.debug(Messages.MESSAGES.mediaType(res.getMediaType())); LogMessages.LOGGER.debug(Messages.MESSAGES.contentTypeHeader(res.getHeaderString("Content-Type"))); tokenResponse = res.readEntity(AccessTokenResponse.class); } finally { res.close(); } tokenString = tokenResponse.getToken(); try { token = RSATokenVerifier.verifyToken(tokenString, realmInfo.getMetadata()); LogMessages.LOGGER.debug(Messages.MESSAGES.verificationSucceeded()); } catch (VerificationException e) { LogMessages.LOGGER.error(Messages.MESSAGES.failedVerificationOfToken()); sendError(Response.Status.FORBIDDEN.getStatusCode()); return false; } // redirect to URL without oauth query parameters sendRedirect(redirectUri); return true; } /** * strip out unwanted query parameters and redirect so bookmarks don't retain oauth protocol bits */ protected String stripOauthParametersFromRedirect() { StringBuffer buf = request.getRequestURL().append("?").append(request.getQueryString()); UriBuilder builder = UriBuilder.fromUri(buf.toString()) .replaceQueryParam("code", null) .replaceQueryParam("state", null); return builder.build().toString(); } }