From 1499b12fa782a57dbaef294ddd16ba61d2193cc7 Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Sat, 27 Feb 2021 13:02:34 -0800 Subject: Implement password reset API --- .../org/traccar/api/resource/PasswordResource.java | 81 +++++++++++++++++++ .../notification/NotificationFormatter.java | 61 ++------------- .../notification/TextTemplateFormatter.java | 91 ++++++++++++++++++++++ templates/full/passwordReset.vm | 8 ++ 4 files changed, 187 insertions(+), 54 deletions(-) create mode 100644 src/main/java/org/traccar/api/resource/PasswordResource.java create mode 100644 src/main/java/org/traccar/notification/TextTemplateFormatter.java create mode 100644 templates/full/passwordReset.vm diff --git a/src/main/java/org/traccar/api/resource/PasswordResource.java b/src/main/java/org/traccar/api/resource/PasswordResource.java new file mode 100644 index 000000000..20e8d768d --- /dev/null +++ b/src/main/java/org/traccar/api/resource/PasswordResource.java @@ -0,0 +1,81 @@ +/* + * Copyright 2021 Anton Tananaev (anton@traccar.org) + * + * 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.traccar.api.resource; + +import org.apache.velocity.VelocityContext; +import org.traccar.Context; +import org.traccar.api.BaseResource; +import org.traccar.model.User; +import org.traccar.notification.FullMessage; +import org.traccar.notification.TextTemplateFormatter; + +import javax.annotation.security.PermitAll; +import javax.mail.MessagingException; +import javax.ws.rs.Consumes; +import javax.ws.rs.FormParam; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import java.sql.SQLException; +import java.util.UUID; + +@Path("password") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_FORM_URLENCODED) +public class PasswordResource extends BaseResource { + + private static final String PASSWORD_RESET_TOKEN = "passwordToken"; + + @Path("reset") + @PermitAll + @POST + public Response reset(@FormParam("email") String email) throws SQLException, MessagingException { + for (long userId : Context.getUsersManager().getAllItems()) { + User user = Context.getUsersManager().getById(userId); + if (email.equals(user.getEmail())) { + String token = UUID.randomUUID().toString().replaceAll("-", ""); + user.set(PASSWORD_RESET_TOKEN, token); + Context.getUsersManager().updateItem(user); + VelocityContext velocityContext = TextTemplateFormatter.prepareContext(null); + velocityContext.put("token", token); + FullMessage message = TextTemplateFormatter.formatFullMessage(velocityContext, "passwordReset"); + Context.getMailManager().sendMessage(userId, message.getSubject(), message.getBody()); + break; + } + } + return Response.ok().build(); + } + + @Path("update") + @PermitAll + @POST + public Response update( + @FormParam("token") String token, @FormParam("password") String password) throws SQLException { + for (long userId : Context.getUsersManager().getAllItems()) { + User user = Context.getUsersManager().getById(userId); + if (token.equals(user.getString(PASSWORD_RESET_TOKEN))) { + user.getAttributes().remove(PASSWORD_RESET_TOKEN); + user.setPassword(password); + Context.getUsersManager().updateItem(user); + return Response.ok().build(); + } + } + return Response.status(Response.Status.NOT_FOUND).build(); + } + +} diff --git a/src/main/java/org/traccar/notification/NotificationFormatter.java b/src/main/java/org/traccar/notification/NotificationFormatter.java index 2f8100226..dabc75b8b 100644 --- a/src/main/java/org/traccar/notification/NotificationFormatter.java +++ b/src/main/java/org/traccar/notification/NotificationFormatter.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2021 Anton Tananaev (anton@traccar.org) * Copyright 2017 - 2018 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,18 +16,7 @@ */ package org.traccar.notification; -import java.io.StringWriter; -import java.nio.charset.StandardCharsets; -import java.nio.file.Paths; -import java.util.Locale; - -import org.apache.velocity.Template; import org.apache.velocity.VelocityContext; -import org.apache.velocity.exception.ResourceNotFoundException; -import org.apache.velocity.tools.generic.DateTool; -import org.apache.velocity.tools.generic.NumberTool; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.traccar.Context; import org.traccar.model.Device; import org.traccar.model.Event; @@ -37,8 +26,6 @@ import org.traccar.reports.ReportUtils; public final class NotificationFormatter { - private static final Logger LOGGER = LoggerFactory.getLogger(NotificationFormatter.class); - private NotificationFormatter() { } @@ -47,8 +34,8 @@ public final class NotificationFormatter { User user = Context.getPermissionsManager().getUser(userId); Device device = Context.getIdentityManager().getById(event.getDeviceId()); - VelocityContext velocityContext = new VelocityContext(); - velocityContext.put("user", user); + VelocityContext velocityContext = TextTemplateFormatter.prepareContext(user); + velocityContext.put("device", device); velocityContext.put("event", event); if (position != null) { @@ -67,52 +54,18 @@ public final class NotificationFormatter { if (driverUniqueId != null) { velocityContext.put("driver", Context.getDriversManager().getDriverByUniqueId(driverUniqueId)); } - velocityContext.put("webUrl", Context.getVelocityEngine().getProperty("web.url")); - velocityContext.put("dateTool", new DateTool()); - velocityContext.put("numberTool", new NumberTool()); - velocityContext.put("timezone", ReportUtils.getTimezone(userId)); - velocityContext.put("locale", Locale.getDefault()); - return velocityContext; - } - - public static Template getTemplate(Event event, String path) { - String templateFilePath; - Template template; - - try { - templateFilePath = Paths.get(path, event.getType() + ".vm").toString(); - template = Context.getVelocityEngine().getTemplate(templateFilePath, StandardCharsets.UTF_8.name()); - } catch (ResourceNotFoundException error) { - LOGGER.warn("Notification template error", error); - templateFilePath = Paths.get(path, "unknown.vm").toString(); - template = Context.getVelocityEngine().getTemplate(templateFilePath, StandardCharsets.UTF_8.name()); - } - return template; + return velocityContext; } public static FullMessage formatFullMessage(long userId, Event event, Position position) { VelocityContext velocityContext = prepareContext(userId, event, position); - String formattedMessage = formatMessage(velocityContext, userId, event, position, "full"); - - return new FullMessage((String) velocityContext.get("subject"), formattedMessage); + return TextTemplateFormatter.formatFullMessage(velocityContext, event.getType()); } public static String formatShortMessage(long userId, Event event, Position position) { - return formatMessage(null, userId, event, position, "short"); - } - - private static String formatMessage(VelocityContext vc, Long userId, Event event, Position position, - String templatePath) { - - VelocityContext velocityContext = vc; - if (velocityContext == null) { - velocityContext = prepareContext(userId, event, position); - } - StringWriter writer = new StringWriter(); - getTemplate(event, templatePath).merge(velocityContext, writer); - - return writer.toString(); + VelocityContext velocityContext = prepareContext(userId, event, position); + return TextTemplateFormatter.formatShortMessage(velocityContext, event.getType()); } } diff --git a/src/main/java/org/traccar/notification/TextTemplateFormatter.java b/src/main/java/org/traccar/notification/TextTemplateFormatter.java new file mode 100644 index 000000000..c7cac2d4d --- /dev/null +++ b/src/main/java/org/traccar/notification/TextTemplateFormatter.java @@ -0,0 +1,91 @@ +/* + * Copyright 2021 Anton Tananaev (anton@traccar.org) + * + * 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.traccar.notification; + +import org.apache.velocity.Template; +import org.apache.velocity.VelocityContext; +import org.apache.velocity.exception.ResourceNotFoundException; +import org.apache.velocity.tools.generic.DateTool; +import org.apache.velocity.tools.generic.NumberTool; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.traccar.Context; +import org.traccar.model.User; +import org.traccar.reports.ReportUtils; + +import java.io.StringWriter; +import java.nio.charset.StandardCharsets; +import java.nio.file.Paths; +import java.util.Locale; + +public final class TextTemplateFormatter { + + private static final Logger LOGGER = LoggerFactory.getLogger(TextTemplateFormatter.class); + + private TextTemplateFormatter() { + } + + public static VelocityContext prepareContext(User user) { + + VelocityContext velocityContext = new VelocityContext(); + + if (user != null) { + velocityContext.put("user", user); + velocityContext.put("timezone", ReportUtils.getTimezone(user.getId())); + } + + velocityContext.put("webUrl", Context.getVelocityEngine().getProperty("web.url")); + velocityContext.put("dateTool", new DateTool()); + velocityContext.put("numberTool", new NumberTool()); + velocityContext.put("locale", Locale.getDefault()); + + return velocityContext; + } + + public static Template getTemplate(String name, String path) { + + String templateFilePath; + Template template; + + try { + templateFilePath = Paths.get(path, name + ".vm").toString(); + template = Context.getVelocityEngine().getTemplate(templateFilePath, StandardCharsets.UTF_8.name()); + } catch (ResourceNotFoundException error) { + LOGGER.warn("Notification template error", error); + templateFilePath = Paths.get(path, "unknown.vm").toString(); + template = Context.getVelocityEngine().getTemplate(templateFilePath, StandardCharsets.UTF_8.name()); + } + return template; + } + + public static FullMessage formatFullMessage(VelocityContext velocityContext, String name) { + String formattedMessage = formatMessage(velocityContext, name, "full"); + return new FullMessage((String) velocityContext.get("subject"), formattedMessage); + } + + public static String formatShortMessage(VelocityContext velocityContext, String name) { + return formatMessage(velocityContext, name, "short"); + } + + private static String formatMessage( + VelocityContext velocityContext, String name, String templatePath) { + + StringWriter writer = new StringWriter(); + getTemplate(name, templatePath).merge(velocityContext, writer); + return writer.toString(); + } + +} diff --git a/templates/full/passwordReset.vm b/templates/full/passwordReset.vm new file mode 100644 index 000000000..fe692ba1d --- /dev/null +++ b/templates/full/passwordReset.vm @@ -0,0 +1,8 @@ +#set($subject = "Password reset") + + + +To reset password please click on the following link:
+$webUrl?passwordReset=$token
+ + -- cgit v1.2.3