diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/CommonServer.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/CommonServer.java index da43cb1ebe714c397e7e26633280c3ed4da1cd4c..29964eaaf15113af4fe14598bc309dda0a9eca52 100644 --- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/CommonServer.java +++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/CommonServer.java @@ -528,7 +528,7 @@ public final class CommonServer extends AbstractCommonServer<ICommonServerForInt } @Override - @RolesAllowed(RoleWithHierarchy.INSTANCE_ADMIN) + @RolesAllowed(RoleWithHierarchy.INSTANCE_ETL_SERVER) public List<Person> listPersons(String sessionToken) { checkSession(sessionToken); diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/controllers/MainController.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/controllers/MainController.js index e8a4a0449357a11a5c5f988c41c1b58e0247e83e..75ba7362cd42cb260e24ae00a3936823d7cfdd48 100644 --- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/controllers/MainController.js +++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/controllers/MainController.js @@ -37,22 +37,14 @@ function MainController(profile) { this.openbisV1 = new openbis(); this.openbisV3 = null; - - + + this.loadV3 = function(callback) { - require(['openbis'], function(openbis) { - //Boilerplate - var testProtocol = window.location.protocol; - var testHost = window.location.hostname; - var testPort = window.location.port; - - var testUrl = testProtocol + "//" + testHost + ":" + testPort; - var testApiUrl = testUrl + "/openbis/openbis/rmi-application-server-v3.json"; - - mainController.openbisV3 = new openbis(testApiUrl); - mainController.openbisV3._private.sessionToken = mainController.serverFacade.getSession(); - callback(); - }); + this.serverFacade.getOpenbisV3(function(openbisV3) { + mainController.openbisV3 = openbisV3 + mainController.openbisV3._private.sessionToken = mainController.serverFacade.getSession(); + callback(); + }); }; // Server Facade Object @@ -100,9 +92,12 @@ function MainController(profile) { $("#username").focus(); var callback = function() {Util.unblockUI();}; Util.showError('The given username or password is not correct.', callback); + this.serverFacade.doIfFileAuthenticationService((function() { + this._enablePasswordResetLink(); + }).bind(this)); return; } - + // // Back Button Logic // @@ -207,7 +202,50 @@ function MainController(profile) { ); }); } - + + this._enablePasswordResetLink = function() { + + var userId = $("#username").val(); + var $container = $("#password-reset-container"); + $container.empty(); + + $resetLink = $("<a>").text("reset password by email for user " + userId); + $container.append($resetLink); + var height = $container.height(); + $container.css({ "margin-top" : -height + "px" }); + + $resetLink.on("click", (function() { + Util.blockUI(); + this.serverFacade.sendResetPasswordEmail(userId, function() { + Util.unblockUI(); + Util.showInfo("An email with instructions how to reset the password has been sent to " + userId + " if this user exists."); + }); + + }).bind(this)); + + } + + this.resetPasswordRequested = function() { + var queryString = Util.queryString(); + return queryString.resetPassword == "true"; + } + + this.resetPassword = function() { + var queryString = Util.queryString(); + var userId = queryString.userId; + var token = queryString.token; + + if (userId && token) { + Util.blockUI(); + this.serverFacade.resetPassword(userId, token, function() { + Util.unblockUI(); + Util.showInfo("A new password has been sent as an email to user " + userId + " if this user exists."); + }); + } else { + Util.showError("To reset the password, the parameters 'userId' and 'token' need to be set."); + } + } + // // Main View Changer - Everything on the application rely on this method to alter the views, arg should be a string // diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/server/ServerFacade.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/server/ServerFacade.js index dc293a163d596794fe4898a0df8025efbf07ddaf..9b942f6316d8db0fc9d65f9e78d2f40ea0c49de4 100644 --- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/server/ServerFacade.js +++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/server/ServerFacade.js @@ -26,8 +26,26 @@ */ function ServerFacade(openbisServer) { this.openbisServer = openbisServer; - + + // + // V3 API creation // + this.getOpenbisV3 = function(callbackFunction) { + require(['openbis'], function(openbis) { + //Boilerplate + var testProtocol = window.location.protocol; + var testHost = window.location.hostname; + var testPort = window.location.port; + + var testUrl = testProtocol + "//" + testHost + ":" + testPort; + var testApiUrl = testUrl + "/openbis/openbis/rmi-application-server-v3.json"; + + var openbisV3 = new openbis(testApiUrl); + callbackFunction(openbisV3); + }); + } + + // // Intercepting general errors // var responseInterceptor = function(response, action){ @@ -560,6 +578,66 @@ function ServerFacade(openbisServer) { // // ELN Custom API // + + this.sendResetPasswordEmail = function(userId, callbackFunction) { + var parameters = { + method : "sendResetPasswordEmail", + userId : userId, + baseUrl : location.protocol + '//' + location.host + location.pathname + }; + this._callPasswordResetService(parameters, callbackFunction); + } + + this.resetPassword = function(userId, token, callbackFunction) { + var parameters = { + method : "resetPassword", + userId : userId, + token : token + }; + this._callPasswordResetService(parameters, callbackFunction); + } + + this.doIfFileAuthenticationService = function(callbackFunction) { + var _this = this; + this.getOpenbisV3(function(openbisV3) { + openbisV3.loginAsAnonymousUser().done(function(sessionToken) { + openbisV3.getServerInformation().done(function(serverInformation) { + var authSystem = serverInformation["authentication-service"]; + if (authSystem && authSystem.indexOf("file") !== -1) { + callbackFunction(); + } + }); + }).fail(function(result) { + console.log("Call failed to server: " + JSON.stringify(result)); + }); + }); + } + + this._callPasswordResetService = function(parameters, callbackFunction) { + var _this = this; + this.getOpenbisV3(function(openbisV3) { + + openbisV3.loginAsAnonymousUser().done(function(sessionToken) { + _this.openbisServer._internal.sessionToken = sessionToken; + + _this.listDataStores(function(dataStores) { + profile.allDataStores = dataStores.result; + _this.customELNApi(parameters, function(error, result) { + if (error) { + Util.showError(error); + } else { + callbackFunction(result); + } + }, "password-reset-api"); + }); + + }).fail(function(result) { + console.log("Call failed to server: " + JSON.stringify(result)); + }); + + }); + } + this.customELNApi = function(parameters, callbackFunction, service) { if(!service) { service = "eln-lims-api"; diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/password-reset-api/lib/persistentkeyvaluestore-source/PersistentKeyValueStore.java b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/password-reset-api/lib/persistentkeyvaluestore-source/PersistentKeyValueStore.java new file mode 100644 index 0000000000000000000000000000000000000000..c1aec05d6bbd423904e249ee5c068aa4ce0371ba --- /dev/null +++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/password-reset-api/lib/persistentkeyvaluestore-source/PersistentKeyValueStore.java @@ -0,0 +1,27 @@ +package ch.ethz.sis; + +import java.io.Serializable; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + + + +public class PersistentKeyValueStore { + private static ConcurrentMap<String, Serializable> keyStore = new ConcurrentHashMap<>(); + + public static void put(String key, Serializable value) { + keyStore.put(key, value); + } + + public static Serializable get(String key) { + return keyStore.get(key); + } + + public static void remove(String key) { + keyStore.remove(key); + } + + public static boolean containsKey(String key) { + return keyStore.containsKey(key); + } +} diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/password-reset-api/lib/persistentkeyvaluestore.jar b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/password-reset-api/lib/persistentkeyvaluestore.jar new file mode 100644 index 0000000000000000000000000000000000000000..d3f77f6a23766b0947ddd07d80311cdfe1069101 Binary files /dev/null and b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/password-reset-api/lib/persistentkeyvaluestore.jar differ diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/password-reset-api/password-reset-api.py b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/password-reset-api/password-reset-api.py new file mode 100644 index 0000000000000000000000000000000000000000..486988df762a7fdf8297c09894d403c9566a9651 --- /dev/null +++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/password-reset-api/password-reset-api.py @@ -0,0 +1,130 @@ +from ch.systemsx.cisd.openbis.dss.generic.server import DataStoreServer +from ch.systemsx.cisd.openbis.generic.shared.api.v1 import IGeneralInformationService; +from ch.systemsx.cisd.common.exceptions import UserFailureException +from ch.ethz.sis import PersistentKeyValueStore +from ch.ethz.sis.openbis.generic.server.sharedapi.v3.json import GenericObjectMapper; +from ch.systemsx.cisd.openbis.common.api.client import ServiceFinder; +from ch.systemsx.cisd.common.mail import EMailAddress; +from java.lang import String +from java.util import UUID +from random import SystemRandom +import subprocess, os, string, time + + +passwdShPath = '../openBIS-server/jetty/bin/passwd.sh' +RESET_TOKEN_KEY_POSTFIX = "-reset-token" +OPENBISURL = DataStoreServer.getConfigParameters().getServerURL() + "/openbis/openbis" + +# +# API functions +# + +def process(tr, parameters, tableBuilder): + if (parameters["method"] == "sendResetPasswordEmail"): + if (parameters["userId"] is not None and parameters["baseUrl"] is not None): + sendResetPasswordEmail(tr, parameters["userId"], parameters["baseUrl"]) + else: + raise UserFailureException("When invoking method 'sendResetPasswordEmail', the parameter 'userId' is required.") + elif (parameters["method"] == "resetPassword"): + if (parameters["userId"] is not None and parameters["token"] is not None): + resetPassword(tr, parameters["userId"], parameters["token"]) + else: + raise UserFailureException("When invoking method 'resetPassword', the parameters 'userId' and 'token' are required.") + else: + raise UserFailureException("Unknown method: " + parameters["method"]) + + tableBuilder.addHeader("STATUS"); + tableBuilder.addHeader("MESSAGE"); + tableBuilder.addHeader("RESULT"); + row = tableBuilder.addRow(); + row.setCell("STATUS","OK"); + row.setCell("MESSAGE", "Operation Successful"); + row.setCell("RESULT", getJsonForData( { "result" : "success" })); + + +def sendResetPasswordEmail(tr, userId, baseUrl): + print("sendResetPasswordEmail") + # generate and store token + token = UUID.randomUUID().toString() + timestamp = time.time() + print("timestamp: " + str(timestamp)) + PersistentKeyValueStore.put(userId + RESET_TOKEN_KEY_POSTFIX, { "token" : token, "timestamp" : timestamp}) + # send email + emailAddress = getUserEmail(tr, userId) + sendResetPasswordEmailInternal(tr, emailAddress, userId, token, baseUrl) + +def resetPassword(tr, userId, token): + if tokenIsValid(userId, token): + email = getUserEmail(tr, userId) + resetPasswordInternal(tr, email, userId) + PersistentKeyValueStore.remove(userId + RESET_TOKEN_KEY_POSTFIX) + else: + raise UserFailureException("Invalid token.") + +# +# internal functions +# + +def sendMail(tr, email, subject, body): + replyTo = None; + fromAddress = None; + recipient1 = EMailAddress(email); + tr.getGlobalState().getMailClient().sendEmailMessage(subject, body, replyTo, fromAddress, recipient1); + # TODO don't print message - contains password + print "--- MAIL ---" + " Recipient: " + email + " Topic: " + subject + " Message: " + body + +def getJsonForData(data): + objectMapper = GenericObjectMapper(); + jsonValue = objectMapper.writeValueAsString(data); + return jsonValue; + +def tokenIsValid(userId, token): + return PersistentKeyValueStore.get(userId + RESET_TOKEN_KEY_POSTFIX) == token + +def sendResetPasswordEmailInternal(tr, email, userId, token, baseUrl): + passwordResetLink = getPasswordResetLink(email, userId, token, baseUrl) + passwordResetRequestSubject = getProperty(tr, "password-reset-request-subject") % (userId) + passwordResetRequestBody = getProperty(tr, "password-reset-request-body") % (userId, passwordResetLink) + sendMail(tr, email, passwordResetRequestSubject, passwordResetRequestBody) + +def sendEmailWithNewPassword(tr, email, userId, newPassword): + newPasswordSubject = getProperty(tr, "new-password-subject") % (userId) + newPasswordBody = getProperty(tr, "new-password-body") % (newPassword) + sendMail(tr, email, newPasswordSubject, newPasswordBody) + +def getPasswordResetLink(emailAddress, userId, token, baseUrl): + return "%s?resetPassword=true&&userId=%s&token=%s" % (baseUrl, userId, token) + +def resetPasswordInternal(tr, email, userId): + newPassword = getNewPassword() + updateUserPassword(userId, newPassword) + sendEmailWithNewPassword(tr, email, userId, newPassword) + +def getUserEmail(tr, userId): + for person in getPersons(tr): + if person.getUserId() == userId: + return person.getEmail() + raise UserFailureException("User email not found.") + +def getPersons(tr): + servFinder = ServiceFinder("openbis", IGeneralInformationService.SERVICE_URL); + infService = servFinder.createService(IGeneralInformationService, OPENBISURL); + return infService.listPersons(tr.getOpenBisServiceSessionToken()); + +def getNewPassword(): + length = 12 + rng = SystemRandom() + chars = string.ascii_letters + string.digits + '!@#$%^&*()' + return ''.join(rng.choice(chars) for i in range(length)) + +def updateUserPassword(userId, password): + if os.path.isfile(passwdShPath): + subprocess.call([path, 'change', userId, '-p', password]) #Changes the user pass, works always + return True; + else: + return False; + +def getProperty(tr, key): + threadPropertyDict = {} + threadProperties = tr.getGlobalState().getThreadParameters().getThreadProperties() + return threadProperties.getProperty(key) diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/password-reset-api/plugin.properties b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/password-reset-api/plugin.properties new file mode 100644 index 0000000000000000000000000000000000000000..18c916fc9a59e026ae19951400e3bf2897d5a9ec --- /dev/null +++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/password-reset-api/plugin.properties @@ -0,0 +1,7 @@ +label = Password Reset API +class = ch.systemsx.cisd.openbis.dss.generic.server.plugins.jython.JythonIngestionService +script-path = password-reset-api.py +password-reset-request-subject = ELN-LIMS password reset for %s +password-reset-request-body = Hi,\n\nA request has been made to reset the password of user %s.\n\nClick on this link in order to get a new password:\n%s\n\nSincere regards,\nYour ELN-LIMS Team +new-password-subject = ELN-LIMS new password for %s +new-password-body = Hi,\n\nYour new password is: %s\n\nPlease login and change it immediately.\n\nSincere regards,\nYour ELN-LIMS Team