| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.sling.jcr.jackrabbit.accessmanager.post; |
| |
| import java.io.IOException; |
| import java.net.URI; |
| import java.net.URISyntaxException; |
| import java.util.ArrayList; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| |
| import javax.jcr.RepositoryException; |
| import javax.jcr.Session; |
| import javax.jcr.security.AccessControlList; |
| import javax.jcr.security.AccessControlManager; |
| import javax.jcr.security.AccessControlPolicy; |
| import javax.jcr.security.AccessControlPolicyIterator; |
| import javax.servlet.ServletException; |
| import javax.servlet.http.HttpServletRequest; |
| import javax.servlet.http.HttpServletResponse; |
| |
| import org.apache.sling.api.SlingHttpServletRequest; |
| import org.apache.sling.api.SlingHttpServletResponse; |
| import org.apache.sling.api.resource.ResourceNotFoundException; |
| import org.apache.sling.api.resource.ResourceUtil; |
| import org.apache.sling.api.servlets.SlingAllMethodsServlet; |
| import org.apache.sling.api.wrappers.SlingRequestPaths; |
| import org.apache.sling.servlets.post.AbstractPostResponse; |
| import org.apache.sling.servlets.post.HtmlResponse; |
| import org.apache.sling.servlets.post.JSONResponse; |
| import org.apache.sling.servlets.post.Modification; |
| import org.apache.sling.servlets.post.PostResponse; |
| import org.apache.sling.servlets.post.PostResponseCreator; |
| import org.apache.sling.servlets.post.SlingPostConstants; |
| import org.apache.sling.servlets.post.impl.helper.MediaRangeList; |
| import org.osgi.framework.Constants; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| /** |
| * Base class for all the POST servlets for the AccessManager operations |
| */ |
| public abstract class AbstractAccessPostServlet extends SlingAllMethodsServlet { |
| private static final long serialVersionUID = -5918670409789895333L; |
| |
| /** |
| * default log |
| */ |
| private final transient Logger log = LoggerFactory.getLogger(getClass()); |
| |
| /** Sorted list of post response creator holders. */ |
| private final List<PostResponseCreatorHolder> postResponseCreators = new ArrayList<>(); |
| |
| /** Cached array of post response creators used during request processing. */ |
| private PostResponseCreator[] cachedPostResponseCreators = new PostResponseCreator[0]; |
| |
| |
| /* (non-Javadoc) |
| * @see org.apache.sling.api.servlets.SlingAllMethodsServlet#doPost(org.apache.sling.api.SlingHttpServletRequest, org.apache.sling.api.SlingHttpServletResponse) |
| */ |
| @Override |
| protected void doPost(SlingHttpServletRequest request, |
| SlingHttpServletResponse httpResponse) throws ServletException, |
| IOException { |
| // prepare the response |
| PostResponse response = createPostResponse(request); |
| response.setReferer(request.getHeader("referer")); |
| |
| // calculate the paths |
| String path = getItemPath(request); |
| response.setPath(path); |
| |
| // location |
| response.setLocation(externalizePath(request, path)); |
| |
| // parent location |
| path = ResourceUtil.getParent(path); |
| if (path != null) { |
| response.setParentLocation(externalizePath(request, path)); |
| } |
| |
| Session session = request.getResourceResolver().adaptTo(Session.class); |
| |
| final List<Modification> changes = new ArrayList<>(); |
| |
| try { |
| handleOperation(request, response, changes); |
| |
| // set changes on html response |
| for(Modification change : changes) { |
| switch ( change.getType() ) { |
| case MODIFY : response.onModified(change.getSource()); break; |
| case DELETE : response.onDeleted(change.getSource()); break; |
| case MOVE : response.onMoved(change.getSource(), change.getDestination()); break; |
| case COPY : response.onCopied(change.getSource(), change.getDestination()); break; |
| case CREATE : response.onCreated(change.getSource()); break; |
| case ORDER : response.onChange("ordered", change.getSource(), change.getDestination()); break; |
| default: |
| break; |
| } |
| } |
| |
| if (session.hasPendingChanges()) { |
| session.save(); |
| } |
| } catch (ResourceNotFoundException rnfe) { |
| response.setStatus(HttpServletResponse.SC_NOT_FOUND, |
| rnfe.getMessage()); |
| } catch (Throwable throwable) { |
| log.debug(String.format("Exception while handling POST %s with %s", |
| request.getResource().getPath(), getClass().getName()), throwable); |
| response.setError(throwable); |
| } finally { |
| try { |
| if (session.hasPendingChanges()) { |
| session.refresh(false); |
| } |
| } catch (RepositoryException e) { |
| log.warn("RepositoryException in finally block: {}", |
| e.getMessage(), e); |
| } |
| } |
| |
| // check for redirect URL if processing succeeded |
| if (response.isSuccessful()) { |
| String redirect = getRedirectUrl(request, response); |
| if (redirect != null) { |
| httpResponse.sendRedirect(redirect); |
| return; |
| } |
| } |
| |
| // create a html response and send if unsuccessful or no redirect |
| response.send(httpResponse, isSetStatus(request)); |
| } |
| |
| /** |
| * Creates an instance of a HtmlResponse. |
| * @param req The request being serviced |
| * @return a {@link org.apache.sling.servlets.post.JSONResponse} if any of these conditions are true: |
| * <ul> |
| * <li>the response content type is application/json |
| * </ul> |
| * or a {@link org.apache.sling.servlets.post.HtmlResponse} otherwise |
| * @deprecated use {@link #createPostResponse(SlingHttpServletRequest)} instead |
| */ |
| @Deprecated |
| protected AbstractPostResponse createHtmlResponse(SlingHttpServletRequest req) { |
| return (AbstractPostResponse)createPostResponse(req); |
| } |
| |
| /** |
| * Creates an instance of a PostResponse. |
| * @param req The request being serviced |
| * @return a {@link org.apache.sling.servlets.post.JSONResponse} if any of these conditions are true: |
| * <ul> |
| * <li> the request has an <code>Accept</code> header of <code>application/json</code></li> |
| * <li>the request is a JSON POST request (see SLING-1172)</li> |
| * <li>the request has a request parameter <code>:accept=application/json</code></li> |
| * </ul> |
| * or a {@link org.apache.sling.api.servlets.PostResponse} otherwise |
| */ |
| PostResponse createPostResponse(final SlingHttpServletRequest req) { |
| for (final PostResponseCreator creator : cachedPostResponseCreators) { |
| final PostResponse response = creator.createPostResponse(req); |
| if (response != null) { |
| return response; |
| } |
| } |
| |
| //for backward compatibility, if no "accept" request param or header is supplied |
| // then prefer the SlingHttpServletRequest#getResponseContentType value |
| MediaRangeList mediaRangeList = null; |
| String queryParam = req.getParameter(MediaRangeList.PARAM_ACCEPT); |
| if (queryParam == null || queryParam.trim().length() == 0) { |
| String headerValue = req.getHeader(MediaRangeList.HEADER_ACCEPT); |
| if (headerValue == null || headerValue.trim().length() == 0) { |
| //no param or header supplied, so try the response content type |
| mediaRangeList = new MediaRangeList(req.getResponseContentType()); |
| } |
| } |
| |
| // Fall through to default behavior |
| if (mediaRangeList == null) { |
| mediaRangeList = new MediaRangeList(req); |
| } |
| if (JSONResponse.RESPONSE_CONTENT_TYPE.equals(mediaRangeList.prefer("text/html", JSONResponse.RESPONSE_CONTENT_TYPE))) { |
| return new JSONResponse(); |
| } else { |
| return new HtmlResponse(); |
| } |
| } |
| |
| /** |
| * Extending Servlet should implement this operation to do the work |
| * |
| * @param request the sling http request to process |
| * @param response the response |
| * @param changes the changes to report |
| * @throws RepositoryException if any errors applying the changes |
| * |
| * @deprecated use {@link #handleOperation(SlingHttpServletRequest, PostResponse, List)} instead |
| */ |
| @Deprecated |
| protected void handleOperation(SlingHttpServletRequest request, |
| AbstractPostResponse response, List<Modification> changes) throws RepositoryException { |
| handleOperation(request, (PostResponse)response, changes); |
| } |
| |
| /** |
| * Extending Servlet should implement this operation to do the work |
| * |
| * @param request the sling http request to process |
| * @param response the response |
| * @param changes the changes to report |
| * @throws RepositoryException if any errors applying the changes |
| */ |
| protected abstract void handleOperation(SlingHttpServletRequest request, |
| PostResponse response, List<Modification> changes) throws RepositoryException; |
| |
| |
| /** |
| * compute redirect URL (SLING-126) |
| * |
| * @param request the sling http request to process |
| * @param ctx the post processor |
| * @return the redirect location or <code>null</code> |
| * @deprecated use {@link #getRedirectUrl(HttpServletRequest, PostResponse)} instead |
| */ |
| @Deprecated |
| protected String getRedirectUrl(HttpServletRequest request, AbstractPostResponse ctx) { |
| return getRedirectUrl(request, (PostResponse)ctx); |
| } |
| |
| /** |
| * compute redirect URL (SLING-126) |
| * |
| * @param request the sling http request to process |
| * @param ctx the post processor |
| * @return the redirect location or <code>null</code> |
| */ |
| protected String getRedirectUrl(HttpServletRequest request, PostResponse ctx) { |
| // redirect param has priority (but see below, magic star) |
| String result = request.getParameter(SlingPostConstants.RP_REDIRECT_TO); |
| if (result != null) { |
| try { |
| URI redirectUri = new URI(result); |
| if (redirectUri.getAuthority() != null) { |
| // if it has a host information |
| log.warn("redirect target ({}) does include host information ({}). This is not allowed for security reasons!", result, redirectUri.getAuthority()); |
| return null; |
| } |
| } catch (URISyntaxException e) { |
| log.warn("given redirect target ({}) is not a valid uri: {}", result, e); |
| return null; |
| } |
| |
| log.debug("redirect requested as [{}] for path [{}]", result, ctx.getPath()); |
| |
| if (ctx.getPath() != null) { |
| // redirect to created/modified Resource |
| int star = result.indexOf('*'); |
| if (star >= 0) { |
| StringBuilder buf = new StringBuilder(); |
| |
| // anything before the star |
| if (star > 0) { |
| buf.append(result.substring(0, star)); |
| } |
| |
| // append the name of the manipulated node |
| buf.append(ResourceUtil.getName(ctx.getPath())); |
| |
| // anything after the star |
| if (star < result.length() - 1) { |
| buf.append(result.substring(star + 1)); |
| } |
| |
| // use the created path as the redirect result |
| result = buf.toString(); |
| |
| } else if (result.endsWith(SlingPostConstants.DEFAULT_CREATE_SUFFIX)) { |
| // if the redirect has a trailing slash, append modified node |
| // name |
| result = result.concat(ResourceUtil.getName(ctx.getPath())); |
| } |
| |
| if (log.isDebugEnabled()) { |
| log.debug("Will redirect to {}", result); |
| } |
| } |
| } |
| return result; |
| } |
| |
| protected boolean isSetStatus(SlingHttpServletRequest request) { |
| String statusParam = request.getParameter(SlingPostConstants.RP_STATUS); |
| if (statusParam == null) { |
| log.debug( |
| "getStatusMode: Parameter {} not set, assuming standard status code", |
| SlingPostConstants.RP_STATUS); |
| return true; |
| } |
| |
| if (SlingPostConstants.STATUS_VALUE_BROWSER.equals(statusParam)) { |
| log.debug( |
| "getStatusMode: Parameter {} asks for user-friendly status code", |
| SlingPostConstants.RP_STATUS); |
| return false; |
| } |
| |
| if (SlingPostConstants.STATUS_VALUE_STANDARD.equals(statusParam)) { |
| log.debug( |
| "getStatusMode: Parameter {} asks for standard status code", |
| SlingPostConstants.RP_STATUS); |
| return true; |
| } |
| |
| log.debug("getStatusMode: Parameter {} set to unknown value {}, assuming standard status code", |
| SlingPostConstants.RP_STATUS, statusParam); |
| return true; |
| } |
| |
| // ------ These methods were copied from AbstractSlingPostOperation ------ |
| |
| /** |
| * Returns the path of the resource of the request as the item path. |
| * <p> |
| * This method may be overwritten by extension if the operation has |
| * different requirements on path processing. |
| * </p> |
| * @param request the sling http request to process |
| * @return the resolved path of the found item |
| */ |
| protected String getItemPath(SlingHttpServletRequest request) { |
| return request.getResource().getPath(); |
| } |
| |
| /** |
| * Returns an external form of the given path prepending the context path |
| * and appending a display extension. |
| * |
| * @param request the sling http request to process |
| * @param path the path to externalize |
| * @return the url |
| */ |
| protected final String externalizePath(SlingHttpServletRequest request, |
| String path) { |
| StringBuilder ret = new StringBuilder(); |
| ret.append(SlingRequestPaths.getContextPath(request)); |
| ret.append(request.getResourceResolver().map(path)); |
| |
| // append optional extension |
| String ext = request.getParameter(SlingPostConstants.RP_DISPLAY_EXTENSION); |
| if (ext != null && ext.length() > 0) { |
| if (ext.charAt(0) != '.') { |
| ret.append('.'); |
| } |
| ret.append(ext); |
| } |
| |
| return ret.toString(); |
| } |
| |
| /** |
| * Returns an <code>AccessControlList</code> to edit for the node at the |
| * given <code>resourcePath</code>. |
| * |
| * @param accessControlManager The manager providing access control lists |
| * @param resourcePath The node path for which to return an access control |
| * list |
| * @param mayCreate <code>true</code> if an access control list should be |
| * created if the node does not have one yet. |
| * @return The <code>AccessControlList</code> to modify to control access to |
| * the node. |
| * @throws RepositoryException If the access control manager does not |
| * provide a <code>AccessControlPolicy</code> which is an |
| * <code>AccessControlList</code>. |
| */ |
| protected AccessControlList getAccessControlList( |
| final AccessControlManager accessControlManager, |
| final String resourcePath, final boolean mayCreate) |
| throws RepositoryException { |
| |
| // check for an existing access control list to edit |
| AccessControlPolicy[] policies = accessControlManager.getPolicies(resourcePath); |
| for (AccessControlPolicy policy : policies) { |
| if (policy instanceof AccessControlList) { |
| return (AccessControlList) policy; |
| } |
| } |
| |
| // no existing access control list, try to create if allowed |
| if (mayCreate) { |
| AccessControlPolicyIterator applicablePolicies = accessControlManager.getApplicablePolicies(resourcePath); |
| while (applicablePolicies.hasNext()) { |
| AccessControlPolicy policy = applicablePolicies.nextAccessControlPolicy(); |
| if (policy instanceof AccessControlList) { |
| return (AccessControlList) policy; |
| } |
| } |
| } |
| |
| // neither an existing nor a create AccessControlList is available, fail |
| throw new RepositoryException( |
| "Unable to find or create an access control policy to update for " |
| + resourcePath); |
| |
| } |
| |
| /** |
| * Returns an <code>AccessControlList</code> to edit for the node at the |
| * given <code>resourcePath</code>. |
| * |
| * @param accessControlManager The manager providing access control lists |
| * @param resourcePath The node path for which to return an access control |
| * list |
| * @param mayCreate <code>true</code> if an access control list should be |
| * created if the node does not have one yet. |
| * @return The <code>AccessControlList</code> to modify to control access to |
| * the node or null if one could not be located or created |
| * @throws RepositoryException if any errors reading the information |
| */ |
| protected AccessControlList getAccessControlListOrNull( |
| final AccessControlManager accessControlManager, |
| final String resourcePath, final boolean mayCreate) |
| throws RepositoryException { |
| AccessControlList acl = null; |
| // check for an existing access control list to edit |
| AccessControlPolicy[] policies = accessControlManager.getPolicies(resourcePath); |
| for (AccessControlPolicy policy : policies) { |
| if (policy instanceof AccessControlList) { |
| acl = (AccessControlList) policy; |
| } |
| } |
| |
| if (acl == null && mayCreate) { |
| // no existing access control list, try to create if allowed |
| AccessControlPolicyIterator applicablePolicies = accessControlManager.getApplicablePolicies(resourcePath); |
| while (applicablePolicies.hasNext()) { |
| AccessControlPolicy policy = applicablePolicies.nextAccessControlPolicy(); |
| if (policy instanceof AccessControlList) { |
| acl = (AccessControlList) policy; |
| } |
| } |
| } |
| return acl; |
| } |
| |
| /** |
| * Bind a new post response creator |
| * |
| * @param creator the response creator service reference |
| * @param properties the component properties for the service reference |
| */ |
| // NOTE: the @Reference annotation is not inherited, so subclasses will need to override the #bindPostResponseCreator |
| // and #unbindPostResponseCreator methods to provide the @Reference annotation. |
| // |
| // @Reference(service = PostResponseCreator.class, |
| // cardinality = ReferenceCardinality.MULTIPLE, |
| // policy = ReferencePolicy.DYNAMIC) |
| protected void bindPostResponseCreator(final PostResponseCreator creator, final Map<String, Object> properties) { |
| final PostResponseCreatorHolder nngh = new PostResponseCreatorHolder(creator, getRanking(properties)); |
| |
| synchronized ( this.postResponseCreators ) { |
| int index = 0; |
| while ( index < this.postResponseCreators.size() && |
| nngh.getRanking() < this.postResponseCreators.get(index).getRanking() ) { |
| index++; |
| } |
| if ( index == this.postResponseCreators.size() ) { |
| this.postResponseCreators.add(nngh); |
| } else { |
| this.postResponseCreators.add(index, nngh); |
| } |
| this.updatePostResponseCreatorCache(); |
| } |
| } |
| |
| /** |
| * Unbind a post response creator |
| * |
| * @param creator the response creator service reference |
| * @param properties the component properties for the service reference |
| */ |
| protected void unbindPostResponseCreator(final PostResponseCreator creator, final Map<String, Object> properties) { |
| synchronized ( this.postResponseCreators ) { |
| final Iterator<PostResponseCreatorHolder> i = this.postResponseCreators.iterator(); |
| while ( i.hasNext() ) { |
| final PostResponseCreatorHolder current = i.next(); |
| if ( current.getCreator() == creator ) { |
| i.remove(); |
| } |
| } |
| this.updatePostResponseCreatorCache(); |
| } |
| } |
| |
| /** |
| * Update the post response creator cache |
| * This method is called by sync'ed methods, no need to add additional syncing. |
| */ |
| private void updatePostResponseCreatorCache() { |
| final PostResponseCreator[] localCache = new PostResponseCreator[this.postResponseCreators.size()]; |
| int index = 0; |
| for(final PostResponseCreatorHolder current : this.postResponseCreators) { |
| localCache[index] = current.getCreator(); |
| index++; |
| } |
| this.cachedPostResponseCreators = localCache; |
| } |
| |
| private int getRanking(final Map<String, Object> properties) { |
| final Object val = properties.get(Constants.SERVICE_RANKING); |
| return val instanceof Integer ? (Integer)val : 0; |
| } |
| |
| private static final class PostResponseCreatorHolder { |
| private final PostResponseCreator creator; |
| private final int ranking; |
| |
| public PostResponseCreatorHolder(PostResponseCreator creator, int ranking) { |
| this.creator = creator; |
| this.ranking = ranking; |
| } |
| |
| public PostResponseCreator getCreator() { |
| return creator; |
| } |
| |
| public int getRanking() { |
| return ranking; |
| } |
| |
| } |
| } |