From 8daa2c3703d3f6ebcf7798735f1d527f802c2394 Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Wed, 18 Jan 2017 18:28:08 -0500 Subject: [PATCH] KEYCLOAK-4256 --- .../jpa/JpaUserFederatedStorageProvider.java | 8 + .../entity/FederatedUserConsentEntity.java | 24 ++ .../META-INF/jpa-changelog-2.5.1.xml | 29 ++ .../META-INF/jpa-changelog-master.xml | 1 + .../UserConsentWithUserStorageModelTest.java | 292 ++++++++++++++++++ 5 files changed, 354 insertions(+) create mode 100755 model/jpa/src/main/resources/META-INF/jpa-changelog-2.5.1.xml create mode 100644 testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserConsentWithUserStorageModelTest.java diff --git a/model/jpa/src/main/java/org/keycloak/storage/jpa/JpaUserFederatedStorageProvider.java b/model/jpa/src/main/java/org/keycloak/storage/jpa/JpaUserFederatedStorageProvider.java index 12228720df..cded4e997b 100644 --- a/model/jpa/src/main/java/org/keycloak/storage/jpa/JpaUserFederatedStorageProvider.java +++ b/model/jpa/src/main/java/org/keycloak/storage/jpa/JpaUserFederatedStorageProvider.java @@ -17,6 +17,7 @@ package org.keycloak.storage.jpa; import org.keycloak.common.util.MultivaluedHashMap; +import org.keycloak.common.util.Time; import org.keycloak.component.ComponentModel; import org.keycloak.credential.CredentialModel; import org.keycloak.credential.UserCredentialStore; @@ -259,6 +260,9 @@ public class JpaUserFederatedStorageProvider implements consentEntity.setClientId(clientId); consentEntity.setRealmId(realm.getId()); consentEntity.setStorageProviderId(new StorageId(userId).getProviderId()); + long currentTime = Time.currentTimeMillis(); + consentEntity.setCreatedDate(currentTime); + consentEntity.setLastUpdatedDate(currentTime); em.persist(consentEntity); em.flush(); @@ -335,6 +339,8 @@ public class JpaUserFederatedStorageProvider implements throw new ModelException("Client with id " + entity.getClientId() + " is not available"); } UserConsentModel model = new UserConsentModel(client); + model.setCreatedDate(entity.getCreatedDate()); + model.setLastUpdatedDate(entity.getLastUpdatedDate()); Collection grantedRoleEntities = entity.getGrantedRoles(); if (grantedRoleEntities != null) { @@ -404,6 +410,8 @@ public class JpaUserFederatedStorageProvider implements em.remove(toRemove); } + consentEntity.setLastUpdatedDate(Time.currentTimeMillis()); + em.flush(); } diff --git a/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserConsentEntity.java b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserConsentEntity.java index 8066310ffc..225e80bd72 100755 --- a/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserConsentEntity.java +++ b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserConsentEntity.java @@ -65,6 +65,14 @@ public class FederatedUserConsentEntity { @Column(name="CLIENT_ID") protected String clientId; + @Column(name = "CREATED_DATE") + private Long createdDate; + + @Column(name = "LAST_UPDATED_DATE") + private Long lastUpdatedDate; + + + @OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "userConsent") Collection grantedRoles = new ArrayList(); @@ -127,6 +135,22 @@ public class FederatedUserConsentEntity { this.grantedProtocolMappers = grantedProtocolMappers; } + public Long getCreatedDate() { + return createdDate; + } + + public void setCreatedDate(Long createdDate) { + this.createdDate = createdDate; + } + + public Long getLastUpdatedDate() { + return lastUpdatedDate; + } + + public void setLastUpdatedDate(Long lastUpdatedDate) { + this.lastUpdatedDate = lastUpdatedDate; + } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/model/jpa/src/main/resources/META-INF/jpa-changelog-2.5.1.xml b/model/jpa/src/main/resources/META-INF/jpa-changelog-2.5.1.xml new file mode 100755 index 0000000000..3184134695 --- /dev/null +++ b/model/jpa/src/main/resources/META-INF/jpa-changelog-2.5.1.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/model/jpa/src/main/resources/META-INF/jpa-changelog-master.xml b/model/jpa/src/main/resources/META-INF/jpa-changelog-master.xml index 4b7f713dc8..0a26548a30 100755 --- a/model/jpa/src/main/resources/META-INF/jpa-changelog-master.xml +++ b/model/jpa/src/main/resources/META-INF/jpa-changelog-master.xml @@ -45,4 +45,5 @@ + diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserConsentWithUserStorageModelTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserConsentWithUserStorageModelTest.java new file mode 100644 index 0000000000..6fd18bf946 --- /dev/null +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserConsentWithUserStorageModelTest.java @@ -0,0 +1,292 @@ +/* + * Copyright 2016 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.testsuite.model; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.keycloak.models.ClientModel; +import org.keycloak.models.ModelException; +import org.keycloak.models.ProtocolMapperModel; +import org.keycloak.models.RealmModel; +import org.keycloak.models.RoleContainerModel; +import org.keycloak.models.RoleModel; +import org.keycloak.models.UserConsentModel; +import org.keycloak.models.UserModel; +import org.keycloak.protocol.oidc.OIDCLoginProtocol; +import org.keycloak.protocol.oidc.mappers.UserPropertyMapper; +import org.keycloak.storage.UserStorageProviderModel; +import org.keycloak.testsuite.federation.storage.UserMapStorageFactory; +import org.keycloak.testsuite.federation.storage.UserPropertyFileStorageFactory; + +import java.util.List; + +/** + * @author Marek Posolda + */ +public class UserConsentWithUserStorageModelTest extends AbstractModelTest { + + @Before + public void setupEnv() { + RealmModel realm = realmManager.createRealm("original"); + + UserStorageProviderModel model = new UserStorageProviderModel(); + model.setName("memory"); + model.setPriority(0); + model.setProviderId(UserMapStorageFactory.PROVIDER_ID); + model.setParentId(realm.getId()); + realm.addComponentModel(model); + + ClientModel fooClient = realm.addClient("foo-client"); + ClientModel barClient = realm.addClient("bar-client"); + + RoleModel realmRole = realm.addRole("realm-role"); + RoleModel barClientRole = barClient.addRole("bar-client-role"); + + ProtocolMapperModel fooMapper = new ProtocolMapperModel(); + fooMapper.setName("foo"); + fooMapper.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL); + fooMapper.setProtocolMapper(UserPropertyMapper.PROVIDER_ID); + fooMapper = fooClient.addProtocolMapper(fooMapper); + + ProtocolMapperModel barMapper = new ProtocolMapperModel(); + barMapper.setName("bar"); + barMapper.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL); + barMapper.setProtocolMapper(UserPropertyMapper.PROVIDER_ID); + barMapper = barClient.addProtocolMapper(barMapper); + + UserModel john = session.users().addUser(realm, "john"); + UserModel mary = session.users().addUser(realm, "mary"); + + UserConsentModel johnFooGrant = new UserConsentModel(fooClient); + johnFooGrant.addGrantedRole(realmRole); + johnFooGrant.addGrantedRole(barClientRole); + johnFooGrant.addGrantedProtocolMapper(fooMapper); + realmManager.getSession().users().addConsent(realm, john.getId(), johnFooGrant); + + UserConsentModel johnBarGrant = new UserConsentModel(barClient); + johnBarGrant.addGrantedProtocolMapper(barMapper); + johnBarGrant.addGrantedRole(realmRole); + + // Update should fail as grant doesn't yet exists + try { + realmManager.getSession().users().updateConsent(realm, john.getId(), johnBarGrant); + Assert.fail("Not expected to end here"); + } catch (ModelException expected) { + } + + realmManager.getSession().users().addConsent(realm, john.getId(), johnBarGrant); + + UserConsentModel maryFooGrant = new UserConsentModel(fooClient); + maryFooGrant.addGrantedRole(realmRole); + maryFooGrant.addGrantedProtocolMapper(fooMapper); + realmManager.getSession().users().addConsent(realm, mary.getId(), maryFooGrant); + + commit(); + } + + @Test + public void basicConsentTest() { + RealmModel realm = realmManager.getRealm("original"); + ClientModel fooClient = realm.getClientByClientId("foo-client"); + ClientModel barClient = realm.getClientByClientId("bar-client"); + + UserModel john = session.users().getUserByUsername("john", realm); + UserModel mary = session.users().getUserByUsername("mary", realm); + + UserConsentModel johnFooConsent = realmManager.getSession().users().getConsentByClient(realm, john.getId(), fooClient.getId()); + Assert.assertEquals(johnFooConsent.getGrantedRoles().size(), 2); + Assert.assertEquals(johnFooConsent.getGrantedProtocolMappers().size(), 1); + Assert.assertTrue(isRoleGranted(realm, "realm-role", johnFooConsent)); + Assert.assertTrue(isRoleGranted(barClient, "bar-client-role", johnFooConsent)); + Assert.assertTrue(isMapperGranted(fooClient, "foo", johnFooConsent)); + Assert.assertNotNull("Created Date should be set", johnFooConsent.getCreatedDate()); + Assert.assertNotNull("Last Updated Date should be set", johnFooConsent.getLastUpdatedDate()); + + UserConsentModel johnBarConsent = realmManager.getSession().users().getConsentByClient(realm, john.getId(), barClient.getId()); + Assert.assertEquals(johnBarConsent.getGrantedRoles().size(), 1); + Assert.assertEquals(johnBarConsent.getGrantedProtocolMappers().size(), 1); + Assert.assertTrue(isRoleGranted(realm, "realm-role", johnBarConsent)); + Assert.assertTrue(isMapperGranted(barClient, "bar", johnBarConsent)); + Assert.assertNotNull("Created Date should be set", johnBarConsent.getCreatedDate()); + Assert.assertNotNull("Last Updated Date should be set", johnBarConsent.getLastUpdatedDate()); + + UserConsentModel maryConsent = realmManager.getSession().users().getConsentByClient(realm, mary.getId(), fooClient.getId()); + Assert.assertEquals(maryConsent.getGrantedRoles().size(), 1); + Assert.assertEquals(maryConsent.getGrantedProtocolMappers().size(), 1); + Assert.assertTrue(isRoleGranted(realm, "realm-role", maryConsent)); + Assert.assertFalse(isRoleGranted(barClient, "bar-client-role", maryConsent)); + Assert.assertTrue(isMapperGranted(fooClient, "foo", maryConsent)); + Assert.assertNotNull("Created Date should be set", maryConsent.getCreatedDate()); + Assert.assertNotNull("Last Updated Date should be set", maryConsent.getLastUpdatedDate()); + + Assert.assertNull(realmManager.getSession().users().getConsentByClient(realm, mary.getId(), barClient.getId())); + } + + @Test + public void getAllConsentTest() { + RealmModel realm = realmManager.getRealm("original"); + ClientModel fooClient = realm.getClientByClientId("foo-client"); + + UserModel john = session.users().getUserByUsername("john", realm); + UserModel mary = session.users().getUserByUsername("mary", realm); + + List johnConsents = realmManager.getSession().users().getConsents(realm, john.getId()); + Assert.assertEquals(2, johnConsents.size()); + + List maryConsents = realmManager.getSession().users().getConsents(realm, mary.getId()); + Assert.assertEquals(1, maryConsents.size()); + UserConsentModel maryConsent = maryConsents.get(0); + Assert.assertEquals(maryConsent.getClient().getId(), fooClient.getId()); + Assert.assertEquals(maryConsent.getGrantedRoles().size(), 1); + Assert.assertEquals(maryConsent.getGrantedProtocolMappers().size(), 1); + Assert.assertTrue(isRoleGranted(realm, "realm-role", maryConsent)); + Assert.assertTrue(isMapperGranted(fooClient, "foo", maryConsent)); + } + + @Test + public void updateWithRoleRemovalTest() { + RealmModel realm = realmManager.getRealm("original"); + ClientModel fooClient = realm.getClientByClientId("foo-client"); + UserModel john = session.users().getUserByUsername("john", realm); + + UserConsentModel johnConsent = realmManager.getSession().users().getConsentByClient(realm, john.getId(), fooClient.getId()); + + // Remove foo protocol mapper from johnConsent + ProtocolMapperModel protMapperModel = fooClient.getProtocolMapperByName(OIDCLoginProtocol.LOGIN_PROTOCOL, "foo"); + johnConsent.getGrantedProtocolMappers().remove(protMapperModel); + + // Remove realm-role and add new-realm-role to johnConsent + RoleModel realmRole = realm.getRole("realm-role"); + johnConsent.getGrantedRoles().remove(realmRole); + + RoleModel newRealmRole = realm.addRole("new-realm-role"); + johnConsent.addGrantedRole(newRealmRole); + + realmManager.getSession().users().updateConsent(realm, john.getId(), johnConsent); + + commit(); + + realm = realmManager.getRealm("original"); + fooClient = realm.getClientByClientId("foo-client"); + john = session.users().getUserByUsername("john", realm); + johnConsent = realmManager.getSession().users().getConsentByClient(realm, john.getId(), fooClient.getId()); + + Assert.assertEquals(johnConsent.getGrantedRoles().size(), 2); + Assert.assertEquals(johnConsent.getGrantedProtocolMappers().size(), 0); + Assert.assertFalse(isRoleGranted(realm, "realm-role", johnConsent)); + Assert.assertTrue(isRoleGranted(realm, "new-realm-role", johnConsent)); + Assert.assertFalse(isMapperGranted(fooClient, "foo", johnConsent)); + Assert.assertTrue("Created date should be less than last updated date", johnConsent.getCreatedDate() < johnConsent.getLastUpdatedDate()); + } + + @Test + public void revokeTest() { + RealmModel realm = realmManager.getRealm("original"); + ClientModel fooClient = realm.getClientByClientId("foo-client"); + UserModel john = session.users().getUserByUsername("john", realm); + + realmManager.getSession().users().revokeConsentForClient(realm, john.getId(), fooClient.getId()); + + commit(); + + realm = realmManager.getRealm("original"); + john = session.users().getUserByUsername("john", realm); + Assert.assertNull(realmManager.getSession().users().getConsentByClient(realm, john.getId(), fooClient.getId())); + } + + @Test + public void deleteUserTest() { + // Validate user deleted without any referential constraint errors + RealmModel realm = realmManager.getRealm("original"); + UserModel john = session.users().getUserByUsername("john", realm); + session.users().removeUser(realm, john); + } + + @Test + public void deleteProtocolMapperTest() { + RealmModel realm = realmManager.getRealm("original"); + ClientModel fooClient = realm.getClientByClientId("foo-client"); + ProtocolMapperModel fooMapper = fooClient.getProtocolMapperByName(OIDCLoginProtocol.LOGIN_PROTOCOL, "foo"); + fooClient.removeProtocolMapper(fooMapper); + + commit(); + + realm = realmManager.getRealm("original"); + fooClient = realm.getClientByClientId("foo-client"); + UserModel john = session.users().getUserByUsername("john", realm); + UserConsentModel johnConsent = realmManager.getSession().users().getConsentByClient(realm, john.getId(), fooClient.getId()); + + Assert.assertEquals(johnConsent.getGrantedRoles().size(), 2); + Assert.assertEquals(johnConsent.getGrantedProtocolMappers().size(), 0); + Assert.assertFalse(johnConsent.isProtocolMapperGranted(fooMapper)); + } + + @Test + public void deleteRoleTest() { + RealmModel realm = realmManager.getRealm("original"); + RoleModel realmRole = realm.getRole("realm-role"); + realm.removeRole(realmRole); + + commit(); + + realm = realmManager.getRealm("original"); + ClientModel fooClient = realm.getClientByClientId("foo-client"); + ClientModel barClient = realm.getClientByClientId("bar-client"); + UserModel john = session.users().getUserByUsername("john", realm); + UserConsentModel johnConsent = realmManager.getSession().users().getConsentByClient(realm, john.getId(), fooClient.getId()); + + Assert.assertEquals(johnConsent.getGrantedRoles().size(), 1); + Assert.assertEquals(johnConsent.getGrantedProtocolMappers().size(), 1); + Assert.assertFalse(johnConsent.isRoleGranted(realmRole)); + Assert.assertTrue(isRoleGranted(barClient, "bar-client-role", johnConsent)); + } + + @Test + public void deleteClientTest() { + RealmModel realm = realmManager.getRealm("original"); + ClientModel barClient = realm.getClientByClientId("bar-client"); + realm.removeClient(barClient.getId()); + + commit(); + + realm = realmManager.getRealm("original"); + ClientModel fooClient = realm.getClientByClientId("foo-client"); + Assert.assertNull(realm.getClientByClientId("bar-client")); + + UserModel john = session.users().getUserByUsername("john", realm); + + UserConsentModel johnFooConsent = realmManager.getSession().users().getConsentByClient(realm, john.getId(), fooClient.getId()); + Assert.assertEquals(johnFooConsent.getGrantedRoles().size(), 1); + Assert.assertEquals(johnFooConsent.getGrantedProtocolMappers().size(), 1); + Assert.assertTrue(isRoleGranted(realm, "realm-role", johnFooConsent)); + Assert.assertTrue(isMapperGranted(fooClient, "foo", johnFooConsent)); + + Assert.assertNull(realmManager.getSession().users().getConsentByClient(realm, john.getId(), barClient.getId())); + } + + private boolean isRoleGranted(RoleContainerModel roleContainer, String roleName, UserConsentModel consentModel) { + RoleModel role = roleContainer.getRole(roleName); + return consentModel.isRoleGranted(role); + } + + private boolean isMapperGranted(ClientModel client, String protocolMapperName, UserConsentModel consentModel) { + ProtocolMapperModel protocolMapper = client.getProtocolMapperByName(OIDCLoginProtocol.LOGIN_PROTOCOL, protocolMapperName); + return consentModel.isProtocolMapperGranted(protocolMapper); + } +}