Skip to content

Commit

Permalink
Merge pull request #781 from hashmapinc/Tempus-746-policy-based-pagin…
Browse files Browse the repository at this point in the history
…ation

Reviewed from Jayesh. No changes suggested, hence merging.
  • Loading branch information
akshaymhetre committed Oct 11, 2018
2 parents 4563ed6 + afc059d commit 8fcf72b
Show file tree
Hide file tree
Showing 29 changed files with 996 additions and 137 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -724,16 +724,23 @@ protected void assignUserToGroup(UserId customerUserId, CustomerGroup savedCusto
logout();
}

protected void unAssignUserFromGroup(CustomerGroupId savedCustomerGroupId, UserId userId) throws Exception {
loginTenantAdmin();
doPut("/api/customer/group/"+savedCustomerGroupId.getId().toString()+"/users", Collections.singletonList(userId.getId().toString()))
.andExpect(status().isOk());
logout();
}

protected UserId getCustomerUserId() throws Exception {
loginCustomerUser();
User user = doGet("/api/auth/user", User.class);
logout();
return user.getId();
}

protected CustomerGroup createGroupWithPolicies(List<String> policies, CustomerId customerId) throws Exception {
protected CustomerGroup createGroupWithPolicies(List<String> policies, CustomerId customerId, String my_customer_group) throws Exception {
CustomerGroup customerGroup = new CustomerGroup();
customerGroup.setTitle("My Customer Group");
customerGroup.setTitle(my_customer_group);
customerGroup.setTenantId(tenantId);
customerGroup.setCustomerId(customerId);
customerGroup.setPolicies(policies);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -590,29 +590,6 @@ public void testFindCustomerAssetsByType() throws Exception {
Assert.assertEquals(0, pageData.getData().size());
}

@Test
public void testFindAssetsByDataModelObjectId() throws Exception {

Asset asset = new Asset();
DataModelObjectId dataModelObjectId = new DataModelObjectId(UUIDs.timeBased());
asset.setDataModelObjectId(dataModelObjectId);
asset.setName("My asset");
asset.setType("default");

Asset asset2 = new Asset();
asset2.setDataModelObjectId(dataModelObjectId);
asset2.setName("My asset2");
asset2.setType("default");

Asset savedAsset = doPost("/api/asset", asset, Asset.class);
Asset savedAsset2 = doPost("/api/asset", asset2, Asset.class);

List<Asset> foundAsset = doGetTyped("/api/datamodelobject/assets/" + savedAsset.getDataModelObjectId().getId().toString(), new TypeReference<List<Asset>>(){});

Assert.assertNotNull(foundAsset);
Assert.assertEquals(2, foundAsset.size());
}

@Test
public void testPolicyForAsset() throws Exception {

Expand All @@ -625,7 +602,7 @@ public void testPolicyForAsset() throws Exception {

List<String> policies = Collections.singletonList(policy);

CustomerGroup savedCustomerGroup = createGroupWithPolicies(policies, customerUser.getCustomerId());
CustomerGroup savedCustomerGroup = createGroupWithPolicies(policies, customerUser.getCustomerId(), "My Customer Group");
String getPolicyUrl = "/api/customer/group/" + savedCustomerGroup.getId().getId().toString() + "/policy";

final Map<String, Map<String, String>> displayablePolicies = doGetTyped(getPolicyUrl, new TypeReference<Map<String, Map<String, String>>>() {
Expand Down Expand Up @@ -679,4 +656,74 @@ public void testPolicyForAsset() throws Exception {
logout();
}

@Test
public void findAllAssetsByDataModeObject() throws Exception{
DataModel dataModel = createDataModel();
DataModelObject dataModelObject = createDataModelObject(dataModel);

String policyNew1 = String.format("CUSTOMER_USER:ASSET?%s=%s:READ",
UserPermission.ResourceAttribute.DATA_MODEL_ID, dataModelObject.getId().getId().toString());


String policyNew2 = String.format("CUSTOMER_USER:ASSET?%s=%s:CREATE",
UserPermission.ResourceAttribute.DATA_MODEL_ID, dataModelObject.getId().getId().toString());
List<String> policies = new ArrayList<>();
policies.add(policyNew1);
policies.add(policyNew2);
CustomerGroup savedCustomerGroup = createGroupWithPolicies(policies, customerUser.getCustomerId(), "My Customer Group");
UserId customerUserId = getCustomerUserId();
assignUserToGroup(customerUserId, savedCustomerGroup);

logout();
loginCustomerUser();
for (int i = 0; i < 20; i++) {
createAsset(dataModelObject.getId(), customerUser.getCustomerId(), "Customer's asset"+i);
}

TextPageLink pageLink = new TextPageLink(15);
TextPageData<Asset> pageData = doGetTypedWithPageLink("/api/datamodelobject/assets/"+ dataModelObject.getId().getId().toString()+"?",
new TypeReference<TextPageData<Asset>>(){}, pageLink);
Assert.assertEquals(15, pageData.getData().size());

TextPageData<Asset> pageData1 = doGetTypedWithPageLink("/api/datamodelobject/assets/"+ dataModelObject.getId().getId().toString()+"?",
new TypeReference<TextPageData<Asset>>(){}, pageData.getNextPageLink());
Assert.assertEquals(5, pageData1.getData().size());
logout();

unAssignUserFromGroup(savedCustomerGroup.getId(), customerUserId);

logout();
loginTenantAdmin();
int i = 0;
List<String> policies1 = new ArrayList<>();
while (i < 20){
Asset asset_i = createAsset(dataModelObject.getId(), customerUser.getCustomerId(), "Tenant's asset "+ i);
String policy_i = String.format("CUSTOMER_USER:ASSET?%s=%s&%s=%s:READ",
UserPermission.ResourceAttribute.ID, asset_i.getId().getId().toString(),
UserPermission.ResourceAttribute.DATA_MODEL_ID, dataModelObject.getId().getId().toString());
if(i % 2 == 0)
policies1.add(policy_i);
i++;

}
CustomerGroup savedCustomerGroup1 = createGroupWithPolicies(policies1, customerUser.getCustomerId(), "My Customer Group New");

String getPolicyUrl1 = "/api/customer/group/" + savedCustomerGroup1.getId().getId().toString() + "/policy";
doGet(getPolicyUrl1).andExpect(status().isOk());
assignUserToGroup(customerUserId, savedCustomerGroup1);

logout();
loginCustomerUser();
TextPageLink pageLink11 = new TextPageLink(7);
TextPageData<Asset> pageData11 = doGetTypedWithPageLink("/api/datamodelobject/assets/"+ dataModelObject.getId().getId().toString()+"?",
new TypeReference<TextPageData<Asset>>(){}, pageLink11);
Assert.assertEquals(7, pageData11.getData().size());

TextPageData<Asset> pageData12 = doGetTypedWithPageLink("/api/datamodelobject/assets/"+ dataModelObject.getId().getId().toString()+"?",
new TypeReference<TextPageData<Asset>>(){}, pageData11.getNextPageLink());
Assert.assertEquals(3, pageData12.getData().size());
logout();

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ public void testGetDisplayablePoliciesForGroup() throws Exception {
policyForAll
);

CustomerGroup savedCustomerGroup = createGroupWithPolicies(policies, customerId);
CustomerGroup savedCustomerGroup = createGroupWithPolicies(policies, customerId, "My Customer Group");
String groupId = savedCustomerGroup.getId().getId().toString();
final Map<String, Map<String, String>> displayablePolicies = getDisplayablePolicies(groupId);
Assert.assertThat("List equality without order",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import com.google.common.util.concurrent.ListenableFuture;
import com.hashmapinc.server.common.data.Customer;
import com.hashmapinc.server.common.data.EntitySubtype;
import com.hashmapinc.server.common.data.TempusResourceCriteriaSpec;
import com.hashmapinc.server.common.data.asset.Asset;
import com.hashmapinc.server.common.data.audit.ActionType;
import com.hashmapinc.server.common.data.id.DataModelObjectId;
Expand Down Expand Up @@ -329,14 +330,20 @@ public List<EntitySubtype> getAssetTypes() throws TempusException {
}
}

@PostFilter("hasPermission(filterObject, 'ASSET_READ')")
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@GetMapping(value = "/datamodelobject/assets/{dataModelObjectId}")
@ResponseBody
public List<Asset> getAssetsByDataModelObjectId(@PathVariable(DATA_MODEL_OBJECT_ID) String strDataModelObjectId) throws TempusException {
public TextPageData<Asset> getAssetsByDataModelObjectId(@PathVariable(DATA_MODEL_OBJECT_ID) String strDataModelObjectId,
@RequestParam int limit,
@RequestParam(required = false) String textSearch,
@RequestParam(required = false) String idOffset,
@RequestParam(required = false) String textOffset) throws TempusException {
checkParameter(DATA_MODEL_OBJECT_ID, strDataModelObjectId);
try {
DataModelObjectId dataModelObjectId = new DataModelObjectId(toUUID(strDataModelObjectId));
return assetService.findAssetsByDataModelObjectId(dataModelObjectId);
final TempusResourceCriteriaSpec tempusResourceCriteriaSpec = getTempusResourceCriteriaSpec(getCurrentUser(), EntityType.ASSET, dataModelObjectId);
TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset);
return assetService.findAll(tempusResourceCriteriaSpec, pageLink);
} catch (Exception e) {
throw handleException(e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,10 @@
import javax.mail.MessagingException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.*;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static com.hashmapinc.server.dao.service.Validator.validateId;

Expand All @@ -89,6 +90,7 @@ public abstract class BaseController {
public static final String INCORRECT_TENANT_ID = "Incorrect tenantId ";
public static final String YOU_DON_T_HAVE_PERMISSION_TO_PERFORM_THIS_OPERATION = "You don't have permission to perform this operation!";
private static final String ITEM_NOT_FOUND = "Requested item wasn't found!";
public static final String NO_PERMISSION_TO_READ = "You don't have permission to read!";

@Autowired
private TempusErrorResponseHandler errorResponseHandler;
Expand Down Expand Up @@ -656,5 +658,97 @@ I extends UUIDBased & EntityId> void logEntityAction(I entityId, E entity, Custo
auditLogService.logEntityAction(user.getTenantId(), customerId, user.getId(), user.getName(), entityId, entity, actionType, e, additionalInfo);
}

protected TempusResourceCriteriaSpec getTempusResourceCriteriaSpec(SecurityUser user, EntityType entityType, DataModelObjectId dataModelObjectId) throws TempusException{
final TempusResourceCriteriaSpec tempusResourceCriteriaSpec = new TempusResourceCriteriaSpec(entityType, user.getTenantId(), dataModelObjectId);

if(isCustomerUser(user)){
tempusResourceCriteriaSpec.setCustomerId(Optional.of(user.getCustomerId()));
}
final Supplier<Stream<UserPermission>> readableAndResourceAccessibleStream = getReadableAndResourceAccessibleStream(user, entityType);

final boolean hasPermOnAllResources =
readableAndResourceAccessibleStream.get().map(UserPermission::getResourceAttributes).anyMatch(Objects::isNull); //case: {USER}:*:READ

final boolean hasHierarchicalPermOnEntireDmo =
hasHierarchicalPermOnEntireDataModel(readableAndResourceAccessibleStream, dataModelObjectId); // case: {USER}:ASSET?dataModelId={dmoid}:READ where dmoid in (parent of given dmoid)

if(!hasPermOnAllResources && !hasHierarchicalPermOnEntireDmo){
final boolean hasPermOnDmoWithResources =
hasPermOnDmoWithResources(readableAndResourceAccessibleStream, dataModelObjectId); // case: {USER}:ASSET?dataModelId={dmoid}&id={resId}:READ where dmoid = dataModelObjectId
if(!hasPermOnDmoWithResources) {
throw new IncorrectParameterException(NO_PERMISSION_TO_READ);
} else {
final Set<EntityId> accessibleResourceIdsForGivenDmo = getAccessibleResourceIdsForGivenDmo(readableAndResourceAccessibleStream, entityType, dataModelObjectId);
tempusResourceCriteriaSpec.setAccessibleIdsForGivenDataModelObject(accessibleResourceIdsForGivenDmo);
}
}
tempusResourceCriteriaSpec.setDataModelObjectId(dataModelObjectId);
return tempusResourceCriteriaSpec;
}

private Supplier<Stream<UserPermission>> getReadableAndResourceAccessibleStream(SecurityUser user, EntityType entityType) {
return () -> user.getUserPermissions()
.stream()
.filter(userPermission -> userPermission.getUserActions().contains(UserAction.READ))
.filter(userPermission -> userPermission.getResources().contains(entityType));
}

private Set<EntityId> getAccessibleResourceIdsForGivenDmo(Supplier<Stream<UserPermission>> userPermissionStream, EntityType entityType, DataModelObjectId dataModelObjectId) {
return userPermissionStream.get()
.map(UserPermission::getResourceAttributes)
.filter(Objects::nonNull)
.filter(resourceAttributes -> hasResourceIdsForGivenDmo(dataModelObjectId, resourceAttributes))
.map(resourceAttributes -> getResourceIdByType(entityType, toUUID(resourceAttributes.get(UserPermission.ResourceAttribute.ID))))
.filter(Objects::nonNull)
.collect(Collectors.toSet());
}

private EntityId getResourceIdByType(EntityType entityType, UUID resourceUUID) {
if(entityType.equals(EntityType.ASSET)){
return new AssetId(resourceUUID);
} else if(entityType.equals(EntityType.DEVICE)){
return new DeviceId(resourceUUID);
}
return null;
}

private boolean hasResourceIdsForGivenDmo(DataModelObjectId dataModelObjectId, Map<UserPermission.ResourceAttribute, String> resourceAttributes) {
if(resourceAttributes.containsKey(UserPermission.ResourceAttribute.DATA_MODEL_ID)
&& resourceAttributes.containsKey(UserPermission.ResourceAttribute.ID)){
final DataModelObjectId accessibleDmoid = new DataModelObjectId(toUUID(resourceAttributes.get(UserPermission.ResourceAttribute.DATA_MODEL_ID)));
return dataModelObjectId.equals(accessibleDmoid);
}
return false;
}

private boolean hasHierarchicalPermOnEntireDataModel(Supplier<Stream<UserPermission>> userPermissionStream, DataModelObjectId dataModelObjectId) {
Set<DataModelObjectId> parentDataModelIds = dataModelObjectService.getAllParentDataModelIdsOf(dataModelObjectId);
return userPermissionStream.get().map(UserPermission::getResourceAttributes).filter(Objects::nonNull).anyMatch(resourceAttributes -> {
boolean isAccessedToDmoidOnly = resourceAttributes.containsKey(UserPermission.ResourceAttribute.DATA_MODEL_ID)
&& !resourceAttributes.containsKey(UserPermission.ResourceAttribute.ID);

if(isAccessedToDmoidOnly){
final DataModelObjectId accessibleDmoid = new DataModelObjectId(toUUID(resourceAttributes.get(UserPermission.ResourceAttribute.DATA_MODEL_ID)));
return dataModelObjectId.equals(accessibleDmoid) || parentDataModelIds.contains(accessibleDmoid);
}
return false;
});
}

private boolean hasPermOnDmoWithResources(Supplier<Stream<UserPermission>> userPermissionStream, DataModelObjectId dataModelObjectId) {

return userPermissionStream.get().map(UserPermission::getResourceAttributes).filter(Objects::nonNull).anyMatch(resourceAttributes -> {
boolean isDmoidPresent = resourceAttributes.containsKey(UserPermission.ResourceAttribute.DATA_MODEL_ID);
if(isDmoidPresent){
final DataModelObjectId accessibleDmoid = new DataModelObjectId(toUUID(resourceAttributes.get(UserPermission.ResourceAttribute.DATA_MODEL_ID)));
return dataModelObjectId.equals(accessibleDmoid);
}
return false;
});
}

private boolean isCustomerUser(SecurityUser user) {
return user.getAuthorities().stream().anyMatch(grantedAuthority -> grantedAuthority.getAuthority().equals(Authority.CUSTOMER_USER.name()));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/**
* Copyright © 2016-2018 The Thingsboard Authors
* Modifications © 2017-2018 Hashmap, Inc
*
* 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 com.hashmapinc.server.common.data;

import com.hashmapinc.server.common.data.id.CustomerId;
import com.hashmapinc.server.common.data.id.DataModelObjectId;
import com.hashmapinc.server.common.data.id.EntityId;
import com.hashmapinc.server.common.data.id.TenantId;
import lombok.Data;

import java.util.HashSet;
import java.util.Optional;
import java.util.Set;

@Data
public class TempusResourceCriteriaSpec {
private EntityType entityType;
private TenantId tenantId;
private DataModelObjectId dataModelObjectId;
private Optional<CustomerId> customerId = Optional.empty();
private Set<? extends EntityId> accessibleIdsForGivenDataModelObject = new HashSet<>();
public TempusResourceCriteriaSpec(EntityType entityType, TenantId tenantId, DataModelObjectId dataModelObjectId) {
this.entityType = entityType;
this.tenantId = tenantId;
this.dataModelObjectId = dataModelObjectId;
}
}
24 changes: 24 additions & 0 deletions dao/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,14 @@
<groupId>com.hashmapinc.common</groupId>
<artifactId>message</artifactId>
</dependency>
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-apt</artifactId>
</dependency>
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-jpa</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
Expand Down Expand Up @@ -239,6 +247,22 @@
</execution>
</executions>
</plugin>
<plugin>
<groupId>com.mysema.maven</groupId>
<artifactId>apt-maven-plugin</artifactId>
<version>${apt-maven-plugin.version}</version>
<executions>
<execution>
<goals>
<goal>process</goal>
</goals>
<configuration>
<outputDirectory>target/generated-sources/java</outputDirectory>
<processor>com.querydsl.apt.jpa.JPAAnnotationProcessor</processor>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@

@RunWith(ClasspathSuite.class)
@ClasspathSuite.ClassnameFilters({
"com.hashmapinc.server.dao.service.*ServiceNoSqlTest"
"com.hashmapinc.server.dao.service.nosql.*ServiceNoSqlTest"
})
public class NoSqlIntegrationTestSuite {

Expand Down
Loading

0 comments on commit 8fcf72b

Please sign in to comment.