From 5310ed9adea7fcf2cbcefc136e91674a412a1fa9 Mon Sep 17 00:00:00 2001 From: Mathieu Baudier Date: Tue, 10 Sep 2019 09:44:16 +0200 Subject: [PATCH] Introduce CMS Integration (with other programming languages and approaches). --- .../cms/integration/CmsLoginServlet.java | 111 ++++++++++ .../org/argeo/cms/integration/JcrServlet.java | 197 ++++++++++++++++++ 2 files changed, 308 insertions(+) create mode 100644 org.argeo.cms/src/org/argeo/cms/integration/CmsLoginServlet.java create mode 100644 org.argeo.cms/src/org/argeo/cms/integration/JcrServlet.java diff --git a/org.argeo.cms/src/org/argeo/cms/integration/CmsLoginServlet.java b/org.argeo.cms/src/org/argeo/cms/integration/CmsLoginServlet.java new file mode 100644 index 000000000..0a4e9b4ea --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/integration/CmsLoginServlet.java @@ -0,0 +1,111 @@ +package org.argeo.cms.integration; + +import java.io.IOException; + +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.NameCallback; +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.callback.UnsupportedCallbackException; +import javax.security.auth.login.LoginContext; +import javax.security.auth.login.LoginException; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.argeo.cms.auth.CmsSessionId; +import org.argeo.cms.auth.HttpRequestCallback; +import org.argeo.cms.auth.HttpRequestCallbackHandler; +import org.argeo.node.NodeConstants; +import org.osgi.service.useradmin.Authorization; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.stream.JsonWriter; + +/** Externally authenticate an http session. */ +public class CmsLoginServlet extends HttpServlet { + private static final long serialVersionUID = 2478080654328751539L; + private Gson gson = new GsonBuilder().setPrettyPrinting().create(); + + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + doPost(request, response); + } + + @Override + protected void doPost(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + LoginContext lc = null; + String username = request.getParameter("username"); + String password = request.getParameter("password"); + if (username != null && password != null) { + try { + lc = new LoginContext(NodeConstants.LOGIN_CONTEXT_USER, + new HttpRequestCallbackHandler(request, response) { + public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { + for (Callback callback : callbacks) { + if (callback instanceof NameCallback && username != null) + ((NameCallback) callback).setName(username); + else if (callback instanceof PasswordCallback && password != null) + ((PasswordCallback) callback).setPassword(password.toCharArray()); + else if (callback instanceof HttpRequestCallback) { + ((HttpRequestCallback) callback).setRequest(request); + ((HttpRequestCallback) callback).setResponse(response); + } + } + } + }); + lc.login(); + + CmsSessionId cmsSessionId = (CmsSessionId) lc.getSubject().getPrivateCredentials(CmsSessionId.class) + .toArray()[0]; + Authorization authorization = (Authorization) lc.getSubject().getPrivateCredentials(Authorization.class) + .toArray()[0]; + + JsonWriter jsonWriter = gson.newJsonWriter(response.getWriter()); + jsonWriter.beginObject(); + // Authorization + jsonWriter.name("username").value(authorization.getName()); + jsonWriter.name("displayName").value(authorization.toString()); + // Roles + jsonWriter.name("roles").beginArray(); + for (String role : authorization.getRoles()) + if (!role.equals(authorization.getName())) + jsonWriter.value(role); + jsonWriter.endArray(); + // CMS session + jsonWriter.name("cmsSession").beginObject(); + jsonWriter.name("uuid").value(cmsSessionId.getUuid().toString()); + jsonWriter.endObject(); + + jsonWriter.endObject(); + + String redirectTo = redirectTo(request); + if (redirectTo != null) + response.sendRedirect(redirectTo); + } catch (LoginException e) { + response.setStatus(403); + return; + } + } else { + response.setStatus(403); + return; + } + } + + /** Does nothing by default. */ + protected void loginSucceeded(LoginContext lc, HttpServletRequest request, HttpServletResponse response) { + + } + + /** Send HTTP code 403 by default. */ + protected void loginFailed(LoginContext lc, HttpServletRequest request, HttpServletResponse response) { + + } + + protected String redirectTo(HttpServletRequest request) { + return null; + } +} diff --git a/org.argeo.cms/src/org/argeo/cms/integration/JcrServlet.java b/org.argeo.cms/src/org/argeo/cms/integration/JcrServlet.java new file mode 100644 index 000000000..a1170937e --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/integration/JcrServlet.java @@ -0,0 +1,197 @@ +package org.argeo.cms.integration; + +import java.io.IOException; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; + +import javax.jcr.Node; +import javax.jcr.NodeIterator; +import javax.jcr.Property; +import javax.jcr.PropertyIterator; +import javax.jcr.PropertyType; +import javax.jcr.Repository; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.Value; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.jackrabbit.api.JackrabbitNode; +import org.apache.jackrabbit.api.JackrabbitValue; +import org.argeo.jcr.JcrUtils; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.stream.JsonWriter; + +/** Canonical access to a JCR repository via web services. */ +public class JcrServlet extends HttpServlet { + private static final long serialVersionUID = 6536175260540484539L; + + private final static String PARAM_VERBOSE = "verbose"; + private final static String PARAM_DEPTH = "depth"; + private final static String PARAM_PRETTY = "pretty"; + + private final static Log log = LogFactory.getLog(JcrServlet.class); + + private Repository repository; + private Integer maxDepth = 8; + + private Gson gson = new GsonBuilder().setPrettyPrinting().create(); + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + String path = req.getPathInfo(); + if (log.isTraceEnabled()) + log.trace("Data service: " + path); + path = URLDecoder.decode(path, StandardCharsets.UTF_8.name()); + String[] pathTokens = path.split("/"); + // TODO make it more robust + + String domain = pathTokens[1]; + String dataWorkspace = "vje_" + domain; + String jcrPath = path.substring(domain.length() + 1); + + boolean verbose = req.getParameter(PARAM_VERBOSE) != null && !req.getParameter(PARAM_VERBOSE).equals("false"); + int depth = 1; + if (req.getParameter(PARAM_DEPTH) != null) { + depth = Integer.parseInt(req.getParameter(PARAM_DEPTH)); + if (depth > maxDepth) + throw new RuntimeException("Depth " + depth + " is higher than maximum " + maxDepth); + } + String urlBase = null; + if (req.getParameter("html") != null) + urlBase = req.getServletPath() + '/' + domain; + boolean pretty = req.getParameter(PARAM_PRETTY) != null; + + resp.setContentType("application/json"); + Session session = null; + try { + // authentication + session = openJcrSession(req, resp, repository, dataWorkspace); + if (!session.itemExists(jcrPath)) + throw new RuntimeException("JCR node " + jcrPath + " does not exist"); + Node node = session.getNode(jcrPath); + + JsonWriter jsonWriter; + if (pretty) + jsonWriter = new GsonBuilder().setPrettyPrinting().create().newJsonWriter(resp.getWriter()); + else + jsonWriter = gson.newJsonWriter(resp.getWriter()); + jsonWriter.beginObject(); + writeNodeProperties(node, jsonWriter, verbose, urlBase); + writeNodeChildren(node, jsonWriter, depth, verbose, urlBase); + jsonWriter.endObject(); + jsonWriter.flush(); + } catch (RepositoryException e) { + resp.setStatus(500); + throw new RuntimeException("Cannot process JCR node " + jcrPath, e); + } finally { + JcrUtils.logoutQuietly(session); + } + } + + protected Session openJcrSession(HttpServletRequest req, HttpServletResponse resp, Repository repository, + String workspace) throws RepositoryException { + return repository.login(); + } + + protected void writeNodeProperties(Node node, JsonWriter jsonWriter, boolean verbose, String urlBase) + throws RepositoryException, IOException { + String jcrPath = node.getPath(); + jsonWriter.name("jcr:name"); + jsonWriter.value(node.getName()); + jsonWriter.name("jcr:path"); + jsonWriter.value(jcrPath); + + PropertyIterator pit = node.getProperties(); + properties: while (pit.hasNext()) { + Property property = pit.nextProperty(); + if (!verbose) { + if (property.getName().equals("jcr:primaryType") || property.getName().equals("jcr:mixinTypes") + || property.getName().equals("jcr:created") || property.getName().equals("jcr:createdBy") + || property.getName().equals("jcr:lastModified") + || property.getName().equals("jcr:lastModifiedBy")) { + continue properties;// skip + } + } + + if (property.getType() == PropertyType.BINARY) { + if (!(node instanceof JackrabbitNode)) { + continue properties;// skip + } + } + + if (!property.isMultiple()) { + jsonWriter.name(property.getName()); + writePropertyValue(property.getType(), property.getValue(), jsonWriter); + } else { + jsonWriter.name(property.getName()); + jsonWriter.beginArray(); + Value[] values = property.getValues(); + for (Value value : values) { + writePropertyValue(property.getType(), value, jsonWriter); + } + jsonWriter.endArray(); + } + } + + // meta data + if (verbose) { + jsonWriter.name("jcr:identifier"); + jsonWriter.value(node.getIdentifier()); + } + if (urlBase != null) {// TODO make it browsable + jsonWriter.name("url"); + String url = urlBase + jcrPath; + jsonWriter.value("" + url + ""); + } + } + + protected void writePropertyValue(int type, Value value, JsonWriter jsonWriter) + throws RepositoryException, IOException { + if (type == PropertyType.DOUBLE) + jsonWriter.value(value.getDouble()); + else if (type == PropertyType.LONG) + jsonWriter.value(value.getLong()); + else if (type == PropertyType.BINARY) { + if (value instanceof JackrabbitValue) { + String contentIdentity = ((JackrabbitValue) value).getContentIdentity(); + jsonWriter.value("SHA256:" + contentIdentity); + } + } else + jsonWriter.value(value.getString()); + } + + protected void writeNodeChildren(Node node, JsonWriter jsonWriter, int depth, boolean verbose, String urlBase) + throws RepositoryException, IOException { + if (!node.hasNodes()) + return; + if (depth <= 0) + return; + NodeIterator nit = node.getNodes(); + jsonWriter.name("jcr:nodes"); + jsonWriter.beginArray(); + while (nit.hasNext()) { + Node child = nit.nextNode(); + jsonWriter.beginObject(); + writeNodeProperties(child, jsonWriter, verbose, urlBase); + writeNodeChildren(child, jsonWriter, depth - 1, verbose, urlBase); + jsonWriter.endObject(); + } + jsonWriter.endArray(); + } + + public void setRepository(Repository repository) { + this.repository = repository; + } + + public void setMaxDepth(Integer maxDepth) { + this.maxDepth = maxDepth; + } + +} -- 2.30.2