2014-08-08 16:49:02 +02:00
package com.commafeed.frontend.resource ;
import io.dropwizard.hibernate.UnitOfWork ;
2014-08-09 15:25:41 +02:00
import io.dropwizard.jersey.sessions.Session ;
2014-08-09 19:04:37 +02:00
import io.dropwizard.jersey.validation.ValidationErrorMessage ;
2013-03-23 19:46:09 +01:00
2013-06-18 12:31:09 +02:00
import java.util.Arrays ;
2014-08-09 19:04:37 +02:00
import java.util.Collections ;
2014-08-11 14:55:41 +02:00
import java.util.Date ;
import java.util.UUID ;
2013-06-18 12:31:09 +02:00
2014-08-09 15:25:41 +02:00
import javax.servlet.http.HttpSession ;
2014-08-09 19:04:37 +02:00
import javax.validation.ConstraintViolation ;
import javax.validation.Valid ;
2014-08-08 16:49:02 +02:00
import javax.ws.rs.Consumes ;
2013-03-23 19:46:09 +01:00
import javax.ws.rs.GET ;
import javax.ws.rs.POST ;
import javax.ws.rs.Path ;
2014-08-08 16:49:02 +02:00
import javax.ws.rs.Produces ;
2014-08-11 14:55:41 +02:00
import javax.ws.rs.QueryParam ;
2014-08-08 16:49:02 +02:00
import javax.ws.rs.core.MediaType ;
2013-03-23 23:14:14 +01:00
import javax.ws.rs.core.Response ;
import javax.ws.rs.core.Response.Status ;
2013-03-23 19:46:09 +01:00
2014-08-08 16:49:02 +02:00
import lombok.AllArgsConstructor ;
2014-08-11 14:55:41 +02:00
import lombok.extern.slf4j.Slf4j ;
2014-08-08 16:49:02 +02:00
2014-08-11 14:55:41 +02:00
import org.apache.commons.codec.digest.DigestUtils ;
import org.apache.commons.lang.RandomStringUtils ;
2013-04-18 12:50:44 +02:00
import org.apache.commons.lang.StringUtils ;
2014-08-11 14:55:41 +02:00
import org.apache.commons.lang.time.DateUtils ;
import org.apache.http.client.utils.URIBuilder ;
2013-04-18 12:50:44 +02:00
2014-08-08 16:49:02 +02:00
import com.commafeed.CommaFeedApplication ;
2014-08-11 14:55:41 +02:00
import com.commafeed.CommaFeedConfiguration ;
2013-07-02 16:10:59 +02:00
import com.commafeed.backend.dao.UserDAO ;
import com.commafeed.backend.dao.UserRoleDAO ;
import com.commafeed.backend.dao.UserSettingsDAO ;
2014-08-11 14:55:41 +02:00
import com.commafeed.backend.feed.FeedUtils ;
2013-04-18 12:50:44 +02:00
import com.commafeed.backend.model.User ;
import com.commafeed.backend.model.UserRole ;
import com.commafeed.backend.model.UserRole.Role ;
2013-04-21 13:50:10 +02:00
import com.commafeed.backend.model.UserSettings ;
2013-03-23 19:46:09 +01:00
import com.commafeed.backend.model.UserSettings.ReadingMode ;
2013-04-10 10:28:48 +02:00
import com.commafeed.backend.model.UserSettings.ReadingOrder ;
2013-05-04 21:12:51 +02:00
import com.commafeed.backend.model.UserSettings.ViewMode ;
2014-08-11 14:55:41 +02:00
import com.commafeed.backend.service.MailService ;
2014-08-08 16:49:02 +02:00
import com.commafeed.backend.service.PasswordEncryptionService ;
import com.commafeed.backend.service.UserService ;
2014-08-08 21:57:16 +02:00
import com.commafeed.frontend.auth.SecurityCheck ;
2013-03-23 19:46:09 +01:00
import com.commafeed.frontend.model.Settings ;
2013-04-18 12:50:44 +02:00
import com.commafeed.frontend.model.UserModel ;
2014-08-09 15:25:41 +02:00
import com.commafeed.frontend.model.request.LoginRequest ;
2014-08-11 14:55:41 +02:00
import com.commafeed.frontend.model.request.PasswordResetRequest ;
2013-04-18 12:50:44 +02:00
import com.commafeed.frontend.model.request.ProfileModificationRequest ;
2013-06-18 12:31:09 +02:00
import com.commafeed.frontend.model.request.RegistrationRequest ;
2014-08-09 15:25:41 +02:00
import com.google.common.base.Optional ;
2013-03-24 13:11:05 +01:00
import com.google.common.base.Preconditions ;
2014-08-09 19:04:37 +02:00
import com.google.common.collect.ImmutableList ;
2013-04-17 07:50:25 +02:00
import com.wordnik.swagger.annotations.Api ;
import com.wordnik.swagger.annotations.ApiOperation ;
import com.wordnik.swagger.annotations.ApiParam ;
2013-03-23 19:46:09 +01:00
2013-04-18 12:50:44 +02:00
@Path ( " /user " )
@Api ( value = " /user " , description = " Operations about the user " )
2014-08-08 16:49:02 +02:00
@Produces ( MediaType . APPLICATION_JSON )
@Consumes ( MediaType . APPLICATION_JSON )
2014-08-11 14:55:41 +02:00
@Slf4j
2014-08-08 16:49:02 +02:00
@AllArgsConstructor
public class UserREST {
2013-07-02 16:10:59 +02:00
2014-08-08 16:49:02 +02:00
private final UserDAO userDAO ;
private final UserRoleDAO userRoleDAO ;
private final UserSettingsDAO userSettingsDAO ;
private final UserService userService ;
private final PasswordEncryptionService encryptionService ;
2014-08-11 14:55:41 +02:00
private final MailService mailService ;
private final CommaFeedConfiguration config ;
2013-07-10 09:25:59 +02:00
2013-04-18 12:50:44 +02:00
@Path ( " /settings " )
2013-03-23 19:46:09 +01:00
@GET
2014-08-08 16:49:02 +02:00
@UnitOfWork
@ApiOperation ( value = " Retrieve user settings " , notes = " Retrieve user settings " , response = Settings . class )
2014-08-08 21:57:16 +02:00
public Response getSettings ( @SecurityCheck User user ) {
2013-03-23 19:46:09 +01:00
Settings s = new Settings ( ) ;
2014-08-08 16:49:02 +02:00
UserSettings settings = userSettingsDAO . findByUser ( user ) ;
2013-03-23 23:14:14 +01:00
if ( settings ! = null ) {
2013-07-29 21:31:08 +02:00
s . setReadingMode ( settings . getReadingMode ( ) . name ( ) ) ;
2013-04-10 10:28:48 +02:00
s . setReadingOrder ( settings . getReadingOrder ( ) . name ( ) ) ;
2013-05-04 21:12:51 +02:00
s . setViewMode ( settings . getViewMode ( ) . name ( ) ) ;
2013-04-12 09:57:18 +02:00
s . setShowRead ( settings . isShowRead ( ) ) ;
2014-08-08 16:49:02 +02:00
2014-04-16 12:29:53 +02:00
s . setEmail ( settings . isEmail ( ) ) ;
s . setGmail ( settings . isGmail ( ) ) ;
s . setFacebook ( settings . isFacebook ( ) ) ;
s . setTwitter ( settings . isTwitter ( ) ) ;
s . setGoogleplus ( settings . isGoogleplus ( ) ) ;
s . setTumblr ( settings . isTumblr ( ) ) ;
s . setPocket ( settings . isPocket ( ) ) ;
s . setInstapaper ( settings . isInstapaper ( ) ) ;
s . setBuffer ( settings . isBuffer ( ) ) ;
s . setReadability ( settings . isReadability ( ) ) ;
2014-08-08 16:49:02 +02:00
2013-05-05 19:35:07 +02:00
s . setScrollMarks ( settings . isScrollMarks ( ) ) ;
2013-05-28 22:40:54 +02:00
s . setTheme ( settings . getTheme ( ) ) ;
2013-04-04 11:36:24 +02:00
s . setCustomCss ( settings . getCustomCss ( ) ) ;
2013-05-11 22:49:33 +02:00
s . setLanguage ( settings . getLanguage ( ) ) ;
2013-10-03 10:40:58 +02:00
s . setScrollSpeed ( settings . getScrollSpeed ( ) ) ;
2013-03-23 23:14:14 +01:00
} else {
s . setReadingMode ( ReadingMode . unread . name ( ) ) ;
2013-04-10 10:28:48 +02:00
s . setReadingOrder ( ReadingOrder . desc . name ( ) ) ;
2013-05-04 21:12:51 +02:00
s . setViewMode ( ViewMode . title . name ( ) ) ;
2013-04-12 09:57:18 +02:00
s . setShowRead ( true ) ;
2013-05-28 22:40:54 +02:00
s . setTheme ( " default " ) ;
2014-08-08 16:49:02 +02:00
2014-04-16 12:29:53 +02:00
s . setEmail ( true ) ;
s . setGmail ( true ) ;
s . setFacebook ( true ) ;
s . setTwitter ( true ) ;
s . setGoogleplus ( true ) ;
s . setTumblr ( true ) ;
s . setPocket ( true ) ;
s . setInstapaper ( true ) ;
s . setBuffer ( true ) ;
s . setReadability ( true ) ;
2014-08-08 16:49:02 +02:00
2013-05-05 19:35:07 +02:00
s . setScrollMarks ( true ) ;
2013-05-11 22:49:33 +02:00
s . setLanguage ( " en " ) ;
2013-10-03 10:40:58 +02:00
s . setScrollSpeed ( 400 ) ;
2013-03-23 23:14:14 +01:00
}
2013-05-17 19:39:52 +02:00
return Response . ok ( s ) . build ( ) ;
2013-03-23 19:46:09 +01:00
}
2013-04-18 12:50:44 +02:00
@Path ( " /settings " )
2013-03-23 19:46:09 +01:00
@POST
2014-08-08 16:49:02 +02:00
@UnitOfWork
2013-04-17 07:50:25 +02:00
@ApiOperation ( value = " Save user settings " , notes = " Save user settings " )
2014-08-08 21:57:16 +02:00
public Response saveSettings ( @SecurityCheck User user , @ApiParam ( required = true ) Settings settings ) {
2013-03-24 13:11:05 +01:00
Preconditions . checkNotNull ( settings ) ;
2013-04-10 10:28:48 +02:00
2014-08-08 16:49:02 +02:00
UserSettings s = userSettingsDAO . findByUser ( user ) ;
2013-03-23 19:46:09 +01:00
if ( s = = null ) {
s = new UserSettings ( ) ;
2014-08-08 16:49:02 +02:00
s . setUser ( user ) ;
2013-03-23 19:46:09 +01:00
}
s . setReadingMode ( ReadingMode . valueOf ( settings . getReadingMode ( ) ) ) ;
2013-04-10 10:28:48 +02:00
s . setReadingOrder ( ReadingOrder . valueOf ( settings . getReadingOrder ( ) ) ) ;
2013-04-12 09:57:18 +02:00
s . setShowRead ( settings . isShowRead ( ) ) ;
2013-05-04 21:12:51 +02:00
s . setViewMode ( ViewMode . valueOf ( settings . getViewMode ( ) ) ) ;
2013-05-05 19:35:07 +02:00
s . setScrollMarks ( settings . isScrollMarks ( ) ) ;
2013-05-28 22:40:54 +02:00
s . setTheme ( settings . getTheme ( ) ) ;
2013-04-04 11:36:24 +02:00
s . setCustomCss ( settings . getCustomCss ( ) ) ;
2013-05-11 22:49:33 +02:00
s . setLanguage ( settings . getLanguage ( ) ) ;
2013-10-03 10:40:58 +02:00
s . setScrollSpeed ( settings . getScrollSpeed ( ) ) ;
2014-08-08 16:49:02 +02:00
2014-04-16 12:29:53 +02:00
s . setEmail ( settings . isEmail ( ) ) ;
s . setGmail ( settings . isGmail ( ) ) ;
s . setFacebook ( settings . isFacebook ( ) ) ;
s . setTwitter ( settings . isTwitter ( ) ) ;
s . setGoogleplus ( settings . isGoogleplus ( ) ) ;
s . setTumblr ( settings . isTumblr ( ) ) ;
s . setPocket ( settings . isPocket ( ) ) ;
s . setInstapaper ( settings . isInstapaper ( ) ) ;
s . setBuffer ( settings . isBuffer ( ) ) ;
s . setReadability ( settings . isReadability ( ) ) ;
2014-08-08 16:49:02 +02:00
2013-04-11 20:49:08 +02:00
userSettingsDAO . saveOrUpdate ( s ) ;
2013-08-11 17:22:17 +02:00
return Response . ok ( ) . build ( ) ;
2013-03-23 19:46:09 +01:00
}
2013-04-21 13:50:10 +02:00
2013-04-18 12:50:44 +02:00
@Path ( " /profile " )
@GET
2014-08-08 16:49:02 +02:00
@UnitOfWork
@ApiOperation ( value = " Retrieve user's profile " , response = UserModel . class )
2014-08-08 21:57:16 +02:00
public Response get ( @SecurityCheck User user ) {
2013-04-18 12:50:44 +02:00
UserModel userModel = new UserModel ( ) ;
userModel . setId ( user . getId ( ) ) ;
userModel . setName ( user . getName ( ) ) ;
userModel . setEmail ( user . getEmail ( ) ) ;
userModel . setEnabled ( ! user . isDisabled ( ) ) ;
2013-05-01 21:56:59 +02:00
userModel . setApiKey ( user . getApiKey ( ) ) ;
2013-04-18 12:50:44 +02:00
for ( UserRole role : userRoleDAO . findAll ( user ) ) {
if ( role . getRole ( ) = = Role . ADMIN ) {
userModel . setAdmin ( true ) ;
}
}
2013-05-17 19:39:52 +02:00
return Response . ok ( userModel ) . build ( ) ;
2013-04-18 12:50:44 +02:00
}
@Path ( " /profile " )
@POST
2014-08-08 16:49:02 +02:00
@UnitOfWork
2013-04-18 12:50:44 +02:00
@ApiOperation ( value = " Save user's profile " )
2014-08-08 21:57:16 +02:00
public Response save ( @SecurityCheck User user , @ApiParam ( required = true ) ProfileModificationRequest request ) {
2013-07-25 09:17:33 +02:00
Preconditions . checkArgument ( StringUtils . isBlank ( request . getPassword ( ) ) | | request . getPassword ( ) . length ( ) > = 6 ) ;
2013-05-21 09:17:12 +02:00
if ( StringUtils . isNotBlank ( request . getEmail ( ) ) ) {
User u = userDAO . findByEmail ( request . getEmail ( ) ) ;
2013-07-25 09:17:33 +02:00
Preconditions . checkArgument ( u = = null | | user . getId ( ) . equals ( u . getId ( ) ) ) ;
2013-05-21 09:17:12 +02:00
}
2014-08-08 16:49:02 +02:00
if ( CommaFeedApplication . USERNAME_DEMO . equals ( user . getName ( ) ) ) {
2013-06-24 16:37:11 +02:00
return Response . status ( Status . FORBIDDEN ) . build ( ) ;
2013-04-21 13:50:10 +02:00
}
2013-05-01 21:56:59 +02:00
2013-05-23 14:35:16 +02:00
user . setEmail ( StringUtils . trimToNull ( request . getEmail ( ) ) ) ;
2013-04-18 12:50:44 +02:00
if ( StringUtils . isNotBlank ( request . getPassword ( ) ) ) {
2013-07-25 09:17:33 +02:00
byte [ ] password = encryptionService . getEncryptedPassword ( request . getPassword ( ) , user . getSalt ( ) ) ;
2013-04-18 12:50:44 +02:00
user . setPassword ( password ) ;
2013-05-20 21:53:13 +02:00
user . setApiKey ( userService . generateApiKey ( user ) ) ;
2013-05-01 21:56:59 +02:00
}
if ( request . isNewApiKey ( ) ) {
2013-05-20 21:53:13 +02:00
user . setApiKey ( userService . generateApiKey ( user ) ) ;
2013-04-18 12:50:44 +02:00
}
2014-08-08 16:49:02 +02:00
userDAO . merge ( user ) ;
2013-04-18 12:50:44 +02:00
return Response . ok ( ) . build ( ) ;
}
2013-05-01 21:56:59 +02:00
2013-06-18 12:31:09 +02:00
@Path ( " /register " )
@POST
2014-08-08 16:49:02 +02:00
@UnitOfWork
2013-06-18 12:31:09 +02:00
@ApiOperation ( value = " Register a new account " )
2014-08-09 19:04:37 +02:00
public Response register ( @Valid @ApiParam ( required = true ) RegistrationRequest req ) {
2013-06-18 12:31:09 +02:00
try {
2013-07-25 09:17:33 +02:00
userService . register ( req . getName ( ) , req . getPassword ( ) , req . getEmail ( ) , Arrays . asList ( Role . USER ) ) ;
2013-06-18 12:31:09 +02:00
return Response . ok ( ) . build ( ) ;
2014-08-09 19:04:37 +02:00
} catch ( final IllegalArgumentException e ) {
return Response . status ( 422 ) . entity ( new ValidationErrorMessage ( Collections . < ConstraintViolation < ? > > emptySet ( ) ) {
@Override
public ImmutableList < String > getErrors ( ) {
return ImmutableList . of ( e . getMessage ( ) ) ;
}
} ) . build ( ) ;
2013-06-18 12:31:09 +02:00
}
2014-08-09 15:25:41 +02:00
}
2013-06-18 12:31:09 +02:00
2014-08-09 15:25:41 +02:00
@Path ( " /login " )
@POST
@UnitOfWork
@ApiOperation ( value = " Login and create a session " )
public Response login ( @ApiParam ( required = true ) LoginRequest req , @Session HttpSession session ) {
Optional < User > user = userService . login ( req . getName ( ) , req . getPassword ( ) ) ;
if ( user . isPresent ( ) ) {
2014-08-09 16:07:24 +02:00
session . setAttribute ( CommaFeedApplication . SESSION_USER , user . get ( ) ) ;
2014-08-09 15:25:41 +02:00
return Response . ok ( ) . build ( ) ;
} else {
2014-08-09 16:07:24 +02:00
return Response . status ( Response . Status . UNAUTHORIZED ) . entity ( " wrong username or password " ) . build ( ) ;
2014-08-09 15:25:41 +02:00
}
2013-06-18 12:31:09 +02:00
}
2014-08-11 14:55:41 +02:00
@Path ( " /passwordReset " )
@POST
@UnitOfWork
@ApiOperation ( value = " send a password reset email " )
public Response sendPasswordReset ( @Valid PasswordResetRequest req ) {
User user = userDAO . findByEmail ( req . getEmail ( ) ) ;
if ( user = = null ) {
return Response . status ( Status . PRECONDITION_FAILED ) . entity ( " Email not found. " ) . build ( ) ;
}
try {
user . setRecoverPasswordToken ( DigestUtils . sha1Hex ( UUID . randomUUID ( ) . toString ( ) ) ) ;
user . setRecoverPasswordTokenDate ( new Date ( ) ) ;
userDAO . saveOrUpdate ( user ) ;
mailService . sendMail ( user , " Password recovery " , buildEmailContent ( user ) ) ;
return Response . ok ( ) . build ( ) ;
} catch ( Exception e ) {
log . error ( e . getMessage ( ) , e ) ;
return Response . status ( Status . INTERNAL_SERVER_ERROR ) . entity ( " could not send email: " + e . getMessage ( ) ) . build ( ) ;
}
}
private String buildEmailContent ( User user ) throws Exception {
String publicUrl = FeedUtils . removeTrailingSlash ( config . getApplicationSettings ( ) . getPublicUrl ( ) ) ;
publicUrl + = " /rest/user/passwordResetCallback " ;
return String
. format ( " You asked for password recovery for account '%s', <a href='%s'>follow this link</a> to change your password. Ignore this if you didn't request a password recovery. " ,
user . getName ( ) , callbackUrl ( user , publicUrl ) ) ;
}
private String callbackUrl ( User user , String publicUrl ) throws Exception {
return new URIBuilder ( publicUrl ) . addParameter ( " email " , user . getEmail ( ) ) . addParameter ( " token " , user . getRecoverPasswordToken ( ) )
. build ( ) . toURL ( ) . toString ( ) ;
}
@Path ( " /passwordResetCallback " )
@GET
@UnitOfWork
@Produces ( MediaType . TEXT_HTML )
public Response passwordRecoveryCallback ( @QueryParam ( " email " ) String email , @QueryParam ( " token " ) String token ) {
Preconditions . checkNotNull ( email ) ;
Preconditions . checkNotNull ( token ) ;
User user = userDAO . findByEmail ( email ) ;
if ( user = = null ) {
return Response . status ( Status . UNAUTHORIZED ) . entity ( " Email not found. " ) . build ( ) ;
}
if ( user . getRecoverPasswordToken ( ) = = null | | ! user . getRecoverPasswordToken ( ) . equals ( token ) ) {
return Response . status ( Status . UNAUTHORIZED ) . entity ( " Invalid token. " ) . build ( ) ;
}
if ( user . getRecoverPasswordTokenDate ( ) . before ( DateUtils . addDays ( new Date ( ) , - 2 ) ) ) {
return Response . status ( Status . UNAUTHORIZED ) . entity ( " token expired. " ) . build ( ) ;
}
String passwd = RandomStringUtils . randomAlphanumeric ( 10 ) ;
2014-08-12 19:56:21 +02:00
byte [ ] encryptedPassword = encryptionService . getEncryptedPassword ( passwd , user . getSalt ( ) ) ;
user . setPassword ( encryptedPassword ) ;
2014-08-11 14:55:41 +02:00
if ( StringUtils . isNotBlank ( user . getApiKey ( ) ) ) {
user . setApiKey ( userService . generateApiKey ( user ) ) ;
}
user . setRecoverPasswordToken ( null ) ;
user . setRecoverPasswordTokenDate ( null ) ;
userDAO . saveOrUpdate ( user ) ;
String message = " Your new password is: " + passwd ;
message + = " <br /> " ;
message + = String . format ( " <a href= \" %s \" >Back to Homepage</a> " , config . getApplicationSettings ( ) . getPublicUrl ( ) ) ;
return Response . ok ( message ) . build ( ) ;
}
2013-05-08 11:15:50 +02:00
@Path ( " /profile/deleteAccount " )
@POST
2014-08-08 16:49:02 +02:00
@UnitOfWork
2013-05-08 11:15:50 +02:00
@ApiOperation ( value = " Delete the user account " )
2014-08-08 21:57:16 +02:00
public Response delete ( @SecurityCheck User user ) {
2014-08-08 16:49:02 +02:00
if ( CommaFeedApplication . USERNAME_ADMIN . equals ( user . getName ( ) ) | | CommaFeedApplication . USERNAME_DEMO . equals ( user . getName ( ) ) ) {
2013-06-24 16:37:11 +02:00
return Response . status ( Status . FORBIDDEN ) . build ( ) ;
2013-05-08 11:15:50 +02:00
}
2014-08-08 16:49:02 +02:00
userService . unregister ( user ) ;
2013-05-08 11:15:50 +02:00
return Response . ok ( ) . build ( ) ;
}
2013-03-23 19:46:09 +01:00
}