added entry point for e-mail validation

This commit is contained in:
Axel Uhl committed 2014-10-17 18:14:41 +02:00
1 parent 7075aba3ad
commit 9524510fa0
32 files changed
+554 -132

No files matched your search

@@ -9,9 +9,9 @@ import org.junit.BeforeClass;
import org.junit.Test;
import com.sap.sse.security.SecurityServiceImpl;
import com.sap.sse.security.UserStore;
import com.sap.sse.security.UsernamePasswordRealm;
import com.sap.sse.security.impl.Activator;
import com.sap.sse.security.shared.UserStore;
import com.sap.sse.security.userstore.mongodb.UserStoreImpl;
public class LoginTest {
@@ -1,3 +1,3 @@
eclipse.preferences.version=1
entryPointModules=
gwtCompileSettings=PGd3dC1jb21waWxlLXNldHRpbmdzPjxsb2ctbGV2ZWw+SU5GTzwvbG9nLWxldmVsPjxvdXRwdXQtc3R5bGU+T0JGVVNDQVRFRDwvb3V0cHV0LXN0eWxlPjxleHRyYS1hcmdzPjwhW0NEQVRBWy13YXIgLiAtbG9jYWxXb3JrZXJzIDMgLXN0cmljdCAtbG9nTGV2ZWwgVFJBQ0VdXT48L2V4dHJhLWFyZ3M+PHZtLWFyZ3M+PCFbQ0RBVEFbLVhteDIwMjRtIC1EZ3d0LnVzZWFyY2hpdmVzPWZhbHNlXV0+PC92bS1hcmdzPjxlbnRyeS1wb2ludC1tb2R1bGU+Y29tLnNhcC5zc2Uuc2VjdXJpdHkudWkuQ2hhbmdlUGFzc3dvcmQ8L2VudHJ5LXBvaW50LW1vZHVsZT48L2d3dC1jb21waWxlLXNldHRpbmdzPg\=\=
gwtCompileSettings=PGd3dC1jb21waWxlLXNldHRpbmdzPjxsb2ctbGV2ZWw+SU5GTzwvbG9nLWxldmVsPjxvdXRwdXQtc3R5bGU+T0JGVVNDQVRFRDwvb3V0cHV0LXN0eWxlPjxleHRyYS1hcmdzPjwhW0NEQVRBWy13YXIgLiAtbG9jYWxXb3JrZXJzIDMgLXN0cmljdCAtbG9nTGV2ZWwgVFJBQ0VdXT48L2V4dHJhLWFyZ3M+PHZtLWFyZ3M+PCFbQ0RBVEFbLVhteDIwMjRtIC1EZ3d0LnVzZWFyY2hpdmVzPWZhbHNlXV0+PC92bS1hcmdzPjxlbnRyeS1wb2ludC1tb2R1bGU+Y29tLnNhcC5zc2Uuc2VjdXJpdHkudWkuRW1haWxWYWxpZGF0aW9uPC9lbnRyeS1wb2ludC1tb2R1bGU+PC9nd3QtY29tcGlsZS1zZXR0aW5ncz4\=
+50
View File
@@ -0,0 +1,50 @@
<!doctype html>
<!-- The DOCTYPE declaration above will set the -->
<!-- browser's rendering engine into -->
<!-- "Standards Mode". Replacing this declaration -->
<!-- with a "Quirks Mode" doctype may lead to some -->
<!-- differences in layout. -->
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<!-- -->
<!-- Consider inlining CSS to reduce the number of requested files -->
<!-- -->
<link rel="shortcut icon" type="image/x-icon" href="images/sap.ico" />
<!-- -->
<!-- Any title is fine -->
<!-- -->
<title>SAP Sailing Analytics Login</title>
<!-- -->
<!-- This script loads your compiled module. -->
<!-- If you add any GWT meta tags, they must -->
<!-- be added before this line. -->
<!-- -->
<script type="text/javascript" language="javascript" src="com.sap.sse.security.ui.EmailValidation/com.sap.sse.security.ui.EmailValidation.nocache.js"></script>
</head>
<!-- -->
<!-- The body can have arbitrary html, or -->
<!-- you can leave the body empty if you want -->
<!-- to create a completely dynamic UI. -->
<!-- -->
<body>
<!-- OPTIONAL: include this if you want history support -->
<iframe src="javascript:''" id="__gwt_historyFrame" tabIndex='-1' style="position:absolute;width:0;height:0;border:0"></iframe>
<!-- RECOMMENDED if your web app will not function without JavaScript enabled -->
<noscript>
<div style="width: 22em; position: absolute; left: 50%; margin-left: -11em; color: red; background-color: white; border: 1px solid red; padding: 4px; font-family: sans-serif">
Your web browser must have JavaScript enabled
in order for this application to display correctly.
</div>
</noscript>
</body>
</html>
@@ -71,5 +71,6 @@
<url-pattern>/com.sap.sse.security.ui.Login/remote_logging</url-pattern>
<url-pattern>/com.sap.sse.security.ui.UserManagement/remote_logging</url-pattern>
<url-pattern>/com.sap.sse.security.ui.ChangePassword/remote_logging</url-pattern>
<url-pattern>/com.sap.sse.security.ui.EmailValidation/remote_logging</url-pattern>
</servlet-mapping>
</web-app>
@@ -13,5 +13,7 @@ bin.includes = META-INF/,\
com.sap.sse.security.ui.Register/,\
com.sap.sse.security.ui.OAuthLogin/,\
ChangePassword.html,\
com.sap.sse.security.ui.ChangePassword/
com.sap.sse.security.ui.ChangePassword/,\
com.sap.sse.security.ui.EmailValidation/,\
EmailValidation.html
output.. = WEB-INF/classes/
+1
View File
@@ -63,6 +63,7 @@
<module>com.sap.sse.security.ui.LoginPanel</module>
<module>com.sap.sse.security.ui.Register</module>
<module>com.sap.sse.security.ui.ChangePassword</module>
<module>com.sap.sse.security.ui.EmailValidation</module>
</modules>
<gen>${basedir}/.generated</gen>
<localWorkers>3</localWorkers>
@@ -111,4 +111,12 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages {
String successfullyUpdatedEmail();
String resetPassword();
String errorDuringPasswordReset(String message);
String cannotResetPasswordWithoutValidatedEmail(String username);
String newPasswordSent(String username);
}
@@ -50,4 +50,8 @@ errorChangingPassword=Error changing password: {0}
invalidCredentials=Invalid credentials
editEmail=Edit e-mail address
errorUpdatingEmail=Error updating e-mail address: {0}
successfullyUpdatedEmail=Successfully updated e-mail address
successfullyUpdatedEmail=Successfully updated e-mail address
resetPassword=Reset password
errorDuringPasswordReset=Error during password reset: {0}
cannotResetPasswordWithoutValidatedEmail=Cannot reset password because user {0} does not have a validated e-mail address
newPasswordSent=A new password has been sent to {0}''s validated e-mail address
@@ -50,4 +50,8 @@ errorChangingPassword=Fehler beim
invalidCredentials=Ungültige Zugangsdaten
editEmail=e-Mail-Adresse bearbeiten
errorUpdatingEmail=Fehler beim Aktualisieren der e-Mail-Adresse: {0}
successfullyUpdatedEmail=e-Mail-Adresse erfolgreich aktualisiert
successfullyUpdatedEmail=e-Mail-Adresse erfolgreich aktualisiert
resetPassword=Passwort zurücksetzen
errorDuringPasswordReset=Fehler beim Zurücksetyen des Passworts: {0}
cannotResetPasswordWithoutValidatedEmail=Passwort für Benutzer {0} kann nicht zurückgesetzt werden, da keine validierte e-Mail-Adresse vorliegt
newPasswordSent=Ein neues Passwort ist an die validierte e-Mail-Adresse des Benutzers {0} gesendet worden
@@ -0,0 +1,139 @@
package com.sap.sse.security.ui.emailvalidation;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.KeyUpEvent;
import com.google.gwt.event.dom.client.KeyUpHandler;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.rpc.ServiceDefTarget;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.DockLayoutPanel;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.FormPanel;
import com.google.gwt.user.client.ui.FormPanel.SubmitEvent;
import com.google.gwt.user.client.ui.FormPanel.SubmitHandler;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.PasswordTextBox;
import com.google.gwt.user.client.ui.RootLayoutPanel;
import com.google.gwt.user.client.ui.SubmitButton;
import com.google.gwt.user.client.ui.TextBox;
import com.sap.sse.gwt.client.EntryPointHelper;
import com.sap.sse.security.shared.UserManagementException;
import com.sap.sse.security.ui.client.RemoteServiceMappingConstants;
import com.sap.sse.security.ui.client.StringMessages;
import com.sap.sse.security.ui.client.UserService;
import com.sap.sse.security.ui.client.UserStatusEventHandler;
import com.sap.sse.security.ui.client.component.NewAccountValidator;
import com.sap.sse.security.ui.shared.UserDTO;
import com.sap.sse.security.ui.shared.UserManagementService;
import com.sap.sse.security.ui.shared.UserManagementServiceAsync;
public class EmailValidationEntryPoint implements EntryPoint {
private final UserManagementServiceAsync userManagementService = GWT.create(UserManagementService.class);
private final StringMessages stringMessages = GWT.create(StringMessages.class);
@Override
public void onModuleLoad() {
EntryPointHelper.registerASyncService((ServiceDefTarget) userManagementService,
RemoteServiceMappingConstants.WEB_CONTEXT_PATH,
RemoteServiceMappingConstants.userManagementServiceRemotePath);
final UserService userService = new UserService(userManagementService);
final NewAccountValidator validator = new NewAccountValidator(stringMessages);
RootLayoutPanel rootPanel = RootLayoutPanel.get();
DockLayoutPanel dockPanel = new DockLayoutPanel(Unit.PX);
rootPanel.add(dockPanel);
FlowPanel fp = new FlowPanel();
final Label errorLabel = new Label();
fp.add(errorLabel);
final Label nameLabel = new Label(stringMessages.username());
fp.add(nameLabel);
final TextBox nameText = new TextBox();
nameText.setEnabled(false);
userService.addUserStatusEventHandler(new UserStatusEventHandler() {
@Override
public void onUserStatusChange(UserDTO user) {
if (user == null) {
nameText.setText("");
} else {
nameText.setText(user.getName());
}
}
});
fp.add(nameText);
Label currentPasswordLabel = new Label(stringMessages.currentPassword());
fp.add(currentPasswordLabel);
final TextBox currentPasswordText = new PasswordTextBox();
fp.add(currentPasswordText);
Label pwLabel = new Label(stringMessages.password());
fp.add(pwLabel);
final PasswordTextBox pwText = new PasswordTextBox();
fp.add(pwText);
Label pw2Label = new Label(stringMessages.passwordRepeat());
fp.add(pw2Label);
final PasswordTextBox pw2Text = new PasswordTextBox();
fp.add(pw2Text);
final SubmitButton submit = new SubmitButton(stringMessages.changePassword());
fp.add(submit);
KeyUpHandler keyUpHandler = new KeyUpHandler() {
@Override
public void onKeyUp(KeyUpEvent event) {
String errorMessage = validator.validateUsernameAndPassword(nameText.getText(), pwText.getText(), pw2Text.getText());
if (errorMessage == null) {
errorLabel.setText("");
submit.setEnabled(true);
} else {
errorLabel.setText(errorMessage);
submit.setEnabled(false);
}
}
};
keyUpHandler.onKeyUp(null); // enable/disable submit button and fill error label
nameText.addKeyUpHandler(keyUpHandler);
pwText.addKeyUpHandler(keyUpHandler);
pw2Text.addKeyUpHandler(keyUpHandler);
FormPanel formPanel = new FormPanel();
formPanel.addSubmitHandler(new SubmitHandler() {
@Override
public void onSubmit(SubmitEvent event) {
userManagementService.updateSimpleUserPassword(nameText.getText(), currentPasswordText.getText(), pwText.getText(), new AsyncCallback<Void>() {
@Override
public void onFailure(Throwable caught) {
GWT.log(caught.getMessage());
if (caught instanceof UserManagementException) {
String message = ((UserManagementException) caught).getMessage();
if (UserManagementException.PASSWORD_DOES_NOT_MEET_REQUIREMENTS.equals(message)) {
Window.alert(stringMessages.passwordDoesNotMeetRequirements());
} else if (UserManagementException.INVALID_CREDENTIALS.equals(message)) {
Window.alert(stringMessages.invalidCredentials());
} else {
Window.alert(stringMessages.errorChangingPassword(caught.getMessage()));
}
} else {
Window.alert(stringMessages.errorChangingPassword(caught.getMessage()));
}
}
@Override
public void onSuccess(Void result) {
Window.alert(stringMessages.passwordSuccessfullyChanged());
}
});
}
});
final Button logout = new Button(stringMessages.signOut());
logout.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
userService.logout();
Window.Location.reload();
}
});
fp.add(logout);
formPanel.add(fp);
dockPanel.add(formPanel);
}
}
@@ -4,11 +4,14 @@ import java.util.Collections;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.rpc.ServiceDefTarget;
import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.DockLayoutPanel;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.FormPanel;
@@ -21,6 +24,8 @@ import com.google.gwt.user.client.ui.SubmitButton;
import com.google.gwt.user.client.ui.TextBox;
import com.sap.sse.gwt.client.AbstractEntryPoint;
import com.sap.sse.gwt.client.EntryPointHelper;
import com.sap.sse.gwt.client.async.MarkedAsyncCallback;
import com.sap.sse.security.shared.UserManagementException;
import com.sap.sse.security.ui.client.EntryPointLinkFactory;
import com.sap.sse.security.ui.client.RemoteServiceMappingConstants;
import com.sap.sse.security.ui.client.StringMessages;
@@ -55,9 +60,34 @@ public class LoginEntryPoint extends AbstractEntryPoint {
fp.add(pwLabel);
final PasswordTextBox pwText = new PasswordTextBox();
fp.add(pwText);
SubmitButton submit = new SubmitButton(stringMessages.signIn());
final SubmitButton submit = new SubmitButton(stringMessages.signIn());
submit.ensureDebugId("login");
fp.add(submit);
final Button passwordReset = new Button(stringMessages.resetPassword());
passwordReset.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
userManagementService.resetPassword(nameText.getText(), new MarkedAsyncCallback<Void>(new AsyncCallback<Void>() {
@Override
public void onFailure(Throwable caught) {
if (caught instanceof UserManagementException) {
if (UserManagementException.CANNOT_RESET_PASSWORD_WITHOUT_VALIDATED_EMAIL.equals(caught.getMessage())) {
Window.alert(stringMessages.cannotResetPasswordWithoutValidatedEmail(nameText.getText()));
} else {
Window.alert(stringMessages.errorDuringPasswordReset(caught.getMessage()));
}
} else {
Window.alert(stringMessages.errorDuringPasswordReset(caught.getMessage()));
}
}
@Override
public void onSuccess(Void result) {
Window.alert(stringMessages.newPasswordSent(nameText.getText()));
}
}));
}
});
fp.add(new Anchor(new SafeHtmlBuilder().appendEscaped(stringMessages.signUp()).toSafeHtml(),
EntryPointLinkFactory.createRegistrationLink(Collections.<String, String> emptyMap())));
FormPanel formPanel = new FormPanel();
@@ -31,12 +31,12 @@ import com.sap.sse.security.Credential;
import com.sap.sse.security.SecurityService;
import com.sap.sse.security.SessionUtils;
import com.sap.sse.security.Social;
import com.sap.sse.security.User;
import com.sap.sse.security.shared.Account;
import com.sap.sse.security.shared.Account.AccountType;
import com.sap.sse.security.shared.DefaultRoles;
import com.sap.sse.security.shared.MailException;
import com.sap.sse.security.shared.SocialUserAccount;
import com.sap.sse.security.shared.User;
import com.sap.sse.security.shared.UserManagementException;
import com.sap.sse.security.shared.UsernamePasswordAccount;
import com.sap.sse.security.ui.oauth.client.CredentialDTO;
@@ -124,7 +124,7 @@ public class UserManagementServiceImpl extends RemoteServiceServlet implements U
}
@Override
public UserDTO createSimpleUser(String name, String email, String password) throws UserManagementException {
public UserDTO createSimpleUser(String name, String email, String password) throws UserManagementException, MailException {
User u = null;
try {
u = getSecurityService().createSimpleUser(name, email, password);
@@ -142,15 +142,22 @@ public class UserManagementServiceImpl extends RemoteServiceServlet implements U
@Override
public void updateSimpleUserPassword(String username, String oldPassword, String newPassword) throws UserManagementException, MailException {
final Subject subject = SecurityUtils.getSubject();
if (!subject.hasRole(DefaultRoles.ADMIN.getRolename())) {
// validate old password before proceeding
if (!getSecurityService().checkPassword(username, oldPassword)) {
throw new UserManagementException(UserManagementException.INVALID_CREDENTIALS);
}
}
if (subject.hasRole(DefaultRoles.ADMIN.getRolename()) || username.equals(SessionUtils.loadUsername())) {
getSecurityService().updateSimpleUserPassword(username, oldPassword, newPassword);
getSecurityService().updateSimpleUserPassword(username, newPassword);
getSecurityService().sendMail(username, "Password Changed", "Somebody changed your password for your user named "+username+".\nIf that wasn't you, I'd be worried...");
} else {
throw new UserManagementException(UserManagementException.INVALID_CREDENTIALS);
}
}
@Override
public void updateSimpleUserEmail(String username, String newEmail) throws UserManagementException {
public void updateSimpleUserEmail(String username, String newEmail) throws UserManagementException, MailException {
final Subject subject = SecurityUtils.getSubject();
if (subject.hasRole(DefaultRoles.ADMIN.getRolename()) || username.equals(SessionUtils.loadUsername())) {
getSecurityService().updateSimpleUserEmail(username, newEmail);
@@ -158,6 +165,11 @@ public class UserManagementServiceImpl extends RemoteServiceServlet implements U
throw new UserManagementException(UserManagementException.INVALID_CREDENTIALS);
}
}
@Override
public void resetPassword(String username) throws UserManagementException, MailException {
getSecurityService().resetPassword(username);
}
@Override
public Collection<UserDTO> getFilteredSortedUserList(String filter) {
@@ -18,11 +18,13 @@ public interface UserManagementService extends RemoteService {
SuccessInfo login(String username, String password);
UserDTO createSimpleUser(String name, String email, String password) throws UserManagementException;
UserDTO createSimpleUser(String name, String email, String password) throws UserManagementException, MailException;
void updateSimpleUserPassword(String name, String oldPassword, String newPassword) throws UserManagementException, MailException;
void updateSimpleUserEmail(String username, String newEmail) throws UserManagementException;
void updateSimpleUserEmail(String username, String newEmail) throws UserManagementException, MailException;
void resetPassword(String username) throws UserManagementException, MailException;
SuccessInfo deleteUser(String username);
@@ -19,6 +19,8 @@ public interface UserManagementServiceAsync {
void updateSimpleUserPassword(String name, String oldPassword, String newPassword, AsyncCallback<Void> callback);
void resetPassword(String username, AsyncCallback<Void> callback);
void updateSimpleUserEmail(String username, String newEmail, AsyncCallback<Void> callback);
void getFilteredSortedUserList(String filter, AsyncCallback<Collection<UserDTO>> callback);
@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Could not determine the version of your GWT SDK; using the module DTD from GWT 1.6.4. You may want to change this. -->
<!DOCTYPE module PUBLIC "-//Google Inc.//DTD Google Web Toolkit 2.4.0//EN" "http://google-web-toolkit.googlecode.com/svn/tags/2.4.0/distro-source/core/src/gwt-module.dtd">
<module>
<!-- Inherit the core Web Toolkit stuff. -->
<inherits name='com.google.gwt.user.User'/>
<!-- Inherit the default GWT style sheet. You can change -->
<!-- the theme of your GWT application by uncommenting -->
<!-- any one of the following lines. -->
<inherits name='com.google.gwt.user.theme.clean.Clean'/>
<inherits name='com.google.gwt.debug.Debug'/>
<!-- <inherits name='com.google.gwt.user.theme.standard.Standard'/> -->
<!-- <inherits name='com.google.gwt.user.theme.chrome.Chrome'/> -->
<!-- <inherits name='com.google.gwt.user.theme.dark.Dark'/> -->
<stylesheet src="../Register.css" />
<inherits name="com.google.gwt.resources.Resources" />
<inherits name="com.google.gwt.logging.Logging"/>
<!-- Other module inherits -->
<inherits name="com.sap.sse.gwt.EmulatedJRE" /> -->
<inherits name="com.sap.sse.gwt.SSESharedGWT" />
<inherits name="com.sap.sse.SSECommon" />
<inherits name="com.sap.sse.Security" />
<super-source path="jre" />
<!-- Specify the app entry point class. -->
<entry-point class='com.sap.sse.security.ui.changepassword.ChangePasswordEntryPoint'/>
<set-property name="gwt.logging.enabled" value="TRUE"/>
<set-property name="gwt.logging.logLevel" value="FINE"/>
<set-property name="gwt.logging.firebugHandler" value="ENABLED" />
<set-property name="gwt.logging.simpleRemoteHandler" value="ENABLED" />
<set-property name="gwt.logging.consoleHandler" value="ENABLED" />
<set-property name="gwt.logging.developmentModeHandler" value="DISABLED" />
<set-property name="gwt.logging.popupHandler" value="DISABLED" />
<set-property name="gwt.logging.systemHandler" value="ENABLED" />
<!-- Specify the paths for translatable code -->
<extend-property name="locale" values="de"/>
<source path='client'/>
<source path='changepassword'/>
<source path='oauth'/>
<source path='shared'/>
<source path='loginpanel'/>
</module>
@@ -6,7 +6,7 @@ import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceRegistration;
import com.sap.sse.security.shared.UserStore;
import com.sap.sse.security.UserStore;
public class Activator implements BundleActivator {
@@ -2,7 +2,7 @@ package com.sap.sse.security.userstore.mongodb;
import java.util.Map;
import com.sap.sse.security.shared.User;
import com.sap.sse.security.User;
public interface DomainObjectFactory {
@@ -6,7 +6,9 @@ public class FieldNames {
NAME,
EMAIL,
ACCOUNTS,
ROLES;
ROLES,
EMAIL_VALIDATED,
VALIDATION_SECRET;
}
public static enum Settings {
@@ -2,7 +2,7 @@ package com.sap.sse.security.userstore.mongodb;
import java.util.Map;
import com.sap.sse.security.shared.User;
import com.sap.sse.security.User;
public interface MongoObjectFactory {
@@ -9,10 +9,10 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Logger;
import com.sap.sse.security.SocialSettingsKeys;
import com.sap.sse.security.User;
import com.sap.sse.security.UserStore;
import com.sap.sse.security.shared.Account;
import com.sap.sse.security.shared.User;
import com.sap.sse.security.shared.UserManagementException;
import com.sap.sse.security.shared.UserStore;
public class UserStoreImpl implements UserStore {
private static final Logger logger = Logger.getLogger(UserStoreImpl.class.getName());
@@ -17,9 +17,9 @@ import com.mongodb.DB;
import com.mongodb.DBCollection;
import com.mongodb.DBObject;
import com.sap.sse.security.Social;
import com.sap.sse.security.User;
import com.sap.sse.security.shared.Account;
import com.sap.sse.security.shared.SocialUserAccount;
import com.sap.sse.security.shared.User;
import com.sap.sse.security.shared.UsernamePasswordAccount;
import com.sap.sse.security.shared.Account.AccountType;
import com.sap.sse.security.userstore.mongodb.CollectionNames;
@@ -70,6 +70,8 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
private User loadUser(DBObject userDBObject) {
String name = (String) userDBObject.get(FieldNames.User.NAME.name());
String email = (String) userDBObject.get(FieldNames.User.EMAIL.name());
Boolean emailValidated = (Boolean) userDBObject.get(FieldNames.User.EMAIL_VALIDATED.name());
String validationSecret = (String) userDBObject.get(FieldNames.User.VALIDATION_SECRET.name());
Set<String> roles = new HashSet<String>();
BasicDBList rolesO = (BasicDBList) userDBObject.get(FieldNames.User.ROLES.name());
if (rolesO != null){
@@ -79,7 +81,7 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
}
DBObject accountsMap = (DBObject) userDBObject.get(FieldNames.User.ACCOUNTS.name());
Map<AccountType, Account> accounts = createAccountMapFromdDBObject(accountsMap);
User result = new User(name, email, accounts.values());
User result = new User(name, email, emailValidated, validationSecret, accounts.values());
for (String role : roles) {
result.addRole(role);
}
@@ -11,9 +11,9 @@ import com.mongodb.DBCollection;
import com.mongodb.DBObject;
import com.mongodb.WriteConcern;
import com.sap.sse.security.Social;
import com.sap.sse.security.User;
import com.sap.sse.security.shared.Account;
import com.sap.sse.security.shared.SocialUserAccount;
import com.sap.sse.security.shared.User;
import com.sap.sse.security.shared.UsernamePasswordAccount;
import com.sap.sse.security.shared.Account.AccountType;
import com.sap.sse.security.userstore.mongodb.CollectionNames;
@@ -36,6 +36,8 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory {
DBObject query = new BasicDBObject(FieldNames.User.NAME.name(), user.getName());
dbUser.put(FieldNames.User.NAME.name(), user.getName());
dbUser.put(FieldNames.User.EMAIL.name(), user.getEmail());
dbUser.put(FieldNames.User.EMAIL_VALIDATED.name(), user.isEmailValidated());
dbUser.put(FieldNames.User.VALIDATION_SECRET.name(), user.getValidationSecret());
dbUser.put(FieldNames.User.ACCOUNTS.name(), createAccountMapObject(user.getAllAccounts()));
dbUser.put(FieldNames.User.ROLES.name(), user.getRoles());
usersCollection.update(query, dbUser, /* upsrt */true, /* multi */false, WriteConcern.SAFE);
@@ -12,7 +12,6 @@ import org.osgi.framework.BundleContext;
import org.osgi.util.tracker.ServiceTracker;
import com.sap.sse.security.impl.Activator;
import com.sap.sse.security.shared.UserStore;
public abstract class AbstractUserStoreBasedRealm extends AuthorizingRealm {
private static final Logger logger = Logger.getLogger(AbstractUserStoreBasedRealm.class.getName());
@@ -39,7 +39,6 @@ import org.scribe.model.Verifier;
import org.scribe.oauth.OAuthService;
import com.sap.sse.security.shared.SocialUserAccount;
import com.sap.sse.security.shared.User;
import com.sap.sse.security.shared.UserManagementException;
// GWT has similar class.
@@ -11,7 +11,6 @@ import org.apache.shiro.mgt.SecurityManager;
import com.sap.sse.security.shared.MailException;
import com.sap.sse.security.shared.SocialUserAccount;
import com.sap.sse.security.shared.User;
import com.sap.sse.security.shared.UserManagementException;
public interface SecurityService {
@@ -35,11 +34,11 @@ public interface SecurityService {
void logout();
User createSimpleUser(String username, String email, String password) throws UserManagementException;
User createSimpleUser(String username, String email, String password) throws UserManagementException, MailException;
void updateSimpleUserPassword(String name, String oldPassword, String newPassword) throws UserManagementException, MailException;
void updateSimpleUserPassword(String name, String newPassword) throws UserManagementException, MailException;
void updateSimpleUserEmail(String username, String newEmail) throws UserManagementException;
void updateSimpleUserEmail(String username, String newEmail) throws UserManagementException, MailException;
User createSocialUser(String username, SocialUserAccount socialUserAccount) throws UserManagementException;
@@ -67,4 +66,18 @@ public interface SecurityService {
void sendMail(String username, String subject, String body) throws MailException;
/**
* Checks whether <code>password</code> is the correct password for the user identified by <code>username</code>
*
* @throws UserManagementException
* in a user by that name does not exist
*/
boolean checkPassword(String username, String password) throws UserManagementException;
/**
* Generates a new random password for the user identified by <code>username</code> and sends it
* to the user's e-mail address.
*/
void resetPassword(String username) throws UserManagementException, MailException;
}
@@ -5,6 +5,7 @@ import java.util.Collection;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Random;
import java.util.Set;
import java.util.UUID;
import java.util.logging.Level;
@@ -64,9 +65,7 @@ import com.sap.sse.security.shared.Account.AccountType;
import com.sap.sse.security.shared.DefaultRoles;
import com.sap.sse.security.shared.MailException;
import com.sap.sse.security.shared.SocialUserAccount;
import com.sap.sse.security.shared.User;
import com.sap.sse.security.shared.UserManagementException;
import com.sap.sse.security.shared.UserStore;
import com.sap.sse.security.shared.UsernamePasswordAccount;
public class SecurityServiceImpl extends RemoteServiceServlet implements SecurityService {
@@ -101,7 +100,7 @@ public class SecurityServiceImpl extends RemoteServiceServlet implements Securit
createSimpleUser("admin", "nobody@sapsailing.com", "admin");
addRoleForUser("admin", DefaultRoles.ADMIN.getRolename());
addRoleForUser("admin", "moderator");
} catch (UserManagementException e) {
} catch (UserManagementException | MailException e) {
logger.log(Level.SEVERE, "Exception while creating default admin user", e);
}
}
@@ -158,6 +157,16 @@ public class SecurityServiceImpl extends RemoteServiceServlet implements Securit
}
}
}
@Override
public void resetPassword(String username) throws UserManagementException, MailException {
byte[] randomBytes = new byte[16];
new Random().nextBytes(randomBytes);
final String newPassword = new Sha256Hash(randomBytes).toBase64();
updateSimpleUserPassword(username, newPassword);
sendMail(username, "Password Reset", "Your new password for your username "+username+
" is \n "+newPassword+"\nPlease change after next sign-in.");
}
@Override
public SecurityManager getSecurityManager() {
@@ -201,7 +210,7 @@ public class SecurityServiceImpl extends RemoteServiceServlet implements Securit
}
@Override
public User createSimpleUser(String name, String email, String password) throws UserManagementException {
public User createSimpleUser(String name, String email, String password) throws UserManagementException, MailException {
if (store.getUserByName(name) != null) {
throw new UserManagementException(UserManagementException.USER_ALREADY_EXISTS);
}
@@ -214,46 +223,65 @@ public class SecurityServiceImpl extends RemoteServiceServlet implements Securit
Object salt = rng.nextBytes();
String hashedPasswordBase64 = hashPassword(password, salt);
UsernamePasswordAccount upa = new UsernamePasswordAccount(name, hashedPasswordBase64, salt);
return store.createUser(name, email, upa);
final User result = store.createUser(name, email, upa);
startEmailValidation(result);
return result;
}
@Override
public void updateSimpleUserPassword(String username, String oldPassword, String newPassword) throws UserManagementException, MailException {
public void updateSimpleUserPassword(String username, String newPassword) throws UserManagementException, MailException {
final User user = store.getUserByName(username);
if (user == null) {
throw new UserManagementException(UserManagementException.USER_DOES_NOT_EXIST);
}
if (!user.isEmailValidated()) {
throw new UserManagementException(UserManagementException.CANNOT_RESET_PASSWORD_WITHOUT_VALIDATED_EMAIL);
}
if (newPassword == null || newPassword.length() < 5) {
throw new UserManagementException(UserManagementException.PASSWORD_DOES_NOT_MEET_REQUIREMENTS);
}
final Subject subject = SecurityUtils.getSubject();
// for non-admins, check that the old password is correct
final UsernamePasswordAccount account = (UsernamePasswordAccount) user.getAccount(AccountType.USERNAME_PASSWORD);
if (!subject.hasRole(DefaultRoles.ADMIN.getRolename())) {
String hashedOldPassword = hashPassword(oldPassword, account.getSalt());
if (!hashedOldPassword.equals(account.getSaltedPassword())) {
throw new UserManagementException(UserManagementException.INVALID_CREDENTIALS);
}
}
RandomNumberGenerator rng = new SecureRandomNumberGenerator();
Object salt = rng.nextBytes();
String hashedPasswordBase64 = hashPassword(newPassword, salt);
account.setSalt(salt);
account.setSaltedPassword(hashedPasswordBase64);
store.updateUser(user);
sendMail(username, "Password Changed", "Somebody changed your password for your user named "+username+".\nIf that wasn't you, I'd be worried...");
}
@Override
public void updateSimpleUserEmail(String username, String newEmail) throws UserManagementException {
public boolean checkPassword(String username, String password) throws UserManagementException {
final User user = store.getUserByName(username);
if (user == null) {
throw new UserManagementException(UserManagementException.USER_DOES_NOT_EXIST);
}
final UsernamePasswordAccount account = (UsernamePasswordAccount) user.getAccount(AccountType.USERNAME_PASSWORD);
String hashedOldPassword = hashPassword(password, account.getSalt());
return hashedOldPassword.equals(account.getSaltedPassword());
}
@Override
public void updateSimpleUserEmail(String username, String newEmail) throws UserManagementException, MailException {
final User user = store.getUserByName(username);
if (user == null) {
throw new UserManagementException(UserManagementException.USER_DOES_NOT_EXIST);
}
user.setEmail(newEmail);
startEmailValidation(user);
store.updateUser(user);
}
/**
* {@link User#startEmailValidation() Triggers} e-mail validation for the <code>user</code> object and
* sends out a URL to the user's e-mail that has the validation secret ready for validation by clicking.
*/
private void startEmailValidation(User user) throws MailException {
String secret = user.startEmailValidation();
sendMail(user.getName(), "e-Mail Validation", "Please click on the link below to validate your e-mail address.\n "+
"http://127.0.0.1:8888/security/ui/EmailValidation.html?gwt.codesvr=127.0.0.1:9997&v="+secret);
}
protected String hashPassword(String password, Object salt) {
return new Sha256Hash(password, salt, 1024).toBase64();
}
@@ -0,0 +1,155 @@
package com.sap.sse.security;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import org.apache.shiro.crypto.hash.Sha256Hash;
import com.sap.sse.security.shared.Account;
import com.sap.sse.security.shared.Account.AccountType;
public class User {
private String name;
private String email;
/**
* When a new e-mail is set for the user, a validation process should be started.
* The validation generates a secret which is then put into a URL which is sent to
* the new e-mail address. When the user follows the URL, the URL parameter will be
* used to validate against the secret stored here. If the secret matches, the
* email address is {@link #emailValidated marked as validated}.
*/
private String validationSecret;
private boolean emailValidated;
private final Set<String> roles;
private final Map<AccountType, Account> accounts;
public User(String name, String email, Account... accounts) {
this(name, email, Arrays.asList(accounts));
}
public User(String name, String email, Collection<Account> accounts) {
this(name, email, /* is email validated */ false, /* validation secret */ null, accounts);
}
public User(String name, String email, Boolean emailValidated, String validationSecret,
Collection<Account> accounts) {
super();
this.name = name;
this.roles = new HashSet<>();
this.email = email;
this.validationSecret = validationSecret;
this.emailValidated = emailValidated;
this.accounts = new HashMap<>();
for (Account a : accounts) {
this.accounts.put(a.getAccountType(), a);
}
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Set<String> getRoles() {
return roles;
}
public void addRole(String role) {
roles.add(role);
}
public boolean hasRole(String role) {
return roles.contains(role);
}
public void removeRole(String role) {
roles.remove(role);
}
public Account getAccount(AccountType type) {
return accounts.get(type);
}
public void removeAccount(AccountType type) {
accounts.remove(type);
}
public Map<AccountType, Account> getAllAccounts() {
return accounts;
}
public String getEmail() {
return email;
}
/**
* Sets an e-mail address for this user. The address is considered not yet validated, therefore the
* {@link #emailValidated} flag is reset, and a new {@link #validationSecret} is generated and returned which
* can be used in a call to {@link #validate(String)} to validate the e-mail address.
*/
public String setEmail(String email) {
this.email = email;
return startEmailValidation();
}
/**
* The email address is set to not yet validated by resetting the
* {@link #emailValidated} flag. A new {@link #validationSecret} is generated and returned which
* can be used in a call to {@link #validate(String)} to validate the e-mail address.
*/
public String startEmailValidation() {
final byte[] bytes1 = new byte[64];
new Random().nextBytes(bytes1);
final byte[] bytes2 = new byte[64];
new Random().nextBytes(bytes2);
validationSecret = new Sha256Hash(bytes1, bytes2, 1024).toBase64();
emailValidated = false;
return validationSecret;
}
/**
* If the <code>validationSecret</code> passed matches {@link #validationSecret}, the e-mail is
* {@link #emailValidated marked as validated}, and <code>true</code> is returned. Otherwise, the validation secret
* on this user remains in place, and the e-mail address is not marked as validated.
*/
public boolean validate(final String validationSecret) {
final boolean result;
if (this.validationSecret.equals(validationSecret)) {
emailValidated = true;
this.validationSecret = null;
result = true;
} else {
result = false;
}
return result;
}
public boolean isEmailValidated() {
return emailValidated;
}
@Override
public String toString() {
return "User [name=" + name + ", email=" + email + (isEmailValidated()?" (validated)":")")+", roles="
+ Arrays.toString(roles.toArray(new String[roles.size()])) + ", accounts="
+ Arrays.toString(accounts.keySet().toArray(new AccountType[accounts.size()])) + "]";
}
public String getValidationSecret() {
return validationSecret;
}
}
@@ -1,9 +1,12 @@
package com.sap.sse.security.shared;
package com.sap.sse.security;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import com.sap.sse.security.shared.Account;
import com.sap.sse.security.shared.UserManagementException;
public interface UserStore {
String getName();
@@ -13,7 +13,6 @@ import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import com.sap.sse.security.shared.User;
import com.sap.sse.security.shared.UserManagementException;
import com.sap.sse.security.shared.UsernamePasswordAccount;
import com.sap.sse.security.shared.Account.AccountType;
@@ -14,8 +14,8 @@ import org.osgi.util.tracker.ServiceTracker;
import com.sap.sse.security.SecurityService;
import com.sap.sse.security.SecurityServiceImpl;
import com.sap.sse.security.UserStore;
import com.sap.sse.security.UsernamePasswordRealm;
import com.sap.sse.security.shared.UserStore;
public class Activator implements BundleActivator {
private static final Logger logger = Logger.getLogger(Activator.class.getName());
@@ -1,87 +0,0 @@
package com.sap.sse.security.shared;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import com.sap.sse.security.shared.Account.AccountType;
public class User {
private String name;
private String email;
private final Set<String> roles;
private final Map<AccountType, Account> accounts;
public User(String name, String email, Account... accounts) {
this(name, email, Arrays.asList(accounts));
}
public User(String name, String email, Collection<Account> accounts) {
super();
this.name = name;
this.roles = new HashSet<>();
this.email = email;
this.accounts = new HashMap<>();
for (Account a : accounts) {
this.accounts.put(a.getAccountType(), a);
}
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Set<String> getRoles() {
return roles;
}
public void addRole(String role) {
roles.add(role);
}
public boolean hasRole(String role) {
return roles.contains(role);
}
public void removeRole(String role) {
roles.remove(role);
}
public Account getAccount(AccountType type) {
return accounts.get(type);
}
public void removeAccount(AccountType type) {
accounts.remove(type);
}
public Map<AccountType, Account> getAllAccounts() {
return accounts;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
@Override
public String toString() {
return "User [name=" + name + ", email=" + email + ", roles="
+ Arrays.toString(roles.toArray(new String[roles.size()])) + ", accounts="
+ Arrays.toString(accounts.keySet().toArray(new AccountType[accounts.size()])) + "]";
}
}
@@ -11,6 +11,7 @@ public class UserManagementException extends Exception implements Serializable {
public static final String INVALID_CREDENTIALS = "Invalid Credentials";
public static final String PASSWORD_DOES_NOT_MEET_REQUIREMENTS = "Password does not meet requirements";
public static final String USERNAME_DOES_NOT_MEET_REQUIREMENTS = "Username does not meet requirements";
public static final String CANNOT_RESET_PASSWORD_WITHOUT_VALIDATED_EMAIL = "Cannot reset password without validated e-mail address";
public UserManagementException() {} // for GWT serialization