Correct unique constraints for UserConsent entity

Closes #13045

Signed-off-by: cgeorgilakis-grnet <cgeorgilakis@admin.grnet.gr>
This commit is contained in:
Konstantinos Georgilakis 2022-10-06 13:26:02 +03:00 committed by Alexander Schwartz
parent 05056330dc
commit 4bca804d5a
4 changed files with 170 additions and 7 deletions

View file

@ -0,0 +1,67 @@
/*
* Copyright 2024 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* 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 org.keycloak.connections.jpa.updater.liquibase.custom;
import liquibase.exception.CustomChangeException;
import liquibase.statement.core.RawSqlStatement;
public class JpaUpdate25_0_0_ConsentConstraints extends CustomKeycloakTask {
@Override
protected void generateStatementsImpl() throws CustomChangeException {
final String userConsentClientScopeTable = getTableName("USER_CONSENT_CLIENT_SCOPE");
final String userConsentTable = getTableName("USER_CONSENT");
statements.add(new RawSqlStatement(
"DELETE FROM "+ userConsentClientScopeTable + " WHERE USER_CONSENT_ID IN (" +
" SELECT uc.ID FROM "+userConsentTable+" uc INNER JOIN (" +
" SELECT CLIENT_ID, USER_ID, MAX(LAST_UPDATED_DATE) AS MAX_UPDATED_DATE FROM " + userConsentTable +
" GROUP BY CLIENT_ID, USER_ID HAVING COUNT(*) > 1 ) max_dates ON uc.CLIENT_ID = max_dates.CLIENT_ID" +
" AND uc.USER_ID = max_dates.USER_ID AND uc.LAST_UPDATED_DATE = max_dates.MAX_UPDATED_DATE)"
));
statements.add(new RawSqlStatement(
" DELETE FROM "+userConsentTable+" WHERE ID IN (" +
" SELECT uc.ID FROM "+userConsentTable+" uc INNER JOIN (" +
" SELECT CLIENT_ID, USER_ID, MAX(LAST_UPDATED_DATE) AS MAX_UPDATED_DATE" +
" FROM "+userConsentTable+" GROUP BY CLIENT_ID, USER_ID HAVING COUNT(*) > 1 )" +
" max_dates ON uc.CLIENT_ID = max_dates.CLIENT_ID" +
" AND uc.USER_ID = max_dates.USER_ID AND uc.LAST_UPDATED_DATE = max_dates.MAX_UPDATED_DATE )"
));
statements.add(new RawSqlStatement(
" DELETE FROM "+ userConsentClientScopeTable + " WHERE USER_CONSENT_ID IN (" +
" SELECT uc.ID FROM "+userConsentTable+" uc INNER JOIN (" +
" SELECT CLIENT_STORAGE_PROVIDER, EXTERNAL_CLIENT_ID, USER_ID, MAX(LAST_UPDATED_DATE) AS MAX_UPDATED_DATE" +
" FROM "+userConsentTable+" GROUP BY CLIENT_STORAGE_PROVIDER, EXTERNAL_CLIENT_ID, USER_ID HAVING COUNT(*) > 1 )" +
" max_dates ON uc.CLIENT_STORAGE_PROVIDER = max_dates.CLIENT_STORAGE_PROVIDER" +
" AND uc.EXTERNAL_CLIENT_ID = max_dates.EXTERNAL_CLIENT_ID AND uc.USER_ID = max_dates.USER_ID AND uc.LAST_UPDATED_DATE = max_dates.MAX_UPDATED_DATE )"
));
statements.add(new RawSqlStatement(
" DELETE FROM "+userConsentTable+" WHERE ID IN (" +
" SELECT uc.ID FROM "+userConsentTable+" uc INNER JOIN (" +
" SELECT CLIENT_STORAGE_PROVIDER, EXTERNAL_CLIENT_ID, USER_ID, MAX(LAST_UPDATED_DATE) AS MAX_UPDATED_DATE" +
" FROM "+userConsentTable+" GROUP BY CLIENT_STORAGE_PROVIDER, EXTERNAL_CLIENT_ID, USER_ID HAVING COUNT(*) > 1 )" +
" max_dates ON uc.CLIENT_STORAGE_PROVIDER = max_dates.CLIENT_STORAGE_PROVIDER" +
" AND uc.EXTERNAL_CLIENT_ID = max_dates.EXTERNAL_CLIENT_ID AND uc.USER_ID = max_dates.USER_ID AND uc.LAST_UPDATED_DATE = max_dates.MAX_UPDATED_DATE )"
));
}
@Override
protected String getTaskId() {
return "Correct User Consent Unique Constraints for PostgreSQL and MariaDB";
}
}

View file

@ -0,0 +1,77 @@
/*
* Copyright 2024 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* 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 org.keycloak.connections.jpa.updater.liquibase.custom;
import liquibase.exception.CustomChangeException;
import liquibase.statement.core.DeleteStatement;
import liquibase.statement.core.RawSqlStatement;
import liquibase.structure.core.Table;
public class JpaUpdate25_0_0_MySQL_ConsentConstraints extends CustomKeycloakTask {
@Override
protected void generateStatementsImpl() throws CustomChangeException {
final String userConsentClientScopeTable = getTableName("USER_CONSENT_CLIENT_SCOPE");
final String userConsentTable = getTableName("USER_CONSENT");
statements.add(new RawSqlStatement(
"DELETE FROM "+ userConsentClientScopeTable + " WHERE USER_CONSENT_ID IN (" +
" SELECT uc.ID FROM "+userConsentTable+" uc INNER JOIN (" +
" SELECT CLIENT_ID, USER_ID, MAX(LAST_UPDATED_DATE) AS MAX_UPDATED_DATE FROM " + userConsentTable +
" GROUP BY CLIENT_ID, USER_ID HAVING COUNT(*) > 1 ) max_dates ON uc.CLIENT_ID = max_dates.CLIENT_ID" +
" AND uc.USER_ID = max_dates.USER_ID AND uc.LAST_UPDATED_DATE = max_dates.MAX_UPDATED_DATE)"
));
statements.add(new RawSqlStatement(
" CREATE TEMPORARY TABLE TEMP_USER_CONSENT_IDS" +
" AS SELECT uc.ID FROM "+userConsentTable+" uc INNER JOIN (" +
" SELECT CLIENT_ID, USER_ID, MAX(LAST_UPDATED_DATE) AS MAX_UPDATED_DATE" +
" FROM "+userConsentTable+" GROUP BY CLIENT_ID, USER_ID HAVING COUNT(*) > 1 )" +
" max_dates ON uc.CLIENT_ID = max_dates.CLIENT_ID" +
" AND uc.USER_ID = max_dates.USER_ID AND uc.LAST_UPDATED_DATE = max_dates.MAX_UPDATED_DATE"
));
statements.add(new DeleteStatement(null, null, database.correctObjectName("USER_CONSENT", Table.class))
.setWhere("ID IN (SELECT ID FROM TEMP_USER_CONSENT_IDS)"));
statements.add(new RawSqlStatement("DROP TEMPORARY TABLE IF EXISTS TEMP_USER_CONSENT_IDS"));
statements.add(new RawSqlStatement(
" DELETE FROM "+ userConsentClientScopeTable + " WHERE USER_CONSENT_ID IN (" +
" SELECT uc.ID FROM "+userConsentTable+" uc INNER JOIN (" +
" SELECT CLIENT_STORAGE_PROVIDER, EXTERNAL_CLIENT_ID, USER_ID, MAX(LAST_UPDATED_DATE) AS MAX_UPDATED_DATE" +
" FROM "+userConsentTable+" GROUP BY CLIENT_STORAGE_PROVIDER, EXTERNAL_CLIENT_ID, USER_ID HAVING COUNT(*) > 1 )" +
" max_dates ON uc.CLIENT_STORAGE_PROVIDER = max_dates.CLIENT_STORAGE_PROVIDER" +
" AND uc.EXTERNAL_CLIENT_ID = max_dates.EXTERNAL_CLIENT_ID AND uc.USER_ID = max_dates.USER_ID AND uc.LAST_UPDATED_DATE = max_dates.MAX_UPDATED_DATE )"
));
statements.add(new RawSqlStatement(
"CREATE TEMPORARY TABLE TEMP_USER_CONSENT_IDS2" +
" AS SELECT uc.ID FROM "+userConsentTable+" uc INNER JOIN (" +
" SELECT CLIENT_STORAGE_PROVIDER, EXTERNAL_CLIENT_ID, USER_ID, MAX(LAST_UPDATED_DATE) AS MAX_UPDATED_DATE" +
" FROM "+userConsentTable+" GROUP BY CLIENT_STORAGE_PROVIDER, EXTERNAL_CLIENT_ID, USER_ID HAVING COUNT(*) > 1 )" +
" max_dates ON uc.CLIENT_STORAGE_PROVIDER = max_dates.CLIENT_STORAGE_PROVIDER" +
" AND uc.EXTERNAL_CLIENT_ID = max_dates.EXTERNAL_CLIENT_ID AND uc.USER_ID = max_dates.USER_ID AND uc.LAST_UPDATED_DATE = max_dates.MAX_UPDATED_DATE;"
));
statements.add(new DeleteStatement(null, null, database.correctObjectName("USER_CONSENT", Table.class))
.setWhere("ID IN (SELECT ID FROM TEMP_USER_CONSENT_IDS2)"));
statements.add(new RawSqlStatement("DROP TEMPORARY TABLE IF EXISTS TEMP_USER_CONSENT_IDS2"));
}
@Override
protected String getTaskId() {
return "Correct User Consent Unique Constraints for MySQL";
}
}

View file

@ -208,15 +208,10 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore {
@Override @Override
public void addConsent(RealmModel realm, String userId, UserConsentModel consent) { public void addConsent(RealmModel realm, String userId, UserConsentModel consent) {
String clientId = consent.getClient().getId(); String clientId = consent.getClient().getId();
UserConsentEntity consentEntity = getGrantedConsentEntity(userId, clientId, LockModeType.NONE);
if (consentEntity != null) {
throw new ModelDuplicateException("Consent already exists for client [" + clientId + "] and user [" + userId + "]");
}
long currentTime = Time.currentTimeMillis(); long currentTime = Time.currentTimeMillis();
consentEntity = new UserConsentEntity(); UserConsentEntity consentEntity = new UserConsentEntity();
consentEntity.setId(KeycloakModelUtils.generateId()); consentEntity.setId(KeycloakModelUtils.generateId());
consentEntity.setUser(em.getReference(UserEntity.class, userId)); consentEntity.setUser(em.getReference(UserEntity.class, userId));
StorageId clientStorageId = new StorageId(clientId); StorageId clientStorageId = new StorageId(clientId);

View file

@ -34,4 +34,28 @@
<addUniqueConstraint tableName="ORGANIZATION" columnNames="REALM_ID, NAME" constraintName="UK_ORG_NAME"/> <addUniqueConstraint tableName="ORGANIZATION" columnNames="REALM_ID, NAME" constraintName="UK_ORG_NAME"/>
</changeSet> </changeSet>
<changeSet author="keycloak" id="unique-consentuser">
<preConditions onSqlOutput="TEST" onFail="MARK_RAN">
<or>
<dbms type="mariadb"/>
<dbms type="postgresql"/>
</or>
</preConditions>
<customChange class="org.keycloak.connections.jpa.updater.liquibase.custom.JpaUpdate25_0_0_ConsentConstraints"/>
<dropUniqueConstraint tableName="USER_CONSENT" constraintName="UK_JKUWUVD56ONTGSUHOGM8UEWRT"/>
<addUniqueConstraint columnNames="CLIENT_ID, USER_ID" constraintName="UK_LOCAL_CONSENT" tableName="USER_CONSENT"/>
<addUniqueConstraint columnNames="CLIENT_STORAGE_PROVIDER, EXTERNAL_CLIENT_ID, USER_ID" constraintName="UK_EXTERNAL_CONSENT" tableName="USER_CONSENT"/>
</changeSet>
<changeSet author="keycloak" id="unique-consentuser-mysql">
<preConditions onSqlOutput="TEST" onFail="MARK_RAN">
<dbms type="mysql"/>
</preConditions>
<customChange class="org.keycloak.connections.jpa.updater.liquibase.custom.JpaUpdate25_0_0_MySQL_ConsentConstraints"/>
<dropUniqueConstraint tableName="USER_CONSENT" constraintName="UK_JKUWUVD56ONTGSUHOGM8UEWRT"/>
<addUniqueConstraint columnNames="CLIENT_ID, USER_ID" constraintName="UK_LOCAL_CONSENT" tableName="USER_CONSENT"/>
<addUniqueConstraint columnNames="CLIENT_STORAGE_PROVIDER, EXTERNAL_CLIENT_ID, USER_ID" constraintName="UK_EXTERNAL_CONSENT" tableName="USER_CONSENT"/>
</changeSet>
</databaseChangeLog> </databaseChangeLog>