From d306364d40eb7c58b032a2a4bdaee1e538d693b0 Mon Sep 17 00:00:00 2001 From: uhensler <urs.hensler@frentix.com> Date: Thu, 26 Mar 2020 15:30:08 +0100 Subject: [PATCH] OO-4595: Limit number of editable documents for OnlyOffice --- .../onlyoffice/OnlyOfficeModule.java | 26 ++++++ .../onlyoffice/OnlyOfficeService.java | 9 ++ .../manager/OnlyOfficeServiceImpl.java | 34 +++++++ .../ui/OnlyOfficeAdminController.java | 23 +++++ .../ui/OnlyOfficeEditorController.java | 15 ++- .../onlyoffice/ui/OnlyOfficeUIFactory.java | 20 ++++ .../ui/_i18n/LocalStrings_de.properties | 6 +- .../ui/_i18n/LocalStrings_en.properties | 8 +- .../services/doceditor/wopi/WopiService.java | 5 +- .../doceditor/wopi/manager/AccessDAO.java | 18 ++++ .../wopi/manager/WopiServiceImpl.java | 5 + .../resources/serviceconfig/olat.properties | 5 +- .../manager/OnlyOfficeServiceImplTest.java | 93 +++++++++++++++++++ .../doceditor/wopi/manager/AccessDAOTest.java | 19 ++++ .../java/org/olat/test/AllTestsJunit4.java | 1 + 15 files changed, 280 insertions(+), 7 deletions(-) create mode 100644 src/test/java/org/olat/core/commons/services/doceditor/onlyoffice/manager/OnlyOfficeServiceImplTest.java diff --git a/src/main/java/org/olat/core/commons/services/doceditor/onlyoffice/OnlyOfficeModule.java b/src/main/java/org/olat/core/commons/services/doceditor/onlyoffice/OnlyOfficeModule.java index d4933815a94..343b0f35256 100644 --- a/src/main/java/org/olat/core/commons/services/doceditor/onlyoffice/OnlyOfficeModule.java +++ b/src/main/java/org/olat/core/commons/services/doceditor/onlyoffice/OnlyOfficeModule.java @@ -47,6 +47,7 @@ public class OnlyOfficeModule extends AbstractSpringModule implements ConfigOnOf private static final String ONLYOFFICE_ENABLED = "onlyoffice.enabled"; private static final String ONLYOFFICE_BASE_URL = "onlyoffice.baseUrl"; private static final String ONLYOFFICE_JWT_SECRET = "onlyoffice.jwt.secret"; + private static final String ONLYOFFICE_LICENSE_EDIT = "onlyoffice.license.edit"; private static final String ONLYOFFICE_DATA_TRANSER_CONFIRMATION_ENABLED = "onlyoffice.data.transfer.confirmation.enabled"; private static final String ONLYOFFICE_USAGE_AUTHORS = "onlyoffice.usage.authors"; private static final String ONLYOFFICE_USAGE_COACHES = "onlyoffice.usage.coaches"; @@ -61,6 +62,8 @@ public class OnlyOfficeModule extends AbstractSpringModule implements ConfigOnOf private String apiUrl; private String jwtSecret; private Key jwtSignKey; + @Value("${onlyoffice.license.edit}") + private Integer licenseEdit; @Value("${onlyoffice.data.transfer.confirmation.enabled:false}") private boolean dataTransferConfirmationEnabled; @Value("${onlyoffice.usage.restricted.authors:false}") @@ -107,6 +110,11 @@ public class OnlyOfficeModule extends AbstractSpringModule implements ConfigOnOf dataTransferConfirmationEnabled = "true".equals(dataTransferConfirmationEnabledObj); } + String licenseEditObj = getStringPropertyValue(ONLYOFFICE_LICENSE_EDIT, true); + if(StringHelper.containsNonWhitespace(licenseEditObj)) { + licenseEdit = getIntOrNull(licenseEditObj); + } + String usageRestrictedToAuthorsObj = getStringPropertyValue(ONLYOFFICE_USAGE_AUTHORS, true); if(StringHelper.containsNonWhitespace(usageRestrictedToAuthorsObj)) { usageRestrictedToAuthors = "true".equals(usageRestrictedToAuthorsObj); @@ -122,6 +130,15 @@ public class OnlyOfficeModule extends AbstractSpringModule implements ConfigOnOf usageRestrictedToManagers = "true".equals(usageRestrictedToManagersObj); } } + + private Integer getIntOrNull(String val) { + try { + return Integer.valueOf(val); + } catch (Exception e) { + // + } + return null; + } @Override public boolean isEnabled() { @@ -172,6 +189,15 @@ public class OnlyOfficeModule extends AbstractSpringModule implements ConfigOnOf return jwtSignKey; } + public Integer getLicenseEdit() { + return licenseEdit; + } + + public void setLicenseEdit(Integer licenseEdit) { + this.licenseEdit = licenseEdit; + setStringProperty(ONLYOFFICE_LICENSE_EDIT, licenseEdit == null? null: licenseEdit.toString(), true); + } + public boolean isDataTransferConfirmationEnabled() { return dataTransferConfirmationEnabled; } diff --git a/src/main/java/org/olat/core/commons/services/doceditor/onlyoffice/OnlyOfficeService.java b/src/main/java/org/olat/core/commons/services/doceditor/onlyoffice/OnlyOfficeService.java index 202ddcff46d..f38a347fc3b 100644 --- a/src/main/java/org/olat/core/commons/services/doceditor/onlyoffice/OnlyOfficeService.java +++ b/src/main/java/org/olat/core/commons/services/doceditor/onlyoffice/OnlyOfficeService.java @@ -23,6 +23,7 @@ import java.io.File; import org.olat.core.commons.services.doceditor.DocEditor.Mode; import org.olat.core.commons.services.doceditor.DocEditorSecurityCallback; +import org.olat.core.commons.services.doceditor.wopi.Access; import org.olat.core.commons.services.vfs.VFSMetadata; import org.olat.core.id.Identity; import org.olat.core.util.vfs.VFSLeaf; @@ -41,6 +42,10 @@ public interface OnlyOfficeService { File getFile(String fileId); VFSLeaf getVfsLeaf(String fileId); + + Access createAccess(VFSMetadata vfsMetadata, Identity identity, DocEditorSecurityCallback secCallback); + + void deleteAccess(Access access); ApiConfig getApiConfig(VFSMetadata vfsMetadata, Identity identity, DocEditorSecurityCallback secCallback); @@ -49,6 +54,10 @@ public interface OnlyOfficeService { boolean canUpdateContent(VFSLeaf vfsLeaf, Identity identity, String documentKey); boolean updateContent(VFSLeaf vfsLeaf, Identity identity, String url, boolean versionControlled); + + boolean isEditLicenseAvailable(); + + Long getEditLicensesInUse(); boolean isLockNeeded(Mode mode); diff --git a/src/main/java/org/olat/core/commons/services/doceditor/onlyoffice/manager/OnlyOfficeServiceImpl.java b/src/main/java/org/olat/core/commons/services/doceditor/onlyoffice/manager/OnlyOfficeServiceImpl.java index 4c97fb8e372..647d3973efc 100644 --- a/src/main/java/org/olat/core/commons/services/doceditor/onlyoffice/manager/OnlyOfficeServiceImpl.java +++ b/src/main/java/org/olat/core/commons/services/doceditor/onlyoffice/manager/OnlyOfficeServiceImpl.java @@ -35,6 +35,7 @@ import org.olat.core.commons.services.doceditor.DocEditor.Mode; import org.olat.core.commons.services.doceditor.DocEditorIdentityService; import org.olat.core.commons.services.doceditor.DocEditorSecurityCallback; import org.olat.core.commons.services.doceditor.onlyoffice.ApiConfig; +import org.olat.core.commons.services.doceditor.onlyoffice.OnlyOfficeModule; import org.olat.core.commons.services.doceditor.onlyoffice.OnlyOfficeSecurityService; import org.olat.core.commons.services.doceditor.onlyoffice.OnlyOfficeService; import org.olat.core.commons.services.doceditor.onlyoffice.model.ApiConfigImpl; @@ -43,6 +44,8 @@ import org.olat.core.commons.services.doceditor.onlyoffice.model.EditorConfigImp import org.olat.core.commons.services.doceditor.onlyoffice.model.InfoImpl; import org.olat.core.commons.services.doceditor.onlyoffice.model.PermissionsImpl; import org.olat.core.commons.services.doceditor.onlyoffice.model.UserImpl; +import org.olat.core.commons.services.doceditor.wopi.Access; +import org.olat.core.commons.services.doceditor.wopi.WopiService; import org.olat.core.commons.services.vfs.VFSMetadata; import org.olat.core.commons.services.vfs.VFSRepositoryService; import org.olat.core.helpers.Settings; @@ -80,6 +83,8 @@ public class OnlyOfficeServiceImpl implements OnlyOfficeService { private static ObjectMapper mapper = new ObjectMapper(); + @Autowired + private OnlyOfficeModule onlyOfficeModule; @Autowired private OnlyOfficeSecurityService onlyOfficeSecurityService; @Autowired @@ -88,6 +93,8 @@ public class OnlyOfficeServiceImpl implements OnlyOfficeService { private VFSRepositoryService vfsRepositoryService; @Autowired private VFSLockManager lockManager; + @Autowired + private WopiService wopiService; @Override public boolean fileExists(String fileId) { @@ -117,6 +124,18 @@ public class OnlyOfficeServiceImpl implements OnlyOfficeService { return null; } + @Override + public Access createAccess(VFSMetadata vfsMetadata, Identity identity, DocEditorSecurityCallback secCallback) { + return wopiService.getOrCreateAccess(vfsMetadata, identity, secCallback, LOCK_APP_NAME, null); + } + + @Override + public void deleteAccess(Access access) { + if (access == null) return; + + wopiService.deleteAccess(access.getToken()); + } + @Override public ApiConfig getApiConfig(VFSMetadata vfsMetadata, Identity identity, DocEditorSecurityCallback secCallback) { String fileName = vfsMetadata.getFilename(); @@ -268,6 +287,21 @@ public class OnlyOfficeServiceImpl implements OnlyOfficeService { lock.setExpiresAt(inADay); } } + + @Override + public boolean isEditLicenseAvailable() { + Integer licenseEdit = onlyOfficeModule.getLicenseEdit(); + if (licenseEdit == null) return true; + if (licenseEdit.intValue() == 0) return false; + + Long accessCount = wopiService.getAccessCount(LOCK_APP_NAME, Mode.EDIT); + return accessCount < licenseEdit.byteValue(); + } + + @Override + public Long getEditLicensesInUse() { + return wopiService.getAccessCount(LOCK_APP_NAME, Mode.EDIT); + } @Override public boolean isLockNeeded(Mode mode) { diff --git a/src/main/java/org/olat/core/commons/services/doceditor/onlyoffice/ui/OnlyOfficeAdminController.java b/src/main/java/org/olat/core/commons/services/doceditor/onlyoffice/ui/OnlyOfficeAdminController.java index 9b7c4474edf..eaae06d19c2 100644 --- a/src/main/java/org/olat/core/commons/services/doceditor/onlyoffice/ui/OnlyOfficeAdminController.java +++ b/src/main/java/org/olat/core/commons/services/doceditor/onlyoffice/ui/OnlyOfficeAdminController.java @@ -20,6 +20,7 @@ package org.olat.core.commons.services.doceditor.onlyoffice.ui; import static org.olat.core.commons.services.doceditor.onlyoffice.ui.OnlyOfficeUIFactory.validateIsMandatory; +import static org.olat.core.commons.services.doceditor.onlyoffice.ui.OnlyOfficeUIFactory.validatePositiveInteger; import static org.olat.core.gui.components.util.KeyValues.entry; import static org.olat.core.gui.translator.TranslatorHelper.translateAll; @@ -27,6 +28,7 @@ import java.util.Collection; import org.olat.core.commons.services.doceditor.onlyoffice.OnlyOfficeModule; import org.olat.core.commons.services.doceditor.onlyoffice.OnlyOfficeSecurityService; +import org.olat.core.commons.services.doceditor.onlyoffice.OnlyOfficeService; import org.olat.core.commons.services.doceditor.ui.DocEditorController; import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.form.flexible.FormItemContainer; @@ -37,6 +39,7 @@ import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer; import org.olat.core.gui.components.util.KeyValues; import org.olat.core.gui.control.Controller; import org.olat.core.gui.control.WindowControl; +import org.olat.core.util.StringHelper; import org.olat.core.util.Util; import org.springframework.beans.factory.annotation.Autowired; @@ -57,11 +60,14 @@ public class OnlyOfficeAdminController extends FormBasicController { private TextElement baseUrlEl; private TextElement jwtSecretEl; private MultipleSelectionElement dataTransferConfirmationEnabledEl; + private TextElement licenseEditEl; private MultipleSelectionElement usageRolesEl; @Autowired private OnlyOfficeModule onlyOfficeModule; @Autowired + private OnlyOfficeService onlyOfficeService; + @Autowired private OnlyOfficeSecurityService onlyOfficeSecurityService; public OnlyOfficeAdminController(UserRequest ureq, WindowControl wControl) { @@ -91,6 +97,15 @@ public class OnlyOfficeAdminController extends FormBasicController { translateAll(getTranslator(), ENABLED_KEYS)); dataTransferConfirmationEnabledEl.select(ENABLED_KEYS[0], onlyOfficeModule.isDataTransferConfirmationEnabled()); + String licenseEdit = onlyOfficeModule.getLicenseEdit() != null + ? onlyOfficeModule.getLicenseEdit().toString() + : null; + licenseEditEl = uifactory.addTextElement("admin.license.edit", 10, licenseEdit, formLayout); + + Long editLicensesInUse = onlyOfficeService.getEditLicensesInUse(); + editLicensesInUse = editLicensesInUse != null? editLicensesInUse: 0; + uifactory.addStaticTextElement("admin.license.edit.in.use", editLicensesInUse.toString(), formLayout); + KeyValues usageRolesKV = new KeyValues(); usageRolesKV.add(entry(USAGE_AUTHOR, translate("admin.usage.roles.author"))); usageRolesKV.add(entry(USAGE_COACH, translate("admin.usage.roles.coach"))); @@ -119,6 +134,8 @@ public class OnlyOfficeAdminController extends FormBasicController { jwtSecretOk = false; } allOk &= jwtSecretOk; + + allOk &= validatePositiveInteger(licenseEditEl); } return allOk & super.validateFormLogic(ureq); @@ -139,6 +156,12 @@ public class OnlyOfficeAdminController extends FormBasicController { boolean dataTransferConfirmationEnabled = dataTransferConfirmationEnabledEl.isAtLeastSelected(1); onlyOfficeModule.setDataTransferConfirmationEnabled(dataTransferConfirmationEnabled); + String licenseEditValue = licenseEditEl.getValue(); + Integer licenseEdit = StringHelper.containsNonWhitespace(licenseEditValue) + ? Integer.valueOf(licenseEditValue) + : null; + onlyOfficeModule.setLicenseEdit(licenseEdit); + Collection<String> restrictionKeys = usageRolesEl.getSelectedKeys(); onlyOfficeModule.setUsageRestrictedToAuthors(restrictionKeys.contains(USAGE_AUTHOR)); onlyOfficeModule.setUsageRestrictedToCoaches(restrictionKeys.contains(USAGE_COACH)); diff --git a/src/main/java/org/olat/core/commons/services/doceditor/onlyoffice/ui/OnlyOfficeEditorController.java b/src/main/java/org/olat/core/commons/services/doceditor/onlyoffice/ui/OnlyOfficeEditorController.java index 4b12b7cac80..8118d08d2ba 100644 --- a/src/main/java/org/olat/core/commons/services/doceditor/onlyoffice/ui/OnlyOfficeEditorController.java +++ b/src/main/java/org/olat/core/commons/services/doceditor/onlyoffice/ui/OnlyOfficeEditorController.java @@ -19,12 +19,14 @@ */ package org.olat.core.commons.services.doceditor.onlyoffice.ui; +import org.apache.logging.log4j.Logger; import org.olat.core.commons.services.doceditor.DocEditor.Mode; import org.olat.core.commons.services.doceditor.DocEditorSecurityCallback; import org.olat.core.commons.services.doceditor.DocEditorSecurityCallbackBuilder; import org.olat.core.commons.services.doceditor.onlyoffice.ApiConfig; import org.olat.core.commons.services.doceditor.onlyoffice.OnlyOfficeModule; import org.olat.core.commons.services.doceditor.onlyoffice.OnlyOfficeService; +import org.olat.core.commons.services.doceditor.wopi.Access; import org.olat.core.commons.services.vfs.VFSMetadata; import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.Component; @@ -32,7 +34,6 @@ import org.olat.core.gui.components.velocity.VelocityContainer; import org.olat.core.gui.control.Event; import org.olat.core.gui.control.WindowControl; import org.olat.core.gui.control.controller.BasicController; -import org.apache.logging.log4j.Logger; import org.olat.core.logging.Tracing; import org.olat.core.util.CodeHelper; import org.olat.core.util.vfs.VFSLeaf; @@ -48,6 +49,8 @@ public class OnlyOfficeEditorController extends BasicController { private static final Logger log = Tracing.createLoggerFor(OnlyOfficeEditorController.class); + private Access access; + @Autowired private OnlyOfficeModule onlyOfficeModule; @Autowired @@ -59,6 +62,13 @@ public class OnlyOfficeEditorController extends BasicController { DocEditorSecurityCallback secCallback = securityCallback; + if (Mode.EDIT == secCallback.getMode() && !onlyOfficeService.isEditLicenseAvailable()) { + secCallback = DocEditorSecurityCallbackBuilder.clone(secCallback) + .withMode(Mode.VIEW) + .build(); + showWarning("editor.warning.no.edit.license"); + } + if (onlyOfficeService.isLockNeeded(secCallback.getMode())) { if (onlyOfficeService.isLockedForMe(vfsLeaf, getIdentity())) { secCallback = DocEditorSecurityCallbackBuilder.clone(secCallback) @@ -82,6 +92,7 @@ public class OnlyOfficeEditorController extends BasicController { if (apiConfig == null) { mainVC.contextPut("warning", translate("editor.warning.no.api.configs")); } else { + this.access = onlyOfficeService.createAccess(vfsMetadata, getIdentity(), secCallback); mainVC.contextPut("id", "o_" + CodeHelper.getRAMUniqueID()); mainVC.contextPut("apiUrl", onlyOfficeModule.getApiUrl()); mainVC.contextPut("apiConfig", apiConfigJson); @@ -98,7 +109,7 @@ public class OnlyOfficeEditorController extends BasicController { @Override protected void doDispose() { - // + onlyOfficeService.deleteAccess(access); } } diff --git a/src/main/java/org/olat/core/commons/services/doceditor/onlyoffice/ui/OnlyOfficeUIFactory.java b/src/main/java/org/olat/core/commons/services/doceditor/onlyoffice/ui/OnlyOfficeUIFactory.java index b8751e66e3d..afb2a010164 100644 --- a/src/main/java/org/olat/core/commons/services/doceditor/onlyoffice/ui/OnlyOfficeUIFactory.java +++ b/src/main/java/org/olat/core/commons/services/doceditor/onlyoffice/ui/OnlyOfficeUIFactory.java @@ -42,5 +42,25 @@ class OnlyOfficeUIFactory { } return allOk; } + + public static boolean validatePositiveInteger(TextElement el) { + boolean allOk = true; + el.clearError(); + if(el.isEnabled() && el.isVisible()) { + String val = el.getValue(); + if(StringHelper.containsNonWhitespace(val)) { + try { + int intVal = Integer.parseInt(val); + if (intVal < 0) { + el.setErrorKey("error.positive.integer", null); + } + } catch (NumberFormatException e) { + el.setErrorKey("error.positive.integer", null); + allOk = false; + } + } + } + return allOk; + } } diff --git a/src/main/java/org/olat/core/commons/services/doceditor/onlyoffice/ui/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/core/commons/services/doceditor/onlyoffice/ui/_i18n/LocalStrings_de.properties index 93b7bba127a..33ad62b6a53 100644 --- a/src/main/java/org/olat/core/commons/services/doceditor/onlyoffice/ui/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/core/commons/services/doceditor/onlyoffice/ui/_i18n/LocalStrings_de.properties @@ -1,5 +1,7 @@ admin.base.url=URL admin.desc=ONLYOFFICE ist eine Software zur einzelnen oder gemeinsamen Bearbeitung von Dokumenten. Es unterst\u00FCtzt alle g\u00E4ngigen Dateiformate zur Textverarbeitung, Tabellenkalkulation und Pr\u00E4sentationen. Weitere Informationen sind auf der <a href\="http\://www.onlyoffice.com/" target\=_blank>Webseite</a> von ONLYOFFICE zu finden. +admin.license.edit=Verf\u00FCgbare Bearbeitungslizezen +admin.license.edit.in.use=Aktuelle genutzte Bearbeitungslizenzen admin.enabled=Modul "ONLYOFFICE" admin.jwt.secret=Secret admin.jwt.secret.invalid=Das Secret ist nicht g\u00FCltig. Vermutlich ist es zu kurz. Siehe: JWA Specification (RFC 7518, Section 3.2). @@ -7,4 +9,6 @@ admin.title=ONLYOFFICE editor.display.name=ONLYOFFICE editor.warning.locked=Das Dokument wird bereits in einem anderen Editor bearbeitet und kann deshalb in ONLYOFFICE nicht bearbeitet werden. editor.warning.no.api.config=Dieses Dokument kann nicht angezeigt werden! -editor.warning.no.metadata=Dieses Dokument kann nicht angezeigt werden! \ No newline at end of file +editor.warning.no.edit.license=Es sind bereits alle Lizenzen zur Bearbeitung von Dokumenten in Verwendung. Das Dokument wird in der Lese-Ansicht ge\u00F6ffnet. +editor.warning.no.metadata=Dieses Dokument kann nicht angezeigt werden! +error.positive.integer=Geben Sie bitte eine positive Zahl ein. \ No newline at end of file diff --git a/src/main/java/org/olat/core/commons/services/doceditor/onlyoffice/ui/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/core/commons/services/doceditor/onlyoffice/ui/_i18n/LocalStrings_en.properties index a99bfb16c38..308c424badd 100644 --- a/src/main/java/org/olat/core/commons/services/doceditor/onlyoffice/ui/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/core/commons/services/doceditor/onlyoffice/ui/_i18n/LocalStrings_en.properties @@ -1,10 +1,14 @@ admin.base.url=URL admin.desc=ONLYOFFICE is a software to edit documents online. It supports all major document, spreadsheet and presentation file formats. Key features are collaborative editing and excellent office file format support. Further information is available on the <a href\="https\://www.onlyoffice.com/" target\=_blank>website</a> of ONLYOFFICE +admin.license.edit=Available edit licenses +admin.license.edit.in.use=Edit licenses in use admin.enabled=Module "ONLYOFFICE" admin.jwt.secret=Secret admin.jwt.secret.invalid=The secret is not valid. Probably it is too short. See: JWA Specification (RFC 7518, Section 3.2). admin.title=ONLYOFFICE editor.display.name=ONLYOFFICE editor.warning.locked=That document is already edited in another editor and therefore it is displayed in a read-only view. -editor.warning.no.api.config=It is not possible to display that document. -editor.warning.no.metadata=It is not possible to display that document. \ No newline at end of file +editor.warning.no.api.config=It is not possible to display that document! +editor.warning.no.edit.license=All licenses for editing documents are already in use. The document is opened in read-only mode. +editor.warning.no.metadata=It is not possible to display that document. +error.positive.integer=Enter a positive number. \ No newline at end of file diff --git a/src/main/java/org/olat/core/commons/services/doceditor/wopi/WopiService.java b/src/main/java/org/olat/core/commons/services/doceditor/wopi/WopiService.java index b50719e7c8f..4526e7f9af5 100644 --- a/src/main/java/org/olat/core/commons/services/doceditor/wopi/WopiService.java +++ b/src/main/java/org/olat/core/commons/services/doceditor/wopi/WopiService.java @@ -22,6 +22,7 @@ package org.olat.core.commons.services.doceditor.wopi; import java.util.Collection; import java.util.Date; +import org.olat.core.commons.services.doceditor.DocEditor.Mode; import org.olat.core.commons.services.doceditor.DocEditorSecurityCallback; import org.olat.core.commons.services.vfs.VFSMetadata; import org.olat.core.id.Identity; @@ -69,9 +70,11 @@ public interface WopiService { String app, Date expiresAt); Access getAccess(String accessToken); - + VFSLeaf getVfsLeaf(Access access); + Long getAccessCount(String app, Mode mode); + void deleteAccess(String accessToken); } diff --git a/src/main/java/org/olat/core/commons/services/doceditor/wopi/manager/AccessDAO.java b/src/main/java/org/olat/core/commons/services/doceditor/wopi/manager/AccessDAO.java index b733dbb07bc..64b339daf07 100644 --- a/src/main/java/org/olat/core/commons/services/doceditor/wopi/manager/AccessDAO.java +++ b/src/main/java/org/olat/core/commons/services/doceditor/wopi/manager/AccessDAO.java @@ -26,6 +26,7 @@ import javax.annotation.PostConstruct; import org.olat.core.commons.persistence.DB; import org.olat.core.commons.persistence.QueryBuilder; +import org.olat.core.commons.services.doceditor.DocEditor.Mode; import org.olat.core.commons.services.doceditor.wopi.Access; import org.olat.core.commons.services.doceditor.wopi.model.AccessImpl; import org.olat.core.commons.services.vfs.VFSMetadata; @@ -119,6 +120,23 @@ class AccessDAO { return accesses.isEmpty() ? null : accesses.get(0); } + Long getAccessCount(String app, Mode mode) { + QueryBuilder sb = new QueryBuilder(); + sb.append("select count(*)"); + sb.append(" from wopiaccess access"); + sb.and().append("access.canEdit = :canEdit"); + if (app != null) { + sb.and().append("access.app = :app"); + } + + return dbInstance.getCurrentEntityManager() + .createQuery(sb.toString(), Long.class) + .setParameter("canEdit", Mode.EDIT == mode) + .setParameter("app", app) + .getResultList() + .get(0); + } + void deleteAccess(String token) { if (!StringHelper.containsNonWhitespace(token)) return; diff --git a/src/main/java/org/olat/core/commons/services/doceditor/wopi/manager/WopiServiceImpl.java b/src/main/java/org/olat/core/commons/services/doceditor/wopi/manager/WopiServiceImpl.java index 1083fcecef8..0f368d29063 100644 --- a/src/main/java/org/olat/core/commons/services/doceditor/wopi/manager/WopiServiceImpl.java +++ b/src/main/java/org/olat/core/commons/services/doceditor/wopi/manager/WopiServiceImpl.java @@ -170,6 +170,11 @@ public class WopiServiceImpl implements WopiService { } return null; } + + @Override + public Long getAccessCount(String app, Mode mode) { + return accessDao.getAccessCount(app, mode); + } @Override public void deleteAccess(String accessToken) { diff --git a/src/main/resources/serviceconfig/olat.properties b/src/main/resources/serviceconfig/olat.properties index d88c8b81b58..67d82e305ab 100644 --- a/src/main/resources/serviceconfig/olat.properties +++ b/src/main/resources/serviceconfig/olat.properties @@ -1585,9 +1585,12 @@ collabora.usage.restricted.managers=false onlyoffice.enabled=false onlyoffice.baseUrl=https://onlyoffice.example.org/ onlyoffice.api.path=web-apps/apps/api/documents/api.js +# Number of usable edit license. +# You may leave the field blank to supress the licnese check. +onlyoffice.license.edit= onlyoffice.data.transfer.confirmation.enabled=false onlyoffice.usage.restricted.authors=false -onlyofficeusage.restricted.coaches=false +onlyoffice.usage.restricted.coaches=false onlyoffice.usage.restricted.managers=false diff --git a/src/test/java/org/olat/core/commons/services/doceditor/onlyoffice/manager/OnlyOfficeServiceImplTest.java b/src/test/java/org/olat/core/commons/services/doceditor/onlyoffice/manager/OnlyOfficeServiceImplTest.java new file mode 100644 index 00000000000..b66d7a0f399 --- /dev/null +++ b/src/test/java/org/olat/core/commons/services/doceditor/onlyoffice/manager/OnlyOfficeServiceImplTest.java @@ -0,0 +1,93 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.core.commons.services.doceditor.onlyoffice.manager; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.olat.core.commons.services.doceditor.onlyoffice.OnlyOfficeModule; +import org.olat.core.commons.services.doceditor.wopi.WopiService; + +/** + * + * Initial date: 26 Mar 2020<br> + * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com + * + */ +public class OnlyOfficeServiceImplTest { + + @Mock + private OnlyOfficeModule onlyOfficeModuleMock; + @Mock + private WopiService wopiServiceMock; + + @InjectMocks + private OnlyOfficeServiceImpl sut ; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void shouldAllowEditIfNoLicensesSet() { + when(onlyOfficeModuleMock.getLicenseEdit()).thenReturn(null); + + boolean editLicenseAvailable = sut.isEditLicenseAvailable(); + + assertThat(editLicenseAvailable).isTrue(); + } + + @Test + public void shouldNotAllowEditIfZeroLicenses() { + when(onlyOfficeModuleMock.getLicenseEdit()).thenReturn(0); + + boolean editLicenseAvailable = sut.isEditLicenseAvailable(); + + assertThat(editLicenseAvailable).isFalse(); + } + + @Test + public void shouldAllowEditIfNotAllLicensesInUse() { + when(onlyOfficeModuleMock.getLicenseEdit()).thenReturn(10); + when(wopiServiceMock.getAccessCount(any(), any())).thenReturn(Long.valueOf(4)); + + boolean editLicenseAvailable = sut.isEditLicenseAvailable(); + + assertThat(editLicenseAvailable).isTrue(); + } + + @Test + public void shouldNotAllowEditIfAllLicensesInUse() { + when(onlyOfficeModuleMock.getLicenseEdit()).thenReturn(10); + when(wopiServiceMock.getAccessCount(any(), any())).thenReturn(Long.valueOf(10)); + + boolean editLicenseAvailable = sut.isEditLicenseAvailable(); + + assertThat(editLicenseAvailable).isFalse(); + } + +} diff --git a/src/test/java/org/olat/core/commons/services/doceditor/wopi/manager/AccessDAOTest.java b/src/test/java/org/olat/core/commons/services/doceditor/wopi/manager/AccessDAOTest.java index 1c209f887ba..59fbc0c778e 100644 --- a/src/test/java/org/olat/core/commons/services/doceditor/wopi/manager/AccessDAOTest.java +++ b/src/test/java/org/olat/core/commons/services/doceditor/wopi/manager/AccessDAOTest.java @@ -30,6 +30,7 @@ import org.assertj.core.api.SoftAssertions; import org.junit.Before; import org.junit.Test; import org.olat.core.commons.persistence.DB; +import org.olat.core.commons.services.doceditor.DocEditor.Mode; import org.olat.core.commons.services.doceditor.wopi.Access; import org.olat.core.commons.services.vfs.VFSMetadata; import org.olat.core.commons.services.vfs.manager.VFSMetadataDAO; @@ -136,6 +137,24 @@ public class AccessDAOTest extends OlatTestCase { assertThat(reloadedAccess).isEqualTo(access); } + + @Test + public void shouldGetAccessCount() { + Identity identity1 = JunitTestHelper.createAndPersistIdentityAsRndUser("wopi"); + Identity identity2 = JunitTestHelper.createAndPersistIdentityAsRndUser("wopi2"); + Identity identity3 = JunitTestHelper.createAndPersistIdentityAsRndUser("wopi3"); + VFSMetadata vfsMetadata = vfsMetadataDAO.createMetadata(random(), "relPath", "file.name", new Date(), 1000l, false, "file://" + random(), "file", null); + boolean canEdit = true; + sut.createAccess(vfsMetadata, identity1, "app1", random(), canEdit, true, true, null); + sut.createAccess(vfsMetadata, identity2, "app1", random(), canEdit, true, true, null); + sut.createAccess(vfsMetadata, identity1, "app2", random(), canEdit, true, true, null); + sut.createAccess(vfsMetadata, identity3, "app1", random(), false, true, true, null); + dbInstance.commitAndCloseSession(); + + Long accessCount = sut.getAccessCount("app1", Mode.EDIT); + + assertThat(accessCount).isEqualTo(2); + } @Test public void shouldDeleteAccess() { diff --git a/src/test/java/org/olat/test/AllTestsJunit4.java b/src/test/java/org/olat/test/AllTestsJunit4.java index 27a62c9a8cf..0c1d58c88ce 100644 --- a/src/test/java/org/olat/test/AllTestsJunit4.java +++ b/src/test/java/org/olat/test/AllTestsJunit4.java @@ -462,6 +462,7 @@ import org.junit.runners.Suite; */ org.olat.core.commons.services.doceditor.office365.manager.UrlParserTest.class, org.olat.core.commons.services.doceditor.onlyoffice.manager.OnlyOfficeSecurityServiceImplTest.class, + org.olat.core.commons.services.doceditor.onlyoffice.manager.OnlyOfficeServiceImplTest.class, org.olat.core.commons.services.doceditor.wopi.manager.WopiServiceImplTest.class, org.olat.core.commons.services.doceditor.wopi.manager.WopiXStreamTest.class, org.olat.core.commons.services.commentAndRating.manager.CommentAndRatingServiceTest.class, -- GitLab