From 84908f97631741cfbd6145cb9c828c53206d4a66 Mon Sep 17 00:00:00 2001
From: srosse <none@none>
Date: Tue, 7 Feb 2017 15:18:08 +0100
Subject: [PATCH] OO-2452: implement reset of password with SMS confirmation /
 authentication

---
 .../bps/olat/user/ChangeEMailController.java  |   4 +-
 .../delete/service/UserDeletionManager.java   |   4 +-
 .../services/_spring/servicesCorecontext.xml  |   2 +
 .../core/commons/services/sms/MessageLog.java |  70 ++++
 .../commons/services/sms/MessagesSPI.java     |  38 +++
 .../services/sms/SimpleMessageException.java  |  28 ++
 .../services/sms/SimpleMessageModule.java     | 132 ++++++++
 .../services/sms/SimpleMessageService.java    |  76 +++++
 .../services/sms/_spring/smsCorecontext.xml   |  58 ++++
 .../services/sms/manager/MessageLogDAO.java   | 123 +++++++
 .../sms/manager/SimpleMessageServiceImpl.java | 166 ++++++++++
 .../services/sms/model/MessageLogImpl.java    | 159 +++++++++
 .../services/sms/model/MessageStatistics.java |  34 ++
 .../services/sms/spi/WebSMSProvider.java      | 168 ++++++++++
 .../sms/ui/MessageStatisticsDataModel.java    |  86 +++++
 .../sms/ui/MessagesStatisticsController.java  |  88 +++++
 .../services/sms/ui/MonthCellRenderer.java    |  35 ++
 .../services/sms/ui/SMSPhoneController.java   | 157 +++++++++
 ...geServiceAdminConfigurationController.java | 140 ++++++++
 .../SimpleMessageServiceAdminController.java  | 102 ++++++
 .../services/sms/ui/YearCellRenderer.java     |  30 ++
 .../services/sms/ui/_content/admin.html       |   4 +
 .../services/sms/ui/_content/statistics.html  |   1 +
 .../sms/ui/_i18n/LocalStrings_de.properties   |  25 ++
 .../sms/ui/_i18n/LocalStrings_en.properties   |  25 ++
 .../sms/ui/_i18n/LocalStrings_fr.properties   |   1 +
 .../java/org/olat/core/id/UserConstants.java  |   2 +
 .../ui/CheckboxAssessmentDataModelSorter.java |  19 ++
 .../ldap/ui/LDAPAuthenticationController.java |   4 +-
 .../login/LoginAuthprovidersController.java   |   2 +-
 .../org/olat/login/auth/OLATAuthManager.java  |   8 +-
 .../registration/ConfirmTokenController.java  |  91 +++++
 .../olat/registration/PwChangeController.java | 312 +++++++++++-------
 .../org/olat/registration/PwChangeForm.java   |  52 ++-
 .../registration/RegistrationController.java  |   2 +-
 .../registration/RegistrationManager.java     |  75 ++---
 .../registration/SendMessageController.java   |  89 +++++
 .../org/olat/registration/TemporaryKey.java   |  23 +-
 .../registration/TemporaryKeyImpl.hbm.xml     |  45 ---
 .../olat/registration/TemporaryKeyImpl.java   | 154 +++++----
 .../olat/registration/_content/pwchange.html  |   4 +-
 .../registration/_content/send_message.html   |   5 +
 .../_i18n/LocalStrings_de.properties          |  54 +--
 .../_i18n/LocalStrings_en.properties          |  35 +-
 .../org/olat/user/ProfileFormController.java  |   5 +-
 src/main/java/org/olat/user/UserImpl.java     |   4 +
 .../SmsPhonePropertyHandler.java              |  64 ++++
 .../UserInterestsElement.java                 |   6 -
 .../UserPropertyUsageContext.java             |  10 +
 .../_i18n/LocalStrings_de.properties          |  73 ++--
 .../_i18n/LocalStrings_en.properties          |  17 +-
 .../_spring/userPropertiesContext.xml         |   1 +
 .../userPropertriesHandlersContext.xml        |   6 +
 .../ui/SmsPhoneComponent.java                 |  50 +++
 .../ui/SmsPhoneComponentRenderer.java         |  64 ++++
 .../ui/SmsPhoneConfirmController.java         |  85 +++++
 .../ui/SmsPhoneController.java                |  90 +++++
 .../propertyhandlers/ui/SmsPhoneElement.java  | 167 ++++++++++
 .../ui/SmsPhoneSendController.java            | 131 ++++++++
 .../ui/_content/edit_sms.html                 |   1 +
 .../ui/_i18n/LocalStrings_de.properties       |  14 +-
 .../ui/_i18n/LocalStrings_en.properties       |  17 +-
 src/main/resources/META-INF/persistence.xml   |   3 +-
 .../database/mysql/alter_11_2_x_to_11_3_0.sql |  22 +-
 .../database/mysql/setupDatabase.sql          |  18 +-
 .../oracle/alter_11_2_x_to_11_3_0.sql         |  19 +-
 .../database/oracle/setupDatabase.sql         |  17 +
 .../postgresql/alter_11_2_x_to_11_3_0.sql     |  28 +-
 .../database/postgresql/setupDatabase.sql     |  24 +-
 .../resources/serviceconfig/olat.properties   |  14 +
 .../sms/manager/MessageLogDAOTest.java        |  87 +++++
 .../services/sms/spi/WebSMSProviderTest.java  |  48 +++
 .../registration/RegistrationManagerTest.java | 103 +++---
 .../java/org/olat/test/AllTestsJunit4.java    |   1 +
 74 files changed, 3451 insertions(+), 470 deletions(-)
 create mode 100644 src/main/java/org/olat/core/commons/services/sms/MessageLog.java
 create mode 100644 src/main/java/org/olat/core/commons/services/sms/MessagesSPI.java
 create mode 100644 src/main/java/org/olat/core/commons/services/sms/SimpleMessageException.java
 create mode 100644 src/main/java/org/olat/core/commons/services/sms/SimpleMessageModule.java
 create mode 100644 src/main/java/org/olat/core/commons/services/sms/SimpleMessageService.java
 create mode 100644 src/main/java/org/olat/core/commons/services/sms/_spring/smsCorecontext.xml
 create mode 100644 src/main/java/org/olat/core/commons/services/sms/manager/MessageLogDAO.java
 create mode 100644 src/main/java/org/olat/core/commons/services/sms/manager/SimpleMessageServiceImpl.java
 create mode 100644 src/main/java/org/olat/core/commons/services/sms/model/MessageLogImpl.java
 create mode 100644 src/main/java/org/olat/core/commons/services/sms/model/MessageStatistics.java
 create mode 100644 src/main/java/org/olat/core/commons/services/sms/spi/WebSMSProvider.java
 create mode 100644 src/main/java/org/olat/core/commons/services/sms/ui/MessageStatisticsDataModel.java
 create mode 100644 src/main/java/org/olat/core/commons/services/sms/ui/MessagesStatisticsController.java
 create mode 100644 src/main/java/org/olat/core/commons/services/sms/ui/MonthCellRenderer.java
 create mode 100644 src/main/java/org/olat/core/commons/services/sms/ui/SMSPhoneController.java
 create mode 100644 src/main/java/org/olat/core/commons/services/sms/ui/SimpleMessageServiceAdminConfigurationController.java
 create mode 100644 src/main/java/org/olat/core/commons/services/sms/ui/SimpleMessageServiceAdminController.java
 create mode 100644 src/main/java/org/olat/core/commons/services/sms/ui/YearCellRenderer.java
 create mode 100644 src/main/java/org/olat/core/commons/services/sms/ui/_content/admin.html
 create mode 100644 src/main/java/org/olat/core/commons/services/sms/ui/_content/statistics.html
 create mode 100644 src/main/java/org/olat/core/commons/services/sms/ui/_i18n/LocalStrings_de.properties
 create mode 100644 src/main/java/org/olat/core/commons/services/sms/ui/_i18n/LocalStrings_en.properties
 create mode 100644 src/main/java/org/olat/core/commons/services/sms/ui/_i18n/LocalStrings_fr.properties
 create mode 100644 src/main/java/org/olat/registration/ConfirmTokenController.java
 create mode 100644 src/main/java/org/olat/registration/SendMessageController.java
 delete mode 100644 src/main/java/org/olat/registration/TemporaryKeyImpl.hbm.xml
 create mode 100644 src/main/java/org/olat/registration/_content/send_message.html
 create mode 100644 src/main/java/org/olat/user/propertyhandlers/SmsPhonePropertyHandler.java
 create mode 100644 src/main/java/org/olat/user/propertyhandlers/ui/SmsPhoneComponent.java
 create mode 100644 src/main/java/org/olat/user/propertyhandlers/ui/SmsPhoneComponentRenderer.java
 create mode 100644 src/main/java/org/olat/user/propertyhandlers/ui/SmsPhoneConfirmController.java
 create mode 100644 src/main/java/org/olat/user/propertyhandlers/ui/SmsPhoneController.java
 create mode 100644 src/main/java/org/olat/user/propertyhandlers/ui/SmsPhoneElement.java
 create mode 100644 src/main/java/org/olat/user/propertyhandlers/ui/SmsPhoneSendController.java
 create mode 100644 src/main/java/org/olat/user/propertyhandlers/ui/_content/edit_sms.html
 create mode 100644 src/test/java/org/olat/core/commons/services/sms/manager/MessageLogDAOTest.java
 create mode 100644 src/test/java/org/olat/core/commons/services/sms/spi/WebSMSProviderTest.java

diff --git a/src/main/java/de/bps/olat/user/ChangeEMailController.java b/src/main/java/de/bps/olat/user/ChangeEMailController.java
index 85cc03d43b3..0b054aeb0d1 100644
--- a/src/main/java/de/bps/olat/user/ChangeEMailController.java
+++ b/src/main/java/de/bps/olat/user/ChangeEMailController.java
@@ -34,7 +34,7 @@ import org.olat.core.id.Identity;
 import org.olat.core.id.User;
 import org.olat.core.util.Util;
 import org.olat.registration.RegistrationManager;
