/*****************************************************************************************
Infosistema - OpenBaas
Copyright(C) 2002-2014 Infosistema, S.A.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
www.infosistema.com
info@openbaas.com
Av. José Gomes Ferreira, 11 3rd floor, s.34
Miraflores
1495-139 Algés Portugal
****************************************************************************************/
package infosistema.openbaas.middleLayer;
import infosistema.openbaas.data.Metadata;
import infosistema.openbaas.data.Result;
import infosistema.openbaas.data.enums.ModelEnum;
import infosistema.openbaas.data.models.User;
import infosistema.openbaas.dataaccess.email.Email;
import infosistema.openbaas.dataaccess.models.ModelAbstract;
import infosistema.openbaas.dataaccess.models.SessionModel;
import infosistema.openbaas.utils.Const;
import infosistema.openbaas.utils.Log;
import infosistema.openbaas.utils.Utils;
import infosistema.openbaas.utils.encryption.PasswordEncryptionService;
import java.io.UnsupportedEncodingException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.UriInfo;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;
import com.mongodb.DBObject;
import com.mongodb.util.JSON;
public class UsersMiddleLayer extends MiddleLayerAbstract {
// *** MEMBERS *** //
SessionModel sessions;
Email emailOp;
// *** INSTANCE *** //
private static UsersMiddleLayer instance = null;
public static UsersMiddleLayer getInstance() {
if (instance == null) instance = new UsersMiddleLayer();
return instance;
}
private UsersMiddleLayer() {
super();
sessions = SessionModel.getInstance();
emailOp = Email.getInstance();
}
// *** PRIVATE *** //
private Map<String, String> getUserFields(String userName, String socialId, String socialNetwork,
String email, String userFile, byte[] salt, byte[] hash, Boolean emailConfirmed,
Boolean baseLocationOption, String baseLocation, Boolean alive, String location) throws UnsupportedEncodingException {
Map<String, String> fields = new HashMap<String, String>();
if (userName != null)
fields.put(User.USER_NAME, userName);
if (socialId != null && socialNetwork != null)
fields.put(User.SOCIAL_NETWORK_ID(socialNetwork), socialId);
if (email != null)
fields.put(User.EMAIL, email);
if (userFile != null)
fields.put(User.USER_FILE, userFile);
if (salt != null)
fields.put(User.SALT, new String(salt, "ISO-8859-1"));
if (hash != null)
fields.put(User.HASH, new String(hash, "ISO-8859-1"));
if (alive != null)
fields.put(User.ALIVE, alive.toString());
if (baseLocationOption != null)
fields.put(User.BASE_LOCATION_OPTION, baseLocationOption.toString());
if (baseLocation != null)
fields.put(User.BASE_LOCATION, baseLocation);
if (location != null)
fields.put(Const.LOCATION, location);
if (emailConfirmed != null)
fields.put(User.EMAIL_CONFIRMED, "" + emailConfirmed);
return fields;
}
// *** CREATE *** //
public Result createUserAndLogin(MultivaluedMap<String, String> headerParams, UriInfo uriInfo, String appId,
String userName, String email, String password, String userFile, Boolean baseLocationOption,
String baseLocation, Map<String, String> extraMetadata) {
User outUser = new User();
Result res = null;
if (baseLocationOption == null) baseLocationOption = false;
String userId = null;
String userAgent = null;
String location = null;
String lastLocation =null;
userId = Utils.getRandomString(Const.getIdLength());
while (userModel.userIdExists(appId, userId))
userId = Utils.getRandomString(Const.getIdLength());
byte[] salt = null;
byte[] hash = null;
PasswordEncryptionService service = new PasswordEncryptionService();
try {
salt = service.generateSalt();
hash = service.getEncryptedPassword(password, salt);
} catch (NoSuchAlgorithmException e) {
Log.error("", this, "createUserAndLogin", "Hashing Algorithm failed, please review the PasswordEncryptionService.", e);
} catch (InvalidKeySpecException e) {
Log.error("", this, "createUserAndLogin", "Invalid Key.", e);
}
try {
location = headerParams.getFirst(Const.LOCATION);
} catch (Exception e) { }
try {
userAgent = headerParams.getFirst(Const.USER_AGENT);
} catch (Exception e) { }
if(baseLocationOption)
if(location!=null)
location = baseLocation;
if (!getConfirmUsersEmailOption(appId)) {
SessionMiddleLayer sessionMid = SessionMiddleLayer.getInstance();
res = createUser(appId, userId, userName, "NOK", "SocialNetwork", email, salt, hash, userFile, null, null,
baseLocationOption, baseLocation, location, extraMetadata);
String sessionToken = Utils.getRandomString(Const.getIdLength());
boolean validation = sessionMid.createSession(sessionToken, appId, userId, password);
Boolean refresh = sessionMid.refreshSession(sessionToken, location, userAgent);
lastLocation = updateUserLocation(userId, appId, location, extraMetadata);
if(lastLocation==null)
lastLocation = outUser.getLocation();
if (validation && refresh) {
outUser.set_id(userId);
outUser.setReturnToken(sessionToken);
outUser.setEmail(email);
outUser.setUserName(userName);
outUser.setUserFile(userFile);
outUser.setBaseLocation(baseLocation);
outUser.setBaseLocationOption(baseLocationOption.toString());
outUser.setLocation(lastLocation);
outUser.setOnline("true");
}
} else if (getConfirmUsersEmailOption(appId)) {
boolean emailConfirmed = false;
res = createUser(appId, userId, userName, "NOK", "SocialNetwork", email, salt,hash, userFile, emailConfirmed,
uriInfo, baseLocationOption, baseLocation, location, extraMetadata);
outUser.set_id(userId);
outUser.setEmail(email);
outUser.setUserName(userName);
outUser.setUserFile(userFile);
outUser.setBaseLocation(baseLocation);
outUser.setBaseLocationOption(baseLocationOption.toString());
outUser.setLocation(location);
outUser.setOnline("true");
}
return new Result(outUser, res.getMetadata());
}
public Result createSocialUserAndLogin(MultivaluedMap<String, String> headerParams, String appId,
String userName, String email, String socialId, String socialNetwork, Map<String, String> extraMetadata) {
User outUser = new User();
String userId = null;
String userAgent = null;
String location = null;
String lastLocation =null;
Result res = null;
userId = Utils.getRandomString(Const.getIdLength());
while (userModel.userIdExists(appId, userId))
userId = Utils.getRandomString(Const.getIdLength());
byte[] salt = null;
byte[] hash = null;
PasswordEncryptionService service = new PasswordEncryptionService();
try {
salt = service.generateSalt();
hash = service.getEncryptedPassword(socialId, salt);
} catch (NoSuchAlgorithmException e) {
Log.error("", this, "createSocialUserAndLogin", "Hashing Algorithm failed, please review the PasswordEncryptionService.", e);
} catch (InvalidKeySpecException e) {
Log.error("", this, "createSocialUserAndLogin", "Invalid Key.", e);
}
SessionMiddleLayer sessionMid = SessionMiddleLayer.getInstance();
res = createUser(appId, userId, userName, socialId, socialNetwork, email, salt, hash, null, null, null, false, null, null, extraMetadata);
String sessionToken = Utils.getRandomString(Const.getIdLength());
boolean validation = sessionMid.createSession(sessionToken, appId, userId, socialId);
try {
location = headerParams.getFirst(Const.LOCATION);
} catch (Exception e) { }
try {
userAgent = headerParams.getFirst(Const.USER_AGENT);
} catch (Exception e) { }
sessionMid.refreshSession(sessionToken, location, userAgent);
lastLocation = updateUserLocation(userId, appId, location, extraMetadata);
if(lastLocation==null)
lastLocation = outUser.getLocation();
if (validation) {
outUser.set_id(userId);
outUser.setEmail(email);
outUser.setUserName(userName);
outUser.setBaseLocationOption("false");
outUser.setLocation(lastLocation);
outUser.setReturnToken(sessionToken);
outUser.setOnline("true");
}
return new Result(outUser,res.getMetadata());
}
/**
* Password already comes hashed, it's safer than having the password
* floating around.
*
* @param appId
* @param userId
* @param email
* @param email2
* @param password
* @return
*/
public Result createUser(String appId, String userId, String userName, String socialId, String socialNetwork,
String email, byte[] salt, byte[] hash, String userFile, Boolean emailConfirmed, UriInfo uriInfo,
Boolean baseLocationOption, String baseLocation, String location, Map<String, String> extraMetadata) {
try {
Metadata metadata = null;
Object data = null;
Map<String, String> fields = getUserFields(userName, socialId, socialNetwork, email, userFile, salt, hash, emailConfirmed,
baseLocationOption, baseLocation, true, location);
data = userModel.createUser(appId, userId, fields, extraMetadata);
metadata = Metadata.getMetadata(new JSONObject(((JSONObject) data).getString(ModelAbstract._METADATA)));
((JSONObject) data).remove(ModelAbstract._METADATA);
data = (DBObject)JSON.parse(data.toString());
String ref = Utils.getRandomString(Const.getIdLength());
if (uriInfo != null) {
emailOp.sendRegistrationEmailWithRegistrationCode(appId, userId, userName, email, ref, uriInfo.getAbsolutePath().toASCIIString());
}
this.emailOp.addUrlToUserId(appId, userId, ref);
return new Result(data, metadata);
} catch (Exception e) {
Log.error("", this, "createUser", "An error ocorred.", e);
}
return null;
}
// *** UPDATE *** //
public Result updateUser(String appId, String userId, String userName, String email, String userFile,
Boolean baseLocationOption, String baseLocation, String location, Map<String, String> extraMetadata) {
Metadata metadata = null;
Object data = null;
try {
Map<String, String> fields = getUserFields(userName, null, null, email, userFile, null, null, null, baseLocationOption, baseLocation, true, location);
data = userModel.updateUser(appId, userId, fields, extraMetadata);
metadata = Metadata.getMetadata(new JSONObject(((JSONObject) data).getString(ModelAbstract._METADATA)));
((JSONObject) data).remove(ModelAbstract._METADATA);
data = (DBObject)JSON.parse(data.toString());
} catch (Exception e) {
Log.error("", this, "updateUser", "updateUser.", e);
return null;
}
return new Result(data, metadata);
}
public Result updateUserPassword(String appId, String userId, String password, Map<String, String> extraMetadata) {
byte[] salt = null;
byte [] hash = null;
PasswordEncryptionService service = new PasswordEncryptionService();
try {
salt = service.generateSalt();
hash = service.getEncryptedPassword(password, salt);
if (appModel.appExists(appId) && userModel.userIdExists(appId, userId)) {
Metadata metadata = null;
Object data = null;
Map<String, String> fields = getUserFields(null, null, null, null, null, salt, hash, null, null, null, null, null);
data = userModel.updateUser(appId, userId, fields, extraMetadata);
metadata = Metadata.getMetadata(new JSONObject(((JSONObject) data).getString(ModelAbstract._METADATA)));
((JSONObject) data).remove(ModelAbstract._METADATA);
data = (DBObject)JSON.parse(data.toString());
return new Result(data, metadata);
}
} catch (Exception e) {
Log.error("", this, "updateUserPassword", "Unsupported Encoding.", e);
}
return null;
}
// *** DELETE *** //
public boolean deleteUserInApp(String appId, String userId) {
boolean operationOk = false;
try {
Map<String, String> fields = getUserFields(null, null, null, null, null, null, null, null, null, null, false, null);
operationOk = userModel.updateUser(appId, userId, fields, null) != null;
} catch (Exception e) {
Log.error("", this, "deleteUserInApp", "deleteUserInApp.", e);
}
return operationOk;
}
// *** GET LIST *** //
@Override
protected List<DBObject> getAllSearchResults(String appId, String userId, String url, Double latitude, Double longitude,
Double radius, JSONObject query, String orderType, String orderBy, ModelEnum type, List<String> toShow) throws Exception {
List<DBObject> result = null;
SessionModel sessionModel = SessionModel.getInstance();
Boolean online = false;
if (query == null || query.length() == 0) {
query = new JSONObject();
JSONObject jAux = new JSONObject();
jAux.put("$exists",1);
query.put(User.EMAIL, jAux);
query.put(User.HASH, jAux);
query.put(User.SALT, jAux);
}
result = docModel.getDocuments(appId, userId, url, latitude, longitude, radius, query, orderType, orderBy, toShow);
if(toShow.contains(User.ONLINE)){
Iterator<DBObject> it = result.iterator();
while(it.hasNext()){
DBObject dbo = it.next();
String _id = (String) dbo.get(User._ID);
online = sessionModel.isUserOnline(_id);
((DBObject) dbo.get(User.DATA)).put("online", online.toString());
}
}
return result;
}
// *** GET *** //
public Result getUserInApp(String appId, String userId) {
JSONObject user = userModel.getUser(appId, userId, true);
if (user == null) return null;
User data = new User(userId);
try {
if(user.has(User.USER_NAME))
data.setUserName(user.getString(User.USER_NAME));
if(user.has(User.USER_FILE))
data.setUserFile(user.getString(User.USER_FILE));
if(user.has(User.EMAIL))
data.setEmail(user.getString(User.EMAIL));
if(user.has(User.ALIVE))
data.setAlive(user.getString(User.ALIVE));
if(user.has(User.EMAIL_CONFIRMED))
data.setEmailConfirmed(user.getString(User.EMAIL_CONFIRMED));
if(user.has(User.BASE_LOCATION_OPTION))
data.setBaseLocationOption(user.getString(User.BASE_LOCATION_OPTION));
if(user.has(User.BASE_LOCATION))
data.setBaseLocation(user.getString(User.BASE_LOCATION));
if(user.has(User.LOCATION))
data.setLocation(user.getString(User.LOCATION));
if(user.has(User.ONLINE))
data.setOnline(user.getString(User.ONLINE));
Metadata metadata = null;
if (user.has(ModelAbstract._METADATA))
metadata = Metadata.getMetadata(new JSONObject(user.getString(ModelAbstract._METADATA)));
return new Result(data, metadata);
} catch (Exception e) {
Log.error("", this, "getUserInApp", "An error ocorred.", e);
}
return null;
}
public String getEmailUsingUserName(String appId, String userName) {
String userId = userModel.getUserIdUsingUserName(appId, userName);
return userModel.getUserField(appId, userId, User.EMAIL);
}
public String getUserIdUsingUserName(String appId, String userName) {
return userModel.getUserIdUsingUserName(appId, userName);
}
public String getUserIdUsingEmail(String appId, String email) {
return userModel.getUserIdUsingEmail(appId, email);
}
public Result getUserUsingEmail(String appId, String email) {
return getUserInApp(appId, userModel.getUserIdUsingEmail(appId, email));
}
// *** EXISTS *** //
public boolean userEmailExists(String appId, String email) {
return userModel.userEmailExists(appId, email);
}
public boolean userIdExists(String appId, String userId) {
return userModel.userIdExists(appId, userId);
}
public String socialUserExists(String appId, String socialId, String socialNetwork) {
if (userModel.socialUserExists(appId, socialId, socialNetwork))
return userModel.getUserIdUsingSocialInfo(appId, socialId,socialNetwork);
return null;
}
// *** METADATA *** //
// *** OTHERS *** //
public boolean getConfirmUsersEmailOption(String appId) {
if (appModel.appExists(appId))
return appModel.getConfirmUsersEmail(appId);
else
return false;
}
public String getUrlUserId(String appId, String userId) {
return this.emailOp.getUrlUserId(appId, userId);
}
public void removeUrlToUserId(String appId, String userId) {
this.emailOp.removeUrlToUserId(appId, userId);
}
public void confirmUserEmail(String appId, String userId, Map<String, String> extraMetadata) {
try {
Map<String, String> fields = getUserFields(null, null, null, null, null, null, null, true, null, null, null, null);
userModel.updateUser(appId, userId, fields, extraMetadata);
} catch (UnsupportedEncodingException e) {
}
}
public boolean userEmailIsConfirmed(String appId, String userId) {
try {
return Boolean.parseBoolean(userModel.getUserField(appId, userId, User.EMAIL_CONFIRMED));
} catch (Exception e) {
return !appModel.getConfirmUsersEmail(appId);
}
}
public boolean updateConfirmUsersEmailOption(String appId, Boolean confirmUsersEmail) {
boolean sucess = false;
if (appModel.appExists(appId)) {
appModel.updateAppFields(appId, null, null, confirmUsersEmail, null, null,null, null,null);
sucess = true;
}
return sucess;
}
public boolean recoverUser(String appId, String userId, String email, UriInfo uriInfo, String newPass,
byte[] hash, byte[] salt, Map<String, String> extraMetadata) {
boolean opOk = false;
try {
JSONObject user = userModel.getUser(appId, userId, false);
String dbEmail = null;
String userName = null;
dbEmail = user.getString(User.EMAIL);
userName = user.getString(User.USER_NAME);
if (email != null && newPass != null) {
try {
Map<String, String> fields = getUserFields(null, null, null, email, null, salt, hash, null, null, null, null, null);
userModel.updateUser(appId, userId, fields, extraMetadata);
} catch (UnsupportedEncodingException e) {
Log.error("", this, "updateUser", "Unsupported Encoding.", e);
}
}
if(dbEmail.equalsIgnoreCase(email)){
boolean emailOk =emailOp.sendRecoveryEmail(appId, userName, userId, email, newPass, uriInfo.getAbsolutePath().toASCIIString());
if(emailOk) {
opOk = true;
}
}
} catch (JSONException e) {
Log.error("", this, "recoverUser", "An error ocorred.", e);
}
return opOk;
}
public String getRecoveryCode(String appId, String userId) {
return this.emailOp.getRecoveryCodeOfUser(appId, userId);
}
public String updateUserLocation(String userId, String appId, String location, Map<String, String> extraMetadata) {
try{
User user = (User)getUserInApp(appId,userId).getData();
if ("true".equalsIgnoreCase(user.getBaseLocationOption())) {
location = user.getBaseLocation();
}
if(location!=null){
Map<String, String> fields = getUserFields(null, null, null, null, null, null, null, null, null, null, null, location);
userModel.updateUser(appId, userId, fields, extraMetadata);
}
}catch(Exception e){
Log.error("", this, "updateUserLocation", "updateUserLocation exception.", e);
}
return location;
}
}