package org.ovirt.engine.core.sso.servlets; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Optional; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang.StringUtils; import org.ovirt.engine.api.extensions.aaa.Authn; import org.ovirt.engine.core.sso.utils.AuthResult; import org.ovirt.engine.core.sso.utils.AuthenticationException; import org.ovirt.engine.core.sso.utils.AuthenticationUtils; import org.ovirt.engine.core.sso.utils.Credentials; import org.ovirt.engine.core.sso.utils.NegotiateAuthUtils; import org.ovirt.engine.core.sso.utils.NonInteractiveAuth; import org.ovirt.engine.core.sso.utils.OAuthException; import org.ovirt.engine.core.sso.utils.SsoConstants; import org.ovirt.engine.core.sso.utils.SsoContext; import org.ovirt.engine.core.sso.utils.SsoSession; import org.ovirt.engine.core.sso.utils.SsoUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class OAuthTokenServlet extends HttpServlet { private static final long serialVersionUID = 7168485079055058668L; private static Logger log = LoggerFactory.getLogger(OAuthTokenServlet.class); private SsoContext ssoContext; @Override public void init(ServletConfig config) throws ServletException { ssoContext = SsoUtils.getSsoContext(config.getServletContext()); } @Override protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { try { log.debug("Entered OAuthTokenServlet Query String: {}, Parameters : {}", request.getQueryString(), SsoUtils.getRequestParameters(request)); String grantType = SsoUtils.getRequestParameter(request, SsoConstants.JSON_GRANT_TYPE, SsoConstants.JSON_GRANT_TYPE); String scope = SsoUtils.getScopeRequestParameter(request, ""); SsoUtils.validateClientAcceptHeader(request); switch(grantType) { case "authorization_code": String[] clientIdAndSecret = SsoUtils.getClientIdClientSecret(request); SsoUtils.validateClientRequest(request, clientIdAndSecret[0], clientIdAndSecret[1], scope, null); issueTokenForAuthCode(request, response, clientIdAndSecret[0], scope); break; case "password": handlePasswordGrantType(request, response, scope); break; case "urn:ovirt:params:oauth:grant-type:http": issueTokenUsingHttpHeaders(request, response); break; default: throw new OAuthException(SsoConstants.ERR_CODE_UNSUPPORTED_GRANT_TYPE, SsoConstants.ERR_CODE_UNSUPPORTED_GRANT_TYPE_MSG); } } catch(OAuthException ex) { SsoUtils.sendJsonDataWithMessage(response, ex); } catch(AuthenticationException ex) { SsoUtils.sendJsonDataWithMessage(response, SsoConstants.ERR_CODE_ACCESS_DENIED, ex); } catch(Exception ex) { SsoUtils.sendJsonDataWithMessage(response, SsoConstants.ERR_CODE_SERVER_ERROR, ex); } } private void issueTokenForAuthCode( HttpServletRequest request, HttpServletResponse response, String clientId, String scope) throws Exception { log.debug("Entered issueTokenForAuthCode"); String authCode = SsoUtils.getRequestParameter(request, SsoConstants.HTTP_PARAM_AUTHORIZATION_CODE, SsoConstants.HTTP_PARAM_AUTHORIZATION_CODE); String accessToken = ssoContext.getTokenForAuthCode(authCode); SsoUtils.validateRequestScope(request, accessToken, scope); SsoSession ssoSession = SsoUtils.getSsoSession(request, clientId, accessToken, true); log.debug("Sending json response"); SsoUtils.sendJsonData(response, buildResponse(request, ssoSession, clientId)); } private void handlePasswordGrantType( HttpServletRequest request, HttpServletResponse response, String scope) throws Exception { if (SsoUtils.scopeAsList(scope).contains("ovirt-ext=token:login-on-behalf")) { issueTokenForLoginOnBehalf(request, response, scope); } else { issueTokenForPasswd(request, response, scope); } } private void issueTokenForLoginOnBehalf( HttpServletRequest request, HttpServletResponse response, String scope) throws Exception { log.debug("Entered issueTokenForLoginOnBehalf"); String[] clientIdAndSecret = SsoUtils.getClientIdClientSecret(request); String username = SsoUtils.getRequestParameter(request, "username"); log.debug("Attempting to issueTokenForLoginOnBehalf for client: {}, user: {}", clientIdAndSecret[0], username); AuthenticationUtils.loginOnBehalf(ssoContext, request, username); String token = (String) request.getAttribute(SsoConstants.HTTP_REQ_ATTR_ACCESS_TOKEN); SsoUtils.validateRequestScope(request, token, scope); SsoSession ssoSession = SsoUtils.getSsoSession(request, token, true); if (ssoSession == null) { throw new OAuthException(SsoConstants.ERR_CODE_INVALID_GRANT, ssoContext.getLocalizationUtils().localize( SsoConstants.APP_ERROR_AUTHORIZATION_GRANT_EXPIRED_FOR_USERNAME_PASSWORD, (Locale) request.getAttribute(SsoConstants.LOCALE))); } log.debug("Sending json response"); SsoUtils.sendJsonData(response, buildResponse(ssoSession)); } private void issueTokenForPasswd( HttpServletRequest request, HttpServletResponse response, String scope) throws Exception { log.debug("Entered issueTokenForPasswd"); Credentials credentials = null; try { boolean isOpenIdScope = SsoUtils.scopeAsList(scope).contains(SsoConstants.OPENID_SCOPE); String clientId = null; if (isOpenIdScope) { String[] clientIdAndSecret = SsoUtils.getClientIdClientSecret(request); SsoUtils.validateClientRequest(request, clientIdAndSecret[0], clientIdAndSecret[1], scope, null); clientId = clientIdAndSecret[0]; } credentials = SsoUtils.translateUser(SsoUtils.getRequestParameter(request, "username"), SsoUtils.getRequestParameter(request, "password"), ssoContext); String token = null; if (credentials != null && SsoUtils.areCredentialsValid(request, credentials)) { AuthenticationUtils.handleCredentials(ssoContext, request, credentials, false); token = (String) request.getAttribute(SsoConstants.HTTP_REQ_ATTR_ACCESS_TOKEN); } log.debug("Attempting to issueTokenForPasswd for user: {}", Optional.ofNullable(credentials).map(Credentials::getUsername).orElse("null")); SsoSession ssoSession = SsoUtils.getSsoSessionFromRequest(request, token); if (ssoSession == null) { throw new OAuthException(SsoConstants.ERR_CODE_INVALID_GRANT, ssoContext.getLocalizationUtils().localize( SsoConstants.APP_ERROR_AUTHORIZATION_GRANT_EXPIRED_FOR_USERNAME_PASSWORD, (Locale) request.getAttribute(SsoConstants.LOCALE))); } SsoUtils.validateRequestScope(request, token, scope); log.debug("Sending json response"); SsoUtils.sendJsonData(response, isOpenIdScope ? buildResponse(request, ssoSession, clientId) : buildResponse(ssoSession)); } catch (AuthenticationException ex) { String profile = "N/A"; if (credentials != null) { profile = credentials.getProfile() == null ? "N/A" : credentials.getProfile(); } throw new AuthenticationException(String.format( ssoContext.getLocalizationUtils().localize( SsoConstants.APP_ERROR_CANNOT_AUTHENTICATE_USER_IN_DOMAIN, (Locale) request.getAttribute(SsoConstants.LOCALE)), credentials == null ? "N/A" : credentials.getUsername(), profile, ex.getMessage())); } } private void issueTokenUsingHttpHeaders(HttpServletRequest request, HttpServletResponse response) throws Exception { log.debug("Entered issueTokenUsingHttpHeaders"); try { AuthResult authResult = null; for (NonInteractiveAuth auth : getAuthSeq()) { authResult = auth.doAuth(request, response); if (authResult.getStatus() == Authn.AuthResult.SUCCESS || authResult.getStatus() == Authn.AuthResult.NEGOTIATION_INCOMPLETE) { break; } } if (authResult != null && authResult.getStatus() != Authn.AuthResult.SUCCESS) { log.debug("Authentication failed using http headers"); List<String> schemes = (List<String>) request.getAttribute(NegotiateAuthUtils.REQUEST_SCHEMES_KEY); for (String scheme : new HashSet<>(schemes == null ? Collections.emptyList() : schemes)) { response.setHeader("WWW-Authenticate", scheme); } response.sendError(HttpServletResponse.SC_UNAUTHORIZED); } else if (authResult != null && StringUtils.isNotEmpty(authResult.getToken())) { SsoSession ssoSession = SsoUtils.getSsoSessionFromRequest(request, authResult.getToken()); if (ssoSession == null) { throw new OAuthException(SsoConstants.ERR_CODE_INVALID_GRANT, ssoContext.getLocalizationUtils().localize( SsoConstants.APP_ERROR_AUTHORIZATION_GRANT_EXPIRED, (Locale) request.getAttribute(SsoConstants.LOCALE))); } log.debug("Sending json response"); SsoUtils.sendJsonData(response, buildResponse(ssoSession)); } else { throw new AuthenticationException( ssoContext.getLocalizationUtils().localize( SsoConstants.APP_ERROR_AUTHENTICATION_FAILED, (Locale) request.getAttribute(SsoConstants.LOCALE))); } } catch (Exception ex) { throw new AuthenticationException( String.format( ssoContext.getLocalizationUtils().localize( SsoConstants.APP_ERROR_CANNOT_AUTHENTICATE_USER, (Locale) request.getAttribute(SsoConstants.LOCALE)), ex.getMessage())); } } private Map<String, Object> buildResponse(SsoSession ssoSession) { Map<String, Object> payload = new HashMap<>(); payload.put(SsoConstants.JSON_ACCESS_TOKEN, ssoSession.getAccessToken()); payload.put(SsoConstants.JSON_SCOPE, StringUtils.isEmpty(ssoSession.getScope()) ? "" : ssoSession.getScope()); payload.put(SsoConstants.JSON_EXPIRES_IN, ssoSession.getValidTo().toString()); payload.put(SsoConstants.JSON_TOKEN_TYPE, "bearer"); return payload; } private Map<String, Object> buildResponse(HttpServletRequest request, SsoSession ssoSession, String clientId) throws Exception { Map<String, Object> payload = buildResponse(ssoSession); if (SsoUtils.scopeAsList(ssoSession.getScope()).contains(SsoConstants.OPENID_SCOPE)) { payload.put("id_token", SsoUtils.createJWT(request, ssoSession, clientId)); } return payload; } private List<NonInteractiveAuth> getAuthSeq() { String appAuthSeq = ssoContext.getSsoLocalConfig().getProperty("SSO_TOKEN_HTTP_LOGIN_SEQUENCE"); List<NonInteractiveAuth> authSeqList = new ArrayList<>(); if (StringUtils.isNotEmpty(appAuthSeq)) { for (char c : appAuthSeq.toCharArray()) { if (c == '~') { continue; } try { authSeqList.add(Enum.valueOf(NonInteractiveAuth.class, "" + c)); } catch (IllegalArgumentException e) { log.error("Unable to retrieve auth for value {}: {}", c, e.getMessage()); log.debug("Exception", e); } } } return authSeqList; } }