diff --git a/frontend-js/src/main/js/ConfigurationType.js b/frontend-js/src/main/js/ConfigurationType.js index 0b85d8d10838ecdd82477b315eab0bdf5957a6cd..abe4fdd441a31848a892b26060d5f68be0328628 100644 --- a/frontend-js/src/main/js/ConfigurationType.js +++ b/frontend-js/src/main/js/ConfigurationType.js @@ -13,6 +13,7 @@ var ConfigurationType = { SIMPLE_COLOR_VAL: "SIMPLE_COLOR_VAL", SEARCH_DISTANCE: "SEARCH_DISTANCE", SEARCH_RESULT_NUMBER: "SEARCH_RESULT_NUMBER", + TERMS_OF_USE: "TERMS_OF_USE", USER_MANUAL_FILE: "USER_MANUAL_FILE", }; diff --git a/frontend-js/src/main/js/ServerConnector.js b/frontend-js/src/main/js/ServerConnector.js index f75bd1ded45626424829e6e27898b810887ef691..5b9ab60c6253b2701f229f93a7ff860ad0d97faf 100644 --- a/frontend-js/src/main/js/ServerConnector.js +++ b/frontend-js/src/main/js/ServerConnector.js @@ -652,6 +652,17 @@ ServerConnector.getModels = function (projectId) { return models; }); }; + +/** + * TODO REMOVE when API v12 appears (this is just a workaround) + */ +ServerConnector.updateTermsOfUse = function (user, value) { + var self = this; + var url = ServerConnector.getUserUrl({login:user.getLogin()}); + url += "/updateTermsOfUse?value=" + value; + return self.readFile(url); +}; + ServerConnector.getProject = function (projectId) { var queryParams = {}; var filterParams = {}; diff --git a/frontend-js/src/main/js/gui/leftPanel/OverlayPanel.js b/frontend-js/src/main/js/gui/leftPanel/OverlayPanel.js index b3a1a1a9421a4a7800baae47cc9b3028f8dcc1ab..a2a207664e1fb12d2ca016cc1d40fff11aa4972f 100644 --- a/frontend-js/src/main/js/gui/leftPanel/OverlayPanel.js +++ b/frontend-js/src/main/js/gui/leftPanel/OverlayPanel.js @@ -458,6 +458,13 @@ OverlayPanel.prototype.openAddOverlayDialog = function () { content.appendChild(contentInput); content.appendChild(guiUtils.createNewLine()); + var consentCheckbox = document.createElement("input"); + consentCheckbox.type = "checkbox"; + content.appendChild(consentCheckbox); + content.appendChild(guiUtils.createLabel("I am aware that this map is displayed under the terms of the <a href='https://cloud.google.com/maps-platform/terms/' target='_blank'>license of Google Maps Platform</a> and I agree to these terms. " + + "In particular, I warrant that this dataset does not contain Protected Health Information (as defined in and subject to HIPAA). ")); + + var buttons = [{ text: "UPLOAD", click: function () { @@ -477,6 +484,7 @@ OverlayPanel.prototype.openAddOverlayDialog = function () { name: nameInput.value, description: descriptionInput.value, content: fileContent, + googleLicenseConsent: consentCheckbox.checked, filename: fileInput.value }; GuiConnector.showProcessing(); diff --git a/frontend-js/src/main/js/map/data/User.js b/frontend-js/src/main/js/map/data/User.js index cd41f7d7f9760ec82f889fe2618259a19fc1c792..2828e4175e8da0d4652755f1871cd417c7a320b0 100644 --- a/frontend-js/src/main/js/map/data/User.js +++ b/frontend-js/src/main/js/map/data/User.js @@ -14,6 +14,7 @@ function User(javaObject) { this.setMinColor(javaObject.minColor); this.setMaxColor(javaObject.maxColor); this.setSimpleColor(javaObject.simpleColor); + this.setTermsOfUseConsent(javaObject.termsOfUseConsent); } User.prototype.setLogin = function(login) { @@ -88,4 +89,12 @@ User.prototype.getPrivileges = function() { return this._privileges; }; +User.prototype.setTermsOfUseConsent = function(termsOfUseConsent) { + this._termsOfUseConsent = termsOfUseConsent; +}; + +User.prototype.isTermsOfUseConsent = function() { + return this._termsOfUseConsent; +}; + module.exports = User; diff --git a/frontend-js/src/main/js/minerva.js b/frontend-js/src/main/js/minerva.js index 5d8ba60ef56cb304e5e9869ec9c9550725307edc..6556f66841cb5010100bfbafc9dc59625629c382 100644 --- a/frontend-js/src/main/js/minerva.js +++ b/frontend-js/src/main/js/minerva.js @@ -57,6 +57,54 @@ function processUrlGetParams(params) { } +function requestConsent(user, termsOfUseUrl) { + var dialog = document.createElement("div"); + var dialogContent = document.createElement("div"); + dialogContent.appendChild(functions.createElement({ + type: "span", + content: "I agree to the minerva <a href='" + termsOfUseUrl + "' target='_blank'>Terms of Use</a>." + })); + var checkbox = functions.createElement({type: "input", inputType: "checkbox", style: "float: left"}); + var okButton = functions.createElement({ + type: "button", + content: "OK", + className: "ui-button ui-corner-all ui-widget", + onclick: function () { + return ServerConnector.updateTermsOfUse(user, $(checkbox).is(':checked')).then(function () { + $(dialog).dialog("close"); + }).catch(GuiConnector.alert); + } + }); + var cancelButton = functions.createElement({ + type: "button", + content: "I disagree", + className: "ui-button ui-corner-all ui-widget", + onclick: function () { + return ServerConnector.logout().catch(GuiConnector.alert); + } + }); + $(okButton).prop("disabled", true); + $(checkbox).change(function () { + $(okButton).prop("disabled", !$(checkbox).is(':checked')); + }); + dialogContent.appendChild(checkbox); + dialogContent.appendChild(okButton); + dialogContent.appendChild(cancelButton); + + dialog.appendChild(dialogContent); + document.body.appendChild(dialog); + $(dialog).dialog({ + classes: { + "ui-dialog": "ui-state-error" + }, + modal: true, + closeOnEscape: false, + title: "Terms of Use" + }).siblings('.ui-dialog-titlebar').css("background", "red"); + $(".ui-dialog-titlebar-close", $(dialog).dialog().siblings('.ui-dialog-titlebar')).hide(); + $(dialog).dialog("open"); +} + function insertGoogleAnalyticsCode() { return ServerConnector.getConfigurationParam(ConfigurationType.GOOGLE_ANALYTICS_IDENTIFIER).then( function (identifier) { @@ -553,9 +601,14 @@ function create(params) { return customMap.openSubmap(submapId); } }).then(function () { + return ServerConnector.getLoggedUser(); + }).then(function (user) { if (leftPanel.isGoogleLicenseConsentRequired()) { GuiConnector.alert("Some data overlays doesn't have consent to the terms of the <a href='https://cloud.google.com/maps-platform/terms/' target='_blank'>license of Google Maps Platform</a>. To be able to visualize them you must edit data overlay. ") } + if (user.getLogin() !== "anonymous" && !user.isTermsOfUseConsent()) { + requestConsent(user, params.getConfiguration().getOption(ConfigurationType.TERMS_OF_USE)); + } var result = createResult(customMap); if (params.isDebug()) { diff --git a/model/src/main/java/lcsb/mapviewer/model/user/ConfigurationElementType.java b/model/src/main/java/lcsb/mapviewer/model/user/ConfigurationElementType.java index e38e7d1c1d1bfeb3bd8bf509ca6b4a193f2bf311..7937c6f9a5dfb0b05b81c2b7e55250ca9fa9b9fe 100644 --- a/model/src/main/java/lcsb/mapviewer/model/user/ConfigurationElementType.java +++ b/model/src/main/java/lcsb/mapviewer/model/user/ConfigurationElementType.java @@ -165,6 +165,11 @@ public enum ConfigurationElementType { + "(\"General overlays\") on this MINERVA server contain Protected Health Information (as defined in and subject to HIPAA).", "", ConfigurationElementEditType.STRING, false), + /** + * File where legend 4/4 is stored. + */ + TERMS_OF_USE("Terms of use file", "resources/other/terms_of_use.pdf", ConfigurationElementEditType.URL, false), + ; /** diff --git a/model/src/main/java/lcsb/mapviewer/model/user/User.java b/model/src/main/java/lcsb/mapviewer/model/user/User.java index 216c7837dbf52005d912857520f45af158284698..65d41b9972dd369814bf13b56f624c1ec4ec768e 100644 --- a/model/src/main/java/lcsb/mapviewer/model/user/User.java +++ b/model/src/main/java/lcsb/mapviewer/model/user/User.java @@ -92,6 +92,9 @@ public class User implements Serializable { */ private boolean removed = false; + @Column(name="terms_of_use_consent") + private boolean termsOfUseConsent = false; + /** * Set of user privileges. */ @@ -333,4 +336,12 @@ public class User implements Serializable { this.simpleColor = simpleColor; } + public boolean isTermsOfUseConsent() { + return termsOfUseConsent; + } + + public void setTermsOfUseConsent(boolean termsOfUseConsent) { + this.termsOfUseConsent = termsOfUseConsent; + } + } diff --git a/persist/src/db/11.1.0/fix_db_20180525.sql b/persist/src/db/11.1.0/fix_db_20180525.sql new file mode 100644 index 0000000000000000000000000000000000000000..70350604e3100f5731f53e5b0162df1098da67f7 --- /dev/null +++ b/persist/src/db/11.1.0/fix_db_20180525.sql @@ -0,0 +1 @@ +alter table user_table add column terms_of_use_consent boolean default false; diff --git a/rest-api/src/main/java/lcsb/mapviewer/api/projects/overlays/OverlayController.java b/rest-api/src/main/java/lcsb/mapviewer/api/projects/overlays/OverlayController.java index c2ac91b71fa855a848b67eec50bea82bed307d85..61db38733a967ded7a8cbbb616945f3bfb471cfe 100644 --- a/rest-api/src/main/java/lcsb/mapviewer/api/projects/overlays/OverlayController.java +++ b/rest-api/src/main/java/lcsb/mapviewer/api/projects/overlays/OverlayController.java @@ -100,10 +100,11 @@ public class OverlayController extends BaseController { @RequestParam(value = "name") String name, // @RequestParam(value = "description") String description, // @RequestParam(value = "content") String content, // - @RequestParam(value = "filename") String filename, // + @RequestParam(value = "filename") String filename, // + @RequestParam(value = "googleLicenseConsent") String googleLicenseConsent, // @RequestParam(value = "type", defaultValue = "") String type // ) throws SecurityException, QueryException, IOException { - return overlayRestImp.addOverlay(token, projectId, name, description, content, filename, type); + return overlayRestImp.addOverlay(token, projectId, name, description, content, filename, type, googleLicenseConsent); } @RequestMapping(value = "/projects/{projectId}/overlays/{overlayId}", method = { RequestMethod.DELETE }, produces = { MediaType.APPLICATION_JSON_VALUE }) diff --git a/rest-api/src/main/java/lcsb/mapviewer/api/projects/overlays/OverlayRestImpl.java b/rest-api/src/main/java/lcsb/mapviewer/api/projects/overlays/OverlayRestImpl.java index 6afdfab0642b513c632fbc78cbeb5bfc83a22edc..596b9b41cadffdbb22e25cc9dcbe8006efc3856e 100644 --- a/rest-api/src/main/java/lcsb/mapviewer/api/projects/overlays/OverlayRestImpl.java +++ b/rest-api/src/main/java/lcsb/mapviewer/api/projects/overlays/OverlayRestImpl.java @@ -212,7 +212,7 @@ public class OverlayRestImpl extends BaseRestImpl { this.layoutDao = layoutDao; } - public LayoutView addOverlay(String token, String projectId, String name, String description, String content, String filename, String type) + public LayoutView addOverlay(String token, String projectId, String name, String description, String content, String filename, String type, String googleLicenseConsent) throws SecurityException, QueryException, IOException { AuthenticationToken authenticationToken = getUserService().getToken(token); User user = getUserService().getUserByToken(token); @@ -239,7 +239,7 @@ public class OverlayRestImpl extends BaseRestImpl { LayoutView layout = layoutService.createLayout( new CreateLayoutParams() .async(false).colorInputStream(stream).description(description).layoutFileName(filename).model(model).name(name).user(user) - .colorSchemaType(colorSchemaType).directory(".")); + .colorSchemaType(colorSchemaType).directory(".").googleLicenseConsent(googleLicenseConsent.equalsIgnoreCase("true"))); return layoutService.getLayoutById(model, Integer.valueOf(layout.getIdObject()), authenticationToken); } catch (InvalidColorSchemaException e) { throw new QueryException(e.getMessage(), e); diff --git a/rest-api/src/main/java/lcsb/mapviewer/api/users/UserController.java b/rest-api/src/main/java/lcsb/mapviewer/api/users/UserController.java index ac97009a284e85a6ab2322187953fca0e40ae2ed..023ba7de7e9f5625b0801eba5b93b2bda856e2e6 100644 --- a/rest-api/src/main/java/lcsb/mapviewer/api/users/UserController.java +++ b/rest-api/src/main/java/lcsb/mapviewer/api/users/UserController.java @@ -91,6 +91,18 @@ public class UserController extends BaseController { result.put("status", "OK"); return result; } + + /** + * TODO remove when API 12 will be released!!! + */ + @RequestMapping(value = "/users/{login:.+}/updateTermsOfUse", method = { RequestMethod.GET, RequestMethod.POST }, produces = { MediaType.APPLICATION_JSON_VALUE }) + public Map<String, Object> updateTermsOfUse(@CookieValue(value = Configuration.AUTH_TOKEN) String token, + @PathVariable(value = "login") String login, // + @RequestParam(value = "value") String value, // + HttpServletResponse response // + ) throws SecurityException, ObjectNotFoundException { + return userRest.updateTermsOfUse(token, login, value); + } /** * @return the userService diff --git a/rest-api/src/main/java/lcsb/mapviewer/api/users/UserRestImpl.java b/rest-api/src/main/java/lcsb/mapviewer/api/users/UserRestImpl.java index bcd07b094b7ae9854fe2952e8f656493de13458f..bf94bdf6be3ab6e1535534f578fbb38e22ad8ec9 100644 --- a/rest-api/src/main/java/lcsb/mapviewer/api/users/UserRestImpl.java +++ b/rest-api/src/main/java/lcsb/mapviewer/api/users/UserRestImpl.java @@ -79,6 +79,7 @@ public class UserRestImpl { columnsSet.add("simpleColor"); columnsSet.add("removed"); columnsSet.add("privileges"); + columnsSet.add("termsOfUseConsent"); } else { for (String str : columns.split(",")) { columnsSet.add(str); @@ -108,8 +109,10 @@ public class UserRestImpl { value = user.getMaxColor(); } else if (column.equals("simplecolor")) { value = user.getSimpleColor(); - } else if (column.equals("removed")) { - value = user.isRemoved(); + } else if (column.equals("removed")) { + value = user.isRemoved(); + } else if (column.equals("termsofuseconsent")) { + value = user.isTermsOfUseConsent(); } else if (column.equals("privileges") && admin) { value = preparePrivileges(user); } else { @@ -172,4 +175,19 @@ public class UserRestImpl { this.layoutService = layoutService; } + public Map<String, Object> updateTermsOfUse(String token, String login, String value) throws SecurityException, ObjectNotFoundException { + User ownUserData = userService.getUserByToken(token); + if (ownUserData == null || ownUserData.getLogin().equals("anonymous")) { + throw new ObjectNotFoundException("User doesn't exist"); + } + if (!ownUserData.getLogin().equals(login)) { + throw new SecurityException("Access denied"); + } + ownUserData.setTermsOfUseConsent(value.equalsIgnoreCase("true")); + userService.updateUser(ownUserData); + Set<String> columnSet = createUserColumnSet(""); + + return prepareUse(ownUserData, columnSet, true); + } + } diff --git a/web/src/main/webapp/resources/other/terms_of_use.pdf b/web/src/main/webapp/resources/other/terms_of_use.pdf new file mode 100644 index 0000000000000000000000000000000000000000..21f7fb0abbde215ce1391ed614874e4060bcb94c Binary files /dev/null and b/web/src/main/webapp/resources/other/terms_of_use.pdf differ