Ravi Nori has uploaded a new change for review. Change subject: aaa: Add support for rest api Basic auth ......................................................................
aaa: Add support for rest api Basic auth Add support for basic authorization for rest api Change-Id: Ib5f6975f2d306a4dc2d81b795ab4905e5d3281a1 Bug-Url: https://bugzilla.redhat.com/1092744 Signed-off-by: Ravi Nori <rn...@redhat.com> --- A backend/manager/modules/aaa/src/main/java/org/ovirt/engine/core/aaa/filters/SSORestApiLoginFilter.java A backend/manager/modules/enginesso/src/main/java/org/ovirt/engine/core/sso/servlets/RestApiLoginServlet.java M backend/manager/modules/enginesso/src/main/java/org/ovirt/engine/core/sso/servlets/SSOContextListener.java M backend/manager/modules/enginesso/src/main/java/org/ovirt/engine/core/sso/utils/SSOUtils.java M backend/manager/modules/enginesso/src/main/webapp/WEB-INF/web.xml M backend/manager/modules/restapi/webapp/src/main/webapp/WEB-INF/jboss-deployment-structure.xml M backend/manager/modules/restapi/webapp/src/main/webapp/WEB-INF/web.xml 7 files changed, 262 insertions(+), 25 deletions(-) git pull ssh://gerrit.ovirt.org:29418/ovirt-engine refs/changes/86/37786/1 diff --git a/backend/manager/modules/aaa/src/main/java/org/ovirt/engine/core/aaa/filters/SSORestApiLoginFilter.java b/backend/manager/modules/aaa/src/main/java/org/ovirt/engine/core/aaa/filters/SSORestApiLoginFilter.java new file mode 100644 index 0000000..74fc4ad --- /dev/null +++ b/backend/manager/modules/aaa/src/main/java/org/ovirt/engine/core/aaa/filters/SSORestApiLoginFilter.java @@ -0,0 +1,173 @@ +package org.ovirt.engine.core.aaa.filters; + +import org.ovirt.engine.api.extensions.ExtMap; +import org.ovirt.engine.core.common.action.CreateUserSessionParameters; +import org.ovirt.engine.core.common.action.VdcActionType; +import org.ovirt.engine.core.common.action.VdcReturnValueBase; +import org.ovirt.engine.core.common.constants.SessionConstants; +import org.ovirt.engine.core.utils.EngineLocalConfig; +import org.ovirt.engine.core.utils.serialization.json.JsonObjectDeserializer; +import org.ovirt.engine.core.uutils.crypto.ticket.TicketDecoder; +import org.ovirt.engine.core.uutils.net.HttpURLConnectionBuilder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.naming.InitialContext; +import javax.naming.NamingException; +import javax.net.ssl.TrustManagerFactory; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; +import java.io.ByteArrayOutputStream; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.security.GeneralSecurityException; +import java.security.KeyStore; +import java.util.HashMap; +import java.util.Map; + +public class SSORestApiLoginFilter implements Filter { + + private final Logger log = LoggerFactory.getLogger(getClass()); + private boolean loginAsAdmin = false; + private TicketDecoder ticketDecoder; + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + try { + KeyStore ks = KeyStore.getInstance(EngineLocalConfig.getInstance().getPKITrustStoreType()); + try (InputStream is = new FileInputStream(EngineLocalConfig.getInstance().getPKITrustStore())) { + ks.load(is, EngineLocalConfig.getInstance().getPKITrustStorePassword().toCharArray()); + } + ticketDecoder = new TicketDecoder(ks, EngineLocalConfig.getInstance().getSsoStoreEku()); + } catch (Exception e) { + throw new RuntimeException("Unable to instantiate TicketDecoder", e); + } + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, + ServletException { + HttpServletRequest req = (HttpServletRequest) request; + try { + if (!FiltersHelper.isAuthenticated(req) || !FiltersHelper.isSessionValid(getSessionId((HttpServletRequest) request))) { + authenticateWithSSO(req); + } + chain.doFilter(request, response); + } catch (NamingException ex) { + log.error("Unable to get reference to backend bean.", ex); + throw new RuntimeException(ex); + } + } + + protected HttpURLConnection create(URL url) throws IOException, GeneralSecurityException { + Boolean verifyHost = false; + Boolean verifyChain = true; + String httpsProtocol = "TLSv1"; + String trustManagerAlgorithm = TrustManagerFactory.getDefaultAlgorithm(); + String trustStore = EngineLocalConfig.getInstance().getProperty("ENGINE_PKI_TRUST_STORE"); + String trustStoreType = KeyStore.getDefaultType(); + String trustStorePassword = EngineLocalConfig.getInstance().getPKITrustStorePassword(); + Integer readTimeout = 0; + return new HttpURLConnectionBuilder(url).setHttpsProtocol(httpsProtocol) + .setReadTimeout(readTimeout) + .setTrustManagerAlgorithm(trustManagerAlgorithm) + .setTrustStore(trustStore) + .setTrustStorePassword(trustStorePassword) + .setTrustStoreType(trustStoreType) + .setURL(url) + .setVerifyChain(verifyChain) + .setVerifyHost(verifyHost).create(); + } + + protected static long copy(final InputStream input, final OutputStream output) throws IOException { + final byte[] buffer = new byte[8*1024]; + long count = 0; + int n; + while ((n = input.read(buffer)) != -1) { + output.write(buffer, 0, n); + count += n; + } + return count; + } + + private void authenticateWithSSO(HttpServletRequest req) throws ServletException { + String headerAuthorization = req.getHeader(FiltersHelper.Constants.HEADER_AUTHORIZATION); + HttpURLConnection connection = null; + try { + connection = create(new URL("https://127.0.0.1/ovirt-engine/sso/login-restapi")); + connection.setDoInput(true); + connection.setDoOutput(true); + connection.setRequestMethod("POST"); + connection.setRequestProperty(FiltersHelper.Constants.HEADER_AUTHORIZATION, headerAuthorization); + connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); + connection.setRequestProperty("Content-Length", "" + Integer.toString(headerAuthorization.getBytes().length)); + connection.setRequestProperty("Content-Language", "en-US"); + ByteArrayOutputStream os = new ByteArrayOutputStream(); + copy(connection.getInputStream(), os); + connection.connect(); + createUserSession(req, new String(os.toByteArray(), "UTF-8")); + } catch (Exception e) { + log.error("Exception obtaining ticket from sso", e.getMessage()); + } finally { + if (connection != null) { + connection.disconnect(); + } + } + + } + + private void createUserSession(HttpServletRequest req, String ticket) { + JsonObjectDeserializer deserializer = new JsonObjectDeserializer(); + InitialContext ctx = null; + try { + Map<String, Object> payload = deserializer.deserialize(ticketDecoder.decode(ticket), HashMap.class); + ctx = new InitialContext(); + String username = (String) payload.get("userName"); + VdcReturnValueBase queryRetVal = FiltersHelper.getBackend(ctx).runAction(VdcActionType.CreateUserSession, + new CreateUserSessionParameters( + (String) payload.get("profile"), + (ExtMap) payload.get("authRecord"), + (ExtMap) payload.get("principalRecord"), + loginAsAdmin)); + if (!queryRetVal.getSucceeded()) { + throw new RuntimeException(String.format("The user %s is not authorized to perform login", username)); + } + HttpSession httpSession = req.getSession(true); + httpSession.setAttribute( + SessionConstants.HTTP_SESSION_ENGINE_SESSION_ID_KEY, + queryRetVal.getActionReturnValue()); + } catch (Exception ex) { + log.error("Exception creating user session", ex.getMessage()); + } finally { + try { + if (ctx != null) { + ctx.close(); + } + } catch (NamingException ex) { + log.error("Unable to close context", ex); + } + } + } + + private String getSessionId(HttpServletRequest request) { + String sessionId = (String) request.getSession(false).getAttribute(SessionConstants.HTTP_SESSION_ENGINE_SESSION_ID_KEY); + if (sessionId == null) { + sessionId = (String) request.getAttribute(SessionConstants.HTTP_SESSION_ENGINE_SESSION_ID_KEY); + } + return sessionId; + } + @Override + public void destroy() { + } + +} diff --git a/backend/manager/modules/enginesso/src/main/java/org/ovirt/engine/core/sso/servlets/RestApiLoginServlet.java b/backend/manager/modules/enginesso/src/main/java/org/ovirt/engine/core/sso/servlets/RestApiLoginServlet.java new file mode 100644 index 0000000..49ae67e --- /dev/null +++ b/backend/manager/modules/enginesso/src/main/java/org/ovirt/engine/core/sso/servlets/RestApiLoginServlet.java @@ -0,0 +1,38 @@ +package org.ovirt.engine.core.sso.servlets; + +import org.ovirt.engine.core.sso.utils.AuthenticationUtils; +import org.ovirt.engine.core.sso.utils.Credentials; +import org.ovirt.engine.core.sso.utils.SSOUtils; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +public class RestApiLoginServlet extends HttpServlet { + + private static final long serialVersionUID = 4414518331391974859L; + + @Override + protected void service(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + Credentials credentials = SSOUtils.getUserCredentialsFromHeader(request); + try { + if (credentials != null) { + AuthenticationUtils.handleCredentials( + request.getSession(true), + credentials.getUsername(), + credentials.getPassword(), + credentials.getProfile()); + response.getOutputStream().write(SSOUtils.issueTicket(request.getSession(true), request).getBytes("UTF-8")); + response.getOutputStream().flush(); + } else { + response.sendError(HttpServletResponse.SC_UNAUTHORIZED); + } + } catch (Exception e) { + response.sendError(HttpServletResponse.SC_UNAUTHORIZED); + } + } + +} diff --git a/backend/manager/modules/enginesso/src/main/java/org/ovirt/engine/core/sso/servlets/SSOContextListener.java b/backend/manager/modules/enginesso/src/main/java/org/ovirt/engine/core/sso/servlets/SSOContextListener.java index c210581..a346761 100644 --- a/backend/manager/modules/enginesso/src/main/java/org/ovirt/engine/core/sso/servlets/SSOContextListener.java +++ b/backend/manager/modules/enginesso/src/main/java/org/ovirt/engine/core/sso/servlets/SSOContextListener.java @@ -65,7 +65,7 @@ KeyStore.PrivateKeyEntry entry = (KeyStore.PrivateKeyEntry) ks.getEntry( getStringInitParam(ctx, localConfig, SSO_STORE_ALIAS), new KeyStore.PasswordProtection(passwd.toCharArray())); - return new TicketEncoder(entry.getCertificate(), entry.getPrivateKey()); + return new TicketEncoder(entry.getCertificate(), entry.getPrivateKey(), 60); } private String getStringInitParam(ServletContext ctx, SSOLocalConfig localConfig, String name) { diff --git a/backend/manager/modules/enginesso/src/main/java/org/ovirt/engine/core/sso/utils/SSOUtils.java b/backend/manager/modules/enginesso/src/main/java/org/ovirt/engine/core/sso/utils/SSOUtils.java index 5998976..7e2d5bf 100644 --- a/backend/manager/modules/enginesso/src/main/java/org/ovirt/engine/core/sso/utils/SSOUtils.java +++ b/backend/manager/modules/enginesso/src/main/java/org/ovirt/engine/core/sso/utils/SSOUtils.java @@ -14,6 +14,7 @@ import javax.servlet.http.HttpSession; import java.io.IOException; import java.nio.charset.Charset; +import java.security.GeneralSecurityException; import java.sql.SQLException; import java.util.ArrayList; import java.util.Collections; @@ -54,42 +55,47 @@ HttpServletResponse response) throws IOException { try { - SSOConfig config = (SSOConfig) session.getServletContext().getAttribute(SSO_CONFIG); - ExtMap principalRecord = (ExtMap) session.getAttribute(SSOUtils.SSO_PRINCIPAL_RECORD_ATTR_NAME); - String authzName = (String) session.getAttribute(SSOUtils.SSO_AUTHZ_ATTR_NAME); + StringBuilder redirectUrl = new StringBuilder(SSOUtils.getParameter(request, POST_ACTION_URL)); - - String userId = config.getSsoUserGroupManager().getUserIdByExternalId(authzName, principalRecord.<String>get(Authz.PrincipalRecord.ID)); - if (userId == null) { - userId = DEFAULT_USER_ID; - } - - String principal = principalRecord.get(Authz.PrincipalRecord.PRINCIPAL); - Map<String, Object> payload = new HashMap<>(); - payload.put("userId", userId); - payload.put("version", SSO_VERSION); - payload.put("userName", principal != null ? principal : principalRecord.<String>get(Authz.PrincipalRecord.NAME)); - payload.put("email", principalRecord.<String>get(Authz.PrincipalRecord.EMAIL)); - payload.put("groupIds", getGroupIds(config.getSsoUserGroupManager(), authzName, principalRecord)); - payload.put("profile", session.getAttribute(SSOUtils.SSO_PROFILE_ATTR_NAME)); - payload.put("principalRecord", principalRecord); - payload.put("authRecord", session.getAttribute(SSOUtils.SSO_AUTH_RECORD_ATTR_NAME)); - - ObjectMapper mapper = new ObjectMapper().configure(Feature.FAIL_ON_UNKNOWN_PROPERTIES, false) - .enableDefaultTyping(ObjectMapper.DefaultTyping.OBJECT_AND_NON_CONCRETE); - mapper.getSerializationConfig().addMixInAnnotations(ExtMap.class, JsonExtMapMixIn.class); if (StringUtils.isNotEmpty(SSOUtils.getParameter(request, OPAQUE))) { redirectUrl.append("&opaque="); redirectUrl.append(response.encodeURL(SSOUtils.getParameter(request, OPAQUE))); } redirectUrl.append("&ticket="); - redirectUrl.append(response.encodeURL(((TicketEncoder) request.getServletContext().getAttribute(SSO_TICKET_ENCODER)).encode(mapper.writeValueAsString(payload)))); + redirectUrl.append(response.encodeURL(issueTicket(session, request))); response.sendRedirect(redirectUrl.toString()); } catch (Exception ex) { throw new RuntimeException(ex); } } + public static String issueTicket(HttpSession session, + HttpServletRequest request) throws SQLException, IOException, GeneralSecurityException { + SSOConfig config = (SSOConfig) session.getServletContext().getAttribute(SSO_CONFIG); + String authzName = (String) session.getAttribute(SSOUtils.SSO_AUTHZ_ATTR_NAME); + ExtMap principalRecord = (ExtMap) session.getAttribute(SSOUtils.SSO_PRINCIPAL_RECORD_ATTR_NAME); + String userId = config.getSsoUserGroupManager().getUserIdByExternalId(authzName, principalRecord.<String>get(Authz.PrincipalRecord.ID)); + if (userId == null) { + userId = DEFAULT_USER_ID; + } + + String principal = principalRecord.get(Authz.PrincipalRecord.PRINCIPAL); + Map<String, Object> payload = new HashMap<>(); + payload.put("userId", userId); + payload.put("version", SSO_VERSION); + payload.put("userName", principal != null ? principal : principalRecord.<String>get(Authz.PrincipalRecord.NAME)); + payload.put("email", principalRecord.<String>get(Authz.PrincipalRecord.EMAIL)); + payload.put("groupIds", getGroupIds(config.getSsoUserGroupManager(), authzName, principalRecord)); + payload.put("profile", session.getAttribute(SSOUtils.SSO_PROFILE_ATTR_NAME)); + payload.put("principalRecord", principalRecord); + payload.put("authRecord", session.getAttribute(SSOUtils.SSO_AUTH_RECORD_ATTR_NAME)); + + ObjectMapper mapper = new ObjectMapper().configure(Feature.FAIL_ON_UNKNOWN_PROPERTIES, false) + .enableDefaultTyping(ObjectMapper.DefaultTyping.OBJECT_AND_NON_CONCRETE); + mapper.getSerializationConfig().addMixInAnnotations(ExtMap.class, JsonExtMapMixIn.class); + return ((TicketEncoder) request.getServletContext().getAttribute(SSO_TICKET_ENCODER)).encode(mapper.writeValueAsString(payload)); + } + private static List<String> getGroupIds(SSOUserGroupManager userGroupManager, String authzName, ExtMap principalRecord) throws SQLException { List<String> externalGroupIds = new ArrayList<>(); principalRecord.put(Authz.PrincipalRecord.GROUPS, flatGroups(principalRecord, Authz.PrincipalRecord.GROUPS, new ArrayList<ExtMap>())); diff --git a/backend/manager/modules/enginesso/src/main/webapp/WEB-INF/web.xml b/backend/manager/modules/enginesso/src/main/webapp/WEB-INF/web.xml index d48ab46..89dc62d 100644 --- a/backend/manager/modules/enginesso/src/main/webapp/WEB-INF/web.xml +++ b/backend/manager/modules/enginesso/src/main/webapp/WEB-INF/web.xml @@ -189,6 +189,16 @@ </servlet-mapping> <servlet> + <servlet-name>RestApiLoginServlet</servlet-name> + <servlet-class>org.ovirt.engine.core.sso.servlets.RestApiLoginServlet</servlet-class> + </servlet> + + <servlet-mapping> + <servlet-name>RestApiLoginServlet</servlet-name> + <url-pattern>/login-restapi</url-pattern> + </servlet-mapping> + + <servlet> <servlet-name>LogoutServlet</servlet-name> <servlet-class>org.ovirt.engine.core.sso.servlets.LogoutServlet</servlet-class> </servlet> diff --git a/backend/manager/modules/restapi/webapp/src/main/webapp/WEB-INF/jboss-deployment-structure.xml b/backend/manager/modules/restapi/webapp/src/main/webapp/WEB-INF/jboss-deployment-structure.xml index 3b9302f..7a07986 100644 --- a/backend/manager/modules/restapi/webapp/src/main/webapp/WEB-INF/jboss-deployment-structure.xml +++ b/backend/manager/modules/restapi/webapp/src/main/webapp/WEB-INF/jboss-deployment-structure.xml @@ -35,6 +35,7 @@ <module name="org.ovirt.engine.api.restapi-jaxrs" annotations="true" services="import"/> <module name="org.ovirt.engine.api.restapi-types" annotations="true" services="import"/> <module name="org.ovirt.engine.core.aaa"/> + <module name="org.ovirt.engine.api.ovirt-engine-extensions-api"/> </dependencies> </deployment> diff --git a/backend/manager/modules/restapi/webapp/src/main/webapp/WEB-INF/web.xml b/backend/manager/modules/restapi/webapp/src/main/webapp/WEB-INF/web.xml index ff1470f..11030b1 100644 --- a/backend/manager/modules/restapi/webapp/src/main/webapp/WEB-INF/web.xml +++ b/backend/manager/modules/restapi/webapp/src/main/webapp/WEB-INF/web.xml @@ -70,6 +70,15 @@ </filter-mapping> <filter> + <filter-name>SSORestApiLoginFilter</filter-name> + <filter-class>org.ovirt.engine.core.aaa.filters.SSORestApiLoginFilter</filter-class> + </filter> + <filter-mapping> + <filter-name>SSORestApiLoginFilter</filter-name> + <url-pattern>/*</url-pattern> + </filter-mapping> + + <filter> <filter-name>BasicAuthenticationFilter</filter-name> <filter-class>org.ovirt.engine.core.aaa.filters.BasicAuthenticationFilter</filter-class> <init-param> -- To view, visit http://gerrit.ovirt.org/37786 To unsubscribe, visit http://gerrit.ovirt.org/settings Gerrit-MessageType: newchange Gerrit-Change-Id: Ib5f6975f2d306a4dc2d81b795ab4905e5d3281a1 Gerrit-PatchSet: 1 Gerrit-Project: ovirt-engine Gerrit-Branch: master Gerrit-Owner: Ravi Nori <rn...@redhat.com> _______________________________________________ Engine-patches mailing list Engine-patches@ovirt.org http://lists.ovirt.org/mailman/listinfo/engine-patches