aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAnton Tananaev <anton@traccar.org>2022-05-22 10:55:48 -0700
committerAnton Tananaev <anton@traccar.org>2022-05-22 10:55:48 -0700
commit9f8a78e5b7afddc6ccb0a54f4b3e59e1395de8eb (patch)
tree26356ca17e21d626843671aac23bf775c6451359
parentd9f51795058399e85ae83a8e308a0c2bc1d13e4b (diff)
parent9c9f5d66147cfa428ea18dd29103b9c82e529dca (diff)
downloadtrackermap-server-9f8a78e5b7afddc6ccb0a54f4b3e59e1395de8eb.tar.gz
trackermap-server-9f8a78e5b7afddc6ccb0a54f4b3e59e1395de8eb.tar.bz2
trackermap-server-9f8a78e5b7afddc6ccb0a54f4b3e59e1395de8eb.zip
Merge branch 'storage'
-rw-r--r--debug.xml1
-rw-r--r--gradle/checkstyle.xml4
-rw-r--r--src/main/java/org/traccar/MainModule.java5
-rw-r--r--src/main/java/org/traccar/api/BaseObjectResource.java105
-rw-r--r--src/main/java/org/traccar/api/BaseResource.java12
-rw-r--r--src/main/java/org/traccar/api/ExtendedObjectResource.java55
-rw-r--r--src/main/java/org/traccar/api/MediaFilter.java6
-rw-r--r--src/main/java/org/traccar/api/SimpleObjectResource.java36
-rw-r--r--src/main/java/org/traccar/api/resource/AttributeResource.java68
-rw-r--r--src/main/java/org/traccar/api/resource/DeviceResource.java4
-rw-r--r--src/main/java/org/traccar/api/resource/EventResource.java18
-rw-r--r--src/main/java/org/traccar/api/resource/NotificationResource.java3
-rw-r--r--src/main/java/org/traccar/api/resource/PositionResource.java2
-rw-r--r--src/main/java/org/traccar/api/resource/ReportResource.java20
-rw-r--r--src/main/java/org/traccar/api/security/PermissionsService.java146
-rw-r--r--src/main/java/org/traccar/config/Keys.java7
-rw-r--r--src/main/java/org/traccar/database/DeviceManager.java3
-rw-r--r--src/main/java/org/traccar/database/PermissionsManager.java22
-rw-r--r--src/main/java/org/traccar/model/Permission.java2
-rw-r--r--src/main/java/org/traccar/model/User.java6
-rw-r--r--src/main/java/org/traccar/storage/DatabaseStorage.java151
-rw-r--r--src/main/java/org/traccar/storage/MemoryStorage.java20
-rw-r--r--src/main/java/org/traccar/storage/QueryBuilder.java20
-rw-r--r--src/main/java/org/traccar/storage/Storage.java32
-rw-r--r--src/main/java/org/traccar/storage/StorageException.java15
-rw-r--r--src/main/java/org/traccar/storage/StorageName.java15
-rw-r--r--src/main/java/org/traccar/storage/query/Columns.java15
-rw-r--r--src/main/java/org/traccar/storage/query/Condition.java82
-rw-r--r--src/main/java/org/traccar/storage/query/Limit.java15
-rw-r--r--src/main/java/org/traccar/storage/query/Order.java15
-rw-r--r--src/main/java/org/traccar/storage/query/Request.java15
31 files changed, 686 insertions, 234 deletions
diff --git a/debug.xml b/debug.xml
index 29ece6260..58335eced 100644
--- a/debug.xml
+++ b/debug.xml
@@ -11,6 +11,7 @@
<entry key='web.console'>true</entry>
<entry key='logger.console'>true</entry>
+ <entry key='logger.queries'>true</entry>
<entry key='database.driver'>org.h2.Driver</entry>
<entry key='database.url'>jdbc:h2:./target/database</entry>
diff --git a/gradle/checkstyle.xml b/gradle/checkstyle.xml
index 9d30e53d6..6cff6ffa5 100644
--- a/gradle/checkstyle.xml
+++ b/gradle/checkstyle.xml
@@ -122,7 +122,9 @@
<module name="FinalClass"/>
<module name="HideUtilityClassConstructor"/>
<module name="InterfaceIsType"/>
- <module name="VisibilityModifier"/>
+ <module name="VisibilityModifier">
+ <property name="protectedAllowed" value="true"/>
+ </module>
<!-- Miscellaneous other checks. -->
<!-- See http://checkstyle.sf.net/config_misc.html -->
diff --git a/src/main/java/org/traccar/MainModule.java b/src/main/java/org/traccar/MainModule.java
index 842f7e3ce..79cfcc0a8 100644
--- a/src/main/java/org/traccar/MainModule.java
+++ b/src/main/java/org/traccar/MainModule.java
@@ -444,9 +444,4 @@ public class MainModule extends AbstractModule {
return GlobalTimer.getTimer();
}
- @Override
- protected void configure() {
- binder().requireExplicitBindings();
- }
-
}
diff --git a/src/main/java/org/traccar/api/BaseObjectResource.java b/src/main/java/org/traccar/api/BaseObjectResource.java
index 22756f62a..07c74449c 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,70 +16,44 @@
*/
package org.traccar.api;
-import java.sql.SQLException;
-import java.util.Set;
-
-import javax.ws.rs.DELETE;
-import javax.ws.rs.GET;
-import javax.ws.rs.POST;
-import javax.ws.rs.PUT;
-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.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.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.core.Response;
public abstract class BaseObjectResource<T extends BaseModel> extends BaseResource {
- private final Class<T> baseClass;
+ protected final Class<T> baseClass;
public BaseObjectResource(Class<T> baseClass) {
this.baseClass = baseClass;
}
- protected final Class<T> getBaseClass() {
- return 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;
- }
-
@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", id)));
if (entity != null) {
return Response.ok(entity).build();
} else {
@@ -89,25 +63,13 @@ public abstract class BaseObjectResource<T extends BaseModel> extends BaseResour
@POST
public Response add(T entity) throws StorageException {
- 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());
- }
+ permissionsService.checkEdit(getUserId(), entity, true);
BaseObjectManager<T> manager = Context.getManager(baseClass);
manager.addItem(entity);
LogAction.create(getUserId(), entity);
- Context.getDataManager().linkObject(User.class, getUserId(), baseClass, entity.getId(), true);
+ storage.addPermission(new Permission(User.class, getUserId(), baseClass, entity.getId()));
LogAction.link(getUserId(), User.class, getUserId(), baseClass, entity.getId());
if (manager instanceof SimpleObjectManager) {
@@ -122,22 +84,8 @@ public abstract class BaseObjectResource<T extends BaseModel> extends BaseResour
@Path("{id}")
@PUT
public Response update(T entity) throws StorageException {
- 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());
- }
- Context.getPermissionsManager().checkPermission(baseClass, getUserId(), entity.getId());
+ permissionsService.checkEdit(getUserId(), entity, false);
+ permissionsService.checkPermission(baseClass, getUserId(), entity.getId());
Context.getManager(baseClass).updateItem(entity);
LogAction.edit(getUserId(), entity);
@@ -152,13 +100,8 @@ public abstract class BaseObjectResource<T extends BaseModel> extends BaseResour
@Path("{id}")
@DELETE
public Response remove(@PathParam("id") long id) throws StorageException {
- 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);
+ permissionsService.checkEdit(getUserId(), baseClass, false);
+ permissionsService.checkPermission(baseClass, getUserId(), id);
BaseObjectManager<T> manager = Context.getManager(baseClass);
manager.removeItem(id);
diff --git a/src/main/java/org/traccar/api/BaseResource.java b/src/main/java/org/traccar/api/BaseResource.java
index 6dff8c8c3..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,8 +15,11 @@
*/
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;
@@ -25,6 +28,12 @@ public class BaseResource {
@Context
private SecurityContext securityContext;
+ @Inject
+ protected Storage storage;
+
+ @Inject
+ protected PermissionsService permissionsService;
+
protected long getUserId() {
UserPrincipal principal = (UserPrincipal) securityContext.getUserPrincipal();
if (principal != null) {
@@ -32,4 +41,5 @@ public class BaseResource {
}
return 0;
}
+
}
diff --git a/src/main/java/org/traccar/api/ExtendedObjectResource.java b/src/main/java/org/traccar/api/ExtendedObjectResource.java
index 9e554217e..40d679ded 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,32 @@ 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) {
+ permissionsService.checkAdmin(getUserId());
+ } else {
+ if (userId == 0) {
+ userId = getUserId();
+ } else {
+ permissionsService.checkUser(getUserId(), userId);
+ }
+ conditions.add(new Condition.Permission(User.class, userId, baseClass));
}
- 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));
+ }
+ if (deviceId > 0) {
+ permissionsService.checkPermission(Device.class, getUserId(), deviceId);
+ conditions.add(new Condition.Permission(Device.class, deviceId, baseClass));
}
- 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/MediaFilter.java b/src/main/java/org/traccar/api/MediaFilter.java
index 77731a810..0433147f8 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");
@@ -17,7 +17,6 @@
package org.traccar.api;
import java.io.IOException;
-import java.sql.SQLException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
@@ -76,9 +75,6 @@ public class MediaFilter implements Filter {
} catch (SecurityException 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..c61101077 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,22 @@ 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) {
+ permissionsService.checkAdmin(getUserId());
+ } 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 d2dc28903..478b7acfd 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");
@@ -30,9 +30,13 @@ 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)
@@ -45,51 +49,61 @@ public class AttributeResource extends ExtendedObjectResource<Attribute> {
@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);
+
+ Device device = storage.getObject(Device.class, new Request(
+ new Columns.All(),
+ new Condition.Equals("id", "id", deviceId)));
+ if (device == null) {
+ throw new IllegalArgumentException("Device not found");
+ }
+
+ Position last = storage.getObject(Position.class, new Request(
+ new Columns.All(),
+ new Condition.Equals("id", "id", device.getPositionId())));
+ if (last == null) {
+ throw new IllegalArgumentException("Device has no last position");
+ }
+
+ 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 {
- throw new IllegalArgumentException("Device has no last position");
+ return Response.noContent().build();
}
}
@POST
public Response add(Attribute entity) throws StorageException {
- Context.getPermissionsManager().checkAdmin(getUserId());
+ permissionsService.checkAdmin(getUserId());
return super.add(entity);
}
@Path("{id}")
@PUT
public Response update(Attribute entity) throws StorageException {
- Context.getPermissionsManager().checkAdmin(getUserId());
+ permissionsService.checkAdmin(getUserId());
return super.update(entity);
}
@Path("{id}")
@DELETE
public Response remove(@PathParam("id") long id) throws StorageException {
- Context.getPermissionsManager().checkAdmin(getUserId());
+ permissionsService.checkAdmin(getUserId());
return super.remove(id);
}
diff --git a/src/main/java/org/traccar/api/resource/DeviceResource.java b/src/main/java/org/traccar/api/resource/DeviceResource.java
index 9436b59f6..309308e75 100644
--- a/src/main/java/org/traccar/api/resource/DeviceResource.java
+++ b/src/main/java/org/traccar/api/resource/DeviceResource.java
@@ -31,8 +31,6 @@ 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.HashSet;
import java.util.List;
@@ -51,7 +49,7 @@ 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 {
+ @QueryParam("id") List<Long> deviceIds) {
DeviceManager deviceManager = Context.getDeviceManager();
Set<Long> result;
if (all) {
diff --git a/src/main/java/org/traccar/api/resource/EventResource.java b/src/main/java/org/traccar/api/resource/EventResource.java
index 354d96e4f..38b101b25 100644
--- a/src/main/java/org/traccar/api/resource/EventResource.java
+++ b/src/main/java/org/traccar/api/resource/EventResource.java
@@ -15,6 +15,11 @@
*/
package org.traccar.api.resource;
+import org.traccar.Context;
+import org.traccar.api.BaseResource;
+import org.traccar.model.Event;
+import org.traccar.storage.StorageException;
+
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
@@ -24,13 +29,6 @@ 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;
-import org.traccar.storage.StorageException;
-
@Path("events")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@@ -44,12 +42,6 @@ public class EventResource extends BaseResource {
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());
- }
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..cf4b66fa1 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.
@@ -33,7 +33,6 @@ import org.traccar.model.Notification;
import org.traccar.model.Typed;
import org.traccar.notification.MessageException;
-
@Path("notifications")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
diff --git a/src/main/java/org/traccar/api/resource/PositionResource.java b/src/main/java/org/traccar/api/resource/PositionResource.java
index 511032402..941417231 100644
--- a/src/main/java/org/traccar/api/resource/PositionResource.java
+++ b/src/main/java/org/traccar/api/resource/PositionResource.java
@@ -55,7 +55,7 @@ public class PositionResource extends BaseResource {
} else {
Context.getPermissionsManager().checkDevice(getUserId(), deviceId);
if (from != null && to != null) {
- Context.getPermissionsManager().checkDisableReports(getUserId());
+ permissionsService.checkReports(getUserId());
return Context.getDataManager().getPositions(deviceId, from, to);
} else {
return Collections.singleton(Context.getDeviceManager().getLastPosition(deviceId));
diff --git a/src/main/java/org/traccar/api/resource/ReportResource.java b/src/main/java/org/traccar/api/resource/ReportResource.java
index 03df0d03a..901385d0d 100644
--- a/src/main/java/org/traccar/api/resource/ReportResource.java
+++ b/src/main/java/org/traccar/api/resource/ReportResource.java
@@ -99,7 +99,7 @@ public class ReportResource extends BaseResource {
public Collection<Position> getRoute(
@QueryParam("deviceId") final List<Long> deviceIds, @QueryParam("groupId") final List<Long> groupIds,
@QueryParam("from") Date from, @QueryParam("to") Date to) throws StorageException {
- Context.getPermissionsManager().checkDisableReports(getUserId());
+ permissionsService.checkReports(getUserId());
LogAction.logReport(getUserId(), "route", from, to, deviceIds, groupIds);
return Route.getObjects(getUserId(), deviceIds, groupIds, from, to);
}
@@ -111,7 +111,7 @@ public class ReportResource extends BaseResource {
@QueryParam("deviceId") final List<Long> deviceIds, @QueryParam("groupId") final List<Long> groupIds,
@QueryParam("from") Date from, @QueryParam("to") Date to, @QueryParam("mail") boolean mail)
throws StorageException, IOException {
- Context.getPermissionsManager().checkDisableReports(getUserId());
+ permissionsService.checkReports(getUserId());
return executeReport(getUserId(), mail, stream -> {
LogAction.logReport(getUserId(), "route", from, to, deviceIds, groupIds);
Route.getExcel(stream, getUserId(), deviceIds, groupIds, from, to);
@@ -124,7 +124,7 @@ public class ReportResource extends BaseResource {
@QueryParam("deviceId") final List<Long> deviceIds, @QueryParam("groupId") final List<Long> groupIds,
@QueryParam("type") final List<String> types,
@QueryParam("from") Date from, @QueryParam("to") Date to) throws StorageException {
- Context.getPermissionsManager().checkDisableReports(getUserId());
+ permissionsService.checkReports(getUserId());
LogAction.logReport(getUserId(), "events", from, to, deviceIds, groupIds);
return Events.getObjects(getUserId(), deviceIds, groupIds, types, from, to);
}
@@ -137,7 +137,7 @@ public class ReportResource extends BaseResource {
@QueryParam("type") final List<String> types,
@QueryParam("from") Date from, @QueryParam("to") Date to, @QueryParam("mail") boolean mail)
throws StorageException, IOException {
- Context.getPermissionsManager().checkDisableReports(getUserId());
+ permissionsService.checkReports(getUserId());
return executeReport(getUserId(), mail, stream -> {
LogAction.logReport(getUserId(), "events", from, to, deviceIds, groupIds);
Events.getExcel(stream, getUserId(), deviceIds, groupIds, types, from, to);
@@ -150,7 +150,7 @@ public class ReportResource extends BaseResource {
@QueryParam("deviceId") final List<Long> deviceIds, @QueryParam("groupId") final List<Long> groupIds,
@QueryParam("from") Date from, @QueryParam("to") Date to, @QueryParam("daily") boolean daily)
throws StorageException {
- Context.getPermissionsManager().checkDisableReports(getUserId());
+ permissionsService.checkReports(getUserId());
LogAction.logReport(getUserId(), "summary", from, to, deviceIds, groupIds);
return Summary.getObjects(getUserId(), deviceIds, groupIds, from, to, daily);
}
@@ -163,7 +163,7 @@ public class ReportResource extends BaseResource {
@QueryParam("from") Date from, @QueryParam("to") Date to, @QueryParam("daily") boolean daily,
@QueryParam("mail") boolean mail)
throws StorageException, IOException {
- Context.getPermissionsManager().checkDisableReports(getUserId());
+ permissionsService.checkReports(getUserId());
return executeReport(getUserId(), mail, stream -> {
LogAction.logReport(getUserId(), "summary", from, to, deviceIds, groupIds);
Summary.getExcel(stream, getUserId(), deviceIds, groupIds, from, to, daily);
@@ -176,7 +176,7 @@ public class ReportResource extends BaseResource {
public Collection<TripReport> getTrips(
@QueryParam("deviceId") final List<Long> deviceIds, @QueryParam("groupId") final List<Long> groupIds,
@QueryParam("from") Date from, @QueryParam("to") Date to) throws StorageException {
- Context.getPermissionsManager().checkDisableReports(getUserId());
+ permissionsService.checkReports(getUserId());
LogAction.logReport(getUserId(), "trips", from, to, deviceIds, groupIds);
return Trips.getObjects(getUserId(), deviceIds, groupIds, from, to);
}
@@ -188,7 +188,7 @@ public class ReportResource extends BaseResource {
@QueryParam("deviceId") final List<Long> deviceIds, @QueryParam("groupId") final List<Long> groupIds,
@QueryParam("from") Date from, @QueryParam("to") Date to, @QueryParam("mail") boolean mail)
throws StorageException, IOException {
- Context.getPermissionsManager().checkDisableReports(getUserId());
+ permissionsService.checkReports(getUserId());
return executeReport(getUserId(), mail, stream -> {
LogAction.logReport(getUserId(), "trips", from, to, deviceIds, groupIds);
Trips.getExcel(stream, getUserId(), deviceIds, groupIds, from, to);
@@ -201,7 +201,7 @@ public class ReportResource extends BaseResource {
public Collection<StopReport> getStops(
@QueryParam("deviceId") final List<Long> deviceIds, @QueryParam("groupId") final List<Long> groupIds,
@QueryParam("from") Date from, @QueryParam("to") Date to) throws StorageException {
- Context.getPermissionsManager().checkDisableReports(getUserId());
+ permissionsService.checkReports(getUserId());
LogAction.logReport(getUserId(), "stops", from, to, deviceIds, groupIds);
return Stops.getObjects(getUserId(), deviceIds, groupIds, from, to);
}
@@ -213,7 +213,7 @@ public class ReportResource extends BaseResource {
@QueryParam("deviceId") final List<Long> deviceIds, @QueryParam("groupId") final List<Long> groupIds,
@QueryParam("from") Date from, @QueryParam("to") Date to, @QueryParam("mail") boolean mail)
throws StorageException, IOException {
- Context.getPermissionsManager().checkDisableReports(getUserId());
+ permissionsService.checkReports(getUserId());
return executeReport(getUserId(), mail, stream -> {
LogAction.logReport(getUserId(), "stops", from, to, deviceIds, groupIds);
Stops.getExcel(stream, getUserId(), deviceIds, groupIds, from, to);
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..c640f8d74
--- /dev/null
+++ b/src/main/java/org/traccar/api/security/PermissionsService.java
@@ -0,0 +1,146 @@
+/*
+ * 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.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.Server;
+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.inject.Inject;
+
+public class PermissionsService {
+
+ private final Storage storage;
+
+ private Server server;
+ private User user;
+
+ @Inject
+ public PermissionsService(Storage storage) {
+ this.storage = storage;
+ }
+
+ private Server getServer() throws StorageException {
+ if (server == null) {
+ server = storage.getObject(
+ Server.class, new Request(new Columns.All()));
+ }
+ return server;
+ }
+
+ private User getUser(long userId) throws StorageException {
+ if (user == null) {
+ user = storage.getObject(
+ User.class, new Request(new Columns.All(), new Condition.Equals("id", "id", userId)));
+ }
+ return user;
+ }
+
+ public void checkAdmin(long userId) throws StorageException, SecurityException {
+ if (!getUser(userId).getAdministrator()) {
+ throw new SecurityException("Account is readonly");
+ }
+ }
+
+ public void checkReports(long userId) throws StorageException, SecurityException {
+ if (!getUser(userId).getAdministrator()
+ && (server.getDisableReports() || getUser(userId).getDisableReports())) {
+ throw new SecurityException("Reports are disabled");
+ }
+ }
+
+ 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();
+ if (addition) {
+ int deviceCount = storage.getPermissions(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, Object object, boolean addition) throws StorageException, SecurityException {
+ if (!getUser(userId).getAdministrator()) {
+ checkEdit(userId, object.getClass(), addition);
+ boolean denied = false;
+ if (object instanceof GroupedModel) {
+ long groupId = ((GroupedModel) object).getGroupId();
+ if (groupId > 0) {
+ checkPermission(Group.class, userId, groupId);
+ }
+ }
+ if (object instanceof ScheduledModel) {
+ long calendarId = ((ScheduledModel) object).getCalendarId();
+ if (calendarId > 0) {
+ denied = storage.getPermissions(User.class, userId, Calendar.class, calendarId).isEmpty();
+ }
+ }
+ if (denied) {
+ throw new SecurityException("Write access denied");
+ }
+ }
+ }
+
+ 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, User.class, managedUserId).isEmpty()) {
+ throw new SecurityException("User access denied");
+ }
+ }
+ }
+
+ public <T extends BaseModel> void checkPermission(
+ Class<T> clazz, long userId, long objectId) throws StorageException, SecurityException {
+ if (!getUser(userId).getAdministrator()) {
+ var objects = storage.getObjects(clazz, new Request(
+ new Columns.Include("id"),
+ new Condition.Permission(User.class, userId, clazz)));
+ boolean found = false;
+ for (var object : objects) {
+ if (object.getId() == objectId) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ throw new SecurityException(clazz.getSimpleName() + " access denied");
+ }
+ }
+ }
+
+}
diff --git a/src/main/java/org/traccar/config/Keys.java b/src/main/java/org/traccar/config/Keys.java
index f5299b90b..bb3ea393a 100644
--- a/src/main/java/org/traccar/config/Keys.java
+++ b/src/main/java/org/traccar/config/Keys.java
@@ -1280,6 +1280,13 @@ public final class Keys {
Collections.singletonList(KeyType.GLOBAL));
/**
+ * Log executed SQL queries.
+ */
+ public static final ConfigKey<Boolean> LOGGER_QUERIES = new ConfigKey<>(
+ "logger.queries",
+ Collections.singletonList(KeyType.GLOBAL));
+
+ /**
* Log file name. For rotating logs, a date is added at the end of the file name for non-current logs.
*/
public static final ConfigKey<String> LOGGER_FILE = new ConfigKey<>(
diff --git a/src/main/java/org/traccar/database/DeviceManager.java b/src/main/java/org/traccar/database/DeviceManager.java
index 40591e869..a9b8454eb 100644
--- a/src/main/java/org/traccar/database/DeviceManager.java
+++ b/src/main/java/org/traccar/database/DeviceManager.java
@@ -15,7 +15,6 @@
*/
package org.traccar.database;
-import java.sql.SQLException;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedList;
@@ -110,7 +109,7 @@ public class DeviceManager extends BaseObjectManager<Device> implements Identity
}
@Override
- public Device getByUniqueId(String uniqueId) throws SQLException {
+ public Device getByUniqueId(String uniqueId) {
boolean forceUpdate;
try {
readLock();
diff --git a/src/main/java/org/traccar/database/PermissionsManager.java b/src/main/java/org/traccar/database/PermissionsManager.java
index 2bb808033..9a673c784 100644
--- a/src/main/java/org/traccar/database/PermissionsManager.java
+++ b/src/main/java/org/traccar/database/PermissionsManager.java
@@ -264,45 +264,23 @@ public class PermissionsManager {
return user != null && user.getReadonly();
}
- public boolean getUserDeviceReadonly(long userId) {
- User user = getUser(userId);
- return user != null && user.getDeviceReadonly();
- }
-
public boolean getUserLimitCommands(long userId) {
User user = getUser(userId);
return user != null && user.getLimitCommands();
}
- public boolean getUserDisableReport(long userId) {
- User user = getUser(userId);
- return user != null && user.getDisableReports();
- }
-
public void checkReadonly(long userId) throws SecurityException {
if (!getUserAdmin(userId) && (server.getReadonly() || getUserReadonly(userId))) {
throw new SecurityException("Account is readonly");
}
}
- public void checkDeviceReadonly(long userId) throws SecurityException {
- if (!getUserAdmin(userId) && (server.getDeviceReadonly() || getUserDeviceReadonly(userId))) {
- throw new SecurityException("Account is device readonly");
- }
- }
-
public void checkLimitCommands(long userId) throws SecurityException {
if (!getUserAdmin(userId) && (server.getLimitCommands() || getUserLimitCommands(userId))) {
throw new SecurityException("Account has limit sending commands");
}
}
- public void checkDisableReports(long userId) throws SecurityException {
- if (!getUserAdmin(userId) && (server.getDisableReports() || getUserDisableReport(userId))) {
- throw new SecurityException("Account has reports disabled");
- }
- }
-
public void checkUserDeviceCommand(long userId, long deviceId, long commandId) throws SecurityException {
if (!getUserAdmin(userId) && Context.getCommandsManager().checkDeviceCommand(deviceId, commandId)) {
throw new SecurityException("Command can not be sent to this device");
diff --git a/src/main/java/org/traccar/model/Permission.java b/src/main/java/org/traccar/model/Permission.java
index ad0176b39..bace6b7d4 100644
--- a/src/main/java/org/traccar/model/Permission.java
+++ b/src/main/java/org/traccar/model/Permission.java
@@ -71,7 +71,7 @@ public class Permission {
data.put(getKey(propertyClass), propertyId);
}
- private static String getKey(Class<?> clazz) {
+ public static String getKey(Class<?> clazz) {
return Introspector.decapitalize(clazz.getSimpleName()) + "Id";
}
diff --git a/src/main/java/org/traccar/model/User.java b/src/main/java/org/traccar/model/User.java
index 464d0cbfe..6a67f3276 100644
--- a/src/main/java/org/traccar/model/User.java
+++ b/src/main/java/org/traccar/model/User.java
@@ -79,6 +79,12 @@ public class User extends ExtendedModel {
private boolean administrator;
+ @QueryIgnore
+ @JsonIgnore
+ public boolean getManager() {
+ return userLimit != 0;
+ }
+
public boolean getAdministrator() {
return administrator;
}
diff --git a/src/main/java/org/traccar/storage/DatabaseStorage.java b/src/main/java/org/traccar/storage/DatabaseStorage.java
index d73dc7b25..ff77ff8a8 100644
--- a/src/main/java/org/traccar/storage/DatabaseStorage.java
+++ b/src/main/java/org/traccar/storage/DatabaseStorage.java
@@ -1,5 +1,23 @@
+/*
+ * 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.storage;
+import org.traccar.model.Device;
+import org.traccar.model.Group;
+import org.traccar.model.GroupedModel;
import org.traccar.model.Permission;
import org.traccar.storage.query.Columns;
import org.traccar.storage.query.Condition;
@@ -10,6 +28,7 @@ import org.traccar.storage.query.Request;
import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.HashMap;
+import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
@@ -27,7 +46,7 @@ public class DatabaseStorage extends Storage {
public <T> List<T> getObjects(Class<T> clazz, Request request) throws StorageException {
StringBuilder query = new StringBuilder("SELECT ");
query.append(formatColumns(request.getColumns(), clazz, "get", c -> c));
- query.append(" FROM ").append(getTableName(clazz));
+ query.append(" FROM ").append(getStorageName(clazz));
query.append(formatCondition(request.getCondition()));
query.append(formatOrder(request.getOrder()));
query.append(formatLimit(request.getLimit()));
@@ -45,7 +64,7 @@ public class DatabaseStorage extends Storage {
@Override
public <T> long addObject(T entity, Request request) throws StorageException {
StringBuilder query = new StringBuilder("INSERT INTO ");
- query.append(getTableName(entity.getClass()));
+ query.append(getStorageName(entity.getClass()));
query.append("(");
query.append(formatColumns(request.getColumns(), entity.getClass(), "set", c -> c));
query.append(") VALUES (");
@@ -63,7 +82,7 @@ public class DatabaseStorage extends Storage {
@Override
public <T> void updateObject(T entity, Request request) throws StorageException {
StringBuilder query = new StringBuilder("UPDATE ");
- query.append(getTableName(entity.getClass()));
+ query.append(getStorageName(entity.getClass()));
query.append(" SET ");
query.append(formatColumns(request.getColumns(), entity.getClass(), "set", c -> c + " = :" + c));
query.append(formatCondition(request.getCondition()));
@@ -82,7 +101,7 @@ public class DatabaseStorage extends Storage {
@Override
public void removeObject(Class<?> clazz, Request request) throws StorageException {
StringBuilder query = new StringBuilder("DELETE FROM ");
- query.append(getTableName(clazz));
+ query.append(getStorageName(clazz));
query.append(formatCondition(request.getCondition()));
try {
QueryBuilder builder = QueryBuilder.create(dataSource, query.toString());
@@ -96,9 +115,20 @@ public class DatabaseStorage extends Storage {
}
@Override
- public List<Permission> getPermissions(Class<?> ownerClass, Class<?> propertyClass) throws StorageException {
+ public List<Permission> getPermissions(
+ Class<?> ownerClass, long ownerId, Class<?> propertyClass, long propertyId) throws StorageException {
StringBuilder query = new StringBuilder("SELECT * FROM ");
query.append(Permission.getStorageName(ownerClass, propertyClass));
+ var conditions = new LinkedList<Condition>();
+ if (ownerId > 0) {
+ conditions.add(new Condition.Equals(
+ Permission.getKey(ownerClass), Permission.getKey(ownerClass), ownerId));
+ }
+ if (propertyId > 0) {
+ conditions.add(new Condition.Equals(
+ Permission.getKey(propertyClass), Permission.getKey(propertyClass), propertyId));
+ }
+ query.append(formatCondition(Condition.merge(conditions)));
try {
QueryBuilder builder = QueryBuilder.create(dataSource, query.toString());
return builder.executePermissionsQuery();
@@ -143,7 +173,7 @@ public class DatabaseStorage extends Storage {
}
}
- private String getTableName(Class<?> clazz) throws StorageException {
+ private String getStorageName(Class<?> clazz) throws StorageException {
StorageName storageName = clazz.getAnnotation(StorageName.class);
if (storageName == null) {
throw new StorageException("StorageName annotation is missing");
@@ -154,18 +184,25 @@ public class DatabaseStorage extends Storage {
private Map<String, Object> getConditionVariables(Condition genericCondition) {
Map<String, Object> results = new HashMap<>();
if (genericCondition instanceof Condition.Compare) {
- Condition.Compare condition = (Condition.Compare) genericCondition;
+ var condition = (Condition.Compare) genericCondition;
if (condition.getValue() != null) {
results.put(condition.getVariable(), condition.getValue());
}
} else if (genericCondition instanceof Condition.Between) {
- Condition.Between condition = (Condition.Between) genericCondition;
+ var condition = (Condition.Between) genericCondition;
results.put(condition.getFromVariable(), condition.getFromValue());
results.put(condition.getToVariable(), condition.getToValue());
} else if (genericCondition instanceof Condition.Binary) {
- Condition.Binary condition = (Condition.Binary) genericCondition;
+ var condition = (Condition.Binary) genericCondition;
results.putAll(getConditionVariables(condition.getFirst()));
results.putAll(getConditionVariables(condition.getSecond()));
+ } else if (genericCondition instanceof Condition.Permission) {
+ var condition = (Condition.Permission) genericCondition;
+ if (condition.getOwnerId() > 0) {
+ results.put(Permission.getKey(condition.getOwnerClass()), condition.getOwnerId());
+ } else {
+ results.put(Permission.getKey(condition.getPropertyClass()), condition.getPropertyId());
+ }
}
return results;
}
@@ -175,11 +212,11 @@ public class DatabaseStorage extends Storage {
return columns.getColumns(clazz, type).stream().map(mapper).collect(Collectors.joining(", "));
}
- private String formatCondition(Condition genericCondition) {
+ private String formatCondition(Condition genericCondition) throws StorageException {
return formatCondition(genericCondition, true);
}
- private String formatCondition(Condition genericCondition, boolean appendWhere) {
+ private String formatCondition(Condition genericCondition, boolean appendWhere) throws StorageException {
StringBuilder result = new StringBuilder();
if (genericCondition != null) {
if (appendWhere) {
@@ -187,7 +224,7 @@ public class DatabaseStorage extends Storage {
}
if (genericCondition instanceof Condition.Compare) {
- Condition.Compare condition = (Condition.Compare) genericCondition;
+ var condition = (Condition.Compare) genericCondition;
result.append(condition.getColumn());
result.append(" ");
result.append(condition.getOperator());
@@ -196,7 +233,7 @@ public class DatabaseStorage extends Storage {
} else if (genericCondition instanceof Condition.Between) {
- Condition.Between condition = (Condition.Between) genericCondition;
+ var condition = (Condition.Between) genericCondition;
result.append(condition.getColumn());
result.append(" BETWEEN :");
result.append(condition.getFromVariable());
@@ -205,13 +242,20 @@ public class DatabaseStorage extends Storage {
} else if (genericCondition instanceof Condition.Binary) {
- Condition.Binary condition = (Condition.Binary) genericCondition;
+ var condition = (Condition.Binary) genericCondition;
result.append(formatCondition(condition.getFirst(), false));
result.append(" ");
result.append(condition.getOperator());
result.append(" ");
result.append(formatCondition(condition.getSecond(), false));
+ } else if (genericCondition instanceof Condition.Permission) {
+
+ var condition = (Condition.Permission) genericCondition;
+ result.append("id IN (");
+ result.append(formatPermissionQuery(condition));
+ result.append(")");
+
}
}
return result.toString();
@@ -238,4 +282,83 @@ public class DatabaseStorage extends Storage {
return result.toString();
}
+ private String formatPermissionQuery(Condition.Permission condition) throws StorageException {
+ StringBuilder result = new StringBuilder();
+
+ String outputKey;
+ String conditionKey;
+ if (condition.getOwnerId() > 0) {
+ outputKey = Permission.getKey(condition.getPropertyClass());
+ conditionKey = Permission.getKey(condition.getOwnerClass());
+ } else {
+ outputKey = Permission.getKey(condition.getOwnerClass());
+ conditionKey = Permission.getKey(condition.getPropertyClass());
+ }
+
+ result.append("SELECT ");
+ result.append(outputKey);
+ result.append(" FROM ");
+ result.append(Permission.getStorageName(condition.getOwnerClass(), condition.getPropertyClass()));
+ result.append(" WHERE ");
+ result.append(conditionKey);
+ result.append(" = :");
+ result.append(conditionKey);
+
+ if (condition.getIncludeGroups()) {
+
+ boolean expandDevices;
+ String groupStorageName;
+ if (GroupedModel.class.isAssignableFrom(condition.getOwnerClass())) {
+ expandDevices = Device.class.isAssignableFrom(condition.getOwnerClass());
+ groupStorageName = Permission.getStorageName(Group.class, condition.getPropertyClass());
+ } else {
+ expandDevices = Device.class.isAssignableFrom(condition.getPropertyClass());
+ groupStorageName = Permission.getStorageName(condition.getOwnerClass(), Group.class);
+ }
+
+ result.append(" UNION ");
+
+ result.append("SELECT DISTINCT ");
+ result.append(expandDevices? "devices." : "groups."); // TODO handle reverse search (e.g. users by device)
+ result.append(outputKey);
+ result.append(" FROM ");
+ result.append(groupStorageName);
+
+ result.append(" INNER JOIN (");
+ result.append("SELECT id as parentid, id as groupid FROM ");
+ result.append(getStorageName(Group.class));
+ result.append(" UNION ");
+ result.append("SELECT groupid as parentid, id as groupid FROM ");
+ result.append(getStorageName(Group.class));
+ result.append(" WHERE groupid IS NOT NULL");
+ result.append(" UNION ");
+ result.append("SELECT g2.groupid as parentid, g1.id as groupid FROM ");
+ result.append(getStorageName(Group.class));
+ result.append(" AS g2");
+ result.append(" INNER JOIN ");
+ result.append(getStorageName(Group.class));
+ result.append(" AS g1 ON g2.id = g1.groupid");
+ result.append(" WHERE g2.groupid IS NOT NULL");
+ result.append(") AS groups ON ");
+ result.append(groupStorageName);
+ result.append(".groupid = groups.parentid");
+
+ if (expandDevices) {
+ result.append(" INNER JOIN (");
+ result.append("SELECT groupid as parentid, id as deviceid FROM ");
+ result.append(getStorageName(Device.class));
+ result.append(" WHERE groupid IS NOT NULL");
+ result.append(") AS devices ON groups.groupid = devices.parentid");
+ }
+
+ result.append(" WHERE ");
+ result.append(conditionKey); // TODO handle search for device / group
+ result.append(" = :");
+ result.append(conditionKey);
+
+ }
+
+ return result.toString();
+ }
+
}
diff --git a/src/main/java/org/traccar/storage/MemoryStorage.java b/src/main/java/org/traccar/storage/MemoryStorage.java
index 9cfe30a2b..71e895428 100644
--- a/src/main/java/org/traccar/storage/MemoryStorage.java
+++ b/src/main/java/org/traccar/storage/MemoryStorage.java
@@ -1,3 +1,18 @@
+/*
+ * 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.storage;
import org.traccar.model.Pair;
@@ -38,8 +53,11 @@ public class MemoryStorage extends Storage {
}
@Override
- public List<Permission> getPermissions(Class<?> ownerClass, Class<?> propertyClass) {
+ public List<Permission> getPermissions(
+ Class<?> ownerClass, long ownerId, Class<?> propertyClass, long propertyId) {
return getPermissionsSet(ownerClass, propertyClass).stream()
+ .filter(pair -> ownerId == 0 || pair.getFirst().equals(ownerId))
+ .filter(pair -> propertyId == 0 || pair.getSecond().equals(propertyId))
.map(pair -> new Permission(ownerClass, pair.getFirst(), propertyClass, pair.getSecond()))
.collect(Collectors.toList());
}
diff --git a/src/main/java/org/traccar/storage/QueryBuilder.java b/src/main/java/org/traccar/storage/QueryBuilder.java
index da8002f0b..874a046b4 100644
--- a/src/main/java/org/traccar/storage/QueryBuilder.java
+++ b/src/main/java/org/traccar/storage/QueryBuilder.java
@@ -19,6 +19,7 @@ import com.fasterxml.jackson.core.JsonProcessingException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.traccar.Context;
+import org.traccar.config.Keys;
import org.traccar.model.Permission;
import javax.sql.DataSource;
@@ -309,15 +310,6 @@ public final class QueryBuilder {
void process(T object, ResultSet resultSet) throws SQLException;
}
- public <T> T executeQuerySingle(Class<T> clazz) throws SQLException {
- List<T> result = executeQuery(clazz);
- if (!result.isEmpty()) {
- return result.iterator().next();
- } else {
- return null;
- }
- }
-
private <T> void addProcessors(
List<ResultSetProcessor<T>> processors,
final Class<?> parameterType, final Method method, final String name) {
@@ -395,6 +387,12 @@ public final class QueryBuilder {
}
}
+ private void logQuery() {
+ if (Context.getConfig().getBoolean(Keys.LOGGER_QUERIES)) {
+ LOGGER.info(query);
+ }
+ }
+
public <T> List<T> executeQuery(Class<T> clazz) throws SQLException {
List<T> result = new LinkedList<>();
@@ -402,6 +400,8 @@ public final class QueryBuilder {
try {
+ logQuery();
+
try (ResultSet resultSet = statement.executeQuery()) {
ResultSetMetaData resultMetaData = resultSet.getMetaData();
@@ -457,6 +457,7 @@ public final class QueryBuilder {
if (query != null) {
try {
+ logQuery();
statement.execute();
if (returnGeneratedKeys) {
ResultSet resultSet = statement.getGeneratedKeys();
@@ -476,6 +477,7 @@ public final class QueryBuilder {
List<Permission> result = new LinkedList<>();
if (query != null) {
try {
+ logQuery();
try (ResultSet resultSet = statement.executeQuery()) {
ResultSetMetaData resultMetaData = resultSet.getMetaData();
while (resultSet.next()) {
diff --git a/src/main/java/org/traccar/storage/Storage.java b/src/main/java/org/traccar/storage/Storage.java
index 22c48cae0..22b5aaedc 100644
--- a/src/main/java/org/traccar/storage/Storage.java
+++ b/src/main/java/org/traccar/storage/Storage.java
@@ -1,3 +1,18 @@
+/*
+ * 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.storage;
import org.traccar.model.Permission;
@@ -16,12 +31,27 @@ public abstract class Storage {
public abstract void removeObject(Class<?> clazz, Request request) throws StorageException;
public abstract List<Permission> getPermissions(
- Class<?> ownerClass, Class<?> propertyClass) throws StorageException;
+ Class<?> ownerClass, long ownerId, Class<?> propertyClass, long propertyId) throws StorageException;
public abstract void addPermission(Permission permission) throws StorageException;
public abstract void removePermission(Permission permission) throws StorageException;
+ public List<Permission> getPermissions(
+ Class<?> ownerClass, Class<?> propertyClass) throws StorageException {
+ return getPermissions(ownerClass, 0, propertyClass, 0);
+ }
+
+ public List<Permission> getPermissions(
+ Class<?> ownerClass, long ownerId, Class<?> propertyClass) throws StorageException {
+ return getPermissions(ownerClass, ownerId, propertyClass, 0);
+ }
+
+ public List<Permission> getPermissions(
+ Class<?> ownerClass, Class<?> propertyClass, long propertyId) throws StorageException {
+ return getPermissions(ownerClass, 0, propertyClass, propertyId);
+ }
+
public <T> T getObject(Class<T> clazz, Request request) throws StorageException {
var objects = getObjects(clazz, request);
return objects.isEmpty() ? null : objects.get(0);
diff --git a/src/main/java/org/traccar/storage/StorageException.java b/src/main/java/org/traccar/storage/StorageException.java
index 6c1e9c2ff..3f066cae6 100644
--- a/src/main/java/org/traccar/storage/StorageException.java
+++ b/src/main/java/org/traccar/storage/StorageException.java
@@ -1,3 +1,18 @@
+/*
+ * 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.storage;
public class StorageException extends Exception {
diff --git a/src/main/java/org/traccar/storage/StorageName.java b/src/main/java/org/traccar/storage/StorageName.java
index b6fa55e02..bf824c333 100644
--- a/src/main/java/org/traccar/storage/StorageName.java
+++ b/src/main/java/org/traccar/storage/StorageName.java
@@ -1,3 +1,18 @@
+/*
+ * 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.storage;
import java.lang.annotation.ElementType;
diff --git a/src/main/java/org/traccar/storage/query/Columns.java b/src/main/java/org/traccar/storage/query/Columns.java
index 196d2281c..97ca13be9 100644
--- a/src/main/java/org/traccar/storage/query/Columns.java
+++ b/src/main/java/org/traccar/storage/query/Columns.java
@@ -1,3 +1,18 @@
+/*
+ * 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.storage.query;
import org.traccar.storage.QueryExtended;
diff --git a/src/main/java/org/traccar/storage/query/Condition.java b/src/main/java/org/traccar/storage/query/Condition.java
index 82c8e8479..91ede236c 100644
--- a/src/main/java/org/traccar/storage/query/Condition.java
+++ b/src/main/java/org/traccar/storage/query/Condition.java
@@ -1,7 +1,38 @@
+/*
+ * 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.storage.query;
+import org.traccar.model.GroupedModel;
+
+import java.util.List;
+
public interface Condition {
+ static Condition merge(List<Condition> conditions) {
+ Condition result = null;
+ var iterator = conditions.iterator();
+ if (iterator.hasNext()) {
+ result = iterator.next();
+ while (iterator.hasNext()) {
+ result = new Condition.And(result, iterator.next());
+ }
+ }
+ return result;
+ }
+
class Equals extends Compare {
public Equals(String column, String variable) {
this(column, variable, null);
@@ -114,4 +145,55 @@ public interface Condition {
}
}
+ class Permission implements Condition {
+ private final Class<?> ownerClass;
+ private final long ownerId;
+ private final Class<?> propertyClass;
+ private final long propertyId;
+ private final boolean excludeGroups;
+
+ private Permission(
+ Class<?> ownerClass, long ownerId, Class<?> propertyClass, long propertyId, boolean excludeGroups) {
+ this.ownerClass = ownerClass;
+ this.ownerId = ownerId;
+ this.propertyClass = propertyClass;
+ this.propertyId = propertyId;
+ this.excludeGroups = excludeGroups;
+ }
+
+ public Permission(Class<?> ownerClass, long ownerId, Class<?> propertyClass) {
+ this(ownerClass, ownerId, propertyClass, 0, false);
+ }
+
+ public Permission(Class<?> ownerClass, Class<?> propertyClass, long propertyId) {
+ this(ownerClass, 0, propertyClass, propertyId, false);
+ }
+
+ public Permission excludeGroups() {
+ return new Permission(this.ownerClass, this.ownerId, this.propertyClass, this.propertyId, true);
+ }
+
+ public Class<?> getOwnerClass() {
+ return ownerClass;
+ }
+
+ public long getOwnerId() {
+ return ownerId;
+ }
+
+ public Class<?> getPropertyClass() {
+ return propertyClass;
+ }
+
+ public long getPropertyId() {
+ return propertyId;
+ }
+
+ public boolean getIncludeGroups() {
+ boolean ownerGroupModel = GroupedModel.class.isAssignableFrom(ownerClass);
+ boolean propertyGroupModel = GroupedModel.class.isAssignableFrom(propertyClass);
+ return (ownerGroupModel || propertyGroupModel) && !excludeGroups;
+ }
+ }
+
}
diff --git a/src/main/java/org/traccar/storage/query/Limit.java b/src/main/java/org/traccar/storage/query/Limit.java
index 4a20f0ce2..9673e5426 100644
--- a/src/main/java/org/traccar/storage/query/Limit.java
+++ b/src/main/java/org/traccar/storage/query/Limit.java
@@ -1,3 +1,18 @@
+/*
+ * 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.storage.query;
public class Limit {
diff --git a/src/main/java/org/traccar/storage/query/Order.java b/src/main/java/org/traccar/storage/query/Order.java
index 6852c5ba8..d12acc83b 100644
--- a/src/main/java/org/traccar/storage/query/Order.java
+++ b/src/main/java/org/traccar/storage/query/Order.java
@@ -1,3 +1,18 @@
+/*
+ * 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.storage.query;
public class Order {
diff --git a/src/main/java/org/traccar/storage/query/Request.java b/src/main/java/org/traccar/storage/query/Request.java
index a98dd48f7..41aa37bf1 100644
--- a/src/main/java/org/traccar/storage/query/Request.java
+++ b/src/main/java/org/traccar/storage/query/Request.java
@@ -1,3 +1,18 @@
+/*
+ * 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.storage.query;
public class Request {