Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update the group last modified date when updating a group [when unique group id is disabled] #590

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -289,16 +289,38 @@ public void addSCIMGroupAttributesToSCIMDisabledHybridRoles(int tenantId,
}
}

/**
* Update SCIM group attributes.
*
* @param tenantId Tenant id.
* @param roleName Group name.
* @param attributes Attributes to be updated.
* @throws IdentitySCIMException If an error occurred while updating the attributes.
*/
public void updateSCIMGroupAttributes(int tenantId, String roleName,
Map<String, String> attributes) throws IdentitySCIMException {

Connection connection = IdentityDatabaseUtil.getDBConnection();
doUpdateSCIMGroupAttributes(tenantId, roleName, attributes, SQLQueries.UPDATE_ATTRIBUTES_SQL);
}

/**
* Do update SCIM group attributes.
*
* @param tenantId Tenant id.
* @param roleName Group name.
* @param attributes Attributes to be updated.
* @param sqlQuery SQL query to update the attributes.
* @throws IdentitySCIMException If an error occurred while updating the attributes.
*/
private void doUpdateSCIMGroupAttributes(int tenantId, String roleName, Map<String, String> attributes,
String sqlQuery) throws IdentitySCIMException {

Connection connection = IdentityDatabaseUtil.getDBConnection(true);
PreparedStatement prepStmt = null;

if (isExistingGroup(SCIMCommonUtils.getGroupNameWithDomain(roleName), tenantId)) {
try {
prepStmt = connection.prepareStatement(SQLQueries.UPDATE_ATTRIBUTES_SQL);

prepStmt = connection.prepareStatement(sqlQuery);
prepStmt.setInt(2, tenantId);
prepStmt.setString(3, roleName);

Expand All @@ -308,19 +330,16 @@ public void updateSCIMGroupAttributes(int tenantId, String roleName,
prepStmt.setString(4, entry.getKey());
prepStmt.setString(1, entry.getValue());
prepStmt.addBatch();

} else {
throw new IdentitySCIMException("Error when adding SCIM Attribute: "
+ entry.getKey()
+ " An attribute with the same name doesn't exists.");
+ entry.getKey() + " An attribute with the same name doesn't exists.");
}
}
int[] return_count = prepStmt.executeBatch();
int[] returnCount = prepStmt.executeBatch();
if (log.isDebugEnabled()) {
log.debug("No. of records updated for updating SCIM Group : " + return_count.length);
log.debug("No. of records updated for updating SCIM Group : " + returnCount.length);
}
connection.commit();

} catch (SQLException e) {
throw new IdentitySCIMException("Error updating the SCIM Group Attributes.", e);
} finally {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,12 @@ public class SQLQueries {
public static final String CHECK_EXISTING_ATTRIBUTE_WITH_AUDIENCE_SQL =
"SELECT TENANT_ID, ROLE_NAME, ATTR_NAME FROM IDN_SCIM_GROUP WHERE IDN_SCIM_GROUP.TENANT_ID=? AND " +
"IDN_SCIM_GROUP.ROLE_NAME=? AND IDN_SCIM_GROUP.ATTR_NAME=? AND IDN_SCIM_GROUP.AUDIENCE_REF_ID=?";

public static final String UPDATE_ATTRIBUTES_SQL =
"UPDATE IDN_SCIM_GROUP SET UM_ATTR_VALUE=? WHERE TENANT_ID=? AND ROLE_NAME=? AND ATTR_NAME=?";
"UPDATE IDN_SCIM_GROUP SET ATTR_VALUE=? WHERE TENANT_ID=? AND ROLE_NAME=? AND ATTR_NAME=?";
public static final String UPDATE_GROUP_NAME_SQL =
"UPDATE IDN_SCIM_GROUP SET ROLE_NAME=? WHERE TENANT_ID=? AND ROLE_NAME=?";

public static final String DELETE_GROUP_SQL =
"DELETE FROM IDN_SCIM_GROUP WHERE TENANT_ID=? AND ROLE_NAME=?";
public static final String CHECK_EXISTING_ATTRIBUTE_SQL =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -335,4 +335,17 @@ public String[] getGroupListFromAttributeName(String attributeName, String searc
GroupDAO groupDAO = new GroupDAO();
return groupDAO.getGroupNameList(attributeName, searchAttribute, this.tenantId, domainName);
}

/**
* Update SCIM attributes of the group.
*
* @param groupName The display name of the group.
* @param attributes The attributes to be updated.
* @throws IdentitySCIMException IdentitySCIMException when updating the SCIM Group information.
*/
public void updateSCIMAttributes(String groupName, Map<String, String> attributes) throws IdentitySCIMException {

GroupDAO groupDAO = new GroupDAO();
groupDAO.updateSCIMGroupAttributes(tenantId, groupName, attributes);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3512,6 +3512,9 @@ private void doPatchGroup(String groupId, String currentGroupName, Map<String, L
List<PatchOperation> displayNameOperations = new ArrayList<>();
List<PatchOperation> memberOperations = new ArrayList<>();
String newGroupName = currentGroupName;
Date groupLastUpdatedTime = null;
String groupNameForIDNScim = SCIMCommonUtils.getGroupNameWithDomain(currentGroupName);

for (List<PatchOperation> patchOperationList : patchOperations.values()) {
for (PatchOperation patchOperation : patchOperationList) {
if (StringUtils.equals(SCIMConstants.GroupSchemaConstants.DISPLAY_NAME,
Expand All @@ -3528,7 +3531,14 @@ private void doPatchGroup(String groupId, String currentGroupName, Map<String, L

if (CollectionUtils.isNotEmpty(displayNameOperations)) {
newGroupName = (String) displayNameOperations.get(0).getValues();
setGroupDisplayName(groupId, currentGroupName, newGroupName);
String[] resolvedGroupNames = resolveGroupDomains(currentGroupName, newGroupName);
boolean groupUpdated = setGroupDisplayName(groupId, resolvedGroupNames[0], resolvedGroupNames[1]);
if (groupUpdated) {
groupLastUpdatedTime = new Date();
groupNameForIDNScim = resolvedGroupNames[1];
} else {
groupNameForIDNScim = resolvedGroupNames[0];
}
}

Collections.sort(memberOperations);
Expand Down Expand Up @@ -3614,10 +3624,20 @@ private void doPatchGroup(String groupId, String currentGroupName, Map<String, L
carbonUM.updateUserListOfRoleWithID(newGroupName,
deletedMemberIdsFromUserstore.toArray(new String[0]),
addedMemberIdsFromUserstore.toArray(new String[0]));
groupLastUpdatedTime = new Date();
}

// Update the group name in UM_HYBRID_GROUP_ROLE table.
carbonUM.updateGroupName(currentGroupName, newGroupName);

// Update the last modified time of the group.
if (!carbonUM.isUniqueGroupIdEnabled() && groupLastUpdatedTime != null) {
Map<String, String> attributes = new HashMap<>();
attributes.put(SCIMConstants.CommonSchemaConstants.LAST_MODIFIED_URI,
AttributeUtil.formatDateTime(groupLastUpdatedTime.toInstant()));
SCIMGroupHandler groupHandler = new SCIMGroupHandler(carbonUM.getTenantId());
groupHandler.updateSCIMAttributes(groupNameForIDNScim, attributes);
}
} catch (UserStoreException e) {
if (e instanceof org.wso2.carbon.user.core.UserStoreException && (StringUtils
.equals(UserCoreErrorConstants.ErrorMessages.ERROR_CODE_DUPLICATE_WHILE_WRITING_TO_DATABASE
Expand Down Expand Up @@ -3685,12 +3705,10 @@ private void prepareAddedRemovedMemberLists(Set<String> addedMembers, Set<String
}
}

private void setGroupDisplayName(String groupId, String oldGroupName, String newGroupName)
throws IdentityApplicationManagementException, CharonException, BadRequestException, IdentitySCIMException,
UserStoreException {
private String[] resolveGroupDomains(String oldGroupName, String newGroupName)
throws IdentityApplicationManagementException, CharonException, IdentitySCIMException {

String userStoreDomainFromSP = getUserStoreDomainFromSP();

String oldGroupDomain = IdentityUtil.extractDomainFromName(oldGroupName);
if (userStoreDomainFromSP != null && !userStoreDomainFromSP.equalsIgnoreCase(oldGroupDomain)) {
throw new CharonException("Group :" + oldGroupName + "is not belong to user store " +
Expand All @@ -3716,10 +3734,19 @@ private void setGroupDisplayName(String groupId, String oldGroupName, String new
oldGroupName = SCIMCommonUtils.getGroupNameWithDomain(oldGroupName);
newGroupName = SCIMCommonUtils.getGroupNameWithDomain(newGroupName);

return new String[] { oldGroupName, newGroupName };
}

private boolean setGroupDisplayName(String groupId, String oldGroupName, String newGroupName)
throws IdentityApplicationManagementException, IdentitySCIMException, UserStoreException {

if (!StringUtils.equals(oldGroupName, newGroupName)) {
// Update group name in carbon UM.
carbonUM.renameGroup(groupId, newGroupName);
return true;
}

return false;
}

@Override
Expand Down Expand Up @@ -3767,6 +3794,9 @@ public Group updateGroup(Group oldGroup, Group newGroup, Map<String, Boolean> re
public boolean doUpdateGroup(Group oldGroup, Group newGroup) throws CharonException, IdentitySCIMException,
BadRequestException, IdentityApplicationManagementException, org.wso2.carbon.user.core.UserStoreException {

Date groupLastUpdatedTime = null;
String groupNameForIDNScim = SCIMCommonUtils.getGroupNameWithDomain(oldGroup.getDisplayName());

setGroupDisplayName(oldGroup, newGroup);
if (log.isDebugEnabled()) {
log.debug("Updating group: " + oldGroup.getDisplayName());
Expand Down Expand Up @@ -3815,14 +3845,27 @@ public boolean doUpdateGroup(Group oldGroup, Group newGroup) throws CharonExcept
// Update group name in carbon UM
carbonUM.renameGroup(oldGroup.getId(), newGroupDisplayName);
updated = true;
groupLastUpdatedTime = new Date();
groupNameForIDNScim = SCIMCommonUtils.getGroupNameWithDomain(newGroupDisplayName);
}
// Update the group with added members and deleted members.
if (isNotEmpty(addedMembers) || isNotEmpty(deletedMembers)) {
carbonUM.updateUserListOfRoleWithID(newGroupDisplayName,
deletedMemberIdsFromUserstore.toArray(new String[0]),
addedMemberIdsFromUserstore.toArray(new String[0]));
updated = true;
groupLastUpdatedTime = new Date();
}

// Update the last modified time of the group.
if (!carbonUM.isUniqueGroupIdEnabled() && groupLastUpdatedTime != null) {
Map<String, String> attributes = new HashMap<>();
attributes.put(SCIMConstants.CommonSchemaConstants.LAST_MODIFIED_URI,
AttributeUtil.formatDateTime(groupLastUpdatedTime.toInstant()));
SCIMGroupHandler groupHandler = new SCIMGroupHandler(carbonUM.getTenantId());
groupHandler.updateSCIMAttributes(groupNameForIDNScim, attributes);
}

return updated;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
/*
* Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com).
*
* WSO2 LLC. 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.wso2.carbon.identity.scim2.common.DAO;

import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import org.wso2.carbon.identity.core.util.IdentityDatabaseUtil;
import org.wso2.carbon.identity.scim2.common.exceptions.IdentitySCIMException;
import org.wso2.carbon.identity.scim2.common.utils.SCIMCommonUtils;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;

import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.*;
import static org.mockito.MockitoAnnotations.initMocks;

public class GroupDAOTest {

@Mock
private Connection connection;

@Mock
private PreparedStatement mockedPreparedStatement;

@Mock
private ResultSet resultSet;

private MockedStatic<IdentityDatabaseUtil> identityDatabaseUtil;
private MockedStatic<SCIMCommonUtils> scimCommonUtils;

@BeforeMethod
public void setUp() {

initMocks(this);
identityDatabaseUtil = mockStatic(IdentityDatabaseUtil.class);
scimCommonUtils = mockStatic(SCIMCommonUtils.class);
}

@AfterMethod
public void tearDown() {

identityDatabaseUtil.close();
scimCommonUtils.close();
}

@Test
public void testUpdateSCIMGroupAttributes() throws Exception {

Map<String, String> attributes = new HashMap<>();
attributes.put("urn:ietf:params:scim:schemas:core:2.0:meta.created", "2017-10-10T10:10:10Z");
attributes.put("urn:ietf:params:scim:schemas:core:2.0:meta.lastModified", "2017-10-10T10:10:10Z");

when(IdentityDatabaseUtil.getDBConnection(true)).thenReturn(connection);
when(connection.prepareStatement(anyString())).thenReturn(mockedPreparedStatement);
when(mockedPreparedStatement.executeBatch()).thenReturn(new int[]{1});
when(resultSet.next()).thenReturn(true);
when(SCIMCommonUtils.getGroupNameWithDomain(anyString())).thenReturn("PRIMARY/GROUP_NAME");

Connection mockedConnection2 = mock(Connection.class);
PreparedStatement mockedPreparedStatement2 = mock(PreparedStatement.class);
ResultSet mockedResultSet2 = mock(ResultSet.class);
when(IdentityDatabaseUtil.getDBConnection()).thenReturn(mockedConnection2);
when(mockedConnection2.prepareStatement(anyString())).thenReturn(mockedPreparedStatement2);
when(mockedPreparedStatement2.executeQuery()).thenReturn(mockedResultSet2);
when(mockedResultSet2.next()).thenReturn(true);

GroupDAO groupDAO = spy(new GroupDAO());
doReturn(true).when(groupDAO).isExistingGroup(anyString(), anyInt());

groupDAO.updateSCIMGroupAttributes(1, "GROUP_NAME", attributes);
verify(mockedPreparedStatement, times(1)).executeBatch();
}

@Test(expectedExceptions = IdentitySCIMException.class,
expectedExceptionsMessageRegExp = "Error when updating SCIM Attributes for the group: GROUP_NAME " +
"A Group with the same name doesn't exists.")
public void testUpdateSCIMGroupAttributesWithNonExistingGroup() throws Exception {

Map<String, String> attributes = new HashMap<>();
attributes.put("urn:ietf:params:scim:schemas:core:2.0:meta.created", "2017-10-10T10:10:10Z");
attributes.put("urn:ietf:params:scim:schemas:core:2.0:meta.lastModified", "2017-10-10T10:10:10Z");

when(IdentityDatabaseUtil.getDBConnection(true)).thenReturn(connection);
when(SCIMCommonUtils.getGroupNameWithDomain(anyString())).thenReturn("PRIMARY/GROUP_NAME");

GroupDAO groupDAO = spy(new GroupDAO());
doReturn(false).when(groupDAO).isExistingGroup(anyString(), anyInt());
groupDAO.updateSCIMGroupAttributes(1, "GROUP_NAME", attributes);
}

@Test(expectedExceptions = IdentitySCIMException.class,
expectedExceptionsMessageRegExp = "Error when adding SCIM Attribute: nonExisting " +
"An attribute with the same name doesn't exists.")
public void testUpdateSCIMGroupAttributesWithNonExistingAttributes() throws Exception {

Map<String, String> attributes = new HashMap<>();
attributes.put("nonExisting", "test-value");

when(IdentityDatabaseUtil.getDBConnection(true)).thenReturn(connection);
when(connection.prepareStatement(anyString())).thenReturn(mockedPreparedStatement);
when(mockedPreparedStatement.executeBatch()).thenReturn(new int[]{1});
when(resultSet.next()).thenReturn(true);
when(SCIMCommonUtils.getGroupNameWithDomain(anyString())).thenReturn("PRIMARY/GROUP_NAME");

Connection mockedConnection2 = mock(Connection.class);
PreparedStatement mockedPreparedStatement2 = mock(PreparedStatement.class);
ResultSet mockedResultSet2 = mock(ResultSet.class);
when(IdentityDatabaseUtil.getDBConnection()).thenReturn(mockedConnection2);
when(mockedConnection2.prepareStatement(anyString())).thenReturn(mockedPreparedStatement2);
when(mockedPreparedStatement2.executeQuery()).thenReturn(mockedResultSet2);
when(mockedResultSet2.next()).thenReturn(false);

GroupDAO groupDAO = spy(new GroupDAO());
doReturn(true).when(groupDAO).isExistingGroup(anyString(), anyInt());
groupDAO.updateSCIMGroupAttributes(1, "GROUP_NAME", attributes);
}

@Test(expectedExceptions = IdentitySCIMException.class,
expectedExceptionsMessageRegExp = "Error updating the SCIM Group Attributes.")
public void testUpdateSCIMGroupAttributesWithSQLException() throws Exception {

Map<String, String> attributes = new HashMap<>();
attributes.put("urn:ietf:params:scim:schemas:core:2.0:meta.created", "2017-10-10T10:10:10Z");
attributes.put("urn:ietf:params:scim:schemas:core:2.0:meta.lastModified", "2017-10-10T10:10:10Z");

when(IdentityDatabaseUtil.getDBConnection(true)).thenReturn(connection);
when(connection.prepareStatement(anyString())).thenThrow(new SQLException());
when(SCIMCommonUtils.getGroupNameWithDomain(anyString())).thenReturn("PRIMARY/GROUP_NAME");

GroupDAO groupDAO = spy(new GroupDAO());
doReturn(true).when(groupDAO).isExistingGroup(anyString(), anyInt());
groupDAO.updateSCIMGroupAttributes(1, "GROUP_NAME", attributes);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -337,4 +337,17 @@ public void testListSCIMRoles() throws Exception {
assertNotNull(new SCIMGroupHandler(1).listSCIMRoles());
}

@Test
public void testUpdateSCIMAttributes() throws IdentitySCIMException {

Map<String, String> attributes = new HashMap<String, String>();
attributes.put("urn:ietf:params:scim:schemas:core:2.0:meta.created", "2017-10-10T10:10:10Z");
attributes.put("urn:ietf:params:scim:schemas:core:2.0:meta.lastModified", "2017-10-10T10:10:10Z");

try (MockedConstruction<GroupDAO> mockedConstruction = Mockito.mockConstruction(GroupDAO.class)) {
new SCIMGroupHandler(1).updateSCIMAttributes("GROUP_NAME", attributes);
verify(mockedConstruction.constructed().get(0))
.updateSCIMGroupAttributes(1, "GROUP_NAME", attributes);
}
}
}
Loading
Loading