diff options
Diffstat (limited to 'src/main/java/org/traccar/api/resource')
19 files changed, 1066 insertions, 516 deletions
diff --git a/src/main/java/org/traccar/api/resource/AttributeResource.java b/src/main/java/org/traccar/api/resource/AttributeResource.java index d2dc28903..44f0ef452 100644 --- a/src/main/java/org/traccar/api/resource/AttributeResource.java +++ b/src/main/java/org/traccar/api/resource/AttributeResource.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 - 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2017 - 2022 Anton Tananaev (anton@traccar.org) * Copyright 2017 - 2018 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,80 +16,84 @@ */ package org.traccar.api.resource; -import javax.ws.rs.Consumes; -import javax.ws.rs.DELETE; -import javax.ws.rs.POST; -import javax.ws.rs.PUT; -import javax.ws.rs.Path; -import javax.ws.rs.PathParam; -import javax.ws.rs.Produces; -import javax.ws.rs.QueryParam; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; +import jakarta.inject.Inject; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.DELETE; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.PUT; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.QueryParam; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; -import org.traccar.Context; import org.traccar.api.ExtendedObjectResource; import org.traccar.model.Attribute; +import org.traccar.model.Device; import org.traccar.model.Position; import org.traccar.handler.ComputedAttributesHandler; import org.traccar.storage.StorageException; +import org.traccar.storage.query.Columns; +import org.traccar.storage.query.Condition; +import org.traccar.storage.query.Request; @Path("attributes/computed") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public class AttributeResource extends ExtendedObjectResource<Attribute> { + @Inject + private ComputedAttributesHandler computedAttributesHandler; + public AttributeResource() { super(Attribute.class); } @POST @Path("test") - public Response test(@QueryParam("deviceId") long deviceId, Attribute entity) { - Context.getPermissionsManager().checkAdmin(getUserId()); - Context.getPermissionsManager().checkDevice(getUserId(), deviceId); - Position last = Context.getIdentityManager().getLastPosition(deviceId); - if (last != null) { - Object result = new ComputedAttributesHandler( - Context.getConfig(), - Context.getIdentityManager(), - Context.getAttributesManager()).computeAttribute(entity, last); - if (result != null) { - switch (entity.getType()) { - case "number": - Number numberValue = (Number) result; - return Response.ok(numberValue).build(); - case "boolean": - Boolean booleanValue = (Boolean) result; - return Response.ok(booleanValue).build(); - default: - return Response.ok(result.toString()).build(); - } - } else { - return Response.noContent().build(); + public Response test(@QueryParam("deviceId") long deviceId, Attribute entity) throws StorageException { + permissionsService.checkAdmin(getUserId()); + permissionsService.checkPermission(Device.class, getUserId(), deviceId); + + Position position = storage.getObject(Position.class, new Request( + new Columns.All(), + new Condition.LatestPositions(deviceId))); + + Object result = computedAttributesHandler.computeAttribute(entity, position); + if (result != null) { + switch (entity.getType()) { + case "number": + Number numberValue = (Number) result; + return Response.ok(numberValue).build(); + case "boolean": + Boolean booleanValue = (Boolean) result; + return Response.ok(booleanValue).build(); + default: + return Response.ok(result.toString()).build(); } } else { - throw new IllegalArgumentException("Device has no last position"); + return Response.noContent().build(); } } @POST public Response add(Attribute entity) throws StorageException { - Context.getPermissionsManager().checkAdmin(getUserId()); + permissionsService.checkAdmin(getUserId()); return super.add(entity); } @Path("{id}") @PUT public Response update(Attribute entity) throws StorageException { - Context.getPermissionsManager().checkAdmin(getUserId()); + permissionsService.checkAdmin(getUserId()); return super.update(entity); } @Path("{id}") @DELETE public Response remove(@PathParam("id") long id) throws StorageException { - Context.getPermissionsManager().checkAdmin(getUserId()); + permissionsService.checkAdmin(getUserId()); return super.remove(id); } diff --git a/src/main/java/org/traccar/api/resource/CalendarResource.java b/src/main/java/org/traccar/api/resource/CalendarResource.java index 9399c34a5..f6c1f3c59 100644 --- a/src/main/java/org/traccar/api/resource/CalendarResource.java +++ b/src/main/java/org/traccar/api/resource/CalendarResource.java @@ -16,10 +16,10 @@ */ package org.traccar.api.resource; -import javax.ws.rs.Consumes; -import javax.ws.rs.Path; -import javax.ws.rs.Produces; -import javax.ws.rs.core.MediaType; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; import org.traccar.api.SimpleObjectResource; import org.traccar.model.Calendar; diff --git a/src/main/java/org/traccar/api/resource/CommandResource.java b/src/main/java/org/traccar/api/resource/CommandResource.java index a31345246..d50c7ee0c 100644 --- a/src/main/java/org/traccar/api/resource/CommandResource.java +++ b/src/main/java/org/traccar/api/resource/CommandResource.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2015 - 2022 Anton Tananaev (anton@traccar.org) * Copyright 2016 Gabor Somogyi (gabor.g.somogyi@gmail.com) * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org) * @@ -17,76 +17,153 @@ */ package org.traccar.api.resource; -import org.traccar.Context; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.traccar.BaseProtocol; +import org.traccar.ServerManager; import org.traccar.api.ExtendedObjectResource; import org.traccar.database.CommandsManager; +import org.traccar.helper.model.DeviceUtil; import org.traccar.model.Command; +import org.traccar.model.Device; +import org.traccar.model.Group; +import org.traccar.model.Position; +import org.traccar.model.QueuedCommand; import org.traccar.model.Typed; +import org.traccar.model.User; +import org.traccar.model.UserRestrictions; +import org.traccar.storage.StorageException; +import org.traccar.storage.query.Columns; +import org.traccar.storage.query.Condition; +import org.traccar.storage.query.Request; +import jakarta.inject.Inject; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.QueryParam; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.ArrayList; import java.util.Collection; -import java.util.HashSet; -import java.util.Set; - -import javax.ws.rs.Consumes; -import javax.ws.rs.GET; -import javax.ws.rs.POST; -import javax.ws.rs.Path; -import javax.ws.rs.Produces; -import javax.ws.rs.QueryParam; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; @Path("commands") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public class CommandResource extends ExtendedObjectResource<Command> { + private static final Logger LOGGER = LoggerFactory.getLogger(CommandResource.class); + + @Inject + private CommandsManager commandsManager; + + @Inject + private ServerManager serverManager; + public CommandResource() { super(Command.class); } + private BaseProtocol getDeviceProtocol(long deviceId) throws StorageException { + Position position = storage.getObject(Position.class, new Request( + new Columns.All(), new Condition.LatestPositions(deviceId))); + if (position != null) { + return serverManager.getProtocol(position.getProtocol()); + } else { + return null; + } + } + @GET @Path("send") - public Collection<Command> get(@QueryParam("deviceId") long deviceId) { - Context.getPermissionsManager().checkDevice(getUserId(), deviceId); - CommandsManager commandsManager = Context.getCommandsManager(); - Set<Long> result = new HashSet<>(commandsManager.getUserItems(getUserId())); - result.retainAll(commandsManager.getSupportedCommands(deviceId)); - return commandsManager.getItems(result); + public Collection<Command> get(@QueryParam("deviceId") long deviceId) throws StorageException { + permissionsService.checkPermission(Device.class, getUserId(), deviceId); + BaseProtocol protocol = getDeviceProtocol(deviceId); + + var commands = storage.getObjects(baseClass, new Request( + new Columns.All(), + Condition.merge(List.of( + new Condition.Permission(User.class, getUserId(), baseClass), + new Condition.Permission(Device.class, deviceId, baseClass) + )))); + + return commands.stream().filter(command -> { + String type = command.getType(); + if (protocol != null) { + return command.getTextChannel() && protocol.getSupportedTextCommands().contains(type) + || !command.getTextChannel() && protocol.getSupportedDataCommands().contains(type); + } else { + return type.equals(Command.TYPE_CUSTOM); + } + }).collect(Collectors.toList()); } @POST @Path("send") - public Response send(Command entity) throws Exception { - Context.getPermissionsManager().checkReadonly(getUserId()); - long deviceId = entity.getDeviceId(); - long id = entity.getId(); - Context.getPermissionsManager().checkDevice(getUserId(), deviceId); - if (id != 0) { - Context.getPermissionsManager().checkPermission(Command.class, getUserId(), id); - Context.getPermissionsManager().checkUserDeviceCommand(getUserId(), deviceId, id); + public Response send(Command entity, @QueryParam("groupId") long groupId) throws Exception { + if (entity.getId() > 0) { + permissionsService.checkPermission(baseClass, getUserId(), entity.getId()); + long deviceId = entity.getDeviceId(); + entity = storage.getObject(baseClass, new Request( + new Columns.All(), new Condition.Equals("id", entity.getId()))); + entity.setDeviceId(deviceId); } else { - Context.getPermissionsManager().checkLimitCommands(getUserId()); + permissionsService.checkRestriction(getUserId(), UserRestrictions::getLimitCommands); } - if (!Context.getCommandsManager().sendCommand(entity)) { - return Response.accepted(entity).build(); + boolean result = true; + if (groupId > 0) { + permissionsService.checkPermission(Group.class, getUserId(), groupId); + var devices = DeviceUtil.getAccessibleDevices(storage, getUserId(), List.of(), List.of(groupId)); + for (Device device : devices) { + Command command = QueuedCommand.fromCommand(entity).toCommand(); + command.setDeviceId(device.getId()); + result = commandsManager.sendCommand(command) && result; + } + } else { + permissionsService.checkPermission(Device.class, getUserId(), entity.getDeviceId()); + result = commandsManager.sendCommand(entity); } - return Response.ok(entity).build(); + return result ? Response.ok(entity).build() : Response.accepted(entity).build(); } @GET @Path("types") public Collection<Typed> get( @QueryParam("deviceId") long deviceId, - @QueryParam("protocol") String protocol, - @QueryParam("textChannel") boolean textChannel) { + @QueryParam("textChannel") boolean textChannel) throws StorageException { if (deviceId != 0) { - Context.getPermissionsManager().checkDevice(getUserId(), deviceId); - return Context.getCommandsManager().getCommandTypes(deviceId, textChannel); - } else if (protocol != null) { - return Context.getCommandsManager().getCommandTypes(protocol, textChannel); + permissionsService.checkPermission(Device.class, getUserId(), deviceId); + BaseProtocol protocol = getDeviceProtocol(deviceId); + if (protocol != null) { + if (textChannel) { + return protocol.getSupportedTextCommands().stream().map(Typed::new).collect(Collectors.toList()); + } else { + return protocol.getSupportedDataCommands().stream().map(Typed::new).collect(Collectors.toList()); + } + } else { + return Collections.singletonList(new Typed(Command.TYPE_CUSTOM)); + } } else { - return Context.getCommandsManager().getAllCommandTypes(); + List<Typed> result = new ArrayList<>(); + Field[] fields = Command.class.getDeclaredFields(); + for (Field field : fields) { + if (Modifier.isStatic(field.getModifiers()) && field.getName().startsWith("TYPE_")) { + try { + result.add(new Typed(field.get(null).toString())); + } catch (IllegalArgumentException | IllegalAccessException error) { + LOGGER.warn("Get command types error", error); + } + } + } + return result; } } + } diff --git a/src/main/java/org/traccar/api/resource/DeviceResource.java b/src/main/java/org/traccar/api/resource/DeviceResource.java index 9436b59f6..61a70bac0 100644 --- a/src/main/java/org/traccar/api/resource/DeviceResource.java +++ b/src/main/java/org/traccar/api/resource/DeviceResource.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2015 - 2022 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. @@ -15,34 +15,58 @@ */ package org.traccar.api.resource; -import org.traccar.Context; import org.traccar.api.BaseObjectResource; -import org.traccar.database.DeviceManager; +import org.traccar.broadcast.BroadcastService; +import org.traccar.database.MediaManager; import org.traccar.helper.LogAction; import org.traccar.model.Device; import org.traccar.model.DeviceAccumulators; +import org.traccar.model.Position; +import org.traccar.model.User; +import org.traccar.session.ConnectionManager; +import org.traccar.session.cache.CacheManager; import org.traccar.storage.StorageException; +import org.traccar.storage.query.Columns; +import org.traccar.storage.query.Condition; +import org.traccar.storage.query.Request; -import javax.ws.rs.Consumes; -import javax.ws.rs.GET; -import javax.ws.rs.PUT; -import javax.ws.rs.Path; -import javax.ws.rs.Produces; -import javax.ws.rs.QueryParam; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; - -import java.sql.SQLException; +import jakarta.inject.Inject; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.HeaderParam; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.PUT; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.QueryParam; +import jakarta.ws.rs.core.HttpHeaders; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; import java.util.Collection; -import java.util.HashSet; +import java.util.LinkedList; import java.util.List; -import java.util.Set; @Path("devices") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public class DeviceResource extends BaseObjectResource<Device> { + @Inject + private CacheManager cacheManager; + + @Inject + private ConnectionManager connectionManager; + + @Inject + private BroadcastService broadcastService; + + @Inject + private MediaManager mediaManager; + public DeviceResource() { super(Device.class); } @@ -51,51 +75,112 @@ public class DeviceResource extends BaseObjectResource<Device> { public Collection<Device> get( @QueryParam("all") boolean all, @QueryParam("userId") long userId, @QueryParam("uniqueId") List<String> uniqueIds, - @QueryParam("id") List<Long> deviceIds) throws SQLException { - DeviceManager deviceManager = Context.getDeviceManager(); - Set<Long> result; - if (all) { - if (Context.getPermissionsManager().getUserAdmin(getUserId())) { - result = deviceManager.getAllItems(); - } else { - Context.getPermissionsManager().checkManager(getUserId()); - result = deviceManager.getManagedItems(getUserId()); - } - } else if (uniqueIds.isEmpty() && deviceIds.isEmpty()) { - if (userId == 0) { - userId = getUserId(); - } - Context.getPermissionsManager().checkUser(getUserId(), userId); - if (Context.getPermissionsManager().getUserAdmin(getUserId())) { - result = deviceManager.getAllUserItems(userId); - } else { - result = deviceManager.getUserItems(userId); - } - } else { - result = new HashSet<>(); + @QueryParam("id") List<Long> deviceIds) throws StorageException { + + if (!uniqueIds.isEmpty() || !deviceIds.isEmpty()) { + + List<Device> result = new LinkedList<>(); for (String uniqueId : uniqueIds) { - Device device = deviceManager.getByUniqueId(uniqueId); - Context.getPermissionsManager().checkDevice(getUserId(), device.getId()); - result.add(device.getId()); + result.addAll(storage.getObjects(Device.class, new Request( + new Columns.All(), + new Condition.And( + new Condition.Equals("uniqueId", uniqueId), + new Condition.Permission(User.class, getUserId(), Device.class))))); } for (Long deviceId : deviceIds) { - Context.getPermissionsManager().checkDevice(getUserId(), deviceId); - result.add(deviceId); + result.addAll(storage.getObjects(Device.class, new Request( + new Columns.All(), + new Condition.And( + new Condition.Equals("id", deviceId), + new Condition.Permission(User.class, getUserId(), Device.class))))); } + return result; + + } else { + + var conditions = new LinkedList<Condition>(); + + if (all) { + if (permissionsService.notAdmin(getUserId())) { + conditions.add(new Condition.Permission(User.class, getUserId(), baseClass)); + } + } else { + if (userId == 0) { + conditions.add(new Condition.Permission(User.class, getUserId(), baseClass)); + } else { + permissionsService.checkUser(getUserId(), userId); + conditions.add(new Condition.Permission(User.class, userId, baseClass).excludeGroups()); + } + } + + return storage.getObjects(baseClass, new Request(new Columns.All(), Condition.merge(conditions))); + } - return deviceManager.getItems(result); } @Path("{id}/accumulators") @PUT public Response updateAccumulators(DeviceAccumulators entity) throws StorageException { - if (!Context.getPermissionsManager().getUserAdmin(getUserId())) { - Context.getPermissionsManager().checkManager(getUserId()); - Context.getPermissionsManager().checkPermission(Device.class, getUserId(), entity.getDeviceId()); + if (permissionsService.notAdmin(getUserId())) { + permissionsService.checkManager(getUserId()); + permissionsService.checkPermission(Device.class, getUserId(), entity.getDeviceId()); } - Context.getDeviceManager().resetDeviceAccumulators(entity); + + Position position = storage.getObject(Position.class, new Request( + new Columns.All(), new Condition.LatestPositions(entity.getDeviceId()))); + if (position != null) { + if (entity.getTotalDistance() != null) { + position.getAttributes().put(Position.KEY_TOTAL_DISTANCE, entity.getTotalDistance()); + } + if (entity.getHours() != null) { + position.getAttributes().put(Position.KEY_HOURS, entity.getHours()); + } + position.setId(storage.addObject(position, new Request(new Columns.Exclude("id")))); + + Device device = new Device(); + device.setId(position.getDeviceId()); + device.setPositionId(position.getId()); + storage.updateObject(device, new Request( + new Columns.Include("positionId"), + new Condition.Equals("id", device.getId()))); + + try { + cacheManager.addDevice(position.getDeviceId()); + cacheManager.updatePosition(position); + connectionManager.updatePosition(true, position); + } finally { + cacheManager.removeDevice(position.getDeviceId()); + } + } else { + throw new IllegalArgumentException(); + } + LogAction.resetDeviceAccumulators(getUserId(), entity.getDeviceId()); return Response.noContent().build(); } + @Path("{id}/image") + @POST + @Consumes("image/*") + public Response uploadImage( + @PathParam("id") long deviceId, File file, + @HeaderParam(HttpHeaders.CONTENT_TYPE) String type) throws StorageException, IOException { + + Device device = storage.getObject(Device.class, new Request( + new Columns.All(), + new Condition.And( + new Condition.Equals("id", deviceId), + new Condition.Permission(User.class, getUserId(), Device.class)))); + if (device != null) { + String name = "device"; + String extension = type.substring("image/".length()); + try (var input = new FileInputStream(file); + var output = mediaManager.createFileStream(device.getUniqueId(), name, extension)) { + input.transferTo(output); + } + return Response.ok(name + "." + extension).build(); + } + return Response.status(Response.Status.NOT_FOUND).build(); + } + } diff --git a/src/main/java/org/traccar/api/resource/DriverResource.java b/src/main/java/org/traccar/api/resource/DriverResource.java index 91aa54c5e..19cf74f39 100644 --- a/src/main/java/org/traccar/api/resource/DriverResource.java +++ b/src/main/java/org/traccar/api/resource/DriverResource.java @@ -16,10 +16,10 @@ */ package org.traccar.api.resource; -import javax.ws.rs.Consumes; -import javax.ws.rs.Path; -import javax.ws.rs.Produces; -import javax.ws.rs.core.MediaType; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; import org.traccar.api.ExtendedObjectResource; import org.traccar.model.Driver; diff --git a/src/main/java/org/traccar/api/resource/EventResource.java b/src/main/java/org/traccar/api/resource/EventResource.java index 354d96e4f..1f20b880d 100644 --- a/src/main/java/org/traccar/api/resource/EventResource.java +++ b/src/main/java/org/traccar/api/resource/EventResource.java @@ -15,21 +15,22 @@ */ package org.traccar.api.resource; -import javax.ws.rs.Consumes; -import javax.ws.rs.GET; -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.MediaType; -import javax.ws.rs.core.Response; - -import org.traccar.Context; import org.traccar.api.BaseResource; +import org.traccar.model.Device; import org.traccar.model.Event; -import org.traccar.model.Geofence; -import org.traccar.model.Maintenance; import org.traccar.storage.StorageException; +import org.traccar.storage.query.Columns; +import org.traccar.storage.query.Condition; +import org.traccar.storage.query.Request; + +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.WebApplicationException; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; @Path("events") @Produces(MediaType.APPLICATION_JSON) @@ -39,17 +40,12 @@ public class EventResource extends BaseResource { @Path("{id}") @GET public Event get(@PathParam("id") long id) throws StorageException { - Event event = Context.getDataManager().getObject(Event.class, id); + Event event = storage.getObject(Event.class, new Request( + new Columns.All(), new Condition.Equals("id", id))); if (event == null) { throw new WebApplicationException(Response.status(Response.Status.NOT_FOUND).build()); } - Context.getPermissionsManager().checkDevice(getUserId(), event.getDeviceId()); - if (event.getGeofenceId() != 0) { - Context.getPermissionsManager().checkPermission(Geofence.class, getUserId(), event.getGeofenceId()); - } - if (event.getMaintenanceId() != 0) { - Context.getPermissionsManager().checkPermission(Maintenance.class, getUserId(), event.getMaintenanceId()); - } + permissionsService.checkPermission(Device.class, getUserId(), event.getDeviceId()); return event; } diff --git a/src/main/java/org/traccar/api/resource/GeofenceResource.java b/src/main/java/org/traccar/api/resource/GeofenceResource.java index 58f2c188c..030690889 100644 --- a/src/main/java/org/traccar/api/resource/GeofenceResource.java +++ b/src/main/java/org/traccar/api/resource/GeofenceResource.java @@ -18,10 +18,10 @@ package org.traccar.api.resource; import org.traccar.api.ExtendedObjectResource; import org.traccar.model.Geofence; -import javax.ws.rs.Consumes; -import javax.ws.rs.Path; -import javax.ws.rs.Produces; -import javax.ws.rs.core.MediaType; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; @Path("geofences") @Produces(MediaType.APPLICATION_JSON) diff --git a/src/main/java/org/traccar/api/resource/GroupResource.java b/src/main/java/org/traccar/api/resource/GroupResource.java index fcea15d0a..628f8f655 100644 --- a/src/main/java/org/traccar/api/resource/GroupResource.java +++ b/src/main/java/org/traccar/api/resource/GroupResource.java @@ -18,10 +18,10 @@ package org.traccar.api.resource; import org.traccar.api.SimpleObjectResource; import org.traccar.model.Group; -import javax.ws.rs.Consumes; -import javax.ws.rs.Path; -import javax.ws.rs.Produces; -import javax.ws.rs.core.MediaType; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; @Path("groups") @Produces(MediaType.APPLICATION_JSON) diff --git a/src/main/java/org/traccar/api/resource/MaintenanceResource.java b/src/main/java/org/traccar/api/resource/MaintenanceResource.java index fa1b359ce..12841e497 100644 --- a/src/main/java/org/traccar/api/resource/MaintenanceResource.java +++ b/src/main/java/org/traccar/api/resource/MaintenanceResource.java @@ -16,10 +16,10 @@ */ package org.traccar.api.resource; -import javax.ws.rs.Consumes; -import javax.ws.rs.Path; -import javax.ws.rs.Produces; -import javax.ws.rs.core.MediaType; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; import org.traccar.api.ExtendedObjectResource; import org.traccar.model.Maintenance; diff --git a/src/main/java/org/traccar/api/resource/NotificationResource.java b/src/main/java/org/traccar/api/resource/NotificationResource.java index 9631a52b7..2a209efb6 100644 --- a/src/main/java/org/traccar/api/resource/NotificationResource.java +++ b/src/main/java/org/traccar/api/resource/NotificationResource.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 - 2018 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2023 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. @@ -15,30 +15,42 @@ */ package org.traccar.api.resource; -import java.util.Collection; - -import javax.ws.rs.Consumes; -import javax.ws.rs.GET; -import javax.ws.rs.POST; -import javax.ws.rs.Path; -import javax.ws.rs.PathParam; -import javax.ws.rs.Produces; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; - -import org.traccar.Context; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.traccar.api.ExtendedObjectResource; import org.traccar.model.Event; import org.traccar.model.Notification; import org.traccar.model.Typed; +import org.traccar.model.User; import org.traccar.notification.MessageException; +import org.traccar.notification.NotificatorManager; +import org.traccar.storage.StorageException; +import jakarta.inject.Inject; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; @Path("notifications") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public class NotificationResource extends ExtendedObjectResource<Notification> { + private static final Logger LOGGER = LoggerFactory.getLogger(NotificationResource.class); + + @Inject + private NotificatorManager notificatorManager; + public NotificationResource() { super(Notification.class); } @@ -46,21 +58,32 @@ public class NotificationResource extends ExtendedObjectResource<Notification> { @GET @Path("types") public Collection<Typed> get() { - return Context.getNotificationManager().getAllNotificationTypes(); + List<Typed> types = new LinkedList<>(); + Field[] fields = Event.class.getDeclaredFields(); + for (Field field : fields) { + if (Modifier.isStatic(field.getModifiers()) && field.getName().startsWith("TYPE_")) { + try { + types.add(new Typed(field.get(null).toString())); + } catch (IllegalArgumentException | IllegalAccessException error) { + LOGGER.warn("Get event types error", error); + } + } + } + return types; } @GET @Path("notificators") public Collection<Typed> getNotificators() { - return Context.getNotificatorManager().getAllNotificatorTypes(); + return notificatorManager.getAllNotificatorTypes(); } @POST @Path("test") - public Response testMessage() throws MessageException, InterruptedException { - for (Typed method : Context.getNotificatorManager().getAllNotificatorTypes()) { - Context.getNotificatorManager() - .getNotificator(method.getType()).sendSync(getUserId(), new Event("test", 0), null); + public Response testMessage() throws MessageException, StorageException { + User user = permissionsService.getUser(getUserId()); + for (Typed method : notificatorManager.getAllNotificatorTypes()) { + notificatorManager.getNotificator(method.getType()).send(null, user, new Event("test", 0), null); } return Response.noContent().build(); } @@ -68,8 +91,9 @@ public class NotificationResource extends ExtendedObjectResource<Notification> { @POST @Path("test/{notificator}") public Response testMessage(@PathParam("notificator") String notificator) - throws MessageException, InterruptedException { - Context.getNotificatorManager().getNotificator(notificator).sendSync(getUserId(), new Event("test", 0), null); + throws MessageException, StorageException { + User user = permissionsService.getUser(getUserId()); + notificatorManager.getNotificator(notificator).send(null, user, new Event("test", 0), null); return Response.noContent().build(); } diff --git a/src/main/java/org/traccar/api/resource/OrderResource.java b/src/main/java/org/traccar/api/resource/OrderResource.java index 77608a508..3852b975f 100644 --- a/src/main/java/org/traccar/api/resource/OrderResource.java +++ b/src/main/java/org/traccar/api/resource/OrderResource.java @@ -18,10 +18,10 @@ package org.traccar.api.resource; import org.traccar.api.SimpleObjectResource; import org.traccar.model.Order; -import javax.ws.rs.Consumes; -import javax.ws.rs.Path; -import javax.ws.rs.Produces; -import javax.ws.rs.core.MediaType; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; @Path("orders") @Produces(MediaType.APPLICATION_JSON) diff --git a/src/main/java/org/traccar/api/resource/PasswordResource.java b/src/main/java/org/traccar/api/resource/PasswordResource.java index 0642ff3cc..029e63a0c 100644 --- a/src/main/java/org/traccar/api/resource/PasswordResource.java +++ b/src/main/java/org/traccar/api/resource/PasswordResource.java @@ -1,5 +1,5 @@ /* - * Copyright 2021 Anton Tananaev (anton@traccar.org) + * Copyright 2021 - 2022 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. @@ -15,49 +15,55 @@ */ package org.traccar.api.resource; -import org.apache.velocity.VelocityContext; -import org.traccar.Context; import org.traccar.api.BaseResource; +import org.traccar.api.signature.TokenManager; +import org.traccar.mail.MailManager; import org.traccar.model.User; -import org.traccar.notification.NotificationMessage; import org.traccar.notification.TextTemplateFormatter; import org.traccar.storage.StorageException; +import org.traccar.storage.query.Columns; +import org.traccar.storage.query.Condition; +import org.traccar.storage.query.Request; -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.util.UUID; +import jakarta.annotation.security.PermitAll; +import jakarta.inject.Inject; +import jakarta.mail.MessagingException; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.FormParam; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import java.io.IOException; +import java.security.GeneralSecurityException; @Path("password") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_FORM_URLENCODED) public class PasswordResource extends BaseResource { - private static final String PASSWORD_RESET_TOKEN = "passwordToken"; + @Inject + private MailManager mailManager; + + @Inject + private TokenManager tokenManager; + + @Inject + private TextTemplateFormatter textTemplateFormatter; @Path("reset") @PermitAll @POST - public Response reset(@FormParam("email") String email) throws StorageException, 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); - NotificationMessage fullMessage = - TextTemplateFormatter.formatMessage(velocityContext, "passwordReset", "full"); - Context.getMailManager().sendMessage(userId, fullMessage.getSubject(), fullMessage.getBody()); - break; - } + public Response reset(@FormParam("email") String email) + throws StorageException, MessagingException, GeneralSecurityException, IOException { + + User user = storage.getObject(User.class, new Request( + new Columns.All(), new Condition.Equals("email", email))); + if (user != null) { + var velocityContext = textTemplateFormatter.prepareContext(permissionsService.getServer(), user); + var fullMessage = textTemplateFormatter.formatMessage(velocityContext, "passwordReset", "full"); + mailManager.sendMessage(user, true, fullMessage.getSubject(), fullMessage.getBody()); } return Response.ok().build(); } @@ -66,15 +72,18 @@ public class PasswordResource extends BaseResource { @PermitAll @POST public Response update( - @FormParam("token") String token, @FormParam("password") String password) throws StorageException { - 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(); - } + @FormParam("token") String token, @FormParam("password") String password) + throws StorageException, GeneralSecurityException, IOException { + + long userId = tokenManager.verifyToken(token); + User user = storage.getObject(User.class, new Request( + new Columns.All(), new Condition.Equals("id", userId))); + if (user != null) { + user.setPassword(password); + storage.updateObject(user, new Request( + new Columns.Include("hashedPassword", "salt"), + new Condition.Equals("id", userId))); + return Response.ok().build(); } return Response.status(Response.Status.NOT_FOUND).build(); } diff --git a/src/main/java/org/traccar/api/resource/PermissionsResource.java b/src/main/java/org/traccar/api/resource/PermissionsResource.java index 7def38919..e8e4e96eb 100644 --- a/src/main/java/org/traccar/api/resource/PermissionsResource.java +++ b/src/main/java/org/traccar/api/resource/PermissionsResource.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 Anton Tananaev (anton@traccar.org) + * Copyright 2017 - 2022 Anton Tananaev (anton@traccar.org) * Copyright 2017 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,47 +16,40 @@ */ package org.traccar.api.resource; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Set; - -import javax.ws.rs.Consumes; -import javax.ws.rs.DELETE; -import javax.ws.rs.POST; -import javax.ws.rs.Path; -import javax.ws.rs.Produces; -import javax.ws.rs.WebApplicationException; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; - -import org.traccar.Context; import org.traccar.api.BaseResource; import org.traccar.helper.LogAction; -import org.traccar.model.Device; import org.traccar.model.Permission; -import org.traccar.model.User; +import org.traccar.model.UserRestrictions; +import org.traccar.session.cache.CacheManager; import org.traccar.storage.StorageException; +import jakarta.inject.Inject; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.DELETE; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.WebApplicationException; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Set; + @Path("permissions") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public class PermissionsResource extends BaseResource { - private void checkPermission(Permission permission, boolean link) { - if (!link && permission.getOwnerClass().equals(User.class) - && permission.getPropertyClass().equals(Device.class)) { - if (getUserId() != permission.getOwnerId()) { - Context.getPermissionsManager().checkUser(getUserId(), permission.getOwnerId()); - } else { - Context.getPermissionsManager().checkAdmin(getUserId()); - } - } else { - Context.getPermissionsManager().checkPermission( - permission.getOwnerClass(), getUserId(), permission.getOwnerId()); + @Inject + private CacheManager cacheManager; + + private void checkPermission(Permission permission) throws StorageException { + if (permissionsService.notAdmin(getUserId())) { + permissionsService.checkPermission(permission.getOwnerClass(), getUserId(), permission.getOwnerId()); + permissionsService.checkPermission(permission.getPropertyClass(), getUserId(), permission.getPropertyId()); } - Context.getPermissionsManager().checkPermission( - permission.getPropertyClass(), getUserId(), permission.getPropertyId()); } private void checkPermissionTypes(List<LinkedHashMap<String, Long>> entities) { @@ -72,18 +65,19 @@ public class PermissionsResource extends BaseResource { @Path("bulk") @POST public Response add(List<LinkedHashMap<String, Long>> entities) throws StorageException, ClassNotFoundException { - Context.getPermissionsManager().checkReadonly(getUserId()); + permissionsService.checkRestriction(getUserId(), UserRestrictions::getReadonly); checkPermissionTypes(entities); for (LinkedHashMap<String, Long> entity: entities) { Permission permission = new Permission(entity); - checkPermission(permission, true); - Context.getDataManager().linkObject(permission.getOwnerClass(), permission.getOwnerId(), - permission.getPropertyClass(), permission.getPropertyId(), true); - LogAction.link(getUserId(), permission.getOwnerClass(), permission.getOwnerId(), + checkPermission(permission); + storage.addPermission(permission); + cacheManager.invalidatePermission( + true, + permission.getOwnerClass(), permission.getOwnerId(), + permission.getPropertyClass(), permission.getPropertyId()); + LogAction.link(getUserId(), + permission.getOwnerClass(), permission.getOwnerId(), permission.getPropertyClass(), permission.getPropertyId()); - } - if (!entities.isEmpty()) { - Context.getPermissionsManager().refreshPermissions(new Permission(entities.get(0))); } return Response.noContent().build(); } @@ -96,18 +90,19 @@ public class PermissionsResource extends BaseResource { @DELETE @Path("bulk") public Response remove(List<LinkedHashMap<String, Long>> entities) throws StorageException, ClassNotFoundException { - Context.getPermissionsManager().checkReadonly(getUserId()); + permissionsService.checkRestriction(getUserId(), UserRestrictions::getReadonly); checkPermissionTypes(entities); for (LinkedHashMap<String, Long> entity: entities) { Permission permission = new Permission(entity); - checkPermission(permission, false); - Context.getDataManager().linkObject(permission.getOwnerClass(), permission.getOwnerId(), - permission.getPropertyClass(), permission.getPropertyId(), false); - LogAction.unlink(getUserId(), permission.getOwnerClass(), permission.getOwnerId(), + checkPermission(permission); + storage.removePermission(permission); + cacheManager.invalidatePermission( + true, + permission.getOwnerClass(), permission.getOwnerId(), + permission.getPropertyClass(), permission.getPropertyId()); + LogAction.unlink(getUserId(), + permission.getOwnerClass(), permission.getOwnerId(), permission.getPropertyClass(), permission.getPropertyId()); - } - if (!entities.isEmpty()) { - Context.getPermissionsManager().refreshPermissions(new Permission(entities.get(0))); } return Response.noContent().build(); } diff --git a/src/main/java/org/traccar/api/resource/PositionResource.java b/src/main/java/org/traccar/api/resource/PositionResource.java index 511032402..0d783a0fe 100644 --- a/src/main/java/org/traccar/api/resource/PositionResource.java +++ b/src/main/java/org/traccar/api/resource/PositionResource.java @@ -15,52 +15,146 @@ */ package org.traccar.api.resource; -import org.traccar.Context; import org.traccar.api.BaseResource; +import org.traccar.helper.model.PositionUtil; +import org.traccar.model.Device; import org.traccar.model.Position; +import org.traccar.model.UserRestrictions; +import org.traccar.reports.CsvExportProvider; +import org.traccar.reports.GpxExportProvider; +import org.traccar.reports.KmlExportProvider; import org.traccar.storage.StorageException; +import org.traccar.storage.query.Columns; +import org.traccar.storage.query.Condition; +import org.traccar.storage.query.Request; -import javax.ws.rs.Consumes; -import javax.ws.rs.GET; -import javax.ws.rs.Path; -import javax.ws.rs.Produces; -import javax.ws.rs.QueryParam; -import javax.ws.rs.core.MediaType; +import jakarta.inject.Inject; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.DELETE; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.QueryParam; +import jakarta.ws.rs.WebApplicationException; +import jakarta.ws.rs.core.HttpHeaders; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.core.StreamingOutput; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.Date; import java.util.List; +import java.util.LinkedList; @Path("positions") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public class PositionResource extends BaseResource { + @Inject + private KmlExportProvider kmlExportProvider; + + @Inject + private CsvExportProvider csvExportProvider; + + @Inject + private GpxExportProvider gpxExportProvider; + @GET public Collection<Position> getJson( @QueryParam("deviceId") long deviceId, @QueryParam("id") List<Long> positionIds, @QueryParam("from") Date from, @QueryParam("to") Date to) throws StorageException { if (!positionIds.isEmpty()) { - ArrayList<Position> positions = new ArrayList<>(); - for (Long positionId : positionIds) { - Position position = Context.getDataManager().getObject(Position.class, positionId); - Context.getPermissionsManager().checkDevice(getUserId(), position.getDeviceId()); + var positions = new ArrayList<Position>(); + for (long positionId : positionIds) { + Position position = storage.getObject(Position.class, new Request( + new Columns.All(), new Condition.Equals("id", positionId))); + permissionsService.checkPermission(Device.class, getUserId(), position.getDeviceId()); positions.add(position); } return positions; - } else if (deviceId == 0) { - return Context.getDeviceManager().getInitialState(getUserId()); - } else { - Context.getPermissionsManager().checkDevice(getUserId(), deviceId); + } else if (deviceId > 0) { + permissionsService.checkPermission(Device.class, getUserId(), deviceId); if (from != null && to != null) { - Context.getPermissionsManager().checkDisableReports(getUserId()); - return Context.getDataManager().getPositions(deviceId, from, to); + permissionsService.checkRestriction(getUserId(), UserRestrictions::getDisableReports); + return PositionUtil.getPositions(storage, deviceId, from, to); } else { - return Collections.singleton(Context.getDeviceManager().getLastPosition(deviceId)); + return storage.getObjects(Position.class, new Request( + new Columns.All(), new Condition.LatestPositions(deviceId))); } + } else { + return PositionUtil.getLatestPositions(storage, getUserId()); } } + @DELETE + public Response remove( + @QueryParam("deviceId") long deviceId, + @QueryParam("from") Date from, @QueryParam("to") Date to) throws StorageException { + permissionsService.checkPermission(Device.class, getUserId(), deviceId); + permissionsService.checkRestriction(getUserId(), UserRestrictions::getReadonly); + + var conditions = new LinkedList<Condition>(); + conditions.add(new Condition.Equals("deviceId", deviceId)); + conditions.add(new Condition.Between("fixTime", "from", from, "to", to)); + storage.removeObject(Position.class, new Request(Condition.merge(conditions))); + + return Response.status(Response.Status.NO_CONTENT).build(); + } + + @Path("kml") + @GET + @Produces("application/vnd.google-earth.kml+xml") + public Response getKml( + @QueryParam("deviceId") long deviceId, + @QueryParam("from") Date from, @QueryParam("to") Date to) throws StorageException { + permissionsService.checkPermission(Device.class, getUserId(), deviceId); + StreamingOutput stream = output -> { + try { + kmlExportProvider.generate(output, deviceId, from, to); + } catch (StorageException e) { + throw new WebApplicationException(e); + } + }; + return Response.ok(stream) + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=positions.kml").build(); + } + + @Path("csv") + @GET + @Produces("text/csv") + public Response getCsv( + @QueryParam("deviceId") long deviceId, + @QueryParam("from") Date from, @QueryParam("to") Date to) throws StorageException { + permissionsService.checkPermission(Device.class, getUserId(), deviceId); + StreamingOutput stream = output -> { + try { + csvExportProvider.generate(output, deviceId, from, to); + } catch (StorageException e) { + throw new WebApplicationException(e); + } + }; + return Response.ok(stream) + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=positions.csv").build(); + } + + @Path("gpx") + @GET + @Produces("application/gpx+xml") + public Response getGpx( + @QueryParam("deviceId") long deviceId, + @QueryParam("from") Date from, @QueryParam("to") Date to) throws StorageException { + permissionsService.checkPermission(Device.class, getUserId(), deviceId); + StreamingOutput stream = output -> { + try { + gpxExportProvider.generate(output, deviceId, from, to); + } catch (StorageException e) { + throw new WebApplicationException(e); + } + }; + return Response.ok(stream) + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=positions.gpx").build(); + } + } diff --git a/src/main/java/org/traccar/api/resource/ReportResource.java b/src/main/java/org/traccar/api/resource/ReportResource.java index 03df0d03a..b4882f219 100644 --- a/src/main/java/org/traccar/api/resource/ReportResource.java +++ b/src/main/java/org/traccar/api/resource/ReportResource.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 - 2022 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2023 Anton Tananaev (anton@traccar.org) * Copyright 2016 - 2018 Andrey Kunitsyn (andrey@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,208 +16,307 @@ */ package org.traccar.api.resource; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.util.Collection; -import java.util.Date; -import java.util.List; - -import javax.activation.DataHandler; -import javax.mail.MessagingException; -import javax.mail.internet.MimeBodyPart; -import javax.mail.util.ByteArrayDataSource; -import javax.ws.rs.Consumes; -import javax.ws.rs.GET; -import javax.ws.rs.Path; -import javax.ws.rs.Produces; -import javax.ws.rs.QueryParam; -import javax.ws.rs.core.HttpHeaders; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.traccar.Context; -import org.traccar.api.BaseResource; +import org.traccar.api.SimpleObjectResource; import org.traccar.helper.LogAction; import org.traccar.model.Event; import org.traccar.model.Position; -import org.traccar.reports.Events; -import org.traccar.reports.Summary; -import org.traccar.reports.Trips; -import org.traccar.reports.model.StopReport; -import org.traccar.reports.model.SummaryReport; -import org.traccar.reports.model.TripReport; -import org.traccar.reports.Route; -import org.traccar.reports.Stops; +import org.traccar.model.Report; +import org.traccar.model.UserRestrictions; +import org.traccar.reports.CombinedReportProvider; +import org.traccar.reports.EventsReportProvider; +import org.traccar.reports.RouteReportProvider; +import org.traccar.reports.StopsReportProvider; +import org.traccar.reports.SummaryReportProvider; +import org.traccar.reports.TripsReportProvider; +import org.traccar.reports.common.ReportExecutor; +import org.traccar.reports.common.ReportMailer; +import org.traccar.reports.model.CombinedReportItem; +import org.traccar.reports.model.StopReportItem; +import org.traccar.reports.model.SummaryReportItem; +import org.traccar.reports.model.TripReportItem; import org.traccar.storage.StorageException; +import jakarta.inject.Inject; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.QueryParam; +import jakarta.ws.rs.WebApplicationException; +import jakarta.ws.rs.core.HttpHeaders; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.core.StreamingOutput; +import java.util.Collection; +import java.util.Date; +import java.util.List; + @Path("reports") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) -public class ReportResource extends BaseResource { +public class ReportResource extends SimpleObjectResource<Report> { - private static final Logger LOGGER = LoggerFactory.getLogger(ReportResource.class); + private static final String EXCEL = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; - private static final String XLSX = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; - private static final String CONTENT_DISPOSITION_VALUE_XLSX = "attachment; filename=report.xlsx"; + @Inject + private CombinedReportProvider combinedReportProvider; - private interface ReportExecutor { - void execute(ByteArrayOutputStream stream) throws StorageException, IOException; - } + @Inject + private EventsReportProvider eventsReportProvider; - private Response executeReport( - long userId, boolean mail, ReportExecutor executor) throws StorageException, IOException { - final ByteArrayOutputStream stream = new ByteArrayOutputStream(); - if (mail) { - new Thread(() -> { - try { - executor.execute(stream); + @Inject + private RouteReportProvider routeReportProvider; - MimeBodyPart attachment = new MimeBodyPart(); + @Inject + private StopsReportProvider stopsReportProvider; - attachment.setFileName("report.xlsx"); - attachment.setDataHandler(new DataHandler(new ByteArrayDataSource( - stream.toByteArray(), "application/octet-stream"))); + @Inject + private SummaryReportProvider summaryReportProvider; - Context.getMailManager().sendMessage( - userId, "Report", "The report is in the attachment.", attachment); - } catch (StorageException | IOException | MessagingException e) { - LOGGER.warn("Report failed", e); - } - }).start(); + @Inject + private TripsReportProvider tripsReportProvider; + + @Inject + private ReportMailer reportMailer; + + public ReportResource() { + super(Report.class); + } + + private Response executeReport(long userId, boolean mail, ReportExecutor executor) { + if (mail) { + reportMailer.sendAsync(userId, executor); return Response.noContent().build(); } else { - executor.execute(stream); - return Response.ok(stream.toByteArray()) - .header(HttpHeaders.CONTENT_DISPOSITION, CONTENT_DISPOSITION_VALUE_XLSX).build(); + StreamingOutput stream = output -> { + try { + executor.execute(output); + } catch (StorageException e) { + throw new WebApplicationException(e); + } + }; + return Response.ok(stream) + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=report.xlsx").build(); } } + @Path("combined") + @GET + public Collection<CombinedReportItem> getCombined( + @QueryParam("deviceId") List<Long> deviceIds, + @QueryParam("groupId") List<Long> groupIds, + @QueryParam("from") Date from, + @QueryParam("to") Date to) throws StorageException { + permissionsService.checkRestriction(getUserId(), UserRestrictions::getDisableReports); + LogAction.logReport(getUserId(), "combined", from, to, deviceIds, groupIds); + return combinedReportProvider.getObjects(getUserId(), deviceIds, groupIds, from, to); + } + @Path("route") @GET public Collection<Position> getRoute( - @QueryParam("deviceId") final List<Long> deviceIds, @QueryParam("groupId") final List<Long> groupIds, - @QueryParam("from") Date from, @QueryParam("to") Date to) throws StorageException { - Context.getPermissionsManager().checkDisableReports(getUserId()); + @QueryParam("deviceId") List<Long> deviceIds, + @QueryParam("groupId") List<Long> groupIds, + @QueryParam("from") Date from, + @QueryParam("to") Date to) throws StorageException { + permissionsService.checkRestriction(getUserId(), UserRestrictions::getDisableReports); LogAction.logReport(getUserId(), "route", from, to, deviceIds, groupIds); - return Route.getObjects(getUserId(), deviceIds, groupIds, from, to); + return routeReportProvider.getObjects(getUserId(), deviceIds, groupIds, from, to); } @Path("route") @GET - @Produces(XLSX) + @Produces(EXCEL) public Response getRouteExcel( - @QueryParam("deviceId") final List<Long> deviceIds, @QueryParam("groupId") final List<Long> groupIds, - @QueryParam("from") Date from, @QueryParam("to") Date to, @QueryParam("mail") boolean mail) - throws StorageException, IOException { - Context.getPermissionsManager().checkDisableReports(getUserId()); + @QueryParam("deviceId") List<Long> deviceIds, + @QueryParam("groupId") List<Long> groupIds, + @QueryParam("from") Date from, + @QueryParam("to") Date to, + @QueryParam("mail") boolean mail) throws StorageException { + permissionsService.checkRestriction(getUserId(), UserRestrictions::getDisableReports); return executeReport(getUserId(), mail, stream -> { LogAction.logReport(getUserId(), "route", from, to, deviceIds, groupIds); - Route.getExcel(stream, getUserId(), deviceIds, groupIds, from, to); + routeReportProvider.getExcel(stream, getUserId(), deviceIds, groupIds, from, to); }); } + @Path("route/{type:xlsx|mail}") + @GET + @Produces(EXCEL) + public Response getRouteExcel( + @QueryParam("deviceId") List<Long> deviceIds, + @QueryParam("groupId") final List<Long> groupIds, + @QueryParam("from") Date from, + @QueryParam("to") Date to, + @PathParam("type") String type) throws StorageException { + return getRouteExcel(deviceIds, groupIds, from, to, type.equals("mail")); + } + @Path("events") @GET public Collection<Event> getEvents( - @QueryParam("deviceId") final List<Long> deviceIds, @QueryParam("groupId") final List<Long> groupIds, - @QueryParam("type") final List<String> types, - @QueryParam("from") Date from, @QueryParam("to") Date to) throws StorageException { - Context.getPermissionsManager().checkDisableReports(getUserId()); + @QueryParam("deviceId") List<Long> deviceIds, + @QueryParam("groupId") List<Long> groupIds, + @QueryParam("type") List<String> types, + @QueryParam("from") Date from, + @QueryParam("to") Date to) throws StorageException { + permissionsService.checkRestriction(getUserId(), UserRestrictions::getDisableReports); LogAction.logReport(getUserId(), "events", from, to, deviceIds, groupIds); - return Events.getObjects(getUserId(), deviceIds, groupIds, types, from, to); + return eventsReportProvider.getObjects(getUserId(), deviceIds, groupIds, types, from, to); } @Path("events") @GET - @Produces(XLSX) + @Produces(EXCEL) public Response getEventsExcel( - @QueryParam("deviceId") final List<Long> deviceIds, @QueryParam("groupId") final List<Long> groupIds, - @QueryParam("type") final List<String> types, - @QueryParam("from") Date from, @QueryParam("to") Date to, @QueryParam("mail") boolean mail) - throws StorageException, IOException { - Context.getPermissionsManager().checkDisableReports(getUserId()); + @QueryParam("deviceId") List<Long> deviceIds, + @QueryParam("groupId") List<Long> groupIds, + @QueryParam("type") List<String> types, + @QueryParam("from") Date from, + @QueryParam("to") Date to, + @QueryParam("mail") boolean mail) throws StorageException { + permissionsService.checkRestriction(getUserId(), UserRestrictions::getDisableReports); return executeReport(getUserId(), mail, stream -> { LogAction.logReport(getUserId(), "events", from, to, deviceIds, groupIds); - Events.getExcel(stream, getUserId(), deviceIds, groupIds, types, from, to); + eventsReportProvider.getExcel(stream, getUserId(), deviceIds, groupIds, types, from, to); }); } + @Path("events/{type:xlsx|mail}") + @GET + @Produces(EXCEL) + public Response getEventsExcel( + @QueryParam("deviceId") List<Long> deviceIds, + @QueryParam("groupId") List<Long> groupIds, + @QueryParam("type") List<String> types, + @QueryParam("from") Date from, + @QueryParam("to") Date to, + @PathParam("type") String type) throws StorageException { + return getEventsExcel(deviceIds, groupIds, types, from, to, type.equals("mail")); + } + @Path("summary") @GET - public Collection<SummaryReport> getSummary( - @QueryParam("deviceId") final List<Long> deviceIds, @QueryParam("groupId") final List<Long> groupIds, - @QueryParam("from") Date from, @QueryParam("to") Date to, @QueryParam("daily") boolean daily) - throws StorageException { - Context.getPermissionsManager().checkDisableReports(getUserId()); + public Collection<SummaryReportItem> getSummary( + @QueryParam("deviceId") List<Long> deviceIds, + @QueryParam("groupId") List<Long> groupIds, + @QueryParam("from") Date from, + @QueryParam("to") Date to, + @QueryParam("daily") boolean daily) throws StorageException { + permissionsService.checkRestriction(getUserId(), UserRestrictions::getDisableReports); LogAction.logReport(getUserId(), "summary", from, to, deviceIds, groupIds); - return Summary.getObjects(getUserId(), deviceIds, groupIds, from, to, daily); + return summaryReportProvider.getObjects(getUserId(), deviceIds, groupIds, from, to, daily); } @Path("summary") @GET - @Produces(XLSX) + @Produces(EXCEL) public Response getSummaryExcel( - @QueryParam("deviceId") final List<Long> deviceIds, @QueryParam("groupId") final List<Long> groupIds, - @QueryParam("from") Date from, @QueryParam("to") Date to, @QueryParam("daily") boolean daily, - @QueryParam("mail") boolean mail) - throws StorageException, IOException { - Context.getPermissionsManager().checkDisableReports(getUserId()); + @QueryParam("deviceId") List<Long> deviceIds, + @QueryParam("groupId") List<Long> groupIds, + @QueryParam("from") Date from, + @QueryParam("to") Date to, + @QueryParam("daily") boolean daily, + @QueryParam("mail") boolean mail) throws StorageException { + permissionsService.checkRestriction(getUserId(), UserRestrictions::getDisableReports); return executeReport(getUserId(), mail, stream -> { LogAction.logReport(getUserId(), "summary", from, to, deviceIds, groupIds); - Summary.getExcel(stream, getUserId(), deviceIds, groupIds, from, to, daily); + summaryReportProvider.getExcel(stream, getUserId(), deviceIds, groupIds, from, to, daily); }); } + @Path("summary/{type:xlsx|mail}") + @GET + @Produces(EXCEL) + public Response getSummaryExcel( + @QueryParam("deviceId") List<Long> deviceIds, + @QueryParam("groupId") List<Long> groupIds, + @QueryParam("from") Date from, + @QueryParam("to") Date to, + @QueryParam("daily") boolean daily, + @PathParam("type") String type) throws StorageException { + return getSummaryExcel(deviceIds, groupIds, from, to, daily, type.equals("mail")); + } + @Path("trips") @GET - @Produces(MediaType.APPLICATION_JSON) - public Collection<TripReport> getTrips( - @QueryParam("deviceId") final List<Long> deviceIds, @QueryParam("groupId") final List<Long> groupIds, - @QueryParam("from") Date from, @QueryParam("to") Date to) throws StorageException { - Context.getPermissionsManager().checkDisableReports(getUserId()); + public Collection<TripReportItem> getTrips( + @QueryParam("deviceId") List<Long> deviceIds, + @QueryParam("groupId") List<Long> groupIds, + @QueryParam("from") Date from, + @QueryParam("to") Date to) throws StorageException { + permissionsService.checkRestriction(getUserId(), UserRestrictions::getDisableReports); LogAction.logReport(getUserId(), "trips", from, to, deviceIds, groupIds); - return Trips.getObjects(getUserId(), deviceIds, groupIds, from, to); + return tripsReportProvider.getObjects(getUserId(), deviceIds, groupIds, from, to); } @Path("trips") @GET - @Produces(XLSX) + @Produces(EXCEL) public Response getTripsExcel( - @QueryParam("deviceId") final List<Long> deviceIds, @QueryParam("groupId") final List<Long> groupIds, - @QueryParam("from") Date from, @QueryParam("to") Date to, @QueryParam("mail") boolean mail) - throws StorageException, IOException { - Context.getPermissionsManager().checkDisableReports(getUserId()); + @QueryParam("deviceId") List<Long> deviceIds, + @QueryParam("groupId") List<Long> groupIds, + @QueryParam("from") Date from, + @QueryParam("to") Date to, + @QueryParam("mail") boolean mail) throws StorageException { + permissionsService.checkRestriction(getUserId(), UserRestrictions::getDisableReports); return executeReport(getUserId(), mail, stream -> { LogAction.logReport(getUserId(), "trips", from, to, deviceIds, groupIds); - Trips.getExcel(stream, getUserId(), deviceIds, groupIds, from, to); + tripsReportProvider.getExcel(stream, getUserId(), deviceIds, groupIds, from, to); }); } + @Path("trips/{type:xlsx|mail}") + @GET + @Produces(EXCEL) + public Response getTripsExcel( + @QueryParam("deviceId") List<Long> deviceIds, + @QueryParam("groupId") List<Long> groupIds, + @QueryParam("from") Date from, + @QueryParam("to") Date to, + @PathParam("type") String type) throws StorageException { + return getTripsExcel(deviceIds, groupIds, from, to, type.equals("mail")); + } + @Path("stops") @GET - @Produces(MediaType.APPLICATION_JSON) - public Collection<StopReport> getStops( - @QueryParam("deviceId") final List<Long> deviceIds, @QueryParam("groupId") final List<Long> groupIds, - @QueryParam("from") Date from, @QueryParam("to") Date to) throws StorageException { - Context.getPermissionsManager().checkDisableReports(getUserId()); + public Collection<StopReportItem> getStops( + @QueryParam("deviceId") List<Long> deviceIds, + @QueryParam("groupId") List<Long> groupIds, + @QueryParam("from") Date from, + @QueryParam("to") Date to) throws StorageException { + permissionsService.checkRestriction(getUserId(), UserRestrictions::getDisableReports); LogAction.logReport(getUserId(), "stops", from, to, deviceIds, groupIds); - return Stops.getObjects(getUserId(), deviceIds, groupIds, from, to); + return stopsReportProvider.getObjects(getUserId(), deviceIds, groupIds, from, to); } @Path("stops") @GET - @Produces(XLSX) + @Produces(EXCEL) public Response getStopsExcel( - @QueryParam("deviceId") final List<Long> deviceIds, @QueryParam("groupId") final List<Long> groupIds, - @QueryParam("from") Date from, @QueryParam("to") Date to, @QueryParam("mail") boolean mail) - throws StorageException, IOException { - Context.getPermissionsManager().checkDisableReports(getUserId()); + @QueryParam("deviceId") List<Long> deviceIds, + @QueryParam("groupId") List<Long> groupIds, + @QueryParam("from") Date from, + @QueryParam("to") Date to, + @QueryParam("mail") boolean mail) throws StorageException { + permissionsService.checkRestriction(getUserId(), UserRestrictions::getDisableReports); return executeReport(getUserId(), mail, stream -> { LogAction.logReport(getUserId(), "stops", from, to, deviceIds, groupIds); - Stops.getExcel(stream, getUserId(), deviceIds, groupIds, from, to); + stopsReportProvider.getExcel(stream, getUserId(), deviceIds, groupIds, from, to); }); } + @Path("stops/{type:xlsx|mail}") + @GET + @Produces(EXCEL) + public Response getStopsExcel( + @QueryParam("deviceId") List<Long> deviceIds, + @QueryParam("groupId") List<Long> groupIds, + @QueryParam("from") Date from, + @QueryParam("to") Date to, + @PathParam("type") String type) throws StorageException { + return getStopsExcel(deviceIds, groupIds, from, to, type.equals("mail")); + } + } diff --git a/src/main/java/org/traccar/api/resource/ServerResource.java b/src/main/java/org/traccar/api/resource/ServerResource.java index 2d17d5e47..8149ec3b8 100644 --- a/src/main/java/org/traccar/api/resource/ServerResource.java +++ b/src/main/java/org/traccar/api/resource/ServerResource.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 - 2022 Anton Tananaev (anton@traccar.org) + * Copyright 2015 - 2023 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. @@ -15,25 +15,46 @@ */ package org.traccar.api.resource; -import org.traccar.Context; import org.traccar.api.BaseResource; +import org.traccar.config.Config; +import org.traccar.config.Keys; +import org.traccar.database.OpenIdProvider; +import org.traccar.geocoder.Geocoder; +import org.traccar.helper.Log; import org.traccar.helper.LogAction; +import org.traccar.helper.model.UserUtil; +import org.traccar.mail.MailManager; import org.traccar.model.Server; -import org.traccar.storage.Storage; +import org.traccar.model.User; +import org.traccar.session.cache.CacheManager; +import org.traccar.sms.SmsManager; import org.traccar.storage.StorageException; import org.traccar.storage.query.Columns; +import org.traccar.storage.query.Condition; import org.traccar.storage.query.Request; -import javax.annotation.security.PermitAll; -import javax.inject.Inject; -import javax.ws.rs.Consumes; -import javax.ws.rs.GET; -import javax.ws.rs.PUT; -import javax.ws.rs.Path; -import javax.ws.rs.Produces; -import javax.ws.rs.QueryParam; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; +import jakarta.annotation.Nullable; +import jakarta.annotation.security.PermitAll; +import jakarta.inject.Inject; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.PUT; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.QueryParam; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.Collection; +import java.util.TimeZone; @Path("server") @Produces(MediaType.APPLICATION_JSON) @@ -41,18 +62,56 @@ import javax.ws.rs.core.Response; public class ServerResource extends BaseResource { @Inject - private Storage storage; + private Config config; + + @Inject + private CacheManager cacheManager; + + @Inject + private MailManager mailManager; + + @Inject + @Nullable + private SmsManager smsManager; + + @Inject + @Nullable + private OpenIdProvider openIdProvider; + + @Inject + @Nullable + private Geocoder geocoder; @PermitAll @GET public Server get() throws StorageException { - return storage.getObject(Server.class, new Request(new Columns.All())); + Server server = storage.getObject(Server.class, new Request(new Columns.All())); + server.setEmailEnabled(mailManager.getEmailEnabled()); + server.setTextEnabled(smsManager != null); + server.setGeocoderEnabled(geocoder != null); + server.setOpenIdEnabled(openIdProvider != null); + server.setOpenIdForce(openIdProvider != null && openIdProvider.getForce()); + User user = permissionsService.getUser(getUserId()); + if (user != null) { + if (user.getAdministrator()) { + server.setStorageSpace(Log.getStorageSpace()); + } + } else { + server.setNewServer(UserUtil.isEmpty(storage)); + } + if (user != null && user.getAdministrator()) { + server.setStorageSpace(Log.getStorageSpace()); + } + return server; } @PUT public Response update(Server entity) throws StorageException { - Context.getPermissionsManager().checkAdmin(getUserId()); - Context.getPermissionsManager().updateServer(entity); + permissionsService.checkAdmin(getUserId()); + storage.updateObject(entity, new Request( + new Columns.Exclude("id"), + new Condition.Equals("id", entity.getId()))); + cacheManager.updateOrInvalidate(true, entity); LogAction.edit(getUserId(), entity); return Response.ok(entity).build(); } @@ -60,11 +119,36 @@ public class ServerResource extends BaseResource { @Path("geocode") @GET public String geocode(@QueryParam("latitude") double latitude, @QueryParam("longitude") double longitude) { - if (Context.getGeocoder() != null) { - return Context.getGeocoder().getAddress(latitude, longitude, null); + if (geocoder != null) { + return geocoder.getAddress(latitude, longitude, null); } else { throw new RuntimeException("Reverse geocoding is not enabled"); } } + @Path("timezones") + @GET + public Collection<String> timezones() { + return Arrays.asList(TimeZone.getAvailableIDs()); + } + + @Path("file/{path}") + @POST + @Consumes("*/*") + public Response uploadImage(@PathParam("path") String path, File inputFile) throws IOException, StorageException { + permissionsService.checkAdmin(getUserId()); + String root = config.getString(Keys.WEB_OVERRIDE, config.getString(Keys.WEB_PATH)); + + var outputPath = Paths.get(root, path); + var directoryPath = outputPath.getParent(); + if (directoryPath != null) { + Files.createDirectories(directoryPath); + } + + try (var input = new FileInputStream(inputFile); var output = new FileOutputStream(outputPath.toFile())) { + input.transferTo(output); + } + return Response.ok().build(); + } + } diff --git a/src/main/java/org/traccar/api/resource/SessionResource.java b/src/main/java/org/traccar/api/resource/SessionResource.java index 8422e0b49..3e738c15a 100644 --- a/src/main/java/org/traccar/api/resource/SessionResource.java +++ b/src/main/java/org/traccar/api/resource/SessionResource.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 - 2021 Anton Tananaev (anton@traccar.org) + * Copyright 2015 - 2022 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. @@ -15,32 +15,44 @@ */ package org.traccar.api.resource; -import org.traccar.Context; import org.traccar.api.BaseResource; +import org.traccar.api.security.LoginService; +import org.traccar.api.signature.TokenManager; +import org.traccar.database.OpenIdProvider; import org.traccar.helper.DataConverter; -import org.traccar.helper.ServletHelper; import org.traccar.helper.LogAction; +import org.traccar.helper.WebHelper; import org.traccar.model.User; import org.traccar.storage.StorageException; - -import javax.annotation.security.PermitAll; -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletRequest; -import javax.ws.rs.Consumes; -import javax.ws.rs.DELETE; -import javax.ws.rs.FormParam; -import javax.ws.rs.GET; -import javax.ws.rs.POST; -import javax.ws.rs.Path; -import javax.ws.rs.Produces; -import javax.ws.rs.QueryParam; -import javax.ws.rs.WebApplicationException; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; - -import java.io.UnsupportedEncodingException; +import org.traccar.storage.query.Columns; +import org.traccar.storage.query.Condition; +import org.traccar.storage.query.Request; + +import com.nimbusds.oauth2.sdk.ParseException; +import jakarta.annotation.Nullable; +import jakarta.annotation.security.PermitAll; +import jakarta.inject.Inject; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.DELETE; +import jakarta.ws.rs.FormParam; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.QueryParam; +import jakarta.ws.rs.WebApplicationException; +import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import java.io.IOException; import java.net.URLDecoder; import java.nio.charset.StandardCharsets; +import java.security.GeneralSecurityException; +import java.util.Date; +import java.net.URI; @Path("session") @Produces(MediaType.APPLICATION_JSON) @@ -51,18 +63,28 @@ public class SessionResource extends BaseResource { public static final String USER_COOKIE_KEY = "user"; public static final String PASS_COOKIE_KEY = "password"; - @javax.ws.rs.core.Context + @Inject + private LoginService loginService; + + @Inject + @Nullable + private OpenIdProvider openIdProvider; + + @Inject + private TokenManager tokenManager; + + @Context private HttpServletRequest request; @PermitAll @GET - public User get(@QueryParam("token") String token) throws StorageException, UnsupportedEncodingException { + public User get(@QueryParam("token") String token) throws StorageException, IOException, GeneralSecurityException { if (token != null) { - User user = Context.getUsersManager().getUserByToken(token); + User user = loginService.login(token); if (user != null) { - Context.getPermissionsManager().checkUserEnabled(user.getId()); request.getSession().setAttribute(USER_ID_KEY, user.getId()); + LogAction.login(user.getId(), WebHelper.retrieveRemoteAddress(request)); return user; } } @@ -76,54 +98,91 @@ public class SessionResource extends BaseResource { for (Cookie cookie : cookies) { if (cookie.getName().equals(USER_COOKIE_KEY)) { byte[] emailBytes = DataConverter.parseBase64( - URLDecoder.decode(cookie.getValue(), StandardCharsets.US_ASCII.name())); + URLDecoder.decode(cookie.getValue(), StandardCharsets.US_ASCII)); email = new String(emailBytes, StandardCharsets.UTF_8); } else if (cookie.getName().equals(PASS_COOKIE_KEY)) { byte[] passwordBytes = DataConverter.parseBase64( - URLDecoder.decode(cookie.getValue(), StandardCharsets.US_ASCII.name())); + URLDecoder.decode(cookie.getValue(), StandardCharsets.US_ASCII)); password = new String(passwordBytes, StandardCharsets.UTF_8); } } } if (email != null && password != null) { - User user = Context.getPermissionsManager().login(email, password); + User user = loginService.login(email, password); if (user != null) { - Context.getPermissionsManager().checkUserEnabled(user.getId()); request.getSession().setAttribute(USER_ID_KEY, user.getId()); + LogAction.login(user.getId(), WebHelper.retrieveRemoteAddress(request)); return user; } } } else { - Context.getPermissionsManager().checkUserEnabled(userId); - return Context.getPermissionsManager().getUser(userId); + User user = permissionsService.getUser(userId); + if (user != null) { + return user; + } } throw new WebApplicationException(Response.status(Response.Status.NOT_FOUND).build()); } + @Path("{id}") + @GET + public User get(@PathParam("id") long userId) throws StorageException { + permissionsService.checkUser(getUserId(), userId); + User user = storage.getObject(User.class, new Request( + new Columns.All(), new Condition.Equals("id", userId))); + request.getSession().setAttribute(USER_ID_KEY, user.getId()); + LogAction.login(user.getId(), WebHelper.retrieveRemoteAddress(request)); + return user; + } + @PermitAll @POST public User add( @FormParam("email") String email, @FormParam("password") String password) throws StorageException { - User user = Context.getPermissionsManager().login(email, password); + User user = loginService.login(email, password); if (user != null) { request.getSession().setAttribute(USER_ID_KEY, user.getId()); - LogAction.login(user.getId()); + LogAction.login(user.getId(), WebHelper.retrieveRemoteAddress(request)); return user; } else { - LogAction.failedLogin(ServletHelper.retrieveRemoteAddress(request)); + LogAction.failedLogin(WebHelper.retrieveRemoteAddress(request)); throw new WebApplicationException(Response.status(Response.Status.UNAUTHORIZED).build()); } } @DELETE public Response remove() { - LogAction.logout(getUserId()); + LogAction.logout(getUserId(), WebHelper.retrieveRemoteAddress(request)); request.getSession().removeAttribute(USER_ID_KEY); return Response.noContent().build(); } + @Path("token") + @POST + public String requestToken( + @FormParam("expiration") Date expiration) throws StorageException, GeneralSecurityException, IOException { + return tokenManager.generateToken(getUserId(), expiration); + } + + @PermitAll + @Path("openid/auth") + @GET + public Response openIdAuth() throws IOException { + return Response.seeOther(openIdProvider.createAuthUri()).build(); + } + + @PermitAll + @Path("openid/callback") + @GET + public Response requestToken() throws IOException, StorageException, ParseException, GeneralSecurityException { + StringBuilder requestUrl = new StringBuilder(request.getRequestURL().toString()); + String queryString = request.getQueryString(); + String requestUri = requestUrl.append('?').append(queryString).toString(); + + return Response.seeOther(openIdProvider.handleCallback(URI.create(requestUri), request)).build(); + } } diff --git a/src/main/java/org/traccar/api/resource/StatisticsResource.java b/src/main/java/org/traccar/api/resource/StatisticsResource.java index 5c0734877..0c728c77d 100644 --- a/src/main/java/org/traccar/api/resource/StatisticsResource.java +++ b/src/main/java/org/traccar/api/resource/StatisticsResource.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 - 2020 Anton Tananaev (anton@traccar.org) + * Copyright 2016 - 2022 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. @@ -15,17 +15,20 @@ */ package org.traccar.api.resource; -import org.traccar.Context; import org.traccar.api.BaseResource; import org.traccar.model.Statistics; import org.traccar.storage.StorageException; +import org.traccar.storage.query.Columns; +import org.traccar.storage.query.Condition; +import org.traccar.storage.query.Order; +import org.traccar.storage.query.Request; -import javax.ws.rs.Consumes; -import javax.ws.rs.GET; -import javax.ws.rs.Path; -import javax.ws.rs.Produces; -import javax.ws.rs.QueryParam; -import javax.ws.rs.core.MediaType; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.QueryParam; +import jakarta.ws.rs.core.MediaType; import java.util.Collection; import java.util.Date; @@ -37,8 +40,11 @@ public class StatisticsResource extends BaseResource { @GET public Collection<Statistics> get( @QueryParam("from") Date from, @QueryParam("to") Date to) throws StorageException { - Context.getPermissionsManager().checkAdmin(getUserId()); - return Context.getDataManager().getStatistics(from, to); + permissionsService.checkAdmin(getUserId()); + return storage.getObjects(Statistics.class, new Request( + new Columns.All(), + new Condition.Between("captureTime", "from", from, "to", to), + new Order("captureTime"))); } } diff --git a/src/main/java/org/traccar/api/resource/UserResource.java b/src/main/java/org/traccar/api/resource/UserResource.java index 83bb8fd0b..cbee3bd4a 100644 --- a/src/main/java/org/traccar/api/resource/UserResource.java +++ b/src/main/java/org/traccar/api/resource/UserResource.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 - 2017 Anton Tananaev (anton@traccar.org) + * Copyright 2015 - 2022 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. @@ -15,81 +15,99 @@ */ package org.traccar.api.resource; -import org.traccar.Context; import org.traccar.api.BaseObjectResource; -import org.traccar.config.Keys; -import org.traccar.database.UsersManager; +import org.traccar.config.Config; import org.traccar.helper.LogAction; +import org.traccar.helper.model.UserUtil; import org.traccar.model.ManagedUser; +import org.traccar.model.Permission; import org.traccar.model.User; import org.traccar.storage.StorageException; +import org.traccar.storage.query.Columns; +import org.traccar.storage.query.Condition; +import org.traccar.storage.query.Request; -import javax.annotation.security.PermitAll; -import javax.ws.rs.Consumes; -import javax.ws.rs.GET; -import javax.ws.rs.POST; -import javax.ws.rs.Path; -import javax.ws.rs.Produces; -import javax.ws.rs.QueryParam; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; -import java.sql.SQLException; +import jakarta.annotation.security.PermitAll; +import jakarta.inject.Inject; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.QueryParam; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; import java.util.Collection; -import java.util.Date; -import java.util.Set; @Path("users") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public class UserResource extends BaseObjectResource<User> { + @Inject + private Config config; + public UserResource() { super(User.class); } @GET - public Collection<User> get(@QueryParam("userId") long userId) throws SQLException { - UsersManager usersManager = Context.getUsersManager(); - Set<Long> result; - if (Context.getPermissionsManager().getUserAdmin(getUserId())) { - if (userId != 0) { - result = usersManager.getUserItems(userId); - } else { - result = usersManager.getAllItems(); - } - } else if (Context.getPermissionsManager().getUserManager(getUserId())) { - result = usersManager.getManagedItems(getUserId()); + public Collection<User> get(@QueryParam("userId") long userId) throws StorageException { + if (userId > 0) { + permissionsService.checkUser(getUserId(), userId); + return storage.getObjects(baseClass, new Request( + new Columns.All(), + new Condition.Permission(User.class, userId, ManagedUser.class).excludeGroups())); + } else if (permissionsService.notAdmin(getUserId())) { + return storage.getObjects(baseClass, new Request( + new Columns.All(), + new Condition.Permission(User.class, getUserId(), ManagedUser.class).excludeGroups())); } else { - throw new SecurityException("Admin or manager access required"); + return storage.getObjects(baseClass, new Request(new Columns.All())); } - return usersManager.getItems(result); } @Override @PermitAll @POST public Response add(User entity) throws StorageException { - if (!Context.getPermissionsManager().getUserAdmin(getUserId())) { - Context.getPermissionsManager().checkUserUpdate(getUserId(), new User(), entity); - if (Context.getPermissionsManager().getUserManager(getUserId())) { - Context.getPermissionsManager().checkUserLimit(getUserId()); + User currentUser = getUserId() > 0 ? permissionsService.getUser(getUserId()) : null; + if (currentUser == null || !currentUser.getAdministrator()) { + permissionsService.checkUserUpdate(getUserId(), new User(), entity); + if (currentUser != null && currentUser.getUserLimit() != 0) { + int userLimit = currentUser.getUserLimit(); + if (userLimit > 0) { + int userCount = storage.getObjects(baseClass, new Request( + new Columns.All(), + new Condition.Permission(User.class, getUserId(), ManagedUser.class).excludeGroups())) + .size(); + if (userCount >= userLimit) { + throw new SecurityException("Manager user limit reached"); + } + } } else { - Context.getPermissionsManager().checkRegistration(getUserId()); - entity.setDeviceLimit(Context.getConfig().getInteger(Keys.USERS_DEFAULT_DEVICE_LIMIT)); - int expirationDays = Context.getConfig().getInteger(Keys.USERS_DEFAULT_EXPIRATION_DAYS); - if (expirationDays > 0) { - entity.setExpirationTime( - new Date(System.currentTimeMillis() + (long) expirationDays * 24 * 3600 * 1000)); + if (!permissionsService.getServer().getRegistration()) { + throw new SecurityException("Registration disabled"); } + UserUtil.setUserDefaults(entity, config); } } - Context.getUsersManager().addItem(entity); + + if (UserUtil.isEmpty(storage)) { + entity.setAdministrator(true); + } + + entity.setId(storage.addObject(entity, new Request(new Columns.Exclude("id")))); + storage.updateObject(entity, new Request( + new Columns.Include("hashedPassword", "salt"), + new Condition.Equals("id", entity.getId()))); + LogAction.create(getUserId(), entity); - if (Context.getPermissionsManager().getUserManager(getUserId())) { - Context.getDataManager().linkObject(User.class, getUserId(), ManagedUser.class, entity.getId(), true); + + if (currentUser != null && currentUser.getUserLimit() != 0) { + storage.addPermission(new Permission(User.class, getUserId(), ManagedUser.class, entity.getId())); LogAction.link(getUserId(), User.class, getUserId(), ManagedUser.class, entity.getId()); } - Context.getUsersManager().refreshUserItems(); return Response.ok(entity).build(); } |