diff --git a/src/main/java/org/olat/core/commons/services/webdav/manager/EmptyWebResource.java b/src/main/java/org/olat/core/commons/services/webdav/manager/EmptyWebResource.java index b936a5495665ad2f658656679e6dc0e358303deb..3e935cb90636738290d40ef0a8c3fa323c820edc 100644 --- a/src/main/java/org/olat/core/commons/services/webdav/manager/EmptyWebResource.java +++ b/src/main/java/org/olat/core/commons/services/webdav/manager/EmptyWebResource.java @@ -98,6 +98,11 @@ public class EmptyWebResource implements WebResource { return null; } + @Override + public long getCreation() { + return 0; + } + @Override public byte[] getContent() { return null;//use the input stream instead diff --git a/src/main/java/org/olat/core/commons/services/webdav/manager/VFSResource.java b/src/main/java/org/olat/core/commons/services/webdav/manager/VFSResource.java index d79a8901055966f7052e4074bbe44834b500c194..52e1b7a4bccb56e1f2f9e50a6371c588716391d5 100644 --- a/src/main/java/org/olat/core/commons/services/webdav/manager/VFSResource.java +++ b/src/main/java/org/olat/core/commons/services/webdav/manager/VFSResource.java @@ -19,11 +19,17 @@ */ package org.olat.core.commons.services.webdav.manager; +import java.io.IOException; import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.attribute.BasicFileAttributes; import java.util.Date; import org.olat.core.commons.services.webdav.servlets.ConcurrentDateFormat; import org.olat.core.commons.services.webdav.servlets.WebResource; +import org.olat.core.logging.OLog; +import org.olat.core.logging.Tracing; +import org.olat.core.util.vfs.JavaIOItem; import org.olat.core.util.vfs.VFSContainer; import org.olat.core.util.vfs.VFSItem; import org.olat.core.util.vfs.VFSLeaf; @@ -35,6 +41,8 @@ import org.olat.core.util.vfs.VFSLeaf; */ public class VFSResource implements WebResource { + private static final OLog log = Tracing.createLoggerFor(VFSResource.class); + private final VFSItem item; private final String path; private String mimeType; @@ -121,6 +129,22 @@ public class VFSResource implements WebResource { return (item instanceof VFSLeaf ? ((VFSLeaf)item).getInputStream() : null); } + @Override + public long getCreation() { + try { + if(item instanceof JavaIOItem) { + JavaIOItem ioItem = (JavaIOItem)item; + BasicFileAttributes attrs = Files.readAttributes(ioItem.getBasefile().toPath(), + BasicFileAttributes.class); + return attrs.creationTime().toMillis(); + } + return 0; + } catch (IOException e) { + log.warn("getCreationFail" + item, e); + return 0; + } + } + @Override public byte[] getContent() { return null;//use the input stream instead diff --git a/src/main/java/org/olat/core/commons/services/webdav/manager/WebDAVAuthManager.java b/src/main/java/org/olat/core/commons/services/webdav/manager/WebDAVAuthManager.java index 54dba9e6368ff571b6b11538c1d29e90cc273573..d74f5584ce556c0d24dcac45a155953c8f0fa482 100644 --- a/src/main/java/org/olat/core/commons/services/webdav/manager/WebDAVAuthManager.java +++ b/src/main/java/org/olat/core/commons/services/webdav/manager/WebDAVAuthManager.java @@ -81,6 +81,8 @@ public class WebDAVAuthManager implements AuthenticationSPI { if(verity.equals(response)) { Identity identity = olatAuth.getIdentity(); return identity; + } else { + log.error("Verity doesn't equals response"); } } } diff --git a/src/main/java/org/olat/core/commons/services/webdav/servlets/DefaultDispatcher.java b/src/main/java/org/olat/core/commons/services/webdav/servlets/DefaultDispatcher.java index 30fa8961e2fe91a5f8bca8e2b70516e985ec53e8..9be10d0588e11985cb368e4cb7815aecb998c466 100644 --- a/src/main/java/org/olat/core/commons/services/webdav/servlets/DefaultDispatcher.java +++ b/src/main/java/org/olat/core/commons/services/webdav/servlets/DefaultDispatcher.java @@ -25,6 +25,7 @@ import java.io.PrintWriter; import java.io.Reader; import java.io.Serializable; import java.util.ArrayList; +import java.util.Collection; import java.util.Enumeration; import java.util.Iterator; import java.util.StringTokenizer; @@ -93,13 +94,6 @@ public abstract class DefaultDispatcher implements Serializable { // ----------------------------------------------------- Instance Variables - - /** - * The debugging detail level for this servlet. - */ - protected final int debug = 0; - - /** * The input buffer size to use when serving resources. */ @@ -188,7 +182,7 @@ public abstract class DefaultDispatcher implements Serializable { protected String getRelativePath(HttpServletRequest request) { // IMPORTANT: DefaultServlet can be mapped to '/' or '/path/*' but always // serves resources from the web app root with context rooted paths. - // i.e. it can not be used to mount the web app root under a sub-path + // i.e. it cannot be used to mount the web app root under a sub-path // This method must construct a complete context rooted path, although // subclasses can change this behaviour. @@ -329,23 +323,19 @@ public abstract class DefaultDispatcher implements Serializable { // If the resource is not a collection, and the resource path // ends with "/" or "\", return NOT FOUND - if (resource.isFile()) { - if (path.endsWith("/") || (path.endsWith("\\"))) { - // Check if we're included so we can return the appropriate - // missing resource name in the error - String requestUri = (String) request.getAttribute( - RequestDispatcher.INCLUDE_REQUEST_URI); - if (requestUri == null) { - requestUri = request.getRequestURI(); - } - response.sendError(HttpServletResponse.SC_NOT_FOUND, - requestUri); - return; + if (resource.isFile() && path.endsWith("/") || path.endsWith("\\")) { + // Check if we're included so we can return the appropriate + // missing resource name in the error + String requestUri = (String) request.getAttribute( + RequestDispatcher.INCLUDE_REQUEST_URI); + if (requestUri == null) { + requestUri = request.getRequestURI(); } + response.sendError(HttpServletResponse.SC_NOT_FOUND, requestUri); + return; } - boolean isError = - response.getStatus() >= HttpServletResponse.SC_BAD_REQUEST; + boolean isError = response.getStatus() >= HttpServletResponse.SC_BAD_REQUEST; boolean included = false; // Check if the conditions specified in the optional If headers are @@ -354,11 +344,9 @@ public abstract class DefaultDispatcher implements Serializable { // Checking If headers included = (request.getAttribute( RequestDispatcher.INCLUDE_CONTEXT_PATH) != null); - if (!included && !isError && - !checkIfHeaders(request, response, resource)) { + if (!included && !isError && !checkIfHeaders(request, response, resource)) { return; } - } // Find content type. @@ -381,16 +369,26 @@ public abstract class DefaultDispatcher implements Serializable { // Serve a gzipped version of the file if present boolean usingGzippedVersion = false; - if (gzip && - resource.isFile() && - !included && - !path.endsWith(".gz") && - checkIfGzip(request)) { + if (gzip && !included && resource.isFile() && !path.endsWith(".gz")) { WebResource gzipResource = resources.getResource(path + ".gz"); if (gzipResource.exists() && gzipResource.isFile()) { - response.addHeader("Content-Encoding", "gzip"); - resource = gzipResource; - usingGzippedVersion = true; + Collection<String> varyHeaders = response.getHeaders("Vary"); + boolean addRequired = true; + for (String varyHeader : varyHeaders) { + if ("*".equals(varyHeader) || + "accept-encoding".equalsIgnoreCase(varyHeader)) { + addRequired = false; + break; + } + } + if (addRequired) { + response.addHeader("Vary", "accept-encoding"); + } + if (checkIfGzip(request)) { + response.addHeader("Content-Encoding", "gzip"); + resource = gzipResource; + usingGzippedVersion = true; + } } } @@ -423,16 +421,13 @@ public abstract class DefaultDispatcher implements Serializable { if (contentLength == 0L) { serveContent = false; } - } ServletOutputStream ostream = null; PrintWriter writer = null; if (serveContent) { - // Trying to retrieve the servlet output stream - try { ostream = response.getOutputStream(); } catch (IllegalStateException e) { @@ -451,7 +446,6 @@ public abstract class DefaultDispatcher implements Serializable { throw e; } } - } // Check to see if a Filter, Valve of wrapper has written some content. @@ -507,10 +501,28 @@ public abstract class DefaultDispatcher implements Serializable { } catch (IllegalStateException e) { // Silent catch } - if (ostream != null) { - copy(resource, renderResult, ostream); - } else { + + if (ostream == null) { + // Output via a writer so can't use sendfile or write + // content directly. + if (resource.isDirectory()) { + renderResult = null;//render(getPathPrefix(request), resource); + } else { + renderResult = resource.getInputStream(); + } copy(resource, renderResult, writer, encoding); + } else { + // Output is via an InputStream + if (resource.isDirectory()) { + renderResult = null;//render(getPathPrefix(request), resource); + } else { + renderResult = resource.getInputStream(); + } + // If a stream was configured, it needs to be copied to + // the output (this method closes the stream) + if (renderResult != null) { + copy(renderResult, ostream); + } } } @@ -553,12 +565,9 @@ public abstract class DefaultDispatcher implements Serializable { throw new IllegalStateException(); } } - } else { - response.setContentType("multipart/byteranges; boundary=" + mimeSeparation); - if (serveContent) { try { response.setBufferSize(output); @@ -566,8 +575,7 @@ public abstract class DefaultDispatcher implements Serializable { // Silent catch } if (ostream != null) { - copy(resource, ostream, ranges.iterator(), - contentType); + copy(resource, ostream, ranges.iterator(), contentType); } else { // we should not get here throw new IllegalStateException(); @@ -647,7 +655,7 @@ public abstract class DefaultDispatcher implements Serializable { * @param resource The resource * @return Vector of ranges */ - private ArrayList<Range> parseRange(HttpServletRequest request, + protected ArrayList<Range> parseRange(HttpServletRequest request, HttpServletResponse response, WebResource resource) throws IOException { @@ -708,7 +716,7 @@ public abstract class DefaultDispatcher implements Serializable { // Vector which will contain all the ranges which are successfully // parsed. - ArrayList<Range> result = new ArrayList<Range>(); + ArrayList<Range> result = new ArrayList<>(); StringTokenizer commaTokenizer = new StringTokenizer(rangeHeader, ","); // Parsing the range list @@ -790,7 +798,7 @@ public abstract class DefaultDispatcher implements Serializable { * and false if the condition is not satisfied, in which case request * processing is stopped */ - private boolean checkIfMatch(HttpServletRequest request, + protected boolean checkIfMatch(HttpServletRequest request, HttpServletResponse response, WebResource resource) throws IOException { @@ -833,7 +841,7 @@ public abstract class DefaultDispatcher implements Serializable { * and false if the condition is not satisfied, in which case request * processing is stopped */ - private boolean checkIfModifiedSince(HttpServletRequest request, + protected boolean checkIfModifiedSince(HttpServletRequest request, HttpServletResponse response, WebResource resource) { try { long headerValue = request.getDateHeader("If-Modified-Since"); @@ -869,7 +877,7 @@ public abstract class DefaultDispatcher implements Serializable { * and false if the condition is not satisfied, in which case request * processing is stopped */ - private boolean checkIfNoneMatch(HttpServletRequest request, + protected boolean checkIfNoneMatch(HttpServletRequest request, HttpServletResponse response, WebResource resource) throws IOException { @@ -921,7 +929,7 @@ public abstract class DefaultDispatcher implements Serializable { * @return boolean true if the user agent supports gzip encoding, * and false if the user agent does not support gzip encoding */ - private boolean checkIfGzip(HttpServletRequest request) { + protected boolean checkIfGzip(HttpServletRequest request) { Enumeration<String> headers = request.getHeaders("Accept-Encoding"); while (headers.hasMoreElements()) { String header = headers.nextElement(); @@ -943,7 +951,7 @@ public abstract class DefaultDispatcher implements Serializable { * and false if the condition is not satisfied, in which case request * processing is stopped */ - private boolean checkIfUnmodifiedSince(HttpServletRequest request, + protected boolean checkIfUnmodifiedSince(HttpServletRequest request, HttpServletResponse response, WebResource resource) throws IOException { try { @@ -969,34 +977,17 @@ public abstract class DefaultDispatcher implements Serializable { * output stream, and ensure that both streams are closed before returning * (even in the face of an exception). * - * @param resource The source resource * @param is The input stream to read the source resource from * @param ostream The output stream to write to * * @exception IOException if an input/output error occurs */ - private void copy(WebResource resource, InputStream is, + private void copy(InputStream is, ServletOutputStream ostream) throws IOException { IOException exception = null; - InputStream resourceInputStream = null; - - // Optimization: If the binary content has already been loaded, send - // it directly - if (resource.isFile()) { - byte buffer[] = resource.getContent(); - if (buffer != null) { - ostream.write(buffer, 0, buffer.length); - return; - } - resourceInputStream = resource.getInputStream(); - } else { - resourceInputStream = is; - } - - InputStream istream = new BufferedInputStream - (resourceInputStream, input); + InputStream istream = new BufferedInputStream(is, input); // Copy the input stream to the output stream exception = copyRange(istream, ostream); @@ -1063,7 +1054,7 @@ public abstract class DefaultDispatcher implements Serializable { * @param range Range the client wanted to retrieve * @exception IOException if an input/output error occurs */ - private void copy(WebResource resource, ServletOutputStream ostream, + protected void copy(WebResource resource, ServletOutputStream ostream, Range range) throws IOException { @@ -1096,7 +1087,7 @@ public abstract class DefaultDispatcher implements Serializable { * @param contentType Content type of the resource * @exception IOException if an input/output error occurs */ - private void copy(WebResource resource, ServletOutputStream ostream, + protected void copy(WebResource resource, ServletOutputStream ostream, Iterator<Range> ranges, String contentType) throws IOException { @@ -1105,27 +1096,24 @@ public abstract class DefaultDispatcher implements Serializable { while ( (exception == null) && (ranges.hasNext()) ) { InputStream resourceInputStream = resource.getInputStream(); - InputStream istream = - new BufferedInputStream(resourceInputStream, input); - - Range currentRange = ranges.next(); - - // Writing MIME header. - ostream.println(); - ostream.println("--" + mimeSeparation); - if (contentType != null) - ostream.println("Content-Type: " + contentType); - ostream.println("Content-Range: bytes " + currentRange.start - + "-" + currentRange.end + "/" - + currentRange.length); - ostream.println(); - - // Printing content - exception = copyRange(istream, ostream, currentRange.start, - currentRange.end); - - istream.close(); - + try (InputStream istream = new BufferedInputStream(resourceInputStream, input)) { + + Range currentRange = ranges.next(); + + // Writing MIME header. + ostream.println(); + ostream.println("--" + mimeSeparation); + if (contentType != null) + ostream.println("Content-Type: " + contentType); + ostream.println("Content-Range: bytes " + currentRange.start + + "-" + currentRange.end + "/" + + currentRange.length); + ostream.println(); + + // Printing content + exception = copyRange(istream, ostream, currentRange.start, + currentRange.end); + } } ostream.println(); @@ -1147,7 +1135,7 @@ public abstract class DefaultDispatcher implements Serializable { * @param ostream The output stream to write to * @return Exception which occurred during processing */ - private IOException copyRange(InputStream istream, + protected IOException copyRange(InputStream istream, ServletOutputStream ostream) { // Copy the input stream to the output stream @@ -1180,7 +1168,7 @@ public abstract class DefaultDispatcher implements Serializable { * @param writer The writer to write to * @return Exception which occurred during processing */ - private IOException copyRange(Reader reader, PrintWriter writer) { + protected IOException copyRange(Reader reader, PrintWriter writer) { // Copy the input stream to the output stream IOException exception = null; @@ -1214,7 +1202,7 @@ public abstract class DefaultDispatcher implements Serializable { * @param end End of the range which will be copied * @return Exception which occurred during processing */ - private IOException copyRange(InputStream istream, + protected IOException copyRange(InputStream istream, ServletOutputStream ostream, long start, long end) { @@ -1261,9 +1249,6 @@ public abstract class DefaultDispatcher implements Serializable { } - // ------------------------------------------------------ Range Inner Class - - protected static class Range { public long start; @@ -1272,6 +1257,8 @@ public abstract class DefaultDispatcher implements Serializable { /** * Validate range. + * + * @return true if the range is valid, otherwise false */ public boolean validate() { if (end >= length) diff --git a/src/main/java/org/olat/core/commons/services/webdav/servlets/WebDAVDispatcherImpl.java b/src/main/java/org/olat/core/commons/services/webdav/servlets/WebDAVDispatcherImpl.java index 137a9b6761a392f0e4db587ec6b0f322bf78421a..bcef405147d3d99db8c6393310384e82bde70d7f 100644 --- a/src/main/java/org/olat/core/commons/services/webdav/servlets/WebDAVDispatcherImpl.java +++ b/src/main/java/org/olat/core/commons/services/webdav/servlets/WebDAVDispatcherImpl.java @@ -38,6 +38,7 @@ import java.util.Stack; import java.util.TimeZone; import java.util.Vector; +import javax.servlet.DispatcherType; import javax.servlet.RequestDispatcher; import javax.servlet.ServletContext; import javax.servlet.ServletException; @@ -300,7 +301,7 @@ public class WebDAVDispatcherImpl if (log.isDebug()) { log.debug("[" + method + "] " + path); } - + if (method.equals(METHOD_PROPFIND)) { doPropfind(req, resp); } else if (method.equals(METHOD_PROPPATCH)) { @@ -514,14 +515,16 @@ public class WebDAVDispatcherImpl } catch (SAXException e) { // Something went wrong - bad request resp.sendError(WebdavStatus.SC_BAD_REQUEST); + return; } catch (IOException e) { // Something went wrong - bad request resp.sendError(WebdavStatus.SC_BAD_REQUEST); + return; } } if (type == FIND_BY_PROPERTY) { - properties = new Vector<String>(); + properties = new Vector<>(); // propNode must be non-null if type == FIND_BY_PROPERTY NodeList childList = propNode.getChildNodes(); @@ -799,7 +802,8 @@ public class WebDAVDispatcherImpl protected void doHead(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { // Serve the requested resource, without the data content - serveResource(request, response, false, fileEncoding); + boolean serveContent = DispatcherType.INCLUDE.equals(request.getDispatcherType()); + serveResource(request, response, serveContent, fileEncoding); } public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { @@ -1424,7 +1428,7 @@ public class WebDAVDispatcherImpl /** * UNLOCK Method. */ - public void doUnlock(HttpServletRequest req, HttpServletResponse resp) + protected void doUnlock(HttpServletRequest req, HttpServletResponse resp) throws IOException { if (isLocked(req)) { @@ -1546,7 +1550,7 @@ public class WebDAVDispatcherImpl throws IOException { // Parsing destination header - String destinationPath = this.getDestinationPath(req); + String destinationPath = getDestinationPath(req); if (destinationPath == null) { resp.sendError(WebdavStatus.SC_BAD_REQUEST); return false; @@ -1613,7 +1617,7 @@ public class WebDAVDispatcherImpl // Copying source to destination - Hashtable<String,Integer> errorList = new Hashtable<String,Integer>(); + Hashtable<String,Integer> errorList = new Hashtable<>(); boolean result = copyResource(req, errorList, path, destinationPath, moved); @@ -1735,6 +1739,19 @@ public class WebDAVDispatcherImpl copyResource(req, errorList, childSrc, childDest, moved); } } else if (sourceResource.isFile()) { + WebResource destResource = resources.getResource(dest); + if (!destResource.exists() && !destResource.getPath().endsWith("/")) { + int lastSlash = destResource.getPath().lastIndexOf('/'); + if (lastSlash > 0) { + String parent = destResource.getPath().substring(0, lastSlash); + WebResource parentResource = resources.getResource(parent); + if (!parentResource.isDirectory()) { + errorList.put(source, new Integer(WebdavStatus.SC_CONFLICT)); + return false; + } + } + } + WebResource movedFrom = moved ? sourceResource : null; try { if (!resources.write(dest, sourceResource.getInputStream(), false, movedFrom)) { @@ -2004,8 +2021,8 @@ public class WebDAVDispatcherImpl generatedXML.writeElement("D", "propstat", XMLWriter.OPENING); generatedXML.writeElement("D", "prop", XMLWriter.OPENING); - //TODO jdk 1.7 generatedXML.writeProperty("D", "creationdate", - // getISOCreationDate(resource.getCreation())); + generatedXML.writeProperty("D", "creationdate", + getISOCreationDate(resource.getCreation())); generatedXML.writeElement("D", "displayname", XMLWriter.OPENING); generatedXML.writeData(resourceName); generatedXML.writeElement("D", "displayname", XMLWriter.CLOSING); @@ -2082,7 +2099,7 @@ public class WebDAVDispatcherImpl case FIND_BY_PROPERTY : - Vector<String> propertiesNotFound = new Vector<String>(); + Vector<String> propertiesNotFound = new Vector<>(); // Parse the list of properties @@ -2096,9 +2113,9 @@ public class WebDAVDispatcherImpl String property = properties.nextElement(); if (property.equals("creationdate")) { - //TODO jdk 1.7generatedXML.writeProperty - // ("D", "creationdate", - // getISOCreationDate(resource.getCreation())); + generatedXML.writeProperty + ("D", "creationdate", + getISOCreationDate(resource.getCreation())); } else if (property.equals("displayname")) { generatedXML.writeElement ("D", "displayname", XMLWriter.OPENING); @@ -2349,7 +2366,7 @@ public class WebDAVDispatcherImpl case FIND_BY_PROPERTY : - Vector<String> propertiesNotFound = new Vector<String>(); + Vector<String> propertiesNotFound = new Vector<>(); // Parse the list of properties @@ -2493,7 +2510,6 @@ public class WebDAVDispatcherImpl if (wroteStart) { generatedXML.writeElement("D", "lockdiscovery", XMLWriter.CLOSING); } else { - generatedXML.writeElement("D", "lockdiscovery", XMLWriter.NO_CONTENT); return false; } @@ -2537,7 +2553,6 @@ public class WebDAVDispatcherImpl // -------------------------------------------------- LockInfo Inner Class - // --------------------------------------------- WebdavResolver Inner Class @@ -2589,7 +2604,7 @@ class WebdavStatus { * variable. */ private static final Hashtable<Integer,String> mapStatusCodes = - new Hashtable<Integer,String>(); + new Hashtable<>(); // ------------------------------------------------------ HTTP Status Codes diff --git a/src/main/java/org/olat/core/commons/services/webdav/servlets/WebResource.java b/src/main/java/org/olat/core/commons/services/webdav/servlets/WebResource.java index f3a20cb52679cce564e20d4c9112b15217184093..fb2f0bd1da37a2f469a1da2703e80d34bf2942d3 100644 --- a/src/main/java/org/olat/core/commons/services/webdav/servlets/WebResource.java +++ b/src/main/java/org/olat/core/commons/services/webdav/servlets/WebResource.java @@ -93,6 +93,12 @@ public interface WebResource { * represent a file */ InputStream getInputStream(); + + /** + * The time the file was created. If not available, the result of + * {@link #getLastModified()} will be returned. + */ + long getCreation(); /** * Obtain the cached binary content of this resource. diff --git a/src/main/java/org/olat/modules/wiki/Wiki.java b/src/main/java/org/olat/modules/wiki/Wiki.java index 56f8128f4ee037727054c162c2ceddac26a262b6..1b07893747f098f87512cfd900119c7b70d24885 100644 --- a/src/main/java/org/olat/modules/wiki/Wiki.java +++ b/src/main/java/org/olat/modules/wiki/Wiki.java @@ -39,13 +39,14 @@ import java.util.Map; import java.util.Properties; import org.jamwiki.utils.Utilities; -import org.olat.basesecurity.BaseSecurityManager; +import org.olat.core.CoreSpringFactory; import org.olat.core.logging.AssertException; import org.olat.core.logging.OLATRuntimeException; import org.olat.core.logging.OLog; import org.olat.core.logging.Tracing; import org.olat.core.util.FileUtils; import org.olat.core.util.Formatter; +import org.olat.core.util.StringHelper; import org.olat.core.util.vfs.VFSContainer; import org.olat.core.util.vfs.VFSItem; import org.olat.core.util.vfs.VFSLeaf; @@ -54,6 +55,7 @@ import org.olat.core.util.vfs.filters.VFSLeafFilter; import org.olat.modules.wiki.gui.components.wikiToHtml.FilterUtil; import org.olat.modules.wiki.versioning.ChangeInfo; import org.olat.modules.wiki.versioning.DifferenceService; +import org.olat.user.UserManager; /** * Description:<br> @@ -77,7 +79,7 @@ public class Wiki implements WikiContainer, Serializable { protected static final String NEW_PAGE = "O_new_page"; private String IMAGE_NAMESPACE = "Image:"; private String MEDIA_NAMESPACE = "Media:"; - OLog log = Tracing.createLoggerFor(this.getClass()); + private static final OLog log = Tracing.createLoggerFor(Wiki.class); protected Wiki(VFSContainer wikiRootContainer) { if(wikiRootContainer == null) throw new AssertException("null values are not allowed for the wiki constructor!"); @@ -282,9 +284,12 @@ public class Wiki implements WikiContainer, Serializable { final int MAX_RESULTS = 5; ArrayList<WikiPage> pages = new ArrayList<WikiPage>(wikiPages.values()); Collections.sort(pages, WikiPageSort.MODTIME_ORDER); - StringBuilder sb = new StringBuilder(); + StringBuilder sb = new StringBuilder(512); int counter = 0; + Formatter f = Formatter.getInstance(locale); + UserManager userManager = CoreSpringFactory.getImpl(UserManager.class); + for (Iterator<WikiPage> iter = pages.iterator(); iter.hasNext();) { if (counter > MAX_RESULTS) break; WikiPage page = iter.next(); @@ -295,7 +300,14 @@ public class Wiki implements WikiContainer, Serializable { sb.append(f.formatDateAndTime(new Date(page.getModificationTime()))); sb.append(" Author: "); long author = page.getModifyAuthor(); - if (author != 0) sb.append(BaseSecurityManager.getInstance().loadIdentityByKey(Long.valueOf(page.getModifyAuthor())).getName()); + if (author != 0) { + String authorFullname = userManager.getUserDisplayName(author); + if(StringHelper.containsNonWhitespace(authorFullname)) { + sb.append(" Author: ").append(authorFullname); + } else { + sb.append("???"); + } + } sb.append("\n"); counter++; } diff --git a/src/main/java/org/olat/repository/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/repository/_i18n/LocalStrings_de.properties index 5a7826e4d712452d84ddad29024b5c96b5862978..6c253d1cf0054d81f1deb8096e106bb789dc7d7b 100644 --- a/src/main/java/org/olat/repository/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/repository/_i18n/LocalStrings_de.properties @@ -327,6 +327,7 @@ details.owner=Besitzer der Lernressource details.personal=Meine Daten details.referenceinfo=Referenzen details.referenceinfoheader=Information zur Verwendung +details.referenceinfo.txt=Wird in folgenden Kursen eingesetzt: details.summaryprop=Zusammenfassung Einstellungen details.typeinfoheader=Typenspezifische Information dialog.modal.bg.leave.text=$org.olat.group.ui.main\:dialog.modal.bg.leave.text diff --git a/src/main/java/org/olat/repository/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/repository/_i18n/LocalStrings_en.properties index f9a7a2e376c05f225989305455caffd45a6d59c8..08e73352a6a6ac3ebc4f66a05facea509161f292 100644 --- a/src/main/java/org/olat/repository/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/repository/_i18n/LocalStrings_en.properties @@ -326,6 +326,7 @@ details.owner=Owner of learning resource details.personal=My data details.referenceinfo=References details.referenceinfoheader=Information on usage +details.referenceinfo.txt=Used in the following courses: details.summaryprop=Summary of settings details.typeinfoheader=Type-specific information dialog.confirm.delete=Do you really want to delete this learning resource? It is used by <strong>{0} users</strong> at the moment. diff --git a/src/main/java/org/olat/repository/_i18n/LocalStrings_fr.properties b/src/main/java/org/olat/repository/_i18n/LocalStrings_fr.properties index af697a8429c9c76a0dde21b1078b679da2d85e37..4f9e7ff2d4f7dfd47999fe893726469b0b1e6b53 100644 --- a/src/main/java/org/olat/repository/_i18n/LocalStrings_fr.properties +++ b/src/main/java/org/olat/repository/_i18n/LocalStrings_fr.properties @@ -325,6 +325,7 @@ details.owner=Propri\u00E9taire d'une ressource didactique details.personal=Mes donn\u00E9es details.referenceinfo=R\u00E9f\u00E9rences details.referenceinfoheader=Informations sur l'application +details.referenceinfo.txt=Utilis\u00E9 par les cours suivants: details.summaryprop=R\u00E9sum\u00E9 r\u00E9glages details.typeinfoheader=Informations typologiques dialog.confirm.delete=Voulez-vous vraiment supprimer cette ressource didactique? Cette ressource est actuellement utilis\u00E9e par <strong>{0} utilisateurs</strong>. diff --git a/src/main/java/org/olat/repository/ui/list/_content/details.html b/src/main/java/org/olat/repository/ui/list/_content/details.html index 3eaf0b4bad0634445b1a4268faee53e204209eaf..68a5d7059cbc3b234db9757bb9bc52c3dc4bb143 100644 --- a/src/main/java/org/olat/repository/ui/list/_content/details.html +++ b/src/main/java/org/olat/repository/ui/list/_content/details.html @@ -281,7 +281,7 @@ <table class="table table-condensed table-striped"><tbody> <tr> <th>$r.translate("details.referenceinfo")</th> - <td> + <td>$r.translate("details.referenceinfo.txt") #foreach($referenceLink in $referenceLinks) #if($velocityCount != 1), #end$r.render($referenceLink) #end diff --git a/src/main/java/org/olat/shibboleth/ShibbolethAdminController.java b/src/main/java/org/olat/shibboleth/ShibbolethAdminController.java index a4b3803f5381fe28995b6dabb0149eb1a40cea45..f3350f0c152cd011bff68ac1ce2e40b624c1750f 100644 --- a/src/main/java/org/olat/shibboleth/ShibbolethAdminController.java +++ b/src/main/java/org/olat/shibboleth/ShibbolethAdminController.java @@ -29,6 +29,7 @@ import org.olat.core.gui.components.form.flexible.impl.FormEvent; import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer; import org.olat.core.gui.control.Controller; import org.olat.core.gui.control.WindowControl; +import org.olat.core.helpers.Settings; import org.springframework.beans.factory.annotation.Autowired; /** @@ -58,6 +59,8 @@ public class ShibbolethAdminController extends FormBasicController { setFormTitle("admin.title"); setFormDescription("admin.description"); + uifactory.addStaticTextElement("admin.ac.url", Settings.getServerContextPathURI() + "/shib/", formLayout); + boolean enabled = shibbolethModule.isAccessControlByAttributes(); String[] values = new String[]{ translate("enabled") }; attributeEl = uifactory.addCheckboxesHorizontal("admin.ac.attribute", formLayout, keys, values); diff --git a/src/main/java/org/olat/shibboleth/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/shibboleth/_i18n/LocalStrings_de.properties index 350aae88b6a5d3f84c4a2ae7c523639c974a71db..d993812021716c5e760d037d9f46010875f0de13 100644 --- a/src/main/java/org/olat/shibboleth/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/shibboleth/_i18n/LocalStrings_de.properties @@ -1,12 +1,13 @@ #Fri Oct 09 12:13:13 CEST 2009 -admin.ac.attribute=Activate attribute access control -admin.ac.attribute.1=Shibboleth attribute 1 -admin.ac.attribute.2=Shibboleth attribute 2 -admin.ac.value.1=Allowed values -admin.ac.value.2=Allowed values -admin.description=Use the form below... +admin.ac.attribute=Attribute Access Control aktivieren +admin.ac.attribute.1=Shibboleth Attribut 1 +admin.ac.attribute.2=Shibboleth Attribut 2 +admin.ac.url=Direktlink für SSO Zugang +admin.ac.value.1=Gültige Werte für Attribut 1 +admin.ac.value.2=Gültige Werte für Attribut 2 +admin.description=Die Shibboleth Basiskonfiguration muss in der OpenOLAT Konfigurationsdatei vorgenommen werden. Mittels des untenstehenden Formulars können Sie die optionale Autorisierung der Benutzer mitels Prüfung von Shibboleth Attributen einschalten. Es können maximal zwei Attribute mit den jeweils zugelassenen Werten festgelegt werden. Eine Person gilt als Autorisiert wenn 1) die Person erfolgreich sich bei seinem IDP authentifizieren kann und 2) eines der beiden konfigurierten Attribute in der jeweiligen Werteliste gefunden werden kann. admin.menu.shibboleth=Shibboleth -admin.menu.shibboleth.desc=Shibboleth +admin.menu.shibboleth.desc=Konfiguration Shibboleth Modul admin.title=Shibboleth auhorization authentication.provider.description=Sind Sie Mitglied einer Institution mit Shibboleth Loginverfahren? authentication.provider.linkText=Anmelden mit Shibboleth Konto diff --git a/src/main/java/org/olat/shibboleth/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/shibboleth/_i18n/LocalStrings_en.properties index 6ba28acbfc7a977ad237a36900dc073b78669000..23c57f11cc84a99b20206fd10d17388da2b418ea 100644 --- a/src/main/java/org/olat/shibboleth/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/shibboleth/_i18n/LocalStrings_en.properties @@ -2,11 +2,12 @@ admin.ac.attribute=Activate attribute access control admin.ac.attribute.1=Shibboleth attribute 1 admin.ac.attribute.2=Shibboleth attribute 2 -admin.ac.value.1=Allowed values -admin.ac.value.2=Allowed values -admin.description=Use the forms below to configure optional authorization rules. this is usefull when you want to restrict access to this OpenOLAT resource provider based on Shibboleth attributes because not all users which successfully authenticated against your IDP are allowed to access this resource. If one of the rule below matches, the user is authorized to access this OpenOLAT installation (OR conjunction). If the user does not have the necessary attributes, access is denied. +admin.ac.url=Direct link for SSO access +admin.ac.value.1=Allowed values for attribute 1 +admin.ac.value.2=Allowed values for attribute 2 +admin.description=Use the forms below to configure optional authorization rules. This is usefull when you want to restrict access to this OpenOLAT resource provider based on Shibboleth attributes because not all users which successfully authenticated against your IDP are allowed to access this resource. If one of the rule below matches, the user is authorized to access this OpenOLAT installation (OR conjunction). If the user does not have the necessary attributes, access is denied. Each value on the value list is treated as a valid value (no multi-line values). admin.menu.shibboleth=Shibboleth -admin.menu.shibboleth.desc=Shibboleth +admin.menu.shibboleth.desc=Configuration Shibboleth module admin.title=Shibboleth auhorization authentication.provider.description=Are you a member of an institutions that uses Shibboleth authentication? authentication.provider.linkText=Login with Shibboleth account diff --git a/src/test/java/org/olat/core/util/EncoderTest.java b/src/test/java/org/olat/core/util/EncoderTest.java index 8a26b9762da9283eaf8845d657e1449c376fafde..de7bb013083623cce7800e11154533b5798cb43d 100644 --- a/src/test/java/org/olat/core/util/EncoderTest.java +++ b/src/test/java/org/olat/core/util/EncoderTest.java @@ -21,6 +21,8 @@ package org.olat.core.util; import java.util.HashSet; import java.util.Set; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; import org.junit.Assert; import org.junit.Test; @@ -102,4 +104,151 @@ public class EncoderTest { String shaOutput = Encoder.encrypt(password, salt, Algorithm.pbkdf2); Assert.assertEquals(hash, shaOutput); } + + @Test + public void concurrentMD5() { + final CountDownLatch finishCount = new CountDownLatch(14); + + EncryptMD5Runner[] encrypters = new EncryptMD5Runner[]{ + new EncryptMD5Runner("openolat#19","lji1/YS8rEwv/ML0JUV2OQ==","67895d9df8d2b3ba011c29e482e89caa", finishCount), + new EncryptMD5Runner("secret#19","zewurtuiozu","e1a9c2c4b2cdc94a1c65aa61e6a098f4", finishCount), + new EncryptMD5Runner("secret?0","9c0ff19b-4b62-4428-8fc3-08c27d9a1328","6f15a2cce20d5ee894e62dfdbc672ba2", finishCount), + new EncryptMD5Runner("secret?1","b07ff38f-6c1f-4c46-a274-12b1d5954f89","931611fa5f1163f81965ba81ebedf7a9", finishCount), + new EncryptMD5Runner("secret?2","00f8dcf6-61ee-4a47-ad95-7b0055740b2c","1c144f086be2d9001ee3fe5d431a6720", finishCount), + new EncryptMD5Runner("secret?3","32f6c9bb-b19a-422e-bb0c-51b4fd1f0196","fb0ab614ea8f448c21d58454c8683c4a", finishCount), + new EncryptMD5Runner("secret?4","e5d993ed-83a5-4521-89e0-a83c1fb7b80d","a383d8089a3cf28f5b89c7262d9237f2", finishCount), + new EncryptMD5Runner("secret?5","aabe7399-920d-4ba0-8d60-9d7ff52c272f","21db34962517f47da0fe2dd49c0027fd", finishCount), + new EncryptMD5Runner("weak?secret#0","c447d1e5-15f6-4be4-a3c8-dc673a0524c7","474c42e67c23a3cddea5519ffdf82d75", finishCount), + new EncryptMD5Runner("weak?secret#1","e1296d99-b3b4-4de6-9f9b-19fdad6e52a1","19b8d7083b0cfd1e52d647ec9ae037c8", finishCount), + new EncryptMD5Runner("weak?secret#2","3d50300e-f53e-46c0-8cd9-fa9ea1dc4887","d7cde9946e7de02e7814952829d84ca6", finishCount), + new EncryptMD5Runner("weak?secret#3","c0f6f257-8c57-4da3-b10b-5bf65bd2037e","970355c4d8f52905367a43121f062e83", finishCount), + new EncryptMD5Runner("weak?secret#4","f547c7c2-e6ae-4545-945d-99d8cb415518","70abf0cf3d63c0b96b676098bd19856d", finishCount), + new EncryptMD5Runner("weak?secret#5","b8d63092-acd5-4824-858a-200162dea348","0fa562df8d25070d9867ebeae51e24d7", finishCount), + }; + + for(int i=0; i<14; i++) { + encrypters[i].start(); + } + + try { + finishCount.await(10, TimeUnit.SECONDS); + } catch (InterruptedException e) { + Assert.fail("Threads did not finish in 10sec"); + } + + for(int i=0; i<14; i++) { + Assert.assertEquals(encrypters[i].getReference(), encrypters[i].getHash()); + } + } + + private static class EncryptMD5Runner extends Thread { + + private final CountDownLatch countDown; + private final String password; + private final String salt; + private final String reference; + private String hash; + + public EncryptMD5Runner(String password, String salt, String reference, CountDownLatch countDown) { + this.salt = salt; + this.password = password; + this.reference = reference; + this.countDown = countDown; + } + + public String getReference() { + return reference; + } + + public String getHash() { + return hash; + } + + @Override + public void run() { + try { + Thread.sleep(20); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + hash = Encoder.encrypt(password, salt, Algorithm.md5); + countDown.countDown(); + } + } + + @Test + public void concurrentPBKDF2() { + + final CountDownLatch finishCount = new CountDownLatch(15); + + EncryptPBKDF2Runner[] encrypters = new EncryptPBKDF2Runner[]{ + new EncryptPBKDF2Runner("openolat#19", "lji1/YS8rEwv/ML0JUV2OQ==", "kUUaki27mueSEkrFeAFQoLfs1k8=", finishCount), + new EncryptPBKDF2Runner("secret#19", "zewurtuiozu", "6ZE0hrBsbs4gNMnrSSQS6TbzpuI=", finishCount), + new EncryptPBKDF2Runner("top-secret", "clearance", "JgRVHHHT29PKlwuhSbQVczBChkY=", finishCount), + new EncryptPBKDF2Runner("secret?0","fb79944a-ab0e-45ad-be1c-0eb50a2f3031","xiZw1itsZGXdFO3AeUg8Du9gbSk=", finishCount), + new EncryptPBKDF2Runner("secret?1","a78872be-5798-4120-9577-98f79f993ebc","qLtZfhh2OSGB71qOT1QoK+e3VCY=", finishCount), + new EncryptPBKDF2Runner("secret?2","94136eb8-99ac-41ab-9bc6-a534ea390492","EnygPiljxgxQPUAo+k4kPTnSEYk=", finishCount), + new EncryptPBKDF2Runner("secret?3","800617f4-80cf-4e92-a0fd-06ca8c83e5a3","osUdlhRDuGKhrtlDk7GqpmRC5rk=", finishCount), + new EncryptPBKDF2Runner("secret?4","cc4aa953-1f13-4370-a803-9388a68aa868","pa/qNiKj8SMP9bTMwZ3M7kzeFuo=", finishCount), + new EncryptPBKDF2Runner("secret?5","83634a3b-fc3d-4dc5-94c4-684063833912","ZJLaMn4xyRvieyK+xYeH4dvzI7Q=", finishCount), + new EncryptPBKDF2Runner("more?secret#0","aaade6df-3319-416d-b405-a6299cc84970","pOV6OT1aVo6oMKJj81UL5wcnpH0=", finishCount), + new EncryptPBKDF2Runner("more?secret#1","d7bc525f-5bf6-49f8-bc51-18606873de9f","isxOvmuPRk8jwwnAaUfKIyMDddc=", finishCount), + new EncryptPBKDF2Runner("more?secret#2","c9638cc5-4e27-497a-8f1d-75d0937459a7","gYSvlDb/p6bxWfVGsKTpfMQR/Ik=", finishCount), + new EncryptPBKDF2Runner("more?secret#3","69d730e1-678f-4ed0-8b98-47f098e0a200","5CPd6B0aXJB5qJoYiCe6xJZW7ug=", finishCount), + new EncryptPBKDF2Runner("more?secret#4","c0ed1714-43b7-46c8-8816-7f72ef0b9d3b","oq1tqJxIlQiKGbluqRzCAKEoxhY=", finishCount), + new EncryptPBKDF2Runner("more?secret#5","5e29f2e9-6bf2-4998-9d21-01f69f12533f","Asz/tXx96d+fIafd4ZSlEPHkay8=", finishCount) + }; + + for(int i=0; i<15; i++) { + encrypters[i].start(); + } + + try { + + finishCount.await(10, TimeUnit.SECONDS); + } catch (InterruptedException e) { + Assert.fail("Threads did not finish in 10sec"); + } + + for(int i=0; i<15; i++) { + Assert.assertEquals(encrypters[i].getReference(), encrypters[i].getHash()); + } + } + + private static class EncryptPBKDF2Runner extends Thread { + + private final CountDownLatch countDown; + private final String password; + private final String salt; + private final String reference; + private String hash; + + public EncryptPBKDF2Runner(String password, String salt, String reference, CountDownLatch countDown) { + this.salt = salt; + this.password = password; + this.reference = reference; + this.countDown = countDown; + } + + public String getReference() { + return reference; + } + + public String getHash() { + return hash; + } + + @Override + public void run() { + try { + Thread.sleep(20); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + hash = Encoder.encrypt(password, salt, Algorithm.pbkdf2); + countDown.countDown(); + } + } }