/* * Copyright (c) 2013 GigaSpaces Technologies Ltd. All rights reserved * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package models; import com.thoughtworks.xstream.annotations.XStreamAlias; import com.thoughtworks.xstream.annotations.XStreamAsAttribute; import com.thoughtworks.xstream.annotations.XStreamOmitField; import controllers.WidgetAdmin; import org.apache.commons.lang.BooleanUtils; import org.apache.commons.lang3.StringUtils; import org.codehaus.jackson.annotate.JsonIgnore; import play.cache.Cache; import play.data.validation.Constraints.Required; import play.db.ebean.Model; import play.i18n.Messages; import play.mvc.Http; import server.ApplicationContext; import server.exceptions.ServerException; import javax.persistence.*; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.UUID; /** * This class creates on sign-up, serves for authentication and keeps information about login and user's widgets. * * @author Igor Goldenberg * @see WidgetAdmin */ @Entity @XStreamAlias("user") public class User extends Model { private static final long serialVersionUID = 1L; /** instantiated on user's sign-in/sign-out */ @XStreamAlias("session") final public static class Session { @XStreamAsAttribute private String authToken; @XStreamAsAttribute private String expires; @XStreamAsAttribute private Boolean admin; private Session( String authToken, String expires, Boolean admin ) { this.authToken = authToken; this.expires = expires; this.admin = admin; } public String getAuthToken() { return authToken; } public String getExpires() { return expires; } public boolean getAdmin(){ // for JSON libraries return BooleanUtils.isTrue( admin ); } @JsonIgnore public boolean isAdmin() { return BooleanUtils.isTrue( admin ); } } @Id @XStreamOmitField private Long id; @ManyToOne( cascade = CascadeType.REMOVE, fetch = FetchType.EAGER ) private UserPermissions permissions = new UserPermissions(); @XStreamOmitField private String firstName; @XStreamOmitField private String lastName; @Required @Column( unique = true ) @XStreamAsAttribute private String email; @Required @JsonIgnore @XStreamOmitField private String password; @JsonIgnore private String authToken; private String expires; @JsonIgnore @XStreamOmitField private Boolean admin; @JsonIgnore // @JsonManagedReference @OneToMany(cascade= CascadeType.ALL, mappedBy = "user") private List<Widget> widgets; public static enum Role{ ADMIN, USER } public static Finder<Long,User> find = new Finder<Long,User>(Long.class, User.class); public User(String email, String password) { this(null, null, email, password); } public String getFullName(){ StringBuilder b = new StringBuilder( ); boolean hasFirstName = false; if ( !StringUtils.isEmpty(firstName) ){ b.append( firstName ); hasFirstName = true; } if ( !StringUtils.isEmpty( lastName )){ if ( hasFirstName ){ b.append( " " ); } b.append( lastName ); } return b.length() == 0 ? email : b.toString(); } public User(String firstName, String lastName, String email, String password) { this.firstName = firstName; this.lastName = lastName; this.email = email; encryptAndSetPassword( password ); this.admin = false; } public Widget createNewWidget( String productName, String productVersion, String title, String youtubeVideoUrl, String providerURL, String recipeURL, String consolename, String consoleURL, String recipeRootPath ) { Widget widget = new Widget( productName, productVersion, title, youtubeVideoUrl, providerURL, recipeURL, consolename, consoleURL, recipeRootPath ); // guy - removing "setUsername" - it is unclear what that was.. // if we want Widget to refer to a user, we should use a foreign key.. return addNewWidget( widget ); } public Widget addNewWidget( Widget widget ){ if ( widgets == null ){ widgets = new ArrayList<Widget>( ); } widgets.add( widget ); save( ); widget.refresh(); return widget; } /** * Create new account. * * @return An authenticator key or error if user already exists. */ static public User newUser( String firstName, String lastName, String email, String password ) { User user = find.where().ilike("email", email).findUnique(); if ( user == null ) { user = new User( firstName, lastName, email, password ); // TODO real expiration createAuthToken(user); user.save(); } else throw new ServerException( Messages.get( "user.already.exists", email )); return user; } // TODO create a real authToken mechanism with expiration: currTime + 1 hour static private void createAuthToken( User user ) { user.renewAuthToken(); user.setExpires(new Date().toString()); } private void renewAuthToken(){ setAuthToken( UUID.randomUUID().toString() ); } public boolean comparePassword( String password ){ if ( StringUtils.equals( password, this.password )){ // backward compatibility.. return true; } return ApplicationContext.get().getHmac().compare( this.password, password ); } public void encryptAndSetPassword( String unencryptedPassword ){ password = ApplicationContext.get().getHmac().sign( unencryptedPassword ); } public static User findById( Long pi ) { return User.find.where().eq( "id", pi ).findUnique(); } static public User authenticate( String email, String password ) { User user = find.where().eq("email", email).findUnique(); if ( user == null ) { return null; } if ( StringUtils.equals( user.getPassword(), password ) ){ // we should encrypt user.encryptAndSetPassword( password ); user.save( ); } else if ( !user.comparePassword( password )){ return null; } return user; } static public User validateAuthToken( String authToken ) { return validateAuthToken( authToken, false ); } // guy - temporarily, the "session" is artificially made with expiring the authtoken. // however, this should not be the case.. We need to create another controller to serve our GUI // which will be separated from the REST API - like all REST clients.. // unlike all the rest APIs we have the ability yo skip the network overhead, and communicate directly with the REST API. // so we can simply invoke methods. However, the "session" should be handled by the GUI controller and not Javascript. static public User validateAuthToken( String authToken, boolean silent ) { return validateAuthToken(authToken, silent, Http.Context.current()); } static public User validateAuthToken( String authToken, boolean silent, Http.Context context ){ Long userId = null; if ( ApplicationContext.get().conf().settings.expireSession ) { userId = ( Long ) Cache.get( authToken ); // for now, lets use the cache as session. we should implement an encrypted cookie. } else { User u = User.find.where().eq( "authToken", authToken ).findUnique(); if ( u != null ) { userId = u.id; } } User user = null; if ( userId == null ) { if ( !silent && context != null) { context.response().setHeader( "session-expired", "session-expired" ); throw new ServerException( Messages.get( "session.expired" ) ).getResponseDetails().setHeaderKey( "session-expired" ).setError( "Session Expired" ).done(); } } else { user = User.find.byId( userId ); if ( user == null && !silent ) { throw new ServerException( Messages.get( "auth.token.not.valid", authToken ) ); } if ( context != null ){ context.session().put( "authToken", authToken ); prolongSession( authToken, user.id ); } } return user; } // makes the session longer. expiration is only if user is idle. private static void prolongSession( String authToken, Long userId ) { Cache.set( authToken, userId, ( int ) (ApplicationContext.get().conf().server.sessionTimeoutMillis / 1000) ); // cache for one hour } static public List<User> getAllUsers() { return find.all(); } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getPassword() { return password; } static public List<User> all() { return find.all(); } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public List<Widget> getWidgets() { return widgets; } public void setWidgets(List<Widget> widgets) { this.widgets = widgets; } public Session getSession() { return new Session(authToken, expires, admin); } public String getAuthToken() { return authToken; } public void setAuthToken(String authToken) { this.authToken = authToken; } public String getExpires() { return expires; } public void setExpires(String expires) { this.expires = expires; } @JsonIgnore public boolean isAdmin() { return BooleanUtils.isTrue( admin ); } public void setAdmin(Boolean admin) { this.admin = admin; } public String toDebugString() { return email + " (" + id + ")"; } public UserPermissions getPermissions() { return permissions; } public void setPermissions(UserPermissions permissions) { this.permissions = permissions; } @Override public String toString() { return "User{" + "id=" + id + ", firstName='" + firstName + '\'' + ", lastName='" + lastName + '\'' + ", email='" + email + '\'' + '}'; } }