branch develop updated (b6a809a -> 166496f)
This is an automated email from the git hooks/post-receive script. New change to branch develop in repository coselmar. See http://git.codelutin.com/coselmar.git from b6a809a Fix way to pass login/password during login Add constant as config for services URL new b837a81 #6014 add service for modify user & some authorization management new c8a14d8 #6014 add way to modify user (as admin and as user for its own account) in UI new 166496f Merge branch 'feature/6014-modify-user' into develop The 3 revisions listed above as "new" are entirely new to this repository and will be described in separate emails. The revisions listed as "adds" were already present in the repository and have only been added to this reference. Detailed log of new commits: commit 166496f3b3eb0da9174658847cf5e4f4571a9e35 Merge: b6a809a c8a14d8 Author: Yannick Martel <martel@©odelutin.com> Date: Tue Nov 25 17:59:52 2014 +0100 Merge branch 'feature/6014-modify-user' into develop commit c8a14d839be2ed6d81bbbc07634e89fe940616a0 Author: Yannick Martel <martel@©odelutin.com> Date: Tue Nov 25 17:59:30 2014 +0100 #6014 add way to modify user (as admin and as user for its own account) in UI commit b837a8107a542acdb709c6a7d3326777f8dd7ebd Author: Yannick Martel <martel@©odelutin.com> Date: Tue Nov 25 11:48:43 2014 +0100 #6014 add service for modify user & some authorization management Summary of changes: .../java/fr/ifremer/coselmar/beans/UserBean.java | 9 + .../fr/ifremer/coselmar/beans/UserWebToken.java | 88 ++++++++++ .../services/CoselmarWebServiceSupport.java | 41 +++++ ...alException.java => UnauthorizedException.java} | 4 +- .../coselmar/services/v1/UsersWebService.java | 194 +++++++++++++++++++-- coselmar-rest/src/main/resources/mapping | 3 +- coselmar-ui/src/main/webapp/index.html | 4 +- .../src/main/webapp/js/coselmar-controllers.js | 30 +++- .../src/main/webapp/js/coselmar-user-services.js | 26 ++- coselmar-ui/src/main/webapp/js/coselmar.js | 2 +- .../src/main/webapp/views/users/edituser.html | 94 ++++++++++ .../src/main/webapp/views/users/newuser.html | 61 +------ coselmar-ui/src/main/webapp/views/users/user.html | 14 +- coselmar-ui/src/main/webapp/views/users/users.html | 6 +- 14 files changed, 485 insertions(+), 91 deletions(-) create mode 100644 coselmar-rest/src/main/java/fr/ifremer/coselmar/beans/UserWebToken.java copy coselmar-rest/src/main/java/fr/ifremer/coselmar/services/errors/{InvalidCredentialException.java => UnauthorizedException.java} (52%) create mode 100644 coselmar-ui/src/main/webapp/views/users/edituser.html -- To stop receiving notification emails like this one, please contact codelutin.com SCM administrator <admin+scm@codelutin.com>.
This is an automated email from the git hooks/post-receive script. New commit to branch develop in repository coselmar. See http://git.codelutin.com/coselmar.git commit b837a8107a542acdb709c6a7d3326777f8dd7ebd Author: Yannick Martel <martel@©odelutin.com> Date: Tue Nov 25 11:48:43 2014 +0100 #6014 add service for modify user & some authorization management --- .../java/fr/ifremer/coselmar/beans/UserBean.java | 9 ++ .../fr/ifremer/coselmar/beans/UserWebToken.java | 88 ++++++++++++ .../services/CoselmarWebServiceSupport.java | 41 ++++++ .../services/errors/UnauthorizedException.java | 11 ++ .../coselmar/services/v1/UsersWebService.java | 159 +++++++++++++++++++-- coselmar-rest/src/main/resources/mapping | 1 + coselmar-ui/src/main/webapp/index.html | 4 +- coselmar-ui/src/main/webapp/js/coselmar.js | 2 +- coselmar-ui/src/main/webapp/views/users/users.html | 2 +- 9 files changed, 302 insertions(+), 15 deletions(-) diff --git a/coselmar-rest/src/main/java/fr/ifremer/coselmar/beans/UserBean.java b/coselmar-rest/src/main/java/fr/ifremer/coselmar/beans/UserBean.java index 4bc9260..c0b976e 100644 --- a/coselmar-rest/src/main/java/fr/ifremer/coselmar/beans/UserBean.java +++ b/coselmar-rest/src/main/java/fr/ifremer/coselmar/beans/UserBean.java @@ -15,6 +15,7 @@ public class UserBean implements Serializable { protected String qualification; protected String organization; protected String password; + protected String newPassword; public UserBean(String id, String firstName, String name, String mail, String role, String qualification, String organization) { this.id = id; @@ -89,4 +90,12 @@ public class UserBean implements Serializable { public void setPassword(String password) { this.password = password; } + + public String getNewPassword() { + return newPassword; + } + + public void setNewPassword(String newPassword) { + this.newPassword = newPassword; + } } diff --git a/coselmar-rest/src/main/java/fr/ifremer/coselmar/beans/UserWebToken.java b/coselmar-rest/src/main/java/fr/ifremer/coselmar/beans/UserWebToken.java new file mode 100644 index 0000000..d2f273d --- /dev/null +++ b/coselmar-rest/src/main/java/fr/ifremer/coselmar/beans/UserWebToken.java @@ -0,0 +1,88 @@ +package fr.ifremer.coselmar.beans; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + +/** + * @author ymartel <martel@codelutin.com> + */ +public class UserWebToken implements Serializable { + + public static final String CLAIMS_USER_ID = "userId"; + public static final String CLAIMS_FIRST_NAME = "firstName"; + public static final String CLAIMS_LAST_NAME = "lastName"; + public static final String CLAIMS_ROLE = "role"; + + protected String userId; + protected String firstName; + protected String lastName; + protected String role; + + public UserWebToken(String userId, String firstName, String lastName, String role) { + this.userId = userId; + this.firstName = firstName; + this.lastName = lastName; + this.role = role; + } + + public UserWebToken(Map<String, Object> claims) { + this.userId = (String) claims.get(CLAIMS_USER_ID); + this.firstName = (String) claims.get(CLAIMS_FIRST_NAME); + this.lastName = (String) claims.get(CLAIMS_LAST_NAME); + this.role = ((String) claims.get(CLAIMS_ROLE)).toUpperCase(); + } + + public String getUserId() { + return userId; + } + + public void setUserId(String userId) { + this.userId = userId; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public String getRole() { + return role; + } + + public void setRole(String role) { + this.role = role; + } + + public Map<String, Object> toJwtClaims() { + + Map<String, Object> claims = new HashMap<>(); + claims.put(CLAIMS_USER_ID, this.userId); + claims.put(CLAIMS_FIRST_NAME, this.firstName); + claims.put(CLAIMS_LAST_NAME, this.lastName); + claims.put(CLAIMS_ROLE, this.role); + return claims; + } + + public static Map<String, Object> toJwtClaims(String userId, String firstName, String lastName, String role) { + + Map<String, Object> claims = new HashMap<>(); + claims.put(CLAIMS_USER_ID, userId); + claims.put(CLAIMS_FIRST_NAME, firstName); + claims.put(CLAIMS_LAST_NAME, lastName); + claims.put(CLAIMS_ROLE, role.toUpperCase()); + return claims; + } + +} diff --git a/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/CoselmarWebServiceSupport.java b/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/CoselmarWebServiceSupport.java index 8e24e97..5213d75 100644 --- a/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/CoselmarWebServiceSupport.java +++ b/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/CoselmarWebServiceSupport.java @@ -24,14 +24,26 @@ package fr.ifremer.coselmar.services; * #L% */ +import java.io.IOException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.SignatureException; import java.util.Date; import java.util.Locale; +import java.util.Map; +import com.auth0.jwt.JWTVerifier; +import fr.ifremer.coselmar.beans.UserWebToken; import fr.ifremer.coselmar.persistence.CoselmarPersistenceContext; import fr.ifremer.coselmar.persistence.entity.CoselmarUserTopiaDao; import fr.ifremer.coselmar.persistence.entity.DocumentTopiaDao; import fr.ifremer.coselmar.services.config.CoselmarServicesConfig; +import fr.ifremer.coselmar.services.errors.InvalidCredentialException; import fr.ifremer.coselmar.services.v1.DocumentsWebService; +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.lang.StringUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.debux.webmotion.server.WebMotionController; import org.debux.webmotion.server.call.HttpContext; @@ -40,6 +52,8 @@ import org.debux.webmotion.server.call.HttpContext; */ public abstract class CoselmarWebServiceSupport extends WebMotionController implements CoselmarService { + private static final Log log = LogFactory.getLog(CoselmarWebServiceSupport.class); + private CoselmarServicesContext servicesContext; @Override @@ -106,4 +120,31 @@ public abstract class CoselmarWebServiceSupport extends WebMotionController impl getPersistenceContext().rollback(); } + protected UserWebToken checkAuthentication(String authorization) throws InvalidCredentialException { + try { + + String webSecurityKey = getServicesContext().getCoselmarServicesConfig().getWebSecurityKey(); + JWTVerifier jwtVerifier = new JWTVerifier(Base64.encodeBase64String(webSecurityKey.getBytes("utf8")), "audience"); + + String token = StringUtils.replace(authorization, "Bearer ", ""); + Map<String, Object> claims = jwtVerifier.verify(token); + UserWebToken userWebToken = new UserWebToken(claims); + return userWebToken; + + } catch (NoSuchAlgorithmException|InvalidKeyException|IOException e) { + // This should not happened or this is really exceptional ! + if (log.isErrorEnabled()) { + log.error("Error during JWT verification : wrong Algorithm !", e); + } + throw new CoselmarTechnicalException(e); + + } catch (SignatureException e) { + // Invalid Signature ! It's a Fake ! + if (log.isErrorEnabled()) { + log.error("Error during JWT verification : bad signature!", e); + } + throw new InvalidCredentialException("Error with signature"); + } + + } } diff --git a/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/errors/UnauthorizedException.java b/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/errors/UnauthorizedException.java new file mode 100644 index 0000000..f67b509 --- /dev/null +++ b/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/errors/UnauthorizedException.java @@ -0,0 +1,11 @@ +package fr.ifremer.coselmar.services.errors; + +/** + * @author ymartel (martel@codelutin.com) + */ +public class UnauthorizedException extends Exception { + + public UnauthorizedException(String message) { + super(message); + } +} diff --git a/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/v1/UsersWebService.java b/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/v1/UsersWebService.java index 6e99a34..e837cbd 100644 --- a/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/v1/UsersWebService.java +++ b/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/v1/UsersWebService.java @@ -1,8 +1,8 @@ package fr.ifremer.coselmar.services.v1; import java.io.StringWriter; +import java.security.InvalidParameterException; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; @@ -16,6 +16,7 @@ import com.github.mustachejava.MustacheFactory; import com.google.common.base.Preconditions; import fr.ifremer.coselmar.beans.UserAccountCreatedMail; import fr.ifremer.coselmar.beans.UserBean; +import fr.ifremer.coselmar.beans.UserWebToken; import fr.ifremer.coselmar.converter.BeanEntityConverter; import fr.ifremer.coselmar.persistence.entity.CoselmarUser; import fr.ifremer.coselmar.persistence.entity.CoselmarUserRole; @@ -23,6 +24,7 @@ import fr.ifremer.coselmar.services.CoselmarTechnicalException; import fr.ifremer.coselmar.services.CoselmarWebServiceSupport; import fr.ifremer.coselmar.services.config.CoselmarServicesConfig; import fr.ifremer.coselmar.services.errors.InvalidCredentialException; +import fr.ifremer.coselmar.services.errors.UnauthorizedException; import org.apache.commons.io.Charsets; import org.apache.commons.lang3.StringUtils; import org.apache.commons.logging.Log; @@ -30,6 +32,7 @@ import org.apache.commons.mail.Email; import org.apache.commons.mail.EmailException; import org.apache.commons.mail.SimpleEmail; import org.debux.webmotion.server.render.Render; +import org.nuiton.topia.persistence.TopiaNoResultException; import static org.apache.commons.logging.LogFactory.getLog; @@ -40,7 +43,21 @@ public class UsersWebService extends CoselmarWebServiceSupport { private static final Log log = getLog(UsersWebService.class); - public UserBean getUser(String userId) { + public UserBean getUser(String userId) throws InvalidCredentialException, UnauthorizedException, TopiaNoResultException { + + // Check authentication + String authorization = getContext().getHeader("Authorization"); + UserWebToken userWebToken = checkAuthentication(authorization); + + // Who is allowed here ? Admin and user himself + if (!StringUtils.equals(userWebToken.getRole(), CoselmarUserRole.ADMIN.name()) + && !StringUtils.equals(userWebToken.getUserId(), userId)) { + if (log.isDebugEnabled()) { + String message = String.format("A non admin user try to see account details with shortId '%s'", userId); + log.debug(message); + } + throw new UnauthorizedException("Not allowed to see user details"); + } // reconstitute full id String fullId = CoselmarUser.class.getCanonicalName() + "_" + userId; @@ -69,7 +86,7 @@ public class UsersWebService extends CoselmarWebServiceSupport { return result; } - public void addUser(UserBean user) { + public void addUser(UserBean user) throws InvalidParameterException { Preconditions.checkNotNull(user); CoselmarUser userEntity = getCoselmarUserDao().create(); @@ -77,6 +94,7 @@ public class UsersWebService extends CoselmarWebServiceSupport { userEntity.setFirstname(user.getFirstName()); userEntity.setName(user.getName()); String mail = getCleanMail(user.getMail()); + checkMailUnicity(mail, null); userEntity.setMail(mail); userEntity.setRole(CoselmarUserRole.valueOf(user.getRole().toUpperCase())); userEntity.setQualification(user.getQualification()); @@ -103,8 +121,90 @@ public class UsersWebService extends CoselmarWebServiceSupport { sendMail(userAccountCreatedMail); } - public void changePassword() { - //TODO ymartel + public void modifyUser(UserBean user) throws InvalidCredentialException, UnauthorizedException, InvalidParameterException, TopiaNoResultException { + + // Check authentication + String authorization = getContext().getHeader("Authorization"); + UserWebToken userWebToken = checkAuthentication(authorization); + + boolean isAdmin = StringUtils.equals(userWebToken.getRole(), CoselmarUserRole.ADMIN.name()); + + String userId = user.getId(); + if (StringUtils.isBlank(userId)) { + throw new InvalidParameterException("User.id is mandaotry"); + } + + // Admin does not need to give password, he should not know it anyway ! + if (StringUtils.isBlank(user.getPassword()) && !isAdmin) { + throw new InvalidParameterException("User.password is mandaotry"); + } + + // Who is allowed here ? Admin and user himself only + if (!isAdmin && !StringUtils.equals(userWebToken.getUserId(), userId)) { + if (log.isDebugEnabled()) { + String message = String.format("A non admin user try to modify account details with shortId '%s'", userId); + log.debug(message); + } + throw new UnauthorizedException("Not allowed to modify user details"); + } + + // Ok, now, retrieve this user + String fullId = CoselmarUser.class.getCanonicalName() + + getPersistenceContext().getTopiaIdFactory().getSeparator() + userId; + CoselmarUser coselmarUser = getCoselmarUserDao().forTopiaIdEquals(fullId).findAny(); + + // Ok, let's start the user update ! + + // start with mail : should be unique + String mail = user.getMail(); + if (StringUtils.isNotBlank(mail)) { + checkMailUnicity(mail, fullId); + coselmarUser.setMail(mail); + } + + String firstName = user.getFirstName(); + if (StringUtils.isNotBlank(firstName)) { + coselmarUser.setFirstname(firstName); + } else { + user.setOrganization(coselmarUser.getFirstname()); + } + + String userName = user.getName(); + if (StringUtils.isNotBlank(userName)) { + coselmarUser.setName(userName); + } else { + // only in case of mail need + user.setName(coselmarUser.getName()); + } + + String userRole = user.getRole(); + if (StringUtils.isNotBlank(userRole)) { + coselmarUser.setRole(CoselmarUserRole.valueOf(userName.toUpperCase())); + } + + String organization = user.getOrganization(); + if (StringUtils.isNotBlank(organization)) { + coselmarUser.setOrganization(organization); + } + + String newPassword = user.getNewPassword(); + if (StringUtils.isNotBlank(newPassword)) { + String salt = getServicesContext().generateSalt(); + String encodedPassword = getServicesContext().encodePassword(salt, newPassword); + coselmarUser.setSalt(salt); + coselmarUser.setPassword(encodedPassword); + + //if it is a modification by Admin, send new mail to user + if (isAdmin) { + UserAccountCreatedMail userAccountCreatedMail = new UserAccountCreatedMail(getServicesContext().getLocale()); + userAccountCreatedMail.setUser(user); + userAccountCreatedMail.setPassword(newPassword); + userAccountCreatedMail.setTo(coselmarUser.getMail()); + sendMail(userAccountCreatedMail); + } + } + + commit(); } public Render login(String mail, String password) throws InvalidCredentialException { @@ -125,20 +225,31 @@ public class UsersWebService extends CoselmarWebServiceSupport { JWTSigner.Options signerOption = new JWTSigner.Options(); signerOption.setAlgorithm(Algorithm.HS512); - Map<String, Object> claims = new HashMap<>(); String userTopiaId = user.getTopiaId(); String shortId = getPersistenceContext().getTopiaIdFactory().getRandomPart(userTopiaId); - claims.put("userId", shortId); - claims.put("userFirstName", user.getFirstname()); - claims.put("userName", user.getName()); - claims.put("userRole", user.getRole()); + Map<String, Object> claims = UserWebToken.toJwtClaims(shortId, user.getFirstname(), user.getName(), user.getRole().name()); String webToken = jwtSigner.sign(claims, signerOption); return renderJSON("jwt", webToken); } - public void deleteUser(String userId) { + public void deleteUser(String userId) throws InvalidCredentialException, UnauthorizedException { + + // Check authentication + String authorization = getContext().getHeader("Authorization"); + UserWebToken userWebToken = checkAuthentication(authorization); + + boolean isAdmin = StringUtils.equals(userWebToken.getRole(), CoselmarUserRole.ADMIN.name()); + + // Only admin is authorized to do this + if (!isAdmin) { + if (log.isDebugEnabled()) { + String message = String.format("A non admin user try to delete account with shortId '%s'", userId); + log.debug(message); + } + throw new UnauthorizedException("Not allowed to delete user"); + } // reconstitute full id String fullId = CoselmarUser.class.getCanonicalName() + "_" + userId; @@ -150,6 +261,32 @@ public class UsersWebService extends CoselmarWebServiceSupport { } ///////////////////////////////////////////// + ///////////// Internal Part ///////// + ///////////////////////////////////////////// + + /** + * Check if the mail is already used by an other user + * @param mail : simply mail to check + * @param userId : the current user#id : this parameter is needed to exclude from the search this user, cause it could already have this mail + * @throws InvalidParameterException if the mail is already used. + */ + protected void checkMailUnicity(String mail, String userId) throws InvalidParameterException { + boolean mailAlreadyUsed; + + if (StringUtils.isNotBlank(userId)) { + mailAlreadyUsed = getCoselmarUserDao().forMailEquals(mail).addNotEquals(CoselmarUser.PROPERTY_TOPIA_ID, userId).exists(); + + } else { + mailAlreadyUsed = getCoselmarUserDao().forMailEquals(mail).exists(); + } + + if (mailAlreadyUsed) { + String msg = String.format("mail '%s' is already used", mail); + throw new InvalidParameterException(msg); + } + } + + ///////////////////////////////////////////// /////////////// MAIL PART /////////////// ///////////////////////////////////////////// diff --git a/coselmar-rest/src/main/resources/mapping b/coselmar-rest/src/main/resources/mapping index 9c9cfcf..89a999e 100644 --- a/coselmar-rest/src/main/resources/mapping +++ b/coselmar-rest/src/main/resources/mapping @@ -38,6 +38,7 @@ DELETE /v1/documents/{documentId} DocumentsWebService.deleteDocume GET /v1/users UsersWebService.getUsers GET /v1/users/{userId} UsersWebService.getUser +PUT /v1/users/{userId} UsersWebService.modifyUser POST /v1/users UsersWebService.addUser DELETE /v1/users/{userId} UsersWebService.deleteUser POST /v1/users/login UsersWebService.login diff --git a/coselmar-ui/src/main/webapp/index.html b/coselmar-ui/src/main/webapp/index.html index e6f8235..d9febf5 100644 --- a/coselmar-ui/src/main/webapp/index.html +++ b/coselmar-ui/src/main/webapp/index.html @@ -63,7 +63,7 @@ <nav class="hidden-xs"> <ul class="nav navbar-nav"> <a href="#" role="button" class="navbar-brand">Coselmar Traceability</a> - <li class="dropdown" ng-if="currentUser.userRole == 'ADMIN'"> + <li class="dropdown" ng-if="currentUser.role == 'ADMIN'"> <a href="#/users" class="dropdown-toggle">User</a> </li> <li class="dropdown" ng-if="currentUser"> @@ -95,7 +95,7 @@ </div> <form class="navbar-form navbar-right" role="form" ng-if="currentUser"> - <div class="form-group">{{currentUser.userFirstName}} {{currentUser.userName}}</div> + <div class="form-group">{{currentUser.firstName}} {{currentUser.lastName}}</div> <button type="submit" class="btn btn-danger" ng-click="logout()">Logout</button> </form> </nav> diff --git a/coselmar-ui/src/main/webapp/js/coselmar.js b/coselmar-ui/src/main/webapp/js/coselmar.js index 409647b..6a14cca 100644 --- a/coselmar-ui/src/main/webapp/js/coselmar.js +++ b/coselmar-ui/src/main/webapp/js/coselmar.js @@ -49,7 +49,7 @@ coselmarApp.config(['$routeProvider', function($routeProvider) { }).when('/users/:userId', { controller : 'UserViewCtrl', - templateUrl : 'views/users/users.html' + templateUrl : 'views/users/user.html' }).otherwise({ redirectTo: '/', diff --git a/coselmar-ui/src/main/webapp/views/users/users.html b/coselmar-ui/src/main/webapp/views/users/users.html index 8f2c904..0f15a7d 100644 --- a/coselmar-ui/src/main/webapp/views/users/users.html +++ b/coselmar-ui/src/main/webapp/views/users/users.html @@ -30,7 +30,7 @@ <th></th> </tr> <tr ng-repeat="user in users"> - <td><a href="#/user/{{user.id}}">{{user.firstname}} {{user.name}}</a></td> + <td><a href="#/users/{{user.id}}">{{user.firstname}} {{user.name}}</a></td> <td>{{user.mail}}</td> <td>{{user.qualification}}</td> <td>{{user.role}}</td> -- To stop receiving notification emails like this one, please contact codelutin.com SCM administrator <admin+scm@codelutin.com>.
This is an automated email from the git hooks/post-receive script. New commit to branch develop in repository coselmar. See http://git.codelutin.com/coselmar.git commit c8a14d839be2ed6d81bbbc07634e89fe940616a0 Author: Yannick Martel <martel@©odelutin.com> Date: Tue Nov 25 17:59:30 2014 +0100 #6014 add way to modify user (as admin and as user for its own account) in UI --- .../coselmar/services/v1/UsersWebService.java | 47 ++++++++--- coselmar-rest/src/main/resources/mapping | 4 +- coselmar-ui/src/main/webapp/index.html | 2 +- .../src/main/webapp/js/coselmar-controllers.js | 30 ++++++- .../src/main/webapp/js/coselmar-user-services.js | 26 +++++- .../src/main/webapp/views/users/edituser.html | 94 ++++++++++++++++++++++ .../src/main/webapp/views/users/newuser.html | 61 +------------- coselmar-ui/src/main/webapp/views/users/user.html | 14 +++- coselmar-ui/src/main/webapp/views/users/users.html | 4 +- 9 files changed, 200 insertions(+), 82 deletions(-) diff --git a/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/v1/UsersWebService.java b/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/v1/UsersWebService.java index e837cbd..ecd02d2 100644 --- a/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/v1/UsersWebService.java +++ b/coselmar-rest/src/main/java/fr/ifremer/coselmar/services/v1/UsersWebService.java @@ -98,6 +98,7 @@ public class UsersWebService extends CoselmarWebServiceSupport { userEntity.setMail(mail); userEntity.setRole(CoselmarUserRole.valueOf(user.getRole().toUpperCase())); userEntity.setQualification(user.getQualification()); + userEntity.setOrganization(user.getOrganization()); String password = user.getPassword(); if (StringUtils.isBlank(password)) { @@ -131,12 +132,12 @@ public class UsersWebService extends CoselmarWebServiceSupport { String userId = user.getId(); if (StringUtils.isBlank(userId)) { - throw new InvalidParameterException("User.id is mandaotry"); + throw new InvalidParameterException("User.id is mandatory"); } // Admin does not need to give password, he should not know it anyway ! if (StringUtils.isBlank(user.getPassword()) && !isAdmin) { - throw new InvalidParameterException("User.password is mandaotry"); + throw new InvalidParameterException("User.password is mandatory"); } // Who is allowed here ? Admin and user himself only @@ -153,7 +154,12 @@ public class UsersWebService extends CoselmarWebServiceSupport { getPersistenceContext().getTopiaIdFactory().getSeparator() + userId; CoselmarUser coselmarUser = getCoselmarUserDao().forTopiaIdEquals(fullId).findAny(); - // Ok, let's start the user update ! + // Last check : the password + if (!isAdmin) { + checkPassword(coselmarUser.getPassword(), coselmarUser.getSalt(), user.getPassword()); + } + + // Ok, now let's start the user update ! // start with mail : should be unique String mail = user.getMail(); @@ -166,7 +172,7 @@ public class UsersWebService extends CoselmarWebServiceSupport { if (StringUtils.isNotBlank(firstName)) { coselmarUser.setFirstname(firstName); } else { - user.setOrganization(coselmarUser.getFirstname()); + user.setFirstName(coselmarUser.getFirstname()); } String userName = user.getName(); @@ -178,8 +184,8 @@ public class UsersWebService extends CoselmarWebServiceSupport { } String userRole = user.getRole(); - if (StringUtils.isNotBlank(userRole)) { - coselmarUser.setRole(CoselmarUserRole.valueOf(userName.toUpperCase())); + if (StringUtils.isNotBlank(userRole) && isAdmin) { // only admin can update UserRole + coselmarUser.setRole(CoselmarUserRole.valueOf(userRole.toUpperCase())); } String organization = user.getOrganization(); @@ -187,6 +193,11 @@ public class UsersWebService extends CoselmarWebServiceSupport { coselmarUser.setOrganization(organization); } + String qualification = user.getQualification(); + if (StringUtils.isNotBlank(qualification)) { + coselmarUser.setQualification(qualification); + } + String newPassword = user.getNewPassword(); if (StringUtils.isNotBlank(newPassword)) { String salt = getServicesContext().generateSalt(); @@ -213,12 +224,7 @@ public class UsersWebService extends CoselmarWebServiceSupport { CoselmarUser user = getCoselmarUserDao().forMailEquals(getCleanMail(mail)).findAny(); String salt = user.getSalt(); - String encodedPassword = getServicesContext().encodePassword(salt, password); - - String userPassword = user.getPassword(); - if (!encodedPassword.equals(userPassword)){ - throw new InvalidCredentialException("Invalid password given"); - } + checkPassword(user.getPassword(), salt, password); // return a Json Web Token for authentication JWTSigner jwtSigner = new JWTSigner(getCoselmarServicesConfig().getWebSecurityKey()); @@ -286,6 +292,23 @@ public class UsersWebService extends CoselmarWebServiceSupport { } } + /** + * Check that the password is the same : encode the one given with the salt, + * and compare with the user password from database + * + * @param currentPassword : password from database + * @param salt : salt use to encode database password + * @param password : given password we want to check + * @throws InvalidCredentialException if the given password is not the same as one from database + */ + protected void checkPassword(String currentPassword, String salt, String password) throws InvalidCredentialException { + String encodedPassword = getServicesContext().encodePassword(salt, password); + + if (!encodedPassword.equals(currentPassword)){ + throw new InvalidCredentialException("Invalid password given"); + } + } + ///////////////////////////////////////////// /////////////// MAIL PART /////////////// ///////////////////////////////////////////// diff --git a/coselmar-rest/src/main/resources/mapping b/coselmar-rest/src/main/resources/mapping index 89a999e..27b2522 100644 --- a/coselmar-rest/src/main/resources/mapping +++ b/coselmar-rest/src/main/resources/mapping @@ -38,8 +38,8 @@ DELETE /v1/documents/{documentId} DocumentsWebService.deleteDocume GET /v1/users UsersWebService.getUsers GET /v1/users/{userId} UsersWebService.getUser -PUT /v1/users/{userId} UsersWebService.modifyUser +POST /v1/users/login UsersWebService.login +POST /v1/users/{userId} UsersWebService.modifyUser POST /v1/users UsersWebService.addUser DELETE /v1/users/{userId} UsersWebService.deleteUser -POST /v1/users/login UsersWebService.login diff --git a/coselmar-ui/src/main/webapp/index.html b/coselmar-ui/src/main/webapp/index.html index d9febf5..968466f 100644 --- a/coselmar-ui/src/main/webapp/index.html +++ b/coselmar-ui/src/main/webapp/index.html @@ -95,7 +95,7 @@ </div> <form class="navbar-form navbar-right" role="form" ng-if="currentUser"> - <div class="form-group">{{currentUser.firstName}} {{currentUser.lastName}}</div> + <div class="form-group">{{currentUser.firstName}} {{currentUser.lastName}} <a href="#/users/{{currentUser.userId}}?edit" class="glyphicon glyphicon-pencil"></a></div> <button type="submit" class="btn btn-danger" ng-click="logout()">Logout</button> </form> </nav> diff --git a/coselmar-ui/src/main/webapp/js/coselmar-controllers.js b/coselmar-ui/src/main/webapp/js/coselmar-controllers.js index 0665085..e31d174 100644 --- a/coselmar-ui/src/main/webapp/js/coselmar-controllers.js +++ b/coselmar-ui/src/main/webapp/js/coselmar-controllers.js @@ -185,10 +185,10 @@ coselmarControllers.controller("NewUserCtrl", ['$scope', '$route', '$location', $scope.user = {'role' : 'expert'}; - $scope.createNewUser = function(){ + $scope.saveUser = function(){ // Call service to create a new user - userService.createUser($scope.user, function() { + userService.saveUser($scope.user, function() { $location.path("/users"); },function(error) { //TODO ymartel 20141118 : deal with error.status or statusText @@ -205,6 +205,11 @@ coselmarControllers.controller("NewUserCtrl", ['$scope', '$route', '$location', coselmarControllers.controller("UserViewCtrl", ['$scope', '$route', '$location', 'userService', '$routeParams', function($scope, $route, $location, userService, $routeParams) { + $scope.editMode = $routeParams.edit; + if ($scope.editMode) { + $scope.editPassword = true; + } + userService.getUser($routeParams.userId, $scope); $scope.deleteUser = function(userId){ @@ -215,4 +220,25 @@ coselmarControllers.controller("UserViewCtrl", }); }; + + $scope.modifyUser = function(){ + $location.search("edit"); + }; + + $scope.saveUser = function(){ + + // Call service to create a new user + userService.saveUser($scope.user, + function() { + // On success, reload the view page + $location.search(""); + + },function(error) { + //TODO ymartel 20141118 : deal with error.status or statusText + console.log("error occurs"); + console.log(error.s); + }); + + }; + } ]); \ No newline at end of file diff --git a/coselmar-ui/src/main/webapp/js/coselmar-user-services.js b/coselmar-ui/src/main/webapp/js/coselmar-user-services.js index 617ffcd..f3b24d3 100644 --- a/coselmar-ui/src/main/webapp/js/coselmar-user-services.js +++ b/coselmar-ui/src/main/webapp/js/coselmar-user-services.js @@ -32,13 +32,17 @@ function User(resource, config){ var baseURL = config.BASE_URL + "/users"; - this.createUser = function(user, successFunction, failFunction){ + this.saveUser = function(user, successFunction, failFunction){ var formData = new FormData(); formData.append("user", JSON.stringify(user)); // Save the User - var userResource = resource(baseURL, null, { + var serviceURl = baseURL; + if (user.id) { + serviceURl = baseURL + "/" + user.id + } + var userResource = resource(serviceURl, null, { 'save': { method:'POST', transformRequest: angular.identity, @@ -96,4 +100,22 @@ function User(resource, config){ }}); userResource.post(credentials, successFunction, failFunction); } + +// this.saveUser = function(user, successFunction, failFunction){ +// +// var formData = new FormData(); +// formData.append("user", JSON.stringify(user)); +// +// // Save the User +// var userResource = resource(baseURL + "/" + user.id, null, { +// 'save': { +// method: 'POST', +// transformRequest: angular.identity, +// headers:{ +// 'Content-Type': undefined +// } +// } +// }); +// userResource.save(null, formData, successFunction, failFunction); +// } }; \ No newline at end of file diff --git a/coselmar-ui/src/main/webapp/views/users/edituser.html b/coselmar-ui/src/main/webapp/views/users/edituser.html new file mode 100644 index 0000000..35f1e52 --- /dev/null +++ b/coselmar-ui/src/main/webapp/views/users/edituser.html @@ -0,0 +1,94 @@ + <div class=""> + + <form name="userForm" class="form-horizontal" role="form" ng-submit="saveUser()"> + + <div class="form-group"> + <label class="col-md-4 control-label">FirstName</label> + + <div class="col-md-5"> + <input type="text" class="form-control" name="firstName" + ng-model="user.firstName" required/> + </div> + </div> + + <div class="form-group"> + <label class="col-md-4 control-label">Name</label> + + <div class="col-md-5"> + <input type="text" class="form-control" name="name" + ng-model="user.name" required/> + </div> + </div> + + <div class="form-group" ng-if="currentUser.role == 'ADMIN'"> + <label class="col-md-4 control-label">Role</label> + + <div class="col-md-5"> + <select class="form-control" name="role" ng-model="user.role"> + <option value="ADMIN">Admin</option> + <option value="SUPERVISOR">Supervisor</option> + <option value="EXPERT">Expert</option> + <option value="MEMBER">Member</option> + <option value="CLIENT">Client</option> + </select> + </div> + + </div> + + <div class="form-group"> + <label class="col-md-4 control-label">Mail</label> + + <div class="col-md-5"> + <input type="email" class="form-control" name="mail" + ng-model="user.mail" required/> + </div> + + </div> + + <div class="form-group"> + <label class="col-md-4 control-label">Organization</label> + + <div class="col-md-5"> + <input type="text" class="form-control" name="organization" + ng-model="user.organization" /> + </div> + + </div> + + <div class="form-group"> + <label class="col-md-4 control-label">Qualification</label> + + <div class="col-md-5"> + <input type="text" class="form-control" name="qualification" + ng-model="user.qualification" required/> + </div> + + </div> + + <div class="form-group" ng-if="currentUser && currentUser.userId == user.id"> + <label class="col-md-4 control-label">Tape your current password to validate</label> + + <div class="col-md-5"> + <input type="password" class="form-control" name="password" + ng-model="user.password" required/> + </div> + + </div> + + <div class="form-group" ng-if="editPassword"> + <label class="col-md-4 control-label">New Password</label> + + <div class="col-md-5"> + <input type="password" class="form-control" name="newPassword" + ng-model="user.newPassword" ng-minlength="6" placeholder="change password"/> + </div> + + </div> + + <div class="form-group" ng-if="userForm.$valid || currentUser.role == 'ADMIN'"> + <div style="padding-left: 200px"> + <input type="submit" value="Validate" class="btn btn-primary"/> + </div> + </div> + </form> + </div> diff --git a/coselmar-ui/src/main/webapp/views/users/newuser.html b/coselmar-ui/src/main/webapp/views/users/newuser.html index 3fa8e87..40d6b50 100644 --- a/coselmar-ui/src/main/webapp/views/users/newuser.html +++ b/coselmar-ui/src/main/webapp/views/users/newuser.html @@ -12,67 +12,8 @@ <li>Client</li> </ul> </div> - <div style="padding-bottom: 50px"> + <div style="padding-bottom: 50px" ng-include="src='views/users/edituser.html'"> - <div class=""> - - <form class="form-horizontal" role="form" ng-submit="createNewUser()"> - - <div class="form-group"> - <label class="col-md-4 control-label">FirstName</label> - - <div class="col-md-5"> - <input type="text" class="form-control" name="firstName" ng-model="user.firstName" required/> - </div> - </div> - - <div class="form-group"> - <label class="col-md-4 control-label">Name</label> - - <div class="col-md-5"> - <input type="text" class="form-control" name="name" ng-model="user.name" required/> - </div> - </div> - - <div class="form-group"> - <label class="col-md-4 control-label">Role</label> - - <div class="col-md-5"> - <select class="form-control" name="role" ng-model="user.role"> - <option value="supervisor">Supervisor</option> - <option value="expert">Expert</option> - <option value="member">Member</option> - <option value="client">Client</option> - </select> - </div> - - </div> - - <div class="form-group"> - <label class="col-md-4 control-label">Mail</label> - - <div class="col-md-5"> - <input type="text" class="form-control" name="mail" ng-model="user.mail" required/> - </div> - - </div> - - <div class="form-group"> - <label class="col-md-4 control-label">Qualification</label> - - <div class="col-md-5"> - <input type="text" class="form-control" name="qualification" ng-model="user.qualification" required/> - </div> - - </div> - - <div class="form-group"> - <div style="padding-left: 200px"> - <input type="submit" value="Submit" class="btn btn-primary" /> - </div> - </div> - </form> - </div> </div> diff --git a/coselmar-ui/src/main/webapp/views/users/user.html b/coselmar-ui/src/main/webapp/views/users/user.html index 3245cba..37514ad 100644 --- a/coselmar-ui/src/main/webapp/views/users/user.html +++ b/coselmar-ui/src/main/webapp/views/users/user.html @@ -4,7 +4,7 @@ {{user.firstName}} {{user.name}} </h1> </div> - <div> + <div ng-if="!editMode" > <table class="table table-striped"> <tr> <td>Contact</td> @@ -15,12 +15,22 @@ <td>{{user.qualification}}</td> </tr> <tr> + <td>Organization</td> + <td>{{user.organization}}</td> + </tr> + <tr> <td>Role</td> <td>{{user.role}}</td> </tr> </table> <div style="padding-left: 200px"> - <a class="btn btn-danger" ng-click="deleteUser(user.id)">Delete</a> + <a class="btn btn-warning" ng-click="modifyUser()">Modify</a> + <a class="btn btn-danger" ng-click="deleteUser(user.id)" ng-if="currentUser.role == 'ADMIN'">Delete</a> </div> </div> + + <div ng-if="editMode" style="padding: 30px 0px 50px 0px"> + <div ng-include="src='views/users/edituser.html'" ></div> + </div> + </div> \ No newline at end of file diff --git a/coselmar-ui/src/main/webapp/views/users/users.html b/coselmar-ui/src/main/webapp/views/users/users.html index 0f15a7d..c9db771 100644 --- a/coselmar-ui/src/main/webapp/views/users/users.html +++ b/coselmar-ui/src/main/webapp/views/users/users.html @@ -26,6 +26,7 @@ <th>Name</th> <th>Mail</th> <th>Qualification</th> + <th>Organization</th> <th>Role</th> <th></th> </tr> @@ -33,8 +34,9 @@ <td><a href="#/users/{{user.id}}">{{user.firstname}} {{user.name}}</a></td> <td>{{user.mail}}</td> <td>{{user.qualification}}</td> + <td>{{user.organization}}</td> <td>{{user.role}}</td> - <td><a class="btn btn-danger" ng-click="deleteUser(user.id)">Delete</a></td> + <td><a class="btn btn-warning" href="users/{{user.id}}?edit">Modify</a><a class="btn btn-danger" ng-click="deleteUser(user.id)">Delete</a></td> </tr> </table> </div> -- To stop receiving notification emails like this one, please contact codelutin.com SCM administrator <admin+scm@codelutin.com>.
This is an automated email from the git hooks/post-receive script. New commit to branch develop in repository coselmar. See http://git.codelutin.com/coselmar.git commit 166496f3b3eb0da9174658847cf5e4f4571a9e35 Merge: b6a809a c8a14d8 Author: Yannick Martel <martel@©odelutin.com> Date: Tue Nov 25 17:59:52 2014 +0100 Merge branch 'feature/6014-modify-user' into develop .../java/fr/ifremer/coselmar/beans/UserBean.java | 9 + .../fr/ifremer/coselmar/beans/UserWebToken.java | 88 ++++++++++ .../services/CoselmarWebServiceSupport.java | 41 +++++ .../services/errors/UnauthorizedException.java | 11 ++ .../coselmar/services/v1/UsersWebService.java | 194 +++++++++++++++++++-- coselmar-rest/src/main/resources/mapping | 3 +- coselmar-ui/src/main/webapp/index.html | 4 +- .../src/main/webapp/js/coselmar-controllers.js | 30 +++- .../src/main/webapp/js/coselmar-user-services.js | 26 ++- coselmar-ui/src/main/webapp/js/coselmar.js | 2 +- .../src/main/webapp/views/users/edituser.html | 94 ++++++++++ .../src/main/webapp/views/users/newuser.html | 61 +------ coselmar-ui/src/main/webapp/views/users/user.html | 14 +- coselmar-ui/src/main/webapp/views/users/users.html | 6 +- 14 files changed, 494 insertions(+), 89 deletions(-) -- To stop receiving notification emails like this one, please contact codelutin.com SCM administrator <admin+scm@codelutin.com>.
participants (1)
-
codelutin.com scm