-import org.olat.registration.TemporaryKeyImpl;
+import org.olat.registration.TemporaryKey;
 import org.olat.user.ProfileAndHomePageEditController;
 import org.olat.user.UserManager;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -56,7 +56,7 @@ public class ChangeEMailController extends DefaultController {
 	
 	protected Translator pT;
 	protected String emKey;
-	protected TemporaryKeyImpl tempKey;
+	protected TemporaryKey tempKey;
 	protected UserRequest userRequest;
 	
 	@Autowired
diff --git a/src/main/java/org/olat/admin/user/delete/service/UserDeletionManager.java b/src/main/java/org/olat/admin/user/delete/service/UserDeletionManager.java
index 926b8414772..ed590c78572 100644
--- a/src/main/java/org/olat/admin/user/delete/service/UserDeletionManager.java
+++ b/src/main/java/org/olat/admin/user/delete/service/UserDeletionManager.java
@@ -65,7 +65,7 @@ import org.olat.core.util.mail.MailerResult;
 import org.olat.properties.Property;
 import org.olat.properties.PropertyManager;
 import org.olat.registration.RegistrationManager;
-import org.olat.registration.TemporaryKeyImpl;
+import org.olat.registration.TemporaryKey;
 import org.olat.repository.RepositoryDeletionModule;
 import org.olat.user.UserDataDeletable;
 import org.olat.user.UserManager;
@@ -294,7 +294,7 @@ public class UserDeletionManager extends BasicManager {
 		groupDao.removeMemberships(identity);
 
 		String key = identity.getUser().getProperty("emchangeKey", null);
-		TemporaryKeyImpl tempKey = registrationManager.loadTemporaryKeyByRegistrationKey(key);
+		TemporaryKey tempKey = registrationManager.loadTemporaryKeyByRegistrationKey(key);
 		if (tempKey != null) {
 			registrationManager.deleteTemporaryKey(tempKey);
 		}		
diff --git a/src/main/java/org/olat/core/commons/services/_spring/servicesCorecontext.xml b/src/main/java/org/olat/core/commons/services/_spring/servicesCorecontext.xml
index fb01bf8cefd..2c74e196d1b 100644
--- a/src/main/java/org/olat/core/commons/services/_spring/servicesCorecontext.xml
+++ b/src/main/java/org/olat/core/commons/services/_spring/servicesCorecontext.xml
@@ -12,6 +12,7 @@
 	<import resource="classpath:/org/olat/core/commons/services/scheduler/_spring/schedulerContext.xml"/>
 	<import resource="classpath:/org/olat/core/commons/services/taskexecutor/_spring/taskExecutorCorecontext.xml"/>
 	<import resource="classpath:/org/olat/core/commons/services/notifications/_spring/notificationsContext.xml"/>
+	<import resource="classpath:/org/olat/core/commons/services/sms/_spring/smsCorecontext.xml"/>
 	
 	<bean id="imageHelper" class="org.olat.core.commons.services.image.ImageHelperBean">
 		<property name="imageHelperServiceProvider" ref="imageHelperServiceProvider_${thumbnail.provider}"/>
@@ -34,4 +35,5 @@
 			</list>
 		</property>
 	</bean>
+
 </beans>
\ No newline at end of file
diff --git a/src/main/java/org/olat/core/commons/services/sms/MessageLog.java b/src/main/java/org/olat/core/commons/services/sms/MessageLog.java
new file mode 100644
index 00000000000..f6bfdd5899c
--- /dev/null
+++ b/src/main/java/org/olat/core/commons/services/sms/MessageLog.java
@@ -0,0 +1,70 @@
+/**
+ * <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.sms;
+
+import java.util.Date;
+
+import org.olat.core.id.Identity;
+
+/**
+ * 
+ * Initial date: 3 févr. 2017<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public interface MessageLog {
+	
+	public Long getKey();
+	
+	public Date getCreationDate();
+	
+	/**
+	 * The unique identifier of this message.
+	 * 
+	 * @return The uuid
+	 */
+	public String getMessageUuid();
+	
+	/**
+	 * The ID of the service provider.
+	 * 
+	 * @return The Id of the service provider
+	 */
+	public String getServiceId();
+	
+	public void setServiceId(String serviceId);
+	
+	/**
+	 * Informations about the status of the message sent.
+	 * 
+	 * @return A string
+	 */
+	public String getServerResponse();
+	
+	public void setServerResponse(String response);
+	
+	/**
+	 * The recipient of the message.
+	 * 
+	 * @return
+	 */
+	public Identity getRecipient();
+
+}
diff --git a/src/main/java/org/olat/core/commons/services/sms/MessagesSPI.java b/src/main/java/org/olat/core/commons/services/sms/MessagesSPI.java
new file mode 100644
index 00000000000..eb01136e15c
--- /dev/null
+++ b/src/main/java/org/olat/core/commons/services/sms/MessagesSPI.java
@@ -0,0 +1,38 @@
+/**
+ * <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.sms;
+
+/**
+ * 
+ * Initial date: 3 févr. 2017<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public interface MessagesSPI {
+
+	public String getId();
+	
+	public String getName();
+	
+	public boolean isValid();
+
+	public boolean send(String messageId, String text, String recipient) throws SimpleMessageException;
+
+}
diff --git a/src/main/java/org/olat/core/commons/services/sms/SimpleMessageException.java b/src/main/java/org/olat/core/commons/services/sms/SimpleMessageException.java
new file mode 100644
index 00000000000..08ec3c3c1d1
--- /dev/null
+++ b/src/main/java/org/olat/core/commons/services/sms/SimpleMessageException.java
@@ -0,0 +1,28 @@
+package org.olat.core.commons.services.sms;
+
+/**
+ * 
+ * Initial date: 7 févr. 2017<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class SimpleMessageException extends Exception {
+
+	private static final long serialVersionUID = 787789477989775426L;
+	
+	private final ErrorCode code;
+
+	public SimpleMessageException(Exception cause, ErrorCode code) {
+		super(cause);
+		this.code = code;
+	}
+	
+	public ErrorCode getErrorCode() {
+		return code;
+	}
+	
+	public enum ErrorCode {
+		numberNotValid,
+		
+	}
+}
diff --git a/src/main/java/org/olat/core/commons/services/sms/SimpleMessageModule.java b/src/main/java/org/olat/core/commons/services/sms/SimpleMessageModule.java
new file mode 100644
index 00000000000..f351063650f
--- /dev/null
+++ b/src/main/java/org/olat/core/commons/services/sms/SimpleMessageModule.java
@@ -0,0 +1,132 @@
+/**
+ * <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.sms;
+
+import java.util.List;
+
+import org.olat.core.configuration.AbstractSpringModule;
+import org.olat.core.configuration.ConfigOnOff;
+import org.olat.core.id.UserConstants;
+import org.olat.core.util.StringHelper;
+import org.olat.core.util.coordinate.CoordinatorManager;
+import org.olat.user.propertyhandlers.UserPropertyHandler;
+import org.olat.user.propertyhandlers.UserPropertyUsageContext;
+import org.olat.user.propertyhandlers.ui.UsrPropCfgManager;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+/**
+ * 
+ * Initial date: 3 févr. 2017<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+@Service
+public class SimpleMessageModule extends AbstractSpringModule implements ConfigOnOff {
+	
+	public static final String SMS_ENABLED = "message.enabled";
+	public static final String RESET_PASSWORD_ENABLED = "message.reset.password.enabled";
+	
+	@Value("${message.enabled:false}")
+	private boolean enabled;
+	@Value("${message.reset.password.enabled:true}")
+	private boolean resetPassword;
+	
+
+	@Autowired
+	private UsrPropCfgManager usrPropCfgMng;
+	
+	@Autowired
+	public SimpleMessageModule(CoordinatorManager coordinatorManager) {
+		super(coordinatorManager);
+	}
+
+	@Override
+	public void init() {
+		String enabledObj = getStringPropertyValue(SMS_ENABLED, true);
+		if(StringHelper.containsNonWhitespace(enabledObj)) {
+			enabled = "true".equals(enabledObj);
+		}
+		
+		String resetPasswordEnabledObj = getStringPropertyValue(RESET_PASSWORD_ENABLED, true);
+		if(StringHelper.containsNonWhitespace(resetPasswordEnabledObj)) {
+			resetPassword = "true".equals(resetPasswordEnabledObj);
+		}
+		
+		if(enabled) {//check
+			enableSmsUserProperty();
+		}
+	}
+
+	@Override
+	protected void initFromChangedProperties() {
+		init();
+	}
+
+	@Override
+	public boolean isEnabled() {
+		return enabled;
+	}
+	
+	public void setEnabled(boolean enabled) {
+		this.enabled = enabled;
+		setStringProperty(SMS_ENABLED, Boolean.toString(enabled), true);
+	}
+	
+	private void enableSmsUserProperty() {
+		List<UserPropertyHandler> handlers = usrPropCfgMng.getUserPropertiesConfigObject().getPropertyHandlers();
+		
+		UserPropertyHandler smsHandler = null;
+		UserPropertyHandler mobileHandler = null;
+		for(UserPropertyHandler handler:handlers) {
+			if(UserConstants.SMSTELMOBILE.equals(handler.getName())) {
+				smsHandler = handler;
+			} else if(UserConstants.TELMOBILE.equals(handler.getName())) {
+				mobileHandler = handler;
+			}
+		}
+		
+		if(smsHandler != null) {
+			UserPropertyUsageContext context = usrPropCfgMng.getUserPropertiesConfigObject()
+					.getUsageContexts().get("org.olat.user.ProfileFormController");
+			if(!context.contains(smsHandler)) {
+				if(mobileHandler == null) {
+					context.addPropertyHandler(smsHandler);
+				} else {
+					int index = context.getPropertyHandlers().indexOf(mobileHandler);
+					context.addPropertyHandler(index + 1, smsHandler);
+				}
+			}
+		}
+	}
+
+	public boolean isResetPasswordEnabled() {
+		return resetPassword;
+	}
+
+	public void setResetPasswordEnabled(boolean resetPassword) {
+		this.resetPassword = resetPassword;
+		setStringProperty(RESET_PASSWORD_ENABLED, Boolean.toString(resetPassword), true);
+	}
+	
+	
+	
+}
diff --git a/src/main/java/org/olat/core/commons/services/sms/SimpleMessageService.java b/src/main/java/org/olat/core/commons/services/sms/SimpleMessageService.java
new file mode 100644
index 00000000000..548a3111ac2
--- /dev/null
+++ b/src/main/java/org/olat/core/commons/services/sms/SimpleMessageService.java
@@ -0,0 +1,76 @@
+/**
+ * <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.sms;
+
+import java.util.List;
+
+import org.olat.core.commons.services.sms.model.MessageStatistics;
+import org.olat.core.id.Identity;
+
+/**
+ * 
+ * Initial date: 3 févr. 2017<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public interface SimpleMessageService {
+	
+	/**
+	 * @return A six digits token random generated.
+	 */
+	public String generateToken();
+	
+	/**
+	 * A method to validate the phone number.
+	 * 
+	 * @param number The phone number
+	 * @return true if the service can send a message with the specified number.
+	 */
+	public boolean validate(String number);
+	
+	/**
+	 * The list of service providers registered in the system.
+	 * 
+	 * @return A list of service providers.
+	 */
+	public List<MessagesSPI> getMessagesSpiList();
+	
+	/**
+	 * Return the active messages service provider.
+	 * 
+	 * @return A message service provider
+	 */
+	public MessagesSPI getMessagesSpi();
+	
+	/**
+	 * Return the message service provider identified by its id.
+	 * 
+	 * @param serviceId The id of the desired service provider.
+	 * @return A message service provider or null
+	 */
+	public MessagesSPI getMessagesSpi(String serviceId);
+
+	public void sendMessage(String text, Identity recipient) throws SimpleMessageException;
+	
+	public void sendMessage(String text, String phone, Identity recipient) throws SimpleMessageException;
+	
+	public List<MessageStatistics> getStatisticsPerMonth();
+
+}
diff --git a/src/main/java/org/olat/core/commons/services/sms/_spring/smsCorecontext.xml b/src/main/java/org/olat/core/commons/services/sms/_spring/smsCorecontext.xml
new file mode 100644
index 00000000000..bce3275ef7a
--- /dev/null
+++ b/src/main/java/org/olat/core/commons/services/sms/_spring/smsCorecontext.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xmlns:context="http://www.springframework.org/schema/context" 
+	xsi:schemaLocation="
+  http://www.springframework.org/schema/beans 
+  http://www.springframework.org/schema/beans/spring-beans.xsd 
+  http://www.springframework.org/schema/context 
+  http://www.springframework.org/schema/context/spring-context.xsd">
+
+	<bean id="smsTelMobile.AfterLogin.Injection" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
+		<property name="targetObject" ref="afterLoginInterceptionManager" />
+		<property name="targetMethod" value="addAfterLoginControllerConfig" />
+		<property name="arguments">
+			<ref bean="smsTelMobile.AfterLoginConfig"/>
+		</property>
+	</bean>
+
+	<bean id="smsTelMobile.AfterLoginConfig" class="org.olat.login.AfterLoginConfig">
+		<property name="afterLoginControllerList">
+			<list>
+				<map>
+					<entry key="controller">
+						<bean class="org.olat.core.gui.control.creator.AutoCreator" scope="prototype">
+							<property name="className" value="org.olat.core.commons.services.sms.ui.SMSPhoneController"/>
+						</bean>
+					</entry>
+					<entry key="forceUser"><value>false</value></entry>
+					<entry key="redoTimeout"><value>0</value></entry>
+					<entry key="i18nIntro"><value>org.olat.core.commons.services.sms.ui:confirm.sms.phone</value></entry>
+					<entry key="order"><value>0</value></entry>
+					<!-- 31536000 = 60 * 60 * 24 * 365 (or a year in seconds) -->
+					<entry key="redoTimeout"><value>31536000</value></entry>
+				</map>
+			</list>
+		</property>
+	</bean>
+	
+	<bean class="org.olat.core.extensions.action.GenericActionExtension" init-method="initExtensionPoints">
+		<property name="order" value="8835" />
+		<property name="actionController">	
+			<bean class="org.olat.core.gui.control.creator.AutoCreator" scope="prototype">
+				<property name="className" value="org.olat.core.commons.services.sms.ui.SimpleMessageServiceAdminController"/>
+			</bean>
+		</property>
+		<property name="navigationKey" value="smsAdmin" />
+		<property name="i18nActionKey" value="admin.menu.title"/>
+		<property name="i18nDescriptionKey" value="admin.menu.title.alt"/>
+		<property name="translationPackage" value="org.olat.core.commons.services.sms.ui"/>
+		<property name="parentTreeNodeIdentifier" value="loginAndSecurityParent" /> 
+		<property name="extensionPoints">
+			<list>	
+				<value>org.olat.admin.SystemAdminMainController</value>		
+			</list>
+		</property>
+	</bean>
+	
+</beans>
\ No newline at end of file
diff --git a/src/main/java/org/olat/core/commons/services/sms/manager/MessageLogDAO.java b/src/main/java/org/olat/core/commons/services/sms/manager/MessageLogDAO.java
new file mode 100644
index 00000000000..9e2fe351998
--- /dev/null
+++ b/src/main/java/org/olat/core/commons/services/sms/manager/MessageLogDAO.java
@@ -0,0 +1,123 @@
+/**
+ * <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.sms.manager;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+import java.util.UUID;
+
+import org.olat.core.commons.persistence.DB;
+import org.olat.core.commons.persistence.PersistenceHelper;
+import org.olat.core.commons.services.sms.MessageLog;
+import org.olat.core.commons.services.sms.model.MessageLogImpl;
+import org.olat.core.commons.services.sms.model.MessageStatistics;
+import org.olat.core.id.Identity;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * 
+ * Initial date: 3 févr. 2017<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+@Service
+public class MessageLogDAO {
+	
+	@Autowired
+	private DB dbInstance;
+	
+	/**
+	 * Create a transient log entry.
+	 * 
+	 * @param recipient
+	 * @return The log entry
+	 */
+	public MessageLog create(Identity recipient, String serviceId) {
+		MessageLogImpl log = new MessageLogImpl();
+		log.setCreationDate(new Date());
+		log.setLastModified(log.getCreationDate());
+		log.setMessageUuid(UUID.randomUUID().toString());
+		log.setServiceId(serviceId);
+		log.setRecipient(recipient);
+		dbInstance.getCurrentEntityManager().persist(log);
+		return log;
+	}
+	
+	public MessageLog save(MessageLog log) {
+		MessageLog mergedLog;
+		if(log.getKey() == null) {
+			dbInstance.getCurrentEntityManager().persist(log);
+			mergedLog = log;
+		} else {
+			((MessageLogImpl)log).setLastModified(new Date());
+			mergedLog = dbInstance.getCurrentEntityManager().merge(log);
+		}
+		return mergedLog;
+	}
+	
+	public MessageLog loadMessageByKey(Long key) {
+		StringBuilder sb = new StringBuilder();
+		sb.append("select mlog from smsmessagelog mlog where mlog.key=:messageKey");
+		
+		List<MessageLog> mLogs = dbInstance.getCurrentEntityManager()
+				.createQuery(sb.toString(), MessageLog.class)
+				.setParameter("messageKey", key)
+				.getResultList();
+		return mLogs == null || mLogs.isEmpty() ? null : mLogs.get(0);
+	}
+	
+	public List<MessageStatistics> getStatisticsPerMonth(String serviceId) {
+		StringBuilder sb = new StringBuilder();
+		sb.append("select count(mlog.key) as numOfMessages, month(mlog.creationDate) as month, year(mlog.creationDate) as year")
+		  .append(" from smsmessagelog mlog")
+		  .append(" where mlog.serviceId=:serviceId")
+		  .append(" group by month(mlog.creationDate), year(mlog.creationDate)");
+		
+		List<Object[]> objects = dbInstance.getCurrentEntityManager()
+				.createQuery(sb.toString(), Object[].class)
+				.setParameter("serviceId", serviceId)
+				.getResultList();
+		
+		Calendar cal = Calendar.getInstance();
+		List<MessageStatistics> stats = new ArrayList<>(objects.size());
+		for(Object[] object:objects) {
+			int pos = 0;
+			Long numOfMessages = PersistenceHelper.extractLong(object, pos++);
+			Long month = PersistenceHelper.extractLong(object, pos++);
+			Long year = PersistenceHelper.extractLong(object, pos++);
+			if(numOfMessages != null) {
+				cal.set(Calendar.YEAR, year.intValue());
+				cal.set(Calendar.MONTH, month.intValue() - 1);
+				cal.set(Calendar.DAY_OF_MONTH, 1);
+				cal.set(Calendar.HOUR_OF_DAY, 0);
+				cal.set(Calendar.MINUTE, 0);
+				cal.set(Calendar.SECOND, 0);
+				cal.set(Calendar.MILLISECOND, 0);
+				
+				Date date = cal.getTime();
+				stats.add(new MessageStatistics(serviceId, date, numOfMessages));
+			}
+		}
+		return stats;
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/org/olat/core/commons/services/sms/manager/SimpleMessageServiceImpl.java b/src/main/java/org/olat/core/commons/services/sms/manager/SimpleMessageServiceImpl.java
new file mode 100644
index 00000000000..32276f77005
--- /dev/null
+++ b/src/main/java/org/olat/core/commons/services/sms/manager/SimpleMessageServiceImpl.java
@@ -0,0 +1,166 @@
+/**
+ * <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.sms.manager;
+
+
+import java.util.List;
+import java.util.Random;
+
+import org.olat.core.commons.services.sms.MessageLog;
+import org.olat.core.commons.services.sms.MessagesSPI;
+import org.olat.core.commons.services.sms.SimpleMessageException;
+import org.olat.core.commons.services.sms.SimpleMessageModule;
+import org.olat.core.commons.services.sms.SimpleMessageService;
+import org.olat.core.commons.services.sms.model.MessageStatistics;
+import org.olat.core.helpers.Settings;
+import org.olat.core.id.Identity;
+import org.olat.core.id.UserConstants;
+import org.olat.core.logging.OLog;
+import org.olat.core.logging.Tracing;
+import org.olat.core.util.StringHelper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * 
+ * Initial date: 3 févr. 2017<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+@Service
+public class SimpleMessageServiceImpl implements SimpleMessageService {
+	
+	private static final OLog log = Tracing.createLoggerFor(SimpleMessageServiceImpl.class);
+	private static final Random rnd = new Random();
+	
+	@Autowired
+	private MessageLogDAO messageLogDao;
+	
+	@Autowired
+	private SimpleMessageModule messageModule;
+	@Autowired
+	private List<MessagesSPI> messagesSpiList;
+
+	@Override
+	public List<MessagesSPI> getMessagesSpiList() {
+		return messagesSpiList;
+	}
+
+	@Override
+	public String generateToken() {
+		StringBuilder sb = new StringBuilder();
+		for(int i=0; i<6; i++) {
+			int n = Math.round(rnd.nextFloat() * 9.0f);
+			if(n < 0) {
+				n = 0;
+			} else if(n > 9) {
+				n = 9;
+			}
+			sb.append(n);
+		}
+		return sb.toString();
+	}
+
+	@Override
+	public boolean validate(String number) {
+		if(!StringHelper.containsNonWhitespace(number)) return false;
+		number = number.replace("+", "").replace(" ", "");
+		if(StringHelper.isLong(number)) {
+			try {
+				Long phone = new Long(number);
+				return phone > 0;
+			} catch (NumberFormatException e) {
+				//
+			}
+		}
+		return false;
+	}
+
+	@Override
+	public void sendMessage(String text, Identity recipient) throws SimpleMessageException {
+		String telNumber = recipient.getUser().getProperty(UserConstants.SMSTELMOBILE, null);
+		sendMessage(text, telNumber, recipient);
+	}
+
+	@Override
+	public void sendMessage(String text, String telNumber, Identity recipient) throws SimpleMessageException {
+		MessagesSPI spi = getMessagesSpi();
+		MessageLog mLog = messageLogDao.create(recipient, spi.getId());
+		boolean allOk = spi.send(mLog.getMessageUuid(), text, telNumber);
+		mLog.setServerResponse(Boolean.toString(allOk));
+		messageLogDao.save(mLog);
+		log.audit("SMS send: " + allOk + " to " + recipient + " with number: " + telNumber);
+	}
+
+	@Override
+	public List<MessageStatistics> getStatisticsPerMonth() {
+		MessagesSPI selectedSpi = getMessagesSpi();
+		return messageLogDao.getStatisticsPerMonth(selectedSpi.getId());
+	}
+
+	@Override
+	public MessagesSPI getMessagesSpi(String serviceId) {
+		MessagesSPI spi = null;
+		if("devnull".equals(serviceId)) {
+			spi = new DevNullProvider();
+		} else if(messagesSpiList != null) {
+			for(MessagesSPI mSpi:messagesSpiList) {
+				if(mSpi.getId().equals(serviceId)) {
+					spi = mSpi;
+				}
+			}
+		}
+		return spi;
+	}
+
+	@Override
+	public MessagesSPI getMessagesSpi() {
+		if(Settings.isDebuging()) return new DevNullProvider();
+
+		if(messageModule.isEnabled() && messagesSpiList.size() > 0) {
+			return messagesSpiList.get(0);
+		}
+		return new DevNullProvider();
+	}
+	
+	private static class DevNullProvider implements MessagesSPI {
+		
+		@Override
+		public String getId() {
+			return "devnull";
+		}
+
+		@Override
+		public String getName() {
+			return "/dev/null";
+		}
+
+		@Override
+		public boolean isValid() {
+			return true;
+		}
+
+		@Override
+		public boolean send(String messageId, String text, String recipient) {
+			log.info("Send: " + text);
+			return true;
+		}
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/org/olat/core/commons/services/sms/model/MessageLogImpl.java b/src/main/java/org/olat/core/commons/services/sms/model/MessageLogImpl.java
new file mode 100644
index 00000000000..8a680cb0045
--- /dev/null
+++ b/src/main/java/org/olat/core/commons/services/sms/model/MessageLogImpl.java
@@ -0,0 +1,159 @@
+/**
+ * <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.sms.model;
+
+import java.util.Date;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+
+import org.olat.basesecurity.IdentityImpl;
+import org.olat.core.commons.services.sms.MessageLog;
+import org.olat.core.id.CreateInfo;
+import org.olat.core.id.Identity;
+import org.olat.core.id.ModifiedInfo;
+import org.olat.core.id.Persistable;
+
+/**
+ * 
+ * Initial date: 3 févr. 2017<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+@Entity(name="smsmessagelog")
+@Table(name="o_sms_message_log")
+public class MessageLogImpl implements Persistable, ModifiedInfo, CreateInfo, MessageLog {
+
+	private static final long serialVersionUID = 2605977680881759364L;
+
+	@Id
+	@GeneratedValue(strategy = GenerationType.IDENTITY)
+	@Column(name="id", nullable=false, unique=true, insertable=true, updatable=false)
+	private Long key;
+	
+	@Temporal(TemporalType.TIMESTAMP)
+	@Column(name="creationdate", nullable=false, insertable=true, updatable=false)
+	private Date creationDate;
+	@Temporal(TemporalType.TIMESTAMP)
+	@Column(name="lastmodified", nullable=false, insertable=true, updatable=true)
+	private Date lastModified;
+
+	@Column(name="s_message_uuid", nullable=false, insertable=true, updatable=false)
+	private String messageUuid;
+	@Column(name="s_server_response", nullable=true, insertable=true, updatable=false)
+	private String serverResponse;
+	@Column(name="s_service_id", nullable=false, insertable=true, updatable=false)
+	private String serviceId;
+	
+	@ManyToOne(targetEntity=IdentityImpl.class,fetch=FetchType.LAZY,optional=false)
+	@JoinColumn(name="fk_identity", nullable=true, insertable=true, updatable=false)
+    private Identity recipient;
+	
+	@Override
+	public Long getKey() {
+		return key;
+	}
+	
+	public void setKey(Long key) {
+		this.key = key;
+	}
+
+	@Override
+	public Date getCreationDate() {
+		return creationDate;
+	}
+
+	public void setCreationDate(Date creationDate) {
+		this.creationDate = creationDate;
+	}
+
+	@Override
+	public Date getLastModified() {
+		return lastModified;
+	}
+
+	@Override
+	public void setLastModified(Date date) {
+		lastModified = date;
+	}
+
+	public String getMessageUuid() {
+		return messageUuid;
+	}
+
+	public void setMessageUuid(String messageUuid) {
+		this.messageUuid = messageUuid;
+	}
+
+	public String getServerResponse() {
+		return serverResponse;
+	}
+
+	public void setServerResponse(String serverResponse) {
+		this.serverResponse = serverResponse;
+	}
+
+	public String getServiceId() {
+		return serviceId;
+	}
+
+	public void setServiceId(String serviceId) {
+		this.serviceId = serviceId;
+	}
+
+	public Identity getRecipient() {
+		return recipient;
+	}
+
+	public void setRecipient(Identity recipient) {
+		this.recipient = recipient;
+	}
+
+	@Override
+	public int hashCode() {
+		return key == null ? 349857 : key.hashCode();
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if(obj == this) {
+			return true;
+		}
+		if(obj instanceof MessageLogImpl) {
+			MessageLogImpl log = (MessageLogImpl)obj;
+			return key != null && key.equals(log.key);
+		}
+		return false;
+	}
+
+	@Override
+	public boolean equalsByPersistableKey(Persistable persistable) {
+		return equals(persistable);
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/org/olat/core/commons/services/sms/model/MessageStatistics.java b/src/main/java/org/olat/core/commons/services/sms/model/MessageStatistics.java
new file mode 100644
index 00000000000..e5305e63ef7
--- /dev/null
+++ b/src/main/java/org/olat/core/commons/services/sms/model/MessageStatistics.java
@@ -0,0 +1,34 @@
+package org.olat.core.commons.services.sms.model;
+
+import java.util.Date;
+
+/**
+ * 
+ * Initial date: 7 févr. 2017<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class MessageStatistics {
+	
+	private final Date date;
+	private final String service;
+	private final long numOfMessages;
+
+	public MessageStatistics(String service, Date date, long numOfMessages) {
+		this.service = service;
+		this.date = date;
+		this.numOfMessages = numOfMessages;
+	}
+
+	public Date getDate() {
+		return date;
+	}
+
+	public String getService() {
+		return service;
+	}
+
+	public long getNumOfMessages() {
+		return numOfMessages;
+	}
+}
diff --git a/src/main/java/org/olat/core/commons/services/sms/spi/WebSMSProvider.java b/src/main/java/org/olat/core/commons/services/sms/spi/WebSMSProvider.java
new file mode 100644
index 00000000000..4c922d29314
--- /dev/null
+++ b/src/main/java/org/olat/core/commons/services/sms/spi/WebSMSProvider.java
@@ -0,0 +1,168 @@
+/**
+ * <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.sms.spi;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.auth.AuthScope;
+import org.apache.http.auth.UsernamePasswordCredentials;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.entity.ContentType;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.BasicCredentialsProvider;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.util.EntityUtils;
+import org.json.JSONArray;
+import org.json.JSONObject;
+import org.olat.core.commons.services.sms.MessagesSPI;
+import org.olat.core.logging.OLog;
+import org.olat.core.logging.Tracing;
+import org.olat.core.util.StringHelper;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+/**
+ * Implementation for https://websms.ch
+ * 
+ * Initial date: 3 févr. 2017<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+@Service("messagesSpiWebSMS")
+public class WebSMSProvider implements MessagesSPI, InitializingBean {
+	
+	private static final OLog log = Tracing.createLoggerFor(WebSMSProvider.class);
+	private final BasicCredentialsProvider provider = new BasicCredentialsProvider();
+	
+	@Value("${websms.url:https://api.websms.com/rest/smsmessaging/text}")
+	private String url;
+	
+	@Value("${websms.username:}")
+	private String username;
+	@Value("${websms.password:}")
+	private String password;
+	
+	private boolean test = false;
+	
+	/**
+	 * Method means for unit tests. The changes are not persisted.
+	 * 
+	 * @param username
+	 * @param password
+	 */
+	protected void setCredentials(String username, String password) {
+		this.username = username;
+		this.password = password;
+		provider.setCredentials(new AuthScope("api.websms.com", 443),
+				new UsernamePasswordCredentials(username, password));
+	}
+	
+	protected void setTest(boolean test) {
+		this.test = test;
+	}
+
+	@Override
+	public String getId() {
+		return "websms";
+	}
+
+	@Override
+	public String getName() {
+		return "WebSMS";
+	}
+
+	@Override
+	public boolean isValid() {
+		return StringHelper.containsNonWhitespace(username) && StringHelper.containsNonWhitespace(password);
+	}
+
+	@Override
+	public void afterPropertiesSet() throws Exception {
+		provider.setCredentials(new AuthScope("api.websms.com", 443),
+				new UsernamePasswordCredentials(username, password));
+	}
+
+	@Override
+	public boolean send(String messageId, String text, String recipient) {
+		HttpPost send = new HttpPost(url);
+		try(CloseableHttpClient httpclient = HttpClientBuilder.create()
+				.setDefaultCredentialsProvider(provider)
+				.build()) {
+			
+			String phone = recipient.replace("+", "").replace(" ", "");
+			String objectStr = jsonPayload(messageId, text, new Long(phone));
+			HttpEntity smsEntity = new StringEntity(objectStr, ContentType.APPLICATION_JSON);
+			send.setEntity(smsEntity);
+			CloseableHttpResponse response = httpclient.execute(send);
+			int returnCode = response.getStatusLine().getStatusCode();
+			String responseString = EntityUtils.toString(response.getEntity());
+			if(returnCode == 200 || returnCode >= 2000) {
+				return true;
+			}
+			log.error("WebSMS return an error code " + returnCode + ": " + responseString);
+			return false;
+		} catch(Exception e) {
+			log.error("", e);
+			return false;
+		}
+	}
+
+	/**
+	 * {
+	 *  "userDataHeaderPresent" : false,
+	 *  "messageContent" : [ "...", ... ],
+	 *  "test" : false,
+	 *  "recipientAddressList" : [ ..., ... ],
+	 *  "senderAddress" : "...",
+	 *  "senderAddressType" : "national",
+	 *  "sendAsFlashSms" : false,
+	 *  "notificationCallbackUrl" : "...",
+	 *  "clientMessageId" : "...",
+	 *  "priority" : ...,
+	 * }
+	 * @param obj
+	 * @return
+	 */
+	private String jsonPayload(String messageId, String text, Long recipient) {
+		try {
+			JSONObject message = new JSONObject();
+			message.put("userDataHeaderPresent", false);
+			message.put("messageContent", text);
+			message.put("test", test);
+			
+			JSONArray recipients = new JSONArray();
+			recipients.put(recipient);
+			message.put("recipientAddressList", recipients);//arrsx
+			//optional message.put("senderAddress", "");
+			//optional message.put("senderAddressType", "national");
+			//optional message.put("sendAsFlashSms", false);
+			//optional message.put("senderAddress", "");//national international shortcode alphanumeric
+			//optional message.put("notificationCallbackUrl", "");
+			message.put("clientMessageId", messageId);
+			//optional message.put("priority", false);
+			return message.toString();
+		} catch (Exception e) {
+			log.error("", e);
+			return null;
+		}
+	}
+}
diff --git a/src/main/java/org/olat/core/commons/services/sms/ui/MessageStatisticsDataModel.java b/src/main/java/org/olat/core/commons/services/sms/ui/MessageStatisticsDataModel.java
new file mode 100644
index 00000000000..b2e25aa90dd
--- /dev/null
+++ b/src/main/java/org/olat/core/commons/services/sms/ui/MessageStatisticsDataModel.java
@@ -0,0 +1,86 @@
+package org.olat.core.commons.services.sms.ui;
+
+import java.util.List;
+import java.util.Locale;
+
+import org.olat.core.commons.persistence.SortKey;
+import org.olat.core.commons.services.sms.model.MessageStatistics;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.DefaultFlexiTableDataModel;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiSortableColumnDef;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableColumnModel;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.SortableFlexiTableDataModel;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.SortableFlexiTableModelDelegate;
+
+/**
+ * 
+ * Initial date: 7 févr. 2017<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class MessageStatisticsDataModel extends DefaultFlexiTableDataModel<MessageStatistics>
+	implements SortableFlexiTableDataModel<MessageStatistics> {
+	
+	private final Locale locale;
+		
+	public MessageStatisticsDataModel(FlexiTableColumnModel columnModel, Locale locale) {
+		super(columnModel);
+		this.locale = locale;
+	}
+
+	@Override
+	public void sort(SortKey orderBy) {
+		if(orderBy != null) {
+			SortableFlexiTableModelDelegate<MessageStatistics> sorter = new SortableFlexiTableModelDelegate<>(orderBy, this, locale);
+			List<MessageStatistics> stats = sorter.sort();
+			super.setObjects(stats);
+		}
+	}
+	
+	@Override
+	public Object getValueAt(int row, int col) {
+		MessageStatistics infos = getObject(row);
+		return getValueAt(infos, col);
+	}
+
+	@Override
+	public Object getValueAt(MessageStatistics row, int col) {
+		switch(MLogStatsCols.values()[col]) {
+			case year:
+			case month: return row.getDate();
+			case numOfMessages: return row.getNumOfMessages();
+			default: return null;
+		}
+	}
+
+	@Override
+	public DefaultFlexiTableDataModel<MessageStatistics> createCopyWithEmptyList() {
+		return new MessageStatisticsDataModel(getTableColumnModel(), locale);
+	}
+	
+	public enum MLogStatsCols implements FlexiSortableColumnDef {
+		year("table.header.year"),
+		month("table.header.month"),
+		numOfMessages("table.header.numOfMessages");
+		
+		private final String i18nKey;
+		
+		private MLogStatsCols(String i18nKey) {
+			this.i18nKey = i18nKey;
+		}
+
+		@Override
+		public String i18nHeaderKey() {
+			return i18nKey;
+		}
+
+		@Override
+		public boolean sortable() {
+			return true;
+		}
+
+		@Override
+		public String sortKey() {
+			return name();
+		}
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/org/olat/core/commons/services/sms/ui/MessagesStatisticsController.java b/src/main/java/org/olat/core/commons/services/sms/ui/MessagesStatisticsController.java
new file mode 100644
index 00000000000..668bb1da120
--- /dev/null
+++ b/src/main/java/org/olat/core/commons/services/sms/ui/MessagesStatisticsController.java
@@ -0,0 +1,88 @@
+/**
+ * <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.sms.ui;
+
+import java.util.List;
+
+import org.olat.core.commons.services.sms.SimpleMessageService;
+import org.olat.core.commons.services.sms.model.MessageStatistics;
+import org.olat.core.commons.services.sms.ui.MessageStatisticsDataModel.MLogStatsCols;
+import org.olat.core.gui.UserRequest;
+import org.olat.core.gui.components.form.flexible.FormItemContainer;
+import org.olat.core.gui.components.form.flexible.elements.FlexiTableElement;
+import org.olat.core.gui.components.form.flexible.impl.FormBasicController;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.DefaultFlexiColumnModel;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableColumnModel;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableDataModelFactory;
+import org.olat.core.gui.control.Controller;
+import org.olat.core.gui.control.WindowControl;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * 
+ * Initial date: 3 févr. 2017<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class MessagesStatisticsController extends FormBasicController {
+	
+	private FlexiTableElement tableEl;
+	private MessageStatisticsDataModel model;
+	
+	@Autowired
+	private SimpleMessageService messageService;
+	
+	public MessagesStatisticsController(UserRequest ureq, WindowControl wControl) {
+		super(ureq, wControl, "statistics");
+		initForm(ureq);
+		loadModel();
+	}
+
+	@Override
+	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
+		FlexiTableColumnModel columnsModel = FlexiTableDataModelFactory.createFlexiTableColumnModel();
+		DefaultFlexiColumnModel yearColumn = new DefaultFlexiColumnModel(MLogStatsCols.year, new YearCellRenderer());
+		yearColumn.setExportable(false);
+		columnsModel.addFlexiColumnModel(yearColumn);
+		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(MLogStatsCols.month, new MonthCellRenderer(getLocale())));
+		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(MLogStatsCols.numOfMessages));
+		
+		model = new MessageStatisticsDataModel(columnsModel, getLocale());
+		tableEl = uifactory.addTableElement(getWindowControl(), "stats", model, 50, false, getTranslator(), formLayout);
+		tableEl.setCustomizeColumns(false);
+		tableEl.setExportEnabled(true);
+	}
+	
+	private void loadModel() {
+		List<MessageStatistics> stats = messageService.getStatisticsPerMonth();
+		model.setObjects(stats);
+		tableEl.reset(true, true, true);
+	}
+
+	@Override
+	protected void doDispose() {
+		//
+	}
+
+	@Override
+	protected void formOK(UserRequest ureq) {
+		//
+	}
+}
diff --git a/src/main/java/org/olat/core/commons/services/sms/ui/MonthCellRenderer.java b/src/main/java/org/olat/core/commons/services/sms/ui/MonthCellRenderer.java
new file mode 100644
index 00000000000..3328d92e84c
--- /dev/null
+++ b/src/main/java/org/olat/core/commons/services/sms/ui/MonthCellRenderer.java
@@ -0,0 +1,35 @@
+package org.olat.core.commons.services.sms.ui;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+
+import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiCellRenderer;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableComponent;
+import org.olat.core.gui.render.Renderer;
+import org.olat.core.gui.render.StringOutput;
+import org.olat.core.gui.render.URLBuilder;
+import org.olat.core.gui.translator.Translator;
+
+/**
+ * 
+ * Initial date: 7 févr. 2017<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class MonthCellRenderer implements FlexiCellRenderer {
+	
+	private final SimpleDateFormat format;
+	
+	public MonthCellRenderer(Locale locale) {
+		format = new SimpleDateFormat("MMMM", locale);
+	}
+
+	@Override
+	public void render(Renderer renderer, StringOutput target, Object cellValue, int row, FlexiTableComponent source,
+			URLBuilder ubu, Translator translator) {
+		if(cellValue instanceof Date) {
+			target.append(format.format((Date)cellValue));
+		}
+	}
+}
diff --git a/src/main/java/org/olat/core/commons/services/sms/ui/SMSPhoneController.java b/src/main/java/org/olat/core/commons/services/sms/ui/SMSPhoneController.java
new file mode 100644
index 00000000000..d09ba49fab3
--- /dev/null
+++ b/src/main/java/org/olat/core/commons/services/sms/ui/SMSPhoneController.java
@@ -0,0 +1,157 @@
+/**
+ * <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.sms.ui;
+
+import org.olat.core.commons.services.sms.SimpleMessageException;
+import org.olat.core.commons.services.sms.SimpleMessageModule;
+import org.olat.core.commons.services.sms.SimpleMessageService;
+import org.olat.core.gui.UserRequest;
+import org.olat.core.gui.components.form.flexible.FormItem;
+import org.olat.core.gui.components.form.flexible.FormItemContainer;
+import org.olat.core.gui.components.form.flexible.elements.FormLink;
+import org.olat.core.gui.components.form.flexible.elements.TextElement;
+import org.olat.core.gui.components.form.flexible.impl.FormBasicController;
+import org.olat.core.gui.components.form.flexible.impl.FormEvent;
+import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer;
+import org.olat.core.gui.components.link.Link;
+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.id.User;
+import org.olat.core.id.UserConstants;
+import org.olat.core.util.StringHelper;
+import org.olat.login.SupportsAfterLoginInterceptor;
+import org.olat.user.UserManager;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * 
+ * Initial date: 3 févr. 2017<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class SMSPhoneController extends FormBasicController implements SupportsAfterLoginInterceptor {
+	
+	private FormLink dontActivateButton;
+	private TextElement phoneEl, tokenEl;
+	
+	private String sentToken;
+	
+	@Autowired
+	private UserManager userManager;
+	@Autowired
+	private SimpleMessageModule messageModule;
+	@Autowired
+	private SimpleMessageService messageService;
+	
+	public SMSPhoneController(UserRequest ureq, WindowControl wControl) {
+		super(ureq, wControl);
+		
+		initForm(ureq);
+	}
+
+	@Override
+	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
+		phoneEl = uifactory.addTextElement("sms.phone.number", "sms.phone.number", 32, "", formLayout);
+		phoneEl.setPlaceholderKey("sms.phone.number.hint", null);
+		
+		tokenEl = uifactory.addTextElement("sms.token.number", "sms.token.number", 16, "", formLayout);
+		tokenEl.setExampleKey("sms.token.number.explain", null);
+		tokenEl.setVisible(false);
+		
+		FormLayoutContainer buttonsCont = FormLayoutContainer.createButtonLayout("buttons", getTranslator());
+		formLayout.add(buttonsCont);
+		uifactory.addFormSubmitButton("save", buttonsCont);
+		dontActivateButton = uifactory.addFormLink("dont.activate", buttonsCont, Link.BUTTON);
+	}
+	
+	@Override
+	protected void doDispose() {
+		//
+	}
+
+	@Override
+	public boolean isUserInteractionRequired(UserRequest ureq) {
+		return messageModule.isEnabled() && messageModule.isResetPasswordEnabled()
+				&& !ureq.getUserSession().getRoles().isGuestOnly()
+				&& !messageService.validate(ureq.getIdentity().getUser().getProperty(UserConstants.SMSTELMOBILE, getLocale()));
+	}
+
+	@Override
+	protected boolean validateFormLogic(UserRequest ureq) {
+		boolean allOk = true;
+		
+		phoneEl.clearError();
+		if(phoneEl.isVisible()) {
+			if(!StringHelper.containsNonWhitespace(phoneEl.getValue())) {
+				phoneEl.setErrorKey("form.legende.mandatory", null);
+				allOk &= false;
+			} else if(!messageService.validate(phoneEl.getValue())) {
+				phoneEl.setErrorKey("error.phone.invalid", null);
+				allOk &= false;
+			}
+		}
+		
+		tokenEl.clearError();
+		if(tokenEl.isVisible()) {
+			String tokenValue = tokenEl.getValue();
+			if(!StringHelper.containsNonWhitespace(tokenValue)) {
+				tokenEl.setErrorKey("form.legende.mandatory", null);
+				allOk &= false;
+			} else if(sentToken == null || !sentToken.equals(tokenValue)) {
+				tokenEl.setErrorKey("error.invalid.token", null);
+				allOk &= false;
+			}
+		}
+		
+		return allOk &= super.validateFormLogic(ureq);
+	}
+
+	@Override
+	protected void formOK(UserRequest ureq) {
+		if(phoneEl.isVisible()) {
+			try {
+				phoneEl.setVisible(false);
+				tokenEl.setVisible(true);
+				
+				sentToken = messageService.generateToken();
+				String msg = translate("sms.token", new String[]{ sentToken });
+				messageService.sendMessage(msg, phoneEl.getValue(), getIdentity());
+			} catch (SimpleMessageException e) {
+				phoneEl.setVisible(true);
+				tokenEl.setVisible(false);
+				phoneEl.setErrorKey("error.phone.invalid", null);
+			}
+		} else if(tokenEl.isVisible()) {
+			User user = getIdentity().getUser();
+			user.setProperty(UserConstants.SMSTELMOBILE, phoneEl.getValue());
+			userManager.updateUserFromIdentity(getIdentity());
+			fireEvent(ureq, Event.DONE_EVENT);
+		}
+	}
+
+	@Override
+	protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) {
+		if(source == dontActivateButton) {
+			fireEvent(ureq, Event.DONE_EVENT);
+		}
+		super.formInnerEvent(ureq, source, event);
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/org/olat/core/commons/services/sms/ui/SimpleMessageServiceAdminConfigurationController.java b/src/main/java/org/olat/core/commons/services/sms/ui/SimpleMessageServiceAdminConfigurationController.java
new file mode 100644
index 00000000000..cd0545bbf92
--- /dev/null
+++ b/src/main/java/org/olat/core/commons/services/sms/ui/SimpleMessageServiceAdminConfigurationController.java
@@ -0,0 +1,140 @@
+/**
+ * <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.sms.ui;
+
+import java.util.List;
+
+import org.olat.core.commons.services.sms.MessagesSPI;
+import org.olat.core.commons.services.sms.SimpleMessageModule;
+import org.olat.core.commons.services.sms.SimpleMessageService;
+import org.olat.core.gui.UserRequest;
+import org.olat.core.gui.components.form.flexible.FormItem;
+import org.olat.core.gui.components.form.flexible.FormItemContainer;
+import org.olat.core.gui.components.form.flexible.elements.MultipleSelectionElement;
+import org.olat.core.gui.components.form.flexible.elements.SingleSelection;
+import org.olat.core.gui.components.form.flexible.impl.FormBasicController;
+import org.olat.core.gui.components.form.flexible.impl.FormEvent;
+import org.olat.core.gui.control.Controller;
+import org.olat.core.gui.control.WindowControl;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * 
+ * Initial date: 3 févr. 2017<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class SimpleMessageServiceAdminConfigurationController extends FormBasicController {
+	
+	private static final String[] onKeys = new String[]{ "on" };
+	
+	private SingleSelection serviceEl;
+	private MultipleSelectionElement enableEl;
+	private MultipleSelectionElement resetPasswordEl;
+	
+	@Autowired
+	private SimpleMessageModule messageModule;
+	@Autowired
+	private SimpleMessageService messagesService;
+	
+	public SimpleMessageServiceAdminConfigurationController(UserRequest ureq, WindowControl wControl) {
+		super(ureq, wControl);
+		
+		initForm(ureq);
+	}
+
+	@Override
+	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
+		setFormTitle("admin.configuration.title");
+		setFormDescription("admin.configuration.description");
+		
+		String[] onValues = new String[]{ translate("on") };
+		enableEl = uifactory.addCheckboxesHorizontal("enable", "admin.enable", formLayout, onKeys, onValues);
+		enableEl.addActionListener(FormEvent.ONCHANGE);
+		if(messageModule.isEnabled()) {
+			enableEl.select(onKeys[0], true);
+		}
+		
+		List<MessagesSPI> spies = messagesService.getMessagesSpiList();
+		String[] serviceKeys = new String[spies.size()];
+		String[] serviceValues = new String[spies.size()];
+		for(int i=spies.size(); i-->0; ) {
+			serviceKeys[i] = spies.get(i).getId();
+			serviceValues[i] = spies.get(i).getName();
+		}
+		serviceEl = uifactory.addDropdownSingleselect("service", "service", formLayout, serviceKeys, serviceValues, null);
+		if(messagesService.getMessagesSpi() != null) {
+			String activeServiceId = messagesService.getMessagesSpi().getId();
+			for(int i=serviceKeys.length; i-->0; ) {
+				if(serviceKeys[i].equals(activeServiceId)) {
+					serviceEl.select(serviceKeys[i], true);
+				}
+			}
+		}
+
+		String[] resetPasswordValues = new String[]{ translate("on.sms") };
+		resetPasswordEl = uifactory.addCheckboxesHorizontal("reset.password", "reset.password", formLayout, onKeys, resetPasswordValues);
+		resetPasswordEl.addActionListener(FormEvent.ONCHANGE);
+		if(messageModule.isResetPasswordEnabled()) {
+			resetPasswordEl.select(onKeys[0], true);
+		}
+		updateEnableDisable();
+	}
+	
+	private void updateEnableDisable() {
+		boolean enabled = enableEl.isAtLeastSelected(1);
+		serviceEl.setVisible(enabled);
+		resetPasswordEl.setVisible(enabled);
+		
+		serviceEl.clearError();
+		if(serviceEl.isOneSelected()) {
+			String serviceId = serviceEl.getSelectedKey();
+			MessagesSPI spi = messagesService.getMessagesSpi(serviceId);
+			if(spi != null && !spi.isValid()) {
+				serviceEl.setErrorKey("warning.spi.not.configured", new String[]{ spi.getName() });
+			}
+		}
+	}
+	
+	@Override
+	protected void doDispose() {
+		//
+	}
+
+	@Override
+	protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) {
+		if(enableEl == source) {
+			updateEnableDisable();
+			messageModule.setEnabled(enableEl.isAtLeastSelected(1));
+		} else if(resetPasswordEl == source) {
+			messageModule.setResetPasswordEnabled(resetPasswordEl.isAtLeastSelected(1));
+		}
+		super.formInnerEvent(ureq, source, event);
+	}
+
+	@Override
+	protected void formOK(UserRequest ureq) {
+		//
+	}
+
+	
+	
+
+}
diff --git a/src/main/java/org/olat/core/commons/services/sms/ui/SimpleMessageServiceAdminController.java b/src/main/java/org/olat/core/commons/services/sms/ui/SimpleMessageServiceAdminController.java
new file mode 100644
index 00000000000..ed8f1511d78
--- /dev/null
+++ b/src/main/java/org/olat/core/commons/services/sms/ui/SimpleMessageServiceAdminController.java
@@ -0,0 +1,102 @@
+/**
+ * <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.sms.ui;
+
+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.segmentedview.SegmentViewComponent;
+import org.olat.core.gui.components.segmentedview.SegmentViewEvent;
+import org.olat.core.gui.components.segmentedview.SegmentViewFactory;
+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;
+
+/**
+ * 
+ * Initial date: 3 févr. 2017<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class SimpleMessageServiceAdminController extends BasicController {
+
+	private final VelocityContainer mainVC;
+	private final SegmentViewComponent segmentView;
+	private final Link configurationLink, statisticsLink;
+	
+	private MessagesStatisticsController statisticsCtrl;
+	private SimpleMessageServiceAdminConfigurationController configCtrl;
+	
+	public SimpleMessageServiceAdminController(UserRequest ureq, WindowControl wControl) {
+		super(ureq, wControl);
+		
+		mainVC = createVelocityContainer("admin");
+		
+		segmentView = SegmentViewFactory.createSegmentView("segments", mainVC, this);
+		segmentView.setReselect(true);
+
+		configurationLink = LinkFactory.createLink("admin.settings", mainVC, this);
+		segmentView.addSegment(configurationLink, true);
+		statisticsLink = LinkFactory.createLink("admin.statistics", mainVC, this);
+		segmentView.addSegment(statisticsLink, false);
+		
+		doOpenConfiguration(ureq);
+		putInitialPanel(mainVC);
+	}
+	
+	@Override
+	protected void doDispose() {
+		//
+	}
+
+	@Override
+	protected void event(UserRequest ureq, Component source, Event event) {
+		if(source == segmentView) {
+			if(event instanceof SegmentViewEvent) {
+				SegmentViewEvent sve = (SegmentViewEvent)event;
+				String segmentCName = sve.getComponentName();
+				Component clickedLink = mainVC.getComponent(segmentCName);
+				if (clickedLink == configurationLink) {
+					doOpenConfiguration(ureq);
+				} else if (clickedLink == statisticsLink) {
+					doOpenStatistics(ureq);
+				}
+			}
+		}
+	}
+
+	private void doOpenStatistics(UserRequest ureq) {
+		if(statisticsCtrl == null) {
+			statisticsCtrl = new MessagesStatisticsController(ureq, getWindowControl());
+			listenTo(statisticsCtrl);
+		}
+		mainVC.put("segmentCmp", statisticsCtrl.getInitialComponent());
+	}
+	
+	private void doOpenConfiguration(UserRequest ureq) {
+		if(configCtrl == null) {
+			configCtrl = new SimpleMessageServiceAdminConfigurationController(ureq, getWindowControl());
+			listenTo(configCtrl);
+		}
+		mainVC.put("segmentCmp", configCtrl.getInitialComponent());
+	}
+}
diff --git a/src/main/java/org/olat/core/commons/services/sms/ui/YearCellRenderer.java b/src/main/java/org/olat/core/commons/services/sms/ui/YearCellRenderer.java
new file mode 100644
index 00000000000..70bbf4e5567
--- /dev/null
+++ b/src/main/java/org/olat/core/commons/services/sms/ui/YearCellRenderer.java
@@ -0,0 +1,30 @@
+package org.olat.core.commons.services.sms.ui;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiCellRenderer;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableComponent;
+import org.olat.core.gui.render.Renderer;
+import org.olat.core.gui.render.StringOutput;
+import org.olat.core.gui.render.URLBuilder;
+import org.olat.core.gui.translator.Translator;
+
+/**
+ * 
+ * Initial date: 7 févr. 2017<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class YearCellRenderer implements FlexiCellRenderer {
+	
+	private static final SimpleDateFormat format = new SimpleDateFormat("yyyy");
+
+	@Override
+	public void render(Renderer renderer, StringOutput target, Object cellValue, int row, FlexiTableComponent source,
+			URLBuilder ubu, Translator translator) {
+		if(cellValue instanceof Date) {
+			target.append(format.format((Date)cellValue));
+		}
+	}
+}
diff --git a/src/main/java/org/olat/core/commons/services/sms/ui/_content/admin.html b/src/main/java/org/olat/core/commons/services/sms/ui/_content/admin.html
new file mode 100644
index 00000000000..c0fa0f020aa
--- /dev/null
+++ b/src/main/java/org/olat/core/commons/services/sms/ui/_content/admin.html
@@ -0,0 +1,4 @@
+$r.render("segments")
+<div class="o_segments_content clearfix">
+	$r.render("segmentCmp")
+</div>
\ No newline at end of file
diff --git a/src/main/java/org/olat/core/commons/services/sms/ui/_content/statistics.html b/src/main/java/org/olat/core/commons/services/sms/ui/_content/statistics.html
new file mode 100644
index 00000000000..9701851200f
--- /dev/null
+++ b/src/main/java/org/olat/core/commons/services/sms/ui/_content/statistics.html
@@ -0,0 +1 @@
+$r.render("stats")
\ No newline at end of file
diff --git a/src/main/java/org/olat/core/commons/services/sms/ui/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/core/commons/services/sms/ui/_i18n/LocalStrings_de.properties
new file mode 100644
index 00000000000..d804ec7e955
--- /dev/null
+++ b/src/main/java/org/olat/core/commons/services/sms/ui/_i18n/LocalStrings_de.properties
@@ -0,0 +1,25 @@
+#Tue Feb 07 13:58:37 CET 2017
+admin.configuration.description=<p>Benarchitugung per SMS ist eine optionale Komponente.</p><p>Achtung\: durch das Versenden von SMS entstehen Kosten pro SMS\!</p>
+admin.configuration.title=SMS Konfiguration
+admin.enable=SMS Versand
+admin.menu.title=SMS
+admin.menu.title.alt=SMS Konfiguration udn Statistik
+admin.settings=SMS Dienst Konfiguration
+admin.statistics=SMS Versand
+confirm.sms.phone=<h3>SMS Authentifizierung</h3><p>Das System erm\u00F6glicht eine Authentifizierung \u00FCber SMS um ein verlorenes Passwort zur\u00FCcksetzen. Bitte geben Sie Ihre Mobile telefonnummer an um diesen Dienst zu aktivieren</p>
+dont.activate=Nicht aktivieren
+error.invalid.token=Token stimmt nicht\!
+error.phone.invalid=Das Nummer ist nicht valid (z.B. +41 12 345 67 89)
+on=ein
+on.sms=mit SMS Code
+reset.password=Password zur\u00FCcksetzen
+service=Dienst
+sms.phone.number=Telephon Mobil
+sms.phone.number.hint=+41 12 345 67 89
+sms.token=Ihr Token ist {0}
+sms.token.number=Token
+sms.token.number.explain=Bitte geben Sie den 6 stellige Nummer ein dass Sie per SMS bekommen haben.
+table.header.month=Monat
+table.header.numOfMessages=SMS
+table.header.year=Jahr
+warning.spi.not.configured=Dienst ist nicht konfiguriert
diff --git a/src/main/java/org/olat/core/commons/services/sms/ui/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/core/commons/services/sms/ui/_i18n/LocalStrings_en.properties
new file mode 100644
index 00000000000..5e77f2caa27
--- /dev/null
+++ b/src/main/java/org/olat/core/commons/services/sms/ui/_i18n/LocalStrings_en.properties
@@ -0,0 +1,25 @@
+#Tue Feb 07 13:47:28 CET 2017
+admin.configuration.description=<p>Notification with SMS is an optional component.</p><p>Warning\: SMS costs iccurend per SMS\!</p>
+admin.configuration.title=SMS configuration
+admin.enable=SMS distribution
+admin.menu.title=SMS
+admin.menu.title.alt=SMS configuration and statistics
+admin.settings=SMS service configuration
+admin.statistics=SMS distribution
+confirm.sms.phone=<h3>SMS Authentication</h3><p>The system allows to authenticate via SMS to reset a lost password. Please enter your mobile phone number to activate this service.</p>
+dont.activate=Don't activate
+error.invalid.token=The code is not valid.
+error.phone.invalid=The number is not a valid phone number (e.g. +41 12 345 67 89)
+on=on
+on.sms=with SMS code
+reset.password=Reset password
+service=Service
+sms.phone.number=Mobile telefon
+sms.phone.number.hint=+41 12 345 67 89
+sms.token=Your token is {0}
+sms.token.number=Code
+sms.token.number.explain=Please enter the 6 digit number that you received by SMS.
+table.header.month=Month
+table.header.numOfMessages=SMS
+table.header.year=Year
+warning.spi.not.configured=The service is not configured.
diff --git a/src/main/java/org/olat/core/commons/services/sms/ui/_i18n/LocalStrings_fr.properties b/src/main/java/org/olat/core/commons/services/sms/ui/_i18n/LocalStrings_fr.properties
new file mode 100644
index 00000000000..ba90e09bcb5
--- /dev/null
+++ b/src/main/java/org/olat/core/commons/services/sms/ui/_i18n/LocalStrings_fr.properties
@@ -0,0 +1 @@
+#Sun Sep 05 15:41:48 CEST 2010
diff --git a/src/main/java/org/olat/core/id/UserConstants.java b/src/main/java/org/olat/core/id/UserConstants.java
index 271f211ef56..e9399883c8e 100644
--- a/src/main/java/org/olat/core/id/UserConstants.java
+++ b/src/main/java/org/olat/core/id/UserConstants.java
@@ -44,6 +44,8 @@ public class UserConstants {
 	public static final String TELOFFICE = "telOffice";
 	/** TELPRIVATE user field identifier. */
 	public static final String TELPRIVATE = "telPrivate";
