diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice/src/main/java/org/apache/hadoop/yarn/server/timelineservice/reader/TimelineFromIdConverter.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice/src/main/java/org/apache/hadoop/yarn/server/timelineservice/reader/TimelineFromIdConverter.java new file mode 100644 index 0000000000..5f5f0b14d1 --- /dev/null +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice/src/main/java/org/apache/hadoop/yarn/server/timelineservice/reader/TimelineFromIdConverter.java @@ -0,0 +1,93 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.hadoop.yarn.server.timelineservice.reader; + +import java.util.List; + +/** + * Used for decoding FROM_ID + */ +enum TimelineFromIdConverter { + + APPLICATION_FROMID { + @Override TimelineReaderContext decodeUID(String fromId) throws Exception { + if (fromId == null) { + return null; + } + + List appTupleList = TimelineReaderUtils.split(fromId); + if (appTupleList == null || appTupleList.size() != 5) { + throw new IllegalArgumentException( + "Invalid row key for application table."); + } + + return new TimelineReaderContext(appTupleList.get(0), appTupleList.get(1), + appTupleList.get(2), Long.parseLong(appTupleList.get(3)), + appTupleList.get(4), null, null); + } + }, + + SUB_APPLICATION_ENTITY_FROMID { + @Override TimelineReaderContext decodeUID(String fromId) throws Exception { + if (fromId == null) { + return null; + } + List split = TimelineReaderUtils.split(fromId); + if (split == null || split.size() != 6) { + throw new IllegalArgumentException( + "Invalid row key for sub app table."); + } + + String subAppUserId = split.get(0); + String clusterId = split.get(1); + String entityType = split.get(2); + Long entityIdPrefix = Long.valueOf(split.get(3)); + String entityId = split.get(4); + String userId = split.get(5); + return new TimelineReaderContext(clusterId, userId, null, null, null, + entityType, entityIdPrefix, entityId, subAppUserId); + } + }, + + GENERIC_ENTITY_FROMID { + @Override TimelineReaderContext decodeUID(String fromId) throws Exception { + if (fromId == null) { + return null; + } + List split = TimelineReaderUtils.split(fromId); + if (split == null || split.size() != 8) { + throw new IllegalArgumentException("Invalid row key for entity table."); + } + Long flowRunId = Long.valueOf(split.get(3)); + Long entityIdPrefix = Long.valueOf(split.get(6)); + return new TimelineReaderContext(split.get(0), split.get(1), split.get(2), + flowRunId, split.get(4), split.get(5), entityIdPrefix, split.get(7)); + } + }; + + /** + * Decodes FROM_ID depending on FROM_ID implementation. + * + * @param fromId FROM_ID to be decoded. + * @return a {@link TimelineReaderContext} object if FROM_ID passed can be + * decoded, null otherwise. + * @throws Exception if any problem occurs while decoding. + */ + abstract TimelineReaderContext decodeUID(String fromId) throws Exception; +} diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice/src/main/java/org/apache/hadoop/yarn/server/timelineservice/reader/TimelineReaderWebServices.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice/src/main/java/org/apache/hadoop/yarn/server/timelineservice/reader/TimelineReaderWebServices.java index 7bf66b0bd7..7f96bfb637 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice/src/main/java/org/apache/hadoop/yarn/server/timelineservice/reader/TimelineReaderWebServices.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice/src/main/java/org/apache/hadoop/yarn/server/timelineservice/reader/TimelineReaderWebServices.java @@ -55,6 +55,7 @@ import org.apache.hadoop.yarn.server.timelineservice.storage.TimelineReader.Field; import org.apache.hadoop.yarn.util.timeline.TimelineUtils; import org.apache.hadoop.yarn.webapp.BadRequestException; +import org.apache.hadoop.yarn.webapp.ForbiddenException; import org.apache.hadoop.yarn.webapp.NotFoundException; import com.google.common.annotations.VisibleForTesting; @@ -188,6 +189,8 @@ private static void handleException(Exception e, String url, long startTime, "Filter Parsing failed." : e.getMessage()); } else if (e instanceof BadRequestException) { throw (BadRequestException)e; + } else if (e instanceof ForbiddenException) { + throw (ForbiddenException) e; } else { LOG.error("Error while processing REST request", e); throw new WebApplicationException(e, @@ -339,6 +342,7 @@ public Set getEntities( TimelineReaderWebServicesUtils.createTimelineDataToRetrieve( confsToRetrieve, metricsToRetrieve, fields, metricsLimit, metricsTimeStart, metricsTimeEnd)); + checkAccessForGenericEntities(entities, callerUGI, entityType); } catch (Exception e) { handleException(e, url, startTime, "createdTime start/end or limit or flowrunid"); @@ -607,13 +611,15 @@ public Set getEntities( .createTimelineReaderContext(clusterId, userId, flowName, flowRunId, appId, entityType, null, null); entities = timelineReaderManager.getEntities(context, - TimelineReaderWebServicesUtils.createTimelineEntityFilters( - limit, createdTimeStart, createdTimeEnd, relatesTo, isRelatedTo, - infofilters, conffilters, metricfilters, eventfilters, - fromId), - TimelineReaderWebServicesUtils.createTimelineDataToRetrieve( - confsToRetrieve, metricsToRetrieve, fields, metricsLimit, - metricsTimeStart, metricsTimeEnd)); + TimelineReaderWebServicesUtils + .createTimelineEntityFilters(limit, createdTimeStart, + createdTimeEnd, relatesTo, isRelatedTo, infofilters, + conffilters, metricfilters, eventfilters, fromId), + TimelineReaderWebServicesUtils + .createTimelineDataToRetrieve(confsToRetrieve, metricsToRetrieve, + fields, metricsLimit, metricsTimeStart, metricsTimeEnd)); + + checkAccessForGenericEntities(entities, callerUGI, entityType); } catch (Exception e) { handleException(e, url, startTime, "createdTime start/end or limit or flowrunid"); @@ -704,6 +710,7 @@ public TimelineEntity getEntity( TimelineReaderWebServicesUtils.createTimelineDataToRetrieve( confsToRetrieve, metricsToRetrieve, fields, metricsLimit, metricsTimeStart, metricsTimeEnd)); + checkAccessForGenericEntity(entity, callerUGI); } catch (Exception e) { handleException(e, url, startTime, "flowrunid"); } @@ -893,6 +900,7 @@ public TimelineEntity getEntity( TimelineReaderWebServicesUtils.createTimelineDataToRetrieve( confsToRetrieve, metricsToRetrieve, fields, metricsLimit, metricsTimeStart, metricsTimeEnd)); + checkAccessForGenericEntity(entity, callerUGI); } catch (Exception e) { handleException(e, url, startTime, "flowrunid"); } @@ -956,6 +964,8 @@ public TimelineEntity getFlowRun( if (context == null) { throw new BadRequestException("Incorrect UID " + uId); } + // TODO to be removed or modified once ACL story is played + checkAccess(timelineReaderManager, callerUGI, context.getUserId()); context.setEntityType(TimelineEntityType.YARN_FLOW_RUN.toString()); entity = timelineReaderManager.getEntity(context, TimelineReaderWebServicesUtils.createTimelineDataToRetrieve( @@ -1063,12 +1073,16 @@ public TimelineEntity getFlowRun( TimelineReaderManager timelineReaderManager = getTimelineReaderManager(); TimelineEntity entity = null; try { - entity = timelineReaderManager.getEntity( - TimelineReaderWebServicesUtils.createTimelineReaderContext( - clusterId, userId, flowName, flowRunId, null, - TimelineEntityType.YARN_FLOW_RUN.toString(), null, null), - TimelineReaderWebServicesUtils.createTimelineDataToRetrieve( - null, metricsToRetrieve, null, null, null, null)); + TimelineReaderContext context = TimelineReaderWebServicesUtils + .createTimelineReaderContext(clusterId, userId, flowName, flowRunId, + null, TimelineEntityType.YARN_FLOW_RUN.toString(), null, null); + // TODO to be removed or modified once ACL story is played + checkAccess(timelineReaderManager, callerUGI, context.getUserId()); + + entity = timelineReaderManager.getEntity(context, + TimelineReaderWebServicesUtils + .createTimelineDataToRetrieve(null, metricsToRetrieve, null, null, + null, null)); } catch (Exception e) { handleException(e, url, startTime, "flowrunid"); } @@ -1156,6 +1170,8 @@ public Set getFlowRuns( if (context == null) { throw new BadRequestException("Incorrect UID " + uId); } + // TODO to be removed or modified once ACL story is played + checkAccess(timelineReaderManager, callerUGI, context.getUserId()); context.setEntityType(TimelineEntityType.YARN_FLOW_RUN.toString()); entities = timelineReaderManager.getEntities(context, TimelineReaderWebServicesUtils.createTimelineEntityFilters( @@ -1304,15 +1320,21 @@ public Set getFlowRuns( TimelineReaderManager timelineReaderManager = getTimelineReaderManager(); Set entities = null; try { - entities = timelineReaderManager.getEntities( - TimelineReaderWebServicesUtils.createTimelineReaderContext( - clusterId, userId, flowName, null, null, - TimelineEntityType.YARN_FLOW_RUN.toString(), null, null), - TimelineReaderWebServicesUtils.createTimelineEntityFilters( - limit, createdTimeStart, createdTimeEnd, null, null, null, - null, null, null, fromId), - TimelineReaderWebServicesUtils.createTimelineDataToRetrieve( - null, metricsToRetrieve, fields, null, null, null)); + TimelineReaderContext timelineReaderContext = TimelineReaderWebServicesUtils + .createTimelineReaderContext(clusterId, userId, flowName, null, + null, TimelineEntityType.YARN_FLOW_RUN.toString(), null, + null); + // TODO to be removed or modified once ACL story is played + checkAccess(timelineReaderManager, callerUGI, + timelineReaderContext.getUserId()); + + entities = timelineReaderManager.getEntities(timelineReaderContext, + TimelineReaderWebServicesUtils + .createTimelineEntityFilters(limit, createdTimeStart, + createdTimeEnd, null, null, null, null, null, null, fromId), + TimelineReaderWebServicesUtils + .createTimelineDataToRetrieve(null, metricsToRetrieve, fields, + null, null, null)); } catch (Exception e) { handleException(e, url, startTime, "createdTime start/end or limit or fromId"); @@ -1435,7 +1457,6 @@ public Set getFlows( long startTime = Time.monotonicNow(); init(res); TimelineReaderManager timelineReaderManager = getTimelineReaderManager(); - Configuration config = timelineReaderManager.getConfig(); Set entities = null; try { DateRange range = parseDateRange(dateRange); @@ -1455,19 +1476,9 @@ public Set getFlows( long endTime = Time.monotonicNow(); if (entities == null) { entities = Collections.emptySet(); - } else if (isDisplayEntityPerUserFilterEnabled(config)) { - Set userEntities = new LinkedHashSet<>(); - userEntities.addAll(entities); - for (TimelineEntity entity : userEntities) { - if (entity.getInfo() != null) { - String userId = - (String) entity.getInfo().get(FlowActivityEntity.USER_INFO_KEY); - if (!validateAuthUserWithEntityUser(timelineReaderManager, callerUGI, - userId)) { - entities.remove(entity); - } - } - } + } else { + checkAccess(timelineReaderManager, callerUGI, entities, + FlowActivityEntity.USER_INFO_KEY, true); } LOG.info("Processed URL " + url + " (Took " + (endTime - startTime) + " ms.)"); @@ -1552,6 +1563,7 @@ public TimelineEntity getApp( TimelineReaderWebServicesUtils.createTimelineDataToRetrieve( confsToRetrieve, metricsToRetrieve, fields, metricsLimit, metricsTimeStart, metricsTimeEnd)); + checkAccessForAppEntity(entity, callerUGI); } catch (Exception e) { handleException(e, url, startTime, "flowrunid"); } @@ -1722,6 +1734,7 @@ public TimelineEntity getApp( TimelineReaderWebServicesUtils.createTimelineDataToRetrieve( confsToRetrieve, metricsToRetrieve, fields, metricsLimit, metricsTimeStart, metricsTimeEnd)); + checkAccessForAppEntity(entity, callerUGI); } catch (Exception e) { handleException(e, url, startTime, "flowrunid"); } @@ -1852,6 +1865,8 @@ public Set getFlowRunApps( if (context == null) { throw new BadRequestException("Incorrect UID " + uId); } + // TODO to be removed or modified once ACL story is played + checkAccess(timelineReaderManager, callerUGI, context.getUserId()); context.setEntityType(TimelineEntityType.YARN_APPLICATION.toString()); entities = timelineReaderManager.getEntities(context, TimelineReaderWebServicesUtils.createTimelineEntityFilters( @@ -3343,6 +3358,7 @@ public Set getSubAppEntities( TimelineReaderWebServicesUtils.createTimelineDataToRetrieve( confsToRetrieve, metricsToRetrieve, fields, metricsLimit, metricsTimeStart, metricsTimeEnd)); + checkAccessForSubAppEntities(entities,callerUGI); } catch (Exception e) { handleException(e, url, startTime, "createdTime start/end or limit"); @@ -3410,6 +3426,7 @@ public Set getSubAppEntities(@Context HttpServletRequest req, TimelineReaderWebServicesUtils.createTimelineDataToRetrieve( confsToRetrieve, metricsToRetrieve, fields, metricsLimit, metricsTimeStart, metricsTimeEnd)); + checkAccessForSubAppEntities(entities,callerUGI); } catch (Exception e) { handleException(e, url, startTime, ""); } @@ -3422,7 +3439,7 @@ public Set getSubAppEntities(@Context HttpServletRequest req, return entities; } - private boolean isDisplayEntityPerUserFilterEnabled(Configuration config) { + static boolean isDisplayEntityPerUserFilterEnabled(Configuration config) { return !config .getBoolean(YarnConfiguration.TIMELINE_SERVICE_READ_AUTH_ENABLED, YarnConfiguration.DEFAULT_TIMELINE_SERVICE_READ_AUTH_ENABLED) @@ -3430,8 +3447,76 @@ private boolean isDisplayEntityPerUserFilterEnabled(Configuration config) { .getBoolean(YarnConfiguration.FILTER_ENTITY_LIST_BY_USER, false); } + // TODO to be removed or modified once ACL story is played + private void checkAccessForSubAppEntities(Set entities, + UserGroupInformation callerUGI) throws Exception { + if (entities != null && entities.size() > 0 + && isDisplayEntityPerUserFilterEnabled( + getTimelineReaderManager().getConfig())) { + TimelineReaderContext timelineReaderContext = null; + TimelineEntity entity = entities.iterator().next(); + String fromId = + (String) entity.getInfo().get(TimelineReaderUtils.FROMID_KEY); + timelineReaderContext = + TimelineFromIdConverter.SUB_APPLICATION_ENTITY_FROMID + .decodeUID(fromId); + checkAccess(getTimelineReaderManager(), callerUGI, + timelineReaderContext.getDoAsUser()); + } + } + + // TODO to be removed or modified once ACL story is played + private void checkAccessForAppEntity(TimelineEntity entity, + UserGroupInformation callerUGI) throws Exception { + if (entity != null && isDisplayEntityPerUserFilterEnabled( + getTimelineReaderManager().getConfig())) { + String fromId = + (String) entity.getInfo().get(TimelineReaderUtils.FROMID_KEY); + TimelineReaderContext timelineReaderContext = + TimelineFromIdConverter.APPLICATION_FROMID.decodeUID(fromId); + checkAccess(getTimelineReaderManager(), callerUGI, + timelineReaderContext.getUserId()); + } + } + + // TODO to be removed or modified once ACL story is played + private void checkAccessForGenericEntity(TimelineEntity entity, + UserGroupInformation callerUGI) throws Exception { + if (entity != null && isDisplayEntityPerUserFilterEnabled( + getTimelineReaderManager().getConfig())) { + String fromId = + (String) entity.getInfo().get(TimelineReaderUtils.FROMID_KEY); + TimelineReaderContext timelineReaderContext = + TimelineFromIdConverter.GENERIC_ENTITY_FROMID.decodeUID(fromId); + checkAccess(getTimelineReaderManager(), callerUGI, + timelineReaderContext.getUserId()); + } + } + + // TODO to be removed or modified once ACL story is played + private void checkAccessForGenericEntities(Set entities, + UserGroupInformation callerUGI, String entityType) throws Exception { + if (entities != null && entities.size() > 0 + && isDisplayEntityPerUserFilterEnabled( + getTimelineReaderManager().getConfig())) { + TimelineReaderContext timelineReaderContext = null; + TimelineEntity entity = entities.iterator().next(); + String uid = + (String) entity.getInfo().get(TimelineReaderUtils.FROMID_KEY); + if (TimelineEntityType.YARN_APPLICATION.matches(entityType)) { + timelineReaderContext = + TimelineFromIdConverter.APPLICATION_FROMID.decodeUID(uid); + } else { + timelineReaderContext = + TimelineFromIdConverter.GENERIC_ENTITY_FROMID.decodeUID(uid); + } + checkAccess(getTimelineReaderManager(), callerUGI, + timelineReaderContext.getUserId()); + } + } + // TODO to be removed/modified once ACL story has played - private boolean validateAuthUserWithEntityUser( + static boolean validateAuthUserWithEntityUser( TimelineReaderManager readerManager, UserGroupInformation ugi, String entityUser) { String authUser = TimelineReaderWebServicesUtils.getUserName(ugi); @@ -3442,4 +3527,41 @@ private boolean validateAuthUserWithEntityUser( } return (readerManager.checkAccess(ugi) || authUser.equals(requestedUser)); } + + // TODO to be removed/modified once ACL story has played + static boolean checkAccess(TimelineReaderManager readerManager, + UserGroupInformation ugi, String entityUser) { + if (isDisplayEntityPerUserFilterEnabled(readerManager.getConfig())) { + if (!validateAuthUserWithEntityUser(readerManager, ugi, entityUser)) { + String userName = ugi.getShortUserName(); + String msg = "User " + userName + + " is not allowed to read TimelineService V2 data."; + throw new ForbiddenException(msg); + } + } + return true; + } + + // TODO to be removed or modified once ACL story is played + static void checkAccess(TimelineReaderManager readerManager, + UserGroupInformation callerUGI, Set entities, + String entityUserKey, boolean verifyForAllEntity) { + if (entities.size() > 0 && isDisplayEntityPerUserFilterEnabled( + readerManager.getConfig())) { + Set userEntities = new LinkedHashSet<>(); + userEntities.addAll(entities); + for (TimelineEntity entity : userEntities) { + if (entity.getInfo() != null) { + String userId = (String) entity.getInfo().get(entityUserKey); + if (!validateAuthUserWithEntityUser(readerManager, callerUGI, + userId)) { + entities.remove(entity); + if (!verifyForAllEntity) { + break; + } + } + } + } + } + } } diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice/src/test/java/org/apache/hadoop/yarn/server/timelineservice/reader/TestTimelineReaderWebServicesBasicAcl.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice/src/test/java/org/apache/hadoop/yarn/server/timelineservice/reader/TestTimelineReaderWebServicesBasicAcl.java new file mode 100644 index 0000000000..4239bf0460 --- /dev/null +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice/src/test/java/org/apache/hadoop/yarn/server/timelineservice/reader/TestTimelineReaderWebServicesBasicAcl.java @@ -0,0 +1,154 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.hadoop.yarn.server.timelineservice.reader; + +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.security.UserGroupInformation; +import org.apache.hadoop.yarn.api.records.timelineservice.TimelineEntity; +import org.apache.hadoop.yarn.conf.YarnConfiguration; +import org.apache.hadoop.yarn.webapp.ForbiddenException; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import java.util.LinkedHashSet; +import java.util.Set; + +public class TestTimelineReaderWebServicesBasicAcl { + + private TimelineReaderManager manager; + private static String adminUser = "admin"; + private static UserGroupInformation adminUgi = + UserGroupInformation.createRemoteUser(adminUser); + private Configuration config; + + @Before public void setUp() throws Exception { + config = new YarnConfiguration(); + } + + @After public void tearDown() throws Exception { + if (manager != null) { + manager.stop(); + manager = null; + } + config = null; + } + + @Test public void testTimelineReaderManagerAclsWhenDisabled() + throws Exception { + config.setBoolean(YarnConfiguration.YARN_ACL_ENABLE, false); + config.set(YarnConfiguration.YARN_ADMIN_ACL, adminUser); + manager = new TimelineReaderManager(null); + manager.init(config); + manager.start(); + + // when acls are disabled, always return true + Assert.assertTrue(manager.checkAccess(null)); + + // filter is disabled, so should return false + Assert.assertFalse( + TimelineReaderWebServices.isDisplayEntityPerUserFilterEnabled(config)); + } + + @Test public void testTimelineReaderManagerAclsWhenEnabled() + throws Exception { + Configuration config = new YarnConfiguration(); + config.setBoolean(YarnConfiguration.YARN_ACL_ENABLE, true); + config.setBoolean(YarnConfiguration.FILTER_ENTITY_LIST_BY_USER, true); + config.set(YarnConfiguration.YARN_ADMIN_ACL, adminUser); + manager = new TimelineReaderManager(null); + manager.init(config); + manager.start(); + + String user1 = "user1"; + String user2 = "user2"; + UserGroupInformation user1Ugi = + UserGroupInformation.createRemoteUser(user1); + UserGroupInformation user2Ugi = + UserGroupInformation.createRemoteUser(user2); + + // false because ugi is null + Assert.assertFalse(TimelineReaderWebServices + .validateAuthUserWithEntityUser(manager, null, user1)); + + // incoming ugi is admin asking for entity owner user1 + Assert.assertTrue( + TimelineReaderWebServices.checkAccess(manager, adminUgi, user1)); + + // incoming ugi is admin asking for entity owner user1 + Assert.assertTrue( + TimelineReaderWebServices.checkAccess(manager, adminUgi, user2)); + + // incoming ugi is non-admin i.e user1Ugi asking for entity owner user2 + try { + TimelineReaderWebServices.checkAccess(manager, user1Ugi, user2); + Assert.fail("user1Ugi is not allowed to view user2"); + } catch (ForbiddenException e) { + // expected + } + + // incoming ugi is non-admin i.e user2Ugi asking for entity owner user1 + try { + TimelineReaderWebServices.checkAccess(manager, user1Ugi, user2); + Assert.fail("user2Ugi is not allowed to view user1"); + } catch (ForbiddenException e) { + // expected + } + + String userKey = "user"; + // incoming ugi is admin asking for entities + Set entities = createEntities(10, userKey); + TimelineReaderWebServices + .checkAccess(manager, adminUgi, entities, userKey, true); + // admin is allowed to view other entities + Assert.assertTrue(entities.size() == 10); + + // incoming ugi is user1Ugi asking for entities + // only user1 entities are allowed to view + entities = createEntities(5, userKey); + TimelineReaderWebServices + .checkAccess(manager, user1Ugi, entities, userKey, true); + Assert.assertTrue(entities.size() == 1); + Assert + .assertEquals(user1, entities.iterator().next().getInfo().get(userKey)); + + // incoming ugi is user2Ugi asking for entities + // only user2 entities are allowed to view + entities = createEntities(8, userKey); + TimelineReaderWebServices + .checkAccess(manager, user2Ugi, entities, userKey, true); + Assert.assertTrue(entities.size() == 1); + Assert + .assertEquals(user2, entities.iterator().next().getInfo().get(userKey)); + } + + Set createEntities(int noOfUsers, String userKey) { + Set entities = new LinkedHashSet<>(); + for (int i = 0; i < noOfUsers; i++) { + TimelineEntity e = new TimelineEntity(); + e.setType("user" + i); + e.setId("user" + i); + e.getInfo().put(userKey, "user" + i); + entities.add(e); + } + return entities; + } + +}