/** * OLAT - Online Learning and Training<br> * http://www.olat.org * <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 * <p> * http://www.apache.org/licenses/LICENSE-2.0 * <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> * Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br> * University of Zurich, Switzerland. * <hr> * <a href="http://www.openolat.org"> * OpenOLAT - Online Learning and Training</a><br> * This file has been modified by the OpenOLAT community. Changes are licensed * under the Apache 2.0 license as the original file. */ package org.olat.admin.user.imp; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import org.apache.velocity.VelocityContext; import org.olat.basesecurity.Authentication; import org.olat.basesecurity.BaseSecurity; import org.olat.core.commons.persistence.DB; import org.olat.core.commons.persistence.DBFactory; import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.Component; import org.olat.core.gui.components.link.Link; import org.olat.core.gui.components.link.LinkFactory; import org.olat.core.gui.components.velocity.VelocityContainer; import org.olat.core.gui.control.Controller; import org.olat.core.gui.control.Event; import org.olat.core.gui.control.WindowControl; import org.olat.core.gui.control.controller.BasicController; import org.olat.core.gui.control.generic.wizard.Step; import org.olat.core.gui.control.generic.wizard.StepRunnerCallback; import org.olat.core.gui.control.generic.wizard.StepsMainRunController; import org.olat.core.gui.control.generic.wizard.StepsRunContext; import org.olat.core.gui.translator.Translator; import org.olat.core.helpers.Settings; import org.olat.core.id.Identity; import org.olat.core.id.User; import org.olat.core.id.UserConstants; import org.olat.core.util.StringHelper; import org.olat.core.util.Util; import org.olat.core.util.i18n.I18nManager; import org.olat.core.util.mail.MailBundle; import org.olat.core.util.mail.MailManager; import org.olat.core.util.mail.MailPackage; import org.olat.core.util.mail.MailTemplate; import org.olat.core.util.mail.MailerResult; import org.olat.group.BusinessGroupService; import org.olat.group.model.BusinessGroupMembershipChange; import org.olat.login.auth.OLATAuthManager; import org.olat.shibboleth.ShibbolethDispatcher; import org.olat.shibboleth.ShibbolethModule; import org.olat.user.UserManager; import org.olat.user.propertyhandlers.UserPropertyHandler; import org.springframework.beans.factory.annotation.Autowired; /** * Description:<br> * Bulk import and update of users. * * <P> * Initial Date: 17.08.2005 <br> * * @author Felix, Roman Haag * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com */ public class UserImportController extends BasicController { public static final String SHIBBOLETH_MARKER = "SHIBBOLETH::"; private List<UserPropertyHandler> userPropertyHandlers; private static final String usageIdentifyer = UserImportController.class.getCanonicalName(); private boolean canCreateOLATPassword; private VelocityContainer mainVC; private Link startLink; private StepsMainRunController importStepsController; @Autowired private DB dbInstance; @Autowired private UserManager um; @Autowired private MailManager mailService; @Autowired private BaseSecurity securityManager; @Autowired private OLATAuthManager olatAuthManager; @Autowired private ShibbolethModule shibbolethModule; @Autowired private BusinessGroupService businessGroupService; /** * @param ureq * @param wControl * @param canCreateOLATPassword true: workflow offers column to create * passwords; false: workflow does not offer pwd column */ public UserImportController(UserRequest ureq, WindowControl wControl, boolean canCreateOLATPassword) { super(ureq, wControl); this.canCreateOLATPassword = canCreateOLATPassword; mainVC = createVelocityContainer("importindex"); startLink = LinkFactory.createButton("import.start", mainVC, this); startLink.setElementCssClass("o_sel_id_start_import_user_button"); startLink.setPrimary(true); putInitialPanel(mainVC); } /** * @see org.olat.core.gui.control.DefaultController#event(org.olat.core.gui.UserRequest, * org.olat.core.gui.control.Controller, org.olat.core.gui.control.Event) */ @Override public void event(UserRequest ureq, Controller source, Event event) { if (source==importStepsController){ if (event == Event.CANCELLED_EVENT) { getWindowControl().pop(); removeAsListenerAndDispose(importStepsController); } else if (event == Event.CHANGED_EVENT || event == Event.DONE_EVENT) { getWindowControl().pop(); StepsRunContext ctxt = importStepsController.getRunContext(); ImportReport report = (ImportReport)ctxt.get("report"); removeAsListenerAndDispose(importStepsController); if(report.isHasErrors()) { StringBuilder errorMsg = new StringBuilder(); errorMsg.append("<ul>"); for(String error:report.getErrors()) { errorMsg.append("<li>").append(error).append("</li>"); } errorMsg.append("</ul>"); showError("import.errors", errorMsg.toString()); } else { showInfo("import.success"); } } } } private Identity doCreateAndPersistIdentity(TransientIdentity singleUser, ImportReport report) { // Create new user and identity and put user to users group String login = singleUser.getName(); //pos 0 is used for existing/non-existing user flag String pwd = singleUser.getPassword(); String lang = singleUser.getLanguage(); // use password only when configured to do so if (canCreateOLATPassword) { if (!StringHelper.containsNonWhitespace(pwd)) { // treat white-space passwords as no-password. This is fine, a password // can be set later on pwd = null; } } // Create transient user without firstName,lastName, email User newUser = um.createUser(null, null, null); List<UserPropertyHandler> userProperties = userPropertyHandlers; for (UserPropertyHandler userPropertyHandler : userProperties) { String thisValue = singleUser.getProperty(userPropertyHandler.getName(), null); String stringValue = userPropertyHandler.getStringValue(thisValue, getLocale()); userPropertyHandler.setUserProperty(newUser, stringValue); } // Init preferences newUser.getPreferences().setLanguage(lang); newUser.getPreferences().setInformSessionTimeout(true); // Save everything in database Identity ident; if(pwd != null && pwd.startsWith(SHIBBOLETH_MARKER) && shibbolethModule.isEnableShibbolethLogins()) { String uniqueID = pwd.substring(SHIBBOLETH_MARKER.length()); ident = securityManager.createAndPersistIdentityAndUserWithUserGroup(login, null, ShibbolethDispatcher.PROVIDER_SHIB, uniqueID, newUser); report.incrementCreatedUser(); report.incrementUpdatedShibboletAuthentication(); } else { ident = securityManager.createAndPersistIdentityAndUserWithDefaultProviderAndUserGroup(login, null, pwd, newUser); report.incrementCreatedUser(); } return ident; } private Identity doUpdateIdentity(UpdateIdentity userToUpdate, Boolean updateUsers, Boolean updatePassword, ImportReport report) { Identity identity; if(updateUsers != null && updateUsers.booleanValue()) { identity = userToUpdate.getIdentity(true); if(um.updateUserFromIdentity(identity)) { report.incrementUpdatedUser(); } } else { identity = userToUpdate.getIdentity(); } String password = userToUpdate.getPassword(); if(StringHelper.containsNonWhitespace(password)) { if(password.startsWith(SHIBBOLETH_MARKER) && shibbolethModule.isEnableShibbolethLogins()) { String uniqueID = password.substring(SHIBBOLETH_MARKER.length()); Authentication auth = securityManager.findAuthentication(identity, ShibbolethDispatcher.PROVIDER_SHIB); if(auth == null) { securityManager.createAndPersistAuthentication(identity, ShibbolethDispatcher.PROVIDER_SHIB, uniqueID, null, null); report.incrementUpdatedShibboletAuthentication(); } else if(!uniqueID.equals(auth.getAuthusername())) { //remove the old authentication securityManager.deleteAuthentication(auth); DBFactory.getInstance().commit(); //create the new one with the new authusername securityManager.createAndPersistAuthentication(identity, ShibbolethDispatcher.PROVIDER_SHIB, uniqueID, null, null); report.incrementUpdatedShibboletAuthentication(); } } else if(updatePassword != null && updatePassword.booleanValue()) { Authentication auth = securityManager.findAuthentication(identity, "OLAT"); if(auth != null) { olatAuthManager.changePassword(getIdentity(), identity, password); report.incrementUpdatedPassword(); } } } return userToUpdate.getIdentity(); } @Override protected void doDispose() { // child controllers disposed by basic controller } @Override protected void event(UserRequest ureq, Component source, Event event) { if (source == startLink) { doOpenImportWizard(ureq); } } private void doOpenImportWizard(UserRequest ureq) { // use fallback translator for user property translation setTranslator(um.getPropertyHandlerTranslator(getTranslator())); userPropertyHandlers = um.getUserPropertyHandlersFor(usageIdentifyer, true); Step start = new ImportStep00(ureq, canCreateOLATPassword); // callback executed in case wizard is finished. StepRunnerCallback finish = new StepRunnerCallback() { @Override public Step execute(UserRequest ureq1, WindowControl wControl1, StepsRunContext runContext) { // all information to do now is within the runContext saved ImportReport report = new ImportReport(); runContext.put("report", report); try { if (runContext.containsKey("validImport") && ((Boolean) runContext.get("validImport")).booleanValue()) { // create new users and persist int count = 0; @SuppressWarnings("unchecked") List<TransientIdentity> newIdents = (List<TransientIdentity>) runContext.get("newIdents"); Map<TransientIdentity,Identity> newPersistedIdentities = new HashMap<>(); for (TransientIdentity newIdent:newIdents) { Identity newIdentity = doCreateAndPersistIdentity(newIdent, report); if(newIdentity != null) { newPersistedIdentities.put(newIdent, newIdentity); } if(++count % 10 == 0) { dbInstance.commitAndCloseSession(); } } dbInstance.commitAndCloseSession(); Boolean updateUsers = (Boolean)runContext.get("updateUsers"); Boolean updatePasswords = (Boolean)runContext.get("updatePasswords"); @SuppressWarnings("unchecked") List<UpdateIdentity> updateIdents = (List<UpdateIdentity>) runContext.get("updateIdents"); for (UpdateIdentity updateIdent:updateIdents) { doUpdateIdentity(updateIdent, updateUsers, updatePasswords, report); if(++count % 10 == 0) { dbInstance.commitAndCloseSession(); } } dbInstance.commitAndCloseSession(); @SuppressWarnings("unchecked") List<Long> ownGroups = (List<Long>) runContext.get("ownerGroups"); @SuppressWarnings("unchecked") List<Long> partGroups = (List<Long>) runContext.get("partGroups"); if ((ownGroups != null && ownGroups.size() > 0) || (partGroups != null && partGroups.size() > 0)) { @SuppressWarnings("unchecked") List<Identity> allIdents = (List<Identity>) runContext.get("idents"); Boolean sendMailObj = (Boolean)runContext.get("sendMail"); boolean sendmail = sendMailObj == null ? true : sendMailObj.booleanValue(); processGroupAdditionForAllIdents(allIdents, ownGroups, partGroups, sendmail); } else { Boolean sendMailObj = (Boolean)runContext.get("sendMail"); if(sendMailObj != null && sendMailObj) { sendMailToNewIdentities(newPersistedIdentities); } } report.setHasChanges(true); } } catch (Exception any) { logError("", any); report.addError("Unexpected error, see log files or call your system administrator"); } // signal correct completion and tell if changes were made or not. return report.isHasChanges() ? StepsMainRunController.DONE_MODIFIED : StepsMainRunController.DONE_UNCHANGED; } }; importStepsController = new StepsMainRunController(ureq, getWindowControl(), start, finish, null, translate("title"), "o_sel_user_import_wizard"); listenTo(importStepsController); getWindowControl().pushAsModalDialog(importStepsController.getInitialComponent()); } private Collection<Identity> getIdentities(List<Identity> allIdents) { Set<Identity> identities = new HashSet<Identity>(allIdents.size()); List<String> usernames = new ArrayList<String>(); for (Object o : allIdents) { if(o instanceof TransientIdentity) { TransientIdentity transIdent = (TransientIdentity)o; usernames.add(transIdent.getName()); } else if (o instanceof UpdateIdentity) { identities.add(((UpdateIdentity)o).getIdentity()); } else if (o instanceof Identity) { identities.add((Identity)o); } } List<Identity> nextIds = securityManager.findIdentitiesByNameCaseInsensitive(usernames); identities.addAll(nextIds); return identities; } private void sendMailToNewIdentities(Map<TransientIdentity,Identity> newIdentities) { MailerResult result = new MailerResult(); for(Map.Entry<TransientIdentity, Identity> newEntry:newIdentities.entrySet()) { if(newEntry.getKey() != null && newEntry.getValue() != null) { Identity newIdentity = newEntry.getValue(); MailTemplate template = createMailTemplateForNewIdentity(newIdentity, newEntry.getKey()); MailBundle bundle = mailService.makeMailBundle(null, newIdentity, template, getIdentity(), null, result); if(bundle != null) { mailService.sendExternMessage(bundle, result, true); } } } } private MailTemplate createMailTemplateForNewIdentity(Identity identity, TransientIdentity transientIdentity) { // get some data about the actor and fetch the translated subject / body via i18n module String[] bodyArgs = new String[] { identity.getName(), //{0} identity.getUser().getProperty(UserConstants.FIRSTNAME, null), //{1} identity.getUser().getProperty(UserConstants.LASTNAME, null), //{2} identity.getUser().getProperty(UserConstants.EMAIL, null), //{3} Settings.getServerContextPathURI(), //{4} transientIdentity.getPassword() //{5} }; Locale locale = I18nManager.getInstance().getLocaleOrDefault(identity.getUser().getPreferences().getLanguage()); Translator translator = Util.createPackageTranslator(UserImportController.class, locale); String subject = translator.translate("mail.new.identity.subject"); String body = translator.translate("mail.new.identity.text", bodyArgs); // create a mail template which all these data MailTemplate mailTempl = new MailTemplate(subject, body, null) { @Override public void putVariablesInMailContext(VelocityContext context, Identity identity) { // Put user variables into velocity context User user = identity.getUser(); context.put("firstname", user.getProperty(UserConstants.FIRSTNAME, null)); context.put("lastname", user.getProperty(UserConstants.LASTNAME, null)); //the email of the user, needs to stay named 'login' context.put("login", user.getProperty(UserConstants.EMAIL, null)); } }; return mailTempl; } private void processGroupAdditionForAllIdents(List<Identity> allIdents, List<Long> tutorGroups, List<Long> partGroups, boolean sendmail) { Collection<Identity> identities = getIdentities(allIdents); List<BusinessGroupMembershipChange> changes = new ArrayList<BusinessGroupMembershipChange>(); for(Identity identity:identities) { if(tutorGroups != null && !tutorGroups.isEmpty()) { for(Long tutorGroupKey:tutorGroups) { BusinessGroupMembershipChange change = new BusinessGroupMembershipChange(identity, tutorGroupKey); change.setTutor(Boolean.TRUE); changes.add(change); } } if(partGroups != null && !partGroups.isEmpty()) { for(Long partGroupKey:partGroups) { BusinessGroupMembershipChange change = new BusinessGroupMembershipChange(identity, partGroupKey); change.setParticipant(Boolean.TRUE); changes.add(change); } } } MailPackage mailing = new MailPackage(sendmail); businessGroupService.updateMemberships(getIdentity(), changes, mailing); DBFactory.getInstance().commit(); } public static class ImportReport { private boolean hasChanges = false; private boolean hasErrors = false; private AtomicInteger updatedUser = new AtomicInteger(0); private AtomicInteger createdUser = new AtomicInteger(0); private AtomicInteger updatedPassword = new AtomicInteger(0); private AtomicInteger updatedShibboletAuthentication = new AtomicInteger(0); private List<String> errors = new ArrayList<>(); public boolean isHasChanges() { return hasChanges; } public void setHasChanges(boolean hasChanges) { this.hasChanges = hasChanges; } public boolean isHasErrors() { return hasErrors; } public void setHasErrors(boolean hasErrors) { this.hasErrors = hasErrors; } public List<String> getErrors() { return errors; } public void addError(String error) { if(StringHelper.containsNonWhitespace(error)) { errors.add(error); hasErrors = true; } } public int getNumOfUpdatedUser() { return updatedUser.get(); } public void incrementUpdatedUser() { updatedUser.incrementAndGet(); } public int getCreatedUser() { return createdUser.get(); } public void incrementCreatedUser() { createdUser.incrementAndGet(); } public int getUpdatedPassword() { return updatedPassword.get(); } public void incrementUpdatedPassword() { updatedPassword.incrementAndGet(); } public int getUpdatedShibboletAuthentication() { return updatedShibboletAuthentication.get(); } public void incrementUpdatedShibboletAuthentication() { updatedShibboletAuthentication.incrementAndGet(); } } }