+	/** TELMOBILE user field identifier. */
+	public static final String SMSTELMOBILE = "smsTelMobile";
 	/** SKYPE user field identifier. */
 	public static final String SKYPE = "skype";
 	/** HOMEPAGE user field identifier. */
diff --git a/src/main/java/org/olat/course/nodes/cl/ui/CheckboxAssessmentDataModelSorter.java b/src/main/java/org/olat/course/nodes/cl/ui/CheckboxAssessmentDataModelSorter.java
index 657ca3acb19..c4764e12b20 100644
--- a/src/main/java/org/olat/course/nodes/cl/ui/CheckboxAssessmentDataModelSorter.java
+++ b/src/main/java/org/olat/course/nodes/cl/ui/CheckboxAssessmentDataModelSorter.java
@@ -1,3 +1,22 @@
+/**
+ * <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.course.nodes.cl.ui;
 
 import java.util.Collections;
diff --git a/src/main/java/org/olat/ldap/ui/LDAPAuthenticationController.java b/src/main/java/org/olat/ldap/ui/LDAPAuthenticationController.java
index dccad37ca9f..c7a0c5008e0 100644
--- a/src/main/java/org/olat/ldap/ui/LDAPAuthenticationController.java
+++ b/src/main/java/org/olat/ldap/ui/LDAPAuthenticationController.java
@@ -100,11 +100,11 @@ public class LDAPAuthenticationController extends AuthenticationController imple
 		loginComp = createVelocityContainer("ldaplogin");
 		
 		if(userModule.isAnyPasswordChangeAllowed() && ldapLoginModule.isPropagatePasswordChangedOnLdapServer()) {
-			Link link = LinkFactory.createLink("_olat_login_change_pwd", "menu.pw", loginComp, this);
+			Link link = LinkFactory.createLink("_ldap_login_change_pwd", "menu.pw", loginComp, this);
 			link.setElementCssClass("o_login_pwd");
 			pwLink = link;
 		} else if(StringHelper.containsNonWhitespace(ldapLoginModule.getChangePasswordUrl())) {
-			ExternalLink link = new ExternalLink("_olat_login_change_pwd", "menu.pw");
+			ExternalLink link = new ExternalLink("_ldap_login_change_pwd", "menu.pw");
 			link.setElementCssClass("o_login_pwd");
 			link.setName(translate("menu.pw"));
 			link.setUrl(ldapLoginModule.getChangePasswordUrl());
diff --git a/src/main/java/org/olat/login/LoginAuthprovidersController.java b/src/main/java/org/olat/login/LoginAuthprovidersController.java
index 8c75e4a99f7..08a57b79b85 100644
--- a/src/main/java/org/olat/login/LoginAuthprovidersController.java
+++ b/src/main/java/org/olat/login/LoginAuthprovidersController.java
@@ -166,7 +166,7 @@ public class LoginAuthprovidersController extends MainLayoutBasicController impl
 		contentBorn.put("loginComp", authController.getInitialComponent());
 		contentBorn.contextPut("currentProvider", authProvider.getName());		
 		Collection<AuthenticationProvider> providers = loginModule.getAuthenticationProviders();
-		List<AuthenticationProvider> providerSet = new ArrayList<AuthenticationProvider>(providers.size());
+		List<AuthenticationProvider> providerSet = new ArrayList<>(providers.size());
 		int count = 0;
 		for (AuthenticationProvider prov : providers) {
 			if (prov.isEnabled()) {
diff --git a/src/main/java/org/olat/login/auth/OLATAuthManager.java b/src/main/java/org/olat/login/auth/OLATAuthManager.java
index 0be2fda87cc..28a048251eb 100644
--- a/src/main/java/org/olat/login/auth/OLATAuthManager.java
+++ b/src/main/java/org/olat/login/auth/OLATAuthManager.java
@@ -31,7 +31,6 @@ import java.util.Map;
 
 import org.olat.basesecurity.Authentication;
 import org.olat.basesecurity.BaseSecurity;
-import org.olat.core.CoreSpringFactory;
 import org.olat.core.commons.services.webdav.manager.WebDAVAuthManager;
 import org.olat.core.gui.translator.Translator;
 import org.olat.core.id.Identity;
@@ -75,7 +74,7 @@ import com.thoughtworks.xstream.XStream;
 @Service("olatAuthenticationSpi")
 public class OLATAuthManager extends BasicManager implements AuthenticationSPI {
 	
-	private static OLog log = Tracing.createLoggerFor(OLATAuthManager.class);
+	private static final OLog log = Tracing.createLoggerFor(OLATAuthManager.class);
 	
 	@Autowired
 	private UserManager userManager;
@@ -90,6 +89,8 @@ public class OLATAuthManager extends BasicManager implements AuthenticationSPI {
 	@Autowired
 	private LDAPLoginModule ldapLoginModule;
 	@Autowired
+	private LDAPLoginManager ldapLoginManager;
+	@Autowired
 	private RegistrationManager registrationManager;
 	
 	/**
@@ -201,7 +202,6 @@ public class OLATAuthManager extends BasicManager implements AuthenticationSPI {
 		if(ldapAuth != null) {
 			if(ldapLoginModule.isPropagatePasswordChangedOnLdapServer()) {
 				LDAPError ldapError = new LDAPError();
-				LDAPLoginManager ldapLoginManager = (LDAPLoginManager) CoreSpringFactory.getBean("org.olat.ldap.LDAPLoginManager");
 				ldapLoginManager.changePassword(identity, newPwd, ldapError);
 				log.audit(doer.getName() + " change the password on the LDAP server for identity: " + identity.getName());
 				allOk = ldapError.isEmpty();
@@ -229,7 +229,7 @@ public class OLATAuthManager extends BasicManager implements AuthenticationSPI {
 		String[] args = new String[] {
 				identity.getName(),//0: changed users username
 				identity.getUser().getProperty(UserConstants.EMAIL, locale),// 1: changed users email address
-				UserManager.getInstance().getUserDisplayName(doer.getUser()),// 2: Name (first and last name) of user who changed the password
+				userManager.getUserDisplayName(doer.getUser()),// 2: Name (first and last name) of user who changed the password
 				WebappHelper.getMailConfig("mailSupport"), //3: configured support email address
 				changePwUrl //4: direct link to change password workflow (e.g. https://xx.xx.xx/olat/url/changepw/0)
 		};
diff --git a/src/main/java/org/olat/registration/ConfirmTokenController.java b/src/main/java/org/olat/registration/ConfirmTokenController.java
new file mode 100644
index 00000000000..8951e48e678
--- /dev/null
+++ b/src/main/java/org/olat/registration/ConfirmTokenController.java
@@ -0,0 +1,91 @@
+/**
+ * <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.registration;
+
+import org.olat.core.gui.UserRequest;
+import org.olat.core.gui.components.form.flexible.FormItemContainer;
+import org.olat.core.gui.components.form.flexible.elements.TextElement;
+import org.olat.core.gui.components.form.flexible.impl.FormBasicController;
+import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer;
+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.id.Identity;
+import org.olat.core.util.StringHelper;
+
+/**
+ * 
+ * Initial date: 6 févr. 2017<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class ConfirmTokenController extends FormBasicController {
+	
+	private TextElement tokenEl;
+	
+	private final String token;
+	private final Identity recipient;
+	
+	public ConfirmTokenController(UserRequest ureq, WindowControl wControl, Identity recipient, String token) {
+		super(ureq, wControl);
+		this.recipient = recipient;
+		this.token = token;
+		initForm(ureq);
+	}
+	
+	public Identity getRecipient() {
+		return recipient;
+	}
+
+	@Override
+	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
+		tokenEl = uifactory.addTextElement("token", "pw.change.confirm.token", 32, "", formLayout);
+		
+		FormLayoutContainer buttonsCont = FormLayoutContainer.createButtonLayout("buttons", getTranslator());
+		formLayout.add(buttonsCont);
+		uifactory.addFormSubmitButton("pw.change.confirm", buttonsCont);
+	}
+
+	@Override
+	protected void doDispose() {
+		//
+	}
+
+	@Override
+	protected boolean validateFormLogic(UserRequest ureq) {
+		boolean allOk = true;
+		
+		if(StringHelper.containsNonWhitespace(tokenEl.getValue())) {
+			if(!tokenEl.getValue().equals(token)) {
+				tokenEl.setErrorKey("error.pw.change.confirm.token", null);
+				allOk &= false;
+			}
+		} else {
+			tokenEl.setErrorKey("form.legende.mandatory", null);
+			allOk &= false;
+		}
+		return allOk & super.validateFormLogic(ureq);
+	}
+
+	@Override
+	protected void formOK(UserRequest ureq) {
+		fireEvent(ureq, Event.DONE_EVENT);
+	}
+}
diff --git a/src/main/java/org/olat/registration/PwChangeController.java b/src/main/java/org/olat/registration/PwChangeController.java
index d15a9a5e0a5..bc821f63779 100644
--- a/src/main/java/org/olat/registration/PwChangeController.java
+++ b/src/main/java/org/olat/registration/PwChangeController.java
@@ -32,10 +32,10 @@ import java.util.List;
 import java.util.Locale;
 
 import org.olat.basesecurity.Authentication;
-import org.olat.basesecurity.BaseSecurityManager;
+import org.olat.basesecurity.BaseSecurity;
 import org.olat.basesecurity.BaseSecurityModule;
-import org.olat.core.CoreSpringFactory;
 import org.olat.core.commons.fullWebApp.LayoutMain3ColsController;
+import org.olat.core.commons.services.sms.SimpleMessageModule;
 import org.olat.core.dispatcher.DispatcherModule;
 import org.olat.core.gui.UserRequest;
 import org.olat.core.gui.components.Component;
@@ -53,6 +53,7 @@ import org.olat.core.helpers.Settings;
 import org.olat.core.id.Identity;
 import org.olat.core.id.Preferences;
 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;
@@ -72,23 +73,32 @@ import org.springframework.beans.factory.annotation.Autowired;
 public class PwChangeController extends BasicController {
 
 	private static String SEPARATOR = "____________________________________________________________________\n";
-	private VelocityContainer myContent;
+
+	private Panel passwordPanel;
+	private Link pwchangeHomelink;
+	private final VelocityContainer myContent;
 	
-	private Panel pwarea;
-	private WizardInfoController wic;
-	private final MailManager mailManager;
-	private String pwKey;
 	private PwChangeForm pwf;
-	private TemporaryKeyImpl tempKey;
+	private WizardInfoController wic;
+	private SendMessageController sendSmsCtr;
+	private ConfirmTokenController confirmTokenCtr;
 	private EmailOrUsernameFormController emailOrUsernameCtr;
-	private Link pwchangeHomelink;
+	
+	private String pwKey;
+	private TemporaryKey tempKey;
 	
 	@Autowired
 	private UserModule userModule;
 	@Autowired
 	private RegistrationManager rm;
 	@Autowired
+	private MailManager mailManager;
+	@Autowired
 	private UserManager userManager;
+	@Autowired
+	private BaseSecurity securityManager;
+	@Autowired
+	private SimpleMessageModule smsModule;
 	
 	/**
 	 * Controller to change a user's password.
@@ -106,38 +116,34 @@ public class PwChangeController extends BasicController {
 	 */
 	public PwChangeController(UserRequest ureq, WindowControl wControl, String initialEmail, boolean modal) {
 		super(ureq, wControl);
-		mailManager = CoreSpringFactory.getImpl(MailManager.class);
 		myContent = createVelocityContainer("pwchange");
 		wic = new WizardInfoController(ureq, 4);
 		myContent.put("pwwizard", wic.getInitialComponent());
-		pwarea = new Panel("pwarea");
-		myContent.put("pwarea", pwarea);
+		passwordPanel = new Panel("pwarea");
+		myContent.put("pwarea", passwordPanel);
 		pwKey = ureq.getHttpReq().getParameter("key");
-		if (pwKey == null || pwKey.equals("")) {
-			// no temporarykey is given, we assume step 1
-			createEmailForm(ureq, wControl, initialEmail);
-		} else {
+		
+		if (StringHelper.containsNonWhitespace(pwKey)) {
 			// we check if given key is a valid temporary key
 			tempKey = rm.loadTemporaryKeyByRegistrationKey(pwKey);
 			// if key is not valid we redirect to first page
 			if (tempKey == null) {
 				// error, there should be an entry
-				getWindowControl().setError(translate("pwkey.missingentry"));
+				showError("pwkey.missingentry");
 				createEmailForm(ureq, wControl, initialEmail);
 			} else {
-				wic.setCurStep(3);
-				pwf = new PwChangeForm(ureq, wControl);
-				listenTo(pwf);
-				myContent.contextPut("pwdhelp", translate("pwdhelp"));
-				myContent.contextPut("text", translate("step3.pw.text"));
-				pwarea.setContent(pwf.getInitialComponent());				
+				showChangePasswordForm(ureq, tempKey);
 			}
+		} else {
+			// no temporarykey is given, we assume step 1
+			createEmailForm(ureq, wControl, initialEmail);
 		}
 		
 		if(modal) {
 			putInitialPanel(myContent);
 		} else {
 			LayoutMain3ColsController layoutCtr = new LayoutMain3ColsController(ureq, getWindowControl(), null, myContent, null);
+			listenTo(layoutCtr);
 			putInitialPanel(layoutCtr.getInitialComponent());
 		}
 	}
@@ -147,144 +153,204 @@ public class PwChangeController extends BasicController {
 	}
 
 	/**
-	 * just needed for creating EmailForm
+	 * Create the email / username form, the first step of the workflow.
 	 */
-	//fxdiff FXOLAT-113: business path in DMZ
 	private void createEmailForm(UserRequest ureq, WindowControl wControl, String initialEmail) {
 		myContent.contextPut("title", translate("step1.pw.title"));
 		myContent.contextPut("text", translate("step1.pw.text"));
 		removeAsListenerAndDispose(emailOrUsernameCtr);
 		emailOrUsernameCtr = new EmailOrUsernameFormController(ureq, wControl, initialEmail);
 		listenTo(emailOrUsernameCtr);
-		pwarea.setContent(emailOrUsernameCtr.getInitialComponent());
+		passwordPanel.setContent(emailOrUsernameCtr.getInitialComponent());
 	}
 
-	/**
-	 * @see org.olat.core.gui.control.DefaultController#event(org.olat.core.gui.UserRequest,
-	 *      org.olat.core.gui.components.Component, org.olat.core.gui.control.Event)
-	 */
+	@Override
 	public void event(UserRequest ureq, Component source, Event event) {
 		if (source == pwchangeHomelink) {
 			DispatcherModule.redirectToDefaultDispatcher(ureq.getHttpResp());				
 		}
 	}
 
-	/**
-	 * @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 == pwf) {
 			// pwchange Form was clicked
 			if (event == Event.DONE_EVENT) { // form
-				// validation was ok
-				wic.setCurStep(4);
-				myContent.contextPut("pwdhelp", "");
-				myContent.contextPut("text", translate("step4.pw.text"));
-				pwchangeHomelink = LinkFactory.createLink("pwchange.homelink", myContent, this);
-				pwchangeHomelink.setCustomEnabledLinkCSS("btn btn-primary");
-				//pwf.setVisible(false);
-				pwarea.setVisible(false);
-				List<Identity> identToChanges = userManager.findIdentitiesByEmail(Collections.singletonList(tempKey.getEmailAddress()));
-				if(identToChanges == null || identToChanges.size() == 0 || identToChanges.size() > 1) {
-					getWindowControl().setError(translate("pwchange.failed"));
-				} else {
-					Identity identToChange = identToChanges.get(0);
-					if(!pwf.saveFormData(identToChange)) {
-						getWindowControl().setError(translate("pwchange.failed"));
-					}
-				}
-				rm.deleteTemporaryKeyWithId(tempKey.getRegistrationKey());				
+				showChangePasswordEnd();
 			} else if (event == Event.CANCELLED_EVENT) {
-				getWindowControl().setInfo(translate("pwform.cancelled"));
+				showInfo("pwform.cancelled");
 				fireEvent(ureq, Event.CANCELLED_EVENT);
 			} 
 		} else if (source == emailOrUsernameCtr) {
 			// eMail Form was clicked
 			if (event == Event.DONE_EVENT) { // form
-				// Email requested for tempkey save the fields somewhere
-				String emailOrUsername = emailOrUsernameCtr.getEmailOrUsername();
-				emailOrUsername = emailOrUsername.trim();
-
-				// get remote address
-				String ip = ureq.getHttpReq().getRemoteAddr();
-				String today = DateFormat.getDateInstance(DateFormat.LONG, ureq.getLocale()).format(new Date());
-				// mailer configuration
-				String serverpath = Settings.getServerContextPathURI();
-				String servername = ureq.getHttpReq().getServerName();
-				if(isLogDebugEnabled()) {
-					logDebug("this servername is " + servername + " and serverpath is " + serverpath, null);
-				}
+				doProcessEmailOrUsername(ureq);
+			} else if (event == Event.CANCELLED_EVENT) {
+				fireEvent(ureq, Event.CANCELLED_EVENT);
+			}
+		} else if (source == sendSmsCtr) {
+			if (event == Event.DONE_EVENT) { // form
+				doConfirmSendToken(ureq, sendSmsCtr.getRecipient(), sendSmsCtr.getSentToken());
+			} else if (event == Event.CANCELLED_EVENT) {
+				fireEvent(ureq, Event.CANCELLED_EVENT);
+			}
+		} else if(source == confirmTokenCtr) {
+			if (event == Event.DONE_EVENT) { // form
+				showChangePasswordForm(ureq, confirmTokenCtr.getRecipient());
+			} else if (event == Event.CANCELLED_EVENT) {
+				fireEvent(ureq, Event.CANCELLED_EVENT);
+			}
+		}
+	}
+	
+	private void doProcessEmailOrUsername(UserRequest ureq) {
+		// Email requested for tempkey save the fields somewhere
+		String emailOrUsername = emailOrUsernameCtr.getEmailOrUsername();
+		emailOrUsername = emailOrUsername.trim();
 
-				// Look for user in "Person" and "user" tables
-				Identity identity = null;
-				// See if the entered value is a username
-				identity = BaseSecurityManager.getInstance().findIdentityByName(emailOrUsername);
-				if (identity == null) {
-					// Try fallback with email, maybe user used his email address instead
-					// only do this, if its really an email, may lead to multiple results else.
-					if (MailHelper.isValidEmailAddress(emailOrUsername)) {
-						List<Identity> identities = userManager.findIdentitiesByEmail(Collections.singletonList(emailOrUsername));
-						if(identities.size() == 1) {
-							identity = identities.get(0);
-						}
-					}
-				}
-				if (identity != null) {
-					// check if user has an OLAT provider token, otherwhise a pwd change makes no sense
-					Authentication auth = BaseSecurityManager.getInstance().findAuthentication(identity, BaseSecurityModule.getDefaultAuthProviderIdentifier());
-					if (auth == null || !userModule.isPwdChangeAllowed(identity)) { 
-						getWindowControl().setWarning(translate("password.cantchange"));
-						return;
-					}
-					Preferences prefs = identity.getUser().getPreferences();
-					Locale locale = I18nManager.getInstance().getLocaleOrDefault(prefs.getLanguage());
-					ureq.getUserSession().setLocale(locale);
-					myContent.contextPut("locale", locale);
-					Translator userTrans = Util.createPackageTranslator(PwChangeController.class, locale) ;
-					String emailAdress = identity.getUser().getProperty(UserConstants.EMAIL, locale); 
-					TemporaryKey tk = rm.loadTemporaryKeyByEmail(emailAdress);
-					if (tk == null) tk = rm.createTemporaryKeyByEmail(emailAdress, ip, RegistrationManager.PW_CHANGE);
-					myContent.contextPut("pwKey", tk.getRegistrationKey());
-					StringBuilder body = new StringBuilder();
-					body.append(userTrans.translate("pwchange.intro", new String[] { identity.getName() }))
-					    .append(userTrans.translate("pwchange.body", new String[] { serverpath, tk.getRegistrationKey(), I18nManager.getInstance().getLocaleKey(ureq.getLocale()) }))
-					    .append(SEPARATOR)
-					    .append(userTrans.translate("reg.wherefrom", new String[] { serverpath, today, ip }));
+		Identity identity = findIdentityByUsernameOrEmail(emailOrUsername);
+		if (identity != null) {
+			if(smsModule.isEnabled() && smsModule.isResetPasswordEnabled()
+					&& StringHelper.containsNonWhitespace(identity.getUser().getProperty(UserConstants.SMSTELMOBILE, getLocale()))) {
+				tempKey = sendEmail(ureq, identity);
+				sendSms(ureq, identity);
+			} else {
+				sendEmail(ureq, identity);
+			}
+		} else {
+			// no user exists, this is an error in the pwchange page
+			// REVIEW:pb:2009-11-23:gw, setter should not be necessary. -> check the error already in th emailOrUsernameCtr
+			emailOrUsernameCtr.setUserNotIdentifiedError();
+		}
+	}
+	
+	private void sendSms(UserRequest ureq, Identity recipient) {
+		removeAsListenerAndDispose(sendSmsCtr);
+		
+		sendSmsCtr = new SendMessageController(ureq, getWindowControl(), recipient);
+		listenTo(sendSmsCtr);
+		passwordPanel.setContent(sendSmsCtr.getInitialComponent());	
+		
+		wic.setCurStep(2);
+		myContent.contextPut("text", translate("step2.pw.text"));
+	}
+	
+	private void doConfirmSendToken(UserRequest ureq, Identity recipient, String sentToken) {
+		confirmTokenCtr = new ConfirmTokenController(ureq, getWindowControl(), recipient, sentToken);
+		listenTo(confirmTokenCtr);
+		passwordPanel.setContent(confirmTokenCtr.getInitialComponent());	
+		
+		wic.setCurStep(3);
+		myContent.contextPut("text", translate("pw.change.confirm.descr"));
+	}
+	
+	private TemporaryKey sendEmail(UserRequest ureq, Identity identity) {
+		// check if user has an OLAT provider token, otherwhise a pwd change makes no sense
+		Authentication auth = securityManager.findAuthentication(identity, BaseSecurityModule.getDefaultAuthProviderIdentifier());
+		if (auth == null || !userModule.isPwdChangeAllowed(identity)) { 
+			getWindowControl().setWarning(translate("password.cantchange"));
+			return null;
+		}
+		Preferences prefs = identity.getUser().getPreferences();
+		Locale locale = I18nManager.getInstance().getLocaleOrDefault(prefs.getLanguage());
+		ureq.getUserSession().setLocale(locale);
+		myContent.contextPut("locale", locale);
+		
+		Translator userTrans = Util.createPackageTranslator(PwChangeController.class, locale) ;
+		String emailAdress = identity.getUser().getProperty(UserConstants.EMAIL, locale); 
+		
+		// get remote address
+		String ip = ureq.getHttpReq().getRemoteAddr();
+		String today = DateFormat.getDateInstance(DateFormat.LONG, ureq.getLocale()).format(new Date());
+		// mailer configuration
+		String serverpath = Settings.getServerContextPathURI();
 		
-					MailBundle bundle = new MailBundle();
-					bundle.setToId(identity);
-					bundle.setContent(userTrans.translate("pwchange.subject"), body.toString());
-					MailerResult result = mailManager.sendExternMessage(bundle, null, false);
-					if(result.getReturnCode() == 0) {
-						getWindowControl().setInfo(translate("email.sent"));
-						// prepare next step
-						wic.setCurStep(2);
-						myContent.contextPut("text", translate("step2.pw.text"));
-						emailOrUsernameCtr.getInitialComponent().setVisible(false);
-					} else {
-						getWindowControl().setError(translate("email.notsent"));
-					}
-				} else {
-					// no user exists, this is an error in the pwchange page
-					// REVIEW:pb:2009-11-23:gw, setter should not be necessary. -> check the error already in th emailOrUsernameCtr
-					emailOrUsernameCtr.setUserNotIdentifiedError();
+		TemporaryKey tk = rm.loadTemporaryKeyByEmail(emailAdress);
+		if (tk == null) {
+			tk = rm.createTemporaryKeyByEmail(emailAdress, ip, RegistrationManager.PW_CHANGE);
+		}
+		myContent.contextPut("pwKey", tk.getRegistrationKey());
+		StringBuilder body = new StringBuilder();
+		body.append(userTrans.translate("pwchange.intro", new String[] { identity.getName() }))
+		    .append(userTrans.translate("pwchange.body", new String[] { serverpath, tk.getRegistrationKey(), I18nManager.getInstance().getLocaleKey(ureq.getLocale()) }))
+		    .append(SEPARATOR)
+		    .append(userTrans.translate("reg.wherefrom", new String[] { serverpath, today, ip }));
+
+		MailBundle bundle = new MailBundle();
+		bundle.setToId(identity);
+		bundle.setContent(userTrans.translate("pwchange.subject"), body.toString());
+		MailerResult result = mailManager.sendExternMessage(bundle, null, false);
+		if(result.getReturnCode() == MailerResult.OK) {
+			getWindowControl().setInfo(translate("email.sent"));
+			// prepare next step
+			wic.setCurStep(2);
+			myContent.contextPut("text", translate("step2.pw.text"));
+			emailOrUsernameCtr.getInitialComponent().setVisible(false);
+		} else {
+			showError("email.notsent");
+		}
+		return tk;
+	}
+	
+	/**
+	 * Look for user in "Person" and "user" tables. Fist search by user name
+	 * and if the user cannot be found, search by email address.
+	 * @return Identity or null if not found.
+	 */
+	private Identity findIdentityByUsernameOrEmail(String emailOrUsername) {
+		// See if the entered value is a username
+		Identity identity = securityManager.findIdentityByName(emailOrUsername);
+		if (identity == null) {
+			// Try fallback with email, maybe user used his email address instead
+			// only do this, if its really an email, may lead to multiple results else.
+			if (MailHelper.isValidEmailAddress(emailOrUsername)) {
+				List<Identity> identities = userManager.findIdentitiesByEmail(Collections.singletonList(emailOrUsername));
+				if(identities.size() == 1) {
+					identity = identities.get(0);
 				}
-			} else if (event == Event.CANCELLED_EVENT) {
-				fireEvent(ureq, Event.CANCELLED_EVENT);
 			}
-		} 
+		}
+		return identity;
+	}
+	
+	private void showChangePasswordForm(UserRequest ureq, TemporaryKey temporaryKey) {
+		wic.setCurStep(3);
+		pwf = new PwChangeForm(ureq, getWindowControl(), temporaryKey);
+		listenTo(pwf);
+		myContent.contextPut("pwdhelp", translate("pwdhelp"));
+		myContent.contextPut("text", translate("step3.pw.text"));
+		passwordPanel.setContent(pwf.getInitialComponent());
+	}
+	
+	private void showChangePasswordForm(UserRequest ureq, Identity identityToChange) {
+		wic.setCurStep(3);
+		pwf = new PwChangeForm(ureq, getWindowControl(), identityToChange, tempKey);
+		listenTo(pwf);
+		myContent.contextPut("pwdhelp", translate("pwdhelp"));
+		myContent.contextPut("text", translate("step3.pw.text"));
+		passwordPanel.setContent(pwf.getInitialComponent());
 	}
 
 	/**
-	 * @see org.olat.core.gui.control.DefaultController#doDispose(boolean)
+	 * Change the password of a confirmed identity (per SMS).
+	 * 
+	 * @param identToChange The identity to change the password
 	 */
