001/* 002 * Licensed to DuraSpace under one or more contributor license agreements. 003 * See the NOTICE file distributed with this work for additional information 004 * regarding copyright ownership. 005 * 006 * DuraSpace licenses this file to you under the Apache License, 007 * Version 2.0 (the "License"); you may not use this file except in 008 * compliance with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.fcrepo.http.commons.session; 019 020import static java.util.Objects.requireNonNull; 021import static org.slf4j.LoggerFactory.getLogger; 022 023import java.security.Principal; 024 025import javax.annotation.PostConstruct; 026import javax.inject.Inject; 027import javax.jcr.Repository; 028import javax.jcr.RepositoryException; 029import javax.jcr.Session; 030import javax.servlet.http.HttpServletRequest; 031import javax.ws.rs.BadRequestException; 032 033import org.fcrepo.kernel.api.exception.RepositoryRuntimeException; 034import org.fcrepo.kernel.api.exception.SessionMissingException; 035import org.fcrepo.kernel.api.Transaction; 036import org.fcrepo.kernel.api.services.CredentialsService; 037import org.fcrepo.kernel.api.services.TransactionService; 038 039import org.slf4j.Logger; 040 041/** 042 * Factory for generating sessions for HTTP requests, taking 043 * into account transactions and authentication. 044 * 045 * @author awoods 046 * @author gregjan 047 * @author kaisternad 048 */ 049public class SessionFactory { 050 051 protected static enum Prefix{ 052 TX("tx:"); 053 054 private final String prefix; 055 056 Prefix(final String prefix) { 057 this.prefix = prefix; 058 } 059 060 public String getPrefix() { 061 return prefix; 062 } 063 } 064 065 private static final Logger LOGGER = getLogger(SessionFactory.class); 066 067 @Inject 068 private Repository repo; 069 070 @Inject 071 private TransactionService transactionService; 072 073 @Inject 074 private CredentialsService credentialsService; 075 076 /** 077 * Default constructor 078 */ 079 public SessionFactory() { 080 } 081 082 /** 083 * Initialize a session factory for the given Repository 084 * 085 * @param repo the repository 086 * @param transactionService the transaction service 087 */ 088 public SessionFactory(final Repository repo, 089 final TransactionService transactionService) { 090 this.repo = repo; 091 this.transactionService = transactionService; 092 } 093 094 /** 095 * Validate the spring wiring 096 */ 097 @PostConstruct 098 public void init() { 099 requireNonNull(repo, "SessionFactory requires a Repository instance!"); 100 } 101 102 /** 103 * Get a new JCR Session 104 * 105 * @return an internal session 106 */ 107 public Session getInternalSession() { 108 try { 109 return repo.login(); 110 } catch (final RepositoryException e) { 111 throw new RepositoryRuntimeException(e); 112 } 113 } 114 115 /** 116 * Get a JCR session for the given HTTP servlet request with a 117 * SecurityContext attached 118 * 119 * @param servletRequest the servlet request 120 * @return the Session 121 * @throws RuntimeException if the transaction could not be found 122 */ 123 public Session getSession(final HttpServletRequest servletRequest) { 124 final Session session; 125 final String txId = getEmbeddedId(servletRequest, Prefix.TX); 126 127 try { 128 if (txId == null) { 129 session = createSession(servletRequest); 130 } else { 131 session = getSessionFromTransaction(servletRequest, txId); 132 } 133 } catch (final SessionMissingException e) { 134 LOGGER.warn("Transaction missing: {}", e.getMessage()); 135 return null; 136 } catch (final RepositoryException e) { 137 throw new BadRequestException(e); 138 } 139 140 return session; 141 } 142 143 /** 144 * Create a JCR session for the given HTTP servlet request with a 145 * SecurityContext attached. 146 * 147 * @param servletRequest the servlet request 148 * @return a newly created JCR session 149 * @throws RepositoryException if the session could not be created 150 */ 151 protected Session createSession(final HttpServletRequest servletRequest) throws RepositoryException { 152 153 LOGGER.debug("Returning an authenticated session in the default workspace"); 154 return repo.login(credentialsService.getCredentials(servletRequest)); 155 } 156 157 /** 158 * Retrieve a JCR session from an active transaction 159 * 160 * @param servletRequest the servlet request 161 * @param txId the transaction id 162 * @return a JCR session that is associated with the transaction 163 */ 164 protected Session getSessionFromTransaction(final HttpServletRequest servletRequest, final String txId) { 165 166 final Principal userPrincipal = servletRequest.getUserPrincipal(); 167 168 String userName = null; 169 if (userPrincipal != null) { 170 userName = userPrincipal.getName(); 171 } 172 173 final Transaction transaction = 174 transactionService.getTransaction(txId, userName); 175 LOGGER.debug( 176 "Returning a session in the transaction {} for user {}", 177 transaction, userName); 178 return transaction.getSession(); 179 180 } 181 182 /** 183 * Extract the id embedded at the beginning of a request path 184 * 185 * @param servletRequest the servlet request 186 * @param prefix the prefix for the id 187 * @return the found id or null 188 */ 189 protected String getEmbeddedId( 190 final HttpServletRequest servletRequest, final Prefix prefix) { 191 String requestPath = servletRequest.getPathInfo(); 192 193 // http://stackoverflow.com/questions/18963562/grizzlys-request-getpathinfo-returns-always-null 194 if (requestPath == null && servletRequest.getContextPath().isEmpty()) { 195 requestPath = servletRequest.getRequestURI(); 196 } 197 198 String id = null; 199 if (requestPath != null) { 200 final String pathPrefix = prefix.getPrefix(); 201 final String[] part = requestPath.split("/"); 202 if (part.length > 1 && part[1].startsWith(pathPrefix)) { 203 id = part[1].substring(pathPrefix.length()); 204 } 205 } 206 return id; 207 } 208 209}