/* * Copyright (c) 2010-2017, b3log.org & hacpai.com * * 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 org.b3log.solo.processor; import org.b3log.latke.Keys; import org.b3log.latke.Latkes; import org.b3log.latke.ioc.inject.Inject; import org.b3log.latke.logging.Level; import org.b3log.latke.logging.Logger; import org.b3log.latke.mail.MailService; import org.b3log.latke.mail.MailServiceFactory; import org.b3log.latke.model.Role; import org.b3log.latke.model.User; import org.b3log.latke.repository.RepositoryException; import org.b3log.latke.repository.Transaction; import org.b3log.latke.service.LangPropsService; import org.b3log.latke.service.ServiceException; import org.b3log.latke.servlet.HTTPRequestContext; import org.b3log.latke.servlet.HTTPRequestMethod; import org.b3log.latke.servlet.annotation.RequestProcessing; import org.b3log.latke.servlet.annotation.RequestProcessor; import org.b3log.latke.servlet.renderer.JSONRenderer; import org.b3log.latke.servlet.renderer.freemarker.AbstractFreeMarkerRenderer; import org.b3log.latke.user.UserService; import org.b3log.latke.user.UserServiceFactory; import org.b3log.latke.util.MD5; import org.b3log.latke.util.Requests; import org.b3log.latke.util.Sessions; import org.b3log.latke.util.Strings; import org.b3log.solo.SoloServletListener; import org.b3log.solo.model.Common; import org.b3log.solo.model.Option; import org.b3log.solo.processor.renderer.ConsoleRenderer; import org.b3log.solo.processor.util.Filler; import org.b3log.solo.repository.OptionRepository; import org.b3log.solo.service.*; import org.b3log.solo.util.Randoms; import org.json.JSONException; import org.json.JSONObject; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.Calendar; import java.util.Map; /** * Login/logout processor. * * <p>Initializes administrator</p>. * * @author <a href="http://88250.b3log.org">Liang Ding</a> * @author <a href="http://vanessa.b3log.org">Liyuan Li</a> * @author <a href="mailto:dongxu.wang@acm.org">Dongxu Wang</a> * @version 1.1.1.7, Nov 20, 2015 * @since 0.3.1 */ @RequestProcessor public class LoginProcessor { /** * Logger. */ private static final Logger LOGGER = Logger.getLogger(LoginProcessor.class.getName()); /** * User query service. */ @Inject private UserQueryService userQueryService; /** * User service. */ private UserService userService = UserServiceFactory.getUserService(); /** * Mail service. */ private MailService mailService = MailServiceFactory.getMailService(); /** * User management service. */ @Inject private UserMgmtService userMgmtService; /** * Language service. */ @Inject private LangPropsService langPropsService; /** * Filler. */ @Inject private Filler filler; /** * Preference query service. */ @Inject private PreferenceQueryService preferenceQueryService; /** * Option query service. */ @Inject private OptionQueryService optionQueryService; /** * Option management service. */ @Inject private OptionMgmtService optionMgmtService; /** * Option repository. */ @Inject private OptionRepository optionRepository; /** * Shows login page. * * @param context the specified context * @throws Exception exception */ @RequestProcessing(value = "/login", method = HTTPRequestMethod.GET) public void showLogin(final HTTPRequestContext context) throws Exception { final HttpServletRequest request = context.getRequest(); String destinationURL = request.getParameter(Common.GOTO); if (Strings.isEmptyOrNull(destinationURL)) { destinationURL = Latkes.getServePath() + Common.ADMIN_INDEX_URI; } final HttpServletResponse response = context.getResponse(); userMgmtService.tryLogInWithCookie(request, response); if (null != userService.getCurrentUser(request)) { // User has already logged in response.sendRedirect(destinationURL); return; } renderPage(context, "login.ftl", destinationURL, request); } /** * Logins. * * <p> * Renders the response with a json object, for example, * <pre> * { * "isLoggedIn": boolean, * "msg": "" // optional, exists if isLoggedIn equals to false * } * </pre> * </p> * * @param context the specified context */ @RequestProcessing(value = "/login", method = HTTPRequestMethod.POST) public void login(final HTTPRequestContext context) { final HttpServletRequest request = context.getRequest(); final JSONRenderer renderer = new JSONRenderer(); context.setRenderer(renderer); final JSONObject jsonObject = new JSONObject(); renderer.setJSONObject(jsonObject); try { jsonObject.put(Common.IS_LOGGED_IN, false); final String loginFailLabel = langPropsService.get("loginFailLabel"); jsonObject.put(Keys.MSG, loginFailLabel); final JSONObject requestJSONObject = Requests.parseRequestJSONObject(request, context.getResponse()); final String userEmail = requestJSONObject.getString(User.USER_EMAIL); final String userPwd = requestJSONObject.getString(User.USER_PASSWORD); if (Strings.isEmptyOrNull(userEmail) || Strings.isEmptyOrNull(userPwd)) { return; } LOGGER.log(Level.INFO, "Login[email={0}]", userEmail); final JSONObject user = userQueryService.getUserByEmail(userEmail); if (null == user) { LOGGER.log(Level.WARN, "Not found user[email={0}]", userEmail); return; } if (MD5.hash(userPwd).equals(user.getString(User.USER_PASSWORD))) { Sessions.login(request, context.getResponse(), user); LOGGER.log(Level.INFO, "Logged in[email={0}]", userEmail); jsonObject.put(Common.IS_LOGGED_IN, true); if (Role.VISITOR_ROLE.equals(user.optString(User.USER_ROLE))) { jsonObject.put("to", Latkes.getServePath()); } else { jsonObject.put("to", Latkes.getServePath() + Common.ADMIN_INDEX_URI); } jsonObject.remove(Keys.MSG); return; } LOGGER.log(Level.WARN, "Wrong password[{0}]", userPwd); } catch (final Exception e) { LOGGER.log(Level.ERROR, e.getMessage(), e); } } /** * Logout. * * @param context the specified context * @throws IOException io exception */ @RequestProcessing(value = "/logout", method = HTTPRequestMethod.GET) public void logout(final HTTPRequestContext context) throws IOException { final HttpServletRequest httpServletRequest = context.getRequest(); Sessions.logout(httpServletRequest, context.getResponse()); String destinationURL = httpServletRequest.getParameter(Common.GOTO); if (Strings.isEmptyOrNull(destinationURL)) { destinationURL = "/"; } context.getResponse().sendRedirect(destinationURL); } /** * Shows forgotten password page. * * @param context the specified context * @throws Exception exception */ @RequestProcessing(value = "/forgot", method = HTTPRequestMethod.GET) public void showForgot(final HTTPRequestContext context) throws Exception { final HttpServletRequest request = context.getRequest(); String destinationURL = request.getParameter(Common.GOTO); if (Strings.isEmptyOrNull(destinationURL)) { destinationURL = Latkes.getServePath() + Common.ADMIN_INDEX_URI; } renderPage(context, "reset-pwd.ftl", destinationURL, request); } /** * Resets forgotten password. * * <p> * Renders the response with a json object, for example, * <pre> * { * "isLoggedIn": boolean, * "msg": "" // optional, exists if isLoggedIn equals to false * } * </pre> * </p> * * @param context the specified context */ @RequestProcessing(value = "/forgot", method = HTTPRequestMethod.POST) public void forgot(final HTTPRequestContext context) { final HttpServletRequest request = context.getRequest(); final JSONRenderer renderer = new JSONRenderer(); context.setRenderer(renderer); final JSONObject jsonObject = new JSONObject(); renderer.setJSONObject(jsonObject); try { jsonObject.put("succeed", false); jsonObject.put(Keys.MSG, langPropsService.get("resetPwdSuccessMsg")); final JSONObject requestJSONObject = Requests.parseRequestJSONObject(request, context.getResponse()); final String userEmail = requestJSONObject.getString(User.USER_EMAIL); if (Strings.isEmptyOrNull(userEmail)) { LOGGER.log(Level.WARN, "Why user's email is empty"); return; } LOGGER.log(Level.INFO, "Login[email={0}]", userEmail); final JSONObject user = userQueryService.getUserByEmail(userEmail); if (null == user) { LOGGER.log(Level.WARN, "Not found user[email={0}]", userEmail); jsonObject.put(Keys.MSG, langPropsService.get("userEmailNotFoundMsg")); return; } sendResetUrl(userEmail, jsonObject); } catch (final Exception e) { LOGGER.log(Level.ERROR, e.getMessage(), e); } } /** * Resets forgotten password. * * <p> * Renders the response with a json object, for example, * <pre> * { * "isLoggedIn": boolean, * "msg": "" // optional, exists if isLoggedIn equals to false * } * </pre> * </p> * * @param context the specified context */ @RequestProcessing(value = "/reset", method = HTTPRequestMethod.POST) public void reset(final HTTPRequestContext context) { final HttpServletRequest request = context.getRequest(); final JSONRenderer renderer = new JSONRenderer(); context.setRenderer(renderer); final JSONObject jsonObject = new JSONObject(); renderer.setJSONObject(jsonObject); try { final JSONObject requestJSONObject; requestJSONObject = Requests.parseRequestJSONObject(request, context.getResponse()); final String userEmail = requestJSONObject.getString(User.USER_EMAIL); final String newPwd = requestJSONObject.getString("newPwd"); final JSONObject user = userQueryService.getUserByEmail(userEmail); user.put(User.USER_PASSWORD, newPwd); userMgmtService.updateUser(user); LOGGER.log(Level.DEBUG, "[{0}]'s password updated successfully.", userEmail); jsonObject.put("succeed", true); jsonObject.put("to", Latkes.getServePath() + "/login?from=reset"); jsonObject.put(Keys.MSG, langPropsService.get("resetPwdSuccessMsg")); } catch (final Exception e) { LOGGER.log(Level.ERROR, e.getMessage(), e); } } /** * Whether user is going to update an expired password out of 24 hours. * * @return whether the password has been expired TODO implement it */ private boolean isPwdExpired() { return false; } /** * Sends the password resetting URL with a random token. * * @param userEmail the given email * @param jsonObject return code and message object * @throws JSONException the JSONException * @throws ServiceException the ServiceException * @throws IOException the IOException * @throws RepositoryException the RepositoryException */ private void sendResetUrl(final String userEmail, final JSONObject jsonObject) throws JSONException, ServiceException, IOException, RepositoryException { final JSONObject preference = preferenceQueryService.getPreference(); final String token = new Randoms().nextStringWithMD5(); final String adminEmail = preference.getString(Option.ID_C_ADMIN_EMAIL); final String mailSubject = langPropsService.get("resetPwdMailSubject"); final String mailBody = langPropsService.get("resetPwdMailBody") + " " + Latkes.getServePath() + "/forgot?token=" + token + "&login=" + userEmail; final MailService.Message message = new MailService.Message(); final JSONObject option = new JSONObject(); option.put(Keys.OBJECT_ID, token); option.put(Option.OPTION_CATEGORY, "passwordReset"); option.put(Option.OPTION_VALUE, System.currentTimeMillis()); final Transaction transaction = optionRepository.beginTransaction(); optionRepository.add(option); transaction.commit(); message.setFrom(adminEmail); message.addRecipient(userEmail); message.setSubject(mailSubject); message.setHtmlBody(mailBody); mailService.send(message); jsonObject.put("succeed", true); jsonObject.put("to", Latkes.getServePath() + "/login?from=forgot"); jsonObject.put(Keys.MSG, langPropsService.get("resetPwdSuccessSend")); LOGGER.log(Level.DEBUG, "Sent a mail[mailSubject={0}, mailBody=[{1}] to [{2}]", mailSubject, mailBody, userEmail); } /** * Render a page template with the destination URL. * * @param context the context * @param pageTemplate the page template * @param destinationURL the destination URL * @param request for reset password page * @throws JSONException the JSONException * @throws ServiceException the ServiceException */ private void renderPage(final HTTPRequestContext context, final String pageTemplate, final String destinationURL, final HttpServletRequest request) throws JSONException, ServiceException { final AbstractFreeMarkerRenderer renderer = new ConsoleRenderer(); renderer.setTemplateName(pageTemplate); context.setRenderer(renderer); final Map<String, Object> dataModel = renderer.getDataModel(); final Map<String, String> langs = langPropsService.getAll(Latkes.getLocale()); final JSONObject preference = preferenceQueryService.getPreference(); dataModel.putAll(langs); dataModel.put(Common.GOTO, destinationURL); dataModel.put(Common.YEAR, String.valueOf(Calendar.getInstance().get(Calendar.YEAR))); dataModel.put(Common.VERSION, SoloServletListener.VERSION); dataModel.put(Common.STATIC_RESOURCE_VERSION, Latkes.getStaticResourceVersion()); dataModel.put(Option.ID_C_BLOG_TITLE, preference.getString(Option.ID_C_BLOG_TITLE)); final String token = request.getParameter("token"); final String email = request.getParameter("login"); final JSONObject tokenObj = optionQueryService.getOptionById(token); if (tokenObj == null) { dataModel.put("inputType", "email"); } else { // TODO verify the expired time in the tokenObj dataModel.put("inputType", "password"); dataModel.put("userEmailHidden", email); } final String from = request.getParameter("from"); if ("forgot".equals(from)) { dataModel.put("resetMsg", langPropsService.get("resetPwdSuccessSend")); } else if ("reset".equals(from)) { dataModel.put("resetMsg", langPropsService.get("resetPwdSuccessMsg")); } else { dataModel.put("resetMsg", ""); } Keys.fillRuntime(dataModel); filler.fillMinified(dataModel); } }