+	private void showChangePasswordEnd() {
+		// validation was ok
+		wic.setCurStep(4);
+		myContent.contextPut("pwdhelp", "");
+		myContent.contextPut("text", translate("step4.pw.text"));
+		pwchangeHomelink = LinkFactory.createLink("pwchange.homelink", myContent, this);
+		pwchangeHomelink.setCustomEnabledLinkCSS("btn btn-primary");
+		passwordPanel.setVisible(false);
+	}
+
+	@Override
 	protected void doDispose() {
 		if (wic != null) {
 			wic.dispose();
 			wic = null;
 		}
 	}
-
 }
\ No newline at end of file
diff --git a/src/main/java/org/olat/registration/PwChangeForm.java b/src/main/java/org/olat/registration/PwChangeForm.java
index 9efcc534637..a798dd381da 100644
--- a/src/main/java/org/olat/registration/PwChangeForm.java
+++ b/src/main/java/org/olat/registration/PwChangeForm.java
@@ -25,7 +25,9 @@
 
 package org.olat.registration;
 
-import org.olat.core.CoreSpringFactory;
+import java.util.Collections;
+import java.util.List;
+
 import org.olat.core.gui.UserRequest;
 import org.olat.core.gui.components.form.flexible.FormItemContainer;
 import org.olat.core.gui.components.form.flexible.elements.TextElement;
@@ -38,6 +40,7 @@ import org.olat.core.util.Util;
 import org.olat.login.auth.OLATAuthManager;
 import org.olat.user.ChangePasswordForm;
 import org.olat.user.UserManager;
+import org.springframework.beans.factory.annotation.Autowired;
 
 /**
  * Description:
@@ -49,24 +52,41 @@ public class PwChangeForm extends FormBasicController {
 	private TextElement newpass1;
 	private TextElement newpass2; // confirm
 	
-	private final OLATAuthManager olatAuthenticationSpi;
+	private TemporaryKey tempKey;
+	private Identity identityToChange;
+	
+	@Autowired
+	private RegistrationManager rm;
+	@Autowired
+	private UserManager userManager;
+	@Autowired
+	private OLATAuthManager olatAuthenticationSpi;
 	
 	/**
 	 * Password change form.
 	 * @param name
 	 */
