/***************************************************************************************** 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.rest; import infosistema.openbaas.middleLayer.AppsMiddleLayer; import infosistema.openbaas.middleLayer.SessionMiddleLayer; import infosistema.openbaas.middleLayer.UsersMiddleLayer; import infosistema.openbaas.comunication.bound.InboundSocket; import infosistema.openbaas.data.Error; import infosistema.openbaas.data.Metadata; import infosistema.openbaas.data.Result; import infosistema.openbaas.data.models.Application; import infosistema.openbaas.data.models.User; import infosistema.openbaas.rest.AppResource.PATCH; import infosistema.openbaas.utils.Const; import infosistema.openbaas.utils.Log; import infosistema.openbaas.utils.Utils; import infosistema.openbaas.utils.encryption.PasswordEncryptionService; import java.security.NoSuchAlgorithmException; import java.security.spec.InvalidKeySpecException; import javax.servlet.http.HttpServletRequest; import javax.ws.rs.Consumes; import javax.ws.rs.GET; import javax.ws.rs.HeaderParam; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.Context; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo; import javax.ws.rs.core.Response.Status; import org.codehaus.jettison.json.JSONException; import org.codehaus.jettison.json.JSONObject; public class AccountResource { private UsersMiddleLayer usersMid; private SessionMiddleLayer sessionMid; private AppsMiddleLayer appsMid; private String appId; @Context UriInfo uriInfo; public AccountResource(String appId) { this.usersMid = UsersMiddleLayer.getInstance(); this.appId = appId; this.sessionMid = SessionMiddleLayer.getInstance(); this.appsMid = AppsMiddleLayer.getInstance(); } // *** CREATE *** // /** * Creates a user in the application, necessary fields: "password"; * and "email". sigup the user creating a session * * @param inputJsonObj * @return */ @Path("/signup") @POST @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public Response createUserAndLogin(JSONObject inputJsonObj, @Context UriInfo ui, @Context HttpHeaders hh) { Response response = null; MultivaluedMap<String, String> headerParams = hh.getRequestHeaders(); String email = null; String userName = null; String password = null; Boolean baseLocationOption = null; String baseLocation = null; String userFile = null; String appKey = null; Boolean readOk = false; String location = null; try { appKey = headerParams.getFirst(Application.APP_KEY); } catch (Exception e) { } try { location = headerParams.getFirst(Const.LOCATION); } catch (Exception e) { } if(appKey==null) return Response.status(Status.BAD_REQUEST).entity("App Key not found").build(); if(!appsMid.authenticateApp(appId,appKey)) return Response.status(Status.UNAUTHORIZED).entity("Wrong App Key").build(); if(!appsMid.appExists(appId)) return Response.status(Status.NOT_FOUND).entity("{\"App\": "+appId+"}").build(); try { userName = (String) inputJsonObj.opt("userName"); userFile = (String) inputJsonObj.opt("userFile"); email = (String) inputJsonObj.get("email"); password = (String) inputJsonObj.get("password"); baseLocationOption = (Boolean) inputJsonObj.opt("baseLocationOption"); if (baseLocationOption == null) baseLocationOption=false; baseLocation = (String) inputJsonObj.opt("baseLocation"); readOk = true; } catch (JSONException e) { Log.error("", this, "createUserAndLogin", "Error parsing the JSON.", e); return Response.status(Status.BAD_REQUEST).entity("Error parsing the JSON.").build(); } if (readOk) { if (!usersMid.userEmailExists(appId, email)) { if (uriInfo == null) uriInfo = ui; Result res = usersMid.createUserAndLogin(headerParams, ui,appId, userName, email, password, userFile, baseLocationOption, baseLocation, Metadata.getNewMetadata(location)); ((User)res.getData()).setSocketPort(InboundSocket.getNextPort().toString()); response = Response.status(Status.CREATED).entity(res).build(); } else { response = Response.status(Status.FORBIDDEN).entity(new Error("email exists" +email)).build(); } } else { response = Response.status(Status.BAD_REQUEST).entity(new Error("")).build(); } return response; } /** * Creates a user session and returns de session Identifier (generated by * the server). Required fields: "email", "password". * * @param req * @param inputJsonObj * @return */ @Path("/signin") @POST @Consumes({ MediaType.APPLICATION_JSON }) @Produces(MediaType.APPLICATION_JSON) public Response createSession(@Context HttpServletRequest req, JSONObject inputJsonObj, @Context UriInfo ui, @Context HttpHeaders hh) { MultivaluedMap<String, String> headerParams = hh.getRequestHeaders(); String email = null; // user inserted fields String attemptedPassword = null; // user inserted fields Response response = null; User outUser = new User(); String userAgent = null; String location = null; String appKey = null; Boolean refreshCode = false; String lastLocation =null; try { email = (String) inputJsonObj.get("email"); attemptedPassword = (String) inputJsonObj.get("password"); } catch (JSONException e) { Log.error("", this, "createSession", "Error parsing the JSON.", e); return Response.status(Status.BAD_REQUEST).entity(new Error("Error reading JSON")).build(); } try { location = headerParams.getFirst(Const.LOCATION); } catch (Exception e) { } try { userAgent = headerParams.getFirst(Const.USER_AGENT); } catch (Exception e) { } try { appKey = headerParams.getFirst(Application.APP_KEY); } catch (Exception e) { } if(appKey==null) return Response.status(Status.BAD_REQUEST).entity("App Key not found").build(); if(!appsMid.authenticateApp(appId,appKey)) return Response.status(Status.UNAUTHORIZED).entity("Wrong App Key").build(); if(email == null && attemptedPassword == null) return Response.status(Status.BAD_REQUEST).entity("Error reading JSON").build(); Result res = usersMid.getUserUsingEmail(appId, email); outUser = (User)res.getData(); if (outUser != null && outUser.get_id() != null) { boolean usersConfirmedOption = usersMid.getConfirmUsersEmailOption(appId); // Remember the order of evaluation in java if (usersConfirmedOption) { if (usersMid.userEmailIsConfirmed(appId, outUser.get_id())) { String sessionToken = Utils.getRandomString(Const.getIdLength()); boolean validation = sessionMid.createSession(sessionToken, appId, outUser.get_id(), attemptedPassword); sessionMid.refreshSession(sessionToken, location, userAgent); lastLocation = usersMid.updateUserLocation(outUser.get_id(), appId, location, Metadata.getNewMetadata(location)); if(lastLocation==null) lastLocation = outUser.getLocation(); refreshCode = true; if (validation && refreshCode) { outUser.set_id(outUser.get_id()); outUser.setReturnToken(sessionToken); outUser.setEmail(email); outUser.setUserName(outUser.getUserName()); outUser.setUserFile(outUser.getUserFile()); outUser.setBaseLocation(outUser.getBaseLocation()); outUser.setBaseLocationOption(outUser.getBaseLocationOption()); outUser.setLocation(lastLocation); outUser.setOnline("true"); outUser.setSocketPort(InboundSocket.getNextPort().toString()); response = Response.status(Status.OK).entity(res).build(); } } else { response = Response.status(Status.FORBIDDEN).entity(new Error(Const.getEmailConfirmationError())).build(); } } else { String sessionToken = Utils.getRandomString(Const.getIdLength()); boolean validation = sessionMid.createSession(sessionToken, appId, outUser.get_id(), attemptedPassword); if(validation){ refreshCode = sessionMid.refreshSession(sessionToken, location, userAgent); lastLocation = usersMid.updateUserLocation(outUser.get_id(), appId, location, Metadata.getNewMetadata(location)); if (validation && refreshCode) { outUser.set_id(outUser.get_id()); outUser.setReturnToken(sessionToken); outUser.setEmail(email); outUser.setUserName(outUser.getUserName()); outUser.setUserFile(outUser.getUserFile()); outUser.setBaseLocation(outUser.getBaseLocation()); outUser.setBaseLocationOption(outUser.getBaseLocationOption()); outUser.setLocation(lastLocation); outUser.setOnline("true"); outUser.setSocketPort(InboundSocket.getNextPort().toString()); response = Response.status(Status.OK).entity(res).build(); } } else { response = Response.status(Status.UNAUTHORIZED).entity(new Error("")).build(); } } } else { response = Response.status(Status.NOT_FOUND).entity(new Error("")).build(); } return response; } // *** UPDATE *** // @PATCH @Path("/sessions/{sessionToken}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) public Response patchSession( @HeaderParam(Const.USER_AGENT) String userAgent, @HeaderParam(Const.LOCATION) String location, @PathParam(Const.SESSION_TOKEN) String sessionToken) { Response response = null; if (sessionMid.sessionTokenExists(sessionToken)) { String userId = sessionMid.getUserIdUsingSessionToken(sessionToken); Result res = usersMid.getUserInApp(appId, userId); User user = (User)res.getData(); if (!sessionMid.checkAppForToken(sessionToken, appId)) return Response.status(Status.UNAUTHORIZED).entity(new Error("Action in wrong app: "+appId)).build(); if (location != null) { String lastLocation = usersMid.updateUserLocation(userId, appId, location, Metadata.getNewMetadata(location)); user.setLocation(lastLocation); Metadata meta = (Metadata)res.getMetadata(); meta.setLocation(lastLocation); sessionMid.refreshSession(sessionToken, location, userAgent); response = Response.status(Status.OK).entity(res).build(); } // if the device does not have the gps turned on we should not // refresh the session. // only refresh it when an action is performed. response = Response.status(Status.OK).entity(("SessionToken: "+sessionToken)).build(); } else response = Response.status(Status.FORBIDDEN).entity(new Error("You do not have permission to access.")).build(); return response; } // *** DELETE *** // /** * Deletes a session (signout). * * @param sessionToken * @return@Context HttpHeaders hh */ @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) @POST @Path("/signout") public Response deleteSession(JSONObject inputJsonObj, @Context HttpHeaders hh) { Response response = null; String sessionToken = null; MultivaluedMap<String, String> headerParams = hh.getRequestHeaders(); sessionToken = headerParams.getFirst(Const.SESSION_TOKEN); Boolean flagAll = (Boolean) inputJsonObj.optBoolean("all",false); String userId = sessionMid.getUserIdUsingSessionToken(sessionToken); if(userId!=null){ if (sessionMid.sessionTokenExists(sessionToken)) { if(!flagAll){ if (!sessionMid.checkAppForToken(sessionToken, appId)) return Response.status(Status.UNAUTHORIZED).entity(new Error("Action in wrong app: "+appId)).build(); if (sessionMid.deleteUserSession(sessionToken, userId)){ Result res = new Result("Signout OK", null); response = Response.status(Status.OK).entity(res).build(); } else{ response = Response.status(Status.NOT_FOUND).entity(new Error("Not found")).build(); } }else{ if (!sessionMid.checkAppForToken(sessionToken, appId)) return Response.status(Status.UNAUTHORIZED).entity(new Error("Action in wrong app: "+appId)).build(); //deletes all sessions user boolean sucess = sessionMid.deleteAllUserSessions(userId); if (sucess){ Result res = new Result("Signout OK", null); response = Response.status(Status.OK).entity(res).build(); } else response = Response.status(Status.NOT_FOUND).entity(new Error("No sessions exist")).build(); } } } else response = Response.status(Status.FORBIDDEN).entity(new Error("No user found")).build(); return response; } // *** GET LIST *** // // *** GET *** // /** * Gets the session fields associated with thif (!sessionMid.checkAppForToken(sessionToken, appId)) return Response.status(Status.UNAUTHORIZED).entity(new Error("Action in wrong app: "+appId)).build();e token. * * @param sessionToken * @return */ @GET @Produces({ MediaType.APPLICATION_JSON }) @Path("/sessions/{sessionToken}") public Response getUserIdWithSession( @PathParam(Const.SESSION_TOKEN) String sessionToken) { Response response = null; if (sessionMid.sessionTokenExists(sessionToken)) { String userId = sessionMid.getUserIdUsingSessionToken(sessionToken); if (!sessionMid.checkAppForToken(sessionToken, appId)) return Response.status(Status.UNAUTHORIZED).entity(new Error("Action in wrong app: "+appId)).build(); Result res = new Result(userId, null); response = Response.status(Status.OK).entity(res).build(); } else response = Response.status(Status.NOT_FOUND).entity(new Error(sessionToken)).build(); return response; } /**@HeaderParam(value = Const.LOCATION) String location * Gets the session fields associated with the token. * * @param sessionToken * @return */ @GET @Produces({ MediaType.APPLICATION_JSON }) @Path("/sessions") public Response getSessionFields(@Context HttpHeaders hh) { Response response = null; String sessionToken = null; MultivaluedMap<String, String> headerParams = hh.getRequestHeaders(); sessionToken = headerParams.getFirst(Const.SESSION_TOKEN); if (sessionMid.sessionTokenExists(sessionToken)) { String userId = sessionMid.getUserIdUsingSessionToken(sessionToken); if (!sessionMid.checkAppForToken(sessionToken, appId)) return Response.status(Status.UNAUTHORIZED).entity(new Error("Action in wrong app: "+appId)).build(); Result res = usersMid.getUserInApp(appId, userId); User outUser = (User)res.getData(); outUser.setReturnToken(sessionToken); response = Response.status(Status.OK).entity(res).build(); } else response = Response.status(Status.NOT_FOUND).entity(new Error("Token NOT_FOUND")).build(); return response; } // *** OTHERS *** // @POST @Path("/recovery") @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public Response makeRecoveryRequest(JSONObject inputJson, @Context UriInfo ui, @Context HttpHeaders hh, @HeaderParam(value = Const.LOCATION) String location, @HeaderParam(value = Application.APP_KEY) String appKey){ Response response = null; String email = null; String newPass = Utils.getRandomString(Const.getPasswordLength()); byte[] salt = null; byte[] hash = null; try { email = (String) inputJson.get("email"); } catch (JSONException e) { Log.error("", this, "makeRecoveryRequest", "Error parsing the JSON.", e); } PasswordEncryptionService service = new PasswordEncryptionService(); try { salt = service.generateSalt(); hash = service.getEncryptedPassword(newPass, salt); } catch (NoSuchAlgorithmException e) { Log.error("", this, "makeRecoveryRequest", "Hashing Algorithm failed, please review the PasswordEncryptionService.", e); } catch (InvalidKeySpecException e) { Log.error("", this, "makeRecoveryRequest", "Invalid Key.", e); } String userId = usersMid.getUserIdUsingEmail(appId, email); if(userId==null) return Response.status(Status.BAD_REQUEST).entity(new Error("Wrong email.")).build(); if(appKey==null) return Response.status(Status.BAD_REQUEST).entity("App Key not found").build(); if(!appsMid.authenticateApp(appId,appKey)) return Response.status(Status.UNAUTHORIZED).entity("Wrong App Key").build(); boolean opOk = usersMid.recoverUser(appId, userId, email, ui, newPass, hash, salt, Metadata.getNewMetadata(location)); if(opOk){ Result res = new Result("Email sent with recovery details.", null); response = Response.status(Status.OK).entity(res).build(); } else response = Response.status(Status.BAD_REQUEST).entity(new Error("Wrong email.")).build(); return response; } @POST @Path("/changepassword") @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public Response changePasswordRequest(JSONObject inputJsonObj, @Context UriInfo ui, @Context HttpHeaders hh){ Response response = null; MultivaluedMap<String, String> headerParams = hh.getRequestHeaders(); String oldPassword = null; String newPassword = null; String userAgent = null; String location = null; try { newPassword = (String) inputJsonObj.get("newPassword"); oldPassword = (String) inputJsonObj.get("oldPassword"); } catch (JSONException e) { Log.error("", this, "createSession", "Error parsing the JSON.", e); return Response.status(Status.BAD_REQUEST).entity(new Error("Error reading JSON")).build(); } try { location = headerParams.getFirst(Const.LOCATION); } catch (Exception e) { } try { userAgent = headerParams.getFirst(Const.USER_AGENT); } catch (Exception e) { } try{ String sessionToken = Utils.getSessionToken(hh); String userId = sessionMid.getUserIdUsingSessionToken(sessionToken); if (!sessionMid.checkAppForToken(Utils.getSessionToken(hh), appId)) return Response.status(Status.UNAUTHORIZED).entity(new Error("Action in wrong app: "+appId)).build(); Boolean auth = sessionMid.authenticateUser(appId, userId, oldPassword); if(auth){ usersMid.updateUserPassword(appId, userId, newPassword, Metadata.getNewMetadata(location)); if(location!=null) sessionMid.refreshSession(sessionToken, location, userAgent); response = Response.status(Status.OK).entity("Passoword correctly changed.").build(); }else response = Response.status(Status.BAD_REQUEST).entity(new Error("Wrong old password.")).build(); }catch (Exception e) { return Response.status(Status.INTERNAL_SERVER_ERROR).entity(new Error("INTERNAL_SERVER_ERROR")).build(); } return response; } // *** RESOURCES *** // /** * Launches the resource integration requests. * * @param appId * @return */ @Path("integration") public IntegrationResource integration() { try { return new IntegrationResource(appId); } catch (IllegalArgumentException e) { Log.error("", this, "integration", "Illegal Argument.", e); throw new WebApplicationException(Response.status(Status.BAD_REQUEST).entity(new Error("Parse error")).build()); } } }