/**
* OLAT - Online Learning and Training<br>
* http://www.olat.org
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); <br>
* you may not use this file except in compliance with the License.<br>
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing,<br>
* software distributed under the License is distributed on an "AS IS" BASIS, <br>
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
* See the License for the specific language governing permissions and <br>
* limitations under the License.
* <p>
* Copyright (c) frentix GmbH<br>
* http://www.frentix.com<br>
* <p>
*/
package org.olat.modules.webFeed.dispatching;
import java.io.IOException;
import java.util.Date;
import java.util.Hashtable;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.olat.basesecurity.Authentication;
import org.olat.basesecurity.Manager;
import org.olat.basesecurity.ManagerFactory;
import org.olat.core.commons.persistence.DBFactory;
import org.olat.core.commons.persistence.DBImpl;
import org.olat.core.dispatcher.Dispatcher;
import org.olat.core.dispatcher.DispatcherAction;
import org.olat.core.gui.media.MediaResource;
import org.olat.core.gui.media.ServletUtil;
import org.olat.core.id.Identity;
import org.olat.core.id.IdentityEnvironment;
import org.olat.core.id.OLATResourceable;
import org.olat.core.id.Roles;
import org.olat.core.logging.LogDelegator;
import org.olat.core.logging.OLog;
import org.olat.core.logging.Tracing;
import org.olat.core.util.WebappHelper;
import org.olat.course.CourseFactory;
import org.olat.course.CourseModule;
import org.olat.course.ICourse;
import org.olat.course.nodes.CourseNode;
import org.olat.course.run.userview.NodeEvaluation;
import org.olat.course.run.userview.TreeEvaluation;
import org.olat.course.run.userview.UserCourseEnvironment;
import org.olat.course.run.userview.UserCourseEnvironmentImpl;
import org.olat.fileresource.types.BlogFileResource;
import org.olat.fileresource.types.PodcastFileResource;
import org.olat.modules.webFeed.managers.FeedManager;
import org.olat.modules.webFeed.models.Feed;
import org.olat.repository.RepositoryEntry;
import org.olat.repository.RepositoryManager;
import org.olat.resource.OLATResourceManager;
/**
* Dispatch any media files belonging to a podcast which an identity is
* authorized to access. The media file can belong to a learning resource or a
* course node.
* <p>
* Examples: see Path constructor
* <p>
* Initial Date: Mar 10, 2009 <br>
*
* @author gwassmann
*/
public class FeedMediaDispatcher extends LogDelegator implements Dispatcher {
private static final String PODCAST_URI_PREFIX = FeedManager.KIND_PODCAST;
private static final String BLOG_URI_PREFIX = FeedManager.KIND_BLOG;
public static final String TOKEN_PROVIDER = "feed";
public static Hashtable<String, String> resourceTypes, uriPrefixes;
static {
// Mapping: uri prefix --> resource type
resourceTypes = new Hashtable<String, String>();
resourceTypes.put(PODCAST_URI_PREFIX, PodcastFileResource.TYPE_NAME);
resourceTypes.put(BLOG_URI_PREFIX, BlogFileResource.TYPE_NAME);
// Mapping: resource type --> uri prefix
uriPrefixes = new Hashtable<String, String>();
uriPrefixes.put(PodcastFileResource.TYPE_NAME, PODCAST_URI_PREFIX);
uriPrefixes.put(BlogFileResource.TYPE_NAME, BLOG_URI_PREFIX);
}
private static final OLog log = Tracing.createLoggerFor(FeedMediaDispatcher.class);
/**
* @see org.olat.core.dispatcher.Dispatcher#execute(javax.servlet.http.HttpServletRequest,
* javax.servlet.http.HttpServletResponse, java.lang.String)
*/
public void execute(HttpServletRequest request, HttpServletResponse response, String uriPrefix) {
String requestedPath = getPath(request, uriPrefix);
Path path = null;
try {
// Assume the URL was correct.
// At first, look up path in cache. Even before extracting any parameters
path = new Path(requestedPath);
// See brasatoconfigpart.xml. The uriPrefix is like '/olat/podcast/' or
// '/company/blog/'. Get the podcast or blog string.
// remove the last slash if it exists
int lastIndex = uriPrefix.length() - 1;
if (uriPrefix.lastIndexOf("/") == lastIndex) {
uriPrefix = uriPrefix.substring(0, lastIndex);
}
int lastSlashPos = uriPrefix.lastIndexOf("/");
String feedUriPrefix = uriPrefix.substring(lastSlashPos + 1);
// String feedUriPrefix = uriPrefix.replaceAll("olat|/", "");
OLATResourceable feed = null;
if (path.isCachedAndAccessible()) {
// Serve request
path.compile();
feed = OLATResourceManager.getInstance().findResourceable(path.getFeedId(), resourceTypes.get(feedUriPrefix));
deliverFile(request, response, feed, path);
} else {
path.compile();
feed = OLATResourceManager.getInstance().findResourceable(path.getFeedId(), resourceTypes.get(feedUriPrefix));
if (hasAccess(feed, path)) {
// Only cache when accessible
path.cache(feed, true);
deliverFile(request, response, feed, path);
} else {
// Deny access
log.info("Access was denied. Path::" + path);
DispatcherAction.sendForbidden(request.getRequestURI(), response);
}
}
} catch (InvalidPathException e) {
logWarn("The requested path is invalid. path::" + path, e);
DispatcherAction.sendBadRequest(request.getRequestURI(), response);
} catch (Throwable t) {
logWarn("Nothing was delivered. Path::" + path, t);
DispatcherAction.sendNotFound(request.getRequestURI(), response);
}
}
/**
* Dispatch and deliver the requested file given in the path.
*
* @param request
* @param response
* @param feed
* @param path
*/
private void deliverFile(HttpServletRequest request, HttpServletResponse response, OLATResourceable feed, Path path) {
// OLAT-5243 related: deliverFile can last arbitrary long, which can cause the open db connection to timeout and cause errors,
// hence we need to do an intermediateCommit here
DBFactory.getInstance().intermediateCommit();
// Create the resource to be delivered
MediaResource resource = null;
FeedManager manager = FeedManager.getInstance();
if (path.isFeedType()) {
// Only create feed if modified. Send not modified response else.
Identity identity = getIdentity(path.getIdentityKey());
long lastResponseMs = request.getDateHeader("If-Modified-Since");
Date lastResponse = null;
if (lastResponseMs != -1) {
lastResponse = new Date(lastResponseMs);
}
Feed feedLight = manager.getFeedLigth(feed);
Date lastModified = null;
if (feedLight != null) {
lastModified = feedLight.getLastModified();
}
if (lastResponse != null && lastResponse.before(lastModified)) {
// Send not modified response
response.setDateHeader("last-modified", lastModified.getTime());
try {
response.sendError(HttpServletResponse.SC_NOT_MODIFIED);
return;
} catch (IOException e) {
// Send not modified failed
log.error("Send not modified failed", e);
return;
}
} else {
resource = manager.createFeedFile(feed, identity, path.getCourseId(), path.getNodeId());
}
} else if (path.isItemType()) {
resource = manager.createItemMediaFile(feed, path.getItemId(), path.getItemFileName());
} else if (path.isIconType()) {
resource = manager.createFeedMediaFile(feed, path.getIconFileName());
}
// Eventually deliver the requested resource
ServletUtil.serveResource(request, response, resource);
}
/**
* Get the identity from the key.
*
* @param idKey
* @return the Identity
*/
private Identity getIdentity(Long idKey) {
Identity identity = null;
if (idKey != null) {
identity = ManagerFactory.getManager().loadIdentityByKey(idKey);
}
return identity;
}
/**
* Remove some prefixes from the request path.
*
* @param request
* @param prefix
* @return The path of the request
*/
private String getPath(HttpServletRequest request, String prefix) {
String path = request.getPathInfo();
// remove servlet context path (/olat) from uri prefix (/olat/podcast)
prefix = prefix.substring(WebappHelper.getServletContextPath().length());
// remove prefix (/podcast) from path
path = path.substring(prefix.length());
return path;
}
/**
* The global access verification method.
*
* @param feed
* @param path
* @return true if the path may be dispatched.
*/
private boolean hasAccess(OLATResourceable feed, Path path) {
boolean hasAccess = false;
Identity identity = getIdentity(path.getIdentityKey());
if (path.isCourseType()) {
// A course node is being requested
OLATResourceable oresCourse = OLATResourceManager.getInstance()
.findResourceable(path.getCourseId(), CourseModule.getCourseTypeName());
ICourse course = CourseFactory.loadCourse(oresCourse);
CourseNode node = course.getEditorTreeModel().getCourseNode(path.getNodeId());
// Check access
hasAccess = hasAccess(identity, path.getToken(), course, node);
} else {
// A learning resource is being requested
hasAccess = hasAccess(identity, path.getToken(), feed);
}
return hasAccess;
}
/**
* Verifies the access of an identity to a course node.
*
* @param identity
* @param token
* @param course
* @param node
* @return True if the identity has access to the node in the given course.
* False otherwise.
*/
private boolean hasAccess(Identity identity, String token, ICourse course, CourseNode node) {
boolean hasAccess = false;
if (allowsGuestAccess(course)) {
hasAccess = true;
} else {
IdentityEnvironment ienv = new IdentityEnvironment();
ienv.setIdentity(identity);
Roles roles = ManagerFactory.getManager().getRoles(identity);
ienv.setRoles(roles);
UserCourseEnvironment userCourseEnv = new UserCourseEnvironmentImpl(ienv, course.getCourseEnvironment());
// Build an evaluation tree
TreeEvaluation treeEval = new TreeEvaluation();
NodeEvaluation nodeEval = node.eval(userCourseEnv.getConditionInterpreter(), treeEval);
if (nodeEval.isVisible() && validAuthentication(identity, token)) {
hasAccess = true;
}
}
return hasAccess;
}
/**
* Verifiy if the identity has access to the feed.
*
* @param identity
* @param token
* @param feed
* @return true if the identity has access.
*/
private boolean hasAccess(Identity identity, String token, OLATResourceable feed) {
boolean hasAccess = false;
if (allowsGuestAccess(feed)) {
hasAccess = true;
} else if (identity != null) {
RepositoryManager resMgr = RepositoryManager.getInstance();
RepositoryEntry repoEntry = resMgr.lookupRepositoryEntry(feed, false);
Roles roles = ManagerFactory.getManager().getRoles(identity);
boolean isAllowedToLaunch = resMgr.isAllowedToLaunch(identity, roles, repoEntry);
if (isAllowedToLaunch && validAuthentication(identity, token)) {
hasAccess = true;
}
}
return hasAccess;
}
/**
* Authenticates the identity by token
*
* @param identity
* @param token
* @return True if authentication is valid
*/
private boolean validAuthentication(Identity identity, String token) {
boolean valid = false;
Manager secMgr = ManagerFactory.getManager();
Authentication authentication = secMgr.findAuthenticationByAuthusername(identity.getKey().toString(), TOKEN_PROVIDER);
if (authentication != null && authentication.getCredential().equals(token)) {
valid = true;
}
return valid;
}
/**
* @param feed
* @return true if the feed allows guest access.
*/
private boolean allowsGuestAccess(OLATResourceable res) {
boolean guestsAllowed = false;
RepositoryManager resMgr = RepositoryManager.getInstance();
RepositoryEntry repoEntry = resMgr.lookupRepositoryEntry(res, false);
if (repoEntry.getAccess() == RepositoryEntry.ACC_USERS_GUESTS) {
guestsAllowed = true;
}
return guestsAllowed;
}
/**
* Redirect to Path.getFeedBaseUri()
*
* @param feed
* @param identityKey
* @return The feed base uri for the given user (identity)
*/
public static String getFeedBaseUri(Feed feed, Identity identity, Long courseId, String nodeId) {
return Path.getFeedBaseUri(feed, identity, courseId, nodeId);
}
}