From 8ed39156072655c3aeba3e880b18d8c7e5c39254 Mon Sep 17 00:00:00 2001 From: srosse <none@none> Date: Mon, 22 Dec 2014 12:15:42 +0100 Subject: [PATCH] OO-1349: implements guards (ip and safe exam browser), update and setup SQL scripts --- pom.xml | 2 + .../fullWebApp/BaseFullWebappController.java | 2 +- src/main/java/org/olat/core/util/Encoder.java | 60 +++++---- src/main/java/org/olat/core/util/IPUtils.java | 104 +++++++++++++++ .../org/olat/core/util/event/EventAgency.java | 52 ++++---- .../course/assessment/AssessmentMode.java | 6 + .../assessment/AssessmentModeManager.java | 22 ++++ .../manager/AssessmentModeManagerImpl.java | 52 ++++++++ .../assessment/manager/IPRangeChecker.java | 63 ---------- .../assessment/model/AssessmentModeImpl.java | 26 +++- .../model/TransientAssessmentMode.java | 54 ++++++++ .../ui/AssessmentModeEditController.java | 46 ++++++- ...essmentModeUserConfirmationController.java | 119 ++++++++++++++++-- .../ui/ChooseStartElementController.java | 101 +++++++++++++++ .../assessment/ui/_content/choose_mode.html | 16 ++- .../ui/_content/course_element.html | 4 + .../ui/_i18n/LocalStrings_de.properties | 8 +- .../database/mysql/alter_10_1_0_to_10_2_0.sql | 2 + .../database/mysql/setupDatabase.sql | 50 ++++++++ .../oracle/alter_10_1_0_to_10_2_0.sql | 51 ++++++++ .../database/oracle/setupDatabase.sql | 54 +++++++- .../postgresql/alter_10_1_0_to_10_2_0.sql | 51 ++++++++ .../database/postgresql/setupDatabase.sql | 52 ++++++++ .../java/org/olat/core/util/IPUtilsTest.java | 41 ++++++ .../manager/AssessmentModeManagerTest.java | 35 ++++++ .../java/org/olat/test/AllTestsJunit4.java | 1 + 26 files changed, 937 insertions(+), 137 deletions(-) create mode 100644 src/main/java/org/olat/core/util/IPUtils.java delete mode 100644 src/main/java/org/olat/course/assessment/manager/IPRangeChecker.java create mode 100644 src/main/java/org/olat/course/assessment/ui/ChooseStartElementController.java create mode 100644 src/main/java/org/olat/course/assessment/ui/_content/course_element.html create mode 100644 src/main/resources/database/oracle/alter_10_1_0_to_10_2_0.sql create mode 100644 src/main/resources/database/postgresql/alter_10_1_0_to_10_2_0.sql create mode 100644 src/test/java/org/olat/core/util/IPUtilsTest.java diff --git a/pom.xml b/pom.xml index 1010e476179..6e1bf36c5ee 100644 --- a/pom.xml +++ b/pom.xml @@ -1649,6 +1649,7 @@ <version>1.4</version> </dependency> <dependency> + <!-- Used by at least velocity engine --> <groupId>commons-collections</groupId> <artifactId>commons-collections</artifactId> <version>3.2.1</version> @@ -1933,6 +1934,7 @@ <version>4.3.3</version> </dependency> <dependency> + <!-- Used by at least ical4j, velocity, basiclti --> <groupId>commons-lang</groupId> <artifactId>commons-lang</artifactId> <version>2.6</version> diff --git a/src/main/java/org/olat/core/commons/fullWebApp/BaseFullWebappController.java b/src/main/java/org/olat/core/commons/fullWebApp/BaseFullWebappController.java index eaa273f4eec..10e99394f95 100644 --- a/src/main/java/org/olat/core/commons/fullWebApp/BaseFullWebappController.java +++ b/src/main/java/org/olat/core/commons/fullWebApp/BaseFullWebappController.java @@ -1195,7 +1195,7 @@ public class BaseFullWebappController extends BasicController implements ChiefCo updateStickyMessage(); } else if (AssessmentModeNotificationEvent.command.equals(event.getCommand()) && event instanceof AssessmentModeNotificationEvent) { - if(lockResource == null) { + if(lockResource == null && getIdentity() != null) { AssessmentModeNotificationEvent amne = (AssessmentModeNotificationEvent)event; if(amne.getAssessedIdentityKeys().contains(getIdentity().getKey())) { asyncLockResource(amne.getAssessementMode()); diff --git a/src/main/java/org/olat/core/util/Encoder.java b/src/main/java/org/olat/core/util/Encoder.java index 36b5ed51664..59e88a10a7c 100644 --- a/src/main/java/org/olat/core/util/Encoder.java +++ b/src/main/java/org/olat/core/util/Encoder.java @@ -57,7 +57,8 @@ public class Encoder { sha1("SHA-1", 100, true), sha256("SHA-256", 100, true), sha512("SHA-512", 100, true), - pbkdf2("PBKDF2WithHmacSHA1", 20000, true); + pbkdf2("PBKDF2WithHmacSHA1", 20000, true), + sha256Exam("SHA-256", 1, false); private final boolean salted; private final int iterations; @@ -108,6 +109,23 @@ public class Encoder { return md5(s, null); } + public static String sha256Exam(String s) { + try { + String HEXES = "0123456789abcdef"; + + java.security.MessageDigest md = java.security.MessageDigest.getInstance("SHA-256"); + byte[] array = md.digest(s.getBytes("UTF-8")); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < array.length; ++i) { + sb.append(HEXES.charAt((array[i] & 0xF0) >> 4)).append(HEXES.charAt((array[i] & 0x0F))); + } + return sb.toString(); + } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) { + log.error("", e); + return null; + } + } + public static String encrypt(String s, String salt, Algorithm algorithm) { switch(algorithm) { case md5: return md5(s, salt); @@ -149,10 +167,7 @@ public class Encoder { input = digest.digest(input); } return byteToBase64(input); - } catch (NoSuchAlgorithmException e) { - log.error("", e); - return null; - } catch (UnsupportedEncodingException e) { + } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) { log.error("", e); return null; } @@ -163,10 +178,7 @@ public class Encoder { KeySpec spec = new PBEKeySpec(password.toCharArray(), salt.getBytes(), algorithm.getIterations(), 160); SecretKeyFactory f = SecretKeyFactory.getInstance(algorithm.getAlgorithm()); return byteToBase64(f.generateSecret(spec).getEncoded()); - } catch (NoSuchAlgorithmException e) { - log.error("", e); - return null; - } catch (InvalidKeySpecException e) { + } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { log.error("", e); return null; } @@ -174,21 +186,21 @@ public class Encoder { public static String getSalt() { try { - //Always use a SecureRandom generator - SecureRandom sr = SecureRandom.getInstance("SHA1PRNG"); - //Create array for salt - byte[] salt = new byte[16]; - //Get a random salt - sr.nextBytes(salt); - //return salt - return byteToBase64(salt); - } catch (NoSuchAlgorithmException e) { - log.error("", e); - return null; - } + //Always use a SecureRandom generator + SecureRandom sr = SecureRandom.getInstance("SHA1PRNG"); + //Create array for salt + byte[] salt = new byte[16]; + //Get a random salt + sr.nextBytes(salt); + //return salt + return byteToBase64(salt); + } catch (NoSuchAlgorithmException e) { + log.error("", e); + return null; + } } - public static String byteToBase64(byte[] data){ - return StringHelper.encodeBase64(data); - } + public static String byteToBase64(byte[] data){ + return StringHelper.encodeBase64(data); + } } \ No newline at end of file diff --git a/src/main/java/org/olat/core/util/IPUtils.java b/src/main/java/org/olat/core/util/IPUtils.java new file mode 100644 index 00000000000..bc916fb7a7b --- /dev/null +++ b/src/main/java/org/olat/core/util/IPUtils.java @@ -0,0 +1,104 @@ +/** + * <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.core.util; + +import org.apache.commons.net.util.SubnetUtils; + + +/** + * + * Thanks: https://gist.github.com/madan712/6651967 + * + * It's based of the InetAddresses clas from guava too and + * prevent a DNS lookup of java.net.InetAddress + * + * Initial date: 18.12.2014<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class IPUtils { + + public static long ipToLong(byte[] octets) { + long result = 0; + for (byte octet : octets) { + result <<= 8; + result |= octet & 0xff; + } + return result; + } + + public static boolean isValidRange(String ipWithMask, String ipToCheck) { + boolean allOk = false; + if(ipWithMask.indexOf("/") > 0) { + SubnetUtils utils = new SubnetUtils(ipWithMask); + allOk = utils.getInfo().isInRange(ipToCheck); + } + return allOk; + } + + /** + * + * @param ipStart + * @param ipEnd + * @param ipToCheck + * @return + */ + public static boolean isValidRange(String ipStart, String ipEnd, String ipToCheck) { + try { + long ipLo = ipToLong(textToNumericFormatV4(ipStart)); + long ipHi = ipToLong(textToNumericFormatV4(ipEnd)); + long ipToTest = ipToLong(textToNumericFormatV4(ipToCheck)); + return (ipToTest >= ipLo && ipToTest <= ipHi); + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + private static byte[] textToNumericFormatV4(String ipString) { + String[] address = ipString.split("\\.", 5); + if (address.length != 4) { + return null; + } + + byte[] bytes = new byte[4]; + try { + for (int i = 0; i < bytes.length; i++) { + bytes[i] = parseOctet(address[i]); + } + } catch (NumberFormatException ex) { + return null; + } + + return bytes; + } + + private static byte parseOctet(String ipPart) { + // Note: we already verified that this string contains only hex digits. + int octet = Integer.parseInt(ipPart); + // Disallow leading zeroes, because no clear standard exists on + // whether these should be interpreted as decimal or octal. + if (octet > 255 || (ipPart.startsWith("0") && ipPart.length() > 1)) { + throw new NumberFormatException(); + } + return (byte) octet; + } +} + diff --git a/src/main/java/org/olat/core/util/event/EventAgency.java b/src/main/java/org/olat/core/util/event/EventAgency.java index e69f7e5e044..6019d8abd78 100644 --- a/src/main/java/org/olat/core/util/event/EventAgency.java +++ b/src/main/java/org/olat/core/util/event/EventAgency.java @@ -89,43 +89,35 @@ class EventAgency { // -> avoid dead lock (see OLAT-3681) //no sync during firing to listeners (potentially "long" taking - although recommendation is to keep event methods short. - for (int i = 0; i < liArr.length; i++) { - try { - final GenericEventListener listener = liArr[i]; - - //make sure GenericEvents are only sent when controller is not yet disposed - if (listener instanceof Controller) { - Controller dCtrl = (Controller)listener; - if (!dCtrl.isDisposed()) { - ThreadLocalUserActivityLoggerInstaller.runWithUserActivityLogger(new Runnable() { - public void run() { - listener.event(event); - } - }, UserActivityLoggerImpl.newLoggerForEventBus(dCtrl)); - } - } else { - if(log.isDebug()){ - log.debug("fireEvent: Non-Controller: "+listener); - } - //is there a need to differ the events sent on one VM and in cluster mode? + for (int i = 0; i < liArr.length; i++) { + try { + final GenericEventListener listener = liArr[i]; + + //make sure GenericEvents are only sent when controller is not yet disposed + if (listener instanceof Controller) { + Controller dCtrl = (Controller)listener; + if (!dCtrl.isDisposed()) { ThreadLocalUserActivityLoggerInstaller.runWithUserActivityLogger(new Runnable() { public void run() { listener.event(event); } - }, ThreadLocalUserActivityLoggerInstaller.createEmptyUserActivityLogger()); + }, UserActivityLoggerImpl.newLoggerForEventBus(dCtrl)); } - } catch (RuntimeException e) { - log.error("Error while sending generic event! Removing listener: "+liArr[i], e); - //don't remove it!!! removeListener(liArr[i]); + } else { + if(log.isDebug()){ + log.debug("fireEvent: Non-Controller: "+listener); + } + //is there a need to differ the events sent on one VM and in cluster mode? + ThreadLocalUserActivityLoggerInstaller.runWithUserActivityLogger(new Runnable() { + public void run() { + listener.event(event); + } + }, ThreadLocalUserActivityLoggerInstaller.createEmptyUserActivityLogger()); } + } catch (RuntimeException e) { + log.error("Error while sending generic event: "+liArr[i], e); } - - // remember the latest event for a while so e.g. controller which link to resources res (link called res') can catch up on missed updates. - /*if (ttl != 0) { - latestEvent = event; - latestEventTimestamp = System.currentTimeMillis(); - }*/ - + } } /** diff --git a/src/main/java/org/olat/course/assessment/AssessmentMode.java b/src/main/java/org/olat/course/assessment/AssessmentMode.java index a9bc07fcf59..3f986ce8d1a 100644 --- a/src/main/java/org/olat/course/assessment/AssessmentMode.java +++ b/src/main/java/org/olat/course/assessment/AssessmentMode.java @@ -49,6 +49,8 @@ public interface AssessmentMode extends ModifiedInfo, CreateInfo { public Date getBegin(); public void setBegin(Date begin); + + public Date getBeginWithLeadTime(); public Date getEnd(); @@ -73,6 +75,10 @@ public interface AssessmentMode extends ModifiedInfo, CreateInfo { public String getElementList(); public void setElementList(String elementList); + + public String getStartElement(); + + public void setStartElement(String startElement); public boolean isRestrictAccessIps(); diff --git a/src/main/java/org/olat/course/assessment/AssessmentModeManager.java b/src/main/java/org/olat/course/assessment/AssessmentModeManager.java index 2074dac9618..4a3226ff724 100644 --- a/src/main/java/org/olat/course/assessment/AssessmentModeManager.java +++ b/src/main/java/org/olat/course/assessment/AssessmentModeManager.java @@ -22,6 +22,8 @@ package org.olat.course.assessment; import java.util.List; import java.util.Set; +import javax.servlet.http.HttpServletRequest; + import org.olat.basesecurity.IdentityRef; import org.olat.group.BusinessGroup; import org.olat.group.area.BGArea; @@ -61,9 +63,29 @@ public interface AssessmentModeManager { */ public List<AssessmentMode> getAssessmentModeFor(IdentityRef identity); + /** + * This return all modes between the begin date minus lead time and end time. + * + * @return The list of modes + */ public List<AssessmentMode> getCurrentAssessmentModes(); public Set<Long> getAssessedIdentityKeys(AssessmentMode assessmentMode); + /** + * + * @param ipList + * @param address + * @return + */ + public boolean isIpAllowed(String ipList, String address); + + /** + * + * @param request + * @param safeExamBrowserKey + * @return + */ + public boolean isSafelyAllowed(HttpServletRequest request, String safeExamBrowserKey); } diff --git a/src/main/java/org/olat/course/assessment/manager/AssessmentModeManagerImpl.java b/src/main/java/org/olat/course/assessment/manager/AssessmentModeManagerImpl.java index 793914f5abe..eab2cc2b36d 100644 --- a/src/main/java/org/olat/course/assessment/manager/AssessmentModeManagerImpl.java +++ b/src/main/java/org/olat/course/assessment/manager/AssessmentModeManagerImpl.java @@ -26,10 +26,16 @@ import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.StringTokenizer; + +import javax.servlet.http.HttpServletRequest; import org.olat.basesecurity.GroupRoles; import org.olat.basesecurity.IdentityRef; import org.olat.core.commons.persistence.DB; +import org.olat.core.util.Encoder; +import org.olat.core.util.IPUtils; +import org.olat.core.util.StringHelper; import org.olat.course.assessment.AssessmentMode; import org.olat.course.assessment.AssessmentMode.Target; import org.olat.course.assessment.AssessmentModeManager; @@ -82,6 +88,18 @@ public class AssessmentModeManagerImpl implements AssessmentModeManager { public AssessmentMode save(AssessmentMode assessmentMode) { AssessmentMode reloadedMode; assessmentMode.setLastModified(new Date()); + + //update begin with lead time + Date begin = assessmentMode.getBegin(); + Calendar cal = Calendar.getInstance(); + cal.setTime(begin); + if(assessmentMode.getLeadTime() > 0) { + cal.add(Calendar.MINUTE, -assessmentMode.getLeadTime()); + } + cal.set(Calendar.SECOND, 0); + cal.set(Calendar.MILLISECOND, 0); + ((AssessmentModeImpl)assessmentMode).setBeginWithLeadTime(cal.getTime()); + if(assessmentMode.getKey() == null) { dbInstance.getCurrentEntityManager().persist(assessmentMode); reloadedMode = assessmentMode; @@ -230,4 +248,38 @@ public class AssessmentModeManagerImpl implements AssessmentModeManager { dbInstance.getCurrentEntityManager().persist(modeToArea); return modeToArea; } + + @Override + public boolean isIpAllowed(String ipList, String address) { + boolean allOk = false; + if(!StringHelper.containsNonWhitespace(ipList)) { + allOk |= true; + } else { + for(StringTokenizer tokenizer = new StringTokenizer(ipList, "\n\r", false); tokenizer.hasMoreTokens(); ) { + String ipRange = tokenizer.nextToken(); + if(StringHelper.containsNonWhitespace(ipRange)) { + int indexMask = ipRange.indexOf("/"); + int indexPseudoRange = ipRange.indexOf("-"); + if(indexMask > 0) { + allOk |= IPUtils.isValidRange(ipRange, address); + } else if(indexPseudoRange > 0) { + String begin = ipRange.substring(0, indexPseudoRange).trim(); + String end = ipRange.substring(indexPseudoRange + 1).trim(); + allOk |= IPUtils.isValidRange(begin, end, address); + } else { + allOk |= ipRange.equals(address); + } + } + } + } + return allOk; + } + + @Override + public boolean isSafelyAllowed(HttpServletRequest request, String safeExamBrowserKey) { + String safeExamHash = request.getHeader("x-safeexambrowser-requesthash"); + String url = request.getRequestURL().toString(); + String hash = Encoder.sha256Exam(url + safeExamBrowserKey); + return safeExamHash != null && safeExamHash.equals(hash); + } } \ No newline at end of file diff --git a/src/main/java/org/olat/course/assessment/manager/IPRangeChecker.java b/src/main/java/org/olat/course/assessment/manager/IPRangeChecker.java deleted file mode 100644 index dec104a2f9d..00000000000 --- a/src/main/java/org/olat/course/assessment/manager/IPRangeChecker.java +++ /dev/null @@ -1,63 +0,0 @@ -/** - * <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.course.assessment.manager; - -import java.net.InetAddress; -import java.net.UnknownHostException; - -/** - * - * Thanks: https://gist.github.com/madan712/6651967 - * - * Initial date: 18.12.2014<br> - * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com - * - */ -public class IPRangeChecker { - - public static long ipToLong(InetAddress ip) { - byte[] octets = ip.getAddress(); - long result = 0; - for (byte octet : octets) { - result <<= 8; - result |= octet & 0xff; - } - return result; - } - - /** - * TODO replace perhaps with guava, jdk make a round trip to the address. - * @param ipStart - * @param ipEnd - * @param ipToCheck - * @return - */ - public static boolean isValidRange(String ipStart, String ipEnd, String ipToCheck) { - try { - long ipLo = ipToLong(InetAddress.getByName(ipStart)); - long ipHi = ipToLong(InetAddress.getByName(ipEnd)); - long ipToTest = ipToLong(InetAddress.getByName(ipToCheck)); - return (ipToTest >= ipLo && ipToTest <= ipHi); - } catch (UnknownHostException e) { - e.printStackTrace(); - return false; - } - } -} diff --git a/src/main/java/org/olat/course/assessment/model/AssessmentModeImpl.java b/src/main/java/org/olat/course/assessment/model/AssessmentModeImpl.java index 2109941c422..8be4f27dfa6 100644 --- a/src/main/java/org/olat/course/assessment/model/AssessmentModeImpl.java +++ b/src/main/java/org/olat/course/assessment/model/AssessmentModeImpl.java @@ -54,7 +54,7 @@ import org.olat.repository.RepositoryEntry; @Table(name="o_as_mode_course") @NamedQueries({ @NamedQuery(name="assessmentModeByRepoEntry", query="select mode from courseassessmentmode mode where mode.repositoryEntry.key=:entryKey"), - @NamedQuery(name="currentAssessmentModes", query="select mode from courseassessmentmode mode where mode.begin<=:now and mode.end>=:now") + @NamedQuery(name="currentAssessmentModes", query="select mode from courseassessmentmode mode where mode.beginWithLeadTime<=:now and mode.end>=:now") }) public class AssessmentModeImpl implements Persistable, AssessmentMode { @@ -86,6 +86,9 @@ public class AssessmentModeImpl implements Persistable, AssessmentMode { private Date end; @Column(name="a_leadtime", nullable=true, insertable=true, updatable=true) private int leadTime; + @Temporal(TemporalType.TIMESTAMP) + @Column(name="a_begin_with_leadtime", nullable=false, insertable=true, updatable=true) + private Date beginWithLeadTime; @Column(name="a_targetaudience", nullable=true, insertable=true, updatable=true) private String targetAudienceString; @@ -100,6 +103,9 @@ public class AssessmentModeImpl implements Persistable, AssessmentMode { private boolean restrictAccessElements; @Column(name="a_elements", nullable=true, insertable=true, updatable=true) private String elementList; + @Column(name="a_start_element", nullable=true, insertable=true, updatable=true) + private String startElement; + @Column(name="a_restrictaccessips", nullable=true, insertable=true, updatable=true) private boolean restrictAccessIps; @@ -199,6 +205,14 @@ public class AssessmentModeImpl implements Persistable, AssessmentMode { this.leadTime = leadTime; } + public Date getBeginWithLeadTime() { + return beginWithLeadTime; + } + + public void setBeginWithLeadTime(Date beginWithLeadTime) { + this.beginWithLeadTime = beginWithLeadTime; + } + @Override public Target getTargetAudience() { return targetAudienceString == null || targetAudienceString.isEmpty() @@ -258,6 +272,16 @@ public class AssessmentModeImpl implements Persistable, AssessmentMode { this.elementList = elementList; } + @Override + public String getStartElement() { + return startElement; + } + + @Override + public void setStartElement(String startElement) { + this.startElement = startElement; + } + @Override public boolean isRestrictAccessIps() { return restrictAccessIps; diff --git a/src/main/java/org/olat/course/assessment/model/TransientAssessmentMode.java b/src/main/java/org/olat/course/assessment/model/TransientAssessmentMode.java index 85b074bdfe0..cdf2fc20fe8 100644 --- a/src/main/java/org/olat/course/assessment/model/TransientAssessmentMode.java +++ b/src/main/java/org/olat/course/assessment/model/TransientAssessmentMode.java @@ -44,8 +44,14 @@ public class TransientAssessmentMode implements Serializable { private String name; private String description; private Date begin; + private Date beginWithLeadTime; private Date end; private int leadTime; + private String startElementKey; + + private String ipList; + private String safeExamBrowserKey; + private String safeExamBrowserHint; public TransientAssessmentMode(AssessmentMode mode) { displayName = mode.getRepositoryEntry().getDisplayname(); @@ -55,8 +61,19 @@ public class TransientAssessmentMode implements Serializable { name = mode.getName(); description = mode.getDescription(); begin = mode.getBegin(); + beginWithLeadTime = mode.getBeginWithLeadTime(); end = mode.getEnd(); leadTime = mode.getLeadTime(); + startElementKey = mode.getStartElement(); + + if(mode.isRestrictAccessIps()) { + ipList = mode.getIpList(); + } + + if(mode.isSafeExamBrowser()) { + safeExamBrowserKey = mode.getSafeExamBrowserKey(); + safeExamBrowserHint = mode.getSafeExamBrowserHint(); + } } public static List<TransientAssessmentMode> create(List<AssessmentMode> modes) { @@ -103,6 +120,30 @@ public class TransientAssessmentMode implements Serializable { this.begin = begin; } + public Date getBeginWithLeadTime() { + return beginWithLeadTime; + } + + public void setBeginWithLeadTime(Date beginWithLeadTime) { + this.beginWithLeadTime = beginWithLeadTime; + } + + public String getSafeExamBrowserKey() { + return safeExamBrowserKey; + } + + public void setSafeExamBrowserKey(String safeExamBrowserKey) { + this.safeExamBrowserKey = safeExamBrowserKey; + } + + public String getSafeExamBrowserHint() { + return safeExamBrowserHint; + } + + public void setSafeExamBrowserHint(String safeExamBrowserHint) { + this.safeExamBrowserHint = safeExamBrowserHint; + } + public Date getEnd() { return end; } @@ -118,4 +159,17 @@ public class TransientAssessmentMode implements Serializable { public void setLeadTime(int leadTime) { this.leadTime = leadTime; } + + public String getStartElementKey() { + return startElementKey; + } + + public void setStartElementKey(String startElementKey) { + this.startElementKey = startElementKey; + } + + public String getIpList() { + return ipList; + } + } \ No newline at end of file diff --git a/src/main/java/org/olat/course/assessment/ui/AssessmentModeEditController.java b/src/main/java/org/olat/course/assessment/ui/AssessmentModeEditController.java index e1ed6caf2f3..eca19748ecd 100644 --- a/src/main/java/org/olat/course/assessment/ui/AssessmentModeEditController.java +++ b/src/main/java/org/olat/course/assessment/ui/AssessmentModeEditController.java @@ -34,6 +34,7 @@ import org.olat.core.gui.components.form.flexible.elements.IntegerElement; import org.olat.core.gui.components.form.flexible.elements.MultipleSelectionElement; import org.olat.core.gui.components.form.flexible.elements.RichTextElement; import org.olat.core.gui.components.form.flexible.elements.SingleSelection; +import org.olat.core.gui.components.form.flexible.elements.StaticTextElement; import org.olat.core.gui.components.form.flexible.elements.TextElement; import org.olat.core.gui.components.form.flexible.impl.FormBasicController; import org.olat.core.gui.components.form.flexible.impl.FormEvent; @@ -78,7 +79,8 @@ public class AssessmentModeEditController extends FormBasicController { private SingleSelection targetEl; private IntegerElement leadTimeEl; private DateChooser beginEl, endEl; - private FormLink chooseGroupsButton, chooseAreasButton, chooseElementsButton; + private StaticTextElement startElementEl; + private FormLink chooseGroupsButton, chooseAreasButton, chooseStartElementButton, chooseElementsButton; private TextElement nameEl, ipListEl, safeExamBrowserKeyEl; private RichTextElement descriptionEl, safeExamBrowserHintEl; private FormLayoutContainer chooseGroupsCont, chooseElementsCont; @@ -88,6 +90,7 @@ public class AssessmentModeEditController extends FormBasicController { private AreaSelectionController areaChooseCtrl; private GroupSelectionController groupChooseCtrl; private ChooseElementsController chooseElementsCtrl; + private ChooseStartElementController chooseStartElementCtrl; private List<Long> areaKeys; private List<String> areaNames; @@ -95,6 +98,7 @@ public class AssessmentModeEditController extends FormBasicController { private List<String> groupNames; private List<String> elementKeys; private List<String> elementNames; + private String startElementKey; private AssessmentMode assessmentMode; private final OLATResourceable courseOres; @@ -231,6 +235,14 @@ public class AssessmentModeEditController extends FormBasicController { chooseElementsButton = uifactory.addFormLink("choose.elements", chooseElementsCont, Link.BUTTON); + startElementKey = assessmentMode.getStartElement(); + String startElementName = ""; + if(StringHelper.containsNonWhitespace(startElementKey)) { + startElementName = getCourseNodeName(startElementKey, treeModel); + } + startElementEl = uifactory.addStaticTextElement("mode.start.element", "mode.start.element", startElementName, formLayout); + chooseStartElementButton = uifactory.addFormLink("choose.start.element", formLayout, Link.BUTTON); + //ips ipsEl = uifactory.addCheckboxesHorizontal("ips", "mode.ips", formLayout, onKeys, onValues); ipsEl.select(onKeys[0], assessmentMode.isRestrictAccessIps()); @@ -297,6 +309,14 @@ public class AssessmentModeEditController extends FormBasicController { } cmc.deactivate(); cleanUp(); + } else if(chooseStartElementCtrl == source) { + if(Event.DONE_EVENT == event || Event.CHANGED_EVENT == event) { + startElementKey = chooseStartElementCtrl.getSelectedKey(); + String elementName = chooseStartElementCtrl.getSelectedName(); + startElementEl.setValue(elementName); + } + cmc.deactivate(); + cleanUp(); } else if(cmc == source) { cmc.deactivate(); } @@ -304,10 +324,12 @@ public class AssessmentModeEditController extends FormBasicController { } private void cleanUp() { + removeAsListenerAndDispose(chooseStartElementCtrl); removeAsListenerAndDispose(chooseElementsCtrl); removeAsListenerAndDispose(groupChooseCtrl); removeAsListenerAndDispose(areaChooseCtrl); removeAsListenerAndDispose(cmc); + chooseStartElementCtrl = null; chooseElementsCtrl = null; groupChooseCtrl = null; areaChooseCtrl = null; @@ -375,6 +397,12 @@ public class AssessmentModeEditController extends FormBasicController { assessmentMode.setElementList(null); } + if(StringHelper.containsNonWhitespace(startElementKey)) { + assessmentMode.setStartElement(startElementKey); + } else { + assessmentMode.setStartElement(null); + } + boolean ipRestriction = ipsEl.isAtLeastSelected(1); assessmentMode.setRestrictAccessIps(ipRestriction); if(ipRestriction) { @@ -471,6 +499,8 @@ public class AssessmentModeEditController extends FormBasicController { doChooseGroups(ureq); } else if(chooseElementsButton == source) { doChooseElements(ureq); + } else if(chooseStartElementButton == source) { + doChooseStartElement(ureq); } super.formInnerEvent(ureq, source, event); @@ -488,7 +518,19 @@ public class AssessmentModeEditController extends FormBasicController { listenTo(chooseElementsCtrl); cmc = new CloseableModalController(getWindowControl(), "close", chooseElementsCtrl.getInitialComponent(), - true, getTranslator().translate("popup.chooseareas")); + true, getTranslator().translate("popup.chooseelements")); + listenTo(cmc); + cmc.activate(); + } + + private void doChooseStartElement(UserRequest ureq) { + if(chooseElementsCtrl != null) return; + + chooseStartElementCtrl = new ChooseStartElementController(ureq, getWindowControl(), startElementKey, courseOres); + listenTo(chooseStartElementCtrl); + + cmc = new CloseableModalController(getWindowControl(), "close", chooseStartElementCtrl.getInitialComponent(), + true, getTranslator().translate("popup.choosestartelement")); listenTo(cmc); cmc.activate(); } diff --git a/src/main/java/org/olat/course/assessment/ui/AssessmentModeUserConfirmationController.java b/src/main/java/org/olat/course/assessment/ui/AssessmentModeUserConfirmationController.java index 5dd63332efb..a9b1a2259fd 100644 --- a/src/main/java/org/olat/course/assessment/ui/AssessmentModeUserConfirmationController.java +++ b/src/main/java/org/olat/course/assessment/ui/AssessmentModeUserConfirmationController.java @@ -20,6 +20,7 @@ package org.olat.course.assessment.ui; import java.util.ArrayList; +import java.util.Date; import java.util.List; import java.util.Locale; @@ -38,7 +39,10 @@ import org.olat.core.gui.control.generic.closablewrapper.CloseableModalControlle import org.olat.core.id.OLATResourceable; import org.olat.core.util.CodeHelper; import org.olat.core.util.Formatter; +import org.olat.core.util.StringHelper; +import org.olat.course.assessment.AssessmentModeManager; import org.olat.course.assessment.model.TransientAssessmentMode; +import org.springframework.beans.factory.annotation.Autowired; /** * @@ -48,29 +52,90 @@ import org.olat.course.assessment.model.TransientAssessmentMode; */ public class AssessmentModeUserConfirmationController extends BasicController { + private final VelocityContainer mainVC; private final CloseableModalController cmc; + private final String address; + + @Autowired + private AssessmentModeManager assessmentmodeMgr; + public AssessmentModeUserConfirmationController(UserRequest ureq, WindowControl wControl, List<TransientAssessmentMode> modes) { super(ureq, wControl); putInitialPanel(new Panel("assessment-mode-chooser")); + + System.out.println("ureq: " + ureq); + System.out.println("hreq: " + ureq.getHttpReq()); + address = ureq.getHttpReq().getRemoteAddr(); - VelocityContainer mainVC = createVelocityContainer("choose_mode"); + mainVC = createVelocityContainer("choose_mode"); List<Mode> modeWrappers = new ArrayList<Mode>(); for(TransientAssessmentMode mode:modes) { - String name = "go-" + CodeHelper.getRAMUniqueID(); - Link button = LinkFactory.createCustomLink(name, "go", "current.mode.start", Link.BUTTON, mainVC, this); - button.setUserObject(mode); - - Mode wrapper = new Mode(button, mode, getLocale()); - modeWrappers.add(wrapper); + Mode wrapper = initAssessmentMode(ureq, mode); + if(wrapper != null) { + modeWrappers.add(wrapper); + } } mainVC.contextPut("modeWrappers", modeWrappers); + //check cmc = new CloseableModalController(getWindowControl(), translate("close"), mainVC, true, translate("current.mode"), false); cmc.activate(); listenTo(cmc); } + private Mode initAssessmentMode(UserRequest ureq, TransientAssessmentMode mode) { + Date now = new Date(); + + Status state = null; + + Date beginWithLeadTime = mode.getBeginWithLeadTime(); + Date begin = mode.getBegin(); + Date end = mode.getEnd(); + + if(begin.after(now)) { + return null; + } else if(beginWithLeadTime.before(now) && begin.after(now)) { + state = Status.wait; + } else if(begin.before(now) && end.after(now)) { + state = Status.allowed; + } else if(end.before(now)) { + return null; + } + + StringBuilder sb = new StringBuilder(); + + boolean allowed = true; + if(mode.getIpList() != null) { + boolean ipInRange = assessmentmodeMgr.isIpAllowed(mode.getIpList(), address); + if(!ipInRange) { + sb.append(translate("error.ip.range")); + } + allowed &= ipInRange; + } + if(mode.getSafeExamBrowserKey() != null) { + boolean safeExamCheck = assessmentmodeMgr.isSafelyAllowed(ureq.getHttpReq(), mode.getSafeExamBrowserKey()); + if(!safeExamCheck) { + if(sb.length() > 0) sb.append("<br />"); + sb.append(translate("error.safe.exam")); + } + allowed &= safeExamCheck; + } + + Link button = null; + if(allowed) { + String name = "go-" + CodeHelper.getRAMUniqueID(); + button = LinkFactory.createCustomLink(name, "go", "current.mode.start", Link.BUTTON, mainVC, this); + button.setCustomEnabledLinkCSS("btn btn-primary"); + button.setUserObject(mode); + button.setEnabled(state == Status.allowed); + } else { + state = Status.refused; + } + + return new Mode(button, state.name(), sb.toString(), mode, getLocale()); + } + @Override protected void doDispose() { // @@ -100,19 +165,33 @@ public class AssessmentModeUserConfirmationController extends BasicController { Windows.getWindows(ureq).getChiefController().lockResource(resource); fireEvent(ureq, new ChooseAssessmentModeEvent(mode)); - String businessPath = "[RepositoryEntry:" + mode.getRepositoryEntryKey() + "]"; //TODO node + String businessPath = "[RepositoryEntry:" + mode.getRepositoryEntryKey() + "]"; + if(StringHelper.containsNonWhitespace(mode.getStartElementKey())) { + businessPath += "[CourseNode:" + mode.getStartElementKey() + "]"; + } NewControllerFactory.getInstance().launch(businessPath, ureq, getWindowControl()); } + public static enum Status { + refused, + allowed, + wait, + closed + } + public static final class Mode { - + + private String status; + private String errors; private final Locale locale; private final Link goButton; private final TransientAssessmentMode mode; - public Mode(Link goButton, TransientAssessmentMode mode, Locale locale) { + public Mode(Link goButton, String status, String errors, TransientAssessmentMode mode, Locale locale) { this.goButton = goButton; this.mode = mode; + this.errors = errors; + this.status = status; this.locale = locale; } @@ -124,10 +203,30 @@ public class AssessmentModeUserConfirmationController extends BasicController { return mode.getDescription(); } + public String getSafeExamBrowserHint() { + return mode.getSafeExamBrowserHint(); + } + public String getDisplayName() { return mode.getDisplayName(); } + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public String getErrors() { + return errors; + } + + public void setErrors(String errors) { + this.errors = errors; + } + public String getBegin() { return Formatter.getInstance(locale).formatDateAndTime(mode.getBegin()); } diff --git a/src/main/java/org/olat/course/assessment/ui/ChooseStartElementController.java b/src/main/java/org/olat/course/assessment/ui/ChooseStartElementController.java new file mode 100644 index 00000000000..1b233aedddd --- /dev/null +++ b/src/main/java/org/olat/course/assessment/ui/ChooseStartElementController.java @@ -0,0 +1,101 @@ +/** + * <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.course.assessment.ui; + +import java.util.Collections; + +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.form.flexible.FormItemContainer; +import org.olat.core.gui.components.form.flexible.impl.FormBasicController; +import org.olat.core.gui.components.tree.MenuTreeItem; +import org.olat.core.gui.control.Controller; +import org.olat.core.gui.control.Event; +import org.olat.core.gui.control.WindowControl; +import org.olat.core.id.OLATResourceable; +import org.olat.core.util.resource.OresHelper; +import org.olat.course.CourseFactory; +import org.olat.course.ICourse; +import org.olat.course.nodes.CourseNode; +import org.olat.course.tree.CourseEditorTreeModel; + +/** + * + * Initial date: 19.12.2014<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class ChooseStartElementController extends FormBasicController { + + private MenuTreeItem selectTree; + private CourseEditorTreeModel treeModel; + + private final OLATResourceable ores; + private final String preSelectedKey; + + public ChooseStartElementController(UserRequest ureq, WindowControl wControl, String selectedKey, OLATResourceable ores) { + super(ureq, wControl, "course_element"); + this.ores = OresHelper.clone(ores); + preSelectedKey = selectedKey; + initForm(ureq); + } + + @Override + protected void doDispose() { + // nothing to dispose + } + + @Override + protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { + ICourse course = CourseFactory.loadCourse(ores); + treeModel = course.getEditorTreeModel(); + selectTree = uifactory.addTreeMultiselect("elements", null, formLayout, treeModel, this); + selectTree.setSelectedKeys(Collections.singletonList(preSelectedKey)); + + uifactory.addFormCancelButton("cancel", formLayout, ureq, getWindowControl()); + uifactory.addFormSubmitButton("ok", formLayout); + } + + public String getSelectedKey() { + return selectTree.getSelectedNodeId(); + } + + public String getSelectedName() { + String selectedKey = getSelectedKey(); + String name = null; + + CourseNode node = treeModel.getCourseNode(selectedKey); + if(node == null) { + //not published?? + } else { + name = node.getShortTitle(); + } + return name; + } + + @Override + protected void formOK(UserRequest ureq) { + fireEvent(ureq, Event.DONE_EVENT); + } + + @Override + protected void formCancelled(UserRequest ureq) { + fireEvent(ureq, Event.CANCELLED_EVENT); + } +} \ No newline at end of file diff --git a/src/main/java/org/olat/course/assessment/ui/_content/choose_mode.html b/src/main/java/org/olat/course/assessment/ui/_content/choose_mode.html index 4506849bc6e..471dd31d3d3 100644 --- a/src/main/java/org/olat/course/assessment/ui/_content/choose_mode.html +++ b/src/main/java/org/olat/course/assessment/ui/_content/choose_mode.html @@ -6,7 +6,15 @@ <br/><em>$r.translate("current.mode.leadtime", $mode.leadTime)</em> #end </p> - <p>$mode.description</p> - <div class="o_button_group">$r.render($mode.buttonName)</div> -#end - + #if($mode.description && !$mode.description.empty) + <p>$mode.description</p> + #end + #if($mode.safeExamBrowserHint && !$mode.safeExamBrowserHint.empty) + <p class="o_info">$mode.safeExamBrowserHint</p> + #end + #if($mode.status == "allowed" || $mode.status == "wait") + <div class="o_button_group">$r.render($mode.buttonName)</div> + #else + <p class="o_error">$mode.errors</p> + #end +#end \ No newline at end of file diff --git a/src/main/java/org/olat/course/assessment/ui/_content/course_element.html b/src/main/java/org/olat/course/assessment/ui/_content/course_element.html new file mode 100644 index 00000000000..b02c1900d53 --- /dev/null +++ b/src/main/java/org/olat/course/assessment/ui/_content/course_element.html @@ -0,0 +1,4 @@ +$r.render("elements") +<div class="o_button_group"> + $r.render("ok") $r.render("cancel") +</div> \ No newline at end of file diff --git a/src/main/java/org/olat/course/assessment/ui/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/course/assessment/ui/_i18n/LocalStrings_de.properties index a327692a1d9..eedef79e040 100644 --- a/src/main/java/org/olat/course/assessment/ui/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/course/assessment/ui/_i18n/LocalStrings_de.properties @@ -4,6 +4,7 @@ areas=Lernbereich choose.areas=Lernbereich auswählen choose.elements=Kursbausteine auswählen choose.groups=Gruppen auswählen +choose.start.element=Kursbaustein auswählen current.mode=Aktuelle Prüfung current.mode.desc=Im folgenden Kurs ist für den aktuellen Zeitraum eine Prüfung für Sie vorgesehen. Während dieser Prüfung können keine anderen Funktionen in OpenOLAT verwendet werden: current.mode.start=Zur Prüfung @@ -12,6 +13,8 @@ current.mode.datetimes=Von {0} bis {1} current.mode.leadtime=Ab {0} Minuten vor Prüfungsbeginn können keine andere Kurse mehr geöffnet werden. delete.mode=Pr\u00FCfungssetting l\u00F6schen elements=Kursbausteine +error.ip.range=Sie sind nicht in dem richtigen Netzwerk. +error.safe.exam=Bitte, benutzen Sie den Safe Exam Browser. form.mode.description=Erstellen Sie ein neues Pr\u00FCfungssetting um f\u00FCr diesen Kurs oder Elemets aus diesem Kurs in einem gesch\u00FCtzten Modus mit einem eingeschr\u00E4nken M\u00F6glichkeiten zu betreiben. form.mode.title=Pr\u00FCfungssetting {0} form.mode.title.add=Pr\u00FCfungssetting @@ -28,13 +31,16 @@ mode.for.coach=Pr\u00FCfundssettings auch bei Betreuer anwenden mode.description=Beschreibung mode.safeexambrowser=Safe exam Browser verwenden mode.safeexambrowser.key=Browser Exam key -mode.safeexambrowser.hint=Safe Exam Browser Hinweis +mode.safeexambrowser.hint=Safe Exam Browser Hinweis +mode.start.element=Startbaustein mode.target=Teilnehmer modes.title=Pr\u00FCfungsmodus modes.description=Erstellen Sie ein neues Pr\u00FCfungssetting um f\u00FCr diesen Kurs oder Elemets aus diesem Kurs in einem gesch\u00FCtzten Modus mit einem eingeschr\u00E4nken M\u00F6glichkeiten zu betreiben. new.mode=Neues Pr\u00FCfungssetting popup.chooseareas=$\:choose.areas popup.choosegroups=$\:choose.groups +popup.chooseelements=$\:choose.elements +popup.choosestartelement=Startbaustein auswählen table.header.name=Prüfung table.header.begin=Von table.header.end=Bis diff --git a/src/main/resources/database/mysql/alter_10_1_0_to_10_2_0.sql b/src/main/resources/database/mysql/alter_10_1_0_to_10_2_0.sql index 4c481cb7189..b295ba15f22 100644 --- a/src/main/resources/database/mysql/alter_10_1_0_to_10_2_0.sql +++ b/src/main/resources/database/mysql/alter_10_1_0_to_10_2_0.sql @@ -5,11 +5,13 @@ create table o_as_mode_course ( a_name varchar(255), a_description longtext, a_begin datetime not null, + a_begin_with_leadtime datetime not null, a_creationdate datetime not null, a_leadtime bigint not null default 0, a_targetaudience varchar(16), a_restrictaccesselements bit not null default 0, a_elements varchar(2048), + a_start_element varchar(64), a_restrictaccessips bit not null default 0, a_ips varchar(2048), a_safeexambrowser bit not null default 0, diff --git a/src/main/resources/database/mysql/setupDatabase.sql b/src/main/resources/database/mysql/setupDatabase.sql index 923d5223fb7..c5af60575c6 100644 --- a/src/main/resources/database/mysql/setupDatabase.sql +++ b/src/main/resources/database/mysql/setupDatabase.sql @@ -1062,6 +1062,44 @@ create table o_as_user_course_infos ( primary key (id) ); +create table o_as_mode_course ( + id bigint not null, + creationdate datetime not null, + lastmodified datetime not null, + a_name varchar(255), + a_description longtext, + a_begin datetime not null, + a_begin_with_leadtime datetime not null, + a_creationdate datetime not null, + a_leadtime bigint not null default 0, + a_targetaudience varchar(16), + a_restrictaccesselements bit not null default 0, + a_elements varchar(2048), + a_start_element varchar(64), + a_restrictaccessips bit not null default 0, + a_ips varchar(2048), + a_safeexambrowser bit not null default 0, + a_safeexambrowserkey varchar(2048), + a_safeexambrowserhint longtext, + a_applysettingscoach bit not null default 0, + fk_entry bigint not null, + primary key (id) +); + +create table o_as_mode_course_to_group ( + id bigint not null, + fk_assessment_mode_id bigint not null, + fk_group_id bigint not null, + primary key (id) +); + +create table o_as_mode_course_to_area ( + id bigint not null, + fk_assessment_mode_id bigint not null, + fk_area_id bigint not null, + primary key (id) +); + create table o_cer_template ( id bigint not null, creationdate datetime not null, @@ -1731,6 +1769,9 @@ alter table o_ac_reservation ENGINE = InnoDB; alter table o_ac_paypal_transaction ENGINE = InnoDB; alter table o_as_eff_statement ENGINE = InnoDB; alter table o_as_user_course_infos ENGINE = InnoDB; +alter table o_as_mode_course ENGINE = InnoDB; +alter table o_as_mode_course ENGINE = InnoDB; +alter table o_as_mode_course_to_area ENGINE = InnoDB; alter table o_mapper ENGINE = InnoDB; alter table o_qp_pool ENGINE = InnoDB; alter table o_qp_taxonomy_level ENGINE = InnoDB; @@ -2067,6 +2108,15 @@ alter table o_qp_item_type add unique (q_type(200)); alter table o_lti_outcome add constraint idx_lti_outcome_ident_id foreign key (fk_identity_id) references o_bs_identity(id); alter table o_lti_outcome add constraint idx_lti_outcome_rsrc_id foreign key (fk_resource_id) references o_olatresource(resource_id); +-- assessment mode +alter table o_as_mode_course add constraint as_mode_to_repo_entry_idx foreign key (fk_entry) references o_repositoryentry (repositoryentry_id); + +alter table o_as_mode_course_to_group add constraint as_modetogroup_group_idx foreign key (fk_group_id) references o_gp_business (group_id); +alter table o_as_mode_course_to_group add constraint as_modetogroup_mode_idx foreign key (fk_assessment_mode_id) references o_as_mode_course (id); + +alter table o_as_mode_course_to_area add constraint as_modetoarea_area_idx foreign key (fk_area_id) references o_gp_bgarea (area_id); +alter table o_as_mode_course_to_area add constraint as_modetoarea_mode_idx foreign key (fk_assessment_mode_id) references o_as_mode_course (id); + -- certificate alter table o_cer_certificate add constraint cer_to_identity_idx foreign key (fk_identity) references o_bs_identity (id); alter table o_cer_certificate add constraint cer_to_resource_idx foreign key (fk_olatresource) references o_olatresource (resource_id); diff --git a/src/main/resources/database/oracle/alter_10_1_0_to_10_2_0.sql b/src/main/resources/database/oracle/alter_10_1_0_to_10_2_0.sql new file mode 100644 index 00000000000..40c0484716a --- /dev/null +++ b/src/main/resources/database/oracle/alter_10_1_0_to_10_2_0.sql @@ -0,0 +1,51 @@ +create table o_as_mode_course ( + id number(20) not null, + creationdate date not null, + lastmodified date not null, + a_name varchar2(255 char), + a_description clob, + a_begin date not null, + a_begin_with_leadtime date not null, + a_creationdate date not null, + a_leadtime number(20) default 0 not null, + a_targetaudience varchar2(16 char), + a_restrictaccesselements number default 0 not null, + a_elements varchar2(2048 char), + a_start_element varchar2(64 char), + a_restrictaccessips number default 0 not null, + a_ips varchar2(2048 char), + a_safeexambrowser number default 0 not null, + a_safeexambrowserkey varchar2(2048 char), + a_safeexambrowserhint clob, + a_applysettingscoach number default 0 not null, + fk_entry number(20) not null, + primary key (id) +); + +create table o_as_mode_course_to_group ( + id number(20) not null, + fk_assessment_mode_id number(20) not null, + fk_group_id number(20) not null, + primary key (id) +); + +create table o_as_mode_course_to_area ( + id number(20) not null, + fk_assessment_mode_id number(20) not null, + fk_area_id number(20) not null, + primary key (id) +); + +alter table o_as_mode_course add constraint as_mode_to_repo_entry_idx foreign key (fk_entry) references o_repositoryentry (repositoryentry_id); +create index idx_as_mode_to_repo_entry_idx on o_as_mode_course (fk_entry); + +alter table o_as_mode_course_to_group add constraint as_modetogroup_group_idx foreign key (fk_group_id) references o_gp_business (group_id); +alter table o_as_mode_course_to_group add constraint as_modetogroup_mode_idx foreign key (fk_assessment_mode_id) references o_as_mode_course (id); +create index idx_as_modetogroup_group_idx on o_as_mode_course_to_group (fk_group_id); +create index idx_as_modetogroup_mode_idx on o_as_mode_course_to_group (fk_assessment_mode_id); + +alter table o_as_mode_course_to_area add constraint as_modetoarea_area_idx foreign key (fk_area_id) references o_gp_bgarea (area_id); +alter table o_as_mode_course_to_area add constraint as_modetoarea_mode_idx foreign key (fk_assessment_mode_id) references o_as_mode_course (id); +create index idx_as_modetoarea_area_idx on o_as_mode_course_to_area (fk_area_id); +create index idx_as_modetoarea_mode_idx on o_as_mode_course_to_area (fk_assessment_mode_id); + diff --git a/src/main/resources/database/oracle/setupDatabase.sql b/src/main/resources/database/oracle/setupDatabase.sql index d3e52ca1bb2..ef37fdd9817 100644 --- a/src/main/resources/database/oracle/setupDatabase.sql +++ b/src/main/resources/database/oracle/setupDatabase.sql @@ -1096,6 +1096,44 @@ create table o_as_user_course_infos ( primary key (id) ); +create table o_as_mode_course ( + id number(20) not null, + creationdate date not null, + lastmodified date not null, + a_name varchar2(255 char), + a_description clob, + a_begin date not null, + a_begin_with_leadtime date not null, + a_creationdate date not null, + a_leadtime number(20) default 0 not null, + a_targetaudience varchar2(16 char), + a_restrictaccesselements number default 0 not null, + a_elements varchar2(2048 char), + a_start_element varchar2(64 char), + a_restrictaccessips number default 0 not null, + a_ips varchar2(2048 char), + a_safeexambrowser number default 0 not null, + a_safeexambrowserkey varchar2(2048 char), + a_safeexambrowserhint clob, + a_applysettingscoach number default 0 not null, + fk_entry number(20) not null, + primary key (id) +); + +create table o_as_mode_course_to_group ( + id number(20) not null, + fk_assessment_mode_id number(20) not null, + fk_group_id number(20) not null, + primary key (id) +); + +create table o_as_mode_course_to_area ( + id number(20) not null, + fk_assessment_mode_id number(20) not null, + fk_area_id number(20) not null, + primary key (id) +); + create table o_cer_template ( id number(20) not null, creationdate date not null, @@ -2234,7 +2272,21 @@ create index idx_lti_outcome_ident_id_idx on o_lti_outcome (fk_identity_id); alter table o_lti_outcome add constraint idx_lti_outcome_rsrc_id foreign key (fk_resource_id) references o_olatresource(resource_id); create index idx_lti_outcome_rsrc_id_idx on o_lti_outcome (fk_resource_id); ---certificates +-- assessment mode +alter table o_as_mode_course add constraint as_mode_to_repo_entry_idx foreign key (fk_entry) references o_repositoryentry (repositoryentry_id); +create index idx_as_mode_to_repo_entry_idx on o_as_mode_course (fk_entry); + +alter table o_as_mode_course_to_group add constraint as_modetogroup_group_idx foreign key (fk_group_id) references o_gp_business (group_id); +alter table o_as_mode_course_to_group add constraint as_modetogroup_mode_idx foreign key (fk_assessment_mode_id) references o_as_mode_course (id); +create index idx_as_modetogroup_group_idx on o_as_mode_course_to_group (fk_group_id); +create index idx_as_modetogroup_mode_idx on o_as_mode_course_to_group (fk_assessment_mode_id); + +alter table o_as_mode_course_to_area add constraint as_modetoarea_area_idx foreign key (fk_area_id) references o_gp_bgarea (area_id); +alter table o_as_mode_course_to_area add constraint as_modetoarea_mode_idx foreign key (fk_assessment_mode_id) references o_as_mode_course (id); +create index idx_as_modetoarea_area_idx on o_as_mode_course_to_area (fk_area_id); +create index idx_as_modetoarea_mode_idx on o_as_mode_course_to_area (fk_assessment_mode_id); + +-- certificates alter table o_cer_certificate add constraint cer_to_identity_idx foreign key (fk_identity) references o_bs_identity (id); create index cer_identity_idx on o_cer_certificate (fk_identity); alter table o_cer_certificate add constraint cer_to_resource_idx foreign key (fk_olatresource) references o_olatresource (resource_id); diff --git a/src/main/resources/database/postgresql/alter_10_1_0_to_10_2_0.sql b/src/main/resources/database/postgresql/alter_10_1_0_to_10_2_0.sql new file mode 100644 index 00000000000..b7fe3089144 --- /dev/null +++ b/src/main/resources/database/postgresql/alter_10_1_0_to_10_2_0.sql @@ -0,0 +1,51 @@ +create table o_as_mode_course ( + id int8 not null, + creationdate timestamp not null, + lastmodified timestamp not null, + a_name varchar(255), + a_description text, + a_begin timestamp not null, + a_begin_with_leadtime timestamp not null, + a_creationdate timestamp not null, + a_leadtime int8 not null default 0, + a_targetaudience varchar(16), + a_restrictaccesselements bool not null default false, + a_elements varchar(2048), + a_start_element varchar(64), + a_restrictaccessips bool not null default false, + a_ips varchar(2048), + a_safeexambrowser bool not null default false, + a_safeexambrowserkey varchar(2048), + a_safeexambrowserhint text, + a_applysettingscoach bool not null default false, + fk_entry int8 not null, + primary key (id) +); + +create table o_as_mode_course_to_group ( + id int8 not null, + fk_assessment_mode_id int8 not null, + fk_group_id int8 not null, + primary key (id) +); + +create table o_as_mode_course_to_area ( + id int8 not null, + fk_assessment_mode_id int8 not null, + fk_area_id int8 not null, + primary key (id) +); + +alter table o_as_mode_course add constraint as_mode_to_repo_entry_idx foreign key (fk_entry) references o_repositoryentry (repositoryentry_id); +create index idx_as_mode_to_repo_entry_idx on o_as_mode_course (fk_entry); + +alter table o_as_mode_course_to_group add constraint as_modetogroup_group_idx foreign key (fk_group_id) references o_gp_business (group_id); +alter table o_as_mode_course_to_group add constraint as_modetogroup_mode_idx foreign key (fk_assessment_mode_id) references o_as_mode_course (id); +create index idx_as_modetogroup_group_idx on o_as_mode_course_to_group (fk_group_id); +create index idx_as_modetogroup_mode_idx on o_as_mode_course_to_group (fk_assessment_mode_id); + +alter table o_as_mode_course_to_area add constraint as_modetoarea_area_idx foreign key (fk_area_id) references o_gp_bgarea (area_id); +alter table o_as_mode_course_to_area add constraint as_modetoarea_mode_idx foreign key (fk_assessment_mode_id) references o_as_mode_course (id); +create index idx_as_modetoarea_area_idx on o_as_mode_course_to_area (fk_area_id); +create index idx_as_modetoarea_mode_idx on o_as_mode_course_to_area (fk_assessment_mode_id); + diff --git a/src/main/resources/database/postgresql/setupDatabase.sql b/src/main/resources/database/postgresql/setupDatabase.sql index 188a862e662..675c13f11d4 100644 --- a/src/main/resources/database/postgresql/setupDatabase.sql +++ b/src/main/resources/database/postgresql/setupDatabase.sql @@ -1063,6 +1063,44 @@ create table o_as_user_course_infos ( primary key (id) ); +create table o_as_mode_course ( + id int8 not null, + creationdate timestamp not null, + lastmodified timestamp not null, + a_name varchar(255), + a_description text, + a_begin timestamp not null, + a_begin_with_leadtime timestamp not null, + a_creationdate timestamp not null, + a_leadtime int8 not null default 0, + a_targetaudience varchar(16), + a_restrictaccesselements bool not null default false, + a_elements varchar(2048), + a_start_element varchar(64), + a_restrictaccessips bool not null default false, + a_ips varchar(2048), + a_safeexambrowser bool not null default false, + a_safeexambrowserkey varchar(2048), + a_safeexambrowserhint text, + a_applysettingscoach bool not null default false, + fk_entry int8 not null, + primary key (id) +); + +create table o_as_mode_course_to_group ( + id int8 not null, + fk_assessment_mode_id int8 not null, + fk_group_id int8 not null, + primary key (id) +); + +create table o_as_mode_course_to_area ( + id int8 not null, + fk_assessment_mode_id int8 not null, + fk_area_id int8 not null, + primary key (id) +); + create table o_cer_template ( id int8 not null, creationdate timestamp not null, @@ -2081,6 +2119,20 @@ create index idx_lti_outcome_ident_id_idx on o_lti_outcome (fk_identity_id); alter table o_lti_outcome add constraint idx_lti_outcome_rsrc_id foreign key (fk_resource_id) references o_olatresource(resource_id); create index idx_lti_outcome_rsrc_id_idx on o_lti_outcome (fk_resource_id); +-- assessment mode +alter table o_as_mode_course add constraint as_mode_to_repo_entry_idx foreign key (fk_entry) references o_repositoryentry (repositoryentry_id); +create index idx_as_mode_to_repo_entry_idx on o_as_mode_course (fk_entry); + +alter table o_as_mode_course_to_group add constraint as_modetogroup_group_idx foreign key (fk_group_id) references o_gp_business (group_id); +alter table o_as_mode_course_to_group add constraint as_modetogroup_mode_idx foreign key (fk_assessment_mode_id) references o_as_mode_course (id); +create index idx_as_modetogroup_group_idx on o_as_mode_course_to_group (fk_group_id); +create index idx_as_modetogroup_mode_idx on o_as_mode_course_to_group (fk_assessment_mode_id); + +alter table o_as_mode_course_to_area add constraint as_modetoarea_area_idx foreign key (fk_area_id) references o_gp_bgarea (area_id); +alter table o_as_mode_course_to_area add constraint as_modetoarea_mode_idx foreign key (fk_assessment_mode_id) references o_as_mode_course (id); +create index idx_as_modetoarea_area_idx on o_as_mode_course_to_area (fk_area_id); +create index idx_as_modetoarea_mode_idx on o_as_mode_course_to_area (fk_assessment_mode_id); + -- certificates alter table o_cer_certificate add constraint cer_to_identity_idx foreign key (fk_identity) references o_bs_identity (id); create index cer_identity_idx on o_cer_certificate (fk_identity); diff --git a/src/test/java/org/olat/core/util/IPUtilsTest.java b/src/test/java/org/olat/core/util/IPUtilsTest.java new file mode 100644 index 00000000000..1fb4707a0e6 --- /dev/null +++ b/src/test/java/org/olat/core/util/IPUtilsTest.java @@ -0,0 +1,41 @@ +package org.olat.core.util; + +import org.junit.Assert; +import org.junit.Test; + +/** + * + * Initial date: 22.12.2014<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class IPUtilsTest { + + @Test + public void checkRange_start_end() { + String start = "192.168.5.5"; + String end = "192.168.5.25"; + + boolean check1 = IPUtils.isValidRange(start, end, "192.168.5.21"); + Assert.assertTrue(check1); + + boolean check2 = IPUtils.isValidRange(start, end, "192.168.5.45"); + Assert.assertFalse(check2); + } + /* + @Test + public void checkRange_mask_31() { + String ipWithMask = "192.168.100.1/24"; + + boolean allowed1 = IPUtils.isValidRange(ipWithMask, "192.168.100.1"); + Assert.assertTrue(allowed1); + + boolean notAllowed1 = IPUtils.isValidRange(ipWithMask, "192.168.99.255"); + Assert.assertFalse(notAllowed1); + boolean notAllowed2 = IPUtils.isValidRange(ipWithMask, "192.168.101.1"); + Assert.assertFalse(notAllowed2); + boolean notAllowed3 = IPUtils.isValidRange(ipWithMask, "212.34.100.0"); + Assert.assertFalse(notAllowed3); + } + */ +} diff --git a/src/test/java/org/olat/course/assessment/manager/AssessmentModeManagerTest.java b/src/test/java/org/olat/course/assessment/manager/AssessmentModeManagerTest.java index bfeb13fa6b9..cafb42392f9 100644 --- a/src/test/java/org/olat/course/assessment/manager/AssessmentModeManagerTest.java +++ b/src/test/java/org/olat/course/assessment/manager/AssessmentModeManagerTest.java @@ -559,6 +559,41 @@ public class AssessmentModeManagerTest extends OlatTestCase { Assert.assertTrue(assessedIdentityKeys.contains(participant2.getKey())); } + @Test + public void isIpAllowed_exactMatch() { + String ipList = "192.168.1.203"; + + boolean allowed1 = assessmentModeMgr.isIpAllowed(ipList, "192.168.1.203"); + Assert.assertTrue(allowed1); + + //negative test + boolean notAllowed1 = assessmentModeMgr.isIpAllowed(ipList, "192.168.1.129"); + Assert.assertFalse(notAllowed1); + boolean notAllowed2 = assessmentModeMgr.isIpAllowed(ipList, "192.168.1.204"); + Assert.assertFalse(notAllowed2); + boolean notAllowed3 = assessmentModeMgr.isIpAllowed(ipList, "192.168.100.203"); + Assert.assertFalse(notAllowed3); + boolean notAllowed4 = assessmentModeMgr.isIpAllowed(ipList, "192.203.203.203"); + Assert.assertFalse(notAllowed4); + } + + @Test + public void isIpAllowed_pseudoRange() { + String ipList = "192.168.1.1 - 192.168.1.128"; + + boolean allowed1 = assessmentModeMgr.isIpAllowed(ipList, "192.168.1.64"); + Assert.assertTrue(allowed1); + + //negative test + boolean notAllowed1 = assessmentModeMgr.isIpAllowed(ipList, "192.168.1.129"); + Assert.assertFalse(notAllowed1); + boolean notAllowed2 = assessmentModeMgr.isIpAllowed(ipList, "192.168.1.204"); + Assert.assertFalse(notAllowed2); + boolean notAllowed3 = assessmentModeMgr.isIpAllowed(ipList, "192.168.100.64"); + Assert.assertFalse(notAllowed3); + boolean notAllowed4 = assessmentModeMgr.isIpAllowed(ipList, "212.203.203.64"); + Assert.assertFalse(notAllowed4); + } private AssessmentMode createMinimalAssessmentmode(RepositoryEntry entry) { AssessmentMode mode = assessmentModeMgr.createAssessmentMode(entry); diff --git a/src/test/java/org/olat/test/AllTestsJunit4.java b/src/test/java/org/olat/test/AllTestsJunit4.java index 708b1681a77..6e782134a4a 100644 --- a/src/test/java/org/olat/test/AllTestsJunit4.java +++ b/src/test/java/org/olat/test/AllTestsJunit4.java @@ -62,6 +62,7 @@ import org.junit.runners.Suite; org.olat.core.util.FormatterTest.class, org.olat.core.util.EncoderTest.class, org.olat.core.util.SimpleHtmlParserTest.class, + org.olat.core.util.IPUtilsTest.class, org.olat.core.util.mail.manager.MailManagerTest.class, org.olat.core.id.context.BusinessControlFactoryTest.class, org.olat.core.id.context.HistoryManagerTest.class, -- GitLab