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 d4933815a94450239637ff53cca9e288062dde0c..343b0f35256be02468c4d19d6a4038c662f9e240 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 202ddcff46d691e45361d2f60cc3a37a4d45d1d4..f38a347fc3bc8f434d63164fe37f167d59de3f2b 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 4c97fb8e3720cd405ad5da0dd13e49c3cb0654c7..647d3973efc306fb703ece0441393a73f0264a12 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 9b7c4474edf1f2c9ecd37da2a349a06cd0ee746f..eaae06d19c23a5ab0c6fc99cbca8b0ec6bc5e4bd 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 4b12b7cac808f8d278a962f2f5c0bbc409758477..8118d08d2ba9d8940a1e792b1b29088cef8de8ab 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 b8751e66e3d5f0df8e2a5c64bd595d0125bf8229..afb2a0101645bed2b62f208fd53ed996ac8d6fa5 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 93b7bba127a7a5fe1b47fe1c6c82576fd228d474..33ad62b6a539cb8a334cf93e2cd85e89ec2e8699 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 a99bfb16c382ca0a56e8d9c65dc10da962474224..308c424badde843cfaaaf30d4df3bcd0e8efed78 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 b50719e7c8f7413c1689ecedb928525d3c21a95e..4526e7f9af54ac847df864850d49b5020b7f4fda 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 b733dbb07bccbb302d938c89da06e56b6fc6dae9..64b339daf07228c66bc7d93260ef18073e4a37ce 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 1083fcecef8459d42db9bfb3bc92e736420b21cf..0f368d29063829ecd48127fe39801571b77c8dd1 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 d88c8b81b58ff714e49d50b0aa30833a487ba7a7..67d82e305aba0b316b73ec3811fdbc0024390182 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 0000000000000000000000000000000000000000..b66d7a0f3995b2fe6509e0c0545523df7b47adc8 --- /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 1c209f887ba888aba1a154c517393570309fff54..59fbc0c778e47e188d65884257093813b70e3334 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 27a62c9a8cf4a565e02fc378345f7266dfdae488..0c1d58c88cec4d19a8d7c5bc4555b970e1680b38 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,