-	public PwChangeForm(UserRequest ureq, WindowControl wControl) {
+	public PwChangeForm(UserRequest ureq, WindowControl wControl, Identity identityToChange, TemporaryKey tempKey) {
 		super(ureq, wControl, null, Util.createPackageTranslator(ChangePasswordForm.class, ureq.getLocale()));
-		
-		olatAuthenticationSpi = CoreSpringFactory.getImpl(OLATAuthManager.class);
-		
+		this.identityToChange = identityToChange;
+		this.tempKey = tempKey;
+		initForm(ureq);
+	}
+	
+	/**
+	 * Password change form.
+	 * @param name
+	 */
+	public PwChangeForm(UserRequest ureq, WindowControl wControl, TemporaryKey tempKey) {
+		super(ureq, wControl, null, Util.createPackageTranslator(ChangePasswordForm.class, ureq.getLocale()));
+		this.tempKey = tempKey;
 		initForm(ureq);
 	}
 
 	@Override
 	public boolean validateFormLogic(UserRequest ureq) {
 		
-		boolean newIsValid = UserManager.getInstance().syntaxCheckOlatPassword(newpass1.getValue());
+		boolean newIsValid = userManager.syntaxCheckOlatPassword(newpass1.getValue());
 		if (!newIsValid) {
 			newpass1.setErrorKey("form.checkPassword", null);
 		}
@@ -90,6 +110,24 @@ public class PwChangeForm extends FormBasicController {
 
 	@Override
 	protected void formOK(UserRequest ureq) {
+		if(tempKey != null) {
+			List<Identity> identToChanges = userManager.findIdentitiesByEmail(Collections.singletonList(tempKey.getEmailAddress()));
+			if(identToChanges == null || identToChanges.size() == 0 || identToChanges.size() > 1) {
+				showError("pwchange.failed");
+			} else {
+				Identity identToChange = identToChanges.get(0);
+				if(!saveFormData(identToChange)) {
+					showError("pwchange.failed");
+				}
+			}
+		} else if(identityToChange != null) {
+			if(!saveFormData(identityToChange)) {
+				showError("pwchange.failed");
+			}
+		}
+		if(tempKey != null) {
+			rm.deleteTemporaryKeyWithId(tempKey.getRegistrationKey());	
+		}
 		fireEvent (ureq, Event.DONE_EVENT);
 	}
 
diff --git a/src/main/java/org/olat/registration/RegistrationController.java b/src/main/java/org/olat/registration/RegistrationController.java
index db4cc5c3b4d..b37da45a021 100644
--- a/src/main/java/org/olat/registration/RegistrationController.java
+++ b/src/main/java/org/olat/registration/RegistrationController.java
@@ -95,7 +95,7 @@ public class RegistrationController extends BasicController implements Activatea
 	private RegistrationForm2 registrationForm;
 	private LanguageChooserController langChooserController;
 	private String uniqueRegistrationKey;
-	private TemporaryKeyImpl tempKey;
+	private TemporaryKey tempKey;
 	
 	@Autowired
 	private I18nManager i18nManager;
diff --git a/src/main/java/org/olat/registration/RegistrationManager.java b/src/main/java/org/olat/registration/RegistrationManager.java
index 8ad83280364..b8115355c1b 100644
--- a/src/main/java/org/olat/registration/RegistrationManager.java
+++ b/src/main/java/org/olat/registration/RegistrationManager.java
@@ -43,7 +43,8 @@ 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.manager.BasicManager;
+import org.olat.core.logging.OLog;
+import org.olat.core.logging.Tracing;
 import org.olat.core.util.Encoder;
 import org.olat.core.util.StringHelper;
 import org.olat.core.util.Util;
@@ -62,13 +63,14 @@ import org.springframework.stereotype.Service;
  * @author Sabina Jeger
  */
 @Service("selfRegistrationManager")
-public class RegistrationManager extends BasicManager {
+public class RegistrationManager {
+	
+	private static final OLog log = Tracing.createLoggerFor(RegistrationManager.class);
 
 	public static final String PW_CHANGE = "PW_CHANGE";
-	public static final String REGISTRATION = "REGISTRATION";//fxdiff FXOLAT-113: business path in DMZ
+	public static final String REGISTRATION = "REGISTRATION";
 	public static final String EMAIL_CHANGE = "EMAIL_CHANGE";
 	protected static final int REG_WORKFLOW_STEPS = 5;
-	protected static final int PWCHANGE_WORKFLOW_STEPS = 4;
 	
 	@Autowired
 	private DB dbInstance;
@@ -106,7 +108,7 @@ public class RegistrationManager extends BasicManager {
 					break;
 				}
 			} catch (Exception e) {
-				logError("Error matching an email adress", e);
+				log.error("Error matching an email adress", e);
 			}
 		}
 		return valid;
@@ -130,7 +132,7 @@ public class RegistrationManager extends BasicManager {
 				emailDomain.matches(pattern);
 			} catch (Exception e) {
 				errors.add(domain);
-				logError("Error matching an email adress", e);
+				log.error("Error matching an email adress", e);
 			}
 		}
 		return errors;
@@ -153,7 +155,7 @@ public class RegistrationManager extends BasicManager {
 	 * @param tk Temporary key
 	 * @return the newly created subject or null
 	 */
-	public Identity createNewUserAndIdentityFromTemporaryKey(String login, String pwd, User myUser, TemporaryKeyImpl tk) {
+	public Identity createNewUserAndIdentityFromTemporaryKey(String login, String pwd, User myUser, TemporaryKey tk) {
 		Identity identity = securityManager.createAndPersistIdentityAndUserWithDefaultProviderAndUserGroup(login, null, pwd, myUser);
 		if (identity == null) return null;
 		deleteTemporaryKey(tk);
@@ -174,7 +176,7 @@ public class RegistrationManager extends BasicManager {
 			from = new InternetAddress(WebappHelper.getMailConfig("mailReplyTo"));
 			to = new Address[] { new InternetAddress(notificationMailAddress)};
 		} catch (AddressException e) {
-			logError("Could not send registration notification message, bad mail address", e);
+			log.error("Could not send registration notification message, bad mail address", e);
 			return;
 		}
 		MailerResult result = new MailerResult();
@@ -189,7 +191,7 @@ public class RegistrationManager extends BasicManager {
 		MimeMessage msg = mailManager.createMimeMessage(from, to, null, null, body, subject, null, result);
 		mailManager.sendMessage(msg, result);
 		if (result.getReturnCode() != MailerResult.OK ) {
-			logError("Could not send registration notification message, MailerResult was ::" + result.getReturnCode(), null);			
+			log.error("Could not send registration notification message, MailerResult was ::" + result.getReturnCode(), null);			
 		}
 	}
 	
@@ -202,16 +204,14 @@ public class RegistrationManager extends BasicManager {
 	 * 
 	 * @return TemporaryKey
 	 */
-	public TemporaryKeyImpl createTemporaryKeyByEmail(String email, String ip, String action) {
+	public TemporaryKey createTemporaryKeyByEmail(String email, String ip, String action) {
 		// check if the user is already registered
 		// we also try to find it in the temporarykey list
-		String q = "select r from org.olat.registration.TemporaryKeyImpl as r where r.emailAddress = :email";
-		List<TemporaryKeyImpl> tks = dbInstance.getCurrentEntityManager()
-				.createQuery(q, TemporaryKeyImpl.class)
+		List<TemporaryKey> tks = dbInstance.getCurrentEntityManager()
+				.createNamedQuery("loadTemporaryKeyByEmailAddress", TemporaryKey.class)
 				.setParameter("email", email)
 				.getResultList();
-		
-		TemporaryKeyImpl tk = null;
+		TemporaryKey tk;
 		if ((tks == null) || (tks.size() != 1)) { // no user found, create a new one
 			tk = register(email, ip, action);
 		} else {
@@ -227,7 +227,7 @@ public class RegistrationManager extends BasicManager {
 	 * 
 	 * @return true if successfully deleted
 	 */
-	public void deleteTemporaryKey(TemporaryKeyImpl key) {
+	public void deleteTemporaryKey(TemporaryKey key) {
 		TemporaryKeyImpl reloadedKey = dbInstance.getCurrentEntityManager()
 				.getReference(TemporaryKeyImpl.class, key.getKey());
 		dbInstance.getCurrentEntityManager().remove(reloadedKey);
@@ -241,13 +241,11 @@ public class RegistrationManager extends BasicManager {
 	 * 
 	 * @return the found temporary key or null if none is found
 	 */
-	public TemporaryKeyImpl loadTemporaryKeyByEmail(String email) {
-		String q = "select r from r in class org.olat.registration.TemporaryKeyImpl where r.emailAddress =:email";
-		List<TemporaryKeyImpl> tks = dbInstance.getCurrentEntityManager()
-				.createQuery(q, TemporaryKeyImpl.class)
+	public TemporaryKey loadTemporaryKeyByEmail(String email) {
+		List<TemporaryKey> tks = dbInstance.getCurrentEntityManager()
+				.createNamedQuery("loadTemporaryKeyByEmailAddress", TemporaryKey.class)
 				.setParameter("email", email)
 				.getResultList();
-		
 		if (tks.size() == 1) {
 			return tks.get(0);
 		}
@@ -263,12 +261,10 @@ public class RegistrationManager extends BasicManager {
 	 * @return the found temporary key or null if none is found
 	 */
 	public List<TemporaryKey> loadTemporaryKeyByAction(String action) {
-		String q = "select r from r in class org.olat.registration.TemporaryKeyImpl where r.regAction = :action";
 		List<TemporaryKey> tks = dbInstance.getCurrentEntityManager()
-				.createQuery(q, TemporaryKey.class)
+				.createNamedQuery("loadTemporaryKeyByRegAction", TemporaryKey.class)
 				.setParameter("action", action)
 				.getResultList();
-		
 		if (tks.size() > 0) {
 			return tks;
 		} else {
@@ -283,10 +279,9 @@ public class RegistrationManager extends BasicManager {
 	 * 
 	 * @return the found TemporaryKey or null if none is found
 	 */
-	public TemporaryKeyImpl loadTemporaryKeyByRegistrationKey(String regkey) {
-		String q = "select r from r in class org.olat.registration.TemporaryKeyImpl where r.registrationKey = :regkey";
-		List<TemporaryKeyImpl> tks = dbInstance.getCurrentEntityManager()
-				.createQuery(q, TemporaryKeyImpl.class)
+	public TemporaryKey loadTemporaryKeyByRegistrationKey(String regkey) {
+		List<TemporaryKey> tks = dbInstance.getCurrentEntityManager()
+				.createNamedQuery("loadTemporaryKeyByRegKey", TemporaryKey.class)
 				.setParameter("regkey", regkey)
 				.getResultList();
 		
@@ -305,10 +300,15 @@ public class RegistrationManager extends BasicManager {
 	 * 
 	 * @return newly created temporary key
 	 */
-	public TemporaryKeyImpl register(String emailaddress, String ipaddress, String action) {
+	public TemporaryKey register(String emailaddress, String ipaddress, String action) {
 		String today = new Date().toString();
 		String encryptMe = Encoder.md5hash(emailaddress + ipaddress + today);
-		TemporaryKeyImpl tk = new TemporaryKeyImpl(emailaddress, ipaddress, encryptMe, action);
+		TemporaryKeyImpl tk = new TemporaryKeyImpl();
+		tk.setCreationDate(new Date());
+		tk.setEmailAddress(emailaddress);
+		tk.setIpAddress(ipaddress);
+		tk.setRegistrationKey(encryptMe);
+		tk.setRegAction(action);
 		dbInstance.getCurrentEntityManager().persist(tk);
 		return tk;
 	}
@@ -318,7 +318,7 @@ public class RegistrationManager extends BasicManager {
 	 * @param keyValue
 	 */
 	public void deleteTemporaryKeyWithId(String keyValue) {
-		TemporaryKeyImpl tKey = loadTemporaryKeyByRegistrationKey(keyValue);
+		TemporaryKey tKey = loadTemporaryKeyByRegistrationKey(keyValue);
 		if(tKey != null) {
 			deleteTemporaryKey(tKey);
 		}
@@ -372,17 +372,4 @@ public class RegistrationManager extends BasicManager {
 	public void revokeConfirmedDisclaimer(Identity identity) {
 		propertyManager.deleteProperties(identity, null, null, "user", "dislaimer_accepted");		
 	}
-	
-	/**
-	 * Get a list of all users that did already confirm the disclaimer
-	 * @return
-	 */
-	public List<Identity> getIdentitiesWithConfirmedDisclaimer() {
-		StringBuilder sb = new StringBuilder();
-		sb.append("select distinct ident from org.olat.core.id.Identity as ident, org.olat.properties.Property as prop ")
-		  .append(" where prop.identity=ident and prop.category='user' and prop.name='dislaimer_accepted'");
-		return dbInstance.getCurrentEntityManager()
-				.createQuery(sb.toString(), Identity.class)
-				.getResultList();
-	}
 }
\ No newline at end of file
diff --git a/src/main/java/org/olat/registration/SendMessageController.java b/src/main/java/org/olat/registration/SendMessageController.java
new file mode 100644
index 00000000000..5a01b96c79d
--- /dev/null
+++ b/src/main/java/org/olat/registration/SendMessageController.java
@@ -0,0 +1,89 @@
+/**
+ * <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.registration;
+
+import org.olat.core.commons.services.sms.SimpleMessageException;
+import org.olat.core.commons.services.sms.SimpleMessageService;
+import org.olat.core.commons.services.sms.ui.SMSPhoneController;
+import org.olat.core.gui.UserRequest;
+import org.olat.core.gui.components.form.flexible.FormItemContainer;
+import org.olat.core.gui.components.form.flexible.impl.FormBasicController;
+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.id.Identity;
+import org.olat.core.util.Util;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * 
+ * Initial date: 6 févr. 2017<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class SendMessageController extends FormBasicController {
+	
+	private final String sentToken;
+	private final Identity recipient;
+	
+	@Autowired
+	private SimpleMessageService messageService;
+	
+	public SendMessageController(UserRequest ureq, WindowControl wControl, Identity recipient) {
+		super(ureq, wControl, "send_message", Util.createPackageTranslator(SMSPhoneController.class, ureq.getLocale()));
+		this.recipient = recipient;
+		sentToken = messageService.generateToken();
+		initForm(ureq);
+	}
+	
+	public Identity getRecipient() {
+		return recipient;
+	}
+	
+	public String getSentToken() {
+		return sentToken;
+	}
+
+	@Override
+	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
+		uifactory.addFormSubmitButton("send.sms", "pw.change.sms", formLayout);
+	}
+
+	@Override
+	protected void doDispose() {
+		//
+	}
+
+	@Override
+	protected void formOK(UserRequest ureq) {
+		try {
+			String msg = translate("sms.token", new String[]{ sentToken });
+			messageService.sendMessage(msg, recipient);
+			fireEvent(ureq, Event.DONE_EVENT);
+		} catch (SimpleMessageException e) {
+			showWarning("warning.message.not.send");
+		}
+	}
+
+	@Override
+	protected void formCancelled(UserRequest ureq) {
+		fireEvent(ureq, Event.CANCELLED_EVENT);
+	}
+}
diff --git a/src/main/java/org/olat/registration/TemporaryKey.java b/src/main/java/org/olat/registration/TemporaryKey.java
index b3486534ad3..71e54bfda50 100644
--- a/src/main/java/org/olat/registration/TemporaryKey.java
+++ b/src/main/java/org/olat/registration/TemporaryKey.java
@@ -34,10 +34,11 @@ import java.util.Date;
  * @author Sabina Jeger
  */
 public interface TemporaryKey {
+	/**
+	 * @return Object key.
+	 */
+	public Long getKey();
 	
-	
-	public int getVersion();
-	public void setVersion(int version);
 	/**
 	 * @return email address
 	 */
@@ -46,6 +47,7 @@ public interface TemporaryKey {
 	 * @param string
 	 */
 	public void setEmailAddress(String string);
+	
 	/**
 	 * @return The ip address the registration request came from
 	 */
@@ -68,6 +70,7 @@ public interface TemporaryKey {
 	 * @param string
 	 */
 	public void setRegistrationKey(String string);
+	
 	/**
 	 * @return Wether email has been sent.
 	 */
@@ -76,18 +79,8 @@ public interface TemporaryKey {
 	 * @param b
 	 */
 	public void setMailSent(boolean b);
-	/**
-	 * @return Object key.
-	 */
-	public Long getKey();
-	/**
-	 * @param long1
-	 */
-	public void setKey(Long long1);
-	/**
-	 * @param date
-	 */
-	public void setCreationDate(Date date);
+
+
 	/**
 	 * @return Registration action.
 	 */
diff --git a/src/main/java/org/olat/registration/TemporaryKeyImpl.hbm.xml b/src/main/java/org/olat/registration/TemporaryKeyImpl.hbm.xml
deleted file mode 100644
index 7286a998983..00000000000
--- a/src/main/java/org/olat/registration/TemporaryKeyImpl.hbm.xml
+++ /dev/null
@@ -1,45 +0,0 @@
-<?xml version="1.0"?>
-<!DOCTYPE hibernate-mapping PUBLIC
-	"-//Hibernate/Hibernate Mapping DTD//EN"
-	"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
-
-<hibernate-mapping default-lazy="false">
-  <class name="org.olat.registration.TemporaryKeyImpl" table="o_temporarykey">
-    <id name="key" type="long" column="reglist_id" unsaved-value="null">
-		<generator class="enhanced-sequence">
-			<param name="sequence_name">hibernate_unique_key</param>
-			<param name="force_table_use">true</param>
-			<param name="optimizer">legacy-hilo</param>
-			<param name="value_column">next_hi</param>
-			<param name="increment_size">32767</param>
-			<param name="initial_value">32767</param>
-		</generator>
-    </id>
-
-	<version name="version" access="field" column="version" type="int"/>
-	<property  name="creationDate" column="creationdate" type="timestamp" />   
- 
-	<property name="emailAddress" >
-		<column name ="email" not-null="true" />
-	</property>		
-	
-	<property name="registrationKey" >
-		<column name ="regkey" not-null="true" />
-	</property>		
-	
-	<property name="ipAddress" >
-		<column name ="ip" not-null="true" />
-	</property>		
-	
-	<property name="mailSent" >
-		<column name ="mailsent" not-null="true" />
-	</property>		
-	
-	<property name="regAction" >
-		<column name ="action" not-null="true" />
-	</property>		
-	
-  </class>
-    
-</hibernate-mapping>
-
diff --git a/src/main/java/org/olat/registration/TemporaryKeyImpl.java b/src/main/java/org/olat/registration/TemporaryKeyImpl.java
index 10b32dd1828..20d933fa1d8 100644
--- a/src/main/java/org/olat/registration/TemporaryKeyImpl.java
+++ b/src/main/java/org/olat/registration/TemporaryKeyImpl.java
@@ -27,7 +27,21 @@ package org.olat.registration;
 
 import java.util.Date;
 
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+import javax.persistence.Version;
+
+import org.hibernate.annotations.GenericGenerator;
+import org.hibernate.annotations.Parameter;
 import org.olat.core.id.CreateInfo;
+import org.olat.core.id.Persistable;
 
 /**
  *  Description:
@@ -35,37 +49,65 @@ import org.olat.core.id.CreateInfo;
  * 
  * @author Sabina Jeger
  */
-public class TemporaryKeyImpl implements CreateInfo, TemporaryKey {
+@Entity(name="otemporarykey")
+@Table(name="o_temporarykey")
+@NamedQueries({
+	@NamedQuery(name="loadTemporaryKeyByRegAction", query="select r from otemporarykey r where r.regAction=:action"),
+	@NamedQuery(name="loadTemporaryKeyByRegKey", query="select r from otemporarykey r where r.registrationKey=:regkey"),
+	@NamedQuery(name="loadTemporaryKeyByEmailAddress", query="select r from otemporarykey r where r.emailAddress=:email")
+})
+public class TemporaryKeyImpl implements Persistable, CreateInfo, TemporaryKey {
+
+	private static final long serialVersionUID = 2617181963956081372L;
 	
-	private Long key = null;
-	private String emailAddress = null;
-	private String ipAddress = null;
-	private Date creationDate = new Date();
-	private Date lastModified = null;
-	private String registrationKey = null;
-	private String regAction = null;
-	private boolean mailSent = false;
+	@Id
+	@GeneratedValue(generator = "system-uuid")
+	@GenericGenerator(name = "system-uuid", strategy = "enhanced-sequence", parameters={
+		@Parameter(name="sequence_name", value="hibernate_unique_key"),
+		@Parameter(name="force_table_use", value="true"),
+		@Parameter(name="optimizer", value="legacy-hilo"),
+		@Parameter(name="value_column", value="next_hi"),
+		@Parameter(name="increment_size", value="32767"),
+		@Parameter(name="initial_value", value="32767")
+	})
+	@Column(name="reglist_id", nullable=false, unique=true, insertable=true, updatable=false)
+	private Long key;
+	@Version
 	private int version;
 
-	/**
-	 * 
-	 */
-	protected TemporaryKeyImpl() {
-		super();
+	@Temporal(TemporalType.TIMESTAMP)
+	@Column(name="creationdate", nullable=false, insertable=true, updatable=false)
+	private Date creationDate;
+
+	@Column(name="email", nullable=false, unique=true, insertable=true, updatable=true)
+	private String emailAddress;
+	@Column(name="ip", nullable=false, unique=true, insertable=true, updatable=true)
+	private String ipAddress;
+	@Column(name="regkey", nullable=false, unique=true, insertable=true, updatable=true)
+	private String registrationKey;
+	@Column(name="action", nullable=false, unique=true, insertable=true, updatable=true)
+	private String regAction;
+	@Column(name="mailsent", nullable=false, unique=true, insertable=true, updatable=true)
+	private boolean mailSent = false;
+	
+	public TemporaryKeyImpl() {
+		//
+	}
+
+	@Override
+	public Long getKey() {
+		return key;
+	}
+
+	public void setKey(Long key) {
+		this.key = key;
 	}
 
 	/**
-	 * Temporary key database object.
-	 * @param emailaddress
-	 * @param ipaddress
-	 * @param registrationKey
-	 * @param action
+	 * @see org.olat.registration.TemporaryKey#setCreationDate(java.util.Date)
 	 */
-	public TemporaryKeyImpl(String emailaddress, String ipaddress, String registrationKey, String action) {
-		this.emailAddress = emailaddress;
-		this.ipAddress = ipaddress;
-		this.registrationKey = registrationKey;
-		this.regAction = action;
+	public void setCreationDate(Date date) {
+		creationDate = date;
 	}
 
 	/**
@@ -103,13 +145,6 @@ public class TemporaryKeyImpl implements CreateInfo, TemporaryKey {
 		return creationDate;
 	}
 
-	/**
-	 * @see org.olat.registration.TemporaryKey#getLastModified()
-	 */
-	public Date getLastModified() {
-		return lastModified;
-	}
-
 	/**
 	 * @see org.olat.registration.TemporaryKey#getRegistrationKey()
 	 */
@@ -138,53 +173,36 @@ public class TemporaryKeyImpl implements CreateInfo, TemporaryKey {
 		mailSent = b;
 	}
 
-	/**
-	 * @see org.olat.registration.TemporaryKey#getKey()
-	 */
-	public Long getKey() {
-		return key;
-	}
-
-	/**
-	 * @see org.olat.registration.TemporaryKey#setKey(java.lang.Long)
-	 */
-	public void setKey(Long long1) {
-		key = long1;
-	}
-
-	/**
-	 * @see org.olat.registration.TemporaryKey#setCreationDate(java.util.Date)
-	 */
-	public void setCreationDate(Date date) {
-		creationDate = date;
-	}
-
-	/**
-	 * @see org.olat.registration.TemporaryKey#setLastModified(java.util.Date)
-	 */
-	public void setLastModified(Date date) {
-		lastModified = date;
-	}
 
-	/**
-	 * @see org.olat.registration.TemporaryKey#getRegAction()
-	 */
+	@Override
 	public String getRegAction() {
 		return regAction;
 	}
 
-	/**
-	 * @see org.olat.registration.TemporaryKey#setRegAction(java.lang.String)
-	 */
+	@Override
 	public void setRegAction(String string) {
 		regAction = string;
 	}
 
-	public int getVersion() {
-		return this.version;
+	@Override
+	public int hashCode() {
+		return getKey() == null ? 8742558 : getKey().hashCode();
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if(this == obj) {
+			return true;
+		}
+		if(obj instanceof TemporaryKeyImpl) {
+			TemporaryKeyImpl tmpKey = (TemporaryKeyImpl)obj;
+			return getKey() != null && getKey().equals(tmpKey.getKey());
+		}
+		return false;
 	}
 
-	public void setVersion(int version) {
-		this.version = version;
+	@Override
+	public boolean equalsByPersistableKey(Persistable persistable) {
+		return equals(persistable);
 	}
 }
diff --git a/src/main/java/org/olat/registration/_content/pwchange.html b/src/main/java/org/olat/registration/_content/pwchange.html
index 31c17b18540..c8ca1f0b25e 100644
--- a/src/main/java/org/olat/registration/_content/pwchange.html
+++ b/src/main/java/org/olat/registration/_content/pwchange.html
@@ -1,9 +1,9 @@
 $r.render("pwwizard")
 <p class="o_info">$text</p>
-#if ($pwdhelp)
+#if ($r.isNotNull($pwdhelp))
 	<p>$pwdhelp</p>
 #end
-<p>$r.render("pwarea")</p>
+$r.render("pwarea")
 #if ($r.available("pwchange.homelink"))
 	<div class="o_button_group o_button_group_left">
 		$r.render("pwchange.homelink")
diff --git a/src/main/java/org/olat/registration/_content/send_message.html b/src/main/java/org/olat/registration/_content/send_message.html
new file mode 100644
index 00000000000..87f6e24eb82
--- /dev/null
+++ b/src/main/java/org/olat/registration/_content/send_message.html
@@ -0,0 +1,5 @@
+<h3>$r.translate("pw.change.sms.title")</h3>
+<p>$r.translate("pw.change.sms.descr")</p>
+<div class="o_button_group">
+	$r.render("send.sms")
+</div>
\ No newline at end of file
diff --git a/src/main/java/org/olat/registration/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/registration/_i18n/LocalStrings_de.properties
index 493f513fa51..f0e1ae9713e 100644
--- a/src/main/java/org/olat/registration/_i18n/LocalStrings_de.properties
+++ b/src/main/java/org/olat/registration/_i18n/LocalStrings_de.properties
@@ -1,22 +1,22 @@
-#Mon Mar 02 09:54:04 CET 2009
+#Tue Feb 07 14:00:34 CET 2017
 admin.enableRegistration=Selbstregistration aktivieren
+admin.enableRegistration.on=ein
 admin.enableRegistrationLink=Selbstregistration \u00FCber externe Webseiten aktivieren 
-admin.registrationLinkExample=Beispielcode
 admin.enableRegistrationLogin=Selbstregistrationslink auf Loginseite aktivieren
-admin.enableRegistration.on=ein
+admin.enableStaticProp=Standardwert aktivieren
 admin.menu.title=Selbstregistration
 admin.menu.title.alt=$\:admin.menu.title
-admin.registration.title=Selbstregistrierung
-admin.registration.domains.title=Einschr\u00E4nkung auf Dom\u00E4ne
-admin.registration.domains.desc=Die Selbtregistration kann optional auf eine oder mehrere Dom\u00E4nen eingeschr\u00E4nkt werden. Um diese Funktion zu aktivieren f\u00FCgen Sie eine Liste von Dom\u00E4nen an z.B: openolat.com, *openolat.org, hotmail.com
 admin.registration.domains=Dom\u00E4nenliste
-admin.registration.domains.error=Diese Dom\u00E4nen sind nicht g\u00FCltig: {0}
-admin.registration.staticprop.title=Benutzerattribut zuweisen
-admin.registration.staticprop.desc=Nach der Selbstregistrierung kann einem Benutzerattribut optional ein Standardwert zugewiesen werden. Dies kann genutzt werden um selbstregistrierte Benutzer einfach zu erkennen und dadurch z.B. von LDAP-Benutzern zu unterscheiden.
-admin.enableStaticProp=Standardwert aktivieren
+admin.registration.domains.desc=Die Selbtregistration kann optional auf eine oder mehrere Dom\u00E4nen eingeschr\u00E4nkt werden. Um diese Funktion zu aktivieren f\u00FCgen Sie eine Liste von Dom\u00E4nen an z.B\: openolat.com, *openolat.org, hotmail.com
+admin.registration.domains.error=Diese Dom\u00E4nen sind nicht g\u00FCltig\: {0}
+admin.registration.domains.title=Einschr\u00E4nkung auf Dom\u00E4ne
 admin.registration.property=Benutzerattribut
 admin.registration.propertyValue=Standardwert
 admin.registration.propertyValue.error=Ung\u00FCltiger Wert
+admin.registration.staticprop.desc=Nach der Selbstregistrierung kann einem Benutzerattribut optional ein Standardwert zugewiesen werden. Dies kann genutzt werden um selbstregistrierte Benutzer einfach zu erkennen und dadurch z.B. von LDAP-Benutzern zu unterscheiden.
+admin.registration.staticprop.title=Benutzerattribut zuweisen
+admin.registration.title=Selbstregistrierung
+admin.registrationLinkExample=Beispielcode
 disclaimer.acknowledged=<b>Ich habe die Nutzungsbedingungen gelesen, verstanden und stimme ihnen zu.</b>
 disclaimer.additionalcheckbox=<b>Ich bin mit der Datenspeicherung einverstanden.</b>
 disclaimer.additionallinktext=Nutzungsbedingungen (PDF Dokument)
@@ -29,14 +29,15 @@ disclaimer.paragraph1=Der/die OpenOLAT-Benutzer/in nimmt zur Kenntnis, dass er/s
 disclaimer.paragraph2=Der Betreiber dieser Plattform beh&auml;lt sich das Recht vor, bei Missbrauch des OpenOLAT-Servers oder Verst&ouml;ssen gegen diese Nutzungsregelung, den Zugang zu OpenOLAT zu sperren und alle Daten des betreffenden Benutzers oder der betreffenden Benutzerin zu l&ouml;schen. Ferner beh&auml;lt sie sich das Recht vor, strafrechtliche oder \tzivilrechtliche Untersuchungen einzuleiten.
 disclaimer.terms.of.usage=<b>Nutzungsbedingungen</b>
 email.address=E-Mail-Adresse
-email.or.username=E-Mail-Adresse oder Benutzername
-email.or.username.maynotbeempty=Bitte geben Sie Ihre E-Mail-Addresse oder Ihren Benutzernamen an
-email.or.username.not.identified=Benutzer konnte nicht eindeutig identifiziert werden
 email.address.maynotbeempty=E-Mail-Adresse darf nicht leer sein
 email.address.notregular=E-Mail-Adresse muss g\u00FCltig sein
 email.address2=E-Mail-Adresse (bitte nochmals eingeben)
 email.notsent=E-Mail konnte nicht verschickt werden. Geben Sie bitte nochmals Ihre E-Mail-Adresse ein.
+email.or.username=E-Mail-Adresse oder Benutzername
+email.or.username.maynotbeempty=Bitte geben Sie Ihre E-Mail-Addresse oder Ihren Benutzernamen an
+email.or.username.not.identified=Benutzer konnte nicht eindeutig identifiziert werden
 email.sent=E-Mail wurde verschickt.
+error.pw.change.confirm.token=Der Code stimmt nicht mit dem per SMS geschickt.
 form.check1=Bitte geben Sie Ihren Vornamen an.\t
 form.check2=Bitte geben Sie Ihren Namen an.
 form.check3=$org.olat.user\:form.checkUsername
@@ -45,12 +46,12 @@ form.check5=Bitte tragen Sie zweimal das gleiche Passwort ein.
 form.check6=Dieser Benutzername ist bereits besetzt. Bitte w\u00E4hlen Sie einen anderen.
 form.general.error=Bitte korrigieren Sie nachstehende Felder\:
 form.legende.mandatory=Diese Felder m\u00FCssen ausgef\u00FCllt werden.
+form.login=Login
+form.mail.whitelist.error=Die Dom\u00E4ne Ihrer E-Mail Adresse ist nicht f\u00FCr die Selbstregistration freigeschaltet. Bitte verwenden Sie die E-Mail Adresse Ihrer Institution.  
 form.password.enter.new=Passwort neu setzen
 form.password.error.nomatch=Beide Passw\u00F6rter m\u00FCssen gleich sein
 form.password.new1=Neues Passwort
 form.password.new2=Passwort best\u00E4tigen
-form.mail.whitelist.error=Die Dom\u00E4ne Ihrer E-Mail Adresse ist nicht f\u00FCr die Selbstregistration freigeschaltet. Bitte verwenden Sie die E-Mail Adresse Ihrer Institution.  
-form.login=Login
 login.body=Sie sind bereits in OpenOLAT registriert unter dem Benutzernamen\: {0} \n\nFalls Sie diese E-Mail unaufgefordert erhalten haben, l\u00F6schen Sie ganz einfach diese E-Mail.\n\nIhr OpenOLAT-Team \n
 login.subject=Bereits vorhandene Registrierung in OpenOLAT
 loginhelp=Der Benutzername<ul><li>muss mindestens 4 Zeichen lang sein</li><li>darf nur Ziffern und / oder Kleinbuchstaben enthalten (keine Umlaute, keine Sonderzeichen ausser .-_)</li><li>kann sp\u00E4ter nicht mehr ge\u00E4ndert werden</li></ul>
@@ -58,8 +59,15 @@ menu.login.alt=Melden Sie sich f\u00FCr das Learning Management System OpenOLAT
 password.cantchange=Um Ihr Passwort zu \u00E4ndern, kontaktieren Sie bitte die entsprechende Stelle an Ihrer Hochschule.
 password.notchanged=OpenOLAT-Passwort wurde nicht neu gesetzt.
 password.successful=Ihr OpenOLAT-Passwort wurde neu gesetzt.
-pwchange.intro=Sie oder eine andere Person haben f\u00FCr den OpenOLAT-Benutzeraccount {0} ein neues Passwort angefordert.\n\n
+pw.change.confirm=SMS Code \u00FCberpr\u00FCfen
+pw.change.confirm.descr=Geben Sie den Authentifizierungscode ein, den Sie per SMS erhalten haben\:
+pw.change.confirm.token=SMS Authentifizierungscode
+pw.change.sms=SMS Authentifizierung starten
+pw.change.sms.descr=Sie haben eine Mobile Telefonnummer in OpenOLAT hinterlegt. Sie k\u00F6nnen sich daher \u00FCber SMS authentifizieren. W\u00E4hlen Sie den untenstehenden Button um die SMS-Authentifizierung zu starten\:
+pw.change.sms.title=SMS Authentifizierung
 pwchange.body=\u00DCber den Link {0}/dmz/pwchange/index.html?key\={1}&language\={2} \nk\u00F6nnen Sie Ihr OpenOLAT-Passwort neu setzen und sich anschlie\u00DFend unter {0}/dmz/1%3A1%3Aomain_loging%3A1%3A0%3Acid%3Alogin/?lp\=OLAT einloggen. \n\nIhr OpenOLAT-Team \n
+pwchange.homelink=Klicken Sie hier um sich anzumelden
+pwchange.intro=Sie oder eine andere Person haben f\u00FCr den OpenOLAT-Benutzeraccount {0} ein neues Passwort angefordert.\n\n
 pwchange.subject=Schl\u00FCssel f\u00FCr neues OpenOLAT-Passwort
 pwdchangekey.missing=Bitte Schl\u00FCssel angeben, um ein neues OpenOLAT-Passwort zu setzen.
 pwdhelp=Das Passwort<ul><li>muss mindestens 4 Zeichen haben</li><li>muss Buchstaben und mindestens eine Ziffer enthalten</li><li>darf keine Umlaute und andere speziellen Buchstaben enthalten z.B. \u00FC, \u00E4, \u00E9</li><li>darf keine Leerschl\u00E4ge oder Escape-Sequenzen enthalten</li><li>kann folgende Sonderzeichen enthalten\: \!  \# $ % &  ( ) * + , - . / \: ; <\=> ? @ [  ] ^ _ ` { | } ~</li></ul>
@@ -67,20 +75,20 @@ pwform.cancelled=Die Eingabe eines neuen OpenOLAT-Passwortes wurde abgebrochen.
 pwform.failed=Ein unerwarteter Fehler ist aufgetreten. Das Passwort bleibt unver\u00E4ndert.
 pwkey.missingentry=Es wurde kein Schl\u00FCssel gefunden, um Ihr OpenOLAT-Passwort neu zu setzen.
 reg.body=<p>Vielen Dank f\u00FCr Ihr Interesse an OpenOLAT.</p><p>\u00DCber den Link {3}<br>k\u00F6nnen Sie die Registrierung vervollst\u00E4ndigen.</p><p>Falls Sie sich doch nicht registrieren wollen, l\u00F6schen Sie diese E-Mail.</p><p>Ihr OpenOLAT-Team</p>
+reg.error.disabled.body=Die Selbstregistrierung wurde f\u00FCr dieses System deaktiviert. Bitte kontaktieren Sie den zust\u00E4ndigen Systemadministrator unter {0} um einen Zugang zu erhalten. 
+reg.error.no_username=Kein Benutzername konnte erstellt werden
+reg.error.title=Registrierung abgebrochen
+reg.error.user_in_use=Benutzername ist schon besetzt
 reg.notiEmail.body=Soeben hat sich ein neuer Benutzer in OpenOLAT registriert\: \n\nName\: \t{2}\nVorname\: \t\t{1}\nLogin\: \t\t{0}\nE-Mail\: \t{3}\nSprache\: \t\t{4}\nServer\: \t\t{5}
 reg.notiEmail.subject=Neuer OpenOLAT-Benutzer\: {1} {2} ({0})
 reg.subject=Registrierungsschl\u00FCssel f\u00FCr OpenOLAT
 reg.wherefrom=Diese Anfrage an den Server {0} wurde am {1} \nvon der IP-Adresse {2} abgeschickt.
-reg.error.disabled.body=Die Selbstregistrierung wurde f\u00fcr dieses System deaktiviert. Bitte kontaktieren Sie den zust\u00e4ndigen Systemadministrator unter {0} um einen Zugang zu erhalten. 
-reg.error.title=Registrierung abgebrochen
-reg.error.no_username=Kein Benutzername konnte erstellt werden
-reg.error.user_in_use=Benutzername ist schon besetzt
 registration.form.cancelled=Sie haben die Registrierung abgebrochen.
 regkey.missing=Der Registrierungsschl\u00FCssel fehlt. Fordern Sie bitte einen neuen an.
 regkey.missingentry=Dieser Registrierungsschl\u00FCssel existiert nicht. Bitte fordern Sie einen neuen an.
 remote.login.title=Loginformular in externe Webseite/CMS einbinden
-select.language.description=W\u00E4hlen Sie die Sprache f\u00FCr die OpenOLAT Registrierung und Ihr Benutzerkonto. Sie k\u00F6nnen die Sprache sp\u00E4ter in Ihrem Benutzerprofil jederzeit anpassen. Anschliessend werden Sie durch den Registrationprozess gef\u00FChrt.
 select.language=Sprache
+select.language.description=W\u00E4hlen Sie die Sprache f\u00FCr die OpenOLAT Registrierung und Ihr Benutzerkonto. Sie k\u00F6nnen die Sprache sp\u00E4ter in Ihrem Benutzerprofil jederzeit anpassen. Anschliessend werden Sie durch den Registrationprozess gef\u00FChrt.
 sr.error.disclaimer.checkbox=Sie m\u00FCssen durch Anklicken des K\u00E4stchens best\u00E4tigen, dass Sie die Nutzungsbedingungen gelesen und verstanden haben und diese akzeptieren.
 sr.error.disclaimer.checkboxes=Sie m\u00FCssen durch Anklicken <i>beider</i> K\u00E4stchen best\u00E4tigen, dass Sie die Nutzungsbedingungen gelesen und verstanden haben und diese akzeptieren.
 step1.pw.text=Hier k\u00F6nnen Sie ein neues OpenOLAT-Passwort eingeben. Geben Sie bitte diejenige E-Mail-Adresse an, die in OpenOLAT bekannt ist, oder den Benutzernamen.
@@ -93,9 +101,8 @@ step3.pw.text=Bitte beachten Sie bei der Eingabe des neuen OpenOLAT-Passwortes f
 step3.reg.text=Um die Registrierung abzuschliessen, f\u00FCllen Sie bitte das Formular mit Ihren Angaben aus.
 step4.pw.text=Sie haben Ihr OpenOLAT-Passwort neu gesetzt. 
 step4.reg.text=<b>Bitte akzeptieren Sie die Nutzungsbedingungen, bevor Sie fortfahren.</b>
-step5.reg.text=Sie k\u00F6nnen sich nun mit Ihrem Benutzernamen <b>{0}</b> und Passwort anmelden:
+step5.reg.text=Sie k\u00F6nnen sich nun mit Ihrem Benutzernamen <b>{0}</b> und Passwort anmelden\:
 step5.reg.yourdata=Zusammenfassung Ihrer Angaben\:
-pwchange.homelink=Klicken Sie hier um sich anzumelden
 submit.accept=Akzeptieren
 submit.cancel=Abbrechen
 submit.speichernUndpwchange=OpenOLAT-Passwort \u00E4ndern
@@ -116,4 +123,5 @@ user.password=OpenOLAT-Passwort
 user.password2=OpenOLAT-Passwort wiederholen
 user.pwlength=min. 4 Zeichen, kann Buchstaben und Ziffern enthalten
 user.regkey=Registrierungsschl\u00FCssel
+warning.message.not.send=SMS cannot be send.
 workflow.browsedback=Bitte benutzen Sie nicht die Browsernavigation.
diff --git a/src/main/java/org/olat/registration/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/registration/_i18n/LocalStrings_en.properties
index 818b42369fc..d521227803f 100644
--- a/src/main/java/org/olat/registration/_i18n/LocalStrings_en.properties
+++ b/src/main/java/org/olat/registration/_i18n/LocalStrings_en.properties
@@ -1,22 +1,22 @@
-#Wed Jan 26 18:46:42 CET 2011
+#Tue Feb 07 13:54:14 CET 2017
 admin.enableRegistration=Activate self-registration
+admin.enableRegistration.on=Enabled
 admin.enableRegistrationLink=Activate self-registration from external web sites
-admin.registrationLinkExample=Example code
 admin.enableRegistrationLogin=Activate self-registration link on login page
-admin.enableRegistration.on=Enabled
+admin.enableStaticProp=Activate user property mapping
 admin.menu.title=Self-registration
 admin.menu.title.alt=$\:admin.menu.title
-admin.registration.title=Self-registration
-admin.registration.domains.title=Domain limitation
-admin.registration.domains.desc=Optionally the self registration can be limited to one or multiple domains. To activate this feature add a list of domains into the form below, e.g. : openolat.com, *openolat.org, hotmail.com
 admin.registration.domains=List of domains
-admin.registration.domains.error=The following domains are not valid: {0}
-admin.registration.staticprop.title=User property mapping
-admin.registration.staticprop.desc=Optionally a user property can be set to a default value after self registration. This can be used to easily identify users who self-registred and separate them e.g. from LDAP users.
-admin.enableStaticProp=Activate user property mapping
+admin.registration.domains.desc=Optionally the self registration can be limited to one or multiple domains. To activate this feature add a list of domains into the form below, e.g. \: openolat.com, *openolat.org, hotmail.com
+admin.registration.domains.error=The following domains are not valid\: {0}
+admin.registration.domains.title=Domain limitation
 admin.registration.property=User property name
 admin.registration.propertyValue=User property value
 admin.registration.propertyValue.error=Invalid user property value
+admin.registration.staticprop.desc=Optionally a user property can be set to a default value after self registration. This can be used to easily identify users who self-registred and separate them e.g. from LDAP users.
+admin.registration.staticprop.title=User property mapping
+admin.registration.title=Self-registration
+admin.registrationLinkExample=Example code
 disclaimer.acknowledged=<b>I have read your terms of use, do understand and agree.</b>
 disclaimer.additionalcheckbox=<b>I agree on the terms of data storage</b>
 disclaimer.additionallinktext=Terms of use (PDF document)
@@ -37,6 +37,7 @@ email.or.username=E-mail address or user name
 email.or.username.maynotbeempty=Please indicate your e-mail address or user name
 email.or.username.not.identified=User could not be identified clearly.
 email.sent=E-mail sent.
+error.pw.change.confirm.token=The code doesn't match the one which was sent to you by SMS.
 form.check1=Please enter your first name.
 form.check2=Please enter your last name.
 form.check3=$org.olat.user\:form.checkUsername
@@ -45,12 +46,12 @@ form.check5=Please enter two identical passwords.
 form.check6=This user name already exists. Please select another.
 form.general.error=Please correct the following fields\:
 form.legende.mandatory=These fields are mandatory.
+form.login=Login
+form.mail.whitelist.error=Your e-mail address doesn't match our list. Please use your institutional e-mail address.
 form.password.enter.new=Set new password
 form.password.error.nomatch=Passwords do not match.
 form.password.new1=New password
 form.password.new2=Confirm password
-form.login=Login
-form.mail.whitelist.error=Your e-mail address doesn't match our list. Please use your institutional e-mail address.
 login.body=You are already registered in OpenOLAT. Your user name is\: {0}.\n\nIn case you got this e-mail unwanted, please delete it.\n\nYour OpenOLAT team.\n
 login.subject=You are already registered in OpenOLAT.
 loginhelp=A user name<ul><li>must contain at least 4 characters</li><li>may only contain digits and/or lower case letters (no special digits or umlauts except for . - _)</li><li>cannot be changed later on</li></ul>
@@ -58,6 +59,12 @@ menu.login.alt=Please log in to profit from the Learning Management System OpenO
 password.cantchange=To change your password, please contact the support team at your university.
 password.notchanged=Your OpenOLAT password could not be changed.
 password.successful=Your OpenOLAT password was changed successfully.
+pw.change.confirm=Check the SMS code
+pw.change.confirm.descr=Please enter the authentication code you receive by SMS.
+pw.change.confirm.token=SMS authentication code
+pw.change.sms=Start authentication by SMS
+pw.change.sms.descr=You have enter a mobile telecom number in OpenOLAT. You can authenticate you by SMS. Choose the button below to start the authentication by SMS\:
+pw.change.sms.title=Authentication by SMS
 pwchange.body=Please use the link {0}/dmz/pwchange/index.html?key\={1}&language\={2} \r\nto change your OpenOLAT password. \r\n\r\nYour OpenOLAT team
 pwchange.homelink=Please click here to log on
 pwchange.intro=You have (or somebody else has) asked for a new password regarding the OpenOLAT user account {0}.
@@ -68,7 +75,7 @@ pwform.cancelled=OpenOLAT password change was cancelled. Password remains unchan
 pwform.failed=An unexpected server error occurred. Your password was not altered.
 pwkey.missingentry=Key for OpenOLAT password change not found.
 reg.body=<p>Thank you for your interest in OpenOLAT.</p><p>Please use the link {3}<br>to complete your registration.</p><p>In case you do not want to register, just ignore this e-mail.</p><p>Your OpenOLAT team</p>
-reg.error.disabled.body=The self registration for this system has been disabled. Please contact the system administrator ({0}) in order to gain access.\u00a0
+reg.error.disabled.body=The self registration for this system has been disabled. Please contact the system administrator ({0}) in order to gain access.\u00A0
 reg.error.no_username=User name could not be created
 reg.error.title=Registration cancelled
 reg.error.user_in_use=This user name already exists
@@ -94,7 +101,7 @@ step3.pw.text=Please consider the following instructions for setting an OpenOLAT
 step3.reg.text=In order to finish your registration, please fill in the form.
 step4.pw.text=You have changed your OpenOLAT password successfully.
 step4.reg.text=<b>Please read the terms of use before proceeding.</b>
-step5.reg.text=You can now log in with your OpenOLAT user name <b>{0}</b> and password:
+step5.reg.text=You can now log in with your OpenOLAT user name <b>{0}</b> and password\:
 step5.reg.yourdata=Summary of your data\:
 submit.accept=Accept
 submit.cancel=Cancel
diff --git a/src/main/java/org/olat/user/ProfileFormController.java b/src/main/java/org/olat/user/ProfileFormController.java
index 0dae8bb84a9..1f8195ae24e 100644
--- a/src/main/java/org/olat/user/ProfileFormController.java
+++ b/src/main/java/org/olat/user/ProfileFormController.java
@@ -65,7 +65,6 @@ import org.olat.core.util.resource.OresHelper;
 import org.olat.core.util.xml.XStreamHelper;
 import org.olat.registration.RegistrationManager;
 import org.olat.registration.TemporaryKey;
-import org.olat.registration.TemporaryKeyImpl;
 import org.olat.user.propertyhandlers.UserPropertyHandler;
 import org.springframework.beans.factory.annotation.Autowired;
 
@@ -215,7 +214,7 @@ public class ProfileFormController extends FormBasicController {
 			// special case for email field
 			if (userPropertyHandler.getName().equals("email")) {
 				String key = user.getProperty("emchangeKey", null);
-				TemporaryKeyImpl tempKey = rm.loadTemporaryKeyByRegistrationKey(key);
+				TemporaryKey tempKey = rm.loadTemporaryKeyByRegistrationKey(key);
 				if (tempKey != null) {
 					XStream xml = XStreamHelper.createXStreamInstance();
 					@SuppressWarnings("unchecked")
@@ -488,7 +487,7 @@ public class ProfileFormController extends FormBasicController {
 						identityToModify.getUser().setProperty("email", currentEmail);
 					} else {
 						String key = identityToModify.getUser().getProperty("emchangeKey", null);
-						TemporaryKeyImpl tempKey = rm.loadTemporaryKeyByRegistrationKey(key);
+						TemporaryKey tempKey = rm.loadTemporaryKeyByRegistrationKey(key);
 						if (tempKey != null) {
 							rm.deleteTemporaryKey(tempKey);
 						}		
diff --git a/src/main/java/org/olat/user/UserImpl.java b/src/main/java/org/olat/user/UserImpl.java
index 2b3094c724a..97bd673daf3 100644
--- a/src/main/java/org/olat/user/UserImpl.java
+++ b/src/main/java/org/olat/user/UserImpl.java
@@ -130,6 +130,8 @@ public class UserImpl implements Persistable, User {
 	private String telPrivate;
 	@Column(name="u_telmobile", nullable=true, insertable=true, updatable=true)
 	private String telMobile;
+	@Column(name="u_smstelmobile", nullable=true, insertable=true, updatable=true)
+	private String smsTelMobile;
 	@Column(name="u_skype", nullable=true, insertable=true, updatable=true)
 	private String skype;
 	@Column(name="u_msn", nullable=true, insertable=true, updatable=true)
@@ -369,6 +371,7 @@ public class UserImpl implements Persistable, User {
 			case "gender": return gender;
 			case "telPrivate": return telPrivate;
 			case "telMobile": return telMobile;
+			case "smsTelMobile": return smsTelMobile;
 			case "telOffice": return telOffice;
 			case "skype": return skype;
 			case "msn": return msn;
@@ -444,6 +447,7 @@ public class UserImpl implements Persistable, User {
 			case "gender": gender = value; break;
 			case "telPrivate": telPrivate = value; break;
 			case "telMobile": telMobile = value; break;
+			case "smsTelMobile": smsTelMobile = value; break;
 			case "telOffice": telOffice = value; break;
 			case "skype": skype = value; break;
 			case "msn": msn = value; break;
diff --git a/src/main/java/org/olat/user/propertyhandlers/SmsPhonePropertyHandler.java b/src/main/java/org/olat/user/propertyhandlers/SmsPhonePropertyHandler.java
new file mode 100644
index 00000000000..87978483cbf
--- /dev/null
+++ b/src/main/java/org/olat/user/propertyhandlers/SmsPhonePropertyHandler.java
@@ -0,0 +1,64 @@
+/**
+ * <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.user.propertyhandlers;
+
+import java.util.Locale;
+import java.util.Map;
+
+import org.olat.core.gui.components.form.ValidationError;
+import org.olat.core.gui.components.form.flexible.FormItem;
+import org.olat.core.gui.components.form.flexible.FormItemContainer;
+import org.olat.core.id.User;
+import org.olat.user.propertyhandlers.ui.SmsPhoneElement;
+
+/**
+ * 
+ * Initial date: 3 févr. 2017<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class SmsPhonePropertyHandler extends PhonePropertyHandler {
+	
+	@Override
+	public FormItem addFormItem(Locale locale, User user, String usageIdentifyer, boolean isAdministrativeUser,
+			FormItemContainer formItemContainer) {
+		String name = getName();
+		SmsPhoneElement phoneEl = new SmsPhoneElement(name, this, user, locale);
+		phoneEl.setLabel("form.name." + name, null);
+		formItemContainer.add(phoneEl);
+		return phoneEl;
+	}
+	
+	@Override
+	public boolean isValid(User user, FormItem formItem, Map<String,String> formContext) {
+		return true;
+	}
+
+	@Override
+	public boolean isValidValue(User user, String value, ValidationError validationError, Locale locale) {
+		return true;
+	}
+	
+	@Override
+	public String getStringValue(FormItem formItem) {
+		return ((SmsPhoneElement) formItem).getPhone();
+	}
+
+}
diff --git a/src/main/java/org/olat/user/propertyhandlers/UserInterestsElement.java b/src/main/java/org/olat/user/propertyhandlers/UserInterestsElement.java
index 983d77c6d37..b01c67e60bf 100644
--- a/src/main/java/org/olat/user/propertyhandlers/UserInterestsElement.java
+++ b/src/main/java/org/olat/user/propertyhandlers/UserInterestsElement.java
@@ -44,7 +44,6 @@ import org.olat.core.gui.control.Event;
 import org.olat.core.gui.control.WindowControl;
 import org.olat.core.gui.control.generic.closablewrapper.CloseableModalController;
 import org.olat.core.util.Util;
-import org.olat.core.util.ValidationStatus;
 
 /**
  * <h3>Description:</h3>
@@ -200,11 +199,6 @@ public class UserInterestsElement extends FormItemImpl implements FormItemCollec
 		}
 	}
 
-	@Override
-	public void validate(List<ValidationStatus> validationResults) {
-		// Do nothing.
-	}
-
 	/**
 	 * Returns the IDs of the selected user interests.
 	 * 
diff --git a/src/main/java/org/olat/user/propertyhandlers/UserPropertyUsageContext.java b/src/main/java/org/olat/user/propertyhandlers/UserPropertyUsageContext.java
index 2103f648ebf..b7622b1d903 100644
--- a/src/main/java/org/olat/user/propertyhandlers/UserPropertyUsageContext.java
+++ b/src/main/java/org/olat/user/propertyhandlers/UserPropertyUsageContext.java
@@ -119,6 +119,16 @@ public class UserPropertyUsageContext {
 		if (propertyHandlers.contains(propertyHandler)) return; // do not add twice
 		propertyHandlers.add(propertyHandler);
 	}
+	
+	public void addPropertyHandler(int index, UserPropertyHandler propertyHandler) {
+		if (propertyHandlers.contains(propertyHandler)) return; // do not add twice
+		
+		if(index < 0 && index >= propertyHandlers.size()) {
+			propertyHandlers.add(propertyHandler);
+		} else {
+			propertyHandlers.add(index, propertyHandler);
+		}
+	}
 
 	/**
 	 * removes the given propertyHandler from this context
diff --git a/src/main/java/org/olat/user/propertyhandlers/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/user/propertyhandlers/_i18n/LocalStrings_de.properties
index adf41bb86f0..44a2c02ad50 100644
--- a/src/main/java/org/olat/user/propertyhandlers/_i18n/LocalStrings_de.properties
+++ b/src/main/java/org/olat/user/propertyhandlers/_i18n/LocalStrings_de.properties
@@ -1,4 +1,4 @@
-#Mon Mar 02 14:11:51 CET 2015
+#Tue Feb 07 13:58:39 CET 2017
 country.code.AD=Andorra
 country.code.AE=Vereinigte Arabische Emirate
 country.code.AG=Antigua und Barbuda
@@ -9,7 +9,7 @@ country.code.AO=Angola
 country.code.AQ=Antarctica
 country.code.AR=Argentinien
 country.code.AS=Samoa (amerikanischer Teil)
-country.code.AT=\u00d6sterreich
+country.code.AT=\u00D6sterreich
 country.code.AU=Australien
 country.code.AW=Aruba
 country.code.AZ=Aserbaidschan
@@ -22,7 +22,7 @@ country.code.BG=Bulgarien
 country.code.BH=Bahrain
 country.code.BI=Burundi
 country.code.BJ=Benin
-country.code.BL=St. Barth\u00e9lemy
+country.code.BL=St. Barth\u00E9lemy
 country.code.BM=Bermuda
 country.code.BN=Brunei
 country.code.BO=Bolivien
@@ -40,7 +40,7 @@ country.code.CD=Kongo, demokratische Republik
 country.code.CF=Zentralafrika
 country.code.CG=Kongo, Republik
 country.code.CH=Schweiz
-country.code.CI=Elfenbeink\u00fcste
+country.code.CI=Elfenbeink\u00FCste
 country.code.CK=Cook-Inseln
 country.code.CL=Chile
 country.code.CM=Kamerun
@@ -49,44 +49,44 @@ country.code.CO=Kolumbien
 country.code.CR=Costa Rica
 country.code.CU=Kuba
 country.code.CV=Kapverdische Inseln
-country.code.CW=Cura\u00e7ao
+country.code.CW=Cura\u00E7ao
 country.code.CX=Weihnachtsinsel (Indischer Ozean)
 country.code.CY=Zypern
 country.code.CZ=Tschechische Republik
 country.code.DE=Deutschland
 country.code.DJ=Djibouti
-country.code.DK=D\u00e4nemark
+country.code.DK=D\u00E4nemark
 country.code.DM=Dominica
 country.code.DO=Dominikanische Republik
 country.code.DZ=Algerien
 country.code.EC=Ekuador
 country.code.EE=Estland
-country.code.EG=\u00c4gypten
+country.code.EG=\u00C4gypten
 country.code.EH=Western Sahara
 country.code.ER=Eritrea
 country.code.ES=Spanien
-country.code.ET=\u00c4thiopien
+country.code.ET=\u00C4thiopien
 country.code.FI=Finnland
 country.code.FJ=Fidschi
 country.code.FK=Falkland
 country.code.FM=Micronesia, Federated States of
-country.code.FO=F\u00e4r\u00f6er
+country.code.FO=F\u00E4r\u00F6er
 country.code.FR=Frankreich
 country.code.GA=Gabun
 country.code.GB=Grossbritannien und Nordirland
 country.code.GD=Grenada
 country.code.GE=Georgien, Republik
-country.code.GF=Franz\u00f6sisch-Guayana
+country.code.GF=Franz\u00F6sisch-Guayana
 country.code.GG=Guernsey
 country.code.GH=Ghana
 country.code.GI=Gibraltar
-country.code.GL=Gr\u00f6nland
+country.code.GL=Gr\u00F6nland
 country.code.GM=Gambia
 country.code.GN=Guinea, Republik
 country.code.GP=Guadeloupe
-country.code.GQ=\u00c4quatorial-Guinea
+country.code.GQ=\u00C4quatorial-Guinea
 country.code.GR=Griechenland
-country.code.GS=S\u00fcdgeorgien und die s\u00fcdlichen Sandwichinseln
+country.code.GS=S\u00FCdgeorgien und die s\u00FCdlichen Sandwichinseln
 country.code.GT=Guatemala
 country.code.GU=Guam
 country.code.GW=Guinea-Bissau
@@ -118,7 +118,7 @@ country.code.KI=Kiribati
 country.code.KM=Komoren
 country.code.KN=St. Christoph (St. Kitts) und Nevis
 country.code.KP=Korea, Demo. Volksrepublik (Nordkorea)
-country.code.KR=Korea, Republik (S\u00fcdkorea)
+country.code.KR=Korea, Republik (S\u00FCdkorea)
 country.code.KW=Kuwait
 country.code.KY=Cayman
 country.code.KZ=Kasachstan
@@ -145,7 +145,7 @@ country.code.ML=Mali
 country.code.MM=Myanmar (Union)
 country.code.MN=Mongolei
 country.code.MO=Macao
-country.code.MP=Marianen, n\u00f6rdliche
+country.code.MP=Marianen, n\u00F6rdliche
 country.code.MQ=Martinique
 country.code.MR=Mauretanien
 country.code.MS=Montserrat
@@ -171,7 +171,7 @@ country.code.NZ=Neuseeland
 country.code.OM=Oman
 country.code.PA=Panama
 country.code.PE=Peru
-country.code.PF=Franz\u00f6sisch-Polynesien
+country.code.PF=Franz\u00F6sisch-Polynesien
 country.code.PG=Papua-Neuguinea
 country.code.PH=Philippinen
 country.code.PK=Pakistan
@@ -179,15 +179,15 @@ country.code.PL=Polen
 country.code.PM=St. Pierre und Miquelon
 country.code.PN=Pitcairn
 country.code.PR=Puerto Rico
-country.code.PS=Pal\u00e4stina
+country.code.PS=Pal\u00E4stina
 country.code.PT=Portugal
 country.code.PW=Palau
 country.code.PY=Paraguay
 country.code.QA=Qatar
-country.code.RE=R\u00e9union
-country.code.RO=Rum\u00e4nien
+country.code.RE=R\u00E9union
+country.code.RO=Rum\u00E4nien
 country.code.RS=Serbien, Republik
-country.code.RU=Russische F\u00f6deration
+country.code.RU=Russische F\u00F6deration
 country.code.RW=Rwanda
 country.code.SA=Saudi-Arabien
 country.code.SB=Salomon-Inseln
@@ -220,14 +220,14 @@ country.code.TL=Timor-Leste
 country.code.TM=Turkmenistan
 country.code.TN=Tunesien
 country.code.TO=Tonga
-country.code.TR=T\u00fcrkei
+country.code.TR=T\u00FCrkei
 country.code.TT=Trinidad und Tobago
 country.code.TV=Tuvalu
 country.code.TW=China, Taiwan
 country.code.TZ=Tansania
 country.code.UA=Ukraine
 country.code.UG=Uganda
-country.code.UM=Amerikanische \u00dcberseeinseln, kleinere
+country.code.UM=Amerikanische \u00DCberseeinseln, kleinere
 country.code.US=Vereinigte Staaten von Amerika
 country.code.UY=Uruguay
 country.code.UZ=Usbekistan
@@ -242,7 +242,7 @@ country.code.WF=Wallis und Futuna
 country.code.WS=Samoa, West
 country.code.YE=Jemen
 country.code.YT=Mayotte
-country.code.ZA=S\u00fcdafrika
+country.code.ZA=S\u00FCdafrika
 country.code.ZM=Sambia
 country.code.ZW=Zimbabwe
 form.example.free={0}
@@ -254,23 +254,23 @@ form.example.phone=(+41 12 345 67 89)
 form.example.skypename=(meinskypename)
 form.example.url=(http\://www.openolat.org)
 form.example.xingname=(XING Profilname)
+form.group.about=\u00DCber mich
 form.group.account=Personalien
 form.group.address=Adresse
 form.group.contact=Kontaktdaten
 form.group.institute=Institution
-form.group.person=Person
-form.group.about=\u00DCber mich
 form.group.officeaddress=Gesch\u00E4ftsadresse
+form.group.person=Person
 form.name.birthDay=Geburtsdatum
 form.name.birthDay.error=Bitte geben Sie ein g\u00FCltiges Datum an.
 form.name.birthDay.error.empty=Das Feld "Geburtsdatum" darf nicht leer sein.
-form.name.date.future.error=Das gew\u00E4hlte Datum muss in der Zukunft liegen
-form.name.date.past.error=Das gew\u00E4hlte Datum muss in der Vergangenheit liegen
 form.name.city=Stadt
 form.name.city.error.empty=Das Feld "Stadt" darf nicht leer sein.
 form.name.country=Land
-form.name.countryCode=$\:form.name.country
 form.name.country.error.empty=Das Feld "Land" darf nicht leer sein.
+form.name.countryCode=$\:form.name.country
+form.name.date.future.error=Das gew\u00E4hlte Datum muss in der Zukunft liegen
+form.name.date.past.error=Das gew\u00E4hlte Datum muss in der Vergangenheit liegen
 form.name.degree=Akademischer Grad
 form.name.department=Dienststelle / Firma
 form.name.email=E-Mail
@@ -326,6 +326,7 @@ form.name.region=Region / Kanton
 form.name.region.error.empty=Das Feld "Region / Kanton" darf nicht leer sein.
 form.name.skype=Skype ID
 form.name.skype.error.empty=Das Feld "Skype ID" darf nicht leer sein.
+form.name.smsTelMobile=SMS Telephon Mobil
 form.name.socialSecurityNumber=Sozialversicherungsnummer
 form.name.street=Strasse
 form.name.street.error.empty=Das Feld "Strasse" darf nicht leer sein.
@@ -352,12 +353,12 @@ gender.error=Ung\u00FCltiger Wert f\u00FCr Geschlecht
 general.error.max.127=Wert enth\u00E4lt mehr wie 127 Zeichen
 general.error.max.32=Wert enth\u00E4lt mehr wie 32 Zeichen
 general.error.unique=Dieser Wert wird bereits von Benutzer {0} verwendet. Bitte geben Sie einen eindeutigen Wert ein.
-gsph.selectionerror=Bitte treffen Sie eine Auswahl
 gsph.doselect=Ausw\u00E4hlen
-gsphc.issingle=Einzelauswahl
+gsph.selectionerror=Bitte treffen Sie eine Auswahl
+gsphc.addoption=+
 gsphc.ismulti=Mehrfachauswahl
+gsphc.issingle=Einzelauswahl
 gsphc.moderadio=Modus
-gsphc.addoption=+
 gsphc.remove=-
 gsphc.translate=\u00FCbersetzen
 import.example.birthDay=10.10.1970
@@ -374,8 +375,8 @@ import.example.institutionalEmail=peter.muster@uzh.ch
 import.example.institutionalName=Universit\u00E4t Z\u00FCrich
 import.example.institutionalUserIdentifier=08-123-987
 import.example.lastName=Muster
+import.example.msn=$\:import.example.msnname
 import.example.msnname=msnbenutzer@hotmail.com
-import.example.msn=$:import.example.msnname
 import.example.orgUnit=Rechtswissenschaftliche Fakult\u00E4t
 import.example.poBox=Postfach
 import.example.region=ZH
@@ -432,12 +433,12 @@ table.name.xing=Xing ID
 table.name.zipCode=Postleitzahl
 table.user.lastlogin=Letzter login
 text.element.error.notlongerthan=Wert enth\u00E4lt mehr wie {0} Zeichen
+userinterests.configinfo=Die Konfiguration der verf\u00FCgbaren Benutzer-Interessen wird in der Datei "olatdata/{0}" vorgenommen...
+userinterests.description=W\u00E4hlen Sie aus den untenstehenden Gebieten diese aus, auf die Sie spezialisiert sind (maximal 5).
 userinterests.editlink=Bearbeiten...
-userinterests.configinfo=Die Konfiguration der verf\u00fcgbaren Benutzer-Interessen wird in der Datei "olatdata/{0}" vorgenommen...
 userinterests.title=Expertise
-userinterests.description=W\u00e4hlen Sie aus den untenstehenden Gebieten diese aus, auf die Sie spezialisiert sind (maximal 5).
 username=Benutzername
-yph.from=Von
-yph.to=Bis
 yph.err=Bitte geben Sie einen g\u00FCltigen Wert ein
+yph.from=Von
 yph.infomsg=Bitte geben Sie 'von' und 'bis' Jahreszahlen (JJJJ) ein. <br />Sie k\u00F6nnen auch '+N' oder '-N' verwenden...
+yph.to=Bis
diff --git a/src/main/java/org/olat/user/propertyhandlers/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/user/propertyhandlers/_i18n/LocalStrings_en.properties
index 64f5f759836..5c9ff0b2785 100644
--- a/src/main/java/org/olat/user/propertyhandlers/_i18n/LocalStrings_en.properties
+++ b/src/main/java/org/olat/user/propertyhandlers/_i18n/LocalStrings_en.properties
@@ -1,4 +1,4 @@
-#Mon Mar 02 14:03:06 CET 2015
+#Tue Feb 07 13:47:41 CET 2017
 country.code.AD=Andorra
 country.code.AE=United Arab Emirates
 country.code.AG=Antigua and Barbuda
@@ -22,7 +22,7 @@ country.code.BG=Bulgaria
 country.code.BH=Bahrain
 country.code.BI=Burundi
 country.code.BJ=Benin
-country.code.BL=Saint Barth\u00e9lemy
+country.code.BL=Saint Barth\u00E9lemy
 country.code.BM=Bermuda
 country.code.BN=Brunei Darussalam
 country.code.BO=Bolivia, Plurinational State of
@@ -40,7 +40,7 @@ country.code.CD=Congo, the Democratic Republic of the
 country.code.CF=Central African Republic
 country.code.CG=Congo
 country.code.CH=Switzerland
-country.code.CI=C\u00f4te d'Ivoire
+country.code.CI=C\u00F4te d'Ivoire
 country.code.CK=Cook Islands
 country.code.CL=Chile
 country.code.CM=Cameroon
@@ -49,7 +49,7 @@ country.code.CO=Colombia
 country.code.CR=Costa Rica
 country.code.CU=Cuba
 country.code.CV=Cape Verde
-country.code.CW=Cura\u00e7ao
+country.code.CW=Cura\u00E7ao
 country.code.CX=Christmas Island
 country.code.CY=Cyprus
 country.code.CZ=Czech Republic
@@ -184,7 +184,7 @@ country.code.PT=Portugal
 country.code.PW=Palau
 country.code.PY=Paraguay
 country.code.QA=Qatar
-country.code.RE=R\u00e9union
+country.code.RE=R\u00E9union
 country.code.RO=Romania
 country.code.RS=Serbia
 country.code.RU=Russian Federation
@@ -254,21 +254,21 @@ form.example.phone=(+41 12 345 67 89)
 form.example.skypename=(myskypename)
 form.example.url=(http\://www.openolat.org)
 form.example.xingname=(XING profile name)
+form.group.about=About me
 form.group.account=Personal data
 form.group.address=Address
 form.group.contact=Contact data
 form.group.institute=Institution
-form.group.person=Person
-form.group.about=About me
 form.group.officeaddress=Business address
+form.group.person=Person
 form.name.birthDay=Date of birth
 form.name.birthDay.error=Please provide a valid date.
 form.name.birthDay.error.empty=The field "Date of birth" is mandatory.
 form.name.city=City
 form.name.city.error.empty=The field "City" is mandatory.
 form.name.country=Country
-form.name.countryCode=$\:form.name.country
 form.name.country.error.empty=The field "Country" is mandatory.
+form.name.countryCode=$\:form.name.country
 form.name.date.future.error=The chosen Date must lie in the future
 form.name.date.past.error=The chosen Date must lie in the past
 form.name.degree=Academic degree
@@ -326,6 +326,7 @@ form.name.region=Region/canton
 form.name.region.error.empty=The field "Region/canton" is mandatory.
 form.name.skype=Skype ID
 form.name.skype.error.empty=The field "Skype ID" is mandatory.
+form.name.smsTelMobile=SMS mobile phone
 form.name.socialSecurityNumber=Social security number
 form.name.street=Street
 form.name.street.error.empty=The field "Street" is mandatory.
diff --git a/src/main/java/org/olat/user/propertyhandlers/_spring/userPropertiesContext.xml b/src/main/java/org/olat/user/propertyhandlers/_spring/userPropertiesContext.xml
index cc777dfb0b3..f5766bda103 100644
--- a/src/main/java/org/olat/user/propertyhandlers/_spring/userPropertiesContext.xml
+++ b/src/main/java/org/olat/user/propertyhandlers/_spring/userPropertiesContext.xml
@@ -25,6 +25,7 @@
 				<ref bean="userPropertyGender" />
 				<ref bean="userPropertyTelPrivate" />
 				<ref bean="userPropertyTelMobile" />
+				<ref bean="userPropertySmsTelMobile" />
 				<ref bean="userPropertyTelOffice" />
 				<ref bean="userPropertySkype" />
 				<ref bean="userPropertyMSN" />
diff --git a/src/main/java/org/olat/user/propertyhandlers/_spring/userPropertriesHandlersContext.xml b/src/main/java/org/olat/user/propertyhandlers/_spring/userPropertriesHandlersContext.xml
index 5989cc828fc..2d4cf567d1f 100644
--- a/src/main/java/org/olat/user/propertyhandlers/_spring/userPropertriesHandlersContext.xml
+++ b/src/main/java/org/olat/user/propertyhandlers/_spring/userPropertriesHandlersContext.xml
@@ -66,6 +66,12 @@
 		<property name="deletable" value="true" />
 	</bean>
 	
+	<bean id="userPropertySmsTelMobile" class="org.olat.user.propertyhandlers.SmsPhonePropertyHandler">
+		<property name="name" value="smsTelMobile" />
+		<property name="group" value="contact" />
+		<property name="deletable" value="true" />
+	</bean>
+	
 	<bean id="userPropertyTelOffice" class="org.olat.user.propertyhandlers.PhonePropertyHandler">
 		<property name="name" value="telOffice" />
 		<property name="group" value="contact" />
diff --git a/src/main/java/org/olat/user/propertyhandlers/ui/SmsPhoneComponent.java b/src/main/java/org/olat/user/propertyhandlers/ui/SmsPhoneComponent.java
new file mode 100644
index 00000000000..74df297c218
--- /dev/null
+++ b/src/main/java/org/olat/user/propertyhandlers/ui/SmsPhoneComponent.java
@@ -0,0 +1,50 @@
+/**
+ * <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.user.propertyhandlers.ui;
+
+import org.olat.core.gui.components.ComponentRenderer;
+import org.olat.core.gui.components.form.flexible.impl.FormBaseComponentImpl;
+
+/**
+ * 
+ * Initial date: 7 févr. 2017<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class SmsPhoneComponent extends FormBaseComponentImpl {
+	
+	private static final ComponentRenderer RENDERER =  new SmsPhoneComponentRenderer();
+	
+	private final SmsPhoneElement element;
+	
+	public SmsPhoneComponent(SmsPhoneElement element) {
+		super(element.getName());
+		this.element = element;
+	}
+	
+	public SmsPhoneElement getSmsPhoneElement() {
+		return element;
+	}
+
+	@Override
+	public ComponentRenderer getHTMLRendererSingleton() {
+		return RENDERER;
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/org/olat/user/propertyhandlers/ui/SmsPhoneComponentRenderer.java b/src/main/java/org/olat/user/propertyhandlers/ui/SmsPhoneComponentRenderer.java
new file mode 100644
index 00000000000..d0e4f9cf19f
--- /dev/null
+++ b/src/main/java/org/olat/user/propertyhandlers/ui/SmsPhoneComponentRenderer.java
@@ -0,0 +1,64 @@
+/**
+ * <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.user.propertyhandlers.ui;
+
+import org.olat.core.gui.components.Component;
+import org.olat.core.gui.components.DefaultComponentRenderer;
+import org.olat.core.gui.components.form.flexible.elements.FormLink;
+import org.olat.core.gui.render.RenderResult;
+import org.olat.core.gui.render.Renderer;
+import org.olat.core.gui.render.StringOutput;
+import org.olat.core.gui.render.URLBuilder;
+import org.olat.core.gui.translator.Translator;
+import org.olat.core.util.StringHelper;
+
+/**
+ * 
+ * Initial date: 7 févr. 2017<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class SmsPhoneComponentRenderer extends DefaultComponentRenderer {
+
+	@Override
+	public void render(Renderer renderer, StringOutput sb, Component source, URLBuilder ubu, Translator translator,
+			RenderResult renderResult, String[] args) {
+		SmsPhoneComponent smsCmp = (SmsPhoneComponent)source;
+		SmsPhoneElement smsFte = smsCmp.getSmsPhoneElement();
+		String id = smsCmp.getFormDispatchId();
+		String phoneNumber = smsFte.getPhone();
+		if(!StringHelper.containsNonWhitespace(phoneNumber)) {
+			phoneNumber = smsFte.getTranslator().translate("sms.phone.not.available");
+		}
+		
+		sb.append("<p id=\"").append(id).append("\" ")
+		  .append(" class='form-control-static ");
+		if(StringHelper.containsNonWhitespace(smsCmp.getElementCssClass())) {
+			sb.append(smsCmp.getElementCssClass());
+		}
+		sb.append("'>").append(phoneNumber).append("</p>");
+		
+		FormLink editLink = smsFte.getEditLink();
+		if(editLink != null && editLink.isVisible()) {
+			Component cmp = editLink.getComponent();
+			cmp.getHTMLRendererSingleton().render(renderer, sb, cmp, ubu, translator, renderResult, args);
+		}
+	}
+}
diff --git a/src/main/java/org/olat/user/propertyhandlers/ui/SmsPhoneConfirmController.java b/src/main/java/org/olat/user/propertyhandlers/ui/SmsPhoneConfirmController.java
new file mode 100644
index 00000000000..3f13595b4e9
--- /dev/null
+++ b/src/main/java/org/olat/user/propertyhandlers/ui/SmsPhoneConfirmController.java
@@ -0,0 +1,85 @@
+/**
+ * <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.user.propertyhandlers.ui;
+
+import org.olat.core.gui.UserRequest;
+import org.olat.core.gui.components.form.flexible.FormItemContainer;
+import org.olat.core.gui.components.form.flexible.elements.TextElement;
+import org.olat.core.gui.components.form.flexible.impl.FormBasicController;
+import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer;
+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.util.StringHelper;
+
+/**
+ * 
+ * Initial date: 6 févr. 2017<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class SmsPhoneConfirmController extends FormBasicController {
+
+	private TextElement tokenEl;
+	
+	private final String token;
+	
+	public SmsPhoneConfirmController(UserRequest ureq, WindowControl wControl,  String token) {
+		super(ureq, wControl);
+		this.token = token;
+		initForm(ureq);
+	}
+
+	@Override
+	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
+		setFormDescription("sms.change.confirm.descr");
+		tokenEl = uifactory.addTextElement("token", "sms.change.confirm.token", 32, "", formLayout);
+		
+		FormLayoutContainer buttonsCont = FormLayoutContainer.createButtonLayout("buttons", getTranslator());
+		formLayout.add(buttonsCont);
+		uifactory.addFormSubmitButton("sms.change.confirm", buttonsCont);
+	}
+
+	@Override
+	protected void doDispose() {
+		//
+	}
+
+	@Override
+	protected boolean validateFormLogic(UserRequest ureq) {
+		boolean allOk = true;
+		
+		if(StringHelper.containsNonWhitespace(tokenEl.getValue())) {
+			if(!tokenEl.getValue().equals(token)) {
+				tokenEl.setErrorKey("error.sms.change.confirm.token", null);
+				allOk &= false;
+			}
+		} else {
+			tokenEl.setErrorKey("form.legende.mandatory", null);
+			allOk &= false;
+		}
+		return allOk & super.validateFormLogic(ureq);
+	}
+
+	@Override
+	protected void formOK(UserRequest ureq) {
+		fireEvent(ureq, Event.DONE_EVENT);
+	}
+}
diff --git a/src/main/java/org/olat/user/propertyhandlers/ui/SmsPhoneController.java b/src/main/java/org/olat/user/propertyhandlers/ui/SmsPhoneController.java
new file mode 100644
index 00000000000..191c889d10e
--- /dev/null
+++ b/src/main/java/org/olat/user/propertyhandlers/ui/SmsPhoneController.java
@@ -0,0 +1,90 @@
+/**
+ * <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.user.propertyhandlers.ui;
+
+import org.olat.core.gui.UserRequest;
+import org.olat.core.gui.components.Component;
+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.id.User;
+import org.olat.user.propertyhandlers.UserPropertyHandler;
+
+/**
+ * 
+ * Initial date: 7 févr. 2017<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class SmsPhoneController extends BasicController {
+	
+	private final VelocityContainer mainVC;
+
+	private SmsPhoneConfirmController confirmCtrl;
+	private final SmsPhoneSendController sendTokenCtrl;
+	
+	public SmsPhoneController(UserRequest ureq, WindowControl wControl, UserPropertyHandler handler, User userToChange) {
+		super(ureq, wControl);
+
+		sendTokenCtrl = new SmsPhoneSendController(ureq, getWindowControl(), handler, userToChange);
+		listenTo(sendTokenCtrl);
+		
+		mainVC = createVelocityContainer("edit_sms");
+		mainVC.put("cmp", sendTokenCtrl.getInitialComponent());
+		putInitialPanel(mainVC);
+	}
+	
+	public String getPhone() {
+		return sendTokenCtrl.getPhone();
+	}
+
+	@Override
+	protected void doDispose() {
+		//
+	}
+	
+	@Override
+	protected void event(UserRequest ureq, Controller source, Event event) {
+		if(source == sendTokenCtrl) {
+			if(event == Event.DONE_EVENT) {
+				doConfirm(ureq);
+			} else {
+				fireEvent(ureq, Event.CANCELLED_EVENT);
+			}
+		} else if(source == confirmCtrl) {
+			fireEvent(ureq, event);
+		}
+	}
+
+	@Override
+	protected void event(UserRequest ureq, Component source, Event event) {
+		//
+	}
+	
+	private void doConfirm(UserRequest ureq) {
+		if(confirmCtrl == null) {
+			confirmCtrl = new SmsPhoneConfirmController(ureq, getWindowControl(), sendTokenCtrl.getSentToken());
+			listenTo(confirmCtrl);
+		}
+		mainVC.put("cmp", confirmCtrl.getInitialComponent());
+	}
+}
diff --git a/src/main/java/org/olat/user/propertyhandlers/ui/SmsPhoneElement.java b/src/main/java/org/olat/user/propertyhandlers/ui/SmsPhoneElement.java
new file mode 100644
index 00000000000..f9e1f0c30e4
--- /dev/null
+++ b/src/main/java/org/olat/user/propertyhandlers/ui/SmsPhoneElement.java
@@ -0,0 +1,167 @@
+/**
+ * <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.user.propertyhandlers.ui;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+import org.olat.core.CoreSpringFactory;
+import org.olat.core.gui.UserRequest;
+import org.olat.core.gui.Windows;
+import org.olat.core.gui.components.Component;
+import org.olat.core.gui.components.form.flexible.FormItem;
+import org.olat.core.gui.components.form.flexible.FormItemCollection;
+import org.olat.core.gui.components.form.flexible.elements.FormLink;
+import org.olat.core.gui.components.form.flexible.impl.Form;
+import org.olat.core.gui.components.form.flexible.impl.FormItemImpl;
+import org.olat.core.gui.components.form.flexible.impl.elements.FormLinkImpl;
+import org.olat.core.gui.components.link.Link;
+import org.olat.core.gui.control.ChiefController;
+import org.olat.core.gui.control.Controller;
+import org.olat.core.gui.control.ControllerEventListener;
+import org.olat.core.gui.control.Event;
+import org.olat.core.gui.control.WindowControl;
+import org.olat.core.gui.control.generic.closablewrapper.CloseableModalController;
+import org.olat.core.id.User;
+import org.olat.core.util.Util;
+import org.olat.user.UserManager;
+import org.olat.user.propertyhandlers.UserPropertyHandler;
+
+/**
+ * 
+ * Initial date: 7 févr. 2017<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class SmsPhoneElement extends FormItemImpl implements FormItemCollection, ControllerEventListener {
+	
+	private final SmsPhoneComponent component;
+	
+	private String phone;
+	private final User editedUser;
+	private final UserPropertyHandler handler;
+	
+	private FormLink editLink;
+	private SmsPhoneController smsPhoneCtrl;
+	private CloseableModalController cmc;
+	
+	public SmsPhoneElement(String name, UserPropertyHandler handler, User editedUser, Locale locale) {
+		super(name);
+		setTranslator(Util.createPackageTranslator(SmsPhoneElement.class, locale));
+		this.handler = handler;
+		this.editedUser = editedUser;
+		setPhone(editedUser.getProperty(handler.getName(), locale));
+		component = new SmsPhoneComponent(this);
+	}
+
+	public String getPhone() {
+		return phone;
+	}
+
+	public void setPhone(String phone) {
+		this.phone = phone;
+	}
+
+	public FormLink getEditLink() {
+		return editLink;
+	}
+
+	@Override
+	public void dispatchEvent(UserRequest ureq, Controller source, Event event) {
+		if(cmc == source) {
+			smsPhoneCtrl = null;
+			cmc = null;
+		} else if(smsPhoneCtrl == source) {
+			if(event == Event.DONE_EVENT || event == Event.CHANGED_EVENT) {
+				setPhone(smsPhoneCtrl.getPhone());
+				getComponent().setDirty(true);
+			}
+			cmc.deactivate();
+			smsPhoneCtrl = null;
+			cmc = null;
+		}
+	}
+
+	@Override
+	public Iterable<FormItem> getFormItems() {
+		List<FormItem> items = new ArrayList<>(1);
+		if(editLink != null) {
+			items.add(editLink);
+		}
+		return items;
+	}
+
+	@Override
+	public FormItem getFormComponent(String name) {
+		return null;
+	}
+	
+	@Override
+	public void setRootForm(Form rootForm) {
+		String dispatchId = component.getDispatchID();
+		editLink = new FormLinkImpl(dispatchId + "_editSmsButton", "editSms", "edit", Link.BUTTON);
+		editLink.setTranslator(getTranslator());
+		editLink.setIconLeftCSS("o_icon o_icon_edit");
+		super.setRootForm(rootForm);
+	}
+
+	@Override
+	protected Component getFormItemComponent() {
+		return component;
+	}
+
+	@Override
+	protected void rootFormAvailable() {
+		if(editLink != null && editLink.getRootForm() != getRootForm()) {
+			editLink.setRootForm(getRootForm());
+		}
+	}
+
+	@Override
+	public void evalFormRequest(UserRequest ureq) {
+		Form form = getRootForm();
+		String dispatchuri = form.getRequestParameter("dispatchuri");
+		if(editLink != null && editLink.getFormDispatchId().equals(dispatchuri)) {
+			doEdit(ureq);
+		}
+	}
+
+	@Override
+	public void reset() {
+		//
+	}
+	
+	private void doEdit(UserRequest ureq) {
+		ChiefController chief = Windows.getWindows(ureq).getChiefController();
+		WindowControl wControl = chief.getWindowControl();
+		if (wControl != null) {
+			smsPhoneCtrl = new SmsPhoneController(ureq, wControl, handler, editedUser);
+			smsPhoneCtrl.addControllerListener(this);
+			
+			String propLabel = CoreSpringFactory.getImpl(UserManager.class)
+					.getPropertyHandlerTranslator(getTranslator()).translate(handler.i18nFormElementLabelKey());
+			String title = getTranslator().translate("sms.title", new String[] { propLabel });
+			cmc = new CloseableModalController(wControl, "close", smsPhoneCtrl.getInitialComponent(), true, title);
+			cmc.suppressDirtyFormWarningOnClose();
+			cmc.activate();
+		}
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/org/olat/user/propertyhandlers/ui/SmsPhoneSendController.java b/src/main/java/org/olat/user/propertyhandlers/ui/SmsPhoneSendController.java
new file mode 100644
index 00000000000..bee123d145b
--- /dev/null
+++ b/src/main/java/org/olat/user/propertyhandlers/ui/SmsPhoneSendController.java
@@ -0,0 +1,131 @@
+/**
+ * <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.user.propertyhandlers.ui;
+
+import org.olat.basesecurity.BaseSecurity;
+import org.olat.core.commons.services.sms.SimpleMessageException;
+import org.olat.core.commons.services.sms.SimpleMessageService;
+import org.olat.core.gui.UserRequest;
+import org.olat.core.gui.components.form.flexible.FormItemContainer;
+import org.olat.core.gui.components.form.flexible.elements.TextElement;
+import org.olat.core.gui.components.form.flexible.impl.FormBasicController;
+import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer;
+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.id.Identity;
+import org.olat.core.id.User;
+import org.olat.core.util.StringHelper;
+import org.olat.core.util.Util;
+import org.olat.user.UserManager;
+import org.olat.user.propertyhandlers.UserPropertyHandler;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * 
+ * Initial date: 6 févr. 2017<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class SmsPhoneSendController extends FormBasicController {
+	
+	private TextElement newPhoneEl;
+	
+	private final String sentToken;
+	private final User userToChange;
+	private final UserPropertyHandler handler;
+	
+	@Autowired
+	private UserManager userManager;
+	@Autowired
+	private BaseSecurity securityManager;
+	@Autowired
+	private SimpleMessageService messageService;
+	
+	public SmsPhoneSendController(UserRequest ureq, WindowControl wControl, UserPropertyHandler handler, User userToChange) {
+		super(ureq, wControl);
+		setTranslator(userManager.getPropertyHandlerTranslator(Util.createPackageTranslator(SmsPhoneSendController.class, getLocale())));
+		this.userToChange = userToChange;
+		this.handler = handler;
+		sentToken = messageService.generateToken();
+		initForm(ureq);
+	}
+	
+	public String getSentToken() {
+		return sentToken;
+	}
+	
+	public String getPhone() {
+		return newPhoneEl.getValue();
+	}
+
+	@Override
+	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
+		setFormDescription("sms.change.number.descr");
+		
+		String i18nLabel = handler.i18nFormElementLabelKey();
+		newPhoneEl = uifactory.addTextElement("sms.new.phone", i18nLabel, 32, "", formLayout);
+		newPhoneEl.setPlaceholderKey("sms.change.number.hint", null);
+
+		FormLayoutContainer buttonsCont = FormLayoutContainer.createButtonLayout("buttons", getTranslator());
+		formLayout.add(buttonsCont);
+		uifactory.addFormSubmitButton("sms.send", "sms.send", buttonsCont);
+	}
+
+	@Override
+	protected void doDispose() {
+		//
+	}
+
+	@Override
+	protected boolean validateFormLogic(UserRequest ureq) {
+		boolean allOk = true;
+
+		newPhoneEl.clearError();
+		if(!StringHelper.containsNonWhitespace(newPhoneEl.getValue())) {
+			newPhoneEl.setErrorKey("form.legende.mandatory", null);
+			allOk &= false;
+		} else if(!messageService.validate(newPhoneEl.getValue())) {
+			newPhoneEl.setErrorKey("error.phone.invalid", null);
+			allOk &= false;
+		}
+		
+		return allOk & super.validateFormLogic(ureq);
+	}
+
+	@Override
+	protected void formOK(UserRequest ureq) {
+		try {
+			String msg = translate("sms.token", new String[]{ sentToken });
+			String phone = newPhoneEl.getValue();
+			
+			Identity recipient = securityManager.findIdentityByUser(userToChange);
+			messageService.sendMessage(msg, phone, recipient);
+			fireEvent(ureq, Event.DONE_EVENT);
+		} catch (SimpleMessageException e) {
+			showWarning("warning.message.not.send");
+		}
+	}
+
+	@Override
+	protected void formCancelled(UserRequest ureq) {
+		fireEvent(ureq, Event.CANCELLED_EVENT);
+	}
+}
diff --git a/src/main/java/org/olat/user/propertyhandlers/ui/_content/edit_sms.html b/src/main/java/org/olat/user/propertyhandlers/ui/_content/edit_sms.html
new file mode 100644
index 00000000000..e9c001c718b
--- /dev/null
+++ b/src/main/java/org/olat/user/propertyhandlers/ui/_content/edit_sms.html
@@ -0,0 +1 @@
+$r.render("cmp")
\ No newline at end of file
diff --git a/src/main/java/org/olat/user/propertyhandlers/ui/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/user/propertyhandlers/ui/_i18n/LocalStrings_de.properties
index a50a5a24f9b..29a651f4daa 100644
--- a/src/main/java/org/olat/user/propertyhandlers/ui/_i18n/LocalStrings_de.properties
+++ b/src/main/java/org/olat/user/propertyhandlers/ui/_i18n/LocalStrings_de.properties
@@ -31,4 +31,16 @@ upc.reset.config.minimal=Minimal
 upc.reset.config.school=School
 upc.reset.config.business=Business
 upc.reset.config.baks=BAKS
-upc.defaulttranslation=Standard\u00FCbersetzung
\ No newline at end of file
+upc.defaulttranslation=Standard\u00FCbersetzung
+sms.title={0} ändern
+sms.send=SMS Authentifizierung starten
+sms.change.number.title=Nummer ändern
+sms.change.number.hint=+41 12 345 67 89
+sms.change.number.descr=Die Nummer muss bestätigt werden. Wählen Sie den untenstehenden Button um die SMS Bestätigungscode zu schicken:
+sms.phone.not.available=Kein Nummer vorhanden
+sms.change.confirm.descr=Geben Sie den Bestätigungscode ein, den Sie per SMS erhalten haben:
+sms.change.confirm.token=SMS Bestätigungscode
+sms.change.confirm=SMS Code überprüfen
+sms.token=Ihr Bestätigungscode ist {0}
+error.sms.change.confirm.token=Invalid
+error.phone.invalid=Nummer ist nicht gültig
\ No newline at end of file
diff --git a/src/main/java/org/olat/user/propertyhandlers/ui/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/user/propertyhandlers/ui/_i18n/LocalStrings_en.properties
index 6951fdbed23..4e834e1356c 100644
--- a/src/main/java/org/olat/user/propertyhandlers/ui/_i18n/LocalStrings_en.properties
+++ b/src/main/java/org/olat/user/propertyhandlers/ui/_i18n/LocalStrings_en.properties
@@ -1,4 +1,16 @@
-#Fri Feb 24 11:41:56 CET 2012
+#Tue Feb 07 13:50:53 CET 2017
+error.phone.invalid=The number is not valid.
+error.sms.change.confirm.token=The code is not valid.
+sms.change.confirm=Enter the SMS code
+sms.change.confirm.descr=Enter the 6 digit number you received per SMS\:
+sms.change.confirm.token=SMS confirmation code
+sms.change.number.descr=The number need a confirmation. Please, choose the button below to send a confirmation code by SMS\:
+sms.change.number.hint=+41 12 345 67 89
+sms.change.number.title=Change the number
+sms.phone.not.available=Number not available
+sms.send=Start authentication by SMS
+sms.title=Edit {}
+sms.token=Your confirmation code is {0}
 upc.active=Activ
 upc.adminonly=Admin only
 upc.context.edit=edit Context
@@ -24,12 +36,13 @@ upc.properties=Properties
 upc.reset=Reset
 upc.reset.config.baks=BAKS
 upc.reset.config.business=Business
+upc.reset.config.done=Predefined config loaded...
 upc.reset.config.minimal=Minimal
 upc.reset.config.olatdefault=OLAT Default
 upc.reset.config.school=School
 upc.reset.configs=Load Preset\:
 upc.reset.configs.note=Note\: While loading a preset, unknown UserProperties stay activated...
-upc.reset.config.done=Predefined config loaded...
 upc.translate=Translate
 upc.up=Up
 upc.userreadonly=User readonly
+warning.message.not.send=The SMS cannot be send.
diff --git a/src/main/resources/META-INF/persistence.xml b/src/main/resources/META-INF/persistence.xml
index f6056eab24c..95f2aa950f6 100644
--- a/src/main/resources/META-INF/persistence.xml
+++ b/src/main/resources/META-INF/persistence.xml
@@ -33,7 +33,6 @@
 		<mapping-file>org/olat/course/assessment/model/UserEfficiencyStatementImpl.hbm.xml</mapping-file>
 		<mapping-file>org/olat/modules/openmeetings/model/OpenMeetingsReference.hbm.xml</mapping-file>
 		<mapping-file>org/olat/properties/Property.hbm.xml</mapping-file>
-		<mapping-file>org/olat/registration/TemporaryKeyImpl.hbm.xml</mapping-file>
 		<mapping-file>org/olat/course/statistic/daily/DailyStat.hbm.xml</mapping-file>
 		<mapping-file>org/olat/course/statistic/dayofweek/DayOfWeekStat.hbm.xml</mapping-file>
 		<mapping-file>org/olat/course/statistic/hourofday/HourOfDayStat.hbm.xml</mapping-file>
@@ -92,6 +91,7 @@
 		<class>org.olat.core.commons.services.taskexecutor.model.PersistentTaskModifier</class>
 		<class>org.olat.core.commons.services.commentAndRating.model.UserRatingImpl</class>
 		<class>org.olat.core.commons.services.commentAndRating.model.UserCommentImpl</class>
+		<class>org.olat.core.commons.services.sms.model.MessageLogImpl</class>
 		<class>org.olat.course.assessment.model.AssessmentModeImpl</class>
 		<class>org.olat.course.assessment.model.AssessmentModeToAreaImpl</class>
 		<class>org.olat.course.assessment.model.AssessmentModeToGroupImpl</class>
@@ -115,6 +115,7 @@
 		<class>org.olat.group.model.GroupToBusinessGroup</class>
 		<class>org.olat.group.model.BusinessGroupToSearch</class>
 		<class>org.olat.group.BusinessGroupImpl</class>
+		<class>org.olat.registration.TemporaryKeyImpl</class>
 		<class>org.olat.repository.RepositoryEntry</class>
 		<class>org.olat.repository.model.RepositoryEntryToGroupRelation</class>
 		<class>org.olat.repository.model.RepositoryEntryLifecycle</class>
diff --git a/src/main/resources/database/mysql/alter_11_2_x_to_11_3_0.sql b/src/main/resources/database/mysql/alter_11_2_x_to_11_3_0.sql
index c021aae0b4f..523a972ebe8 100644
--- a/src/main/resources/database/mysql/alter_11_2_x_to_11_3_0.sql
+++ b/src/main/resources/database/mysql/alter_11_2_x_to_11_3_0.sql
@@ -1,7 +1,7 @@
 create table o_vid_metadata (
   id bigint not null auto_increment,
-  creationdate timestamp not null,
-  lastmodified timestamp not null,
+  creationdate datetime not null,
+  lastmodified datetime not null,
   vid_width bigint default null,
   vid_height bigint default null,
   vid_size bigint default null,
@@ -14,3 +14,21 @@ create table o_vid_metadata (
 alter table o_vid_metadata ENGINE = InnoDB;
 
 alter table o_vid_metadata add constraint vid_meta_rsrc_idx foreign key (fk_resource_id) references o_olatresource (resource_id);
+
+
+
+alter table o_user add column u_smstelmobile varchar(255);
+
+create table o_sms_message_log (
+   id bigint not null auto_increment,
+   creationdate datetime not null,
+   lastmodified datetime not null,
+   s_message_uuid varchar(256) not null,
+   s_server_response varchar(256),
+   s_service_id varchar(32) not null,
+   fk_identity bigint not null,
+   primary key (id)
+);
+alter table o_sms_message_log ENGINE = InnoDB;
+
+alter table o_sms_message_log add constraint sms_log_to_identity_idx foreign key (fk_identity) references o_bs_identity (id);
diff --git a/src/main/resources/database/mysql/setupDatabase.sql b/src/main/resources/database/mysql/setupDatabase.sql
index 1d9106e9683..8c919488a31 100644
--- a/src/main/resources/database/mysql/setupDatabase.sql
+++ b/src/main/resources/database/mysql/setupDatabase.sql
@@ -230,6 +230,7 @@ create table if not exists o_user (
    u_telprivate varchar(255),
    u_telmobile varchar(255),
    u_teloffice varchar(255),
+   u_smstelmobile varchar(255),
    u_skype varchar(255),
    u_msn varchar(255),
    u_xing varchar(255),
@@ -1905,6 +1906,18 @@ create table o_ex_task_modifier (
    primary key (id)
 );
 
+-- sms
+create table o_sms_message_log (
+   id bigint not null auto_increment,
+   creationdate datetime not null,
+   lastmodified datetime not null,
+   s_message_uuid varchar(256) not null,
+   s_server_response varchar(256),
+   s_service_id varchar(32) not null,
+   fk_identity bigint not null,
+   primary key (id)
+);
+
 -- user view
 create view o_bs_identity_short_v as (
    select
@@ -2235,7 +2248,7 @@ alter table o_pf_assignment ENGINE = InnoDB;
 alter table o_pf_binder_user_infos ENGINE = InnoDB;
 alter table o_eva_form_session ENGINE = InnoDB;
 alter table o_eva_form_response ENGINE = InnoDB;
-
+alter table o_sms_message_log ENGINE = InnoDB;
 
 -- rating
 alter table o_userrating add constraint FKF26C8375236F20X foreign key (creator_id) references o_bs_identity (id);
@@ -2692,6 +2705,9 @@ alter table o_cer_certificate add constraint cer_to_resource_idx foreign key (fk
 create index cer_archived_resource_idx on o_cer_certificate (c_archived_resource_id);
 create index cer_uuid_idx on o_cer_certificate (c_uuid);
 
+-- sms
+alter table o_sms_message_log add constraint sms_log_to_identity_idx foreign key (fk_identity) references o_bs_identity (id);
+
 -- o_logging_table
 create index log_target_resid_idx on o_loggingtable(targetresid);
 create index log_ptarget_resid_idx on o_loggingtable(parentresid);
diff --git a/src/main/resources/database/oracle/alter_11_2_x_to_11_3_0.sql b/src/main/resources/database/oracle/alter_11_2_x_to_11_3_0.sql
index 9bbd58a54bd..d713c012bfd 100644
--- a/src/main/resources/database/oracle/alter_11_2_x_to_11_3_0.sql
+++ b/src/main/resources/database/oracle/alter_11_2_x_to_11_3_0.sql
@@ -12,4 +12,21 @@ create table o_vid_metadata (
 );
 
 alter table o_vid_metadata add constraint vid_meta_rsrc_idx foreign key (fk_resource_id) references o_olatresource (resource_id);
-create index idx_vid_meta_rsrc_idx on o_vid_metadata(fk_resource_id);
\ No newline at end of file
+create index idx_vid_meta_rsrc_idx on o_vid_metadata(fk_resource_id);
+
+
+alter table o_user add u_smstelmobile varchar2(255 char);
+
+create table o_sms_message_log (
+   id number(20) GENERATED ALWAYS AS IDENTITY,
+   creationdate date not null,
+   lastmodified date not null,
+   s_message_uuid varchar2(256 char) not null,
+   s_server_response varchar2(256 char),
+   s_service_id varchar2(32 char) not null,
+   fk_identity number(20) not null,
+   primary key (id)
+);
+
+alter table o_sms_message_log add constraint sms_log_to_identity_idx foreign key (fk_identity) references o_bs_identity (id);
+create index idx_sms_log_to_identity_idx on o_sms_message_log(fk_identity);
diff --git a/src/main/resources/database/oracle/setupDatabase.sql b/src/main/resources/database/oracle/setupDatabase.sql
index 9aaa09011fb..2f51847d080 100644
--- a/src/main/resources/database/oracle/setupDatabase.sql
+++ b/src/main/resources/database/oracle/setupDatabase.sql
@@ -259,6 +259,7 @@ CREATE TABLE o_user (
    u_telprivate varchar2(255 char),
    u_telmobile varchar2(255 char),
    u_teloffice varchar2(255 char),
+   u_smstelmobile varchar2(255 char),
    u_skype varchar2(255 char),
    u_msn varchar2(255 char),
    u_xing varchar2(255 char),
@@ -1949,6 +1950,18 @@ create table o_co_db_entry (
    primary key (id)
 );
 
+-- sms
+create table o_sms_message_log (
+   id number(20) GENERATED ALWAYS AS IDENTITY,
+   creationdate date not null,
+   lastmodified date not null,
+   s_message_uuid varchar2(256 char) not null,
+   s_server_response varchar2(256 char),
+   s_service_id varchar2(32 char) not null,
+   fk_identity number(20) not null,
+   primary key (id)
+);
+
 -- user view
 create view o_bs_identity_short_v as (
    select
@@ -2885,6 +2898,10 @@ create index cer_resource_idx on o_cer_certificate (fk_olatresource);
 create index cer_archived_resource_idx on o_cer_certificate (c_archived_resource_id);
 create index cer_uuid_idx on o_cer_certificate (c_uuid);
 
+-- sms
+alter table o_sms_message_log add constraint sms_log_to_identity_idx foreign key (fk_identity) references o_bs_identity (id);
+create index idx_sms_log_to_identity_idx on o_sms_message_log(fk_identity);
+
 -- o_logging_table
 create index log_target_resid_idx on o_loggingtable(targetresid);
 create index log_ptarget_resid_idx on o_loggingtable(parentresid);
diff --git a/src/main/resources/database/postgresql/alter_11_2_x_to_11_3_0.sql b/src/main/resources/database/postgresql/alter_11_2_x_to_11_3_0.sql
index 8d495aff18d..fa2ce5850a7 100644
--- a/src/main/resources/database/postgresql/alter_11_2_x_to_11_3_0.sql
+++ b/src/main/resources/database/postgresql/alter_11_2_x_to_11_3_0.sql
@@ -2,14 +2,32 @@ create table o_vid_metadata (
   id bigserial not null,
   creationdate timestamp not null,
   lastmodified timestamp not null,
-  vid_width bigint default null,
-  vid_height bigint default null,
-  vid_size bigint default null,
+  vid_width int8 default null,
+  vid_height int8 default null,
+  vid_size int8 default null,
   vid_format varchar(32) default null,
   vid_length varchar(32) default null,
-  fk_resource_id bigint not null,
+  fk_resource_id int8 not null,
   primary key (id)
 );
 
 alter table o_vid_metadata add constraint vid_meta_rsrc_idx foreign key (fk_resource_id) references o_olatresource (resource_id);
-create index idx_vid_meta_rsrc_idx on o_vid_metadata(fk_resource_id);
\ No newline at end of file
+create index idx_vid_meta_rsrc_idx on o_vid_metadata(fk_resource_id);
+
+
+
+
+alter table o_user add column u_smstelmobile varchar(255);
+
+create table o_sms_message_log (
+   id bigserial not null,
+   creationdate timestamp not null,
+   lastmodified timestamp not null,
+   s_message_uuid varchar(256) not null,   s_server_response varchar(256),
+   s_service_id varchar(32) not null,
+   fk_identity int8 not null,
+   primary key (id)
+);
+
+alter table o_sms_message_log add constraint sms_log_to_identity_idx foreign key (fk_identity) references o_bs_identity (id);
+create index idx_sms_log_to_identity_idx on o_sms_message_log(fk_identity);
diff --git a/src/main/resources/database/postgresql/setupDatabase.sql b/src/main/resources/database/postgresql/setupDatabase.sql
index 3488c69638a..e44a49bd49f 100644
--- a/src/main/resources/database/postgresql/setupDatabase.sql
+++ b/src/main/resources/database/postgresql/setupDatabase.sql
@@ -228,6 +228,7 @@ create table o_user (
    u_telprivate varchar(255),
    u_telmobile varchar(255),
    u_teloffice varchar(255),
+   u_smstelmobile varchar(255),
    u_skype varchar(255),
    u_msn varchar(255),
    u_xing varchar(255),
@@ -1315,12 +1316,12 @@ create table o_vid_metadata (
   id bigserial not null,
   creationdate timestamp not null,
   lastmodified timestamp not null,
-  vid_width bigint default null,
-  vid_height bigint default null,
-  vid_size bigint default null,
+  vid_width int8 default null,
+  vid_height int8 default null,
+  vid_size int8 default null,
   vid_format varchar(32) default null,
   vid_length varchar(32) default null,
-  fk_resource_id bigint not null,
+  fk_resource_id int8 not null,
   primary key (id)
 );
 
@@ -1903,6 +1904,17 @@ create table o_ex_task_modifier (
    primary key (id)
 );
 
+-- sms
+create table o_sms_message_log (
+   id bigserial not null,
+   creationdate timestamp not null,
+   lastmodified timestamp not null,
+   s_message_uuid varchar(256) not null,   s_server_response varchar(256),
+   s_service_id varchar(32) not null,
+   fk_identity int8 not null,
+   primary key (id)
+);
+
 -- user view
 create view o_bs_identity_short_v as (
    select
@@ -2736,6 +2748,10 @@ create index cer_resource_idx on o_cer_certificate (fk_olatresource);
 create index cer_archived_resource_idx on o_cer_certificate (c_archived_resource_id);
 create index cer_uuid_idx on o_cer_certificate (c_uuid);
 
+-- sms
+alter table o_sms_message_log add constraint sms_log_to_identity_idx foreign key (fk_identity) references o_bs_identity (id);
+create index idx_sms_log_to_identity_idx on o_sms_message_log(fk_identity);
+
 -- o_logging_table
 create index log_target_resid_idx on o_loggingtable(targetresid);
 create index log_ptarget_resid_idx on o_loggingtable(parentresid);
diff --git a/src/main/resources/serviceconfig/olat.properties b/src/main/resources/serviceconfig/olat.properties
index 045d530515d..0d2ffebcbec 100644
--- a/src/main/resources/serviceconfig/olat.properties
+++ b/src/main/resources/serviceconfig/olat.properties
@@ -1029,6 +1029,20 @@ build.repo.revision=local-devel
 #####
 log.anonymous=false
 
+########################################
+# Simple message service
+########################################
+message.enabled=false
+message.provider=Dummy
+message.provider.values=Dummy,WebSMS
+
+#Features using the sms
+message.reset.password.enabled=true
+
+#WebSMS configuration
+websms.username=
+websms.password=
+
 ########################################
 # Access control
 ########################################
diff --git a/src/test/java/org/olat/core/commons/services/sms/manager/MessageLogDAOTest.java b/src/test/java/org/olat/core/commons/services/sms/manager/MessageLogDAOTest.java
new file mode 100644
index 00000000000..186e6ac5ed7
--- /dev/null
+++ b/src/test/java/org/olat/core/commons/services/sms/manager/MessageLogDAOTest.java
@@ -0,0 +1,87 @@
+/**
+ * <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.sms.manager;
+
+import java.util.List;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.olat.core.commons.persistence.DB;
+import org.olat.core.commons.services.sms.MessageLog;
+import org.olat.core.commons.services.sms.model.MessageStatistics;
+import org.olat.core.id.Identity;
+import org.olat.test.JunitTestHelper;
+import org.olat.test.OlatTestCase;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * 
+ * Initial date: 3 févr. 2017<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class MessageLogDAOTest extends OlatTestCase {
+	
+	@Autowired
+	private DB dbInstance;
+	@Autowired
+	private MessageLogDAO messageLogDao;
+	
+	@Test
+	public void createMessageLog() {
+		Identity recipient = JunitTestHelper.createAndPersistIdentityAsRndUser("sms-1");
+		MessageLog mLog = messageLogDao.create(recipient, "devnull");
+		Assert.assertNotNull(mLog);
+		Assert.assertNotNull(mLog.getCreationDate());
+		Assert.assertNotNull(mLog.getMessageUuid());
+		Assert.assertNotNull(mLog.getServiceId());
+	}
+	
+	@Test
+	public void createAndPersistMessageLog() {
+		Identity recipient = JunitTestHelper.createAndPersistIdentityAsRndUser("sms-2");
+		MessageLog mLog = messageLogDao.create(recipient, "devnull");
+		Assert.assertNotNull(mLog);
+		mLog = messageLogDao.save(mLog);
+		dbInstance.commitAndCloseSession();
+		
+		MessageLog reloadedLog = messageLogDao.loadMessageByKey(mLog.getKey());
+		Assert.assertNotNull(mLog);
+		Assert.assertNotNull(reloadedLog.getCreationDate());
+		Assert.assertEquals(mLog, reloadedLog);
+		Assert.assertEquals(mLog.getMessageUuid(), reloadedLog.getMessageUuid());
+		Assert.assertEquals(recipient, reloadedLog.getRecipient());
+		Assert.assertEquals("devnull", mLog.getServiceId());
+	}
+	
+	@Test
+	public void getStatisticsPerMonth() {
+		Identity recipient = JunitTestHelper.createAndPersistIdentityAsRndUser("sms-2");
+		MessageLog mLog = messageLogDao.create(recipient, "devnull");
+		Assert.assertNotNull(mLog);
+		mLog = messageLogDao.save(mLog);
+		dbInstance.commitAndCloseSession();
+		
+		List<MessageStatistics> stats = messageLogDao.getStatisticsPerMonth("devnull");
+		Assert.assertNotNull(stats);
+
+	}
+
+}
diff --git a/src/test/java/org/olat/core/commons/services/sms/spi/WebSMSProviderTest.java b/src/test/java/org/olat/core/commons/services/sms/spi/WebSMSProviderTest.java
new file mode 100644
index 00000000000..68cd351ee7f
--- /dev/null
+++ b/src/test/java/org/olat/core/commons/services/sms/spi/WebSMSProviderTest.java
@@ -0,0 +1,48 @@
+/**
+ * <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.sms.spi;
+
+import java.util.UUID;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.olat.test.OlatTestCase;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * 
+ * Initial date: 3 févr. 2017<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class WebSMSProviderTest extends OlatTestCase {
+	
+	@Autowired
+	private WebSMSProvider webSmsProvider;
+	
+	@Test
+	public void send() {
+		//webSmsProvider.setCredentials("stephane.rosse@frentix.com", "secret");
+		webSmsProvider.setTest(true);
+		boolean ok = webSmsProvider.send(UUID.randomUUID().toString(), "Hello from jenkins", "41797346615");
+		Assert.assertTrue(ok);
+	}
+
+}
diff --git a/src/test/java/org/olat/registration/RegistrationManagerTest.java b/src/test/java/org/olat/registration/RegistrationManagerTest.java
index 8300c073422..4b1aa800213 100644
--- a/src/test/java/org/olat/registration/RegistrationManagerTest.java
+++ b/src/test/java/org/olat/registration/RegistrationManagerTest.java
@@ -31,8 +31,10 @@ import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
+import java.util.List;
 import java.util.UUID;
 
+import org.junit.Assert;
 import org.junit.Test;
 import org.olat.core.commons.persistence.DB;
 import org.olat.test.OlatTestCase;
@@ -64,12 +66,13 @@ public class RegistrationManagerTest extends OlatTestCase {
 	 */
 	@Test
 	public void testRegister() {
-		String emailaddress = "sabina@jeger.net";
+		String emailaddress = UUID.randomUUID() +  "@openolat.com";
 		String ipaddress = "130.60.112.10";
-		TemporaryKeyImpl result = registrationManager.register(emailaddress, ipaddress, "register");
-		assertTrue(result != null);
-		assertEquals(emailaddress,result.getEmailAddress());
-		assertEquals(ipaddress,result.getIpAddress());
+		
+		TemporaryKey result = registrationManager.register(emailaddress, ipaddress, "register");
+		Assert.assertTrue(result != null);
+		Assert.assertEquals(emailaddress, result.getEmailAddress());
+		Assert.assertEquals(ipaddress, result.getIpAddress());
 	}
 
 
@@ -78,27 +81,43 @@ public class RegistrationManagerTest extends OlatTestCase {
 	 */
 	@Test
 	public void testLoadTemporaryKeyByRegistrationKey() {
-		String emailaddress = "christian.guretzki@id.uzh.ch";
+		String emailaddress = UUID.randomUUID() + "@openolat.com";
 		String regkey = "";
-		TemporaryKeyImpl result = null;
 		String ipaddress = "130.60.112.12";
 
-		//
-		result = registrationManager.loadTemporaryKeyByRegistrationKey(regkey);
-		assertTrue("not found, as registration key is empty", result == null);
+		TemporaryKey result1 = registrationManager.loadTemporaryKeyByRegistrationKey(regkey);
+		Assert.assertNull("not found, as registration key is empty", result1);
 		
 		//now create a temp key
-		result = registrationManager.createTemporaryKeyByEmail(emailaddress,ipaddress, RegistrationManager.REGISTRATION);
-		assertTrue("result not null because key generated", result != null);
-		//**
-		dbInstance.closeSession();
-		regkey = result.getRegistrationKey();
-		//**
-		
+		TemporaryKey result2 = registrationManager.createTemporaryKeyByEmail(emailaddress, ipaddress, RegistrationManager.REGISTRATION);
+		Assert.assertNotNull("result not null because key generated", result2);
+		dbInstance.commitAndCloseSession();
+
+		//check that loading the key by registration key works
+		TemporaryKey reloadedResult = registrationManager.loadTemporaryKeyByRegistrationKey(result2.getRegistrationKey());
+		Assert.assertNotNull("we should find the key just created", reloadedResult);
+		Assert.assertEquals(result2, reloadedResult);
+	}
+	
+	/**
+	 * Test load of temp key.
+	 */
+	@Test
+	public void testLoadTemporaryKeyByAction() {
+		String emailaddress = UUID.randomUUID() + "@openolat.com";
+		String regAction =  UUID.randomUUID().toString();
+		String ipaddress = "130.60.112.12";
+
+		//now create a temporary key
+		TemporaryKey result = registrationManager.createTemporaryKeyByEmail(emailaddress, ipaddress, regAction);
+		Assert.assertNotNull("result not null because key generated", result);
+		dbInstance.commitAndCloseSession();
+
 		//check that loading the key by registration key works
-		result = null;
-		result = registrationManager.loadTemporaryKeyByRegistrationKey(regkey);
-		assertTrue("we should find the key just created", result != null);
+		List<TemporaryKey> reloadedResults = registrationManager.loadTemporaryKeyByAction(regAction);
+		Assert.assertNotNull(reloadedResults);
+		Assert.assertEquals(1, reloadedResults.size());
+		Assert.assertEquals(result, reloadedResults.get(0));
 	}
 	
 	/**
@@ -106,48 +125,38 @@ public class RegistrationManagerTest extends OlatTestCase {
 	 */
 	@Test
 	public void testLoadTemporaryKeyEntry() {
-		String emailaddress = UUID.randomUUID().toString().replace("-", "") + "@frentix.com";
-		TemporaryKeyImpl result = null;
+		String emailaddress = UUID.randomUUID() + "@frentix.com";
 		String ipaddress = "130.60.112.11";
 
 		//try to load temp key which was not created before
-		result = registrationManager.loadTemporaryKeyByEmail(emailaddress);
-		assertTrue("result should be null, because not found", result == null);
+		TemporaryKey result = registrationManager.loadTemporaryKeyByEmail(emailaddress);
+		Assert.assertTrue("result should be null, because not found", result == null);
 		
 		//now create a temp key
-		result = registrationManager.createTemporaryKeyByEmail(emailaddress,ipaddress, RegistrationManager.REGISTRATION);
-		assertTrue("result not null because key generated", result != null);
-		//**
-		dbInstance.closeSession();
-		//**
+		TemporaryKey realResult = registrationManager.createTemporaryKeyByEmail(emailaddress,ipaddress, RegistrationManager.REGISTRATION);
+		Assert.assertNotNull("result not null because key generated", realResult);
+		dbInstance.commitAndCloseSession();
 		
 		//check that loading the key by e-mail works
-		result = null;
-		result = registrationManager.loadTemporaryKeyByEmail(emailaddress);
-		assertTrue("we shoult find the key just created", result != null);
+		TemporaryKey reloadResult = registrationManager.loadTemporaryKeyByEmail(emailaddress);
+		Assert.assertNotNull("we shoult find the key just created", reloadResult);
+		Assert.assertEquals(realResult, reloadResult);
 	}
 	
-	
 	/**
 	 * Test load of temp key.
 	 */
 	@Test public void testCreateTemporaryKeyEntry() {
-		String emailaddress = "sabina@jeger.net";
-		TemporaryKeyImpl result = null;
+		String emailaddress = UUID.randomUUID() + "@openolat.com";
 		String ipaddress = "130.60.112.10";
 
-		result = registrationManager.createTemporaryKeyByEmail(emailaddress,ipaddress, RegistrationManager.REGISTRATION);
-		assertTrue(result != null);
-
-		emailaddress = "sabina@jeger.ch";
-		result = registrationManager.createTemporaryKeyByEmail(emailaddress,ipaddress, RegistrationManager.REGISTRATION);
-
-		assertTrue(result != null);
-		
-		emailaddress = "info@jeger.net";
-		result = registrationManager.createTemporaryKeyByEmail(emailaddress,ipaddress, RegistrationManager.REGISTRATION);
-
-		assertNotNull(result);
+		TemporaryKey result = registrationManager.createTemporaryKeyByEmail(emailaddress, ipaddress, RegistrationManager.REGISTRATION);
+		dbInstance.commitAndCloseSession();
+		Assert.assertNotNull(result);
+		Assert.assertNotNull(result.getKey());
+		Assert.assertNotNull(result.getCreationDate());
+		Assert.assertEquals(ipaddress, result.getIpAddress());
+		Assert.assertEquals(emailaddress, result.getEmailAddress());
 	}
 	
 	@Test
diff --git a/src/test/java/org/olat/test/AllTestsJunit4.java b/src/test/java/org/olat/test/AllTestsJunit4.java
index 26bd9fd12a1..17cee890688 100644
--- a/src/test/java/org/olat/test/AllTestsJunit4.java
+++ b/src/test/java/org/olat/test/AllTestsJunit4.java
@@ -93,6 +93,7 @@ import org.junit.runners.Suite;
 	org.olat.core.commons.services.webdav.manager.DigestAuthenticationTest.class,
 	org.olat.core.commons.services.webdav.manager.WebDAVManagerTest.class,
 	org.olat.core.commons.services.webdav.servlets.RequestUtilsTest.class,
+	org.olat.core.commons.services.sms.manager.MessageLogDAOTest.class,
 	org.olat.core.commons.services.taskexecutor.PersistentTaskDAOTest.class,
 	org.olat.core.commons.services.taskexecutor.TaskExecutorManagerTest.class,
 	org.olat.admin.user.delete.service.UserDeletionManagerTest.class,
-- 
GitLab