diff --git a/src/main/java/org/olat/basesecurity/AuthHelper.java b/src/main/java/org/olat/basesecurity/AuthHelper.java index 7000f8c763e31b8b44f3a54033b4198801efe199..8d272b355d39472565e0edc574b55db0f5f8dcf4 100644 --- a/src/main/java/org/olat/basesecurity/AuthHelper.java +++ b/src/main/java/org/olat/basesecurity/AuthHelper.java @@ -91,8 +91,8 @@ public class AuthHelper { * <code>LOGOUT_PAGE</code> */ public static final int LOGIN_OK = 0; - private static final int LOGIN_FAILED = 1; - private static final int LOGIN_DENIED = 2; + public static final int LOGIN_FAILED = 1; + public static final int LOGIN_DENIED = 2; public static final int LOGIN_NOTAVAILABLE = 3; private static final int MAX_SESSION_NO_LIMIT = 0; @@ -228,8 +228,14 @@ public class AuthHelper { if ( locale == null || ! supportedLanguages.contains(locale.toString()) ) { locale = I18nModule.getDefaultLocale(); } - Identity guestIdent = BaseSecurityManager.getInstance().getAndUpdateAnonymousUserForLanguage(locale); - return doLogin(guestIdent, BaseSecurityModule.getDefaultAuthProviderIdentifier(), ureq); + BaseSecurity securityManager = CoreSpringFactory.getImpl(BaseSecurity.class); + Identity guestIdent = securityManager.getAndUpdateAnonymousUserForLanguage(locale); + Roles guestRoles = securityManager.getRoles(guestIdent); + if(guestRoles.isGuestOnly()) { + return doLogin(guestIdent, BaseSecurityModule.getDefaultAuthProviderIdentifier(), ureq); + } + log.error("Guest account has user permissions: " + guestIdent); + return LOGIN_DENIED; } public static int doInvitationLogin(String invitationToken, UserRequest ureq, Locale locale) { diff --git a/src/main/java/org/olat/core/dispatcher/_spring/dispatcherContext.xml b/src/main/java/org/olat/core/dispatcher/_spring/dispatcherContext.xml index bc26b80c70b770c10ba06323ee9de1c32ca13684..3b8a6745ec2598c0d1ad75e496e8fbe4242c4c6f 100644 --- a/src/main/java/org/olat/core/dispatcher/_spring/dispatcherContext.xml +++ b/src/main/java/org/olat/core/dispatcher/_spring/dispatcherContext.xml @@ -247,9 +247,23 @@ class="org.olat.core.gui.components.form.flexible.impl.elements.richText.plugins.olatmovieviewer.TinyDispatcher" /> <!-- podcast media dispatcher --> - <bean id="podcastMediaBean" class="org.olat.modules.webFeed.dispatching.FeedMediaDispatcher" /> + <bean id="podcastMediaBean" class="org.olat.modules.webFeed.dispatching.FeedMediaDispatcher"> + <property name="feedManager" ref="feedManager"/> + <property name="coordinatorManager" ref="coordinatorManager"/> + <property name="securityManager" ref="baseSecurityManager"/> + <property name="repositoryManager" ref="repositoryManager"/> + <property name="dbInstance" ref="database"/> + <property name="resourceManager" ref="resourceManager"/> + </bean> <!-- blog media dispatcher --> - <bean id="blogMediaBean" class="org.olat.modules.webFeed.dispatching.FeedMediaDispatcher" /> + <bean id="blogMediaBean" class="org.olat.modules.webFeed.dispatching.FeedMediaDispatcher"> + <property name="feedManager" ref="feedManager"/> + <property name="coordinatorManager" ref="coordinatorManager"/> + <property name="securityManager" ref="baseSecurityManager"/> + <property name="repositoryManager" ref="repositoryManager"/> + <property name="dbInstance" ref="database"/> + <property name="resourceManager" ref="resourceManager"/> + </bean> </beans> diff --git a/src/main/java/org/olat/core/util/mail/manager/MailManagerImpl.java b/src/main/java/org/olat/core/util/mail/manager/MailManagerImpl.java index 74c12867c35369f503bec0f3938423151a5cce35..a84b0b7285fd4936842efefbe05113af5a29ec6b 100644 --- a/src/main/java/org/olat/core/util/mail/manager/MailManagerImpl.java +++ b/src/main/java/org/olat/core/util/mail/manager/MailManagerImpl.java @@ -1679,7 +1679,7 @@ public class MailManagerImpl implements MailManager, InitializingBean { @Override public void sendMessage(MimeMessage msg, MailerResult result) { if (msg == null) return; - + String smtpFrom = WebappHelper.getMailConfig("smtpFrom"); if(StringHelper.containsNonWhitespace(smtpFrom)) { try { @@ -1689,6 +1689,17 @@ public class MailManagerImpl implements MailManager, InitializingBean { } catch (MessagingException e) { log.error("", e); } + } else { + try { + if (msg.getReplyTo().length > 0) { + Address a = msg.getReplyTo()[0]; + SMTPMessage smtpMsg = new SMTPMessage(msg); + smtpMsg.setEnvelopeFrom(((InternetAddress)a).getAddress()); + msg = smtpMsg; + } + } catch (MessagingException e) { + log.error("", e); + } } try{ diff --git a/src/main/java/org/olat/course/CourseFactory.java b/src/main/java/org/olat/course/CourseFactory.java index 17a36fe87982367866b972ce124e4d07b5beb663..9aabc5a0e491ad99bea98fe2508b9ca166d40f30 100644 --- a/src/main/java/org/olat/course/CourseFactory.java +++ b/src/main/java/org/olat/course/CourseFactory.java @@ -480,7 +480,7 @@ public class CourseFactory { int count = 0; for (Reference ref: refs) { referenceManager.addReference(targetCourse, ref.getTarget(), ref.getUserdata()); - if(count % 20 == 0) { + if(count++ % 20 == 0) { DBFactory.getInstance().intermediateCommit(); } } diff --git a/src/main/java/org/olat/course/editor/PublishProcess.java b/src/main/java/org/olat/course/editor/PublishProcess.java index 684a1dbe4fae92332c4d92924ab1984a4a799954..91b495d72042a70f63e2aed1e4f02004a129cb1e 100644 --- a/src/main/java/org/olat/course/editor/PublishProcess.java +++ b/src/main/java/org/olat/course/editor/PublishProcess.java @@ -100,7 +100,7 @@ public class PublishProcess { private static final OLog log = Tracing.createLoggerFor(PublishProcess.class); private static final String PACKAGE = Util.getPackageName(PublishProcess.class); - private static Translator translator; + private final Translator translator; /* * publishing means diff --git a/src/main/java/org/olat/login/LoginAuthprovidersController.java b/src/main/java/org/olat/login/LoginAuthprovidersController.java index 12bb709eba1bca4146aee439a6d8c28712100427..0b29ffe4ad2165975cd6970591fba63c9b4f3ba4 100644 --- a/src/main/java/org/olat/login/LoginAuthprovidersController.java +++ b/src/main/java/org/olat/login/LoginAuthprovidersController.java @@ -67,8 +67,7 @@ import org.olat.login.auth.AuthenticationProvider; import org.springframework.beans.factory.annotation.Autowired; /** - * Description:<br> - * TODO: patrickb Class Description for LoginAuthprovidersController + * A container for all the authentications methods available to the user. * * <P> * Initial Date: 02.09.2007 <br> @@ -81,7 +80,7 @@ public class LoginAuthprovidersController extends MainLayoutBasicController impl private VelocityContainer content; private Controller authController; - private final List<Controller> authControllers = new ArrayList<Controller>(); + private final List<Controller> authControllers = new ArrayList<>(); private Link anoLink; private StackedPanel dmzPanel; @@ -227,27 +226,23 @@ public class LoginAuthprovidersController extends MainLayoutBasicController impl return contentBorn; } - - /** - * @see org.olat.core.gui.control.DefaultController#doDispose() - */ + @Override protected void doDispose() { //auto-disposed } - /** - * @see org.olat.core.gui.control.DefaultController#event(org.olat.core.gui.UserRequest, org.olat.core.gui.components.Component, org.olat.core.gui.control.Event) - */ @Override protected void event(UserRequest ureq, Component source, Event event) { if (source == anoLink) { if (loginModule.isGuestLoginEnabled()) { int loginStatus = AuthHelper.doAnonymousLogin(ureq, ureq.getLocale()); if (loginStatus == AuthHelper.LOGIN_OK) { - return; - } else if (loginStatus == AuthHelper.LOGIN_NOTAVAILABLE){ + // + } else if (loginStatus == AuthHelper.LOGIN_NOTAVAILABLE) { DispatcherModule.redirectToServiceNotAvailable( ureq.getHttpResp() ); + } else if(loginStatus == AuthHelper.LOGIN_DENIED) { + getWindowControl().setError(translate("error.guest.login", WebappHelper.getMailConfig("mailSupport"))); } else { getWindowControl().setError(translate("login.error", WebappHelper.getMailConfig("mailSupport"))); } @@ -288,8 +283,8 @@ public class LoginAuthprovidersController extends MainLayoutBasicController impl // Add translator and languages info I18nManager i18nMgr = I18nManager.getInstance(); Collection<String> enabledKeysSet = i18nModule.getEnabledLanguageKeys(); - Map<String, String> langNames = new HashMap<String, String>(); - Map<String, String> langTranslators = new HashMap<String, String>(); + Map<String, String> langNames = new HashMap<>(); + Map<String, String> langTranslators = new HashMap<>(); String[] enabledKeys = ArrayHelper.toArray(enabledKeysSet); String[] names = new String[enabledKeys.length]; for (int i = 0; i < enabledKeys.length; i++) { @@ -318,7 +313,7 @@ public class LoginAuthprovidersController extends MainLayoutBasicController impl Identity identity = authEvent.getIdentity(); int loginStatus = AuthHelper.doLogin(identity, BaseSecurityModule.getDefaultAuthProviderIdentifier(), ureq); if (loginStatus == AuthHelper.LOGIN_OK) { - return; + // it's ok } else if (loginStatus == AuthHelper.LOGIN_NOTAVAILABLE){ DispatcherModule.redirectToDefaultDispatcher(ureq.getHttpResp()); } else { diff --git a/src/main/java/org/olat/login/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/login/_i18n/LocalStrings_de.properties index 8039081a567a87ee31a632229dd20eef4ee51640..443f742ffe8428a5cc3f60faf7d8c282f773de79 100644 --- a/src/main/java/org/olat/login/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/login/_i18n/LocalStrings_de.properties @@ -114,6 +114,7 @@ browsercheck.yourbrowser.usragent=User agent\: default.shib.intro=Sie werden zur Authentifizierung weitergeleitet. enabled=ein +error.guest.login=Gastanmeldung ist derzeit nicht verf\u00FCgbar. Wenden Sie Sich an {0}. guest.login=Gastlogin auf Login Seite guest.login.links=Links f\u00FCr Gast guest.search=Volltextsuche f\u00FCr Gast diff --git a/src/main/java/org/olat/login/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/login/_i18n/LocalStrings_en.properties index 8fe0703372af67aeb79eee71073055fd4580d3ec..7ebc3a2d35877a531d19409f8591a59cf73e600e 100644 --- a/src/main/java/org/olat/login/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/login/_i18n/LocalStrings_en.properties @@ -114,6 +114,7 @@ browsercheck.yourbrowser.usragent=User agent\: default.shib.intro=You will be redirected to authentication. enabled=enabled +error.guest.login=Guest is currently not available. Please contact {0}. guest.login=Guest login on login page guest.login.links=Links for guests guest.search=Full-text search for guest diff --git a/src/main/java/org/olat/login/_i18n/LocalStrings_fr.properties b/src/main/java/org/olat/login/_i18n/LocalStrings_fr.properties index e2045cdb6d7a05d134c758c4b447f8b2229ca012..79e681facdee5b66a6c35f24ce48500f8a1478ed 100644 --- a/src/main/java/org/olat/login/_i18n/LocalStrings_fr.properties +++ b/src/main/java/org/olat/login/_i18n/LocalStrings_fr.properties @@ -1,4 +1,4 @@ -#Tue Sep 11 19:11:27 CEST 2018 +#Fri Jun 01 10:19:57 CEST 2018 about.copyright=Copyright et participations about.custom= about.custom.title= @@ -79,6 +79,7 @@ default.shib.intro=Vous \u00EAtes redirig\u00E9 vers la connexion disable.history=d\u00E9sactiv\u00E9 enabled=on error.wrong.int=Format de nombre incorrect. +error.guest.login=La connexion en temps qu'invit\u00E9 n'est pas disponible pour l'instant. Veuillez contacter {0}. guest.login=Connexion invit\u00E9 sur la page de login guest.login.links=Liens pour invit\u00E9s guest.search=Recherche plein texte pour les invit\u00E9s diff --git a/src/main/java/org/olat/modules/webFeed/dispatching/FeedMediaDispatcher.java b/src/main/java/org/olat/modules/webFeed/dispatching/FeedMediaDispatcher.java index 19b712adde8b5b67501733e375ecd695e4cf0ec9..9dc24338fe28863b37e128af9baded94815135e2 100644 --- a/src/main/java/org/olat/modules/webFeed/dispatching/FeedMediaDispatcher.java +++ b/src/main/java/org/olat/modules/webFeed/dispatching/FeedMediaDispatcher.java @@ -20,34 +20,37 @@ package org.olat.modules.webFeed.dispatching; import java.io.IOException; -import java.util.Hashtable; +import java.util.List; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.olat.basesecurity.Authentication; import org.olat.basesecurity.BaseSecurity; -import org.olat.basesecurity.BaseSecurityManager; import org.olat.core.CoreSpringFactory; -import org.olat.core.commons.persistence.DBFactory; +import org.olat.core.commons.persistence.DB; import org.olat.core.commons.services.image.Size; import org.olat.core.dispatcher.Dispatcher; import org.olat.core.dispatcher.DispatcherModule; +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.UserRequestImpl; +import org.olat.core.gui.control.Event; 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.StringHelper; import org.olat.core.util.WebappHelper; +import org.olat.core.util.cache.CacheWrapper; +import org.olat.core.util.coordinate.CoordinatorManager; +import org.olat.core.util.event.GenericEventListener; import org.olat.core.util.vfs.VFSLeaf; import org.olat.core.util.vfs.VFSMediaResource; 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; @@ -62,6 +65,10 @@ import org.olat.modules.webFeed.manager.FeedManager; import org.olat.portfolio.manager.EPFrontendManager; import org.olat.repository.RepositoryEntry; import org.olat.repository.RepositoryManager; +import org.olat.repository.RepositoryService; +import org.olat.repository.controllers.EntryChangedEvent; +import org.olat.repository.controllers.EntryChangedEvent.Change; +import org.olat.repository.model.RepositoryEntrySecurity; import org.olat.resource.OLATResourceManager; /** @@ -75,80 +82,182 @@ import org.olat.resource.OLATResourceManager; * * @author gwassmann */ -public class FeedMediaDispatcher extends LogDelegator implements Dispatcher { +public class FeedMediaDispatcher implements Dispatcher, GenericEventListener { + + private static final OLog log = Tracing.createLoggerFor(FeedMediaDispatcher.class); - 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"; + + private DB dbInstance; + private FeedManager feedManager; + private BaseSecurity securityManager; + private RepositoryManager repositoryManager; + private OLATResourceManager resourceManager; + private CacheWrapper<FeedPathKey,Boolean> validatedUriCache; - 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); + /** + * [used by Spring] + * @param feedManager + */ + public void setFeedManager(FeedManager feedManager) { + this.feedManager = feedManager; + } + + /** + * [used by Spring] + * @param coordinator + */ + public void setCoordinatorManager(CoordinatorManager coordinator) { + validatedUriCache = coordinator.getCoordinator().getCacher().getCache(Path.class.getSimpleName(), "feed"); + coordinator.getCoordinator().getEventBus().registerFor(this, null, RepositoryService.REPOSITORY_EVENT_ORES); + } + + /** + * [used by Spring] + * @param securityManager + */ + public void setSecurityManager(BaseSecurity securityManager) { + this.securityManager = securityManager; + } + + /** + * [used by Spring] + * @param repositoryManager + */ + public void setRepositoryManager(RepositoryManager repositoryManager) { + this.repositoryManager = repositoryManager; + } + + /** + * [used by Spring] + * @param dbInstance + */ + public void setDbInstance(DB dbInstance) { + this.dbInstance = dbInstance; } - - 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) + * [used by Spring] + * @param resourceManager */ + public void setResourceManager(OLATResourceManager resourceManager) { + this.resourceManager = resourceManager; + } + + @Override + public void event(Event event) { + if(event instanceof EntryChangedEvent) { + EntryChangedEvent ece = (EntryChangedEvent)event; + if(ece.getChange() == Change.modifiedAccess || ece.getChange() == Change.modifiedAtPublish) { + Long entryKey = ece.getRepositoryEntryKey(); + discardCache(entryKey); + } + } + } + + private void discardCache(Long entryKey) { + List<FeedPathKey> keys = validatedUriCache.getKeys(); + if(!keys.isEmpty()) { + RepositoryEntry entry = repositoryManager.lookupRepositoryEntry(entryKey); + if(entry != null) { + Long resourceId = entry.getOlatResource().getResourceableId(); + for(FeedPathKey key:keys) { + if(resourceId.equals(key.getResourceId())) { + try { + validatedUriCache.remove(key); + } catch (Exception e) { + log.info("Cannot remove this key: " + key); + } + } + } + } + } + } + @Override public void execute(HttpServletRequest request, HttpServletResponse response) { String uriPrefix = DispatcherModule.getLegacyUriPrefix(request); String requestedPath = getPath(request, uriPrefix); + UserRequest ureq = null; + try{ + //upon creation URL is checked for + ureq = new UserRequestImpl(uriPrefix, request, response); + } catch(NumberFormatException nfe) { + // + } + 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); + path.compile(); + // 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) { + if (uriPrefix.lastIndexOf('/') == lastIndex) { uriPrefix = uriPrefix.substring(0, lastIndex); } - int lastSlashPos = uriPrefix.lastIndexOf("/"); + 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)); + OLATResourceable feed = resourceManager.findResourceable(path.getFeedId(), getResourceType(feedUriPrefix)); + if(isAccessible(ureq, path, feed)) { 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); - DispatcherModule.sendForbidden(request.getRequestURI(), response); - } + log.info("Access was denied. Path::" + path); + DispatcherModule.sendForbidden(request.getRequestURI(), response); } } catch (InvalidPathException e) { - logWarn("The requested path is invalid. path::" + path, e); + log.warn("The requested path is invalid. path::" + path, e); DispatcherModule.sendBadRequest(request.getRequestURI(), response); - } catch (Throwable t) { - logWarn("Nothing was delivered. Path::" + path, t); + } catch (Exception e) { + log.warn("Nothing was delivered. Path::" + path, e); DispatcherModule.sendNotFound(request.getRequestURI(), response); } } + + public static final String getURIPrefix(String type) { + if(PodcastFileResource.TYPE_NAME.equals(type)) { + return FeedManager.KIND_PODCAST; + } else if(BlogFileResource.TYPE_NAME.equals(type)) { + return FeedManager.KIND_BLOG; + } + return null; + } + + private static final String getResourceType(String feedUriPrefix) { + if(FeedManager.KIND_PODCAST.equals(feedUriPrefix)) { + return PodcastFileResource.TYPE_NAME; + } else if(FeedManager.KIND_BLOG.equals(feedUriPrefix)) { + return BlogFileResource.TYPE_NAME; + } + return null; + } + + private boolean isAccessible(UserRequest ureq, Path path, OLATResourceable feed) { + Boolean accessible = null; + + Long ressourceId; + if(path.getCourseId() == null) { + ressourceId = path.getFeedId(); + } else { + ressourceId = path.getCourseId(); + } + if(path.getIdentityKey() == null && ureq != null && ureq.getIdentity() != null) { + path.setIdentityKey(ureq.getIdentity().getKey()); + } + + FeedPathKey key = new FeedPathKey(path.getIdentityKey(), ressourceId, path.getNodeId()); + accessible = validatedUriCache.computeIfAbsent(key, k -> { + boolean hasAccess = hasAccess(ureq, feed, path); + return Boolean.valueOf(hasAccess); + }); + + return accessible != null && accessible.booleanValue(); + } /** * Dispatch and deliver the requested file given in the path. @@ -161,18 +270,20 @@ public class FeedMediaDispatcher extends LogDelegator implements Dispatcher { 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(); + dbInstance.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()); + Identity identity = null; + if(path.getIdentityKey() != null) { + identity = securityManager.loadIdentityByKey(path.getIdentityKey()); + } long sinceModifiedMillis = request.getDateHeader("If-Modified-Since"); - Feed feedLight = manager.loadFeed(feed); + Feed feedLight = feedManager.loadFeed(feed); long lastModifiedMillis = -1; if (feedLight != null) { lastModifiedMillis = feedLight.getLastModified().getTime(); @@ -189,17 +300,17 @@ public class FeedMediaDispatcher extends LogDelegator implements Dispatcher { return; } } else { - resource = manager.createFeedFile(feed, identity, path.getCourseId(), path.getNodeId()); + resource = feedManager.createFeedFile(feed, identity, path.getCourseId(), path.getNodeId()); } } else if (path.isItemType()) { - resource = manager.createItemMediaFile(feed, path.getItemId(), path.getItemFileName()); + resource = feedManager.createItemMediaFile(feed, path.getItemId(), path.getItemFileName()); } else if (path.isIconType()) { Size thumbnailSize = null; String thumbnail = request.getParameter("thumbnail"); if(StringHelper.containsNonWhitespace(thumbnail)) { thumbnailSize = Size.parseString(thumbnail); } - VFSLeaf resourceFile = manager.createFeedMediaFile(feed, path.getIconFileName(), thumbnailSize); + VFSLeaf resourceFile = feedManager.createFeedMediaFile(feed, path.getIconFileName(), thumbnailSize); if(resourceFile != null) { resource = new VFSMediaResource(resourceFile); } @@ -208,20 +319,6 @@ public class FeedMediaDispatcher extends LogDelegator implements Dispatcher { 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 = BaseSecurityManager.getInstance().loadIdentityByKey(idKey); - } - return identity; - } - /** * Remove some prefixes from the request path. * @@ -245,21 +342,18 @@ public class FeedMediaDispatcher extends LogDelegator implements Dispatcher { * @param path * @return true if the path may be dispatched. */ - private boolean hasAccess(OLATResourceable feed, Path path) { + private boolean hasAccess(UserRequest ureq, 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); + ICourse course = CourseFactory.loadCourse(path.getCourseId()); + if(course != null) { + CourseNode node = course.getEditorTreeModel().getCourseNode(path.getNodeId()); + hasAccess = hasAccess(ureq, path, course, node, feed); + } } else { // A learning resource is being requested - hasAccess = hasAccess(identity, path.getToken(), feed); + hasAccess = hasAccess(ureq, path, feed); } return hasAccess; } @@ -274,25 +368,48 @@ public class FeedMediaDispatcher extends LogDelegator implements Dispatcher { * @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) { + private boolean hasAccess(UserRequest ureq, Path path, ICourse course, CourseNode node, OLATResourceable feed) { + RepositoryEntry entry = course.getCourseEnvironment().getCourseGroupManager().getCourseEntry(); + if (allowsGuestAccess(entry)) { + return true; + } + + Roles roles = null; + Identity identity = null; + RepositoryEntrySecurity reSecurity = null; + if(ureq != null && ureq.getIdentity() != null) { + identity = ureq.getIdentity(); + roles = ureq.getUserSession().getRoles(); + reSecurity = repositoryManager.isAllowed(ureq, entry); + } else if(path.getIdentityKey() != null) { + identity = securityManager.loadIdentityByKey(path.getIdentityKey()); + if(validAuthentication(identity, path.getToken())) { + roles = securityManager.getRoles(identity); + reSecurity = repositoryManager.isAllowed(identity, roles, entry); + } + } + boolean hasAccess = false; - final RepositoryManager resMgr = RepositoryManager.getInstance(); - final RepositoryEntry repoEntry = resMgr.lookupRepositoryEntry(course, false); - if (allowsGuestAccess(repoEntry)) { - hasAccess = true; - } else { - IdentityEnvironment ienv = new IdentityEnvironment(); - ienv.setIdentity(identity); - Roles roles = BaseSecurityManager.getInstance().getRoles(identity); - ienv.setRoles(roles); - UserCourseEnvironment userCourseEnv = new UserCourseEnvironmentImpl(ienv, course.getCourseEnvironment()); + if(identity != null && roles != null && reSecurity != null) { + IdentityEnvironment ienv = new IdentityEnvironment(identity, roles); + UserCourseEnvironment userCourseEnv = new UserCourseEnvironmentImpl(ienv, course.getCourseEnvironment(), null, null, null, null, + reSecurity.isCourseCoach() || reSecurity.isGroupCoach(), reSecurity.isEntryAdmin(), reSecurity.isCourseParticipant() || reSecurity.isGroupParticipant(), + false); // Build an evaluation tree TreeEvaluation treeEval = new TreeEvaluation(); NodeEvaluation nodeEval = node.eval(userCourseEnv.getConditionInterpreter(), treeEval, new VisibleTreeFilter()); - if (nodeEval.isVisible() && validAuthentication(identity, token)) { + if (nodeEval.isVisible()) { hasAccess = true; } } + + if(!hasAccess) { + //allow if the feed resource itself allow guest access + entry = repositoryManager.lookupRepositoryEntry(feed, false); + if (allowsGuestAccess(entry)) { + return true; + } + } return hasAccess; } @@ -304,24 +421,39 @@ public class FeedMediaDispatcher extends LogDelegator implements Dispatcher { * @param feed * @return true if the identity has access. */ - private boolean hasAccess(Identity identity, String token, OLATResourceable feed) { + private boolean hasAccess(UserRequest ureq, Path path, OLATResourceable feed) { + RepositoryEntry entry = repositoryManager.lookupRepositoryEntry(feed, false); + if (allowsGuestAccess(entry)) { + return true; + } + + Identity identity = null; + RepositoryEntrySecurity reSecurity = null; + if(ureq != null && ureq.getIdentity() != null) { + identity = ureq.getIdentity(); + if(entry != null) { + Roles roles = ureq.getUserSession().getRoles(); + reSecurity = repositoryManager.isAllowed(identity, roles, entry); + } + } else if(path.getIdentityKey() != null) { + identity = securityManager.loadIdentityByKey(path.getIdentityKey()); + if(validAuthentication(identity, path.getToken())) { + Roles roles = securityManager.getRoles(identity); + reSecurity = repositoryManager.isAllowed(identity, roles, entry); + } + } + boolean hasAccess = false; - RepositoryManager resMgr = RepositoryManager.getInstance(); - RepositoryEntry repoEntry = resMgr.lookupRepositoryEntry(feed, false); - if (allowsGuestAccess(repoEntry)) { - hasAccess = true; - } else if (identity != null) { - if (repoEntry != null){ - final Roles roles = BaseSecurityManager.getInstance().getRoles(identity); - final boolean isAllowedToLaunch = resMgr.isAllowedToLaunch(identity, roles, repoEntry); - if (isAllowedToLaunch && validAuthentication(identity, token)) { + if (identity != null) { + if (entry != null){ + if (reSecurity != null && reSecurity.canLaunch()) { hasAccess = true; } } else { // no repository entry -> could be a feed without a repository-entry (ePortfolio-Blog-feed) EPFrontendManager ePFMgr = (EPFrontendManager) CoreSpringFactory.getBean("epFrontendManager"); if (ePFMgr.checkFeedAccess(feed, identity)){ - return validAuthentication(identity, token); + return validAuthentication(identity, path.getToken()); } } } @@ -336,13 +468,10 @@ public class FeedMediaDispatcher extends LogDelegator implements Dispatcher { * @return True if authentication is valid */ private boolean validAuthentication(Identity identity, String token) { - boolean valid = false; - BaseSecurity secMgr = BaseSecurityManager.getInstance(); - Authentication authentication = secMgr.findAuthenticationByAuthusername(identity.getKey().toString(), TOKEN_PROVIDER); - if (authentication != null && authentication.getCredential().equals(token)) { - valid = true; - } - return valid; + if(identity == null || token == null) return false; + + Authentication authentication = securityManager.findAuthenticationByAuthusername(identity.getKey().toString(), TOKEN_PROVIDER); + return authentication != null && authentication.getCredential().equals(token); } /** @@ -356,15 +485,4 @@ public class FeedMediaDispatcher extends LogDelegator implements Dispatcher { } 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); - } } diff --git a/src/main/java/org/olat/modules/webFeed/dispatching/FeedPathKey.java b/src/main/java/org/olat/modules/webFeed/dispatching/FeedPathKey.java new file mode 100644 index 0000000000000000000000000000000000000000..ec57b2e0be569fe258265a0d16abe8b9d9874108 --- /dev/null +++ b/src/main/java/org/olat/modules/webFeed/dispatching/FeedPathKey.java @@ -0,0 +1,65 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <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 the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <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> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.modules.webFeed.dispatching; + +/** + * + * Initial date: 9 Oct 2018<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class FeedPathKey { + + private final Long identityKey; + private final Long ressourceId; + private final String nodeId; + + public FeedPathKey(Long identityKey, Long ressourceId, String nodeId) { + this.identityKey = identityKey; + this.ressourceId = ressourceId; + this.nodeId = nodeId; + } + + public Long getResourceId() { + return ressourceId; + } + + @Override + public int hashCode() { + return ressourceId.hashCode() + + (identityKey == null ? 1819879 : identityKey.hashCode()) + + (nodeId == null ? 52387 : nodeId.hashCode()); + } + + @Override + public boolean equals(Object obj) { + if(obj == this) { + return true; + } + if(obj instanceof FeedPathKey) { + FeedPathKey key = (FeedPathKey)obj; + return ressourceId.equals(key.ressourceId) + && ((identityKey == null && key.identityKey == null) || (identityKey != null && identityKey.equals(key.identityKey))) + && ((nodeId == null && key.nodeId == null) || (nodeId != null && nodeId.equals(key.nodeId))); + + } + return false; + } +} diff --git a/src/main/java/org/olat/modules/webFeed/dispatching/Path.java b/src/main/java/org/olat/modules/webFeed/dispatching/Path.java index 9da1482bbbefc3ad16accf15a1a629b84e18aff6..5a439f2618260c2c7d9cbae02ca59b69926d8f2e 100644 --- a/src/main/java/org/olat/modules/webFeed/dispatching/Path.java +++ b/src/main/java/org/olat/modules/webFeed/dispatching/Path.java @@ -24,20 +24,7 @@ import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; -import org.apache.commons.lang.RandomStringUtils; -import org.olat.basesecurity.Authentication; -import org.olat.basesecurity.BaseSecurity; -import org.olat.basesecurity.BaseSecurityManager; -import org.olat.core.helpers.Settings; -import org.olat.core.id.Identity; -import org.olat.core.id.OLATResourceable; -import org.olat.core.util.cache.CacheWrapper; -import org.olat.core.util.coordinate.CoordinatorManager; -import org.olat.core.util.coordinate.SyncerExecutor; -import org.olat.modules.webFeed.Feed; import org.olat.modules.webFeed.manager.FeedManager; -import org.olat.repository.RepositoryEntry; -import org.olat.repository.RepositoryManager; /** * The Path class. @@ -53,9 +40,6 @@ import org.olat.repository.RepositoryManager; * @author gwassmann */ public class Path { - // Not private for better performance (apperently) - protected static final CacheWrapper<String,Boolean> validatedUriCache = CoordinatorManager.getInstance().getCoordinator().getCacher().getCache(Path.class.getSimpleName(), - "feed"); // Instance variables private int type; @@ -81,16 +65,13 @@ public class Path { private static final int AUTHENTICATED_COURSE_ICON = FEED_MEDIA + COURSE + AUTHENTICATED; private static final int AUTHENTICATED_COURSE_ITEM = ITEM_MEDIA + COURSE + AUTHENTICATED; - // Class variables - private static final String TOKEN_PROVIDER = FeedMediaDispatcher.TOKEN_PROVIDER; - // Patterns private static final String NUMBER = "(\\d+)"; private static final String WORD = "(\\w+)"; private static final String FILE_NAME = "([^/]+\\.\\w{3,4})"; private static final String SLASH = "/"; private static final String BASE_PATH_DELIMITER = "/_/"; - private static final String COURSE_NODE_INDICATOR = "coursenode"; + public static final String COURSE_NODE_INDICATOR = "coursenode"; public static final String MEDIA_DIR = "media"; private static final String AUTHENTICATION = NUMBER + SLASH + WORD + SLASH; @@ -118,7 +99,7 @@ public class Path { private static final Pattern authCourseFeedMediaPattern = Pattern.compile(AUTH_COURSE_PATH + FEED_MEDIA_PATH); private static final Pattern authCourseItemMediaPattern = Pattern.compile(AUTH_COURSE_PATH + ITEM_MEDIA_PATH); - private static List<Pattern> patterns = new ArrayList<Pattern>(); + private static List<Pattern> patterns = new ArrayList<>(); static { patterns.add(feedPattern); patterns.add(feedMediaPattern); @@ -275,52 +256,6 @@ public class Path { } } - /** - * @return true if the path is in cache and it is accessible, meaning access - * has been verified successfully. - */ - public boolean isCachedAndAccessible() { - // return true if path is in the cache and the validation was successful - Boolean accessible = validatedUriCache.get(getKey()); - return accessible != null ? accessible : false; - } - - /** - * A feed contains many URLs and links etc. The validation of the URL and - * verification of access should only be done once for performance reasons. - * That's where caching comes in. We'll store the URL as the key and give it a - * boolean value whether access has been successfully granted or not. - */ - public void cache(OLATResourceable ores, final boolean accessible) { - final String key = getKey(); - CoordinatorManager.getInstance().getCoordinator().getSyncer().doInSync(ores, new SyncerExecutor() { - public void execute() { - if (validatedUriCache.get(key) == null) { - validatedUriCache.put(key, accessible); - } else { - validatedUriCache.update(key, accessible); - } - } - }); - } - - /** - * Get the key for caching. - * - * @return the key for caching. I.e. the URL without any file or item - * information. - */ - private String getKey() { - String key = null; - // If the type is ITEM_MEDIA (+ something) remove the item id and the filename - // from the original path. - if (originalPath != null) { - int delimiterIndex = originalPath.indexOf(BASE_PATH_DELIMITER); - key = originalPath.substring(0, delimiterIndex); - } - return key; - } - /** * @param feedId The feedId to set. */ @@ -475,74 +410,9 @@ public class Path { return type > COURSE; } - /** - * @see java.lang.Object#toString() - */ + @Override public String toString() { return originalPath; } - - /** - * Returns a podcast base URI of the type<br> - * http://myolat.org/olat/[podcast|blog]/[IDKEY/TOKEN]/ORESID - * - * @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) { - BaseSecurity manager = BaseSecurityManager.getInstance(); - boolean isCourseNode = courseId != null && nodeId != null; - - final String slash = "/"; - StringBuffer uri = new StringBuffer(); - uri.append(Settings.getServerContextPathURI()); - uri.append(slash); - uri.append(FeedMediaDispatcher.uriPrefixes.get(feed.getResourceableTypeName())); - uri.append(slash); - - if (isCourseNode) { - uri.append(COURSE_NODE_INDICATOR); - uri.append(slash); - } - - if (identity != null) { - // The identity can be null for guests - String idKey = identity.getKey().toString(); - Authentication authentication = manager.findAuthenticationByAuthusername(idKey, TOKEN_PROVIDER); - if (authentication == null) { - // Create an authentication - String token = RandomStringUtils.randomAlphanumeric(6); - authentication = manager.createAndPersistAuthentication(identity, TOKEN_PROVIDER, idKey, token, null); - } - // If the repository entry allows guest access it is public, thus not - // private. - boolean isPrivate = true; - RepositoryEntry entry = RepositoryManager.getInstance().lookupRepositoryEntry(feed, false); - if (entry != null && entry.isGuests()) { - isPrivate = false; - } - - if (isPrivate) { - // identity key - uri.append(idKey); - uri.append(slash); - // token - uri.append(authentication.getCredential()); - uri.append(slash); - } - } - - if (isCourseNode) { - uri.append(courseId); - uri.append(slash); - uri.append(nodeId); - uri.append(slash); - } - // feed id - uri.append(feed.getResourceableId()); - // Append base uri delimiter. (Used to identify the root path for caching) - uri.append("/_"); - return uri.toString(); - } + } diff --git a/src/main/java/org/olat/modules/webFeed/manager/FeedManagerImpl.java b/src/main/java/org/olat/modules/webFeed/manager/FeedManagerImpl.java index d47e4afeb35bf37c939ca0ad43d3c75b2f69e65d..33e9d1bc2748ce9544b762c7b34adddd6e9cc879 100644 --- a/src/main/java/org/olat/modules/webFeed/manager/FeedManagerImpl.java +++ b/src/main/java/org/olat/modules/webFeed/manager/FeedManagerImpl.java @@ -27,7 +27,9 @@ import java.util.Collections; import java.util.Date; import java.util.List; +import org.apache.commons.lang.RandomStringUtils; import org.olat.admin.quota.QuotaConstants; +import org.olat.basesecurity.Authentication; import org.olat.basesecurity.BaseSecurity; import org.olat.core.CoreSpringFactory; import org.olat.core.commons.modules.bc.meta.tagged.MetaTagged; @@ -39,6 +41,7 @@ import org.olat.core.commons.services.image.Size; import org.olat.core.commons.services.notifications.NotificationsManager; import org.olat.core.gui.components.form.flexible.elements.FileElement; import org.olat.core.gui.media.MediaResource; +import org.olat.core.helpers.Settings; import org.olat.core.id.Identity; import org.olat.core.id.OLATResourceable; import org.olat.core.logging.OLog; @@ -591,7 +594,7 @@ public class FeedManagerImpl extends FeedManager { * @return A unique key for the item of the feed */ private String itemKey(String string, String string2) { - final StringBuffer key = new StringBuffer(); + final StringBuilder key = new StringBuilder(128); key.append("feed").append(string2); key.append("_item_").append(string); return key.toString(); @@ -606,8 +609,7 @@ public class FeedManagerImpl extends FeedManager { * @return A unique key for the item of the feed */ protected String itemKey(Item item, OLATResourceable feed) { - String key = itemKey(item.getGuid(), feed.getResourceableId().toString()); - return key; + return itemKey(item.getGuid(), feed.getResourceableId().toString()); } @Override @@ -669,9 +671,69 @@ public class FeedManagerImpl extends FeedManager { return mediaResource; } + /** + * Returns a podcast base URI of the type<br> + * http://myolat.org/olat/[podcast|blog]/[IDKEY/TOKEN]/ORESID + * + * @param feed + * @param identityKey + * @return The feed base uri for the given user (identity) + */ @Override public String getFeedBaseUri(Feed feed, Identity identity, Long courseId, String nodeId) { - return FeedMediaDispatcher.getFeedBaseUri(feed, identity, courseId, nodeId); + boolean isCourseNode = courseId != null && nodeId != null; + + final String slash = "/"; + StringBuilder uri = new StringBuilder(256); + uri.append(Settings.getServerContextPathURI()); + uri.append(slash); + uri.append(FeedMediaDispatcher.getURIPrefix(feed.getResourceableTypeName())); + uri.append(slash); + + if (isCourseNode) { + uri.append(org.olat.modules.webFeed.dispatching.Path.COURSE_NODE_INDICATOR); + uri.append(slash); + } + + if (identity != null) { + // The identity can be null for guests + String idKey = identity.getKey().toString(); + Authentication authentication = securityManager.findAuthenticationByAuthusername(idKey, FeedMediaDispatcher.TOKEN_PROVIDER); + if (authentication == null) { + // Create an authentication + String token = RandomStringUtils.randomAlphanumeric(6); + authentication = securityManager.createAndPersistAuthentication(identity, FeedMediaDispatcher.TOKEN_PROVIDER, idKey, token, null); + } + // If the repository entry allows guest access it is public, thus not + // private. + RepositoryEntry entry; + if(courseId != null) {//check the course + OLATResourceable courseOres = OresHelper.createOLATResourceableInstance("CourseModule", courseId); + entry = repositoryManager.lookupRepositoryEntry(courseOres, false); + } else { + entry = repositoryManager.lookupRepositoryEntry(feed, false); + } + if (entry == null || entry.isGuests()) { + // identity key + uri.append(idKey); + uri.append(slash); + // token + uri.append(authentication.getCredential()); + uri.append(slash); + } + } + + if (isCourseNode) { + uri.append(courseId); + uri.append(slash); + uri.append(nodeId); + uri.append(slash); + } + // feed id + uri.append(feed.getResourceableId()); + // Append base uri delimiter. (Used to identify the root path for caching) + uri.append("/_"); + return uri.toString(); } @Override @@ -813,6 +875,14 @@ public class FeedManagerImpl extends FeedManager { if (feed != null) { List<Item> itemsFromXml = feedFileStorage.loadItemsFromXML(ores); + + // clean up for MySQL + if(dbInstance.isMySQL()) { + for (Item itemFromXml : itemsFromXml) { + mysqlCleanUp(itemFromXml); + } + } + itemsFromXml = fixFeedVersionIssues(feedFromXml, itemsFromXml); for (Item itemFromXml : itemsFromXml) { // Check if the item already exits or create it. diff --git a/src/main/resources/infinispan-config.xml b/src/main/resources/infinispan-config.xml index 68aed851e512d73d36f7c0076f8f52c26aa9b590..a6d45b940a00315defbe6914c18695abf0e99188 100644 --- a/src/main/resources/infinispan-config.xml +++ b/src/main/resources/infinispan-config.xml @@ -161,20 +161,11 @@ <expiration max-idle="3600000" interval="15000" /> </local-cache> - <local-cache name="FeedManager@feed" simple-cache="true" statistics="true" statistics-available="true"> - <locking isolation="READ_COMMITTED" concurrency-level="1000" acquire-timeout="15000" striping="false" /> - <transaction mode="NONE" auto-commit="true" /> - <memory> - <object size="1000" strategy="REMOVE" /> - </memory> - <expiration max-idle="900000" interval="15000" /> - </local-cache> - <local-cache name="Path@feed" simple-cache="true" statistics="true" statistics-available="true"> <locking isolation="READ_COMMITTED" concurrency-level="1000" acquire-timeout="15000" striping="false" /> <transaction mode="NONE" auto-commit="true" /> <memory> - <object size="1000" strategy="REMOVE" /> + <object size="5000" strategy="REMOVE" /> </memory> <expiration max-idle="900000" interval="15000" /> </local-cache> diff --git a/src/main/resources/serviceconfig/olat.properties b/src/main/resources/serviceconfig/olat.properties index 49e637d9992e2bf02c1420b599afc33313d5ddd8..6f7b48510df4667078c731b7b404f7a560be808a 100644 --- a/src/main/resources/serviceconfig/olat.properties +++ b/src/main/resources/serviceconfig/olat.properties @@ -124,7 +124,7 @@ smtp.sslCheckCertificate=false smtp.starttls=false # timeout in milliseconds smtp.timeout=8000 -# fix from of the smtp envelope +# smtp.from will override the mail envelope, leave it empty to set it to the first reply-to address smtp.from= # system mails will be sent from this address (from local domain with valid reverse dns): fromemail=no-reply@your.domain @@ -1190,8 +1190,13 @@ ldap.learningResourceManagerRoleValue= # Build properties ##### application.name=OpenOLAT +<<<<<<< HEAD build.version=13.0b7 build.identifier=openolat130b7-dev +======= +build.version=12.5.7 +build.identifier=openolat1257-dev +>>>>>>> refs/remotes/origin/OpenOLAT_12.5 build.repo.revision=local-devel ##### diff --git a/src/test/java/org/olat/modules/webFeed/manager/FeedManagerImplTest.java b/src/test/java/org/olat/modules/webFeed/manager/FeedManagerImplTest.java index d43a1349b30bf649e67c1742bc3a1e6856507608..13504230d16d8779325cad18cf0f22d73e9efde0 100644 --- a/src/test/java/org/olat/modules/webFeed/manager/FeedManagerImplTest.java +++ b/src/test/java/org/olat/modules/webFeed/manager/FeedManagerImplTest.java @@ -42,6 +42,7 @@ import org.junit.Before; import org.junit.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.olat.core.commons.persistence.DB; import org.olat.core.commons.services.notifications.NotificationsManager; import org.olat.core.gui.components.form.flexible.elements.FileElement; import org.olat.core.id.Identity; @@ -87,7 +88,9 @@ public class FeedManagerImplTest { private FileElement fileElementDummy; @Mock private Syncer syncerDummy; - + + @Mock + private DB dbInstanceMock; @Mock private FeedDAO feedDAOMock; @Mock @@ -118,6 +121,8 @@ public class FeedManagerImplTest { when(coordinaterMock.getSyncer()).thenReturn(syncerDummy); sut = new FeedManagerImpl(resourceManagerMock, fileResourceManagerMock, coordinaterManagerMock); feedDAOMock = mock(FeedDAO.class); + + ReflectionTestUtils.setField(sut, "dbInstance", dbInstanceMock); ReflectionTestUtils.setField(sut, "feedDAO", feedDAOMock); ReflectionTestUtils.setField(sut, "itemDAO", itemDAOMock); ReflectionTestUtils.setField(sut, "feedFileStorage", feedFileStorageMock); diff --git a/src/test/java/org/olat/selenium/ImsQTI21EditorTest.java b/src/test/java/org/olat/selenium/ImsQTI21EditorTest.java index 342e45c0f1ccf90ec6c5d5c560dcbbc6f93367d1..f80e802b58018c5f1e8781d98e92ec76720b0a43 100644 --- a/src/test/java/org/olat/selenium/ImsQTI21EditorTest.java +++ b/src/test/java/org/olat/selenium/ImsQTI21EditorTest.java @@ -935,6 +935,7 @@ public class ImsQTI21EditorTest extends Deployments { File backgroundImageFile = new File(backgroundImageUrl.toURI()); hotspotEditor .updloadBackground(backgroundImageFile) + .moveToHotspotEditor() .resizeCircle() .moveCircle(300, 120) .addRectangle() @@ -960,6 +961,7 @@ public class ImsQTI21EditorTest extends Deployments { .addHotspot(); hotspotEditor .updloadBackground(backgroundImageFile) + .moveToHotspotEditor() .resizeCircle() .moveCircle(310, 125) .addRectangle() @@ -1015,17 +1017,17 @@ public class ImsQTI21EditorTest extends Deployments { ryomouQtiPage .assertOnAssessmentItem() .answerHotspot("rect") - .saveAnswer() + .saveGraphicAnswer() .assertFeedback("Incorrect") .assertCorrectSolution("Correct solution") .hint() .assertFeedback("Hint") .answerHotspot("circle") - .saveAnswer() + .saveGraphicAnswer() .assertFeedback("Correct feedback") .nextAnswer() .answerHotspot("rect") - .saveAnswer() + .saveGraphicAnswer() .assertCorrectSolution("Correct solution") .assertFeedback("Incorrect") .endTest() @@ -1051,11 +1053,11 @@ public class ImsQTI21EditorTest extends Deployments { .getQTI21Page(participantBrowser) .assertOnAssessmentItem() .answerHotspot("circle") - .saveAnswer() + .saveGraphicAnswer() .assertFeedback("Correct feedback") .nextAnswer() .answerHotspot("circle") - .saveAnswer() + .saveGraphicAnswer() .endTest() .assertOnAssessmentResults() .assertOnAssessmentTestScore(5);// 3 points from the first question, 2 from the second @@ -1106,6 +1108,7 @@ public class ImsQTI21EditorTest extends Deployments { File backgroundImageFile = new File(backgroundImageUrl.toURI()); hotspotEditor .updloadBackground(backgroundImageFile) + .moveToHotspotEditor() .resizeCircle() .moveCircle(300, 120) .addRectangle() @@ -1132,6 +1135,7 @@ public class ImsQTI21EditorTest extends Deployments { .addHotspot(); hotspotEditor .updloadBackground(backgroundImageFile) + .moveToHotspotEditor() .resizeCircle() .moveCircle(310, 125) .addRectangle() @@ -1188,17 +1192,17 @@ public class ImsQTI21EditorTest extends Deployments { ryomouQtiPage .assertOnAssessmentItem() .answerHotspot("rect") - .saveAnswer() + .saveGraphicAnswer() .assertFeedback("Incorrect") .assertCorrectSolution("Correct solution") .hint() .assertFeedback("Hint") .answerHotspot("circle") - .saveAnswer() + .saveGraphicAnswer() .assertFeedback("Correct feedback") .nextAnswer() .answerHotspot("circle") - .saveAnswer() + .saveGraphicAnswer() .assertCorrectSolution("Correct solution") .assertFeedback("Incorrect") .endTest() @@ -1225,12 +1229,12 @@ public class ImsQTI21EditorTest extends Deployments { .assertOnAssessmentItem() .answerHotspot("circle") .answerHotspot("rect") - .saveAnswer() + .saveGraphicAnswer() .assertFeedback("Correct feedback") .nextAnswer() .answerHotspot("circle") .answerHotspot("rect") - .saveAnswer() + .saveGraphicAnswer() .endTest() .assertOnAssessmentResults() .assertOnAssessmentTestScore(6);// 3 points from the first question, 3 from the second @@ -3280,6 +3284,7 @@ public class ImsQTI21EditorTest extends Deployments { File backgroundImageFile = new File(backgroundImageUrl.toURI()); hotspotEditor .updloadBackground(backgroundImageFile) + .moveToHotspotEditor() .resizeCircle() .moveCircle(300, 120) .addRectangle() @@ -3333,7 +3338,7 @@ public class ImsQTI21EditorTest extends Deployments { .answerMultipleChoice("Correct") .saveAnswer() .answerHotspot("circle") - .saveAnswer() + .saveGraphicAnswer() .endTest() //check the results .assertOnAssessmentResults() @@ -3366,7 +3371,7 @@ public class ImsQTI21EditorTest extends Deployments { .answerMultipleChoice("Faux") .saveAnswer() .answerHotspot("rect") - .saveAnswer() + .saveGraphicAnswer() .endTest() //check the results .assertOnAssessmentResults() @@ -3398,7 +3403,7 @@ public class ImsQTI21EditorTest extends Deployments { .answerMultipleChoice("Correct") .saveAnswer() .answerHotspot("circle") - .saveAnswer() + .saveGraphicAnswer() .endTest() //check the results .assertOnAssessmentResults() diff --git a/src/test/java/org/olat/selenium/ImsQTI21InteractionsTest.java b/src/test/java/org/olat/selenium/ImsQTI21InteractionsTest.java index b5ffd935b492fecc83b98bf9c5d4c14c1134516d..78d8a208a10671916881a9b6763e141192ec6b7d 100644 --- a/src/test/java/org/olat/selenium/ImsQTI21InteractionsTest.java +++ b/src/test/java/org/olat/selenium/ImsQTI21InteractionsTest.java @@ -448,7 +448,7 @@ public class ImsQTI21InteractionsTest extends Deployments { qtiPage .clickToolbarBack() .assertOnAssessmentItem() - .answerSelectPoint(100, 110) + .answerSelectPoint(100, 110, 192, 280) .saveAnswer() .endTest() .closeTest(); diff --git a/src/test/java/org/olat/selenium/page/graphene/Position.java b/src/test/java/org/olat/selenium/page/graphene/Position.java new file mode 100644 index 0000000000000000000000000000000000000000..fd434f254bad8f5d627c38ff9cf9211f7a83b71c --- /dev/null +++ b/src/test/java/org/olat/selenium/page/graphene/Position.java @@ -0,0 +1,93 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <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 the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <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> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.selenium.page.graphene; + +import java.util.List; + +import org.olat.ims.qti21.model.xml.AssessmentItemFactory; +import org.openqa.selenium.Dimension; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.firefox.FirefoxDriver; + +/** + * + * Initial date: 5 Oct 2018<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class Position { + + private final int x; + private final int y; + + private Position(int x, int y) { + this.x = x; + this.y = y; + } + + public int getX() { + return x; + } + + public int getY() { + return y; + } + + public static Position valueOf(String coords, Dimension dimension, WebDriver browser) { + List<Integer> coordList = AssessmentItemFactory.coordsList(coords); + + int x = 0; + int y = 0; + if(coordList.size() == 3) {// circle + x = coordList.get(0).intValue(); + y = coordList.get(1).intValue(); + } else if(coordList.size() == 4) {// rectangle + int x1 = coordList.get(0).intValue(); + int y1 = coordList.get(1).intValue(); + int x2 = coordList.get(2).intValue(); + int y2 = coordList.get(3).intValue(); + x = (x2 + x1) / 2; + y = (y2 + y1) / 2; + } + + if(browser instanceof FirefoxDriver) { + x = x - Math.round(dimension.getWidth() / 2.0f); + y = y - Math.round(dimension.getHeight() / 2.0f); + } + return new Position(x, y); + } + + public static Position valueOf(int x, int y, Dimension dimension, WebDriver browser) { + if(browser instanceof FirefoxDriver) { + x = x - Math.round(dimension.getWidth() / 2.0f); + y = y - Math.round(dimension.getHeight() / 2.0f); + } + return new Position(x, y); + } + + public static Position valueOf(int x, int y, int width, int height, WebDriver browser) { + if(browser instanceof FirefoxDriver) { + x = x - Math.round(width / 2.0f); + y = y - Math.round(height / 2.0f); + } + return new Position(x, y); + } + +} diff --git a/src/test/java/org/olat/selenium/page/qti/QTI21HotspotEditorPage.java b/src/test/java/org/olat/selenium/page/qti/QTI21HotspotEditorPage.java index faed98382a9d26bb16c33d592ae00c5983e68c22..beb294e84680f532531b6be856c8363abab7dec1 100644 --- a/src/test/java/org/olat/selenium/page/qti/QTI21HotspotEditorPage.java +++ b/src/test/java/org/olat/selenium/page/qti/QTI21HotspotEditorPage.java @@ -22,10 +22,12 @@ package org.olat.selenium.page.qti; import java.io.File; import org.olat.selenium.page.graphene.OOGraphene; +import org.olat.selenium.page.graphene.Position; import org.openqa.selenium.By; import org.openqa.selenium.Dimension; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; +import org.openqa.selenium.firefox.FirefoxDriver; import org.openqa.selenium.interactions.Actions; import uk.ac.ed.ph.jqtiplus.value.Cardinality; @@ -77,6 +79,16 @@ public class QTI21HotspotEditorPage extends QTI21AssessmentItemEditorPage { return this; } + public QTI21HotspotEditorPage moveToHotspotEditor() { + By editorBy = By.id("o_qti_hotspots_edit"); + OOGraphene.waitElement(editorBy, browser); + + if(browser instanceof FirefoxDriver) { + OOGraphene.scrollTo(editorBy, browser); + } + return this; + } + /** * Resize the default circle * @return Itself @@ -85,8 +97,10 @@ public class QTI21HotspotEditorPage extends QTI21AssessmentItemEditorPage { By circleBy = By.cssSelector("div.o_draw_circle"); OOGraphene.waitElement(circleBy, browser); WebElement circleEl = browser.findElement(circleBy); + Dimension dim = circleEl.getSize(); + Position pos = Position.valueOf(10, 10, dim, browser); new Actions(browser) - .moveToElement(circleEl, 10, 10) + .moveToElement(circleEl, pos.getX(), pos.getY()) .clickAndHold() .moveByOffset(60, 60) .release() diff --git a/src/test/java/org/olat/selenium/page/qti/QTI21Page.java b/src/test/java/org/olat/selenium/page/qti/QTI21Page.java index 977bfe014a48929c7c7fe21049a84314ba04e772..9e65fdb3712212bfbb79d8ed6d9fb9da31bda60d 100644 --- a/src/test/java/org/olat/selenium/page/qti/QTI21Page.java +++ b/src/test/java/org/olat/selenium/page/qti/QTI21Page.java @@ -25,11 +25,13 @@ import java.util.List; import org.junit.Assert; import org.olat.selenium.page.NavigationPage; import org.olat.selenium.page.graphene.OOGraphene; +import org.olat.selenium.page.graphene.Position; import org.olat.selenium.page.repository.RepositoryAccessPage; import org.openqa.selenium.By; import org.openqa.selenium.Dimension; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; +import org.openqa.selenium.firefox.FirefoxDriver; import org.openqa.selenium.interactions.Actions; import org.openqa.selenium.support.ui.Select; @@ -161,8 +163,21 @@ public class QTI21Page { OOGraphene.waitElement(By.className("hotspotInteraction"), browser); By areaBy = By.xpath("//div[contains(@class,'hotspotInteraction')]//map/area[@shape='" + shape + "']"); List<WebElement> elements = browser.findElements(areaBy); - Assert.assertEquals("Hotspot of shape " + shape, 1, elements.size()); - elements.get(0).click(); + Assert.assertEquals("Hotspot of shape " + shape, 1, elements.size()); + WebElement areaEl = elements.get(0); + if(browser instanceof FirefoxDriver) { + String coords = areaEl.getAttribute("coords"); + By hotspotBy = By.xpath("//div[contains(@class,'hotspotInteraction')]/div/div/img"); + WebElement element = browser.findElement(hotspotBy); + Dimension dim = element.getSize(); + Position pos = Position.valueOf(coords, dim, browser); + new Actions(browser) + .moveToElement(element, pos.getX(), pos.getY()) + .click() + .perform(); + } else { + elements.get(0).click(); + } return this; } @@ -389,11 +404,12 @@ public class QTI21Page { * @param y The y coordinate * @return Itself */ - public QTI21Page answerSelectPoint(int x, int y) { + public QTI21Page answerSelectPoint(int x, int y, int width, int height) { + Position pos = Position.valueOf(x, y, width, height, browser); By canvasBy = By.xpath("//div[contains(@class,'selectPointInteraction')]/div/canvas"); WebElement canvasEl = browser.findElement(canvasBy); new Actions(browser) - .moveToElement(canvasEl, x, y) + .moveToElement(canvasEl, pos.getX(), pos.getY()) .click() .build() .perform(); @@ -492,6 +508,22 @@ public class QTI21Page { return this; } + /** + * For hotspot because Firefox cannot click the save without + * special scrolling. + * @return + */ + public QTI21Page saveGraphicAnswer() { + By saveAnswerBy = By.cssSelector("button.o_sel_assessment_item_submit"); + browser.findElement(saveAnswerBy).click(); + if(browser instanceof FirefoxDriver) { + OOGraphene.clickAndWait(saveAnswerBy, browser); + } else { + OOGraphene.waitBusy(browser); + } + return this; + } + public QTI21Page saveAnswerMoveAndScrollTop() { By saveAnswerBy = By.cssSelector("button.o_sel_assessment_item_submit"); OOGraphene.click(saveAnswerBy, browser);