aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/org/traccar/api
diff options
context:
space:
mode:
authorcasswarry0 <casswarry0@gmail.com>2023-01-17 17:14:53 -0700
committercasswarry0 <casswarry0@gmail.com>2023-01-17 17:14:53 -0700
commit7338b8730949ed027b3f8b31d7dca20687ebbb8b (patch)
treec2d171e6121818ab511460a786f69aab97a2a628 /src/main/java/org/traccar/api
parentcdecd3fa4427a382c0b09f8ad9d69ec14388960a (diff)
parent85501f9cf4918d5eee345f83aed7a31eecb26b8d (diff)
downloadtrackermap-server-7338b8730949ed027b3f8b31d7dca20687ebbb8b.tar.gz
trackermap-server-7338b8730949ed027b3f8b31d7dca20687ebbb8b.tar.bz2
trackermap-server-7338b8730949ed027b3f8b31d7dca20687ebbb8b.zip
Merge branch 'master' into develop
Diffstat (limited to 'src/main/java/org/traccar/api')
-rw-r--r--src/main/java/org/traccar/api/AsyncSocket.java34
-rw-r--r--src/main/java/org/traccar/api/AsyncSocketServlet.java35
-rw-r--r--src/main/java/org/traccar/api/BaseObjectResource.java185
-rw-r--r--src/main/java/org/traccar/api/BaseResource.java17
-rw-r--r--src/main/java/org/traccar/api/CorsResponseFilter.java16
-rw-r--r--src/main/java/org/traccar/api/ExtendedObjectResource.java57
-rw-r--r--src/main/java/org/traccar/api/HealthCheckService.java92
-rw-r--r--src/main/java/org/traccar/api/MediaFilter.java53
-rw-r--r--src/main/java/org/traccar/api/SimpleObjectResource.java38
-rw-r--r--src/main/java/org/traccar/api/resource/AttributeResource.java69
-rw-r--r--src/main/java/org/traccar/api/resource/CommandResource.java122
-rw-r--r--src/main/java/org/traccar/api/resource/DeviceResource.java166
-rw-r--r--src/main/java/org/traccar/api/resource/EventResource.java28
-rw-r--r--src/main/java/org/traccar/api/resource/NotificationResource.java60
-rw-r--r--src/main/java/org/traccar/api/resource/PasswordResource.java69
-rw-r--r--src/main/java/org/traccar/api/resource/PermissionsResource.java87
-rw-r--r--src/main/java/org/traccar/api/resource/PositionResource.java108
-rw-r--r--src/main/java/org/traccar/api/resource/ReportResource.java286
-rw-r--r--src/main/java/org/traccar/api/resource/ServerResource.java66
-rw-r--r--src/main/java/org/traccar/api/resource/SessionResource.java97
-rw-r--r--src/main/java/org/traccar/api/resource/StatisticsResource.java18
-rw-r--r--src/main/java/org/traccar/api/resource/UserResource.java89
-rw-r--r--src/main/java/org/traccar/api/security/LoginService.java99
-rw-r--r--src/main/java/org/traccar/api/security/PermissionsService.java211
-rw-r--r--src/main/java/org/traccar/api/security/SecurityRequestFilter.java (renamed from src/main/java/org/traccar/api/SecurityRequestFilter.java)39
-rw-r--r--src/main/java/org/traccar/api/security/ServiceAccountUser.java (renamed from src/main/java/org/traccar/api/ObjectMapperProvider.java)22
-rw-r--r--src/main/java/org/traccar/api/security/UserPrincipal.java (renamed from src/main/java/org/traccar/api/UserPrincipal.java)2
-rw-r--r--src/main/java/org/traccar/api/security/UserSecurityContext.java (renamed from src/main/java/org/traccar/api/UserSecurityContext.java)6
-rw-r--r--src/main/java/org/traccar/api/signature/CryptoManager.java103
-rw-r--r--src/main/java/org/traccar/api/signature/KeystoreModel.java44
-rw-r--r--src/main/java/org/traccar/api/signature/TokenManager.java77
31 files changed, 1685 insertions, 710 deletions
diff --git a/src/main/java/org/traccar/api/AsyncSocket.java b/src/main/java/org/traccar/api/AsyncSocket.java
index b1853822d..5fc4b4412 100644
--- a/src/main/java/org/traccar/api/AsyncSocket.java
+++ b/src/main/java/org/traccar/api/AsyncSocket.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.
@@ -16,15 +16,18 @@
package org.traccar.api;
import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.WebSocketAdapter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.traccar.Context;
-import org.traccar.database.ConnectionManager;
+import org.traccar.helper.model.PositionUtil;
+import org.traccar.session.ConnectionManager;
import org.traccar.model.Device;
import org.traccar.model.Event;
import org.traccar.model.Position;
+import org.traccar.storage.Storage;
+import org.traccar.storage.StorageException;
import java.util.Collection;
import java.util.Collections;
@@ -39,9 +42,15 @@ public class AsyncSocket extends WebSocketAdapter implements ConnectionManager.U
private static final String KEY_POSITIONS = "positions";
private static final String KEY_EVENTS = "events";
+ private final ObjectMapper objectMapper;
+ private final ConnectionManager connectionManager;
+ private final Storage storage;
private final long userId;
- public AsyncSocket(long userId) {
+ public AsyncSocket(ObjectMapper objectMapper, ConnectionManager connectionManager, Storage storage, long userId) {
+ this.objectMapper = objectMapper;
+ this.connectionManager = connectionManager;
+ this.storage = storage;
this.userId = userId;
}
@@ -49,18 +58,21 @@ public class AsyncSocket extends WebSocketAdapter implements ConnectionManager.U
public void onWebSocketConnect(Session session) {
super.onWebSocketConnect(session);
- Map<String, Collection<?>> data = new HashMap<>();
- data.put(KEY_POSITIONS, Context.getDeviceManager().getInitialState(userId));
- sendData(data);
-
- Context.getConnectionManager().addListener(userId, this);
+ try {
+ Map<String, Collection<?>> data = new HashMap<>();
+ data.put(KEY_POSITIONS, PositionUtil.getLatestPositions(storage, userId));
+ sendData(data);
+ connectionManager.addListener(userId, this);
+ } catch (StorageException e) {
+ throw new RuntimeException(e);
+ }
}
@Override
public void onWebSocketClose(int statusCode, String reason) {
super.onWebSocketClose(statusCode, reason);
- Context.getConnectionManager().removeListener(userId, this);
+ connectionManager.removeListener(userId, this);
}
@Override
@@ -92,7 +104,7 @@ public class AsyncSocket extends WebSocketAdapter implements ConnectionManager.U
private void sendData(Map<String, Collection<?>> data) {
if (isConnected()) {
try {
- getRemote().sendString(Context.getObjectMapper().writeValueAsString(data), null);
+ getRemote().sendString(objectMapper.writeValueAsString(data), null);
} catch (JsonProcessingException e) {
LOGGER.warn("Socket JSON formatting error", e);
}
diff --git a/src/main/java/org/traccar/api/AsyncSocketServlet.java b/src/main/java/org/traccar/api/AsyncSocketServlet.java
index a964ead10..91a745eeb 100644
--- a/src/main/java/org/traccar/api/AsyncSocketServlet.java
+++ b/src/main/java/org/traccar/api/AsyncSocketServlet.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,27 +15,48 @@
*/
package org.traccar.api;
+import com.fasterxml.jackson.databind.ObjectMapper;
import org.eclipse.jetty.websocket.server.JettyWebSocketServlet;
import org.eclipse.jetty.websocket.server.JettyWebSocketServletFactory;
-import org.traccar.Context;
import org.traccar.api.resource.SessionResource;
+import org.traccar.config.Config;
import org.traccar.config.Keys;
+import org.traccar.session.ConnectionManager;
+import org.traccar.storage.Storage;
+import javax.inject.Inject;
+import javax.inject.Singleton;
import javax.servlet.http.HttpSession;
import java.time.Duration;
+@Singleton
public class AsyncSocketServlet extends JettyWebSocketServlet {
+ private final Config config;
+ private final ObjectMapper objectMapper;
+ private final ConnectionManager connectionManager;
+ private final Storage storage;
+
+ @Inject
+ public AsyncSocketServlet(
+ Config config, ObjectMapper objectMapper, ConnectionManager connectionManager, Storage storage) {
+ this.config = config;
+ this.objectMapper = objectMapper;
+ this.connectionManager = connectionManager;
+ this.storage = storage;
+ }
+
@Override
public void configure(JettyWebSocketServletFactory factory) {
- factory.setIdleTimeout(Duration.ofMillis(Context.getConfig().getLong(Keys.WEB_TIMEOUT)));
+ factory.setIdleTimeout(Duration.ofMillis(config.getLong(Keys.WEB_TIMEOUT)));
factory.setCreator((req, resp) -> {
if (req.getSession() != null) {
- long userId = (Long) ((HttpSession) req.getSession()).getAttribute(SessionResource.USER_ID_KEY);
- return new AsyncSocket(userId);
- } else {
- return null;
+ Long userId = (Long) ((HttpSession) req.getSession()).getAttribute(SessionResource.USER_ID_KEY);
+ if (userId != null) {
+ return new AsyncSocket(objectMapper, connectionManager, storage, userId);
+ }
}
+ return null;
});
}
diff --git a/src/main/java/org/traccar/api/BaseObjectResource.java b/src/main/java/org/traccar/api/BaseObjectResource.java
index 71f3939cb..904781e54 100644
--- a/src/main/java/org/traccar/api/BaseObjectResource.java
+++ b/src/main/java/org/traccar/api/BaseObjectResource.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2017 - 2020 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,9 +16,19 @@
*/
package org.traccar.api;
-import java.sql.SQLException;
-import java.util.Set;
-
+import org.traccar.helper.LogAction;
+import org.traccar.model.BaseModel;
+import org.traccar.model.Group;
+import org.traccar.model.Permission;
+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.inject.Inject;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
@@ -27,58 +37,26 @@ import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.core.Response;
-import org.traccar.Context;
-import org.traccar.database.BaseObjectManager;
-import org.traccar.database.ExtendedObjectManager;
-import org.traccar.database.ManagableObjects;
-import org.traccar.database.SimpleObjectManager;
-import org.traccar.helper.LogAction;
-import org.traccar.model.BaseModel;
-import org.traccar.model.Calendar;
-import org.traccar.model.Command;
-import org.traccar.model.Device;
-import org.traccar.model.Group;
-import org.traccar.model.GroupedModel;
-import org.traccar.model.ScheduledModel;
-import org.traccar.model.User;
-
public abstract class BaseObjectResource<T extends BaseModel> extends BaseResource {
- private final Class<T> baseClass;
+ @Inject
+ private CacheManager cacheManager;
- public BaseObjectResource(Class<T> baseClass) {
- this.baseClass = baseClass;
- }
+ @Inject
+ private ConnectionManager connectionManager;
- protected final Class<T> getBaseClass() {
- return baseClass;
- }
+ protected final Class<T> baseClass;
- protected final Set<Long> getSimpleManagerItems(BaseObjectManager<T> manager, boolean all, long userId) {
- Set<Long> result;
- if (all) {
- if (Context.getPermissionsManager().getUserAdmin(getUserId())) {
- result = manager.getAllItems();
- } else {
- Context.getPermissionsManager().checkManager(getUserId());
- result = ((ManagableObjects) manager).getManagedItems(getUserId());
- }
- } else {
- if (userId == 0) {
- userId = getUserId();
- }
- Context.getPermissionsManager().checkUser(getUserId(), userId);
- result = ((ManagableObjects) manager).getUserItems(userId);
- }
- return result;
+ public BaseObjectResource(Class<T> baseClass) {
+ this.baseClass = baseClass;
}
@Path("{id}")
@GET
- public Response getSingle(@PathParam("id") long id) throws SQLException {
- Context.getPermissionsManager().checkPermission(baseClass, getUserId(), id);
- BaseObjectManager<T> manager = Context.getManager(baseClass);
- T entity = manager.getById(id);
+ public Response getSingle(@PathParam("id") long id) throws StorageException {
+ permissionsService.checkPermission(baseClass, getUserId(), id);
+ T entity = storage.getObject(baseClass, new Request(
+ new Columns.All(), new Condition.Equals("id", id)));
if (entity != null) {
return Response.ok(entity).build();
} else {
@@ -87,103 +65,64 @@ public abstract class BaseObjectResource<T extends BaseModel> extends BaseResour
}
@POST
- public Response add(T entity) throws SQLException {
- Context.getPermissionsManager().checkReadonly(getUserId());
- if (baseClass.equals(Device.class)) {
- Context.getPermissionsManager().checkDeviceReadonly(getUserId());
- Context.getPermissionsManager().checkDeviceLimit(getUserId());
- } else if (baseClass.equals(Command.class)) {
- Context.getPermissionsManager().checkLimitCommands(getUserId());
- } else if (entity instanceof GroupedModel && ((GroupedModel) entity).getGroupId() != 0) {
- Context.getPermissionsManager().checkPermission(
- Group.class, getUserId(), ((GroupedModel) entity).getGroupId());
- } else if (entity instanceof ScheduledModel && ((ScheduledModel) entity).getCalendarId() != 0) {
- Context.getPermissionsManager().checkPermission(
- Calendar.class, getUserId(), ((ScheduledModel) entity).getCalendarId());
- }
+ public Response add(T entity) throws StorageException {
+ permissionsService.checkEdit(getUserId(), entity, true);
- BaseObjectManager<T> manager = Context.getManager(baseClass);
- manager.addItem(entity);
+ entity.setId(storage.addObject(entity, new Request(new Columns.Exclude("id"))));
LogAction.create(getUserId(), entity);
-
- Context.getDataManager().linkObject(User.class, getUserId(), baseClass, entity.getId(), true);
+ storage.addPermission(new Permission(User.class, getUserId(), baseClass, entity.getId()));
+ cacheManager.invalidatePermission(true, User.class, getUserId(), baseClass, entity.getId());
+ connectionManager.invalidatePermission(true, User.class, getUserId(), baseClass, entity.getId());
LogAction.link(getUserId(), User.class, getUserId(), baseClass, entity.getId());
- if (manager instanceof SimpleObjectManager) {
- ((SimpleObjectManager<T>) manager).refreshUserItems();
- } else if (baseClass.equals(Group.class) || baseClass.equals(Device.class)) {
- Context.getPermissionsManager().refreshDeviceAndGroupPermissions();
- Context.getPermissionsManager().refreshAllExtendedPermissions();
- }
return Response.ok(entity).build();
}
@Path("{id}")
@PUT
- public Response update(T entity) throws SQLException {
- Context.getPermissionsManager().checkReadonly(getUserId());
- if (baseClass.equals(Device.class)) {
- Context.getPermissionsManager().checkDeviceReadonly(getUserId());
- } else if (baseClass.equals(User.class)) {
- User before = Context.getPermissionsManager().getUser(entity.getId());
- Context.getPermissionsManager().checkUserUpdate(getUserId(), before, (User) entity);
- } else if (baseClass.equals(Command.class)) {
- Context.getPermissionsManager().checkLimitCommands(getUserId());
- } else if (entity instanceof GroupedModel && ((GroupedModel) entity).getGroupId() != 0) {
- Context.getPermissionsManager().checkPermission(
- Group.class, getUserId(), ((GroupedModel) entity).getGroupId());
- } else if (entity instanceof ScheduledModel && ((ScheduledModel) entity).getCalendarId() != 0) {
- Context.getPermissionsManager().checkPermission(
- Calendar.class, getUserId(), ((ScheduledModel) entity).getCalendarId());
+ public Response update(T entity) throws StorageException {
+ permissionsService.checkEdit(getUserId(), entity, false);
+ permissionsService.checkPermission(baseClass, getUserId(), entity.getId());
+
+ if (entity instanceof User) {
+ User before = storage.getObject(User.class, new Request(
+ new Columns.All(), new Condition.Equals("id", entity.getId())));
+ permissionsService.checkUserUpdate(getUserId(), before, (User) entity);
+ } else if (entity instanceof Group) {
+ Group group = (Group) entity;
+ if (group.getId() == group.getGroupId()) {
+ throw new IllegalArgumentException("Cycle in group hierarchy");
+ }
}
- Context.getPermissionsManager().checkPermission(baseClass, getUserId(), entity.getId());
- Context.getManager(baseClass).updateItem(entity);
+ storage.updateObject(entity, new Request(
+ new Columns.Exclude("id"),
+ new Condition.Equals("id", entity.getId())));
+ if (entity instanceof User) {
+ User user = (User) entity;
+ if (user.getHashedPassword() != null) {
+ storage.updateObject(entity, new Request(
+ new Columns.Include("hashedPassword", "salt"),
+ new Condition.Equals("id", entity.getId())));
+ }
+ }
+ cacheManager.updateOrInvalidate(true, entity);
LogAction.edit(getUserId(), entity);
- if (baseClass.equals(Group.class) || baseClass.equals(Device.class)) {
- Context.getPermissionsManager().refreshDeviceAndGroupPermissions();
- Context.getPermissionsManager().refreshAllExtendedPermissions();
- }
return Response.ok(entity).build();
}
@Path("{id}")
@DELETE
- public Response remove(@PathParam("id") long id) throws SQLException {
- Context.getPermissionsManager().checkReadonly(getUserId());
- if (baseClass.equals(Device.class)) {
- Context.getPermissionsManager().checkDeviceReadonly(getUserId());
- } else if (baseClass.equals(Command.class)) {
- Context.getPermissionsManager().checkLimitCommands(getUserId());
- }
- Context.getPermissionsManager().checkPermission(baseClass, getUserId(), id);
+ public Response remove(@PathParam("id") long id) throws StorageException {
+ permissionsService.checkEdit(getUserId(), baseClass, false);
+ permissionsService.checkPermission(baseClass, getUserId(), id);
+
+ storage.removeObject(baseClass, new Request(new Condition.Equals("id", id)));
+ cacheManager.invalidate(baseClass, id);
- BaseObjectManager<T> manager = Context.getManager(baseClass);
- manager.removeItem(id);
LogAction.remove(getUserId(), baseClass, id);
- if (manager instanceof SimpleObjectManager) {
- ((SimpleObjectManager<T>) manager).refreshUserItems();
- if (manager instanceof ExtendedObjectManager) {
- ((ExtendedObjectManager<T>) manager).refreshExtendedPermissions();
- }
- }
- if (baseClass.equals(Group.class) || baseClass.equals(Device.class) || baseClass.equals(User.class)) {
- if (baseClass.equals(Group.class)) {
- Context.getGroupsManager().refreshItems();
- Context.getDeviceManager().updateDeviceCache(true);
- }
- Context.getPermissionsManager().refreshDeviceAndGroupPermissions();
- if (baseClass.equals(User.class)) {
- Context.getPermissionsManager().refreshAllUsersPermissions();
- } else {
- Context.getPermissionsManager().refreshAllExtendedPermissions();
- }
- } else if (baseClass.equals(Calendar.class)) {
- Context.getGeofenceManager().refreshItems();
- Context.getNotificationManager().refreshItems();
- }
return Response.noContent().build();
}
diff --git a/src/main/java/org/traccar/api/BaseResource.java b/src/main/java/org/traccar/api/BaseResource.java
index cc272df9c..33abe73fa 100644
--- a/src/main/java/org/traccar/api/BaseResource.java
+++ b/src/main/java/org/traccar/api/BaseResource.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,13 +15,25 @@
*/
package org.traccar.api;
+import org.traccar.api.security.PermissionsService;
+import org.traccar.api.security.UserPrincipal;
+import org.traccar.storage.Storage;
+
+import javax.inject.Inject;
+import javax.ws.rs.core.Context;
import javax.ws.rs.core.SecurityContext;
public class BaseResource {
- @javax.ws.rs.core.Context
+ @Context
private SecurityContext securityContext;
+ @Inject
+ protected Storage storage;
+
+ @Inject
+ protected PermissionsService permissionsService;
+
protected long getUserId() {
UserPrincipal principal = (UserPrincipal) securityContext.getUserPrincipal();
if (principal != null) {
@@ -29,4 +41,5 @@ public class BaseResource {
}
return 0;
}
+
}
diff --git a/src/main/java/org/traccar/api/CorsResponseFilter.java b/src/main/java/org/traccar/api/CorsResponseFilter.java
index 91aea5718..67d0341a1 100644
--- a/src/main/java/org/traccar/api/CorsResponseFilter.java
+++ b/src/main/java/org/traccar/api/CorsResponseFilter.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.
@@ -16,16 +16,26 @@
package org.traccar.api;
import io.netty.handler.codec.http.HttpHeaderNames;
-import org.traccar.Context;
+import org.traccar.config.Config;
import org.traccar.config.Keys;
+import javax.inject.Inject;
+import javax.inject.Singleton;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerResponseContext;
import javax.ws.rs.container.ContainerResponseFilter;
import java.io.IOException;
+@Singleton
public class CorsResponseFilter implements ContainerResponseFilter {
+ private final String allowed;
+
+ @Inject
+ public CorsResponseFilter(Config config) {
+ allowed = config.getString(Keys.WEB_ORIGIN);
+ }
+
private static final String ORIGIN_ALL = "*";
private static final String HEADERS_ALL = "origin, content-type, accept, authorization";
private static final String METHODS_ALL = "GET, POST, PUT, DELETE, OPTIONS";
@@ -46,8 +56,6 @@ public class CorsResponseFilter implements ContainerResponseFilter {
if (!response.getHeaders().containsKey(HttpHeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN.toString())) {
String origin = request.getHeaderString(HttpHeaderNames.ORIGIN.toString());
- String allowed = Context.getConfig().getString(Keys.WEB_ORIGIN);
-
if (origin == null) {
response.getHeaders().add(HttpHeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN.toString(), ORIGIN_ALL);
} else if (allowed == null || allowed.equals(ORIGIN_ALL) || allowed.contains(origin)) {
diff --git a/src/main/java/org/traccar/api/ExtendedObjectResource.java b/src/main/java/org/traccar/api/ExtendedObjectResource.java
index 9e554217e..8467b46c6 100644
--- a/src/main/java/org/traccar/api/ExtendedObjectResource.java
+++ b/src/main/java/org/traccar/api/ExtendedObjectResource.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,17 +16,19 @@
*/
package org.traccar.api;
-import java.sql.SQLException;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.Set;
+import org.traccar.model.BaseModel;
+import org.traccar.model.Device;
+import org.traccar.model.Group;
+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.ws.rs.GET;
import javax.ws.rs.QueryParam;
-
-import org.traccar.Context;
-import org.traccar.database.ExtendedObjectManager;
-import org.traccar.model.BaseModel;
+import java.util.Collection;
+import java.util.LinkedList;
public class ExtendedObjectResource<T extends BaseModel> extends BaseObjectResource<T> {
@@ -36,27 +38,34 @@ public class ExtendedObjectResource<T extends BaseModel> extends BaseObjectResou
@GET
public Collection<T> get(
- @QueryParam("all") boolean all, @QueryParam("userId") long userId, @QueryParam("groupId") long groupId,
- @QueryParam("deviceId") long deviceId, @QueryParam("refresh") boolean refresh) throws SQLException {
-
- ExtendedObjectManager<T> manager = (ExtendedObjectManager<T>) Context.getManager(getBaseClass());
- if (refresh) {
- manager.refreshItems();
- }
+ @QueryParam("all") boolean all, @QueryParam("userId") long userId,
+ @QueryParam("groupId") long groupId, @QueryParam("deviceId") long deviceId) throws StorageException {
- Set<Long> result = new HashSet<>(getSimpleManagerItems(manager, all, userId));
+ var conditions = new LinkedList<Condition>();
- if (groupId != 0) {
- Context.getPermissionsManager().checkGroup(getUserId(), groupId);
- result.retainAll(manager.getGroupItems(groupId));
+ 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());
+ }
}
- if (deviceId != 0) {
- Context.getPermissionsManager().checkDevice(getUserId(), deviceId);
- result.retainAll(manager.getDeviceItems(deviceId));
+ if (groupId > 0) {
+ permissionsService.checkPermission(Group.class, getUserId(), groupId);
+ conditions.add(new Condition.Permission(Group.class, groupId, baseClass).excludeGroups());
+ }
+ if (deviceId > 0) {
+ permissionsService.checkPermission(Device.class, getUserId(), deviceId);
+ conditions.add(new Condition.Permission(Device.class, deviceId, baseClass).excludeGroups());
}
- return manager.getItems(result);
+ return storage.getObjects(baseClass, new Request(new Columns.All(), Condition.merge(conditions)));
}
}
diff --git a/src/main/java/org/traccar/api/HealthCheckService.java b/src/main/java/org/traccar/api/HealthCheckService.java
deleted file mode 100644
index 0182cc358..000000000
--- a/src/main/java/org/traccar/api/HealthCheckService.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright 2020 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;
-
-import com.sun.jna.Library;
-import com.sun.jna.Native;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.traccar.Context;
-import org.traccar.config.Keys;
-
-import java.util.TimerTask;
-
-public class HealthCheckService {
-
- private static final Logger LOGGER = LoggerFactory.getLogger(HealthCheckService.class);
-
- private SystemD systemD;
-
- private boolean enabled;
- private long period;
-
- public HealthCheckService() {
- if (!Context.getConfig().getBoolean(Keys.WEB_DISABLE_HEALTH_CHECK)
- && System.getProperty("os.name").toLowerCase().startsWith("linux")) {
- try {
- systemD = Native.load("systemd", SystemD.class);
- String watchdogTimer = System.getenv("WATCHDOG_USEC");
- if (watchdogTimer != null && !watchdogTimer.isEmpty()) {
- period = Long.parseLong(watchdogTimer) / 1000 * 4 / 5;
- }
- if (period > 0) {
- LOGGER.info("Health check enabled with period {}", period);
- enabled = true;
- }
- } catch (UnsatisfiedLinkError e) {
- LOGGER.warn("No systemd support", e);
- }
- }
- }
-
- public boolean isEnabled() {
- return enabled;
- }
-
- public long getPeriod() {
- return period;
- }
-
- private String getUrl() {
- String address = Context.getConfig().getString(Keys.WEB_ADDRESS, "localhost");
- int port = Context.getConfig().getInteger(Keys.WEB_PORT);
- return "http://" + address + ":" + port + "/api/server";
- }
-
- public TimerTask createTask() {
- return new TimerTask() {
- @Override
- public void run() {
- LOGGER.debug("Health check running");
- int status = Context.getClient().target(getUrl()).request().get().getStatus();
- if (status == 200) {
- int result = systemD.sd_notify(0, "WATCHDOG=1");
- if (result < 0) {
- LOGGER.warn("Health check notify error {}", result);
- }
- } else {
- LOGGER.warn("Health check failed with status {}", status);
- }
- }
- };
- }
-
- interface SystemD extends Library {
- @SuppressWarnings("checkstyle:MethodName")
- int sd_notify(@SuppressWarnings("checkstyle:ParameterName") int unset_environment, String state);
- }
-
-}
diff --git a/src/main/java/org/traccar/api/MediaFilter.java b/src/main/java/org/traccar/api/MediaFilter.java
index 77731a810..ab75bdc5d 100644
--- a/src/main/java/org/traccar/api/MediaFilter.java
+++ b/src/main/java/org/traccar/api/MediaFilter.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2018 - 2021 Anton Tananaev (anton@traccar.org)
+ * Copyright 2018 - 2022 Anton Tananaev (anton@traccar.org)
* Copyright 2018 Andrey Kunitsyn (andrey@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,9 +16,20 @@
*/
package org.traccar.api;
-import java.io.IOException;
-import java.sql.SQLException;
+import com.google.inject.Provider;
+import org.traccar.api.resource.SessionResource;
+import org.traccar.api.security.PermissionsService;
+import org.traccar.database.StatisticsManager;
+import org.traccar.helper.Log;
+import org.traccar.model.Device;
+import org.traccar.storage.Storage;
+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.inject.Inject;
+import javax.inject.Singleton;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
@@ -28,16 +39,24 @@ import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
+import java.io.IOException;
-import org.traccar.Context;
-import org.traccar.Main;
-import org.traccar.api.resource.SessionResource;
-import org.traccar.database.StatisticsManager;
-import org.traccar.helper.Log;
-import org.traccar.model.Device;
-
+@Singleton
public class MediaFilter implements Filter {
+ private final Storage storage;
+ private final StatisticsManager statisticsManager;
+ private final Provider<PermissionsService> permissionsServiceProvider;
+
+ @Inject
+ public MediaFilter(
+ Storage storage, StatisticsManager statisticsManager,
+ Provider<PermissionsService> permissionsServiceProvider) {
+ this.storage = storage;
+ this.statisticsManager = statisticsManager;
+ this.permissionsServiceProvider = permissionsServiceProvider;
+ }
+
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@@ -45,6 +64,7 @@ public class MediaFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
+
HttpServletResponse httpResponse = (HttpServletResponse) response;
try {
HttpSession session = ((HttpServletRequest) request).getSession(false);
@@ -52,8 +72,7 @@ public class MediaFilter implements Filter {
if (session != null) {
userId = (Long) session.getAttribute(SessionResource.USER_ID_KEY);
if (userId != null) {
- Context.getPermissionsManager().checkUserEnabled(userId);
- Main.getInjector().getInstance(StatisticsManager.class).registerRequest(userId);
+ statisticsManager.registerRequest(userId);
}
}
if (userId == null) {
@@ -64,21 +83,19 @@ public class MediaFilter implements Filter {
String path = ((HttpServletRequest) request).getPathInfo();
String[] parts = path != null ? path.split("/") : null;
if (parts != null && parts.length >= 2) {
- Device device = Context.getDeviceManager().getByUniqueId(parts[1]);
+ Device device = storage.getObject(Device.class, new Request(
+ new Columns.All(), new Condition.Equals("uniqueId", parts[1])));
if (device != null) {
- Context.getPermissionsManager().checkDevice(userId, device.getId());
+ permissionsServiceProvider.get().checkPermission(Device.class, userId, device.getId());
chain.doFilter(request, response);
return;
}
}
httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN);
- } catch (SecurityException e) {
+ } catch (SecurityException | StorageException e) {
httpResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
httpResponse.getWriter().println(Log.exceptionStack(e));
- } catch (SQLException e) {
- httpResponse.setStatus(HttpServletResponse.SC_BAD_REQUEST);
- httpResponse.getWriter().println(Log.exceptionStack(e));
}
}
diff --git a/src/main/java/org/traccar/api/SimpleObjectResource.java b/src/main/java/org/traccar/api/SimpleObjectResource.java
index a7fcae0e7..4a435ca7d 100644
--- a/src/main/java/org/traccar/api/SimpleObjectResource.java
+++ b/src/main/java/org/traccar/api/SimpleObjectResource.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,15 +16,17 @@
*/
package org.traccar.api;
-import java.sql.SQLException;
-import java.util.Collection;
+import org.traccar.model.BaseModel;
+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.ws.rs.GET;
import javax.ws.rs.QueryParam;
-
-import org.traccar.Context;
-import org.traccar.database.BaseObjectManager;
-import org.traccar.model.BaseModel;
+import java.util.Collection;
+import java.util.LinkedList;
public class SimpleObjectResource<T extends BaseModel> extends BaseObjectResource<T> {
@@ -34,10 +36,24 @@ public class SimpleObjectResource<T extends BaseModel> extends BaseObjectResourc
@GET
public Collection<T> get(
- @QueryParam("all") boolean all, @QueryParam("userId") long userId) throws SQLException {
-
- BaseObjectManager<T> manager = Context.getManager(getBaseClass());
- return manager.getItems(getSimpleManagerItems(manager, all, userId));
+ @QueryParam("all") boolean all, @QueryParam("userId") long userId) throws StorageException {
+
+ var conditions = new LinkedList<Condition>();
+
+ if (all) {
+ if (permissionsService.notAdmin(getUserId())) {
+ conditions.add(new Condition.Permission(User.class, getUserId(), baseClass));
+ }
+ } else {
+ if (userId == 0) {
+ userId = getUserId();
+ } else {
+ permissionsService.checkUser(getUserId(), userId);
+ }
+ conditions.add(new Condition.Permission(User.class, userId, baseClass));
+ }
+
+ return storage.getObjects(baseClass, new Request(new Columns.All(), Condition.merge(conditions)));
}
}
diff --git a/src/main/java/org/traccar/api/resource/AttributeResource.java b/src/main/java/org/traccar/api/resource/AttributeResource.java
index de69d871c..f85e90133 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,8 +16,7 @@
*/
package org.traccar.api.resource;
-import java.sql.SQLException;
-
+import javax.inject.Inject;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.POST;
@@ -29,68 +28,72 @@ import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.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 SQLException {
- Context.getPermissionsManager().checkAdmin(getUserId());
+ public Response add(Attribute entity) throws StorageException {
+ permissionsService.checkAdmin(getUserId());
return super.add(entity);
}
@Path("{id}")
@PUT
- public Response update(Attribute entity) throws SQLException {
- Context.getPermissionsManager().checkAdmin(getUserId());
+ public Response update(Attribute entity) throws StorageException {
+ permissionsService.checkAdmin(getUserId());
return super.update(entity);
}
@Path("{id}")
@DELETE
- public Response remove(@PathParam("id") long id) throws SQLException {
- Context.getPermissionsManager().checkAdmin(getUserId());
+ public Response remove(@PathParam("id") long id) throws StorageException {
+ permissionsService.checkAdmin(getUserId());
return super.remove(id);
}
diff --git a/src/main/java/org/traccar/api/resource/CommandResource.java b/src/main/java/org/traccar/api/resource/CommandResource.java
index a31345246..6ef6ee9c5 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,16 +17,24 @@
*/
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.model.Command;
+import org.traccar.model.Device;
+import org.traccar.model.Position;
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 java.util.Collection;
-import java.util.HashSet;
-import java.util.Set;
-
+import javax.inject.Inject;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
@@ -35,40 +43,80 @@ import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.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.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);
+ permissionsService.checkRestriction(getUserId(), UserRestrictions::getReadonly);
+ 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)) {
+ permissionsService.checkPermission(Device.class, getUserId(), entity.getDeviceId());
+ if (!commandsManager.sendCommand(entity)) {
return Response.accepted(entity).build();
}
return Response.ok(entity).build();
@@ -78,15 +126,33 @@ public class CommandResource extends ExtendedObjectResource<Command> {
@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 7006cdb84..c0b0cea0d 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,33 +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.inject.Inject;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
+import javax.ws.rs.HeaderParam;
+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.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
-
-import java.sql.SQLException;
+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);
}
@@ -50,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 SQLException {
- if (!Context.getPermissionsManager().getUserAdmin(getUserId())) {
- Context.getPermissionsManager().checkManager(getUserId());
- Context.getPermissionsManager().checkPermission(Device.class, getUserId(), entity.getDeviceId());
+ public Response updateAccumulators(DeviceAccumulators entity) throws StorageException {
+ 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/EventResource.java b/src/main/java/org/traccar/api/resource/EventResource.java
index 34e4a94ce..afdaf52b5 100644
--- a/src/main/java/org/traccar/api/resource/EventResource.java
+++ b/src/main/java/org/traccar/api/resource/EventResource.java
@@ -15,7 +15,13 @@
*/
package org.traccar.api.resource;
-import java.sql.SQLException;
+import org.traccar.api.BaseResource;
+import org.traccar.model.Device;
+import org.traccar.model.Event;
+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;
@@ -26,32 +32,20 @@ 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.Event;
-import org.traccar.model.Geofence;
-import org.traccar.model.Maintenance;
-
@Path("events")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
-
public class EventResource extends BaseResource {
@Path("{id}")
@GET
- public Event get(@PathParam("id") long id) throws SQLException {
- Event event = Context.getDataManager().getObject(Event.class, id);
+ public Event get(@PathParam("id") long id) throws StorageException {
+ 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/NotificationResource.java b/src/main/java/org/traccar/api/resource/NotificationResource.java
index 9631a52b7..2e4ad12f3 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 - 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,8 +15,18 @@
*/
package org.traccar.api.resource;
-import java.util.Collection;
+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 javax.inject.Inject;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
@@ -25,20 +35,22 @@ 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.traccar.api.ExtendedObjectResource;
-import org.traccar.model.Event;
-import org.traccar.model.Notification;
-import org.traccar.model.Typed;
-import org.traccar.notification.MessageException;
-
+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, InterruptedException, StorageException {
+ User user = permissionsService.getUser(getUserId());
+ for (Typed method : notificatorManager.getAllNotificatorTypes()) {
+ notificatorManager.getNotificator(method.getType()).send(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, InterruptedException, StorageException {
+ User user = permissionsService.getUser(getUserId());
+ notificatorManager.getNotificator(notificator).send(user, new Event("test", 0), null);
return Response.noContent().build();
}
diff --git a/src/main/java/org/traccar/api/resource/PasswordResource.java b/src/main/java/org/traccar/api/resource/PasswordResource.java
index 1868a6191..2d87a8665 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,14 +15,18 @@
*/
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.inject.Inject;
import javax.mail.MessagingException;
import javax.ws.rs.Consumes;
import javax.ws.rs.FormParam;
@@ -31,33 +35,35 @@ 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;
+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 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);
- 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, 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 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();
- }
+ @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 54d3964b6..d35cb98bb 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,12 +16,14 @@
*/
package org.traccar.api.resource;
-import java.sql.SQLException;
-import java.util.Collections;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Set;
+import org.traccar.api.BaseResource;
+import org.traccar.helper.LogAction;
+import org.traccar.model.Permission;
+import org.traccar.model.UserRestrictions;
+import org.traccar.session.cache.CacheManager;
+import org.traccar.storage.StorageException;
+import javax.inject.Inject;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.POST;
@@ -30,33 +32,24 @@ 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 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.getOwnerClass(), getUserId(), permission.getOwnerId());
}
- Context.getPermissionsManager().checkPermission(
- permission.getPropertyClass(), getUserId(), permission.getPropertyId());
}
private void checkPermissionTypes(List<LinkedHashMap<String, Long>> entities) {
@@ -71,49 +64,51 @@ public class PermissionsResource extends BaseResource {
@Path("bulk")
@POST
- public Response add(List<LinkedHashMap<String, Long>> entities) throws SQLException, ClassNotFoundException {
- Context.getPermissionsManager().checkReadonly(getUserId());
+ public Response add(List<LinkedHashMap<String, Long>> entities) throws StorageException, ClassNotFoundException {
+ 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();
}
@POST
- public Response add(LinkedHashMap<String, Long> entity) throws SQLException, ClassNotFoundException {
+ public Response add(LinkedHashMap<String, Long> entity) throws StorageException, ClassNotFoundException {
return add(Collections.singletonList(entity));
}
@DELETE
@Path("bulk")
- public Response remove(List<LinkedHashMap<String, Long>> entities) throws SQLException, ClassNotFoundException {
- Context.getPermissionsManager().checkReadonly(getUserId());
+ public Response remove(List<LinkedHashMap<String, Long>> entities) throws StorageException, ClassNotFoundException {
+ 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();
}
@DELETE
- public Response remove(LinkedHashMap<String, Long> entity) throws SQLException, ClassNotFoundException {
+ public Response remove(LinkedHashMap<String, Long> entity) throws StorageException, ClassNotFoundException {
return remove(Collections.singletonList(entity));
}
diff --git a/src/main/java/org/traccar/api/resource/PositionResource.java b/src/main/java/org/traccar/api/resource/PositionResource.java
index 998d59706..042dd1e23 100644
--- a/src/main/java/org/traccar/api/resource/PositionResource.java
+++ b/src/main/java/org/traccar/api/resource/PositionResource.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2015 - 2020 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,20 +15,32 @@
*/
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.inject.Inject;
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.WebApplicationException;
+import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
-import java.sql.SQLException;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.StreamingOutput;
import java.util.ArrayList;
import java.util.Collection;
-import java.util.Collections;
import java.util.Date;
import java.util.List;
@@ -37,29 +49,95 @@ import java.util.List;
@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 SQLException {
+ 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) {
- 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());
}
}
+ @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 7347bfd64..70177dd4d 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 - 2020 Anton Tananaev (anton@traccar.org)
+ * Copyright 2016 - 2022 Anton Tananaev (anton@traccar.org)
* Copyright 2016 - 2018 Andrey Kunitsyn (andrey@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,41 +16,47 @@
*/
package org.traccar.api.resource;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.sql.SQLException;
-import java.util.Collection;
-import java.util.Date;
-import java.util.List;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.traccar.api.BaseResource;
+import org.traccar.mail.MailManager;
+import org.traccar.helper.LogAction;
+import org.traccar.model.Event;
+import org.traccar.model.Position;
+import org.traccar.model.User;
+import org.traccar.model.UserRestrictions;
+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.model.StopReportItem;
+import org.traccar.reports.model.SummaryReportItem;
+import org.traccar.reports.model.TripReportItem;
+import org.traccar.storage.StorageException;
import javax.activation.DataHandler;
+import javax.inject.Inject;
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.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
+import javax.ws.rs.WebApplicationException;
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.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 javax.ws.rs.core.StreamingOutput;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
@Path("reports")
@Produces(MediaType.APPLICATION_JSON)
@@ -59,155 +65,267 @@ public class ReportResource extends BaseResource {
private static final Logger LOGGER = LoggerFactory.getLogger(ReportResource.class);
- private static final String XLSX = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
- private static final String CONTENT_DISPOSITION_VALUE_XLSX = "attachment; filename=report.xlsx";
+ private static final String EXCEL = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
+
+ @Inject
+ private EventsReportProvider eventsReportProvider;
+
+ @Inject
+ private RouteReportProvider routeReportProvider;
+
+ @Inject
+ private StopsReportProvider stopsReportProvider;
+
+ @Inject
+ private SummaryReportProvider summaryReportProvider;
+
+ @Inject
+ private TripsReportProvider tripsReportProvider;
+
+ @Inject
+ private MailManager mailManager;
private interface ReportExecutor {
- void execute(ByteArrayOutputStream stream) throws SQLException, IOException;
+ void execute(OutputStream stream) throws StorageException, IOException;
}
private Response executeReport(
- long userId, boolean mail, ReportExecutor executor) throws SQLException, IOException {
- final ByteArrayOutputStream stream = new ByteArrayOutputStream();
+ long userId, boolean mail, ReportExecutor executor) {
if (mail) {
new Thread(() -> {
try {
+ var stream = new ByteArrayOutputStream();
executor.execute(stream);
MimeBodyPart attachment = new MimeBodyPart();
-
attachment.setFileName("report.xlsx");
attachment.setDataHandler(new DataHandler(new ByteArrayDataSource(
stream.toByteArray(), "application/octet-stream")));
- Context.getMailManager().sendMessage(
- userId, "Report", "The report is in the attachment.", attachment);
- } catch (SQLException | IOException | MessagingException e) {
+ User user = permissionsService.getUser(userId);
+ mailManager.sendMessage(user, "Report", "The report is in the attachment.", attachment);
+ } catch (StorageException | IOException | MessagingException e) {
LOGGER.warn("Report failed", e);
}
}).start();
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("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 SQLException {
+ @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 SQLException, IOException {
+ @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 SQLException {
+ @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 SQLException, IOException {
+ @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 SQLException {
+ 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 SQLException, IOException {
+ @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 SQLException {
+ 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 SQLException, IOException {
+ @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 SQLException {
+ 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 SQLException, IOException {
+ @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 91488afff..4b7ee9189 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 - 2020 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,12 +15,23 @@
*/
package org.traccar.api.resource;
-import org.traccar.Context;
import org.traccar.api.BaseResource;
+import org.traccar.helper.model.UserUtil;
+import org.traccar.mail.MailManager;
+import org.traccar.geocoder.Geocoder;
+import org.traccar.helper.Log;
import org.traccar.helper.LogAction;
import org.traccar.model.Server;
+import org.traccar.model.User;
+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.annotation.Nullable;
import javax.annotation.security.PermitAll;
+import javax.inject.Inject;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
@@ -29,27 +40,52 @@ 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 java.util.Arrays;
+import java.util.Collection;
+import java.util.TimeZone;
@Path("server")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class ServerResource extends BaseResource {
+ @Inject
+ private CacheManager cacheManager;
+
+ @Inject
+ private MailManager mailManager;
+
+ @Inject
+ @Nullable
+ private Geocoder geocoder;
+
@PermitAll
@GET
- public Server get(@QueryParam("force") boolean force) throws SQLException {
- if (force) {
- return Context.getDataManager().getServer();
+ public Server get() throws StorageException {
+ Server server = storage.getObject(Server.class, new Request(new Columns.All()));
+ server.setEmailEnabled(mailManager.getEmailEnabled());
+ server.setGeocoderEnabled(geocoder != null);
+ User user = permissionsService.getUser(getUserId());
+ if (user != null) {
+ if (user.getAdministrator()) {
+ server.setStorageSpace(Log.getStorageSpace());
+ }
} else {
- return Context.getPermissionsManager().getServer();
+ server.setNewServer(UserUtil.isEmpty(storage));
+ }
+ if (user != null && user.getAdministrator()) {
+ server.setStorageSpace(Log.getStorageSpace());
}
+ return server;
}
@PUT
- public Response update(Server entity) throws SQLException {
- Context.getPermissionsManager().checkAdmin(getUserId());
- Context.getPermissionsManager().updateServer(entity);
+ public Response update(Server entity) throws StorageException {
+ 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();
}
@@ -57,11 +93,17 @@ 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());
+ }
+
}
diff --git a/src/main/java/org/traccar/api/resource/SessionResource.java b/src/main/java/org/traccar/api/resource/SessionResource.java
index e3c5d457f..7025d5fa7 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 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,14 +15,20 @@
*/
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.helper.DataConverter;
-import org.traccar.helper.ServletHelper;
import org.traccar.helper.LogAction;
+import org.traccar.helper.ServletHelper;
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.inject.Inject;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.Consumes;
@@ -31,16 +37,18 @@ import javax.ws.rs.FormParam;
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.QueryParam;
import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
-
-import java.io.UnsupportedEncodingException;
+import java.io.IOException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
-import java.sql.SQLException;
+import java.security.GeneralSecurityException;
+import java.util.Date;
@Path("session")
@Produces(MediaType.APPLICATION_JSON)
@@ -51,60 +59,86 @@ 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
+ private TokenManager tokenManager;
+
+ @Context
private HttpServletRequest request;
@PermitAll
@GET
- public User get(@QueryParam("token") String token) throws SQLException, UnsupportedEncodingException {
+ public User get(@QueryParam("token") String token) throws StorageException, IOException, GeneralSecurityException {
+
+ if (token != null) {
+ User user = loginService.login(token);
+ if (user != null) {
+ request.getSession().setAttribute(USER_ID_KEY, user.getId());
+ LogAction.login(user.getId(), ServletHelper.retrieveRemoteAddress(request));
+ return user;
+ }
+ }
+
Long userId = (Long) request.getSession().getAttribute(USER_ID_KEY);
if (userId == null) {
+
Cookie[] cookies = request.getCookies();
String email = null, password = null;
if (cookies != null) {
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);
- if (user != null) {
- userId = user.getId();
- request.getSession().setAttribute(USER_ID_KEY, userId);
- }
- } else if (token != null) {
- User user = Context.getUsersManager().getUserByToken(token);
+ User user = loginService.login(email, password);
if (user != null) {
- userId = user.getId();
- request.getSession().setAttribute(USER_ID_KEY, userId);
+ request.getSession().setAttribute(USER_ID_KEY, user.getId());
+ LogAction.login(user.getId(), ServletHelper.retrieveRemoteAddress(request));
+ return user;
}
}
- }
- if (userId != null) {
- Context.getPermissionsManager().checkUserEnabled(userId);
- return Context.getPermissionsManager().getUser(userId);
} else {
- throw new WebApplicationException(Response.status(Response.Status.NOT_FOUND).build());
+
+ 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.checkAdmin(getUserId());
+ 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(), ServletHelper.retrieveRemoteAddress(request));
+ return user;
}
@PermitAll
@POST
public User add(
- @FormParam("email") String email, @FormParam("password") String password) throws SQLException {
- User user = Context.getPermissionsManager().login(email, password);
+ @FormParam("email") String email, @FormParam("password") String password) throws StorageException {
+ 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(), ServletHelper.retrieveRemoteAddress(request));
return user;
} else {
LogAction.failedLogin(ServletHelper.retrieveRemoteAddress(request));
@@ -114,9 +148,16 @@ public class SessionResource extends BaseResource {
@DELETE
public Response remove() {
- LogAction.logout(getUserId());
+ LogAction.logout(getUserId(), ServletHelper.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);
+ }
+
}
diff --git a/src/main/java/org/traccar/api/resource/StatisticsResource.java b/src/main/java/org/traccar/api/resource/StatisticsResource.java
index 58073e7d1..1f2296f28 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,9 +15,13 @@
*/
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;
@@ -25,7 +29,6 @@ import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
-import java.sql.SQLException;
import java.util.Collection;
import java.util.Date;
@@ -36,9 +39,12 @@ public class StatisticsResource extends BaseResource {
@GET
public Collection<Statistics> get(
- @QueryParam("from") Date from, @QueryParam("to") Date to) throws SQLException {
- Context.getPermissionsManager().checkAdmin(getUserId());
- return Context.getDataManager().getStatistics(from, to);
+ @QueryParam("from") Date from, @QueryParam("to") Date to) throws StorageException {
+ 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 d54cc2382..e41ebbe61 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,15 +15,21 @@
*/
package org.traccar.api.resource;
-import org.traccar.Context;
import org.traccar.api.BaseObjectResource;
+import org.traccar.config.Config;
import org.traccar.config.Keys;
-import org.traccar.database.UsersManager;
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.inject.Inject;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
@@ -32,63 +38,82 @@ 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 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 SQLException {
- if (!Context.getPermissionsManager().getUserAdmin(getUserId())) {
- Context.getPermissionsManager().checkUserUpdate(getUserId(), new User(), entity);
- if (Context.getPermissionsManager().getUserManager(getUserId())) {
- Context.getPermissionsManager().checkUserLimit(getUserId());
+ public Response add(User entity) throws StorageException {
+ 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 (!permissionsService.getServer().getRegistration()) {
+ throw new SecurityException("Registration disabled");
+ }
+ entity.setDeviceLimit(config.getInteger(Keys.USERS_DEFAULT_DEVICE_LIMIT));
+ int expirationDays = config.getInteger(Keys.USERS_DEFAULT_EXPIRATION_DAYS);
if (expirationDays > 0) {
- entity.setExpirationTime(
- new Date(System.currentTimeMillis() + (long) expirationDays * 24 * 3600 * 1000));
+ entity.setExpirationTime(new Date(System.currentTimeMillis() + expirationDays * 86400000L));
}
}
}
- 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();
}
diff --git a/src/main/java/org/traccar/api/security/LoginService.java b/src/main/java/org/traccar/api/security/LoginService.java
new file mode 100644
index 000000000..88bafcfb5
--- /dev/null
+++ b/src/main/java/org/traccar/api/security/LoginService.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 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.
+ * 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.security;
+
+import org.traccar.api.signature.TokenManager;
+import org.traccar.config.Config;
+import org.traccar.config.Keys;
+import org.traccar.database.LdapProvider;
+import org.traccar.model.User;
+import org.traccar.storage.Storage;
+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.Nullable;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+
+@Singleton
+public class LoginService {
+
+ private final Storage storage;
+ private final TokenManager tokenManager;
+ private final LdapProvider ldapProvider;
+
+ private final String serviceAccountToken;
+ private final boolean forceLdap;
+
+ @Inject
+ public LoginService(
+ Config config, Storage storage, TokenManager tokenManager, @Nullable LdapProvider ldapProvider) {
+ this.storage = storage;
+ this.tokenManager = tokenManager;
+ this.ldapProvider = ldapProvider;
+ serviceAccountToken = config.getString(Keys.WEB_SERVICE_ACCOUNT_TOKEN);
+ forceLdap = config.getBoolean(Keys.LDAP_FORCE);
+ }
+
+ public User login(String token) throws StorageException, GeneralSecurityException, IOException {
+ if (serviceAccountToken != null && serviceAccountToken.equals(token)) {
+ return new ServiceAccountUser();
+ }
+ long userId = tokenManager.verifyToken(token);
+ User user = storage.getObject(User.class, new Request(
+ new Columns.All(), new Condition.Equals("id", userId)));
+ if (user != null) {
+ checkUserEnabled(user);
+ }
+ return user;
+ }
+
+ public User login(String email, String password) throws StorageException {
+ email = email.trim();
+ User user = storage.getObject(User.class, new Request(
+ new Columns.All(),
+ new Condition.Or(
+ new Condition.Equals("email", email),
+ new Condition.Equals("login", email))));
+ if (user != null) {
+ if (ldapProvider != null && user.getLogin() != null && ldapProvider.login(user.getLogin(), password)
+ || !forceLdap && user.isPasswordValid(password)) {
+ checkUserEnabled(user);
+ return user;
+ }
+ } else {
+ if (ldapProvider != null && ldapProvider.login(email, password)) {
+ user = ldapProvider.getUser(email);
+ user.setId(storage.addObject(user, new Request(new Columns.Exclude("id"))));
+ checkUserEnabled(user);
+ return user;
+ }
+ }
+ return null;
+ }
+
+ private void checkUserEnabled(User user) throws SecurityException {
+ if (user == null) {
+ throw new SecurityException("Unknown account");
+ }
+ user.checkDisabled();
+ }
+
+}
diff --git a/src/main/java/org/traccar/api/security/PermissionsService.java b/src/main/java/org/traccar/api/security/PermissionsService.java
new file mode 100644
index 000000000..4421572d7
--- /dev/null
+++ b/src/main/java/org/traccar/api/security/PermissionsService.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright 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.
+ * 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.security;
+
+import com.google.inject.servlet.RequestScoped;
+import org.traccar.model.BaseModel;
+import org.traccar.model.Calendar;
+import org.traccar.model.Command;
+import org.traccar.model.Device;
+import org.traccar.model.Group;
+import org.traccar.model.GroupedModel;
+import org.traccar.model.ManagedUser;
+import org.traccar.model.ScheduledModel;
+import org.traccar.model.Server;
+import org.traccar.model.User;
+import org.traccar.model.UserRestrictions;
+import org.traccar.storage.Storage;
+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.inject.Inject;
+import java.util.Objects;
+
+@RequestScoped
+public class PermissionsService {
+
+ private final Storage storage;
+
+ private Server server;
+ private User user;
+
+ @Inject
+ public PermissionsService(Storage storage) {
+ this.storage = storage;
+ }
+
+ public Server getServer() throws StorageException {
+ if (server == null) {
+ server = storage.getObject(
+ Server.class, new Request(new Columns.All()));
+ }
+ return server;
+ }
+
+ public User getUser(long userId) throws StorageException {
+ if (user == null && userId > 0) {
+ if (userId == ServiceAccountUser.ID) {
+ user = new ServiceAccountUser();
+ } else {
+ user = storage.getObject(
+ User.class, new Request(new Columns.All(), new Condition.Equals("id", userId)));
+ }
+ }
+ return user;
+ }
+
+ public boolean notAdmin(long userId) throws StorageException {
+ return !getUser(userId).getAdministrator();
+ }
+
+ public void checkAdmin(long userId) throws StorageException, SecurityException {
+ if (!getUser(userId).getAdministrator()) {
+ throw new SecurityException("Administrator access required");
+ }
+ }
+
+ public void checkManager(long userId) throws StorageException, SecurityException {
+ if (!getUser(userId).getAdministrator() && getUser(userId).getUserLimit() == 0) {
+ throw new SecurityException("Manager access required");
+ }
+ }
+
+ public interface CheckRestrictionCallback {
+ boolean denied(UserRestrictions userRestrictions);
+ }
+
+ public void checkRestriction(
+ long userId, CheckRestrictionCallback callback) throws StorageException, SecurityException {
+ if (!getUser(userId).getAdministrator()
+ && (callback.denied(getServer()) || callback.denied(getUser(userId)))) {
+ throw new SecurityException("Operation restricted");
+ }
+ }
+
+ public void checkEdit(long userId, Class<?> clazz, boolean addition) throws StorageException, SecurityException {
+ if (!getUser(userId).getAdministrator()) {
+ boolean denied = false;
+ if (getServer().getReadonly() || getUser(userId).getReadonly()) {
+ denied = true;
+ } else if (clazz.equals(Device.class)) {
+ denied = getServer().getDeviceReadonly() || getUser(userId).getDeviceReadonly()
+ || addition && getUser(userId).getDeviceLimit() == 0;
+ if (!denied && addition && getUser(userId).getDeviceLimit() > 0) {
+ int deviceCount = storage.getObjects(Device.class, new Request(
+ new Columns.Include("id"),
+ new Condition.Permission(User.class, userId, Device.class))).size();
+ denied = deviceCount >= getUser(userId).getDeviceLimit();
+ }
+ } else if (clazz.equals(Command.class)) {
+ denied = getServer().getLimitCommands() || getUser(userId).getLimitCommands();
+ }
+ if (denied) {
+ throw new SecurityException("Write access denied");
+ }
+ }
+ }
+
+ public void checkEdit(long userId, BaseModel object, boolean addition) throws StorageException, SecurityException {
+ if (!getUser(userId).getAdministrator()) {
+ checkEdit(userId, object.getClass(), addition);
+ if (object instanceof GroupedModel) {
+ GroupedModel after = ((GroupedModel) object);
+ if (after.getGroupId() > 0) {
+ GroupedModel before = null;
+ if (!addition) {
+ before = storage.getObject(after.getClass(), new Request(
+ new Columns.Include("groupId"), new Condition.Equals("id", object.getId())));
+ }
+ if (before == null || before.getGroupId() != after.getGroupId()) {
+ checkPermission(Group.class, userId, after.getGroupId());
+ }
+ }
+ }
+ if (object instanceof ScheduledModel) {
+ ScheduledModel after = ((ScheduledModel) object);
+ if (after.getCalendarId() > 0) {
+ ScheduledModel before = null;
+ if (!addition) {
+ before = storage.getObject(after.getClass(), new Request(
+ new Columns.Include("calendarId"), new Condition.Equals("id", object.getId())));
+ }
+ if (before == null || before.getCalendarId() != after.getCalendarId()) {
+ checkPermission(Calendar.class, userId, after.getCalendarId());
+ }
+ }
+ }
+ }
+ }
+
+ public void checkUser(long userId, long managedUserId) throws StorageException, SecurityException {
+ if (userId != managedUserId && !getUser(userId).getAdministrator()) {
+ if (!getUser(userId).getManager()
+ || storage.getPermissions(User.class, userId, ManagedUser.class, managedUserId).isEmpty()) {
+ throw new SecurityException("User access denied");
+ }
+ }
+ }
+
+ public void checkUserUpdate(long userId, User before, User after) throws StorageException, SecurityException {
+ if (before.getAdministrator() != after.getAdministrator()
+ || before.getDeviceLimit() != after.getDeviceLimit()
+ || before.getUserLimit() != after.getUserLimit()) {
+ checkAdmin(userId);
+ }
+ User user = getUser(userId);
+ if (user != null && user.getExpirationTime() != null
+ && !Objects.equals(before.getExpirationTime(), after.getExpirationTime())
+ && (after.getExpirationTime() == null
+ || user.getExpirationTime().compareTo(after.getExpirationTime()) < 0)) {
+ checkAdmin(userId);
+ }
+ if (before.getReadonly() != after.getReadonly()
+ || before.getDeviceReadonly() != after.getDeviceReadonly()
+ || before.getDisabled() != after.getDisabled()
+ || before.getLimitCommands() != after.getLimitCommands()
+ || before.getDisableReports() != after.getDisableReports()
+ || before.getFixedEmail() != after.getFixedEmail()) {
+ if (userId == after.getId()) {
+ checkAdmin(userId);
+ } else if (after.getId() > 0) {
+ checkUser(userId, after.getId());
+ } else {
+ checkManager(userId);
+ }
+ }
+ if (before.getFixedEmail() && !before.getEmail().equals(after.getEmail())) {
+ checkAdmin(userId);
+ }
+ }
+
+ public <T extends BaseModel> void checkPermission(
+ Class<T> clazz, long userId, long objectId) throws StorageException, SecurityException {
+ if (!getUser(userId).getAdministrator() && !(clazz.equals(User.class) && userId == objectId)) {
+ var object = storage.getObject(clazz, new Request(
+ new Columns.Include("id"),
+ new Condition.And(
+ new Condition.Equals("id", objectId),
+ new Condition.Permission(
+ User.class, userId, clazz.equals(User.class) ? ManagedUser.class : clazz))));
+ if (object == null) {
+ throw new SecurityException(clazz.getSimpleName() + " access denied");
+ }
+ }
+ }
+
+}
diff --git a/src/main/java/org/traccar/api/SecurityRequestFilter.java b/src/main/java/org/traccar/api/security/SecurityRequestFilter.java
index 33b6b37df..94b6bbf05 100644
--- a/src/main/java/org/traccar/api/SecurityRequestFilter.java
+++ b/src/main/java/org/traccar/api/security/SecurityRequestFilter.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2015 - 2016 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.
@@ -13,28 +13,30 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.traccar.api;
+package org.traccar.api.security;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.traccar.Context;
-import org.traccar.Main;
import org.traccar.api.resource.SessionResource;
import org.traccar.database.StatisticsManager;
import org.traccar.helper.DataConverter;
import org.traccar.model.User;
+import org.traccar.storage.StorageException;
import javax.annotation.security.PermitAll;
+import javax.inject.Inject;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.container.ResourceInfo;
+import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.SecurityContext;
+import java.io.IOException;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
-import java.sql.SQLException;
+import java.security.GeneralSecurityException;
public class SecurityRequestFilter implements ContainerRequestFilter {
@@ -43,6 +45,7 @@ public class SecurityRequestFilter implements ContainerRequestFilter {
public static final String AUTHORIZATION_HEADER = "Authorization";
public static final String WWW_AUTHENTICATE = "WWW-Authenticate";
public static final String BASIC_REALM = "Basic realm=\"api\"";
+ public static final String BEARER_PREFIX = "Bearer ";
public static final String X_REQUESTED_WITH = "X-Requested-With";
public static final String XML_HTTP_REQUEST = "XMLHttpRequest";
@@ -55,12 +58,18 @@ public class SecurityRequestFilter implements ContainerRequestFilter {
return null;
}
- @javax.ws.rs.core.Context
+ @Context
private HttpServletRequest request;
- @javax.ws.rs.core.Context
+ @Context
private ResourceInfo resourceInfo;
+ @Inject
+ private LoginService loginService;
+
+ @Inject
+ private StatisticsManager statisticsManager;
+
@Override
public void filter(ContainerRequestContext requestContext) {
@@ -76,13 +85,18 @@ public class SecurityRequestFilter implements ContainerRequestFilter {
if (authHeader != null) {
try {
- String[] auth = decodeBasicAuth(authHeader);
- User user = Context.getPermissionsManager().login(auth[0], auth[1]);
+ User user;
+ if (authHeader.startsWith(BEARER_PREFIX)) {
+ user = loginService.login(authHeader.substring(BEARER_PREFIX.length()));
+ } else {
+ String[] auth = decodeBasicAuth(authHeader);
+ user = loginService.login(auth[0], auth[1]);
+ }
if (user != null) {
- Main.getInjector().getInstance(StatisticsManager.class).registerRequest(user.getId());
+ statisticsManager.registerRequest(user.getId());
securityContext = new UserSecurityContext(new UserPrincipal(user.getId()));
}
- } catch (SQLException e) {
+ } catch (StorageException | GeneralSecurityException | IOException e) {
throw new WebApplicationException(e);
}
@@ -90,8 +104,7 @@ public class SecurityRequestFilter implements ContainerRequestFilter {
Long userId = (Long) request.getSession().getAttribute(SessionResource.USER_ID_KEY);
if (userId != null) {
- Context.getPermissionsManager().checkUserEnabled(userId);
- Main.getInjector().getInstance(StatisticsManager.class).registerRequest(userId);
+ statisticsManager.registerRequest(userId);
securityContext = new UserSecurityContext(new UserPrincipal(userId));
}
diff --git a/src/main/java/org/traccar/api/ObjectMapperProvider.java b/src/main/java/org/traccar/api/security/ServiceAccountUser.java
index f81b20917..644142434 100644
--- a/src/main/java/org/traccar/api/ObjectMapperProvider.java
+++ b/src/main/java/org/traccar/api/security/ServiceAccountUser.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2015 - 2016 Anton Tananaev (anton@traccar.org)
+ * Copyright 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.
@@ -13,20 +13,18 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.traccar.api;
+package org.traccar.api.security;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import org.traccar.Context;
+import org.traccar.model.User;
-import javax.ws.rs.ext.ContextResolver;
-import javax.ws.rs.ext.Provider;
+public class ServiceAccountUser extends User {
-@Provider
-public class ObjectMapperProvider implements ContextResolver<ObjectMapper> {
+ public static final long ID = 9000000000000000000L;
- @Override
- public ObjectMapper getContext(Class<?> type) {
- return Context.getObjectMapper();
+ public ServiceAccountUser() {
+ setId(ID);
+ setName("Service Account");
+ setEmail("none");
+ setAdministrator(true);
}
-
}
diff --git a/src/main/java/org/traccar/api/UserPrincipal.java b/src/main/java/org/traccar/api/security/UserPrincipal.java
index 175bca391..18b84a0e1 100644
--- a/src/main/java/org/traccar/api/UserPrincipal.java
+++ b/src/main/java/org/traccar/api/security/UserPrincipal.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.traccar.api;
+package org.traccar.api.security;
import java.security.Principal;
diff --git a/src/main/java/org/traccar/api/UserSecurityContext.java b/src/main/java/org/traccar/api/security/UserSecurityContext.java
index 55c0621bc..97df6b6c7 100644
--- a/src/main/java/org/traccar/api/UserSecurityContext.java
+++ b/src/main/java/org/traccar/api/security/UserSecurityContext.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2015 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.
@@ -13,14 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.traccar.api;
+package org.traccar.api.security;
import javax.ws.rs.core.SecurityContext;
import java.security.Principal;
public class UserSecurityContext implements SecurityContext {
- private UserPrincipal principal;
+ private final UserPrincipal principal;
public UserSecurityContext(UserPrincipal principal) {
this.principal = principal;
diff --git a/src/main/java/org/traccar/api/signature/CryptoManager.java b/src/main/java/org/traccar/api/signature/CryptoManager.java
new file mode 100644
index 000000000..249d5bd97
--- /dev/null
+++ b/src/main/java/org/traccar/api/signature/CryptoManager.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 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.
+ * 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.signature;
+
+import org.traccar.storage.Storage;
+import org.traccar.storage.StorageException;
+import org.traccar.storage.query.Columns;
+import org.traccar.storage.query.Request;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import java.security.GeneralSecurityException;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.Signature;
+import java.security.spec.ECGenParameterSpec;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+
+@Singleton
+public class CryptoManager {
+
+ private final Storage storage;
+
+ private PublicKey publicKey;
+ private PrivateKey privateKey;
+
+ @Inject
+ public CryptoManager(Storage storage) {
+ this.storage = storage;
+ }
+
+ public byte[] sign(byte[] data) throws GeneralSecurityException, StorageException {
+ if (privateKey == null) {
+ initializeKeys();
+ }
+ Signature signature = Signature.getInstance("SHA256withECDSA");
+ signature.initSign(privateKey);
+ signature.update(data);
+ byte[] block = signature.sign();
+ byte[] combined = new byte[1 + block.length + data.length];
+ combined[0] = (byte) block.length;
+ System.arraycopy(block, 0, combined, 1, block.length);
+ System.arraycopy(data, 0, combined, 1 + block.length, data.length);
+ return combined;
+ }
+
+ public byte[] verify(byte[] data) throws GeneralSecurityException, StorageException {
+ if (publicKey == null) {
+ initializeKeys();
+ }
+ Signature signature = Signature.getInstance("SHA256withECDSA");
+ signature.initVerify(publicKey);
+ int length = data[0];
+ byte[] originalData = new byte[data.length - 1 - length];
+ System.arraycopy(data, 1 + length, originalData, 0, originalData.length);
+ signature.update(originalData);
+ if (!signature.verify(data, 1, length)) {
+ throw new SecurityException("Invalid signature");
+ }
+ return originalData;
+ }
+
+ private void initializeKeys() throws StorageException, GeneralSecurityException {
+ KeystoreModel model = storage.getObject(KeystoreModel.class, new Request(new Columns.All()));
+ if (model != null) {
+ publicKey = KeyFactory.getInstance("EC")
+ .generatePublic(new X509EncodedKeySpec(model.getPublicKey()));
+ privateKey = KeyFactory.getInstance("EC")
+ .generatePrivate(new PKCS8EncodedKeySpec(model.getPrivateKey()));
+ } else {
+ KeyPairGenerator generator = KeyPairGenerator.getInstance("EC");
+ generator.initialize(new ECGenParameterSpec("secp256r1"), new SecureRandom());
+ KeyPair pair = generator.generateKeyPair();
+
+ publicKey = pair.getPublic();
+ privateKey = pair.getPrivate();
+
+ model = new KeystoreModel();
+ model.setPublicKey(publicKey.getEncoded());
+ model.setPrivateKey(privateKey.getEncoded());
+ storage.addObject(model, new Request(new Columns.Exclude("id")));
+ }
+ }
+
+}
diff --git a/src/main/java/org/traccar/api/signature/KeystoreModel.java b/src/main/java/org/traccar/api/signature/KeystoreModel.java
new file mode 100644
index 000000000..7f3140e81
--- /dev/null
+++ b/src/main/java/org/traccar/api/signature/KeystoreModel.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 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.
+ * 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.signature;
+
+import org.traccar.model.BaseModel;
+import org.traccar.storage.StorageName;
+
+@StorageName("tc_keystore")
+public class KeystoreModel extends BaseModel {
+
+ private byte[] publicKey;
+
+ public byte[] getPublicKey() {
+ return publicKey;
+ }
+
+ public void setPublicKey(byte[] publicKey) {
+ this.publicKey = publicKey;
+ }
+
+ private byte[] privateKey;
+
+ public byte[] getPrivateKey() {
+ return privateKey;
+ }
+
+ public void setPrivateKey(byte[] privateKey) {
+ this.privateKey = privateKey;
+ }
+
+}
diff --git a/src/main/java/org/traccar/api/signature/TokenManager.java b/src/main/java/org/traccar/api/signature/TokenManager.java
new file mode 100644
index 000000000..6a0d90b40
--- /dev/null
+++ b/src/main/java/org/traccar/api/signature/TokenManager.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 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.
+ * 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.signature;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.commons.codec.binary.Base64;
+import org.traccar.storage.StorageException;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.util.Date;
+import java.util.concurrent.TimeUnit;
+
+@Singleton
+public class TokenManager {
+
+ private static final int DEFAULT_EXPIRATION_DAYS = 7;
+
+ private final ObjectMapper objectMapper;
+ private final CryptoManager cryptoManager;
+
+ public static class Data {
+ @JsonProperty("u")
+ private long userId;
+ @JsonProperty("e")
+ private Date expiration;
+ }
+
+ @Inject
+ public TokenManager(ObjectMapper objectMapper, CryptoManager cryptoManager) {
+ this.objectMapper = objectMapper;
+ this.cryptoManager = cryptoManager;
+ }
+
+ public String generateToken(long userId) throws IOException, GeneralSecurityException, StorageException {
+ return generateToken(userId, null);
+ }
+
+ public String generateToken(
+ long userId, Date expiration) throws IOException, GeneralSecurityException, StorageException {
+ Data data = new Data();
+ data.userId = userId;
+ if (expiration != null) {
+ data.expiration = expiration;
+ } else {
+ data.expiration = new Date(System.currentTimeMillis() + TimeUnit.DAYS.toMillis(DEFAULT_EXPIRATION_DAYS));
+ }
+ byte[] encoded = objectMapper.writeValueAsBytes(data);
+ return Base64.encodeBase64URLSafeString(cryptoManager.sign(encoded));
+ }
+
+ public long verifyToken(String token) throws IOException, GeneralSecurityException, StorageException {
+ byte[] encoded = cryptoManager.verify(Base64.decodeBase64(token));
+ Data data = objectMapper.readValue(encoded, Data.class);
+ if (data.expiration.before(new Date())) {
+ throw new SecurityException("Token has expired");
+ }
+ return data.userId;
+ }
+
+}