/** * Find a file with its name matching last path part * * @param securityContext * @param request * @param path * @return * @throws FrameworkException */ private File findFile( final SecurityContext securityContext, HttpServletRequest request, final String path) throws FrameworkException { List<Linkable> entryPoints = findPossibleEntryPoints(securityContext, request, path); // If no results were found, try to replace whitespace by '+' or '%20' if (entryPoints.isEmpty()) { entryPoints = findPossibleEntryPoints( securityContext, request, PathHelper.replaceWhitespaceByPlus(path)); } if (entryPoints.isEmpty()) { entryPoints = findPossibleEntryPoints( securityContext, request, PathHelper.replaceWhitespaceByPercentTwenty(path)); } for (Linkable node : entryPoints) { if (node instanceof File && (path.equals(node.getPath()) || node.getUuid().equals(PathHelper.getName(path)))) { return (File) node; } } return null; }
private List<Linkable> findPossibleEntryPoints( final SecurityContext securityContext, HttpServletRequest request, final String path) throws FrameworkException { List<Linkable> possibleEntryPoints = (List<Linkable>) request.getAttribute(POSSIBLE_ENTRY_POINTS); if (CollectionUtils.isNotEmpty(possibleEntryPoints)) { return possibleEntryPoints; } if (path.length() > 0) { logger.log(Level.FINE, "Requested name {0}", path); possibleEntryPoints = findPossibleEntryPointsByPath(securityContext, request, path); if (possibleEntryPoints.isEmpty()) { possibleEntryPoints = findPossibleEntryPointsByUuid(securityContext, request, PathHelper.getName(path)); } return possibleEntryPoints; } return Collections.EMPTY_LIST; }
/** * Find a page with matching path. * * <p>To be compatible with older versions, fallback to name-only lookup. * * @param securityContext * @param request * @param path * @return * @throws FrameworkException */ private Page findPage( final SecurityContext securityContext, HttpServletRequest request, final String path) throws FrameworkException { List<Linkable> entryPoints = findPossibleEntryPoints(securityContext, request, path); if (entryPoints.isEmpty()) { entryPoints = findPossibleEntryPointsByName(securityContext, request, PathHelper.getName(path)); } for (Linkable node : entryPoints) { if (node instanceof Page) { // && path.equals(node.getPath())) { return (Page) node; } } return null; }
/** * Find first node whose name matches the last part of the given path * * @param securityContext * @param request * @param path * @return * @throws FrameworkException */ private AbstractNode findFirstNodeByName( final SecurityContext securityContext, HttpServletRequest request, final String path) throws FrameworkException { final String name = PathHelper.getName(path); if (!name.isEmpty()) { logger.log(Level.FINE, "Requested name: {0}", name); final Result results = StructrApp.getInstance(securityContext) .nodeQuery() .and(AbstractNode.name, name) .getResult(); logger.log(Level.FINE, "{0} results", results.size()); request.setAttribute(POSSIBLE_ENTRY_POINTS, results.getResults()); return (results.size() > 0 ? (AbstractNode) results.get(0) : null); } return null; }
@Override protected void doGet(final HttpServletRequest request, final HttpServletResponse response) { final Authenticator auth = config.getAuthenticator(); final SecurityContext securityContext; final App app; try { String path = request.getPathInfo(); // check for registration (has its own tx because of write access if (checkRegistration(auth, request, response, path)) { return; } // isolate request authentication in a transaction try (final Tx tx = StructrApp.getInstance().tx()) { securityContext = auth.initializeAndExamineRequest(request, response); tx.success(); } app = StructrApp.getInstance(securityContext); try (final Tx tx = app.tx()) { // Ensure access mode is frontend securityContext.setAccessMode(AccessMode.Frontend); request.setCharacterEncoding("UTF-8"); // Important: Set character encoding before calling response.getWriter() !!, see Servlet // Spec 5.4 response.setCharacterEncoding("UTF-8"); boolean dontCache = false; logger.log(Level.FINE, "Path info {0}", path); // don't continue on redirects if (response.getStatus() == 302) { return; } Principal user = securityContext.getUser(false); if (user != null) { // Don't cache if a user is logged in dontCache = true; } final RenderContext renderContext = RenderContext.getInstance(request, response, getEffectiveLocale(request)); renderContext.setResourceProvider(config.getResourceProvider()); EditMode edit = renderContext.getEditMode(user); DOMNode rootElement = null; AbstractNode dataNode = null; String[] uriParts = PathHelper.getParts(path); if ((uriParts == null) || (uriParts.length == 0)) { // find a visible page rootElement = findIndexPage(securityContext); logger.log(Level.FINE, "No path supplied, trying to find index page"); } else { if (rootElement == null) { rootElement = findPage(securityContext, request, path); } else { dontCache = true; } } if (rootElement == null) { // No page found // Look for a file File file = findFile(securityContext, request, path); if (file != null) { streamFile(securityContext, file, request, response, edit); return; } // store remaining path parts in request Matcher matcher = threadLocalUUIDMatcher.get(); boolean requestUriContainsUuids = false; for (int i = 0; i < uriParts.length; i++) { request.setAttribute(uriParts[i], i); matcher.reset(uriParts[i]); // set to "true" if part matches UUID pattern requestUriContainsUuids |= matcher.matches(); } if (!requestUriContainsUuids) { // Try to find a data node by name dataNode = findFirstNodeByName(securityContext, request, path); } else { dataNode = findNodeByUuid(securityContext, PathHelper.getName(path)); } if (dataNode != null) { // Last path part matches a data node // Remove last path part and try again searching for a page // clear possible entry points request.removeAttribute(POSSIBLE_ENTRY_POINTS); rootElement = findPage( securityContext, request, StringUtils.substringBeforeLast(path, PathHelper.PATH_SEP)); renderContext.setDetailsDataObject(dataNode); // Start rendering on data node if (rootElement == null && dataNode instanceof DOMNode) { rootElement = ((DOMNode) dataNode); } } } // Still nothing found, do error handling if (rootElement == null) { // Check if security context has set an 401 status if (response.getStatus() == HttpServletResponse.SC_UNAUTHORIZED) { try { UiAuthenticator.writeUnauthorized(response); } catch (IllegalStateException ise) { } } else { rootElement = notFound(response, securityContext); } } if (rootElement == null) { return; } if (EditMode.WIDGET.equals(edit) || dontCache) { setNoCacheHeaders(response); } if (!securityContext.isVisible(rootElement)) { rootElement = notFound(response, securityContext); if (rootElement == null) { return; } } if (securityContext.isVisible(rootElement)) { if (!EditMode.WIDGET.equals(edit) && !dontCache && notModifiedSince(request, response, rootElement, dontCache)) { ServletOutputStream out = response.getOutputStream(); out.flush(); // response.flushBuffer(); out.close(); } else { // prepare response response.setCharacterEncoding("UTF-8"); String contentType = rootElement.getProperty(Page.contentType); if (contentType != null && contentType.equals("text/html")) { contentType = contentType.concat(";charset=UTF-8"); response.setContentType(contentType); } else { // Default response.setContentType("text/html;charset=UTF-8"); } response.setHeader("Strict-Transport-Security", "max-age=60"); response.setHeader("X-Content-Type-Options", "nosniff"); response.setHeader("X-Frame-Options", "SAMEORIGIN"); response.setHeader("X-XSS-Protection", "1; mode=block"); // async or not? boolean isAsync = HttpService.parseBoolean( Services.getBaseConfiguration().getProperty(HttpService.ASYNC), true); if (isAsync) { final AsyncContext async = request.startAsync(); final ServletOutputStream out = async.getResponse().getOutputStream(); final AtomicBoolean finished = new AtomicBoolean(false); final DOMNode rootNode = rootElement; threadPool.submit( new Runnable() { @Override public void run() { try (final Tx tx = app.tx()) { // final long start = System.currentTimeMillis(); // render rootNode.render(securityContext, renderContext, 0); finished.set(true); // final long end = System.currentTimeMillis(); // System.out.println("Done in " + (end-start) + " ms"); tx.success(); } catch (Throwable t) { t.printStackTrace(); final String errorMsg = t.getMessage(); try { // response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); response.sendError( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, errorMsg); finished.set(true); } catch (IOException ex) { ex.printStackTrace(); } } } }); // start output write listener out.setWriteListener( new WriteListener() { @Override public void onWritePossible() throws IOException { try { final Queue<String> queue = renderContext.getBuffer().getQueue(); while (out.isReady()) { String buffer = null; synchronized (queue) { buffer = queue.poll(); } if (buffer != null) { out.print(buffer); } else { if (finished.get()) { async.complete(); response.setStatus(HttpServletResponse.SC_OK); // prevent this block from being called again break; } Thread.sleep(1); } } } catch (Throwable t) { t.printStackTrace(); } } @Override public void onError(Throwable t) { t.printStackTrace(); } }); } else { final StringRenderBuffer buffer = new StringRenderBuffer(); renderContext.setBuffer(buffer); // render rootElement.render(securityContext, renderContext, 0); response.getOutputStream().write(buffer.getBuffer().toString().getBytes("utf-8")); response.getOutputStream().flush(); response.getOutputStream().close(); } } } else { notFound(response, securityContext); } tx.success(); } catch (FrameworkException fex) { fex.printStackTrace(); logger.log(Level.SEVERE, "Exception while processing request", fex); } } catch (IOException | FrameworkException t) { t.printStackTrace(); logger.log(Level.SEVERE, "Exception while processing request", t); UiAuthenticator.writeInternalServerError(response); } }
/** * This method checks all configured external authentication services. * * @param request * @param response * @return user */ protected static Principal checkExternalAuthentication( final HttpServletRequest request, final HttpServletResponse response) throws FrameworkException { final String path = PathHelper.clean(request.getPathInfo()); final String[] uriParts = PathHelper.getParts(path); logger.log(Level.FINE, "Checking external authentication ..."); if (uriParts == null || uriParts.length != 3 || !("oauth".equals(uriParts[0]))) { logger.log(Level.FINE, "Incorrect URI parts for OAuth process, need /oauth/<name>/<action>"); return null; } final String name = uriParts[1]; final String action = uriParts[2]; // Try to getValue an OAuth2 server for the given name final StructrOAuthClient oauthServer = StructrOAuthClient.getServer(name); if (oauthServer == null) { logger.log(Level.FINE, "No OAuth2 authentication server configured for {0}", path); return null; } if ("login".equals(action)) { try { response.sendRedirect(oauthServer.getEndUserAuthorizationRequestUri(request)); return null; } catch (Exception ex) { logger.log(Level.SEVERE, "Could not send redirect to authorization server", ex); } } else if ("auth".equals(action)) { final String accessToken = oauthServer.getAccessToken(request); final SecurityContext superUserContext = SecurityContext.getSuperUserInstance(); if (accessToken != null) { logger.log(Level.FINE, "Got access token {0}", accessToken); // securityContext.setAttribute("OAuthAccessToken", accessToken); String value = oauthServer.getCredential(request); logger.log(Level.FINE, "Got credential value: {0}", new Object[] {value}); if (value != null) { PropertyKey credentialKey = oauthServer.getCredentialKey(); Principal user = AuthHelper.getPrincipalForCredential(credentialKey, value); if (user == null && userAutoCreate) { user = RegistrationResource.createUser( superUserContext, credentialKey, value, true, userClass); } if (user != null) { AuthHelper.doLogin(request, user); HtmlServlet.setNoCacheHeaders(response); try { logger.log(Level.FINE, "Response status: {0}", response.getStatus()); response.sendRedirect(oauthServer.getReturnUri()); } catch (IOException ex) { logger.log( Level.SEVERE, "Could not redirect to {0}: {1}", new Object[] {oauthServer.getReturnUri(), ex}); } return user; } } } } try { response.sendRedirect(oauthServer.getErrorUri()); } catch (IOException ex) { logger.log( Level.SEVERE, "Could not redirect to {0}: {1}", new Object[] {oauthServer.getReturnUri(), ex}); } return null; }