Skip to content

Commit

Permalink
for notification emails expand groups and delegated role membership (A…
Browse files Browse the repository at this point in the history
…thenZ#2388)

Signed-off-by: Henry Avetisyan <[email protected]>
Co-authored-by: Henry Avetisyan <[email protected]>
  • Loading branch information
havetisyan and havetisyan authored Nov 2, 2023
1 parent 8da0c22 commit e620552
Show file tree
Hide file tree
Showing 17 changed files with 262 additions and 22 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ libs/nodejs/auth_core/package-lock.json
.envrc
.clover/
athenz-docker-build.log
syncers/auth_history_syncer/dynamodb-local-metadata.json

# Logs
logs
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,32 @@
package com.yahoo.athenz.common.server.db;

import com.yahoo.athenz.zms.Role;

import java.util.List;

/**
* A common interface used by ZMS and ZTS for providing roles by domain
*/
public interface RolesProvider {

/**
*
* @param domain name of the domain
* Return the full list of roles from the given domain
* @param domainName name of the domain
* @return List of roles from the domain
*/
List<Role> getRolesByDomain(String domain);
List<Role> getRolesByDomain(String domainName);

/**
* Return the requested role from the given domain. If the
* expand flag is set to true, the provider will automatically
* expand the role members and return the full list of members
* @param domainName name of the domain
* @param roleName name of the role
* @param auditLog flag to indicate to return audit log entries
* @param expand flag to indicate to expand group and delegated role membership
* @param pending flag to indicate to return pending members
* @return the role object from the given domain
*/
default Role getRole(String domainName, String roleName, Boolean auditLog, Boolean expand, Boolean pending) {
throw new UnsupportedOperationException();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,20 @@

package com.yahoo.athenz.common.server.notification;

import com.yahoo.athenz.auth.AuthorityConsts;
import com.yahoo.athenz.common.server.db.RolesProvider;

import com.yahoo.athenz.zms.Role;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class DomainRoleMembersFetcher {

private static final Logger LOGGER = LoggerFactory.getLogger(DomainRoleMembersFetcher.class);

private final RolesProvider rolesProvider;
private final DomainRoleMembersFetcherCommon domainRoleMembersFetcherCommon;

Expand All @@ -33,15 +39,32 @@ public DomainRoleMembersFetcher(RolesProvider rolesProvider, String userDomainPr
}

public Set<String> getDomainRoleMembers(String domainName, String roleName) {

if (rolesProvider == null) {
return new HashSet<>();
}

List<Role> roles = rolesProvider.getRolesByDomain(domainName);
if (roles == null) {
// we're going to use our new getRole interface api to get the
// role fully expanded with all its members. However, if the
// provider does not support this interface then we're going
// fall back to the old method of getting the role members

try {
// our given role name is the full arn, so first we need to
// extract the local role component from the role name

int idx = roleName.indexOf(AuthorityConsts.ROLE_SEP);
Role role = rolesProvider.getRole(domainName, roleName.substring(idx + AuthorityConsts.ROLE_SEP.length()),
Boolean.FALSE, Boolean.TRUE, Boolean.FALSE);
return domainRoleMembersFetcherCommon.getDomainRoleMembers(role);
} catch (Exception ex) {
if (ex instanceof UnsupportedOperationException) {
return domainRoleMembersFetcherCommon.getDomainRoleMembers(roleName,
rolesProvider.getRolesByDomain(domainName));
}
LOGGER.error("unable to fetch members for role: {} in domain: {} error: {}",
roleName, domainName, ex.getMessage());
return new HashSet<>();
}

return domainRoleMembersFetcherCommon.getDomainRoleMembers(roleName, roles);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,19 @@ public DomainRoleMembersFetcherCommon(String userDomainPrefix) {
this.userDomainPrefix = userDomainPrefix;
}

public Set<String> getDomainRoleMembers(Role role) {

if (role.getRoleMembers() == null) {
return new HashSet<>();
}

return role.getRoleMembers().stream()
.filter(this::isUnexpiredUser)
.map(RoleMember::getMemberName).collect(Collectors.toSet());
}

public Set<String> getDomainRoleMembers(String roleName, List<Role> roles) {

if (roles == null) {
return new HashSet<>();
}
Expand All @@ -42,13 +54,7 @@ public Set<String> getDomainRoleMembers(String roleName, List<Role> roles) {
}

if (role.getName().equals(roleName)) {
if (role.getRoleMembers() == null) {
return new HashSet<>();
}

return role.getRoleMembers().stream()
.filter(this::isUnexpiredUser)
.map(RoleMember::getMemberName).collect(Collectors.toSet());
return getDomainRoleMembers(role);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

package com.yahoo.athenz.common.server.notification;

import com.yahoo.athenz.common.server.db.RolesProvider;
import com.yahoo.athenz.common.server.rest.ResourceException;
import com.yahoo.athenz.zms.Role;
import com.yahoo.athenz.zms.RoleMember;
import com.yahoo.rdl.Timestamp;
Expand All @@ -28,6 +30,7 @@
import static org.testng.AssertJUnit.assertTrue;

public class DomainRoleMembersFetcherCommonTest {

@Test
public void testGetDomainRoleMembers() {
DomainRoleMembersFetcherCommon fetcherCommon = new DomainRoleMembersFetcherCommon(USER_DOMAIN_PREFIX);
Expand Down Expand Up @@ -65,6 +68,113 @@ public void testGetDomainRoleMembers() {

receivedMembers = fetcherCommon.getDomainRoleMembers("roleDoesntExist", rolesList);
assertEquals(new HashSet<>(), receivedMembers);

// if the role list is empty we get an empty set

assertEquals(new HashSet<>(), fetcherCommon.getDomainRoleMembers("role1", null));
}

@Test
public void testGetDomainRoleMembersFromRole() {
DomainRoleMembersFetcherCommon fetcherCommon = new DomainRoleMembersFetcherCommon(USER_DOMAIN_PREFIX);

long currentTimeInMillis = System.currentTimeMillis();
Timestamp futureTimeStamp = Timestamp.fromMillis(currentTimeInMillis + 100000);
Timestamp pastTimeStamp = Timestamp.fromMillis(currentTimeInMillis - 100000);

Role role1 = new Role();
role1.setName("role1");
RoleMember roleMember1 = new RoleMember().setMemberName("user.unexpiredUser").setExpiration(futureTimeStamp);
RoleMember roleMember2 = new RoleMember().setMemberName("user.expiredUser").setExpiration(pastTimeStamp);
RoleMember roleMember3 = new RoleMember().setMemberName("user.noExpiration");
RoleMember roleMember4 = new RoleMember().setMemberName("notProperUsername");
List<RoleMember> role1MemberList = new ArrayList<>(Arrays.asList(roleMember1, roleMember2, roleMember3, roleMember4));
role1.setRoleMembers(role1MemberList);

Set<String> receivedMembers = fetcherCommon.getDomainRoleMembers(role1);
assertEquals(2, receivedMembers.size());
assertTrue(receivedMembers.contains("user.unexpiredUser"));
assertTrue(receivedMembers.contains("user.noExpiration"));
}

@Test
public void testDomainRoleMembersFetcherNullProvider() {
DomainRoleMembersFetcher fetcher = new DomainRoleMembersFetcher(null, USER_DOMAIN_PREFIX);
assertEquals(new HashSet<>(), fetcher.getDomainRoleMembers("domain", "role"));
}

@Test
public void testDomainRoleMembersFetcherRole() {

Role role1 = new Role();
role1.setName("role1");
List<RoleMember> role1MemberList = Collections.singletonList(new RoleMember().setMemberName("user.user1"));
role1.setRoleMembers(role1MemberList);

RolesProvider provider = new RolesProvider() {
@Override
public List<Role> getRolesByDomain(String domainName) {
return null;
}
@Override
public Role getRole(String domainName, String roleName, Boolean auditLog, Boolean expand, Boolean pending) {
return role1;
}
};

DomainRoleMembersFetcher fetcher = new DomainRoleMembersFetcher(provider, USER_DOMAIN_PREFIX);
Set<String> users = fetcher.getDomainRoleMembers("domain1", "role1");
assertEquals(1, users.size());
assertTrue(users.contains("user.user1"));
}

@Test
public void testDomainRoleMembersFetcherNotImpl() {

Role role1 = new Role();
role1.setName("role1");
List<RoleMember> role1MemberList = Collections.singletonList(new RoleMember().setMemberName("user.user1"));
role1.setRoleMembers(role1MemberList);

List<Role> rolesList = new ArrayList<>();
rolesList.add(role1);

RolesProvider provider = new RolesProvider() {
@Override
public List<Role> getRolesByDomain(String domainName) {
return rolesList;
}
};

DomainRoleMembersFetcher fetcher = new DomainRoleMembersFetcher(provider, USER_DOMAIN_PREFIX);
Set<String> users = fetcher.getDomainRoleMembers("domain1", "role1");
assertEquals(1, users.size());
assertTrue(users.contains("user.user1"));
}

@Test
public void testDomainRoleMembersFetcherExc() {

Role role1 = new Role();
role1.setName("role1");
List<RoleMember> role1MemberList = Collections.singletonList(new RoleMember().setMemberName("user.user1"));
role1.setRoleMembers(role1MemberList);

List<Role> rolesList = new ArrayList<>();
rolesList.add(role1);

RolesProvider provider = new RolesProvider() {
@Override
public List<Role> getRolesByDomain(String domainName) {
return rolesList;
}
@Override
public Role getRole(String domainName, String roleName, Boolean auditLog, Boolean expand, Boolean pending) {
throw new ResourceException(400, "Invalid request");
}
};

DomainRoleMembersFetcher fetcher = new DomainRoleMembersFetcher(provider, USER_DOMAIN_PREFIX);
assertEquals(new HashSet<>(), fetcher.getDomainRoleMembers("domain1", "role1"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ public void testCreateNotificationException() {
NotificationToEmailConverter converter = Mockito.mock(NotificationToEmailConverter.class);
NotificationToMetricConverter metricConverter = Mockito.mock(NotificationToMetricConverter.class);
Notification notification = notificationCommon.createNotification(recipient, details, converter, metricConverter);
Mockito.verify(rolesProvider, Mockito.times(1)).getRolesByDomain(Mockito.any());
Mockito.verify(rolesProvider, Mockito.times(1)).getRole("test.domain", "admin", false, true, false);
assertNull(notification);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ public void testGetDomainRoleMembers() {
adminRole.setRoleMembers(Arrays.asList(roleMember1, roleMember2));
domainData.setRoles(Collections.singletonList(adminRole));
Mockito.when(dbsvc.getRolesByDomain(eq("domain1"))).thenReturn(domainData.getRoles());
Mockito.when(dbsvc.getRole("domain1", "admin", Boolean.FALSE, Boolean.TRUE, Boolean.FALSE))
.thenReturn(adminRole);

DomainRoleMembersFetcher domainRoleMembersFetcher = new DomainRoleMembersFetcher(
dbsvc,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,8 @@ public void testSendGroupMemberExpiryReminders() {
domain.setRoles(roles);

Mockito.when(dbsvc.getRolesByDomain("athenz1")).thenReturn(domain.getRoles());
Mockito.when(dbsvc.getRole("athenz1", "admin", Boolean.FALSE, Boolean.TRUE, Boolean.FALSE))
.thenReturn(adminRole);

List<Notification> notifications = new GroupMemberExpiryNotificationTask(dbsvc, USER_DOMAIN_PREFIX,
notificationToEmailConverterCommon, false).getNotifications();
Expand Down Expand Up @@ -421,6 +423,8 @@ public void testSendConsolidatedGroupMemberExpiryReminders() {
domain.setRoles(roles);

Mockito.when(dbsvc.getRolesByDomain("athenz1")).thenReturn(domain.getRoles());
Mockito.when(dbsvc.getRole("athenz1", "admin", Boolean.FALSE, Boolean.TRUE, Boolean.FALSE))
.thenReturn(adminRole);

List<Notification> notifications = new GroupMemberExpiryNotificationTask(dbsvc, USER_DOMAIN_PREFIX,
notificationToEmailConverterCommon, true).getNotifications();
Expand Down Expand Up @@ -467,6 +471,8 @@ public void testConsolidateGroupMembers() {
Role role = new Role().setName("athenz:role.admin").setRoleMembers(roleMembers);
athenzRoles.add(role);
Mockito.when(dbsvc.getRolesByDomain("athenz")).thenReturn(athenzRoles);
Mockito.when(dbsvc.getRole("athenz", "admin", Boolean.FALSE, Boolean.TRUE, Boolean.FALSE))
.thenReturn(role);

List<Role> sportsRoles = new ArrayList<>();
roleMembers = new ArrayList<>();
Expand All @@ -475,11 +481,15 @@ public void testConsolidateGroupMembers() {
role = new Role().setName("sports:role.admin").setRoleMembers(roleMembers);
sportsRoles.add(role);
Mockito.when(dbsvc.getRolesByDomain("sports")).thenReturn(sportsRoles);
Mockito.when(dbsvc.getRole("sports", "admin", Boolean.FALSE, Boolean.TRUE, Boolean.FALSE))
.thenReturn(role);

List<Role> weatherRoles = new ArrayList<>();
role = new Role().setName("weather:role.admin").setRoleMembers(new ArrayList<>());
weatherRoles.add(role);
Mockito.when(dbsvc.getRolesByDomain("weather")).thenReturn(weatherRoles);
Mockito.when(dbsvc.getRole("weather", "admin", Boolean.FALSE, Boolean.TRUE, Boolean.FALSE))
.thenReturn(role);

GroupMemberExpiryNotificationTask task = new GroupMemberExpiryNotificationTask(
dbsvc, USER_DOMAIN_PREFIX, new NotificationToEmailConverterCommon(null), true);
Expand Down Expand Up @@ -528,6 +538,8 @@ public void testConsolidateDomainMembers() {
Role role = new Role().setName("athenz:role.admin").setRoleMembers(roleMembers);
athenzRoles.add(role);
Mockito.when(dbsvc.getRolesByDomain("athenz")).thenReturn(athenzRoles);
Mockito.when(dbsvc.getRole("athenz", "admin", Boolean.FALSE, Boolean.TRUE, Boolean.FALSE))
.thenReturn(role);

List<Role> sportsRoles = new ArrayList<>();
roleMembers = new ArrayList<>();
Expand All @@ -536,11 +548,15 @@ public void testConsolidateDomainMembers() {
role = new Role().setName("sports:role.admin").setRoleMembers(roleMembers);
sportsRoles.add(role);
Mockito.when(dbsvc.getRolesByDomain("sports")).thenReturn(sportsRoles);
Mockito.when(dbsvc.getRole("sports", "admin", Boolean.FALSE, Boolean.TRUE, Boolean.FALSE))
.thenReturn(role);

List<Role> weatherRoles = new ArrayList<>();
role = new Role().setName("sports:role.admin").setRoleMembers(new ArrayList<>());
role = new Role().setName("weather:role.admin").setRoleMembers(new ArrayList<>());
weatherRoles.add(role);
Mockito.when(dbsvc.getRolesByDomain("weather")).thenReturn(weatherRoles);
Mockito.when(dbsvc.getRole("weather", "admin", Boolean.FALSE, Boolean.TRUE, Boolean.FALSE))
.thenReturn(role);

GroupMemberExpiryNotificationTask task = new GroupMemberExpiryNotificationTask(
dbsvc, USER_DOMAIN_PREFIX, new NotificationToEmailConverterCommon(null), true);
Expand Down
Loading

0 comments on commit e620552

Please sign in to comment.