diff options
author | Anton Tananaev <anton@traccar.org> | 2022-05-22 10:55:48 -0700 |
---|---|---|
committer | Anton Tananaev <anton@traccar.org> | 2022-05-22 10:55:48 -0700 |
commit | 9f8a78e5b7afddc6ccb0a54f4b3e59e1395de8eb (patch) | |
tree | 26356ca17e21d626843671aac23bf775c6451359 | |
parent | d9f51795058399e85ae83a8e308a0c2bc1d13e4b (diff) | |
parent | 9c9f5d66147cfa428ea18dd29103b9c82e529dca (diff) | |
download | trackermap-server-9f8a78e5b7afddc6ccb0a54f4b3e59e1395de8eb.tar.gz trackermap-server-9f8a78e5b7afddc6ccb0a54f4b3e59e1395de8eb.tar.bz2 trackermap-server-9f8a78e5b7afddc6ccb0a54f4b3e59e1395de8eb.zip |
Merge branch 'storage'
31 files changed, 686 insertions, 234 deletions
@@ -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 { |