/* * Userer.java * * Created on Aug 4, 2007, 10:31:57 AM * * Handles dealing with registering users, and logging them * in and out of the system. * */ package com.pugh.sockso.web.action; import com.pugh.sockso.Constants; import com.pugh.sockso.Validater; import com.pugh.sockso.web.*; import com.pugh.sockso.Properties; import com.pugh.sockso.Utils; import com.pugh.sockso.auth.Authenticator; import com.pugh.sockso.db.Database; import com.pugh.sockso.music.Track; import com.pugh.sockso.templates.web.users.TUserLogin; import com.pugh.sockso.templates.web.users.TUserRegister; import com.pugh.sockso.templates.web.users.TUserRegistered; import com.pugh.sockso.templates.web.users.TUserEdit; import com.pugh.sockso.templates.web.users.TUserUpdated; import com.pugh.sockso.templates.web.users.TScrobbleLog; import com.pugh.sockso.resources.Locale; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.PreparedStatement; import java.io.IOException; import java.util.ArrayList; import java.util.List; import org.apache.log4j.Logger; public class Userer extends BaseAction { private static final Logger log = Logger.getLogger( Userer.class ); private final List<Authenticator> authenticators; /** * Constructor * */ public Userer() { this.authenticators = new ArrayList<Authenticator>(); } /** * handles the "user" command to manage user accounts * * @param req the request object * @param res the response object * */ public void handleRequest() throws BadRequestException, IOException, SQLException { final Request req = getRequest(); final String type = req.getUrlParam( 1 ); if ( type.equals("register") ) register(); else if ( type.equals("login") ) login(); else if ( type.equals("logout") ) logout(); else if ( type.equals("edit") ) edit(); else if ( type.equals("update") ) update(); else if ( type.equals("scrobbleLog") ) scrobbleLog(); else throw new BadRequestException( "unknown command '" + type + "'", 400 ); } /** * Adds an authenticator that can be used to log in users * * @param authenticator */ public void addAuthenticator( Authenticator authenticator ) { authenticators.add( authenticator ); } /** * creates a scrobble log file for the tracks the user has listened to (but that * haven't already been scrobbled) * */ protected void scrobbleLog() throws SQLException, IOException { final User user = getUser(); final List<Track> tracks = getNonScrobbledTracks( user ); markUsersTracksScrobbled( user ); showScrobbleLog( tracks ); } /** * shows the template for the users scrobble log of unscrobbled tracks * * @param tracks * * @throws java.io.IOException * */ protected void showScrobbleLog( final List<Track> tracks ) throws IOException { final TScrobbleLog tpl = new TScrobbleLog(); final Response res = getResponse(); tpl.setTracks( tracks ); res.addHeader( "Content-Disposition", "inline; filename=\".scrobbler.log\"" ); res.showText( tpl.makeRenderer() ); } /** * marks all a users played tracks as having been scrobbled * */ protected void markUsersTracksScrobbled( final User user ) throws SQLException { PreparedStatement st = null; try { final Database db = getDatabase(); final String sql = " update play_log " + " set scrobbled = 1 " + " where user_id = ? "; st = db.prepare( sql ); st.setInt( 1, user.getId() ); st.executeUpdate(); } finally { Utils.close( st ); } } /** * returns the tracks the user has listened to that haven't been scrobbled yet * * @param user * * @return * */ protected List<Track> getNonScrobbledTracks( final User user ) throws SQLException { PreparedStatement st = null; ResultSet rs = null; try { final Database db = getDatabase(); // use the date the track was played instead of the date it // was added to the collection. final String sql = Utils.replaceAll( "t.date_added", "l.date_played", Track.getSelectSql() ) + " from play_log l " + " inner join tracks t " + " on t.id = l.track_id " + " inner join artists ar " + " on ar.id = t.artist_id " + " inner join albums al " + " on al.id = t.album_id " + " inner join genres g " + " on g.id = t.genre_id " + " where l.user_id = ? " + " and l.scrobbled = 0 "; st = db.prepare( sql ); st.setInt( 1, user.getId() ); rs = st.executeQuery(); return Track.createListFromResultSet( rs ); } finally { Utils.close( rs ); Utils.close( st ); } } /** * updates a users details * * @throws java.io.IOException * */ private void update() throws IOException, BadRequestException, SQLException { requireLogin(); getUpdateSubmission().validate(); updateUser(); showUserUpdated(); } /** * shows the page to inform the user their profile has been updated * * @throws java.io.IOException * */ protected void showUserUpdated() throws IOException, SQLException { final TUserUpdated tpl = new TUserUpdated(); getResponse().showHtml( tpl ); } /** * returns the submission object for validating updating users * * @return * */ protected Submission getUpdateSubmission() { final Submission s = new Submission( getRequest(), getLocale() ); s.addField( "email", Submission.FIELD_EMAIL, "www.error.invalidEmail" ); s.addMatchingFields( "pass1", "pass2", "www.error.passwordsDontMatch" ); return s; } /** * updates the user with the submitted details * */ protected void updateUser() throws SQLException { PreparedStatement st = null; try { final User user = getUser(); final Request req = getRequest(); final Database db = getDatabase(); final String sql = " update users " + " set email = ?, " + " pass = ? " + " where id = ? "; st = db.prepare( sql ); st.setString( 1, req.getArgument("email") ); st.setString( 2, Utils.md5(req.getArgument("pass1")) ); st.setInt( 3, user.getId() ); st.execute(); } finally { Utils.close( st ); } } /** * makes sure the user is logged in, and redirects them to the login * page if they're not * * @throws java.io.IOException * */ protected void requireLogin() throws IOException { if ( getUser() == null ) getResponse().redirect( getProperties().getUrl("/user/login")); } /** * shows the page with the users profile on it to edit * * @throws IOException * */ private void edit() throws IOException, SQLException { requireLogin(); showUserEdit(); } /** * shows the form to edit the current user * * @throws java.io.IOException * */ protected void showUserEdit() throws IOException, SQLException { final TUserEdit tpl = new TUserEdit(); getResponse().showHtml( tpl ); } @Override public boolean requiresLogin() { return false; } public void register() throws IOException, BadRequestException, SQLException { final Properties p = getProperties(); final Request req = getRequest(); final User user = getUser(); final Locale locale = getLocale(); if ( p.get(Constants.WWW_USERS_DISABLE_REGISTRATION).equals(Properties.YES) ) throw new BadRequestException( locale.getString("www.error.registrationDisabled"), 403 ); if ( user != null ) throw new BadRequestException( locale.getString("www.error.alreadyLoggedIn"), 403 ); final String todo = req.getArgument( "todo" ); // try and register the user? if ( todo.equals("register") ) registerUser(); // just show the register form else showUserRegister(); } /** * shows the form for the user to register * */ protected void showUserRegister() throws IOException, SQLException { final TUserRegister tpl = new TUserRegister(); getResponse().showHtml( tpl ); } /** * tries to register the user with the system, if all goes * well they'll see a page confirming this * * @param req the request object * @param res the response object * * @throws BadRequestException * @throws SQLException * @throws IOException * */ protected void registerUser() throws BadRequestException, SQLException, IOException { final Request req = getRequest(); final Locale locale = getLocale(); final Properties p = getProperties(); final String name = req.getArgument( "name" ).trim(); final String pass1 = req.getArgument( "pass1" ); final String pass2 = req.getArgument( "pass2" ); final String email = req.getArgument( "email" ).trim(); final Database db = getDatabase(); final Validater v = new Validater( db ); if ( !v.checkRequiredFields(new String[]{name,pass1,email}) ) throw new BadRequestException( locale.getString("www.error.missingField") ); if ( !pass1.equals(pass2) ) throw new BadRequestException( locale.getString("www.error.passwordsDontMatch") ); if ( !v.isValidEmail(email) ) throw new BadRequestException( locale.getString("www.error.invalidEmail") ); if ( v.usernameExists(name) ) throw new BadRequestException( locale.getString("www.error.duplicateUsername") ); if ( v.emailExists(email) ) throw new BadRequestException( locale.getString("www.error.duplicateEmail") ); User newUser = new User( -1, name, pass1, email ); newUser.setActive( !p.get(Constants.WWW_USERS_REQUIRE_ACTIVATION).equals(Properties.YES) ); newUser.save( db ); if ( newUser.isActive() ) { loginUser( name, pass1 ); } showUserRegistered( newUser ); } /** * shows the page with confirmation the user has registered * * @param newUser * * @throws java.io.IOException * */ protected void showUserRegistered( final User newUser ) throws IOException, SQLException { final TUserRegistered tpl = new TUserRegistered(); final Response res = getResponse(); res.setUser( newUser ); res.showHtml( tpl ); } /** * shows the login form initially, then tries to log the user * in if the right data is submitted * * @param req the request object * @param res the response object * */ public void login() throws IOException, SQLException, BadRequestException { final Request req = getRequest(); final User user = getUser(); final Locale locale = getLocale(); if ( user != null ) { log.debug( "User appears logged in: " +user.getId()+ " = '" +user.getName()+ "'" ); throw new BadRequestException( locale.getString("www.error.alreadyLoggedIn"), 403 ); } final String todo = req.getArgument( "todo" ); if ( todo.equals("login") ) loginUser(); else showUserLogin(); } /** * shows the page for users to log in * * @throws java.io.IOException * */ protected void showUserLogin() throws IOException, SQLException { final TUserLogin tpl = new TUserLogin(); getResponse().showHtml( tpl ); } /** * tries to log the user in. if all goes well then a session will be * created and they'll be redirected to the home page * * @param req the request object * @param res the response object * */ protected void loginUser() throws IOException, SQLException, BadRequestException { final Request req = getRequest(); final Response res = getResponse(); final String name = req.getArgument( "name" ); final String pass = req.getArgument( "pass" ); loginUser( name, pass ); res.redirect( getProperties().getUrl("/")); } /** * tried to log a user in and create a session for them. if the user isn't * valid then it'll throw a BadRequestException. if all goes well then a * User object will be returned for the user that was logged in. * * @param name * @param pass * * @return user that was logged in * * @throws java.sql.SQLException * @throws com.pugh.sockso.web.BadRequestException * */ protected void loginUser( final String name, final String pass ) throws SQLException, BadRequestException { log.debug( "Login user with '" +name+ "' identified by '" +pass+ "'" ); for ( final Authenticator auth : authenticators ) { try { if ( auth.authenticate(name,pass) ) { log.debug( "Authentication ok, creating session" ); final User user = findOrCreateUser( name, pass ); final Session sess = new Session( getDatabase(), getRequest(), getResponse() ); sess.create( user.getId() ); log.debug( "Session created!" ); return; } } catch ( final Exception e ) { log.debug( "Error authenticating: " +e.getMessage() ); throw new BadRequestException( e.getMessage() ); } } throw new BadRequestException( getLocale().getString( "www.error.loginFailed" ) ); } /** * Tries to find a user with the specified name, if they don't exist then * they are created with the specified password. The user is then returned. * * @param name * @param pass * * @return * * @throws SQLException * */ public User findOrCreateUser( final String name, final String pass ) throws SQLException { ResultSet rs = null; PreparedStatement st = null; try { final Database db = getDatabase(); final String sql = " select id, name " + " from users " + " where name = ? " + " limit 1 "; st = db.prepare( sql ); st.setString( 1, name ); rs = st.executeQuery(); if ( rs.next() ) { return new User( rs.getInt("id"), rs.getString("name") ); } else { final User user = new User( name, pass, "", false ); user.save( db ); return user; } } finally { Utils.close( rs ); Utils.close( st ); } } /** * logs a user out by setting the session cookies to expire now * * @param req the request object * @param res the response object * */ public void logout() throws IOException { final Response res = getResponse(); final Session sess = new Session( null, null, res ); sess.destroy(); res.redirect(getProperties().getUrl("/")); } }