diff --git a/Core/classes/env.properties b/Core/classes/env.properties index a5b3d8543d..1699568cc0 100644 --- a/Core/classes/env.properties +++ b/Core/classes/env.properties @@ -118,7 +118,12 @@ db.nosql.runCorruptionOnStartupIfDirty=false #Size of in memory cache to hold a role's inheritance list, this represents the # maximum number of roles to keep in the cache at any given time cache.roles.size=1000 +#Size of in memory cache for Users, this represents the +# maximum number of roles to keep in the cache at any given time cache.users.size=1000 +#Size of in memory cache to hold created Permissions, this represents the +# maximum number of roles to keep in the cache at any given time +cache.permission.size=1000 # The location of the Mango Automation store from which to get license files. store.url=https://store.infiniteautomation.com diff --git a/Core/src/com/infiniteautomation/mango/spring/service/PermissionService.java b/Core/src/com/infiniteautomation/mango/spring/service/PermissionService.java index 09ef3687cb..aeb88e899b 100644 --- a/Core/src/com/infiniteautomation/mango/spring/service/PermissionService.java +++ b/Core/src/com/infiniteautomation/mango/spring/service/PermissionService.java @@ -3,13 +3,34 @@ */ package com.infiniteautomation.mango.spring.service; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Proxy; +import java.util.Collections; +import java.util.HashSet; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.Callable; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.event.EventListener; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken; +import org.springframework.stereotype.Service; + import com.github.benmanes.caffeine.cache.Caffeine; import com.github.benmanes.caffeine.cache.LoadingCache; import com.infiniteautomation.mango.permission.MangoPermission; import com.infiniteautomation.mango.spring.MangoRuntimeContextConfiguration; import com.infiniteautomation.mango.spring.events.DaoEvent; import com.infiniteautomation.mango.util.Functions; +import com.infiniteautomation.mango.util.exception.NotFoundException; import com.serotonin.m2m2.Common; +import com.serotonin.m2m2.db.dao.PermissionDao; import com.serotonin.m2m2.db.dao.RoleDao; import com.serotonin.m2m2.i18n.ProcessResult; import com.serotonin.m2m2.i18n.TranslatableMessage; @@ -24,24 +45,6 @@ import com.serotonin.m2m2.vo.permission.PermissionHolder; import com.serotonin.m2m2.vo.role.Role; import com.serotonin.m2m2.vo.role.RoleVO; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.context.event.EventListener; -import org.springframework.security.core.context.SecurityContext; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken; -import org.springframework.stereotype.Service; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Proxy; -import java.util.Collections; -import java.util.HashSet; -import java.util.Map.Entry; -import java.util.Objects; -import java.util.Set; -import java.util.concurrent.Callable; -import java.util.function.Supplier; -import java.util.stream.Collectors; /** * @author Terry Packer @@ -51,26 +54,34 @@ public class PermissionService { private final RoleDao roleDao; + private final PermissionDao permissionDao; private final DataSourcePermissionDefinition dataSourcePermission; private final PermissionHolder systemSuperadmin; private final EventsViewPermissionDefinition eventsViewPermission; //Cache of role xid to inheritance private final LoadingCache roleHierarchyCache; + //Cache of permissionId to MangoPermission + private final LoadingCache permissionCache; @Autowired public PermissionService(RoleDao roleDao, + PermissionDao permissionDao, @Qualifier(MangoRuntimeContextConfiguration.SYSTEM_SUPERADMIN_PERMISSION_HOLDER) PermissionHolder systemSuperadmin, DataSourcePermissionDefinition dataSourcePermission, EventsViewPermissionDefinition eventsView) { this.roleDao = roleDao; + this.permissionDao = permissionDao; this.dataSourcePermission = dataSourcePermission; this.systemSuperadmin = systemSuperadmin; this.eventsViewPermission = eventsView; this.roleHierarchyCache = Caffeine.newBuilder() .maximumSize(Common.envProps.getLong("cache.roles.size", 1000)) .build(this::loadRoleInheritance); + this.permissionCache = Caffeine.newBuilder() + .maximumSize(Common.envProps.getLong("cache.permission.size", 1000)) + .build(this::loadPermission); } /** @@ -445,6 +456,55 @@ public Set getAllInheritedRoles(PermissionHolder holder) { return Collections.unmodifiableSet(allRoles); } + /** + * Get a permission from the cache, load from db if necessary + * @param id + * @return + * @throws NotFoundException if permission with this ID not found + */ + public MangoPermission get(Integer id) throws NotFoundException { + MangoPermission permission = this.permissionCache.get(id); + if(permission == null) { + throw new NotFoundException(); + }else { + return permission; + } + } + + /** + * Get/Create a permission based on the minterms of this permission + * and return the id for it. This is done before saving a VO with a + * permission so there is a FK to reference. + * @param permission + * @return + */ + public Integer permissionId(MangoPermission permission) { + //TODO Mango 4.0 use cache, need to be able to quickly find a permission + // without using the ID. + return permissionDao.permissionId(permission); + } + + /** + * A vo with a permission was deleted, attempt to delete it and clean up + * if other VOs reference this permission it will not be deleted + * @param permission + */ + public void permissionDeleted(MangoPermission permission) { + if(permissionDao.permissionDeleted(permission.getId())) { + this.permissionCache.invalidate(permission.getId()); + } + } + + /** + * Load a permission from the database via it's id, used by cache + * @param id + * @return + */ + private MangoPermission loadPermission(Integer id) { + //TODO Mango 4.0 throw NotFoundException? + return permissionDao.get(id); + } + /** * Validate a permission. This will validate that: * diff --git a/Core/src/com/serotonin/m2m2/db/dao/DataPointDao.java b/Core/src/com/serotonin/m2m2/db/dao/DataPointDao.java index e17de1d160..ec0613a2e0 100644 --- a/Core/src/com/serotonin/m2m2/db/dao/DataPointDao.java +++ b/Core/src/com/serotonin/m2m2/db/dao/DataPointDao.java @@ -4,11 +4,55 @@ */ package com.serotonin.m2m2.db.dao; +import static com.serotonin.m2m2.db.dao.DataPointTagsDao.DATA_POINT_TAGS_PIVOT_ALIAS; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.stream.Collectors; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.jooq.Condition; +import org.jooq.Field; +import org.jooq.Name; +import org.jooq.Record; +import org.jooq.Select; +import org.jooq.SelectJoinStep; +import org.jooq.SortField; +import org.jooq.Table; +import org.jooq.impl.DSL; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.dao.DataAccessException; +import org.springframework.jdbc.core.ResultSetExtractor; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Repository; + import com.fasterxml.jackson.databind.ObjectMapper; -import com.infiniteautomation.mango.db.query.*; +import com.infiniteautomation.mango.db.query.ConditionSortLimit; +import com.infiniteautomation.mango.db.query.ConditionSortLimitWithTagKeys; +import com.infiniteautomation.mango.db.query.RQLSubSelectCondition; +import com.infiniteautomation.mango.db.query.RQLToCondition; +import com.infiniteautomation.mango.db.query.RQLToConditionWithTagKeys; import com.infiniteautomation.mango.permission.MangoPermission; import com.infiniteautomation.mango.spring.MangoRuntimeContextConfiguration; -import com.infiniteautomation.mango.spring.db.*; +import com.infiniteautomation.mango.spring.db.DataPointTableDefinition; +import com.infiniteautomation.mango.spring.db.DataSourceTableDefinition; +import com.infiniteautomation.mango.spring.db.EventDetectorTableDefinition; +import com.infiniteautomation.mango.spring.db.EventHandlerTableDefinition; +import com.infiniteautomation.mango.spring.db.RoleTableDefinition; +import com.infiniteautomation.mango.spring.db.UserCommentTableDefinition; import com.infiniteautomation.mango.spring.events.DaoEvent; import com.infiniteautomation.mango.spring.events.DaoEventType; import com.infiniteautomation.mango.spring.events.DataPointTagsUpdatedEvent; @@ -38,28 +82,6 @@ import com.serotonin.m2m2.vo.permission.PermissionHolder; import com.serotonin.provider.Providers; import com.serotonin.util.SerializationHelper; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.jooq.*; -import org.jooq.impl.DSL; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.dao.DataAccessException; -import org.springframework.jdbc.core.ResultSetExtractor; -import org.springframework.jdbc.core.RowMapper; -import org.springframework.stereotype.Repository; - -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.Comparator; -import java.util.*; -import java.util.function.BiConsumer; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.stream.Collectors; - -import static com.serotonin.m2m2.db.dao.DataPointTagsDao.DATA_POINT_TAGS_PIVOT_ALIAS; /** * @@ -74,7 +96,6 @@ public class DataPointDao extends AbstractVoDao deleteDataPoints(final int dataSourceId) { //Clean permissions for(DataPointVO vo : points) { - permissionDao.permissionDeleted(vo.getReadPermission(), vo.getSetPermission()); + permissionService.permissionDeleted(vo.getReadPermission()); + permissionService.permissionDeleted(vo.getSetPermission()); + permissionService.permissionDeleted(vo.getEditPermission()); } } @@ -360,9 +381,9 @@ public DataPointSummary getSummary(String xid) { summary.setName(rs.getString(3)); summary.setDataSourceId(rs.getInt(4)); summary.setDeviceName(rs.getString(5)); - summary.setReadPermission(permissionDao.get(rs.getInt(6))); - summary.setEditPermission(permissionDao.get(rs.getInt(7))); - summary.setSetPermission(permissionDao.get(rs.getInt(8))); + summary.setReadPermission(permissionService.get(rs.getInt(6))); + summary.setEditPermission(permissionService.get(rs.getInt(7))); + summary.setSetPermission(permissionService.get(rs.getInt(8))); return summary; }else { return null; @@ -573,9 +594,9 @@ public DataPointVO mapRow(ResultSet rs, int rowNum) throws SQLException { @Override public void savePreRelationalData(DataPointVO existing, DataPointVO vo) { - permissionDao.permissionId(vo.getReadPermission()); - permissionDao.permissionId(vo.getEditPermission()); - permissionDao.permissionId(vo.getSetPermission()); + permissionService.permissionId(vo.getReadPermission()); + permissionService.permissionId(vo.getEditPermission()); + permissionService.permissionId(vo.getSetPermission()); } @Override @@ -601,13 +622,13 @@ public void saveRelationalData(DataPointVO existing, DataPointVO vo) { if(existing != null) { if(!existing.getReadPermission().equals(vo.getReadPermission())) { - permissionDao.permissionDeleted(existing.getReadPermission()); + permissionService.permissionDeleted(existing.getReadPermission()); } if(!existing.getEditPermission().equals(vo.getEditPermission())) { - permissionDao.permissionDeleted(existing.getEditPermission()); + permissionService.permissionDeleted(existing.getEditPermission()); } if(!existing.getSetPermission().equals(vo.getSetPermission())) { - permissionDao.permissionDeleted(existing.getSetPermission()); + permissionService.permissionDeleted(existing.getSetPermission()); } } } @@ -623,9 +644,9 @@ public void loadRelationalData(DataPointVO vo) { vo.setTags(dataPointTagsDao.getTagsForDataPointId(vo.getId())); //Populate permissions - vo.setReadPermission(permissionDao.get(vo.getReadPermission().getId())); - vo.setEditPermission(permissionDao.get(vo.getEditPermission().getId())); - vo.setSetPermission(permissionDao.get(vo.getSetPermission().getId())); + vo.setReadPermission(permissionService.get(vo.getReadPermission().getId())); + vo.setEditPermission(permissionService.get(vo.getEditPermission().getId())); + vo.setSetPermission(permissionService.get(vo.getSetPermission().getId())); DataSourceDefinition def = ModuleRegistry.getDataSourceDefinition(vo.getPointLocator().getDataSourceType()); if(def != null) { @@ -657,7 +678,9 @@ public void deleteRelationalData(DataPointVO vo) { @Override public void deletePostRelationalData(DataPointVO vo) { //Clean permissions - permissionDao.permissionDeleted(vo.getReadPermission(), vo.getEditPermission(), vo.getSetPermission()); + permissionService.permissionDeleted(vo.getReadPermission()); + permissionService.permissionDeleted(vo.getEditPermission()); + permissionService.permissionDeleted(vo.getSetPermission()); } @Override diff --git a/Core/src/com/serotonin/m2m2/db/dao/PermissionDao.java b/Core/src/com/serotonin/m2m2/db/dao/PermissionDao.java index a6e4064e13..930d4b086b 100644 --- a/Core/src/com/serotonin/m2m2/db/dao/PermissionDao.java +++ b/Core/src/com/serotonin/m2m2/db/dao/PermissionDao.java @@ -28,7 +28,6 @@ import org.springframework.stereotype.Repository; import com.infiniteautomation.mango.permission.MangoPermission; -import com.infiniteautomation.mango.spring.db.DataSourceTableDefinition; import com.infiniteautomation.mango.spring.db.RoleTableDefinition; import com.serotonin.m2m2.vo.role.Role; @@ -39,12 +38,10 @@ public class PermissionDao extends BaseDao { private final RoleTableDefinition roleTable; - private final DataSourceTableDefinition dataSourceTable; @Autowired - PermissionDao(RoleTableDefinition roleTable, DataSourceTableDefinition dataSourceTable) { + PermissionDao(RoleTableDefinition roleTable) { this.roleTable = roleTable; - this.dataSourceTable = dataSourceTable; } /** @@ -162,9 +159,10 @@ private Integer getOrInsertPermission(MangoPermission permission) { .values(permissionIdFinal, id)) .collect(Collectors.toList())) .execute(); + permission.setId(permissionId); + }else { + permission.setId(permissionId); } - - permission.setId(permissionId); return permissionId; } @@ -228,10 +226,13 @@ public void permissionUnlinked() { } /** + * TODO Mango 4.0 remove (use PermissionService.permissionDeleted()) + * * A vo with a permission was deleted, attempt to delete it and clean up * if other VOs reference this permission it will not be deleted * @param permissions */ + @Deprecated public void permissionDeleted(MangoPermission... permissions) { int deleted = 0; for(MangoPermission permission : permissions) { @@ -245,4 +246,21 @@ public void permissionDeleted(MangoPermission... permissions) { permissionUnlinked(); } } + + /** + * A vo with a permission was deleted, attempt to delete it and clean up + * if other VOs reference this permission it will not be deleted + * @param permissions + */ + public boolean permissionDeleted(Integer id) { + try{ + if(create.deleteFrom(PERMISSIONS).where(PERMISSIONS.id.eq(id)).execute() > 0) { + permissionUnlinked(); + return true; + } + }catch(Exception e) { + //permission still in use + } + return false; + } }