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}