keycloak-memoizeit
Changes
adapters/oidc/adapter-core/pom.xml 2(+1 -1)
adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java 2(+1 -1)
adapters/oidc/as7-eap6/pom.xml 2(+1 -1)
adapters/oidc/installed/pom.xml 2(+1 -1)
adapters/oidc/jetty/jetty8.1/pom.xml 2(+1 -1)
adapters/oidc/jetty/jetty9.1/pom.xml 2(+1 -1)
adapters/oidc/jetty/jetty9.2/pom.xml 2(+1 -1)
adapters/oidc/jetty/jetty-core/src/main/java/org/keycloak/adapters/jetty/core/AbstractKeycloakJettyAuthenticator.java 36(+36 -0)
adapters/oidc/jetty/pom.xml 2(+1 -1)
adapters/oidc/js/pom.xml 2(+1 -1)
adapters/oidc/osgi-adapter/pom.xml 2(+1 -1)
adapters/oidc/pom.xml 2(+1 -1)
adapters/oidc/servlet-filter/pom.xml 2(+1 -1)
adapters/oidc/servlet-filter/src/main/java/org/keycloak/adapters/servlet/OIDCFilterSessionStore.java 2(+2 -0)
adapters/oidc/spring-boot/pom.xml 2(+1 -1)
adapters/oidc/tomcat/pom.xml 2(+1 -1)
adapters/oidc/tomcat/tomcat6/pom.xml 2(+1 -1)
adapters/oidc/tomcat/tomcat7/pom.xml 2(+1 -1)
adapters/oidc/tomcat/tomcat8/pom.xml 2(+1 -1)
adapters/oidc/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/CatalinaSessionTokenStore.java 17(+15 -2)
adapters/oidc/undertow/pom.xml 2(+1 -1)
adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/ServletSessionTokenStore.java 4(+3 -1)
adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowSessionTokenStore.java 4(+4 -0)
adapters/oidc/wildfly/pom.xml 2(+1 -1)
adapters/pom.xml 2(+1 -1)
adapters/saml/as7-eap6/pom.xml 2(+1 -1)
adapters/saml/core/pom.xml 2(+1 -1)
adapters/saml/jetty/jetty8.1/pom.xml 2(+1 -1)
adapters/saml/jetty/jetty9.1/pom.xml 2(+1 -1)
adapters/saml/jetty/jetty9.2/pom.xml 2(+1 -1)
adapters/saml/jetty/jetty-core/src/main/java/org/keycloak/adapters/saml/jetty/AbstractSamlAuthenticator.java 36(+36 -0)
adapters/saml/jetty/pom.xml 2(+1 -1)
adapters/saml/pom.xml 2(+1 -1)
adapters/saml/servlet-filter/pom.xml 2(+1 -1)
adapters/saml/tomcat/pom.xml 2(+1 -1)
adapters/saml/tomcat/tomcat6/pom.xml 2(+1 -1)
adapters/saml/tomcat/tomcat7/pom.xml 2(+1 -1)
adapters/saml/tomcat/tomcat8/pom.xml 2(+1 -1)
adapters/saml/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/saml/AbstractSamlAuthenticatorValve.java 5(+3 -2)
adapters/saml/undertow/pom.xml 2(+1 -1)
adapters/saml/wildfly/pom.xml 2(+1 -1)
adapters/spi/adapter-spi/pom.xml 2(+1 -1)
adapters/spi/pom.xml 2(+1 -1)
common/pom.xml 2(+1 -1)
core/pom.xml 2(+1 -1)
dependencies/pom.xml 2(+1 -1)
dependencies/server-all/pom.xml 2(+1 -1)
dependencies/server-min/pom.xml 2(+1 -1)
distribution/adapters/osgi/pom.xml 2(+1 -1)
distribution/adapters/pom.xml 2(+1 -1)
distribution/demo-dist/pom.xml 2(+1 -1)
distribution/docs-dist/pom.xml 2(+1 -1)
distribution/downloads/pom.xml 2(+1 -1)
distribution/examples-dist/pom.xml 2(+1 -1)
distribution/feature-packs/pom.xml 2(+1 -1)
distribution/pom.xml 2(+1 -1)
distribution/proxy-dist/pom.xml 2(+1 -1)
distribution/saml-adapters/pom.xml 2(+1 -1)
distribution/server-dist/pom.xml 2(+1 -1)
distribution/server-overlay/pom.xml 2(+1 -1)
distribution/src-dist/pom.xml 2(+1 -1)
docbook/auth-server-docs/pom.xml 2(+1 -1)
docbook/pom.xml 2(+1 -1)
docbook/saml-adapter-docs/pom.xml 2(+1 -1)
examples/admin-client/pom.xml 2(+1 -1)
examples/basic-auth/pom.xml 2(+1 -1)
examples/broker/pom.xml 2(+1 -1)
examples/cors/pom.xml 2(+1 -1)
examples/demo-template/pom.xml 2(+1 -1)
examples/fuse/camel/pom.xml 2(+1 -1)
examples/fuse/cxf-jaxrs/pom.xml 2(+1 -1)
examples/fuse/cxf-jaxws/pom.xml 2(+1 -1)
examples/fuse/features/pom.xml 2(+1 -1)
examples/fuse/pom.xml 2(+1 -1)
examples/fuse/testrealm.json 3(+2 -1)
examples/js-console/pom.xml 2(+1 -1)
examples/kerberos/pom.xml 2(+1 -1)
examples/ldap/pom.xml 2(+1 -1)
examples/multi-tenant/pom.xml 2(+1 -1)
examples/pom.xml 2(+1 -1)
examples/providers/pom.xml 2(+1 -1)
examples/saml/pom.xml 2(+1 -1)
examples/saml/servlet-filter/pom.xml 2(+1 -1)
examples/themes/pom.xml 2(+1 -1)
federation/kerberos/pom.xml 2(+1 -1)
federation/ldap/pom.xml 2(+1 -1)
federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/LDAPQuery.java 3(+2 -1)
federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/LDAPQueryConditionsBuilder.java 3(+0 -3)
federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/ldap/LDAPIdentityStore.java 4(+4 -0)
federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProviderFactory.java 24(+17 -7)
federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/AbstractLDAPFederationMapperFactory.java 6(+3 -3)
federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/FullNameLDAPFederationMapper.java 14(+14 -0)
federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/FullNameLDAPFederationMapperFactory.java 33(+27 -6)
federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/HardcodedLDAPRoleMapperFactory.java 8(+4 -4)
federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/LDAPMappersComparator.java 114(+114 -0)
federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/group/GroupLDAPFederationMapper.java 51(+38 -13)
federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/group/GroupLDAPFederationMapperFactory.java 16(+10 -6)
federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/role/RoleLDAPFederationMapper.java 6(+3 -3)
federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/role/RoleLDAPFederationMapperFactory.java 13(+6 -7)
federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/msad/MSADUserAccountControlMapper.java 29(+27 -2)
federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/msad/MSADUserAccountControlMapperFactory.java 4(+2 -2)
federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/UserAttributeLDAPFederationMapperFactory.java 4(+2 -2)
federation/ldap/src/test/java/org/keycloak/federation/ldap/idm/model/LDAPMappersComparatorTest.java 117(+117 -0)
federation/pom.xml 2(+1 -1)
integration/admin-client/pom.xml 7(+6 -1)
integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ClientAttributeCertificateResource.java 114(+114 -0)
integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ClientResource.java 44(+20 -24)
integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RealmResource.java 2(+1 -1)
integration/admin-client/src/main/java/org/keycloak/admin/client/token/TokenManager.java 34(+17 -17)
integration/client-registration-cli/src/main/java/org/keycloak/client/registration/cli/Context.java 8(+4 -4)
integration/pom.xml 2(+1 -1)
model/infinispan/pom.xml 2(+1 -1)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedUser.java 13(+5 -8)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/UserListQuery.java 49(+49 -0)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/UserQuery.java 11(+11 -0)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanCacheRealmProviderFactory.java 6(+3 -3)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanCacheUserProviderFactory.java 108(+6 -102)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmCacheManager.java 162(+162 -0)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmCacheSession.java 80(+47 -33)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/StreamRealmCache.java 304(+0 -304)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheManager.java 68(+19 -49)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheSession.java 183(+133 -50)
model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java 10(+7 -3)
model/infinispan/src/test/java/org/keycloak/models/sessions/infinispan/initializer/ClusteredCacheBehaviorTest.java 9(+9 -0)
model/jpa/pom.xml 2(+1 -1)
model/jpa/src/main/java/org/keycloak/connections/jpa/DefaultJpaConnectionProviderFactory.java 75(+65 -10)
model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/conn/DefaultLiquibaseConnectionProvider.java 237(+237 -0)
model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/conn/LiquibaseConnectionProvider.java 33(+33 -0)
model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/conn/LiquibaseConnectionProviderFactory.java 7(+3 -4)
model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/conn/LiquibaseConnectionSpi.java 13(+6 -7)
model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/LiquibaseJpaUpdaterProvider.java 157(+12 -145)
model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/LiquibaseJpaUpdaterProviderFactory.java 2(+1 -1)
model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/lock/CustomInsertLockRecordGenerator.java 64(+64 -0)
model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/lock/CustomLockService.java 175(+175 -0)
model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/lock/DummyLockService.java 35(+12 -23)
model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/lock/LiquibaseDBLockProvider.java 159(+159 -0)
model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/lock/LiquibaseDBLockProviderFactory.java 47(+36 -11)
model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/lock/LockRetryException.java 15(+11 -4)
model/jpa/src/main/resources/META-INF/services/org.keycloak.connections.jpa.updater.liquibase.conn.LiquibaseConnectionProviderFactory 2(+1 -1)
model/jpa/src/main/resources/META-INF/services/org.keycloak.models.dblock.DBLockProviderFactory 18(+18 -0)
model/mongo/pom.xml 2(+1 -1)
model/mongo/src/main/java/org/keycloak/connections/mongo/DefaultMongoConnectionFactoryProvider.java 88(+61 -27)
model/mongo/src/main/java/org/keycloak/connections/mongo/lock/MongoDBLockProviderFactory.java 84(+84 -0)
model/mongo/src/main/java/org/keycloak/connections/mongo/MongoConnectionProviderFactory.java 7(+7 -0)
model/mongo/src/main/resources/META-INF/services/org.keycloak.models.dblock.DBLockProviderFactory 35(+35 -0)
model/pom.xml 2(+1 -1)
pom.xml 2(+1 -1)
proxy/launcher/pom.xml 2(+1 -1)
proxy/pom.xml 2(+1 -1)
proxy/proxy-server/pom.xml 2(+1 -1)
saml-core/pom.xml 2(+1 -1)
saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/AbstractDescriptorParser.java 22(+1 -21)
server-spi/pom.xml 2(+1 -1)
server-spi/src/main/java/org/keycloak/models/UserFederationValidatingProviderFactory.java 37(+37 -0)
services/pom.xml 2(+1 -1)
services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpCreateUserIfUniqueAuthenticator.java 8(+7 -1)
services/src/main/java/org/keycloak/authentication/authenticators/browser/AbstractUsernameFormAuthenticator.java 2(+1 -1)
services/src/main/java/org/keycloak/forms/account/freemarker/FreeMarkerAccountProvider.java 4(+2 -2)
services/src/main/java/org/keycloak/forms/account/freemarker/model/AccountFederatedIdentityBean.java 2(+1 -1)
services/src/main/java/org/keycloak/forms/login/freemarker/FreeMarkerLoginFormsProvider.java 6(+3 -3)
services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java 2(+1 -1)
services/src/main/java/org/keycloak/services/resources/admin/ClientAttributeCertificateResource.java 63(+3 -60)
services/src/main/java/org/keycloak/services/resources/admin/info/ServerInfoAdminResource.java 8(+7 -1)
services/src/main/java/org/keycloak/services/resources/admin/UserFederationProviderResource.java 17(+12 -5)
services/src/main/java/org/keycloak/services/resources/admin/UserFederationProvidersResource.java 24(+24 -0)
testsuite/docker-cluster/pom.xml 2(+1 -1)
testsuite/integration/pom.xml 47(+11 -36)
testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java 10(+9 -1)
testsuite/integration/src/test/java/org/keycloak/testsuite/admin/ClusteredConcurrencyTest.java 212(+212 -0)
testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java 4(+4 -0)
testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractKeycloakIdentityProviderTest.java 189(+132 -57)
testsuite/integration/src/test/java/org/keycloak/testsuite/DummyUserFederationProvider.java 28(+23 -5)
testsuite/integration/src/test/java/org/keycloak/testsuite/DummyUserFederationProviderFactory.java 7(+5 -2)
testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/base/FederationProvidersIntegrationTest.java 45(+41 -4)
testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/base/LDAPGroupMapper2WaySyncTest.java 1(+1 -0)
testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/base/LDAPGroupMapperSyncTest.java 51(+51 -0)
testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/base/LDAPGroupMapperTest.java 2(+2 -0)
testsuite/integration/src/test/java/org/keycloak/testsuite/keycloaksaml/InputServlet.java 13(+13 -0)
testsuite/integration/src/test/java/org/keycloak/testsuite/keycloaksaml/SamlAdapterTestStrategy.java 6(+6 -0)
testsuite/integration/src/test/java/org/keycloak/testsuite/model/ConcurrentTransactionsTest.java 148(+148 -0)
testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/ResourceOwnerPasswordCredentialsGrantTest.java 71(+69 -2)
testsuite/integration/src/test/java/org/keycloak/testsuite/saml/SamlPicketlinkSPTest.java 34(+33 -1)
testsuite/integration/src/test/java/org/keycloak/testsuite/util/cli/AbstractOfflineCacheCommand.java 28(+28 -0)
testsuite/integration-arquillian/servers/eap7/pom.xml 172(+122 -50)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/admin/ApiUtil.java 7(+7 -0)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/KeycloakArquillianExtension.java 4(+3 -1)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/TestContext.java 12(+12 -0)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/account/AccountManagement.java 12(+9 -3)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/login/Login.java 2(+1 -1)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/login/SAMLIDPInitiatedLogin.java 19(+19 -0)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/login/TermsAndConditions.java 60(+60 -0)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/WelcomePage.java 8(+7 -1)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/AdminConsole.java 10(+6 -4)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/page/PageWithLogOutAction.java 11(+11 -0)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/IOUtil.java 2(+2 -0)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/LogChecker.java 37(+24 -13)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/Timer.java 147(+122 -25)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java 69(+66 -3)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/ChangePasswordTest.java 3(+3 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/RegistrationTest.java 6(+5 -1)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/ResetCredentialsTest.java 2(+2 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractDemoExampleAdapterTest.java 10(+5 -5)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractDemoServletsAdapterTest.java 0(+0 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLServletsAdapterTest.java 43(+40 -3)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSessionServletAdapterTest.java 13(+11 -2)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/AbstractAuthenticationTest.java 40(+37 -3)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ExecutionTest.java 4(+2 -2)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/FlowTest.java 70(+68 -2)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/InitialFlowsTest.java 142(+97 -45)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/InitialProvidersTest.java 101(+0 -101)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ProvidersTest.java 159(+159 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/RequiredActionsTest.java 121(+121 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/AbstractClientTest.java 28(+20 -8)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/ClientProtocolMapperTest.java 266(+266 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/ClientRolesTest.java 10(+6 -4)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/CredentialsTest.java 74(+74 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/InstallationTest.java 7(+7 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/SessionTest.java 92(+92 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cluster/SessionFailoverClusterTest.java 168(+112 -56)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/user/AbstractUserTest.java 52(+52 -0)
testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/testsaml.json 3(+2 -1)
testsuite/integration-arquillian/tests/other/adapters/eap6/src/main/xslt/add-adapter-log-level.xsl 50(+50 -0)
testsuite/integration-arquillian/tests/other/adapters/eap7/src/main/xslt/add-adapter-log-level.xsl 50(+50 -0)
testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/clients/CreateClientForm.java 4(+2 -2)
testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/clients/settings/ClientSettingsForm.java 6(+3 -3)
testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/federation/LdapUserProviderForm.java 7(+7 -0)
testsuite/integration-arquillian/tests/other/console/src/main/resources/themes/qe/login/messages/messages_en.properties 4(+4 -0)
testsuite/integration-arquillian/tests/other/console/src/main/resources/themes/qe/login/theme.properties 1(+1 -0)
testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/authentication/actions/TermsAndConditionsTest.java 166(+166 -0)
testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/clients/ClientSettingsTest.java 4(+2 -2)
testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/events/LoginEventsTest.java 2(+1 -1)
testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/federation/LdapUserFederationTest.java 17(+17 -0)
testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/realm/SecurityDefensesTest.java 2(+1 -1)
testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/roles/RealmRolesTest.java 4(+2 -2)
testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/themes/TermsAndConditionsThemeTest.java 83(+83 -0)
testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/users/UsersTest.java 8(+4 -4)
testsuite/integration-arquillian/tests/other/console_no_users/src/test/java/org/keycloak/testsuite/console/pages/WelcomePageTest.java 112(+112 -0)
testsuite/integration-arquillian/tests/other/jpa-performance/src/test/java/org/keycloak/testsuite/user/ManyUsersTest.java 148(+148 -0)
testsuite/integration-arquillian/tests/pom.xml 202(+195 -7)
testsuite/jetty/jetty81/pom.xml 2(+1 -1)
testsuite/jetty/jetty91/pom.xml 2(+1 -1)
testsuite/jetty/jetty92/pom.xml 2(+1 -1)
testsuite/jetty/pom.xml 2(+1 -1)
testsuite/performance/pom.xml 2(+1 -1)
testsuite/pom.xml 2(+1 -1)
testsuite/proxy/pom.xml 2(+1 -1)
testsuite/stress/pom.xml 2(+1 -1)
testsuite/tomcat6/pom.xml 2(+1 -1)
testsuite/tomcat7/pom.xml 2(+1 -1)
testsuite/tomcat8/pom.xml 2(+1 -1)
themes/pom.xml 2(+1 -1)
themes/src/main/resources/theme/base/admin/resources/partials/user-federated-identity-list.html 4(+1 -3)
util/embedded-ldap/pom.xml 2(+1 -1)
util/pom.xml 2(+1 -1)
wildfly/adduser/pom.xml 2(+1 -1)
wildfly/extensions/pom.xml 2(+1 -1)
wildfly/pom.xml 2(+1 -1)
wildfly/server-subsystem/pom.xml 2(+1 -1)
Details
adapters/oidc/adapter-core/pom.xml 2(+1 -1)
diff --git a/adapters/oidc/adapter-core/pom.xml b/adapters/oidc/adapter-core/pom.xml
index c44e1f5..015fbf4 100755
--- a/adapters/oidc/adapter-core/pom.xml
+++ b/adapters/oidc/adapter-core/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java
index 0683075..cf51bdf 100755
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java
@@ -206,7 +206,7 @@ public class OAuthRequestAuthenticator {
tokenStore.saveRequest();
log.debug("Sending redirect to login page: " + redirect);
exchange.getResponse().setStatus(302);
- exchange.getResponse().setCookie(deployment.getStateCookieName(), state, /* need to set path? */ null, null, -1, deployment.getSslRequired().isRequired(facade.getRequest().getRemoteAddr()), false);
+ exchange.getResponse().setCookie(deployment.getStateCookieName(), state, /* need to set path? */ null, null, -1, deployment.getSslRequired().isRequired(facade.getRequest().getRemoteAddr()), true);
exchange.getResponse().setHeader("Location", redirect);
return true;
}
diff --git a/adapters/oidc/as7-eap6/as7-adapter/pom.xml b/adapters/oidc/as7-eap6/as7-adapter/pom.xml
index 79f7a89..88625c5 100755
--- a/adapters/oidc/as7-eap6/as7-adapter/pom.xml
+++ b/adapters/oidc/as7-eap6/as7-adapter/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-as7-integration-pom</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
diff --git a/adapters/oidc/as7-eap6/as7-adapter-spi/pom.xml b/adapters/oidc/as7-eap6/as7-adapter-spi/pom.xml
index 2253a4a..03d68cc 100755
--- a/adapters/oidc/as7-eap6/as7-adapter-spi/pom.xml
+++ b/adapters/oidc/as7-eap6/as7-adapter-spi/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-as7-integration-pom</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
diff --git a/adapters/oidc/as7-eap6/as7-subsystem/pom.xml b/adapters/oidc/as7-eap6/as7-subsystem/pom.xml
index 8943fb5..35ff17a 100755
--- a/adapters/oidc/as7-eap6/as7-subsystem/pom.xml
+++ b/adapters/oidc/as7-eap6/as7-subsystem/pom.xml
@@ -21,7 +21,7 @@
<parent>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-as7-integration-pom</artifactId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
adapters/oidc/as7-eap6/pom.xml 2(+1 -1)
diff --git a/adapters/oidc/as7-eap6/pom.xml b/adapters/oidc/as7-eap6/pom.xml
index 65333f2..edd1761 100755
--- a/adapters/oidc/as7-eap6/pom.xml
+++ b/adapters/oidc/as7-eap6/pom.xml
@@ -20,7 +20,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<name>Keycloak AS7 / JBoss EAP 6 Integration</name>
adapters/oidc/installed/pom.xml 2(+1 -1)
diff --git a/adapters/oidc/installed/pom.xml b/adapters/oidc/installed/pom.xml
index 5935dda..aede1db 100755
--- a/adapters/oidc/installed/pom.xml
+++ b/adapters/oidc/installed/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
diff --git a/adapters/oidc/jaxrs-oauth-client/pom.xml b/adapters/oidc/jaxrs-oauth-client/pom.xml
index 75ec83a..dd04fd1 100755
--- a/adapters/oidc/jaxrs-oauth-client/pom.xml
+++ b/adapters/oidc/jaxrs-oauth-client/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
adapters/oidc/jetty/jetty8.1/pom.xml 2(+1 -1)
diff --git a/adapters/oidc/jetty/jetty8.1/pom.xml b/adapters/oidc/jetty/jetty8.1/pom.xml
index 71d2710..51431bf 100755
--- a/adapters/oidc/jetty/jetty8.1/pom.xml
+++ b/adapters/oidc/jetty/jetty8.1/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
adapters/oidc/jetty/jetty9.1/pom.xml 2(+1 -1)
diff --git a/adapters/oidc/jetty/jetty9.1/pom.xml b/adapters/oidc/jetty/jetty9.1/pom.xml
index c489ff0..2263af2 100755
--- a/adapters/oidc/jetty/jetty9.1/pom.xml
+++ b/adapters/oidc/jetty/jetty9.1/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
adapters/oidc/jetty/jetty9.2/pom.xml 2(+1 -1)
diff --git a/adapters/oidc/jetty/jetty9.2/pom.xml b/adapters/oidc/jetty/jetty9.2/pom.xml
index ae5bf10..c090f8f 100755
--- a/adapters/oidc/jetty/jetty9.2/pom.xml
+++ b/adapters/oidc/jetty/jetty9.2/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
diff --git a/adapters/oidc/jetty/jetty-core/pom.xml b/adapters/oidc/jetty/jetty-core/pom.xml
index 66f747a..9c95f53 100755
--- a/adapters/oidc/jetty/jetty-core/pom.xml
+++ b/adapters/oidc/jetty/jetty-core/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
diff --git a/adapters/oidc/jetty/jetty-core/src/main/java/org/keycloak/adapters/jetty/core/AbstractKeycloakJettyAuthenticator.java b/adapters/oidc/jetty/jetty-core/src/main/java/org/keycloak/adapters/jetty/core/AbstractKeycloakJettyAuthenticator.java
index f8d7278..56d8f51 100755
--- a/adapters/oidc/jetty/jetty-core/src/main/java/org/keycloak/adapters/jetty/core/AbstractKeycloakJettyAuthenticator.java
+++ b/adapters/oidc/jetty/jetty-core/src/main/java/org/keycloak/adapters/jetty/core/AbstractKeycloakJettyAuthenticator.java
@@ -18,6 +18,8 @@
package org.keycloak.adapters.jetty.core;
import org.eclipse.jetty.security.DefaultUserIdentity;
+import org.eclipse.jetty.security.IdentityService;
+import org.eclipse.jetty.security.LoginService;
import org.eclipse.jetty.security.ServerAuthException;
import org.eclipse.jetty.security.UserAuthentication;
import org.eclipse.jetty.security.authentication.DeferredAuthentication;
@@ -135,10 +137,44 @@ public abstract class AbstractKeycloakJettyAuthenticator extends LoginAuthentica
return new DefaultUserIdentity(theSubject, principal, theRoles);
}
+ private class DummyLoginService implements LoginService {
+ @Override
+ public String getName() {
+ return null;
+ }
+
+ @Override
+ public UserIdentity login(String username, Object credentials) {
+ return null;
+ }
+
+ @Override
+ public boolean validate(UserIdentity user) {
+ return false;
+ }
+
+ @Override
+ public IdentityService getIdentityService() {
+ return null;
+ }
+
+ @Override
+ public void setIdentityService(IdentityService service) {
+
+ }
+
+ @Override
+ public void logout(UserIdentity user) {
+
+ }
+ }
+
@Override
public void setConfiguration(AuthConfiguration configuration) {
//super.setConfiguration(configuration);
initializeKeycloak();
+ // need this so that getUserPrincipal does not throw NPE
+ _loginService = new DummyLoginService();
String error = configuration.getInitParameter(FormAuthenticator.__FORM_ERROR_PAGE);
setErrorPage(error);
}
adapters/oidc/jetty/pom.xml 2(+1 -1)
diff --git a/adapters/oidc/jetty/pom.xml b/adapters/oidc/jetty/pom.xml
index c5d37ea..6300953 100755
--- a/adapters/oidc/jetty/pom.xml
+++ b/adapters/oidc/jetty/pom.xml
@@ -20,7 +20,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<name>Keycloak Jetty Integration</name>
adapters/oidc/js/pom.xml 2(+1 -1)
diff --git a/adapters/oidc/js/pom.xml b/adapters/oidc/js/pom.xml
index e147af2..315f4b7 100755
--- a/adapters/oidc/js/pom.xml
+++ b/adapters/oidc/js/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
adapters/oidc/osgi-adapter/pom.xml 2(+1 -1)
diff --git a/adapters/oidc/osgi-adapter/pom.xml b/adapters/oidc/osgi-adapter/pom.xml
index dd3115e..f453ffd 100755
--- a/adapters/oidc/osgi-adapter/pom.xml
+++ b/adapters/oidc/osgi-adapter/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
adapters/oidc/pom.xml 2(+1 -1)
diff --git a/adapters/oidc/pom.xml b/adapters/oidc/pom.xml
index fc0e302..1398672 100755
--- a/adapters/oidc/pom.xml
+++ b/adapters/oidc/pom.xml
@@ -20,7 +20,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<name>Keycloak OIDC Client Adapter Modules</name>
adapters/oidc/servlet-filter/pom.xml 2(+1 -1)
diff --git a/adapters/oidc/servlet-filter/pom.xml b/adapters/oidc/servlet-filter/pom.xml
index 808272c..2e607c2 100755
--- a/adapters/oidc/servlet-filter/pom.xml
+++ b/adapters/oidc/servlet-filter/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
diff --git a/adapters/oidc/servlet-filter/src/main/java/org/keycloak/adapters/servlet/OIDCFilterSessionStore.java b/adapters/oidc/servlet-filter/src/main/java/org/keycloak/adapters/servlet/OIDCFilterSessionStore.java
index 8a3010d..70a67de 100755
--- a/adapters/oidc/servlet-filter/src/main/java/org/keycloak/adapters/servlet/OIDCFilterSessionStore.java
+++ b/adapters/oidc/servlet-filter/src/main/java/org/keycloak/adapters/servlet/OIDCFilterSessionStore.java
@@ -89,6 +89,7 @@ public class OIDCFilterSessionStore extends FilterSessionStore implements Adapte
protected void cleanSession(HttpSession session) {
session.removeAttribute(KeycloakAccount.class.getName());
+ session.removeAttribute(KeycloakSecurityContext.class.getName());
clearSavedRequest(session);
}
@@ -160,6 +161,7 @@ public class OIDCFilterSessionStore extends FilterSessionStore implements Adapte
SerializableKeycloakAccount sAccount = new SerializableKeycloakAccount(roles, account.getPrincipal(), securityContext);
HttpSession httpSession = request.getSession();
httpSession.setAttribute(KeycloakAccount.class.getName(), sAccount);
+ httpSession.setAttribute(KeycloakSecurityContext.class.getName(), sAccount.getKeycloakSecurityContext());
if (idMapper != null) idMapper.map(account.getKeycloakSecurityContext().getToken().getClientSession(), account.getPrincipal().getName(), httpSession.getId());
//String username = securityContext.getToken().getSubject();
//log.fine("userSessionManagement.login: " + username);
diff --git a/adapters/oidc/servlet-oauth-client/pom.xml b/adapters/oidc/servlet-oauth-client/pom.xml
index acebeec..c3f9a9a 100755
--- a/adapters/oidc/servlet-oauth-client/pom.xml
+++ b/adapters/oidc/servlet-oauth-client/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
adapters/oidc/spring-boot/pom.xml 2(+1 -1)
diff --git a/adapters/oidc/spring-boot/pom.xml b/adapters/oidc/spring-boot/pom.xml
index 1c4998f..e06f6ef 100755
--- a/adapters/oidc/spring-boot/pom.xml
+++ b/adapters/oidc/spring-boot/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
diff --git a/adapters/oidc/spring-security/pom.xml b/adapters/oidc/spring-security/pom.xml
index 6943e5e..5644c0f 100755
--- a/adapters/oidc/spring-security/pom.xml
+++ b/adapters/oidc/spring-security/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
adapters/oidc/tomcat/pom.xml 2(+1 -1)
diff --git a/adapters/oidc/tomcat/pom.xml b/adapters/oidc/tomcat/pom.xml
index dcbe1b4..a3b172a 100755
--- a/adapters/oidc/tomcat/pom.xml
+++ b/adapters/oidc/tomcat/pom.xml
@@ -20,7 +20,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<name>Keycloak Tomcat Integration</name>
adapters/oidc/tomcat/tomcat6/pom.xml 2(+1 -1)
diff --git a/adapters/oidc/tomcat/tomcat6/pom.xml b/adapters/oidc/tomcat/tomcat6/pom.xml
index 6dddf97..8130e60 100755
--- a/adapters/oidc/tomcat/tomcat6/pom.xml
+++ b/adapters/oidc/tomcat/tomcat6/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-tomcat-integration-pom</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
adapters/oidc/tomcat/tomcat7/pom.xml 2(+1 -1)
diff --git a/adapters/oidc/tomcat/tomcat7/pom.xml b/adapters/oidc/tomcat/tomcat7/pom.xml
index fc5e76d..eeed2c8 100755
--- a/adapters/oidc/tomcat/tomcat7/pom.xml
+++ b/adapters/oidc/tomcat/tomcat7/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-tomcat-integration-pom</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
adapters/oidc/tomcat/tomcat8/pom.xml 2(+1 -1)
diff --git a/adapters/oidc/tomcat/tomcat8/pom.xml b/adapters/oidc/tomcat/tomcat8/pom.xml
index 74e8d1d..5eea623 100755
--- a/adapters/oidc/tomcat/tomcat8/pom.xml
+++ b/adapters/oidc/tomcat/tomcat8/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-tomcat-integration-pom</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
diff --git a/adapters/oidc/tomcat/tomcat-core/pom.xml b/adapters/oidc/tomcat/tomcat-core/pom.xml
index 66ca303..27f7acb 100755
--- a/adapters/oidc/tomcat/tomcat-core/pom.xml
+++ b/adapters/oidc/tomcat/tomcat-core/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-tomcat-integration-pom</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
diff --git a/adapters/oidc/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/CatalinaSessionTokenStore.java b/adapters/oidc/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/CatalinaSessionTokenStore.java
index 7734c2d..0a07e9e 100755
--- a/adapters/oidc/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/CatalinaSessionTokenStore.java
+++ b/adapters/oidc/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/CatalinaSessionTokenStore.java
@@ -69,12 +69,22 @@ public class CatalinaSessionTokenStore extends CatalinaAdapterSessionStore imple
// just in case session got serialized
if (session.getDeployment() == null) session.setCurrentRequestInfo(deployment, this);
- if (session.isActive() && !session.getDeployment().isAlwaysRefreshToken()) return;
+ if (session.isActive() && !session.getDeployment().isAlwaysRefreshToken()) {
+ request.setAttribute(KeycloakSecurityContext.class.getName(), session);
+ request.setUserPrincipal(account.getPrincipal());
+ request.setAuthType("KEYCLOAK");
+ return;
+ }
// FYI: A refresh requires same scope, so same roles will be set. Otherwise, refresh will fail and token will
// not be updated
boolean success = session.refreshExpiredToken(false);
- if (success && session.isActive()) return;
+ if (success && session.isActive()) {
+ request.setAttribute(KeycloakSecurityContext.class.getName(), session);
+ request.setUserPrincipal(account.getPrincipal());
+ request.setAuthType("KEYCLOAK");
+ return;
+ }
// Refresh failed, so user is already logged out from keycloak. Cleanup and expire our session
log.fine("Cleanup and expire session " + catalinaSession.getId() + " after failed refresh");
@@ -85,6 +95,8 @@ public class CatalinaSessionTokenStore extends CatalinaAdapterSessionStore imple
}
protected void cleanSession(Session catalinaSession) {
+ catalinaSession.getSession().removeAttribute(KeycloakSecurityContext.class.getName());
+ catalinaSession.getSession().removeAttribute(SerializableKeycloakAccount.class.getName());
catalinaSession.getSession().removeAttribute(OidcKeycloakAccount.class.getName());
catalinaSession.setPrincipal(null);
catalinaSession.setAuthType(null);
@@ -164,6 +176,7 @@ public class CatalinaSessionTokenStore extends CatalinaAdapterSessionStore imple
session.setPrincipal(principal);
session.setAuthType("KEYCLOAK");
session.getSession().setAttribute(SerializableKeycloakAccount.class.getName(), sAccount);
+ session.getSession().setAttribute(KeycloakSecurityContext.class.getName(), account.getKeycloakSecurityContext());
String username = securityContext.getToken().getSubject();
log.fine("userSessionManagement.login: " + username);
this.sessionManagement.login(session);
adapters/oidc/undertow/pom.xml 2(+1 -1)
diff --git a/adapters/oidc/undertow/pom.xml b/adapters/oidc/undertow/pom.xml
index 71f7dc7..4a4df89 100755
--- a/adapters/oidc/undertow/pom.xml
+++ b/adapters/oidc/undertow/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
diff --git a/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/ServletSessionTokenStore.java b/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/ServletSessionTokenStore.java
index 63c27d7..5db3ead 100755
--- a/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/ServletSessionTokenStore.java
+++ b/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/ServletSessionTokenStore.java
@@ -92,7 +92,8 @@ public class ServletSessionTokenStore implements AdapterTokenStore {
} else {
log.debug("Refresh failed. Account was not active. Returning null and invalidating Http session");
try {
- session.setAttribute(KeycloakUndertowAccount.class.getName(), null);
+ session.removeAttribute(KeycloakUndertowAccount.class.getName());
+ session.removeAttribute(KeycloakSecurityContext.class.getName());
session.invalidate();
} catch (Exception e) {
log.debug("Failed to invalidate session, might already be invalidated");
@@ -106,6 +107,7 @@ public class ServletSessionTokenStore implements AdapterTokenStore {
final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
HttpSession session = getSession(true);
session.setAttribute(KeycloakUndertowAccount.class.getName(), account);
+ session.setAttribute(KeycloakSecurityContext.class.getName(), account.getKeycloakSecurityContext());
sessionManagement.login(servletRequestContext.getDeployment().getSessionManager());
}
diff --git a/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowSessionTokenStore.java b/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowSessionTokenStore.java
index de57268..80a7109 100755
--- a/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowSessionTokenStore.java
+++ b/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowSessionTokenStore.java
@@ -22,6 +22,7 @@ import io.undertow.server.HttpServerExchange;
import io.undertow.server.session.Session;
import io.undertow.util.Sessions;
import org.jboss.logging.Logger;
+import org.keycloak.KeycloakSecurityContext;
import org.keycloak.adapters.AdapterTokenStore;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.OidcKeycloakAccount;
@@ -82,6 +83,7 @@ public class UndertowSessionTokenStore implements AdapterTokenStore {
} else {
log.debug("Account was not active, returning false");
session.removeAttribute(KeycloakUndertowAccount.class.getName());
+ session.removeAttribute(KeycloakSecurityContext.class.getName());
session.invalidate(exchange);
return false;
}
@@ -101,6 +103,7 @@ public class UndertowSessionTokenStore implements AdapterTokenStore {
public void saveAccountInfo(OidcKeycloakAccount account) {
Session session = Sessions.getOrCreateSession(exchange);
session.setAttribute(KeycloakUndertowAccount.class.getName(), account);
+ session.setAttribute(KeycloakSecurityContext.class.getName(), account.getKeycloakSecurityContext());
sessionManagement.login(session.getSessionManager());
}
@@ -111,6 +114,7 @@ public class UndertowSessionTokenStore implements AdapterTokenStore {
KeycloakUndertowAccount account = (KeycloakUndertowAccount)session.getAttribute(KeycloakUndertowAccount.class.getName());
if (account == null) return;
session.removeAttribute(KeycloakUndertowAccount.class.getName());
+ session.removeAttribute(KeycloakSecurityContext.class.getName());
}
@Override
adapters/oidc/wildfly/pom.xml 2(+1 -1)
diff --git a/adapters/oidc/wildfly/pom.xml b/adapters/oidc/wildfly/pom.xml
index 1e9267b..69d41aa 100755
--- a/adapters/oidc/wildfly/pom.xml
+++ b/adapters/oidc/wildfly/pom.xml
@@ -20,7 +20,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<name>Keycloak WildFly Integration</name>
diff --git a/adapters/oidc/wildfly/wf8-subsystem/pom.xml b/adapters/oidc/wildfly/wf8-subsystem/pom.xml
index 8d66b1d..edddfed 100755
--- a/adapters/oidc/wildfly/wf8-subsystem/pom.xml
+++ b/adapters/oidc/wildfly/wf8-subsystem/pom.xml
@@ -21,7 +21,7 @@
<parent>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-parent</artifactId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../../../pom.xml</relativePath>
</parent>
diff --git a/adapters/oidc/wildfly/wildfly-adapter/pom.xml b/adapters/oidc/wildfly/wildfly-adapter/pom.xml
index fd585c4..3f55e63 100755
--- a/adapters/oidc/wildfly/wildfly-adapter/pom.xml
+++ b/adapters/oidc/wildfly/wildfly-adapter/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
diff --git a/adapters/oidc/wildfly/wildfly-subsystem/pom.xml b/adapters/oidc/wildfly/wildfly-subsystem/pom.xml
index d17c12e..5d36bb8 100755
--- a/adapters/oidc/wildfly/wildfly-subsystem/pom.xml
+++ b/adapters/oidc/wildfly/wildfly-subsystem/pom.xml
@@ -21,7 +21,7 @@
<parent>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-parent</artifactId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../../../pom.xml</relativePath>
</parent>
adapters/pom.xml 2(+1 -1)
diff --git a/adapters/pom.xml b/adapters/pom.xml
index 0003a4f..060978e 100755
--- a/adapters/pom.xml
+++ b/adapters/pom.xml
@@ -20,7 +20,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<name>Keycloak Integration</name>
diff --git a/adapters/saml/as7-eap6/adapter/pom.xml b/adapters/saml/as7-eap6/adapter/pom.xml
index 43016f8..13787ef 100755
--- a/adapters/saml/as7-eap6/adapter/pom.xml
+++ b/adapters/saml/as7-eap6/adapter/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-saml-eap-integration-pom</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
adapters/saml/as7-eap6/pom.xml 2(+1 -1)
diff --git a/adapters/saml/as7-eap6/pom.xml b/adapters/saml/as7-eap6/pom.xml
index e919401..265a1f8 100755
--- a/adapters/saml/as7-eap6/pom.xml
+++ b/adapters/saml/as7-eap6/pom.xml
@@ -20,7 +20,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<name>Keycloak SAML EAP Integration</name>
diff --git a/adapters/saml/as7-eap6/subsystem/pom.xml b/adapters/saml/as7-eap6/subsystem/pom.xml
index 993d01f..4c92714 100755
--- a/adapters/saml/as7-eap6/subsystem/pom.xml
+++ b/adapters/saml/as7-eap6/subsystem/pom.xml
@@ -21,7 +21,7 @@
<parent>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-saml-eap-integration-pom</artifactId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
adapters/saml/core/pom.xml 2(+1 -1)
diff --git a/adapters/saml/core/pom.xml b/adapters/saml/core/pom.xml
index 7751fb2..27b658e 100755
--- a/adapters/saml/core/pom.xml
+++ b/adapters/saml/core/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
adapters/saml/jetty/jetty8.1/pom.xml 2(+1 -1)
diff --git a/adapters/saml/jetty/jetty8.1/pom.xml b/adapters/saml/jetty/jetty8.1/pom.xml
index 36f97e7..85b49d1 100755
--- a/adapters/saml/jetty/jetty8.1/pom.xml
+++ b/adapters/saml/jetty/jetty8.1/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
adapters/saml/jetty/jetty9.1/pom.xml 2(+1 -1)
diff --git a/adapters/saml/jetty/jetty9.1/pom.xml b/adapters/saml/jetty/jetty9.1/pom.xml
index 5ec3c72..dd7f2b1 100755
--- a/adapters/saml/jetty/jetty9.1/pom.xml
+++ b/adapters/saml/jetty/jetty9.1/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
adapters/saml/jetty/jetty9.2/pom.xml 2(+1 -1)
diff --git a/adapters/saml/jetty/jetty9.2/pom.xml b/adapters/saml/jetty/jetty9.2/pom.xml
index dd370fd..b194a2e 100755
--- a/adapters/saml/jetty/jetty9.2/pom.xml
+++ b/adapters/saml/jetty/jetty9.2/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
diff --git a/adapters/saml/jetty/jetty-core/pom.xml b/adapters/saml/jetty/jetty-core/pom.xml
index 674c74c..81a081c 100755
--- a/adapters/saml/jetty/jetty-core/pom.xml
+++ b/adapters/saml/jetty/jetty-core/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
diff --git a/adapters/saml/jetty/jetty-core/src/main/java/org/keycloak/adapters/saml/jetty/AbstractSamlAuthenticator.java b/adapters/saml/jetty/jetty-core/src/main/java/org/keycloak/adapters/saml/jetty/AbstractSamlAuthenticator.java
index 07f2a40..eb17fee 100755
--- a/adapters/saml/jetty/jetty-core/src/main/java/org/keycloak/adapters/saml/jetty/AbstractSamlAuthenticator.java
+++ b/adapters/saml/jetty/jetty-core/src/main/java/org/keycloak/adapters/saml/jetty/AbstractSamlAuthenticator.java
@@ -18,6 +18,8 @@
package org.keycloak.adapters.saml.jetty;
import org.eclipse.jetty.security.DefaultUserIdentity;
+import org.eclipse.jetty.security.IdentityService;
+import org.eclipse.jetty.security.LoginService;
import org.eclipse.jetty.security.ServerAuthException;
import org.eclipse.jetty.security.UserAuthentication;
import org.eclipse.jetty.security.authentication.DeferredAuthentication;
@@ -135,12 +137,46 @@ public abstract class AbstractSamlAuthenticator extends LoginAuthenticator {
}
+ private class DummyLoginService implements LoginService {
+ @Override
+ public String getName() {
+ return null;
+ }
+
+ @Override
+ public UserIdentity login(String username, Object credentials) {
+ return null;
+ }
+
+ @Override
+ public boolean validate(UserIdentity user) {
+ return false;
+ }
+
+ @Override
+ public IdentityService getIdentityService() {
+ return null;
+ }
+
+ @Override
+ public void setIdentityService(IdentityService service) {
+
+ }
+
+ @Override
+ public void logout(UserIdentity user) {
+
+ }
+ }
+
@Override
public void setConfiguration(AuthConfiguration configuration) {
//super.setConfiguration(configuration);
initializeKeycloak();
+ // need this so that getUserPrincipal does not throw NPE
+ _loginService = new DummyLoginService();
String error = configuration.getInitParameter(FormAuthenticator.__FORM_ERROR_PAGE);
setErrorPage(error);
}
adapters/saml/jetty/pom.xml 2(+1 -1)
diff --git a/adapters/saml/jetty/pom.xml b/adapters/saml/jetty/pom.xml
index 70e130c..62b951e 100755
--- a/adapters/saml/jetty/pom.xml
+++ b/adapters/saml/jetty/pom.xml
@@ -20,7 +20,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<name>Keycloak SAML Jetty Integration</name>
adapters/saml/pom.xml 2(+1 -1)
diff --git a/adapters/saml/pom.xml b/adapters/saml/pom.xml
index 22b03c2..646a009 100755
--- a/adapters/saml/pom.xml
+++ b/adapters/saml/pom.xml
@@ -20,7 +20,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<name>Keycloak SAML Client Adapter Modules</name>
adapters/saml/servlet-filter/pom.xml 2(+1 -1)
diff --git a/adapters/saml/servlet-filter/pom.xml b/adapters/saml/servlet-filter/pom.xml
index 25b8b34..3f1fea4 100755
--- a/adapters/saml/servlet-filter/pom.xml
+++ b/adapters/saml/servlet-filter/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
adapters/saml/tomcat/pom.xml 2(+1 -1)
diff --git a/adapters/saml/tomcat/pom.xml b/adapters/saml/tomcat/pom.xml
index be42d18..e863a45 100755
--- a/adapters/saml/tomcat/pom.xml
+++ b/adapters/saml/tomcat/pom.xml
@@ -20,7 +20,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<name>Keycloak SAML Tomcat Integration</name>
adapters/saml/tomcat/tomcat6/pom.xml 2(+1 -1)
diff --git a/adapters/saml/tomcat/tomcat6/pom.xml b/adapters/saml/tomcat/tomcat6/pom.xml
index 1e1c7bd..edac214 100755
--- a/adapters/saml/tomcat/tomcat6/pom.xml
+++ b/adapters/saml/tomcat/tomcat6/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-saml-tomcat-integration-pom</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
adapters/saml/tomcat/tomcat7/pom.xml 2(+1 -1)
diff --git a/adapters/saml/tomcat/tomcat7/pom.xml b/adapters/saml/tomcat/tomcat7/pom.xml
index ea6dd78..1278ec2 100755
--- a/adapters/saml/tomcat/tomcat7/pom.xml
+++ b/adapters/saml/tomcat/tomcat7/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-saml-tomcat-integration-pom</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
adapters/saml/tomcat/tomcat8/pom.xml 2(+1 -1)
diff --git a/adapters/saml/tomcat/tomcat8/pom.xml b/adapters/saml/tomcat/tomcat8/pom.xml
index 4867e82..038d499 100755
--- a/adapters/saml/tomcat/tomcat8/pom.xml
+++ b/adapters/saml/tomcat/tomcat8/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-saml-tomcat-integration-pom</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
diff --git a/adapters/saml/tomcat/tomcat-core/pom.xml b/adapters/saml/tomcat/tomcat-core/pom.xml
index b776ec5..a377a94 100755
--- a/adapters/saml/tomcat/tomcat-core/pom.xml
+++ b/adapters/saml/tomcat/tomcat-core/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-saml-tomcat-integration-pom</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
diff --git a/adapters/saml/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/saml/AbstractSamlAuthenticatorValve.java b/adapters/saml/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/saml/AbstractSamlAuthenticatorValve.java
index 51bdb4b..aa75439 100755
--- a/adapters/saml/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/saml/AbstractSamlAuthenticatorValve.java
+++ b/adapters/saml/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/saml/AbstractSamlAuthenticatorValve.java
@@ -167,9 +167,9 @@ public abstract class AbstractSamlAuthenticatorValve extends FormAuthenticator i
@Override
public void invoke(Request request, Response response) throws IOException, ServletException {
log.fine("*********************** SAML ************");
+ CatalinaHttpFacade facade = new CatalinaHttpFacade(response, request);
+ SamlDeployment deployment = deploymentContext.resolveDeployment(facade);
if (request.getRequestURI().substring(request.getContextPath().length()).endsWith("/saml")) {
- CatalinaHttpFacade facade = new CatalinaHttpFacade(response, request);
- SamlDeployment deployment = deploymentContext.resolveDeployment(facade);
if (deployment != null && deployment.isConfigured()) {
SamlSessionStore tokenStore = getSessionStore(request, facade, deployment);
SamlAuthenticator authenticator = new CatalinaSamlEndpoint(facade, deployment, tokenStore);
@@ -180,6 +180,7 @@ public abstract class AbstractSamlAuthenticatorValve extends FormAuthenticator i
}
try {
+ getSessionStore(request, facade, deployment).isLoggedIn(); // sets request UserPrincipal if logged in. we do this so that the UserPrincipal is available on unsecured, unconstrainted URLs
super.invoke(request, response);
} finally {
}
adapters/saml/undertow/pom.xml 2(+1 -1)
diff --git a/adapters/saml/undertow/pom.xml b/adapters/saml/undertow/pom.xml
index 9a82918..74cfa78 100755
--- a/adapters/saml/undertow/pom.xml
+++ b/adapters/saml/undertow/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
adapters/saml/wildfly/pom.xml 2(+1 -1)
diff --git a/adapters/saml/wildfly/pom.xml b/adapters/saml/wildfly/pom.xml
index 035af90..708a4c8 100755
--- a/adapters/saml/wildfly/pom.xml
+++ b/adapters/saml/wildfly/pom.xml
@@ -20,7 +20,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<name>Keycloak SAML Wildfly Integration</name>
diff --git a/adapters/saml/wildfly/wildfly-adapter/pom.xml b/adapters/saml/wildfly/wildfly-adapter/pom.xml
index cd64e12..2dd2ed6 100755
--- a/adapters/saml/wildfly/wildfly-adapter/pom.xml
+++ b/adapters/saml/wildfly/wildfly-adapter/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
diff --git a/adapters/saml/wildfly/wildfly-subsystem/pom.xml b/adapters/saml/wildfly/wildfly-subsystem/pom.xml
index 6fc49fe..7861150 100755
--- a/adapters/saml/wildfly/wildfly-subsystem/pom.xml
+++ b/adapters/saml/wildfly/wildfly-subsystem/pom.xml
@@ -21,7 +21,7 @@
<parent>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-parent</artifactId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../../../pom.xml</relativePath>
</parent>
adapters/spi/adapter-spi/pom.xml 2(+1 -1)
diff --git a/adapters/spi/adapter-spi/pom.xml b/adapters/spi/adapter-spi/pom.xml
index d9eabbd..d0f2837 100755
--- a/adapters/spi/adapter-spi/pom.xml
+++ b/adapters/spi/adapter-spi/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
diff --git a/adapters/spi/jboss-adapter-core/pom.xml b/adapters/spi/jboss-adapter-core/pom.xml
index f9bf0b0..503ca08 100755
--- a/adapters/spi/jboss-adapter-core/pom.xml
+++ b/adapters/spi/jboss-adapter-core/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
diff --git a/adapters/spi/jetty-adapter-spi/pom.xml b/adapters/spi/jetty-adapter-spi/pom.xml
index 908d6c1..568d718 100755
--- a/adapters/spi/jetty-adapter-spi/pom.xml
+++ b/adapters/spi/jetty-adapter-spi/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
adapters/spi/pom.xml 2(+1 -1)
diff --git a/adapters/spi/pom.xml b/adapters/spi/pom.xml
index 5f7f231..362a249 100755
--- a/adapters/spi/pom.xml
+++ b/adapters/spi/pom.xml
@@ -20,7 +20,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<name>Keycloak Client Adapter SPI Modules</name>
diff --git a/adapters/spi/servlet-adapter-spi/pom.xml b/adapters/spi/servlet-adapter-spi/pom.xml
index 45095ec..dbe6e96 100755
--- a/adapters/spi/servlet-adapter-spi/pom.xml
+++ b/adapters/spi/servlet-adapter-spi/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
diff --git a/adapters/spi/tomcat-adapter-spi/pom.xml b/adapters/spi/tomcat-adapter-spi/pom.xml
index d830f52..c094474 100755
--- a/adapters/spi/tomcat-adapter-spi/pom.xml
+++ b/adapters/spi/tomcat-adapter-spi/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
diff --git a/adapters/spi/undertow-adapter-spi/pom.xml b/adapters/spi/undertow-adapter-spi/pom.xml
index 06dc8c6..e057685 100755
--- a/adapters/spi/undertow-adapter-spi/pom.xml
+++ b/adapters/spi/undertow-adapter-spi/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
common/pom.xml 2(+1 -1)
diff --git a/common/pom.xml b/common/pom.xml
index 9cbe55c..ad165ad 100755
--- a/common/pom.xml
+++ b/common/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
diff --git a/common/src/main/java/org/keycloak/common/util/KerberosJdkProvider.java b/common/src/main/java/org/keycloak/common/util/KerberosJdkProvider.java
index 00a5f12..ffd42f7 100644
--- a/common/src/main/java/org/keycloak/common/util/KerberosJdkProvider.java
+++ b/common/src/main/java/org/keycloak/common/util/KerberosJdkProvider.java
@@ -56,7 +56,7 @@ public abstract class KerberosJdkProvider {
return kerberosTicketToGSSCredential(kerberosTicket, GSSCredential.DEFAULT_LIFETIME, GSSCredential.INITIATE_ONLY);
}
- // Actually same on both JDKs
+ // Actually can use same on both JDKs
public GSSCredential kerberosTicketToGSSCredential(KerberosTicket kerberosTicket, final int lifetime, final int usage) {
try {
final GSSManager gssManager = GSSManager.getInstance();
@@ -85,7 +85,7 @@ public abstract class KerberosJdkProvider {
public static KerberosJdkProvider getProvider() {
- if (KerberosSerializationUtils.JAVA_INFO.contains("IBM")) {
+ if (Environment.IS_IBM_JAVA) {
return new IBMJDKProvider();
} else {
return new SunJDKProvider();
core/pom.xml 2(+1 -1)
diff --git a/core/pom.xml b/core/pom.xml
index 72768b2..3bcd9ef 100755
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
diff --git a/core/src/main/java/org/keycloak/AbstractOAuthClient.java b/core/src/main/java/org/keycloak/AbstractOAuthClient.java
old mode 100755
new mode 100644
index bf75b57..5eeb399
--- a/core/src/main/java/org/keycloak/AbstractOAuthClient.java
+++ b/core/src/main/java/org/keycloak/AbstractOAuthClient.java
@@ -110,6 +110,14 @@ public class AbstractOAuthClient {
this.publicClient = publicClient;
}
+ public boolean isSecure() {
+ return isSecure;
+ }
+
+ public void setSecure(boolean secure) {
+ isSecure = secure;
+ }
+
public RelativeUrlsUsed getRelativeUrlsUsed() {
return relativeUrlsUsed;
}
diff --git a/core/src/main/java/org/keycloak/Config.java b/core/src/main/java/org/keycloak/Config.java
index f619393..0624006 100755
--- a/core/src/main/java/org/keycloak/Config.java
+++ b/core/src/main/java/org/keycloak/Config.java
@@ -135,7 +135,11 @@ public class Config {
@Override
public Boolean getBoolean(String key, Boolean defaultValue) {
String v = get(key, null);
- return v != null ? Boolean.parseBoolean(v) : defaultValue;
+ if (v != null) {
+ return Boolean.parseBoolean(v);
+ } else {
+ return defaultValue;
+ }
}
@Override
diff --git a/core/src/main/java/org/keycloak/KeycloakSecurityContext.java b/core/src/main/java/org/keycloak/KeycloakSecurityContext.java
index 3806ffe..118fd1b 100755
--- a/core/src/main/java/org/keycloak/KeycloakSecurityContext.java
+++ b/core/src/main/java/org/keycloak/KeycloakSecurityContext.java
@@ -28,6 +28,9 @@ import java.io.ObjectOutputStream;
import java.io.Serializable;
/**
+ * Available in secured requests under HttpServlerRequest.getAttribute()
+ * Also available in HttpSession.getAttribute under the classname of this class
+ *
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
diff --git a/core/src/main/java/org/keycloak/KeyPairVerifier.java b/core/src/main/java/org/keycloak/KeyPairVerifier.java
new file mode 100644
index 0000000..c8b6dce
--- /dev/null
+++ b/core/src/main/java/org/keycloak/KeyPairVerifier.java
@@ -0,0 +1,59 @@
+/*
+ * 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;
+
+import org.keycloak.common.VerificationException;
+import org.keycloak.common.util.PemUtils;
+import org.keycloak.jose.jws.JWSBuilder;
+import org.keycloak.jose.jws.JWSInput;
+import org.keycloak.jose.jws.crypto.RSAProvider;
+
+import java.security.PrivateKey;
+import java.security.PublicKey;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class KeyPairVerifier {
+
+ public static void verify(String privateKeyPem, String publicKeyPem) throws VerificationException {
+ PrivateKey privateKey;
+ try {
+ privateKey = PemUtils.decodePrivateKey(privateKeyPem);
+ } catch (Exception e) {
+ throw new VerificationException("Failed to decode private key");
+ }
+
+ PublicKey publicKey;
+ try {
+ publicKey = PemUtils.decodePublicKey(publicKeyPem);
+ } catch (Exception e) {
+ throw new VerificationException("Failed to decode public key");
+ }
+
+ try {
+ String jws = new JWSBuilder().content("content".getBytes()).rsa256(privateKey);
+ if (!RSAProvider.verify(new JWSInput(jws), publicKey)) {
+ throw new VerificationException("Keys don't match");
+ }
+ } catch (Exception e) {
+ throw new VerificationException("Keys don't match");
+ }
+ }
+
+}
diff --git a/core/src/main/java/org/keycloak/representations/KeyStoreConfig.java b/core/src/main/java/org/keycloak/representations/KeyStoreConfig.java
new file mode 100644
index 0000000..7826694
--- /dev/null
+++ b/core/src/main/java/org/keycloak/representations/KeyStoreConfig.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2016 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * 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.representations;
+
+/**
+ * Configuration of KeyStore.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc.
+ */
+public class KeyStoreConfig {
+
+ protected Boolean realmCertificate;
+ protected String storePassword;
+ protected String keyPassword;
+ protected String keyAlias;
+ protected String realmAlias;
+ protected String format;
+
+ public Boolean isRealmCertificate() {
+ return realmCertificate;
+ }
+
+ public void setRealmCertificate(Boolean realmCertificate) {
+ this.realmCertificate = realmCertificate;
+ }
+
+ public String getStorePassword() {
+ return storePassword;
+ }
+
+ public void setStorePassword(String storePassword) {
+ this.storePassword = storePassword;
+ }
+
+ public String getKeyPassword() {
+ return keyPassword;
+ }
+
+ public void setKeyPassword(String keyPassword) {
+ this.keyPassword = keyPassword;
+ }
+
+ public String getKeyAlias() {
+ return keyAlias;
+ }
+
+ public void setKeyAlias(String keyAlias) {
+ this.keyAlias = keyAlias;
+ }
+
+ public String getRealmAlias() {
+ return realmAlias;
+ }
+
+ public void setRealmAlias(String realmAlias) {
+ this.realmAlias = realmAlias;
+ }
+
+ public String getFormat() {
+ return format;
+ }
+
+ public void setFormat(String format) {
+ this.format = format;
+ }
+}
diff --git a/core/src/test/java/org/keycloak/KeyPairVerifierTest.java b/core/src/test/java/org/keycloak/KeyPairVerifierTest.java
new file mode 100644
index 0000000..da76a61
--- /dev/null
+++ b/core/src/test/java/org/keycloak/KeyPairVerifierTest.java
@@ -0,0 +1,69 @@
+/*
+ * 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;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.keycloak.common.VerificationException;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class KeyPairVerifierTest {
+
+ String privateKey1 = "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y=";
+ String publicKey1 = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB";
+
+ String privateKey2048 = "-----BEGIN RSA PRIVATE KEY-----\n" + "MIIEpQIBAAKCAQEA4V3MpOnuKsdBbR1UzNjK9o5meEMQ4s5Vpykhv1DpqTilKOiE\n"
+ + "H7VQ/XtjNxw0yjnFBilCnpK6yN9mDEHbBEzaRjtdrgVhkIejiaXFBP5MBhUQ5l9u\n" + "8E3IZC3E8pwDjVF0Z9u0R4lGeUg2k6O+NKumqIvxoLCTuG0zf53bctGsRd57LuFi\n"
+ + "pgCkNyxvscOhulsbEMYrLwlb5bMGgx9v+RCnwvunNEb7RK+5pzP+iH1MRejRsX+U\n" + "7h9zHRn2gQhIl7SzG9GXebuPWr4KKwfMHWy0PEuQrsfWRXm9/dTEavbfNkv5E53z\n"
+ + "WXjWyf93ezkVhBX0YoXmf6UO7PAlvsrjno3TuwIDAQABAoIBAQC5iCAOcCtLemhp\n" + "bOlADwXgPtErFoNTROyMxjbrKrCCSIjniawj8oAvfiHq38Sx6ydBcDxREZjF/+wi\n"
+ + "ESE+hAp6ISt5NSLh+lhu3FK7TqLFqxgTn+NT36Umm+t0k231LGa5jcz3y5KCDCoq\n" + "F3ZiJCH6xeLxGA00mmn4GLvt5aF+jiO80ICGs4iUg99IoXhc5u/VU0hB5J78BinW\n"
+ + "inkCABuBNkDLgIqc9BoH4L5MOx3zDqzmHffeq9+2V4X7NiD5QyiyWtABaQpEIY5k\n" + "R48RTno6xN3hvG48/DwkO2gABSLQ/OJd3Hupv4wlmmSc1xo93CaV44hq2i2GsU1i\n"
+ + "m6d3xDW5AoGBAPCfkvPkqr88xg+8Cu3G/3GFpUsQ0VEme+8dIjXMTJHa13K7xaRh\n" + "GHCVg4a8oHJ/P/vNSwvPyR71iRX4csqkKSaprvJk8vxbU539unmHWKkfUHrywQlz\n"
+ + "q4OuXOjOdvILLOTsu3/+k6vAIE6SZJiDmf2eGxi9Qbm5rlxE3h3HRAKfAoGBAO/E\n" + "ogHV86LmnJTJbx1hP3IfRHk0qaiSj35ljlAz+3v6GN/KSUYCWTtp2GjRIKY3qQ8I\n"
+ + "7l+PVTFg3SY7cPq2C9TE+6xroiWkUd2JldPLYSxpWpFNYlo709SzmLquDho+fwJC\n" + "nAxoxKghsXJarz7TRfNyFqDXscS6oQLurU9P5lVlAoGBAJh1QvLtS5Jnu0Z06qfF\n"
+ + "kkwnVZe+TCGStKvIVciobUts0V2Mw6lnK8kJspBIK5DgN3YfmREe0lufTwBwrqre\n" + "YIRytro2ZA6o/s332ZLuwqpFgQSlktGeTGnerFeFma+6jPNvW025y27jCJVABCTu\n"
+ + "HT+oUZrXLzGyCFvF9sX/X4QZAoGBAICap4r0h0nJCBOGN+M6Vh2QR9n7NUUF15Gk\n" + "R0EdoLZO3yiqB8NVXydPDpSqFykQkc1OrQz0hG2H1xa6q07OdmoZfiRtVvt5t69s\n"
+ + "LMD9RZHcsIdfSnG7xVNBQZpf4ZCSFO3RbIH7b//+kn8TxQudptd9SkXba65prBM2\n" + "kh8IbDNBAoGAVsKvkruH7RK7CimDSWcdAKvHARqkjs/PoeKEEY8Yu6zf0Z9TQM5l\n"
+ + "uC9EwBamYcSusWRcdcz+9HYG58XFnmXq+3EUuFbJ+Ljb8YWBgePjSHDoS/6+/+zq\n" + "B1b5uQp/jYFbYQl50UPRPTF+ul1eQoy7F43Ngj3/5cDRarFZe3ZTzZo=\n"
+ + "-----END RSA PRIVATE KEY-----";
+ String publicKey2048 = "-----BEGIN PUBLIC KEY-----\n" + "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4V3MpOnuKsdBbR1UzNjK\n"
+ + "9o5meEMQ4s5Vpykhv1DpqTilKOiEH7VQ/XtjNxw0yjnFBilCnpK6yN9mDEHbBEza\n" + "RjtdrgVhkIejiaXFBP5MBhUQ5l9u8E3IZC3E8pwDjVF0Z9u0R4lGeUg2k6O+NKum\n"
+ + "qIvxoLCTuG0zf53bctGsRd57LuFipgCkNyxvscOhulsbEMYrLwlb5bMGgx9v+RCn\n" + "wvunNEb7RK+5pzP+iH1MRejRsX+U7h9zHRn2gQhIl7SzG9GXebuPWr4KKwfMHWy0\n"
+ + "PEuQrsfWRXm9/dTEavbfNkv5E53zWXjWyf93ezkVhBX0YoXmf6UO7PAlvsrjno3T\n" + "uwIDAQAB\n" + "-----END PUBLIC KEY-----";
+
+ @Test
+ public void verify() throws Exception {
+ KeyPairVerifier.verify(privateKey1, publicKey1);
+ KeyPairVerifier.verify(privateKey2048, publicKey2048);
+
+ try {
+ KeyPairVerifier.verify(privateKey1, publicKey2048);
+ Assert.fail("Expected VerificationException");
+ } catch (VerificationException e) {
+ }
+
+ try {
+ KeyPairVerifier.verify(privateKey2048, publicKey1);
+ Assert.fail("Expected VerificationException");
+ } catch (VerificationException e) {
+ }
+ }
+
+}
dependencies/pom.xml 2(+1 -1)
diff --git a/dependencies/pom.xml b/dependencies/pom.xml
index 1682881..622b1e0 100755
--- a/dependencies/pom.xml
+++ b/dependencies/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
dependencies/server-all/pom.xml 2(+1 -1)
diff --git a/dependencies/server-all/pom.xml b/dependencies/server-all/pom.xml
index 33c80b1..75bda3e 100755
--- a/dependencies/server-all/pom.xml
+++ b/dependencies/server-all/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
dependencies/server-min/pom.xml 2(+1 -1)
diff --git a/dependencies/server-min/pom.xml b/dependencies/server-min/pom.xml
index fb072f6..126ff9e 100755
--- a/dependencies/server-min/pom.xml
+++ b/dependencies/server-min/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
diff --git a/distribution/adapters/as7-eap6-adapter/as7-adapter-zip/pom.xml b/distribution/adapters/as7-eap6-adapter/as7-adapter-zip/pom.xml
index a6e510d..33a30cd 100755
--- a/distribution/adapters/as7-eap6-adapter/as7-adapter-zip/pom.xml
+++ b/distribution/adapters/as7-eap6-adapter/as7-adapter-zip/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../../../pom.xml</relativePath>
</parent>
diff --git a/distribution/adapters/as7-eap6-adapter/as7-modules/pom.xml b/distribution/adapters/as7-eap6-adapter/as7-modules/pom.xml
index bc7d813..189495c 100755
--- a/distribution/adapters/as7-eap6-adapter/as7-modules/pom.xml
+++ b/distribution/adapters/as7-eap6-adapter/as7-modules/pom.xml
@@ -25,7 +25,7 @@
<parent>
<artifactId>keycloak-as7-eap6-adapter-dist-pom</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
diff --git a/distribution/adapters/as7-eap6-adapter/eap6-adapter-zip/pom.xml b/distribution/adapters/as7-eap6-adapter/eap6-adapter-zip/pom.xml
index 3101406..a73e608 100755
--- a/distribution/adapters/as7-eap6-adapter/eap6-adapter-zip/pom.xml
+++ b/distribution/adapters/as7-eap6-adapter/eap6-adapter-zip/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-as7-eap6-adapter-dist-pom</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
diff --git a/distribution/adapters/as7-eap6-adapter/pom.xml b/distribution/adapters/as7-eap6-adapter/pom.xml
index 50c75af..a209f73 100644
--- a/distribution/adapters/as7-eap6-adapter/pom.xml
+++ b/distribution/adapters/as7-eap6-adapter/pom.xml
@@ -20,7 +20,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<name>Keycloak AS7 / JBoss EAP 6 Adapter Distros</name>
diff --git a/distribution/adapters/jetty81-adapter-zip/pom.xml b/distribution/adapters/jetty81-adapter-zip/pom.xml
index ee2c736..53cda9b 100755
--- a/distribution/adapters/jetty81-adapter-zip/pom.xml
+++ b/distribution/adapters/jetty81-adapter-zip/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
diff --git a/distribution/adapters/jetty91-adapter-zip/pom.xml b/distribution/adapters/jetty91-adapter-zip/pom.xml
index a894294..2377171 100755
--- a/distribution/adapters/jetty91-adapter-zip/pom.xml
+++ b/distribution/adapters/jetty91-adapter-zip/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
diff --git a/distribution/adapters/jetty92-adapter-zip/pom.xml b/distribution/adapters/jetty92-adapter-zip/pom.xml
index 0dab89c..b1f4b41 100755
--- a/distribution/adapters/jetty92-adapter-zip/pom.xml
+++ b/distribution/adapters/jetty92-adapter-zip/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
diff --git a/distribution/adapters/js-adapter-zip/pom.xml b/distribution/adapters/js-adapter-zip/pom.xml
index a26f432..3a2836f 100755
--- a/distribution/adapters/js-adapter-zip/pom.xml
+++ b/distribution/adapters/js-adapter-zip/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
diff --git a/distribution/adapters/osgi/features/pom.xml b/distribution/adapters/osgi/features/pom.xml
index 24e77d4..7bd2236 100755
--- a/distribution/adapters/osgi/features/pom.xml
+++ b/distribution/adapters/osgi/features/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../../../pom.xml</relativePath>
</parent>
<name>Keycloak OSGI Features</name>
diff --git a/distribution/adapters/osgi/jaas/pom.xml b/distribution/adapters/osgi/jaas/pom.xml
index 093a760..bbb0a3a 100755
--- a/distribution/adapters/osgi/jaas/pom.xml
+++ b/distribution/adapters/osgi/jaas/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../../../pom.xml</relativePath>
</parent>
<name>Keycloak OSGI JAAS Realm Configuration</name>
distribution/adapters/osgi/pom.xml 2(+1 -1)
diff --git a/distribution/adapters/osgi/pom.xml b/distribution/adapters/osgi/pom.xml
index 74c30aa..2829909 100755
--- a/distribution/adapters/osgi/pom.xml
+++ b/distribution/adapters/osgi/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<name>Keycloak OSGI Integration</name>
diff --git a/distribution/adapters/osgi/thirdparty/pom.xml b/distribution/adapters/osgi/thirdparty/pom.xml
index d796451..23e2ab2 100755
--- a/distribution/adapters/osgi/thirdparty/pom.xml
+++ b/distribution/adapters/osgi/thirdparty/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../../../pom.xml</relativePath>
</parent>
distribution/adapters/pom.xml 2(+1 -1)
diff --git a/distribution/adapters/pom.xml b/distribution/adapters/pom.xml
index 29dfce2..91e512f 100755
--- a/distribution/adapters/pom.xml
+++ b/distribution/adapters/pom.xml
@@ -20,7 +20,7 @@
<parent>
<artifactId>keycloak-distribution-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<name>Adapters Distribution Parent</name>
diff --git a/distribution/adapters/tomcat6-adapter-zip/pom.xml b/distribution/adapters/tomcat6-adapter-zip/pom.xml
index 1d797e8..9f91013 100755
--- a/distribution/adapters/tomcat6-adapter-zip/pom.xml
+++ b/distribution/adapters/tomcat6-adapter-zip/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
diff --git a/distribution/adapters/tomcat7-adapter-zip/pom.xml b/distribution/adapters/tomcat7-adapter-zip/pom.xml
index b6005af..b74a7d7 100755
--- a/distribution/adapters/tomcat7-adapter-zip/pom.xml
+++ b/distribution/adapters/tomcat7-adapter-zip/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
diff --git a/distribution/adapters/tomcat8-adapter-zip/pom.xml b/distribution/adapters/tomcat8-adapter-zip/pom.xml
index 36dfa05..2aba29c 100755
--- a/distribution/adapters/tomcat8-adapter-zip/pom.xml
+++ b/distribution/adapters/tomcat8-adapter-zip/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
diff --git a/distribution/adapters/wf8-adapter/pom.xml b/distribution/adapters/wf8-adapter/pom.xml
index 59cef87..aa6a3d7 100644
--- a/distribution/adapters/wf8-adapter/pom.xml
+++ b/distribution/adapters/wf8-adapter/pom.xml
@@ -20,7 +20,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<name>Keycloak Wildfly 8 Adapter</name>
diff --git a/distribution/adapters/wf8-adapter/wf8-adapter-zip/pom.xml b/distribution/adapters/wf8-adapter/wf8-adapter-zip/pom.xml
index 48d91fa..98d4d2d 100755
--- a/distribution/adapters/wf8-adapter/wf8-adapter-zip/pom.xml
+++ b/distribution/adapters/wf8-adapter/wf8-adapter-zip/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../../../pom.xml</relativePath>
</parent>
diff --git a/distribution/adapters/wf8-adapter/wf8-modules/pom.xml b/distribution/adapters/wf8-adapter/wf8-modules/pom.xml
index 1fba204..72a2727 100755
--- a/distribution/adapters/wf8-adapter/wf8-modules/pom.xml
+++ b/distribution/adapters/wf8-adapter/wf8-modules/pom.xml
@@ -25,7 +25,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../../../pom.xml</relativePath>
</parent>
diff --git a/distribution/adapters/wildfly-adapter/pom.xml b/distribution/adapters/wildfly-adapter/pom.xml
index f36704c..4084253 100644
--- a/distribution/adapters/wildfly-adapter/pom.xml
+++ b/distribution/adapters/wildfly-adapter/pom.xml
@@ -20,7 +20,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<name>Keycloak Wildfly Adapter</name>
diff --git a/distribution/adapters/wildfly-adapter/wildfly-adapter-zip/pom.xml b/distribution/adapters/wildfly-adapter/wildfly-adapter-zip/pom.xml
index 855cd8b..34c0a42 100755
--- a/distribution/adapters/wildfly-adapter/wildfly-adapter-zip/pom.xml
+++ b/distribution/adapters/wildfly-adapter/wildfly-adapter-zip/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../../../pom.xml</relativePath>
</parent>
diff --git a/distribution/adapters/wildfly-adapter/wildfly-modules/pom.xml b/distribution/adapters/wildfly-adapter/wildfly-modules/pom.xml
index e817ec0..4fb605e 100755
--- a/distribution/adapters/wildfly-adapter/wildfly-modules/pom.xml
+++ b/distribution/adapters/wildfly-adapter/wildfly-modules/pom.xml
@@ -25,7 +25,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../../../pom.xml</relativePath>
</parent>
distribution/demo-dist/pom.xml 2(+1 -1)
diff --git a/distribution/demo-dist/pom.xml b/distribution/demo-dist/pom.xml
index 3da79e9..76ec682 100755
--- a/distribution/demo-dist/pom.xml
+++ b/distribution/demo-dist/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-distribution-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<artifactId>keycloak-demo-dist</artifactId>
distribution/docs-dist/pom.xml 2(+1 -1)
diff --git a/distribution/docs-dist/pom.xml b/distribution/docs-dist/pom.xml
index 44d1f74..a25e72d 100755
--- a/distribution/docs-dist/pom.xml
+++ b/distribution/docs-dist/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-distribution-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<artifactId>keycloak-docs-dist</artifactId>
distribution/downloads/pom.xml 2(+1 -1)
diff --git a/distribution/downloads/pom.xml b/distribution/downloads/pom.xml
index e2d67d7..3e1e7f1 100755
--- a/distribution/downloads/pom.xml
+++ b/distribution/downloads/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-distribution-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<artifactId>keycloak-dist-downloads</artifactId>
distribution/examples-dist/pom.xml 2(+1 -1)
diff --git a/distribution/examples-dist/pom.xml b/distribution/examples-dist/pom.xml
index f30995b..eb1b1ef 100755
--- a/distribution/examples-dist/pom.xml
+++ b/distribution/examples-dist/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-distribution-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<artifactId>keycloak-examples-dist</artifactId>
diff --git a/distribution/feature-packs/adapter-feature-pack/pom.xml b/distribution/feature-packs/adapter-feature-pack/pom.xml
index a7c8035..3bced41 100755
--- a/distribution/feature-packs/adapter-feature-pack/pom.xml
+++ b/distribution/feature-packs/adapter-feature-pack/pom.xml
@@ -19,7 +19,7 @@
<parent>
<groupId>org.keycloak</groupId>
<artifactId>feature-packs-parent</artifactId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
distribution/feature-packs/pom.xml 2(+1 -1)
diff --git a/distribution/feature-packs/pom.xml b/distribution/feature-packs/pom.xml
index 1e01087..0e1c075 100644
--- a/distribution/feature-packs/pom.xml
+++ b/distribution/feature-packs/pom.xml
@@ -20,7 +20,7 @@
<parent>
<artifactId>keycloak-distribution-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<name>Feature Pack Builds</name>
diff --git a/distribution/feature-packs/server-feature-pack/pom.xml b/distribution/feature-packs/server-feature-pack/pom.xml
index 0ddd945..82f2f74 100644
--- a/distribution/feature-packs/server-feature-pack/pom.xml
+++ b/distribution/feature-packs/server-feature-pack/pom.xml
@@ -19,7 +19,7 @@
<parent>
<groupId>org.keycloak</groupId>
<artifactId>feature-packs-parent</artifactId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
distribution/pom.xml 2(+1 -1)
diff --git a/distribution/pom.xml b/distribution/pom.xml
index c4a4d6a..707733c 100755
--- a/distribution/pom.xml
+++ b/distribution/pom.xml
@@ -20,7 +20,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
distribution/proxy-dist/pom.xml 2(+1 -1)
diff --git a/distribution/proxy-dist/pom.xml b/distribution/proxy-dist/pom.xml
index 0d89c45..379fee3 100755
--- a/distribution/proxy-dist/pom.xml
+++ b/distribution/proxy-dist/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-distribution-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<artifactId>keycloak-proxy-dist</artifactId>
diff --git a/distribution/saml-adapters/as7-eap6-adapter/as7-adapter-zip/pom.xml b/distribution/saml-adapters/as7-eap6-adapter/as7-adapter-zip/pom.xml
index 72be75f..0600717 100755
--- a/distribution/saml-adapters/as7-eap6-adapter/as7-adapter-zip/pom.xml
+++ b/distribution/saml-adapters/as7-eap6-adapter/as7-adapter-zip/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../../../pom.xml</relativePath>
</parent>
diff --git a/distribution/saml-adapters/as7-eap6-adapter/as7-modules/pom.xml b/distribution/saml-adapters/as7-eap6-adapter/as7-modules/pom.xml
index a7a6c5b..4206a4c 100755
--- a/distribution/saml-adapters/as7-eap6-adapter/as7-modules/pom.xml
+++ b/distribution/saml-adapters/as7-eap6-adapter/as7-modules/pom.xml
@@ -25,7 +25,7 @@
<parent>
<artifactId>keycloak-saml-as7-eap6-adapter-dist-pom</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
diff --git a/distribution/saml-adapters/as7-eap6-adapter/eap6-adapter-zip/pom.xml b/distribution/saml-adapters/as7-eap6-adapter/eap6-adapter-zip/pom.xml
index 286e25a..848ad41 100755
--- a/distribution/saml-adapters/as7-eap6-adapter/eap6-adapter-zip/pom.xml
+++ b/distribution/saml-adapters/as7-eap6-adapter/eap6-adapter-zip/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-saml-as7-eap6-adapter-dist-pom</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
diff --git a/distribution/saml-adapters/as7-eap6-adapter/pom.xml b/distribution/saml-adapters/as7-eap6-adapter/pom.xml
index 8513a5b..fa1337d 100755
--- a/distribution/saml-adapters/as7-eap6-adapter/pom.xml
+++ b/distribution/saml-adapters/as7-eap6-adapter/pom.xml
@@ -20,7 +20,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<name>Keycloak SAML AS7 / JBoss EAP 6 Adapter Distros</name>
diff --git a/distribution/saml-adapters/jetty81-adapter-zip/pom.xml b/distribution/saml-adapters/jetty81-adapter-zip/pom.xml
index b945a2e..33a6ab7 100755
--- a/distribution/saml-adapters/jetty81-adapter-zip/pom.xml
+++ b/distribution/saml-adapters/jetty81-adapter-zip/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
diff --git a/distribution/saml-adapters/jetty92-adapter-zip/pom.xml b/distribution/saml-adapters/jetty92-adapter-zip/pom.xml
index 0497458..0c790e3 100755
--- a/distribution/saml-adapters/jetty92-adapter-zip/pom.xml
+++ b/distribution/saml-adapters/jetty92-adapter-zip/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
distribution/saml-adapters/pom.xml 2(+1 -1)
diff --git a/distribution/saml-adapters/pom.xml b/distribution/saml-adapters/pom.xml
index bfb566b..b66acfe 100755
--- a/distribution/saml-adapters/pom.xml
+++ b/distribution/saml-adapters/pom.xml
@@ -20,7 +20,7 @@
<parent>
<artifactId>keycloak-distribution-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<name>SAML Adapters Distribution Parent</name>
diff --git a/distribution/saml-adapters/tomcat6-adapter-zip/pom.xml b/distribution/saml-adapters/tomcat6-adapter-zip/pom.xml
index 3a44ec5..025a901 100755
--- a/distribution/saml-adapters/tomcat6-adapter-zip/pom.xml
+++ b/distribution/saml-adapters/tomcat6-adapter-zip/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
diff --git a/distribution/saml-adapters/tomcat7-adapter-zip/pom.xml b/distribution/saml-adapters/tomcat7-adapter-zip/pom.xml
index 607744a..de00474 100755
--- a/distribution/saml-adapters/tomcat7-adapter-zip/pom.xml
+++ b/distribution/saml-adapters/tomcat7-adapter-zip/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
diff --git a/distribution/saml-adapters/tomcat8-adapter-zip/pom.xml b/distribution/saml-adapters/tomcat8-adapter-zip/pom.xml
index b9704e0..25f58b7 100755
--- a/distribution/saml-adapters/tomcat8-adapter-zip/pom.xml
+++ b/distribution/saml-adapters/tomcat8-adapter-zip/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
diff --git a/distribution/saml-adapters/wildfly-adapter/pom.xml b/distribution/saml-adapters/wildfly-adapter/pom.xml
index 95efce0..8f13b8a 100755
--- a/distribution/saml-adapters/wildfly-adapter/pom.xml
+++ b/distribution/saml-adapters/wildfly-adapter/pom.xml
@@ -20,7 +20,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<name>Keycloak Wildfly SAML Adapter</name>
diff --git a/distribution/saml-adapters/wildfly-adapter/wildfly-adapter-zip/pom.xml b/distribution/saml-adapters/wildfly-adapter/wildfly-adapter-zip/pom.xml
index 8edb2e9..cf95ea2 100755
--- a/distribution/saml-adapters/wildfly-adapter/wildfly-adapter-zip/pom.xml
+++ b/distribution/saml-adapters/wildfly-adapter/wildfly-adapter-zip/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../../../pom.xml</relativePath>
</parent>
diff --git a/distribution/saml-adapters/wildfly-adapter/wildfly-modules/pom.xml b/distribution/saml-adapters/wildfly-adapter/wildfly-modules/pom.xml
index cfa4031..061928e 100755
--- a/distribution/saml-adapters/wildfly-adapter/wildfly-modules/pom.xml
+++ b/distribution/saml-adapters/wildfly-adapter/wildfly-modules/pom.xml
@@ -25,7 +25,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../../../pom.xml</relativePath>
</parent>
distribution/server-dist/pom.xml 2(+1 -1)
diff --git a/distribution/server-dist/pom.xml b/distribution/server-dist/pom.xml
index 698fbde..ac12d0e 100755
--- a/distribution/server-dist/pom.xml
+++ b/distribution/server-dist/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-distribution-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<artifactId>keycloak-server-dist</artifactId>
distribution/server-overlay/pom.xml 2(+1 -1)
diff --git a/distribution/server-overlay/pom.xml b/distribution/server-overlay/pom.xml
index 3164f32..d4a3bb5 100755
--- a/distribution/server-overlay/pom.xml
+++ b/distribution/server-overlay/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-distribution-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<artifactId>keycloak-server-overlay</artifactId>
distribution/src-dist/pom.xml 2(+1 -1)
diff --git a/distribution/src-dist/pom.xml b/distribution/src-dist/pom.xml
index acfdceb..4057c7e 100755
--- a/distribution/src-dist/pom.xml
+++ b/distribution/src-dist/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-distribution-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<artifactId>keycloak-src-dist</artifactId>
docbook/auth-server-docs/pom.xml 2(+1 -1)
diff --git a/docbook/auth-server-docs/pom.xml b/docbook/auth-server-docs/pom.xml
index 0e35210..7365106 100755
--- a/docbook/auth-server-docs/pom.xml
+++ b/docbook/auth-server-docs/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-docbook-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
diff --git a/docbook/auth-server-docs/reference/en/en-US/modules/adapter-context.xml b/docbook/auth-server-docs/reference/en/en-US/modules/adapter-context.xml
new file mode 100755
index 0000000..cfcf18f
--- /dev/null
+++ b/docbook/auth-server-docs/reference/en/en-US/modules/adapter-context.xml
@@ -0,0 +1,12 @@
+<chapter>
+ <title>KeycloakSecurityContext</title>
+ <para>
+ The <literal>KeycloakSecurityContext</literal> interface is available if you need to look at the access token directly. This context is also useful if you need to
+ get the encoded access token so you can make additional REST invocations. In servlet environments it is available in secured invocations as an attribute in HttpServletRequest.
+ Or, it is available in secure and insecure requests in the HttpSession for browser apps.
+ <programlisting>
+ httpServletRequest.getAttribute(KeycloakSecurityContext.class.getName());
+ httpServletRequest.getSession().getAttribute(KeycloakSecurityContext.class.getName());
+ </programlisting>
+ </para>
+</chapter>
\ No newline at end of file
diff --git a/docbook/auth-server-docs/reference/en/en-US/modules/cache.xml b/docbook/auth-server-docs/reference/en/en-US/modules/cache.xml
index 4d68eb4..dec9ecf 100755
--- a/docbook/auth-server-docs/reference/en/en-US/modules/cache.xml
+++ b/docbook/auth-server-docs/reference/en/en-US/modules/cache.xml
@@ -23,11 +23,21 @@
</para>
<section>
+ <title>Eviction and Expiration</title>
+
+ <para>
+ By default the user cache contains a maximum of 10000 entries. This is not 10000 users, but 10000 entries in the cache. You can change the maximum
+ number of entries by editing the server configuration <literal>standalone.xml</literal> or <literal>standalone-ha.xml</literal>.
+ Locate the element <literal>cache-container name="keycloak"</literal> and change the eviction policy for the <literal>users</literal> cache. For
+ more information see <ulink url="https://docs.jboss.org/author/display/WFLY10/Infinispan+Subsystem">Infinispan Subsystem documentation</ulink>.
+ </para>
+ </section>
+
+ <section>
<title>Disabling Caches</title>
<para>
- The realm and user caches can be cleared through the management console. To
- disable the realm or user cache, you must edit the <literal>keycloak-server.json</literal> file
- in your distribution. Here's what the config looks like initially.
+ To disable the realm or user cache, you must edit the <literal>keycloak-server.json</literal> file
+ in your distribution. Here's what the config looks like initially.
</para>
<para>
<programlisting><![CDATA[
@@ -44,7 +54,7 @@
},
]]></programlisting>
</para>
- <para>You must then change it to:
+ <para>To disable the cache set the enabled field to false for the cache you want to disable:
<programlisting><![CDATA[
"userCache": {
"infinispan" : {
@@ -60,11 +70,12 @@
]]></programlisting>
</para>
</section>
+
<section>
<title>Clear Caches</title>
<para>
- To clear the realm or user cache, go to the Keycloak admin console Realm Settings->Cache Config page. Disable the cache
- you want. This will cause the cache to be cleared.
+ To clear the realm or user cache, go to the Keycloak admin console Realm Settings->Cache Config page. On this page you can clear the realm cache
+ or the user cache. This will clear the caches for all realms and not only the selected realm.
</para>
</section>
</chapter>
diff --git a/docbook/auth-server-docs/reference/en/en-US/modules/clustering.xml b/docbook/auth-server-docs/reference/en/en-US/modules/clustering.xml
index 850326e..cb36fc4 100755
--- a/docbook/auth-server-docs/reference/en/en-US/modules/clustering.xml
+++ b/docbook/auth-server-docs/reference/en/en-US/modules/clustering.xml
@@ -57,6 +57,29 @@
database. This can be a relational database or Mongo. To make sure your database doesn't become a single
point of failure you may also want to deploy your database to a cluster.
</para>
+ <section>
+ <title>DB lock</title>
+ <para>Note that Keycloak supports concurrent startup by more cluster nodes at the same. This is ensured by DB lock, which prevents that some
+ startup actions (migrating database from previous version, importing realms at startup, initial bootstrap of admin user) are always executed just by one
+ cluster node at a time and other cluster nodes need to wait until the current node finishes startup actions and release the DB lock.
+ </para>
+ <para>
+ By default, the maximum timeout for lock is 900 seconds, so in case that second node is not able to acquire the lock within 900 seconds, it fails to start.
+ The lock checking is done every 2 seconds by default. Typically you won't need to increase/decrease the default value, but just in case
+ it's possible to configure it in <literal>standalone/configuration/keycloak-server.json</literal>:
+<programlisting>
+<![CDATA[
+"dblock": {
+ "jpa": {
+ "lockWaitTimeout": 900,
+ "lockRecheckTime": 2
+ }
+}
+]]>
+</programlisting>
+ or similarly if you're using Mongo (just by replace <literal>jpa</literal> with <literal>mongo</literal>)
+ </para>
+ </section>
</section>
<section>
diff --git a/docbook/auth-server-docs/reference/en/en-US/modules/javascript-adapter.xml b/docbook/auth-server-docs/reference/en/en-US/modules/javascript-adapter.xml
index 4090712..74a3e27 100755
--- a/docbook/auth-server-docs/reference/en/en-US/modules/javascript-adapter.xml
+++ b/docbook/auth-server-docs/reference/en/en-US/modules/javascript-adapter.xml
@@ -30,7 +30,7 @@
</para>
<para>
To use this adapter, you must first configure an application (or client) through the <literal>Keycloak Admin Console</literal>.
- You should select <literal>public</literal> for the <literal>Client Type</literal> field. As public clients can't
+ You should select <literal>public</literal> for the <literal>Access Type</literal> field. As public clients can't
be verified with a client secret, you are required to configure one or more valid redirect uris.
Once you've configured the application, click on the <literal>Installation</literal> tab and download the <literal>keycloak.json</literal>
file. This file should be hosted on your web-server at the same root as your HTML pages. Alternatively, you can manually
diff --git a/docbook/auth-server-docs/reference/en/en-US/modules/kerberos.xml b/docbook/auth-server-docs/reference/en/en-US/modules/kerberos.xml
index e89df83..1d5a1c9 100644
--- a/docbook/auth-server-docs/reference/en/en-US/modules/kerberos.xml
+++ b/docbook/auth-server-docs/reference/en/en-US/modules/kerberos.xml
@@ -154,7 +154,7 @@ ktadd -k /tmp/http.keytab HTTP/www.mydomain.org@MYDOMAIN.ORG
</listitem>
<listitem>
<para>
- Finally run Keycloak server and configure SPNEGO/Kerberos authentication in Keycloak admin console. Keycloak supports Kerberos authentication
+ Run Keycloak server and configure SPNEGO/Kerberos authentication in Keycloak admin console. Keycloak supports Kerberos authentication
through <link linkend='user_federation'>Federation provider SPI</link> . We have 2 federation providers with Kerberos authentication support:
<variablelist>
<varlistentry>
@@ -185,6 +185,20 @@ ktadd -k /tmp/http.keytab HTTP/www.mydomain.org@MYDOMAIN.ORG
</variablelist>
</para>
</listitem>
+ <listitem>
+ <para>
+ Finally you may need to check the Kerberos authenticator correctly configured. You can go to <literal>Authentication</literal> tab in
+ admin console and select <literal>Browser</literal> flow. Here you will see <literal>Kerberos</literal> authenticator, which is used by Keycloak for SPNEGO
+ handshake with client (exchange <literal>Negotiate</literal> header etc.). By default it's disabled, so Keycloak doesn't ask for Negotiate header, however once you
+ configured federation provider in previous step, it's automatically switched to <literal>ALTERNATIVE</literal>. So defacto you don't need to do anything, just
+ check that it's really switched to Alternative.
+ </para>
+ <para>
+ Alternative means that Keycloak tries to ask browser for Negotiate header, but if it's not available, it will continue on next authenticator (which usually means
+ displaying username/password form to user). You can switch to <literal>REQUIRED</literal> if you want to enforce login with
+ kerberos ticket and not allow fallback to username/password form.
+ </para>
+ </listitem>
</itemizedlist>
</section>
<section>
diff --git a/docbook/auth-server-docs/reference/en/en-US/modules/server-installation.xml b/docbook/auth-server-docs/reference/en/en-US/modules/server-installation.xml
index aceff9b..9d3237d 100755
--- a/docbook/auth-server-docs/reference/en/en-US/modules/server-installation.xml
+++ b/docbook/auth-server-docs/reference/en/en-US/modules/server-installation.xml
@@ -128,7 +128,13 @@ bin/add-user.[sh|bat] -r master -u <username> -p <password>
]]></programlisting>
Then restart the server.
</para>
- </section>
+ <para>
+ For <literal>keycloak-overlay</literal>, please make sure to use:
+<programlisting><![CDATA[
+bin/add-user-keycloak.[sh|bat] -r master -u <username> -p <password>
+]]></programlisting>
+ </para>
+ </section>
<section>
<title>Relational Database Configuration</title>
@@ -214,8 +220,8 @@ bin/add-user.[sh|bat] -r master -u <username> -p <password>
<title>Tested databases</title>
<para>
Here is list of RDBMS databases and corresponding JDBC drivers, which were tested with Keycloak. Note that Hibernate dialect
- is usually set automatically according to your database, but in some cases, you must manually set the proper dialect,
- as the default dialect may not work correctly. You can setup dialect by adding property <literal>driverDialect</literal>
+ is usually set automatically according to your database, but you have possibility to override if default dialect doesn't work correctly.
+ You can setup dialect by adding property <literal>driverDialect</literal>
to the <literal>keycloak-server.json</literal> into <literal>connectionsJpa</literal> section (see above).
<table frame='all'><title>Tested databases</title>
<tgroup cols='3' align='left' colsep='1' rowsep='1'>
@@ -255,7 +261,7 @@ bin/add-user.[sh|bat] -r master -u <username> -p <password>
<row>
<entry>Microsoft SQL Server 2012</entry>
<entry>Microsoft SQL Server JDBC Driver 4.0.2206.100</entry>
- <entry>org.hibernate.dialect.SQLServer2008Dialect</entry>
+ <entry>auto</entry>
</row>
<row>
<entry>IBM DB2 10.5</entry>
@@ -832,7 +838,7 @@ $ keytool -import -alias yourdomain -keystore keycloak.jks -file your-certificat
<link linkend='themes'>Themes</link> sections for more information on how to do this.
</para>
</section>
-
+
<section>
<title>Installing Keycloak Server as Root Context</title>
<para>
@@ -855,11 +861,11 @@ $ keytool -import -alias yourdomain -keystore keycloak.jks -file your-certificat
<note>
If you have run your server before altering the root context, your database
will contain references to the old /auth context. Your clients may also have incorrect
- references. To fix this on the server side, you will need to <link linkend="export-import">export
- your database to json, make corrections, and then import.</link> Client-side keycloak.json
+ references. To fix this on the server side, you will need to <link linkend="export-import">export
+ your database to json, make corrections, and then import.</link> Client-side keycloak.json
files will need to be updated manually as well.
</note>
</para>
-
+
</section>
</chapter>
diff --git a/docbook/auth-server-docs/reference/en/en-US/modules/user-federation.xml b/docbook/auth-server-docs/reference/en/en-US/modules/user-federation.xml
index f44e6ab..7cbd966 100755
--- a/docbook/auth-server-docs/reference/en/en-US/modules/user-federation.xml
+++ b/docbook/auth-server-docs/reference/en/en-US/modules/user-federation.xml
@@ -294,8 +294,24 @@
We have an example, which is showing LDAP integration and set of base mappers and sample mappers (mappers for street and postalCode) . It's in <literal>examples/ldap</literal>
in the Keycloak example distribution or demo distribution download. You can also check the example sources directly <ulink url="https://github.com/keycloak/keycloak/blob/master/examples/ldap">here</ulink> .
</para>
+ <section>
+ <title>Writing your own LDAP Mapper</title>
+ <para>
+ For the more advanced usecases, you have the possibility to create your own implementation of LDAP mapper or just subclass from
+ some already existing mapper implementation. You will need to implement <literal>UserFederationMapperFactory</literal> interface. In most cases, instead of
+ creating <literal>UserFederationMapperFactory</literal> from scratch, you can create subclasses of <literal>AbstractLDAPFederationMapperFactory</literal>, which itself
+ implements <literal>UserFederationMapperFactory</literal>. Then you need to create mapper implementation, which will be subclass of
+ <literal>AbstractLDAPFederationMapper</literal> (this mapper implementation will be returned by <literal>YourAbstractLDAPFederationMapperFactorySubclass.createMapper</literal> method).
+ </para>
+ <para>
+ After your code is written you must package up all your classes within a JAR file. This jar file must contain a file called
+ <literal>org.keycloak.mappers.UserFederationMapperFactory</literal> within the <literal>META-INF/services directory</literal> of the JAR. This file is a list of fully
+ qualified classnames of all implementations of <literal>UserFederationMapperFactory</literal>. For more details, look at section for
+ <link linkend="write_federation_provider">Write your own federation provider</link> and at <link linkend="providers">Providers and SPI</link> section.
+ </para>
+ </section>
</section>
- <section>
+ <section id="write_federation_provider">
<title>Writing your own User Federation Provider</title>
<para>
The keycloak examples directory contains an example of a simple User Federation Provider backed by
docbook/pom.xml 2(+1 -1)
diff --git a/docbook/pom.xml b/docbook/pom.xml
index a0c9c0a..fc9e55b 100755
--- a/docbook/pom.xml
+++ b/docbook/pom.xml
@@ -20,7 +20,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<name>Keycloak Documentation</name>
docbook/saml-adapter-docs/pom.xml 2(+1 -1)
diff --git a/docbook/saml-adapter-docs/pom.xml b/docbook/saml-adapter-docs/pom.xml
index 65411ec..3d418b6 100755
--- a/docbook/saml-adapter-docs/pom.xml
+++ b/docbook/saml-adapter-docs/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-docbook-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
examples/admin-client/pom.xml 2(+1 -1)
diff --git a/examples/admin-client/pom.xml b/examples/admin-client/pom.xml
index a93ce0a..f210c62 100755
--- a/examples/admin-client/pom.xml
+++ b/examples/admin-client/pom.xml
@@ -22,7 +22,7 @@
<parent>
<artifactId>keycloak-examples-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<name>Keycloak Examples - Admin Client</name>
examples/basic-auth/pom.xml 2(+1 -1)
diff --git a/examples/basic-auth/pom.xml b/examples/basic-auth/pom.xml
index 6e457ab..df1a594 100755
--- a/examples/basic-auth/pom.xml
+++ b/examples/basic-auth/pom.xml
@@ -23,7 +23,7 @@
<parent>
<artifactId>keycloak-examples-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<name>Keycloak Examples - Basic Auth</name>
diff --git a/examples/broker/facebook-authentication/pom.xml b/examples/broker/facebook-authentication/pom.xml
index 975968b..cdd609c 100755
--- a/examples/broker/facebook-authentication/pom.xml
+++ b/examples/broker/facebook-authentication/pom.xml
@@ -23,7 +23,7 @@
<parent>
<artifactId>keycloak-examples-broker-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<name>Keycloak Broker Examples - Facebook Authentication</name>
diff --git a/examples/broker/google-authentication/pom.xml b/examples/broker/google-authentication/pom.xml
index be71066..a7dd2fa 100755
--- a/examples/broker/google-authentication/pom.xml
+++ b/examples/broker/google-authentication/pom.xml
@@ -23,7 +23,7 @@
<parent>
<artifactId>keycloak-examples-broker-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<name>Keycloak Broker Examples - Google Authentication</name>
examples/broker/pom.xml 2(+1 -1)
diff --git a/examples/broker/pom.xml b/examples/broker/pom.xml
index b9aba5a..b104917 100755
--- a/examples/broker/pom.xml
+++ b/examples/broker/pom.xml
@@ -20,7 +20,7 @@
<parent>
<artifactId>keycloak-examples-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<name>Broker Examples</name>
diff --git a/examples/broker/saml-broker-authentication/pom.xml b/examples/broker/saml-broker-authentication/pom.xml
index aa6ee47..c5640ae 100755
--- a/examples/broker/saml-broker-authentication/pom.xml
+++ b/examples/broker/saml-broker-authentication/pom.xml
@@ -23,7 +23,7 @@
<parent>
<artifactId>keycloak-examples-broker-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<name>Keycloak Broker Examples - SAML Identity Provider Brokering</name>
diff --git a/examples/broker/twitter-authentication/pom.xml b/examples/broker/twitter-authentication/pom.xml
index 372ce2b..a4a6ac7 100755
--- a/examples/broker/twitter-authentication/pom.xml
+++ b/examples/broker/twitter-authentication/pom.xml
@@ -23,7 +23,7 @@
<parent>
<artifactId>keycloak-examples-broker-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<name>Keycloak Broker Examples - Twitter Authentication</name>
diff --git a/examples/cors/angular-product-app/pom.xml b/examples/cors/angular-product-app/pom.xml
index b4dbf08..b67e4de 100755
--- a/examples/cors/angular-product-app/pom.xml
+++ b/examples/cors/angular-product-app/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-examples-cors-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
diff --git a/examples/cors/database-service/pom.xml b/examples/cors/database-service/pom.xml
index df482be..6e4fc7d 100755
--- a/examples/cors/database-service/pom.xml
+++ b/examples/cors/database-service/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-examples-cors-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
examples/cors/pom.xml 2(+1 -1)
diff --git a/examples/cors/pom.xml b/examples/cors/pom.xml
index 6613cb7..f0a52d5 100755
--- a/examples/cors/pom.xml
+++ b/examples/cors/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-examples-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<name>Keycloak Examples - CORS</name>
diff --git a/examples/demo-template/admin-access-app/pom.xml b/examples/demo-template/admin-access-app/pom.xml
index 74f5ad3..1a5f460 100755
--- a/examples/demo-template/admin-access-app/pom.xml
+++ b/examples/demo-template/admin-access-app/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-examples-demo-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
diff --git a/examples/demo-template/angular-product-app/pom.xml b/examples/demo-template/angular-product-app/pom.xml
index 14871df..fa8d798 100755
--- a/examples/demo-template/angular-product-app/pom.xml
+++ b/examples/demo-template/angular-product-app/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-examples-demo-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
diff --git a/examples/demo-template/customer-app/pom.xml b/examples/demo-template/customer-app/pom.xml
index 4ad3339..479e282 100755
--- a/examples/demo-template/customer-app/pom.xml
+++ b/examples/demo-template/customer-app/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-examples-demo-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
diff --git a/examples/demo-template/customer-app-cli/pom.xml b/examples/demo-template/customer-app-cli/pom.xml
index b15672a..b6091bc 100755
--- a/examples/demo-template/customer-app-cli/pom.xml
+++ b/examples/demo-template/customer-app-cli/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-examples-demo-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
diff --git a/examples/demo-template/customer-app-filter/pom.xml b/examples/demo-template/customer-app-filter/pom.xml
index 6c141fa..c28fb7e 100755
--- a/examples/demo-template/customer-app-filter/pom.xml
+++ b/examples/demo-template/customer-app-filter/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-examples-demo-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
diff --git a/examples/demo-template/customer-app-js/pom.xml b/examples/demo-template/customer-app-js/pom.xml
index d0fd979..7f5a1bb 100755
--- a/examples/demo-template/customer-app-js/pom.xml
+++ b/examples/demo-template/customer-app-js/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-examples-demo-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
diff --git a/examples/demo-template/database-service/pom.xml b/examples/demo-template/database-service/pom.xml
index 298929f..e209f26 100755
--- a/examples/demo-template/database-service/pom.xml
+++ b/examples/demo-template/database-service/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-examples-demo-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
diff --git a/examples/demo-template/example-ear/pom.xml b/examples/demo-template/example-ear/pom.xml
index 6c03852..4926f3d 100755
--- a/examples/demo-template/example-ear/pom.xml
+++ b/examples/demo-template/example-ear/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-examples-demo-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
diff --git a/examples/demo-template/offline-access-app/pom.xml b/examples/demo-template/offline-access-app/pom.xml
index 990cea2..3965a25 100644
--- a/examples/demo-template/offline-access-app/pom.xml
+++ b/examples/demo-template/offline-access-app/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-examples-demo-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
examples/demo-template/pom.xml 2(+1 -1)
diff --git a/examples/demo-template/pom.xml b/examples/demo-template/pom.xml
index eb5b8c8..2da8e1a 100755
--- a/examples/demo-template/pom.xml
+++ b/examples/demo-template/pom.xml
@@ -20,7 +20,7 @@
<parent>
<artifactId>keycloak-examples-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<name>Examples</name>
diff --git a/examples/demo-template/product-app/pom.xml b/examples/demo-template/product-app/pom.xml
index c055d20..736f0f9 100755
--- a/examples/demo-template/product-app/pom.xml
+++ b/examples/demo-template/product-app/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-examples-demo-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
diff --git a/examples/demo-template/service-account/pom.xml b/examples/demo-template/service-account/pom.xml
index 4dbe8a4..a6a006c 100644
--- a/examples/demo-template/service-account/pom.xml
+++ b/examples/demo-template/service-account/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-examples-demo-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
diff --git a/examples/demo-template/third-party/pom.xml b/examples/demo-template/third-party/pom.xml
index f15a607..8b82458 100755
--- a/examples/demo-template/third-party/pom.xml
+++ b/examples/demo-template/third-party/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-examples-demo-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
diff --git a/examples/demo-template/third-party-cdi/pom.xml b/examples/demo-template/third-party-cdi/pom.xml
index 8ee3f86..af43b9c 100755
--- a/examples/demo-template/third-party-cdi/pom.xml
+++ b/examples/demo-template/third-party-cdi/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-examples-demo-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
examples/fuse/camel/pom.xml 2(+1 -1)
diff --git a/examples/fuse/camel/pom.xml b/examples/fuse/camel/pom.xml
index 5edfef7..e70fb23 100755
--- a/examples/fuse/camel/pom.xml
+++ b/examples/fuse/camel/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-examples-fuse-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
diff --git a/examples/fuse/customer-app-fuse/pom.xml b/examples/fuse/customer-app-fuse/pom.xml
index 2185351..64e4a71 100755
--- a/examples/fuse/customer-app-fuse/pom.xml
+++ b/examples/fuse/customer-app-fuse/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-examples-fuse-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
examples/fuse/cxf-jaxrs/pom.xml 2(+1 -1)
diff --git a/examples/fuse/cxf-jaxrs/pom.xml b/examples/fuse/cxf-jaxrs/pom.xml
index 67462c5..4600f11 100755
--- a/examples/fuse/cxf-jaxrs/pom.xml
+++ b/examples/fuse/cxf-jaxrs/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-examples-fuse-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
examples/fuse/cxf-jaxws/pom.xml 2(+1 -1)
diff --git a/examples/fuse/cxf-jaxws/pom.xml b/examples/fuse/cxf-jaxws/pom.xml
index 37e10b9..494d7ef 100755
--- a/examples/fuse/cxf-jaxws/pom.xml
+++ b/examples/fuse/cxf-jaxws/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-examples-fuse-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
examples/fuse/features/pom.xml 2(+1 -1)
diff --git a/examples/fuse/features/pom.xml b/examples/fuse/features/pom.xml
index a82fc2b..22664f3 100755
--- a/examples/fuse/features/pom.xml
+++ b/examples/fuse/features/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-examples-fuse-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
examples/fuse/pom.xml 2(+1 -1)
diff --git a/examples/fuse/pom.xml b/examples/fuse/pom.xml
index fa5b564..1d89e04 100755
--- a/examples/fuse/pom.xml
+++ b/examples/fuse/pom.xml
@@ -20,7 +20,7 @@
<parent>
<artifactId>keycloak-examples-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<name>Fuse examples</name>
diff --git a/examples/fuse/product-app-fuse/pom.xml b/examples/fuse/product-app-fuse/pom.xml
index ca7f49b..4de29b3 100755
--- a/examples/fuse/product-app-fuse/pom.xml
+++ b/examples/fuse/product-app-fuse/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-examples-fuse-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
examples/fuse/testrealm.json 3(+2 -1)
diff --git a/examples/fuse/testrealm.json b/examples/fuse/testrealm.json
index de93f7d..88afec8 100644
--- a/examples/fuse/testrealm.json
+++ b/examples/fuse/testrealm.json
@@ -69,7 +69,8 @@
],
"realmRoles": [ "user","admin" ],
"clientRoles": {
- "realm-management": [ "realm-admin" ]
+ "realm-management": [ "realm-admin" ],
+ "account": [ "manage-account" ]
}
},
{
examples/js-console/pom.xml 2(+1 -1)
diff --git a/examples/js-console/pom.xml b/examples/js-console/pom.xml
index 8a6c157..e9a784d 100755
--- a/examples/js-console/pom.xml
+++ b/examples/js-console/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-examples-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
examples/kerberos/pom.xml 2(+1 -1)
diff --git a/examples/kerberos/pom.xml b/examples/kerberos/pom.xml
index 226ea63..ca1b196 100755
--- a/examples/kerberos/pom.xml
+++ b/examples/kerberos/pom.xml
@@ -22,7 +22,7 @@
<parent>
<artifactId>keycloak-examples-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<name>Keycloak Examples - Kerberos Credential Delegation</name>
examples/ldap/pom.xml 2(+1 -1)
diff --git a/examples/ldap/pom.xml b/examples/ldap/pom.xml
index 2a09808..f16e8b0 100644
--- a/examples/ldap/pom.xml
+++ b/examples/ldap/pom.xml
@@ -22,7 +22,7 @@
<parent>
<artifactId>keycloak-examples-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
examples/multi-tenant/pom.xml 2(+1 -1)
diff --git a/examples/multi-tenant/pom.xml b/examples/multi-tenant/pom.xml
index 0d1e380..d6271f0 100755
--- a/examples/multi-tenant/pom.xml
+++ b/examples/multi-tenant/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-examples-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<name>Keycloak Examples - Multi Tenant</name>
examples/pom.xml 2(+1 -1)
diff --git a/examples/pom.xml b/examples/pom.xml
index dd91c30..3d55a42 100755
--- a/examples/pom.xml
+++ b/examples/pom.xml
@@ -20,7 +20,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<name>Examples</name>
diff --git a/examples/providers/authenticator/pom.xml b/examples/providers/authenticator/pom.xml
index 35f30a0..0097cae 100755
--- a/examples/providers/authenticator/pom.xml
+++ b/examples/providers/authenticator/pom.xml
@@ -20,7 +20,7 @@
<parent>
<artifactId>keycloak-examples-providers-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<name>Authenticator Example</name>
diff --git a/examples/providers/event-listener-sysout/pom.xml b/examples/providers/event-listener-sysout/pom.xml
index 8050ef7..81a5e50 100755
--- a/examples/providers/event-listener-sysout/pom.xml
+++ b/examples/providers/event-listener-sysout/pom.xml
@@ -20,7 +20,7 @@
<parent>
<artifactId>keycloak-examples-providers-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<name>Event Listener System.out Example</name>
diff --git a/examples/providers/event-store-mem/pom.xml b/examples/providers/event-store-mem/pom.xml
index be6d636..189438e 100755
--- a/examples/providers/event-store-mem/pom.xml
+++ b/examples/providers/event-store-mem/pom.xml
@@ -20,7 +20,7 @@
<parent>
<artifactId>keycloak-examples-providers-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<name>Event Store In-Mem Example</name>
diff --git a/examples/providers/federation-provider/pom.xml b/examples/providers/federation-provider/pom.xml
index 8823071..78f7986 100755
--- a/examples/providers/federation-provider/pom.xml
+++ b/examples/providers/federation-provider/pom.xml
@@ -20,7 +20,7 @@
<parent>
<artifactId>keycloak-examples-providers-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<name>Properties Authentication Provider Example</name>
examples/providers/pom.xml 2(+1 -1)
diff --git a/examples/providers/pom.xml b/examples/providers/pom.xml
index 1c3c418..0735a2a 100755
--- a/examples/providers/pom.xml
+++ b/examples/providers/pom.xml
@@ -20,7 +20,7 @@
<parent>
<artifactId>keycloak-examples-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<name>Provider Examples</name>
examples/saml/pom.xml 2(+1 -1)
diff --git a/examples/saml/pom.xml b/examples/saml/pom.xml
index 74c2ba5..88b9ada 100755
--- a/examples/saml/pom.xml
+++ b/examples/saml/pom.xml
@@ -20,7 +20,7 @@
<parent>
<artifactId>keycloak-examples-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<name>Provider Examples</name>
diff --git a/examples/saml/post-with-encryption/pom.xml b/examples/saml/post-with-encryption/pom.xml
index 2f84cb5..73e5191 100755
--- a/examples/saml/post-with-encryption/pom.xml
+++ b/examples/saml/post-with-encryption/pom.xml
@@ -22,7 +22,7 @@
<parent>
<artifactId>keycloak-examples-saml-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<artifactId>saml-post-encryption</artifactId>
diff --git a/examples/saml/post-with-signature/pom.xml b/examples/saml/post-with-signature/pom.xml
index 6f0740b..a1bd611 100755
--- a/examples/saml/post-with-signature/pom.xml
+++ b/examples/saml/post-with-signature/pom.xml
@@ -22,7 +22,7 @@
<parent>
<artifactId>keycloak-examples-saml-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<artifactId>sales-post-sig</artifactId>
diff --git a/examples/saml/redirect-with-signature/pom.xml b/examples/saml/redirect-with-signature/pom.xml
index f069408..cf3be5a 100755
--- a/examples/saml/redirect-with-signature/pom.xml
+++ b/examples/saml/redirect-with-signature/pom.xml
@@ -22,7 +22,7 @@
<parent>
<artifactId>keycloak-examples-saml-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<artifactId>saml-redirect-signatures</artifactId>
examples/saml/servlet-filter/pom.xml 2(+1 -1)
diff --git a/examples/saml/servlet-filter/pom.xml b/examples/saml/servlet-filter/pom.xml
index c67f6d4..bf05a91 100755
--- a/examples/saml/servlet-filter/pom.xml
+++ b/examples/saml/servlet-filter/pom.xml
@@ -22,7 +22,7 @@
<parent>
<artifactId>keycloak-examples-saml-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<artifactId>saml-servlet-filter</artifactId>
examples/themes/pom.xml 2(+1 -1)
diff --git a/examples/themes/pom.xml b/examples/themes/pom.xml
index 6a5ce62..b0ebc4d 100755
--- a/examples/themes/pom.xml
+++ b/examples/themes/pom.xml
@@ -20,7 +20,7 @@
<parent>
<artifactId>keycloak-examples-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<name>Themes Examples</name>
federation/kerberos/pom.xml 2(+1 -1)
diff --git a/federation/kerberos/pom.xml b/federation/kerberos/pom.xml
index 8634b56..cdb65d2 100755
--- a/federation/kerberos/pom.xml
+++ b/federation/kerberos/pom.xml
@@ -20,7 +20,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
federation/ldap/pom.xml 2(+1 -1)
diff --git a/federation/ldap/pom.xml b/federation/ldap/pom.xml
index a47374c..7fe4b65 100755
--- a/federation/ldap/pom.xml
+++ b/federation/ldap/pom.xml
@@ -20,7 +20,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/LDAPQuery.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/LDAPQuery.java
index dea173f..957ba33 100644
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/LDAPQuery.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/LDAPQuery.java
@@ -153,7 +153,8 @@ public class LDAPQuery {
public List<LDAPObject> getResultList() {
// Apply mappers now
- for (UserFederationMapperModel mapperModel : mappers) {
+ List<UserFederationMapperModel> sortedMappers = ldapFedProvider.sortMappersAsc(mappers);
+ for (UserFederationMapperModel mapperModel : sortedMappers) {
LDAPFederationMapper fedMapper = ldapFedProvider.getMapper(mapperModel);
fedMapper.beforeLDAPQuery(mapperModel, this);
}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/LDAPQueryConditionsBuilder.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/LDAPQueryConditionsBuilder.java
index 0ba9a39..4be4ba4 100644
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/LDAPQueryConditionsBuilder.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/query/internal/LDAPQueryConditionsBuilder.java
@@ -61,9 +61,6 @@ public class LDAPQueryConditionsBuilder {
public Condition addCustomLDAPFilter(String filter) {
filter = filter.trim();
- if (!filter.startsWith("(") || !filter.endsWith(")")) {
- throw new ModelException("Custom filter doesn't start with ( or doesn't end with ). ");
- }
return new CustomLDAPFilter(filter);
}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/ldap/LDAPIdentityStore.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/ldap/LDAPIdentityStore.java
index b8adbdd..9a9b929 100644
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/ldap/LDAPIdentityStore.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/ldap/LDAPIdentityStore.java
@@ -220,6 +220,8 @@ public class LDAPIdentityStore implements IdentityStore {
mods[0] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, mod0);
operationManager.modifyAttribute(userDN, mod0);
+ } catch (ModelException me) {
+ throw me;
} catch (Exception e) {
throw new ModelException("Error updating password.", e);
}
@@ -240,6 +242,8 @@ public class LDAPIdentityStore implements IdentityStore {
modItems.add(new ModificationItem(DirContext.REPLACE_ATTRIBUTE, unicodePwd));
operationManager.modifyAttributes(userDN, modItems.toArray(new ModificationItem[] {}));
+ } catch (ModelException me) {
+ throw me;
} catch (Exception e) {
throw new ModelException(e);
}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPConfig.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPConfig.java
index 15af133..eb8eaaa 100644
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPConfig.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPConfig.java
@@ -131,7 +131,12 @@ public class LDAPConfig {
public boolean isPagination() {
String pagination = config.get(LDAPConstants.PAGINATION);
- return pagination==null ? false : Boolean.parseBoolean(pagination);
+ return Boolean.parseBoolean(pagination);
+ }
+
+ public int getBatchSizeForSync() {
+ String pageSizeConfig = config.get(LDAPConstants.BATCH_SIZE_FOR_SYNC);
+ return pageSizeConfig!=null ? Integer.parseInt(pageSizeConfig) : LDAPConstants.DEFAULT_BATCH_SIZE_FOR_SYNC;
}
public String getUsernameLdapAttribute() {
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java
index e800bc3..4ddd142 100755
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java
@@ -27,6 +27,7 @@ import org.keycloak.federation.ldap.idm.query.internal.LDAPQueryConditionsBuilde
import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStore;
import org.keycloak.federation.ldap.kerberos.LDAPProviderKerberosConfig;
import org.keycloak.federation.ldap.mappers.LDAPFederationMapper;
+import org.keycloak.federation.ldap.mappers.LDAPMappersComparator;
import org.keycloak.models.CredentialValidationOutput;
import org.keycloak.models.GroupModel;
import org.keycloak.models.KeycloakSession;
@@ -47,6 +48,7 @@ import org.keycloak.services.managers.UserManager;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@@ -128,7 +130,8 @@ public class LDAPFederationProvider implements UserFederationProvider {
}
Set<UserFederationMapperModel> federationMappers = realm.getUserFederationMappersByFederationProvider(model.getId());
- for (UserFederationMapperModel mapperModel : federationMappers) {
+ List<UserFederationMapperModel> sortedMappers = sortMappersAsc(federationMappers);
+ for (UserFederationMapperModel mapperModel : sortedMappers) {
LDAPFederationMapper ldapMapper = getMapper(mapperModel);
proxied = ldapMapper.proxy(mapperModel, this, ldapObject, proxied, realm);
}
@@ -176,8 +179,8 @@ public class LDAPFederationProvider implements UserFederationProvider {
@Override
public boolean removeUser(RealmModel realm, UserModel user) {
if (editMode == EditMode.READ_ONLY || editMode == EditMode.UNSYNCED) {
- logger.warnf("User '%s' can't be deleted in LDAP as editMode is '%s'", user.getUsername(), editMode.toString());
- return false;
+ logger.warnf("User '%s' can't be deleted in LDAP as editMode is '%s'. Deleting user just from Keycloak DB, but he will be re-imported from LDAP again once searched in Keycloak", user.getUsername(), editMode.toString());
+ return true;
}
LDAPObject ldapObject = loadAndValidateUser(realm, user);
@@ -313,7 +316,8 @@ public class LDAPFederationProvider implements UserFederationProvider {
imported.setEnabled(true);
Set<UserFederationMapperModel> federationMappers = realm.getUserFederationMappersByFederationProvider(getModel().getId());
- for (UserFederationMapperModel mapperModel : federationMappers) {
+ List<UserFederationMapperModel> sortedMappers = sortMappersDesc(federationMappers);
+ for (UserFederationMapperModel mapperModel : sortedMappers) {
if (logger.isTraceEnabled()) {
logger.tracef("Using mapper %s during import user from LDAP", mapperModel);
}
@@ -517,4 +521,13 @@ public class LDAPFederationProvider implements UserFederationProvider {
return ldapMapper;
}
+
+
+ public List<UserFederationMapperModel> sortMappersAsc(Collection<UserFederationMapperModel> mappers) {
+ return LDAPMappersComparator.sortAsc(getLdapIdentityStore().getConfig(), mappers);
+ }
+
+ protected List<UserFederationMapperModel> sortMappersDesc(Collection<UserFederationMapperModel> mappers) {
+ return LDAPMappersComparator.sortDesc(getLdapIdentityStore().getConfig(), mappers);
+ }
}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProviderFactory.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProviderFactory.java
index f6e4026..51a3c8c 100755
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProviderFactory.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProviderFactory.java
@@ -34,6 +34,7 @@ import org.keycloak.federation.ldap.mappers.LDAPFederationMapper;
import org.keycloak.federation.ldap.mappers.UserAttributeLDAPFederationMapper;
import org.keycloak.federation.ldap.mappers.UserAttributeLDAPFederationMapperFactory;
import org.keycloak.federation.ldap.mappers.msad.MSADUserAccountControlMapperFactory;
+import org.keycloak.mappers.FederationConfigValidationException;
import org.keycloak.mappers.UserFederationMapper;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
@@ -46,6 +47,7 @@ import org.keycloak.models.UserFederationMapperModel;
import org.keycloak.models.UserFederationProvider;
import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.UserFederationSyncResult;
+import org.keycloak.models.UserFederationValidatingProviderFactory;
import org.keycloak.models.UserModel;
import org.keycloak.models.utils.KeycloakModelUtils;
@@ -59,7 +61,7 @@ import java.util.Set;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
-public class LDAPFederationProviderFactory extends UserFederationEventAwareProviderFactory {
+public class LDAPFederationProviderFactory extends UserFederationEventAwareProviderFactory implements UserFederationValidatingProviderFactory {
private static final Logger logger = Logger.getLogger(LDAPFederationProviderFactory.class);
public static final String PROVIDER_NAME = LDAPConstants.LDAP_PROVIDER;
@@ -77,6 +79,13 @@ public class LDAPFederationProviderFactory extends UserFederationEventAwareProvi
}
@Override
+ public void validateConfig(RealmModel realm, UserFederationProviderModel providerModel) throws FederationConfigValidationException {
+ LDAPConfig cfg = new LDAPConfig(providerModel.getConfig());
+ String customFilter = cfg.getCustomUserSearchFilter();
+ LDAPUtils.validateCustomLdapFilter(customFilter);
+ }
+
+ @Override
public void init(Config.Scope config) {
this.ldapStoreRegistry = new LDAPIdentityStoreRegistry();
}
@@ -156,7 +165,8 @@ public class LDAPFederationProviderFactory extends UserFederationEventAwareProvi
// For read-only LDAP, we map "cn" as full name
mapperModel = KeycloakModelUtils.createUserFederationMapperModel("full name", newProviderModel.getId(), FullNameLDAPFederationMapperFactory.PROVIDER_ID,
FullNameLDAPFederationMapper.LDAP_FULL_NAME_ATTRIBUTE, LDAPConstants.CN,
- UserAttributeLDAPFederationMapper.READ_ONLY, readOnly);
+ FullNameLDAPFederationMapper.READ_ONLY, readOnly,
+ FullNameLDAPFederationMapper.WRITE_ONLY, "false");
realm.addUserFederationMapper(mapperModel);
}
}
@@ -274,11 +284,10 @@ public class LDAPFederationProviderFactory extends UserFederationEventAwareProvi
final UserFederationSyncResult syncResult = new UserFederationSyncResult();
- boolean pagination = Boolean.parseBoolean(fedModel.getConfig().get(LDAPConstants.PAGINATION));
+ LDAPConfig ldapConfig = new LDAPConfig(fedModel.getConfig());
+ boolean pagination = ldapConfig.isPagination();
if (pagination) {
-
- String pageSizeConfig = fedModel.getConfig().get(LDAPConstants.BATCH_SIZE_FOR_SYNC);
- int pageSize = pageSizeConfig!=null ? Integer.parseInt(pageSizeConfig) : LDAPConstants.DEFAULT_BATCH_SIZE_FOR_SYNC;
+ int pageSize = ldapConfig.getBatchSizeForSync();
boolean nextPage = true;
while (nextPage) {
@@ -354,7 +363,8 @@ public class LDAPFederationProviderFactory extends UserFederationEventAwareProvi
// Update keycloak user
Set<UserFederationMapperModel> federationMappers = currentRealm.getUserFederationMappersByFederationProvider(fedModel.getId());
- for (UserFederationMapperModel mapperModel : federationMappers) {
+ List<UserFederationMapperModel> sortedMappers = ldapFedProvider.sortMappersDesc(federationMappers);
+ for (UserFederationMapperModel mapperModel : sortedMappers) {
LDAPFederationMapper ldapMapper = ldapFedProvider.getMapper(mapperModel);
ldapMapper.onImportUserFromLDAP(mapperModel, ldapFedProvider, ldapUser, currentUser, currentRealm, false);
}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPUtils.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPUtils.java
index a373625..e406c11 100755
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPUtils.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPUtils.java
@@ -19,6 +19,8 @@ package org.keycloak.federation.ldap;
import java.util.Collection;
import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -30,6 +32,7 @@ import org.keycloak.federation.ldap.idm.query.internal.LDAPQueryConditionsBuilde
import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStore;
import org.keycloak.federation.ldap.mappers.LDAPFederationMapper;
import org.keycloak.federation.ldap.mappers.membership.MembershipType;
+import org.keycloak.mappers.FederationConfigValidationException;
import org.keycloak.models.LDAPConstants;
import org.keycloak.models.ModelException;
import org.keycloak.models.RealmModel;
@@ -58,7 +61,8 @@ public class LDAPUtils {
ldapUser.setObjectClasses(ldapConfig.getUserObjectClasses());
Set<UserFederationMapperModel> federationMappers = realm.getUserFederationMappersByFederationProvider(ldapProvider.getModel().getId());
- for (UserFederationMapperModel mapperModel : federationMappers) {
+ List<UserFederationMapperModel> sortedMappers = ldapProvider.sortMappersAsc(federationMappers);
+ for (UserFederationMapperModel mapperModel : sortedMappers) {
LDAPFederationMapper ldapMapper = ldapProvider.getMapper(mapperModel);
ldapMapper.onRegisterUserToLDAP(mapperModel, ldapProvider, ldapUser, user, realm);
}
@@ -224,4 +228,58 @@ public class LDAPUtils {
public static String getMemberValueOfChildObject(LDAPObject ldapUser, MembershipType membershipType) {
return membershipType == MembershipType.DN ? ldapUser.getDn().toString() : ldapUser.getAttributeAsString(ldapUser.getRdnAttributeName());
}
+
+
+ /**
+ * Load all LDAP objects corresponding to given query. We will load them paginated, so we allow to bypass the limitation of 1000
+ * maximum loaded objects in single query in MSAD
+ *
+ * @param ldapQuery
+ * @param ldapProvider
+ * @return
+ */
+ public static List<LDAPObject> loadAllLDAPObjects(LDAPQuery ldapQuery, LDAPFederationProvider ldapProvider) {
+ LDAPConfig ldapConfig = ldapProvider.getLdapIdentityStore().getConfig();
+ boolean pagination = ldapConfig.isPagination();
+ if (pagination) {
+ // For now reuse globally configured batch size in LDAP provider page
+ int pageSize = ldapConfig.getBatchSizeForSync();
+
+ List<LDAPObject> result = new LinkedList<>();
+ boolean nextPage = true;
+
+ while (nextPage) {
+ ldapQuery.setLimit(pageSize);
+ final List<LDAPObject> currentPageGroups = ldapQuery.getResultList();
+ result.addAll(currentPageGroups);
+ nextPage = ldapQuery.getPaginationContext() != null;
+ }
+
+ return result;
+ } else {
+ // LDAP pagination not available. Do everything in single transaction
+ return ldapQuery.getResultList();
+ }
+ }
+
+
+ /**
+ * Validate configured customFilter matches the requested format
+ *
+ * @param customFilter
+ * @throws FederationConfigValidationException
+ */
+ public static void validateCustomLdapFilter(String customFilter) throws FederationConfigValidationException {
+ if (customFilter != null) {
+
+ customFilter = customFilter.trim();
+ if (customFilter.isEmpty()) {
+ return;
+ }
+
+ if (!customFilter.startsWith("(") || !customFilter.endsWith(")")) {
+ throw new FederationConfigValidationException("ldapErrorInvalidCustomFilter");
+ }
+ }
+ }
}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/AbstractLDAPFederationMapperFactory.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/AbstractLDAPFederationMapperFactory.java
index 933d614..d681125 100755
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/AbstractLDAPFederationMapperFactory.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/AbstractLDAPFederationMapperFactory.java
@@ -20,7 +20,7 @@ package org.keycloak.federation.ldap.mappers;
import org.keycloak.Config;
import org.keycloak.federation.ldap.LDAPFederationProvider;
import org.keycloak.federation.ldap.LDAPFederationProviderFactory;
-import org.keycloak.mappers.MapperConfigValidationException;
+import org.keycloak.mappers.FederationConfigValidationException;
import org.keycloak.mappers.UserFederationMapper;
import org.keycloak.mappers.UserFederationMapperFactory;
import org.keycloak.models.KeycloakSession;
@@ -85,10 +85,10 @@ public abstract class AbstractLDAPFederationMapperFactory implements UserFederat
return configProperty;
}
- protected void checkMandatoryConfigAttribute(String name, String displayName, UserFederationMapperModel mapperModel) throws MapperConfigValidationException {
+ protected void checkMandatoryConfigAttribute(String name, String displayName, UserFederationMapperModel mapperModel) throws FederationConfigValidationException {
String attrConfigValue = mapperModel.getConfig().get(name);
if (attrConfigValue == null || attrConfigValue.trim().isEmpty()) {
- throw new MapperConfigValidationException("Missing configuration for '" + displayName + "'");
+ throw new FederationConfigValidationException("Missing configuration for '" + displayName + "'");
}
}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/FullNameLDAPFederationMapper.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/FullNameLDAPFederationMapper.java
index 113a57d..b94b5b4 100644
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/FullNameLDAPFederationMapper.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/FullNameLDAPFederationMapper.java
@@ -40,6 +40,8 @@ public class FullNameLDAPFederationMapper extends AbstractLDAPFederationMapper {
public static final String LDAP_FULL_NAME_ATTRIBUTE = "ldap.full.name.attribute";
public static final String READ_ONLY = "read.only";
+ public static final String WRITE_ONLY = "write.only";
+
public FullNameLDAPFederationMapper(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, RealmModel realm) {
super(mapperModel, ldapProvider, realm);
@@ -47,6 +49,10 @@ public class FullNameLDAPFederationMapper extends AbstractLDAPFederationMapper {
@Override
public void onImportUserFromLDAP(LDAPObject ldapUser, UserModel user, boolean isCreate) {
+ if (isWriteOnly()) {
+ return;
+ }
+
String ldapFullNameAttrName = getLdapFullNameAttrName();
String fullName = ldapUser.getAttributeAsString(ldapFullNameAttrName);
if (fullName == null) {
@@ -117,6 +123,10 @@ public class FullNameLDAPFederationMapper extends AbstractLDAPFederationMapper {
@Override
public void beforeLDAPQuery(LDAPQuery query) {
+ if (isWriteOnly()) {
+ return;
+ }
+
String ldapFullNameAttrName = getLdapFullNameAttrName();
query.addReturningLdapAttribute(ldapFullNameAttrName);
@@ -178,4 +188,8 @@ public class FullNameLDAPFederationMapper extends AbstractLDAPFederationMapper {
private boolean isReadOnly() {
return parseBooleanParameter(mapperModel, READ_ONLY);
}
+
+ private boolean isWriteOnly() {
+ return parseBooleanParameter(mapperModel, WRITE_ONLY);
+ }
}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/FullNameLDAPFederationMapperFactory.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/FullNameLDAPFederationMapperFactory.java
index 32b0acd..32826b2 100755
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/FullNameLDAPFederationMapperFactory.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/FullNameLDAPFederationMapperFactory.java
@@ -24,7 +24,7 @@ import java.util.Map;
import org.keycloak.federation.ldap.LDAPConfig;
import org.keycloak.federation.ldap.LDAPFederationProvider;
-import org.keycloak.mappers.MapperConfigValidationException;
+import org.keycloak.mappers.FederationConfigValidationException;
import org.keycloak.models.LDAPConstants;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserFederationMapperModel;
@@ -43,12 +43,17 @@ public class FullNameLDAPFederationMapperFactory extends AbstractLDAPFederationM
static {
ProviderConfigProperty userModelAttribute = createConfigProperty(FullNameLDAPFederationMapper.LDAP_FULL_NAME_ATTRIBUTE, "LDAP Full Name Attribute",
- "Name of LDAP attribute, which contains fullName of user. In most cases it will be 'cn' ", ProviderConfigProperty.STRING_TYPE, null);
+ "Name of LDAP attribute, which contains fullName of user. Usually it will be 'cn' ", ProviderConfigProperty.STRING_TYPE, null);
configProperties.add(userModelAttribute);
- ProviderConfigProperty readOnly = createConfigProperty(UserAttributeLDAPFederationMapper.READ_ONLY, "Read Only",
+ ProviderConfigProperty readOnly = createConfigProperty(FullNameLDAPFederationMapper.READ_ONLY, "Read Only",
"For Read-only is data imported from LDAP to Keycloak DB, but it's not saved back to LDAP when user is updated in Keycloak.", ProviderConfigProperty.BOOLEAN_TYPE, null);
configProperties.add(readOnly);
+
+ ProviderConfigProperty writeOnly = createConfigProperty(FullNameLDAPFederationMapper.WRITE_ONLY, "Write Only",
+ "For Write-only is data propagated to LDAP when user is created or updated in Keycloak. But this mapper is not used to propagate data from LDAP back into Keycloak. " +
+ "This setting is useful if you configured separate firstName and lastName attribute mappers and you want to use those to read attribute from LDAP into Keycloak", ProviderConfigProperty.BOOLEAN_TYPE, null);
+ configProperties.add(writeOnly);
}
@Override
@@ -78,8 +83,11 @@ public class FullNameLDAPFederationMapperFactory extends AbstractLDAPFederationM
defaultValues.put(FullNameLDAPFederationMapper.LDAP_FULL_NAME_ATTRIBUTE, LDAPConstants.CN);
- String readOnly = config.getEditMode() == UserFederationProvider.EditMode.WRITABLE ? "false" : "true";
- defaultValues.put(UserAttributeLDAPFederationMapper.READ_ONLY, readOnly);
+ boolean readOnly = config.getEditMode() != UserFederationProvider.EditMode.WRITABLE;
+ defaultValues.put(FullNameLDAPFederationMapper.READ_ONLY, String.valueOf(readOnly));
+
+ String writeOnly = String.valueOf(!readOnly);
+ defaultValues.put(FullNameLDAPFederationMapper.WRITE_ONLY, writeOnly);
return defaultValues;
}
@@ -90,8 +98,21 @@ public class FullNameLDAPFederationMapperFactory extends AbstractLDAPFederationM
}
@Override
- public void validateConfig(RealmModel realm, UserFederationMapperModel mapperModel) throws MapperConfigValidationException {
+ public void validateConfig(RealmModel realm, UserFederationProviderModel fedProviderModel, UserFederationMapperModel mapperModel) throws FederationConfigValidationException {
checkMandatoryConfigAttribute(FullNameLDAPFederationMapper.LDAP_FULL_NAME_ATTRIBUTE, "LDAP Full Name Attribute", mapperModel);
+
+ boolean readOnly = AbstractLDAPFederationMapper.parseBooleanParameter(mapperModel, FullNameLDAPFederationMapper.READ_ONLY);
+ boolean writeOnly = AbstractLDAPFederationMapper.parseBooleanParameter(mapperModel, FullNameLDAPFederationMapper.WRITE_ONLY);
+
+ LDAPConfig cfg = new LDAPConfig(fedProviderModel.getConfig());
+ UserFederationProvider.EditMode editMode = cfg.getEditMode();
+
+ if (writeOnly && cfg.getEditMode() != UserFederationProvider.EditMode.WRITABLE) {
+ throw new FederationConfigValidationException("ldapErrorCantWriteOnlyForReadOnlyLdap");
+ }
+ if (writeOnly && readOnly) {
+ throw new FederationConfigValidationException("ldapErrorCantWriteOnlyAndReadOnly");
+ }
}
@Override
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/HardcodedLDAPRoleMapperFactory.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/HardcodedLDAPRoleMapperFactory.java
index ae07c0f..1ca93f5 100644
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/HardcodedLDAPRoleMapperFactory.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/HardcodedLDAPRoleMapperFactory.java
@@ -23,7 +23,7 @@ import java.util.List;
import java.util.Map;
import org.keycloak.federation.ldap.LDAPFederationProvider;
-import org.keycloak.mappers.MapperConfigValidationException;
+import org.keycloak.mappers.FederationConfigValidationException;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserFederationMapperModel;
@@ -77,14 +77,14 @@ public class HardcodedLDAPRoleMapperFactory extends AbstractLDAPFederationMapper
}
@Override
- public void validateConfig(RealmModel realm, UserFederationMapperModel mapperModel) throws MapperConfigValidationException {
+ public void validateConfig(RealmModel realm, UserFederationProviderModel fedProviderModel, UserFederationMapperModel mapperModel) throws FederationConfigValidationException {
String roleName = mapperModel.getConfig().get(HardcodedLDAPRoleMapper.ROLE);
if (roleName == null) {
- throw new MapperConfigValidationException("Role can't be null");
+ throw new FederationConfigValidationException("Role can't be null");
}
RoleModel role = KeycloakModelUtils.getRoleFromString(realm, roleName);
if (role == null) {
- throw new MapperConfigValidationException("There is no role corresponding to configured value");
+ throw new FederationConfigValidationException("There is no role corresponding to configured value");
}
}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/LDAPMappersComparator.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/LDAPMappersComparator.java
new file mode 100644
index 0000000..c5507e6
--- /dev/null
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/LDAPMappersComparator.java
@@ -0,0 +1,114 @@
+/*
+ * 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.federation.ldap.mappers;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+import org.keycloak.federation.ldap.LDAPConfig;
+import org.keycloak.models.UserFederationMapperModel;
+import org.keycloak.models.UserModel;
+
+/**
+ * TODO: Possibly add "priority" to UserFederationMapper instead of hardcoding behaviour
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class LDAPMappersComparator {
+
+ public static List<UserFederationMapperModel> sortAsc(LDAPConfig ldapConfig, Collection<UserFederationMapperModel> mappers) {
+ Comparator<UserFederationMapperModel> comparator = new ImportantFirstComparator(ldapConfig);
+
+ List<UserFederationMapperModel> result = new ArrayList<>(mappers);
+ Collections.sort(result, comparator);
+ return result;
+ }
+
+ public static List<UserFederationMapperModel> sortDesc(LDAPConfig ldapConfig, Collection<UserFederationMapperModel> mappers) {
+ Comparator<UserFederationMapperModel> comparator = new ImportantFirstComparator(ldapConfig).reversed();
+
+ List<UserFederationMapperModel> result = new ArrayList<>(mappers);
+ Collections.sort(result, comparator);
+ return result;
+ }
+
+
+ private static class ImportantFirstComparator implements Comparator<UserFederationMapperModel> {
+
+ private final LDAPConfig ldapConfig;
+
+ public ImportantFirstComparator(LDAPConfig ldapConfig) {
+ this.ldapConfig = ldapConfig;
+ }
+
+ @Override
+ public int compare(UserFederationMapperModel o1, UserFederationMapperModel o2) {
+ // UserAttributeLDAPFederationMapper first
+ boolean isO1AttrMapper = o1.getFederationMapperType().equals(UserAttributeLDAPFederationMapperFactory.PROVIDER_ID);
+ boolean isO2AttrMapper = o2.getFederationMapperType().equals(UserAttributeLDAPFederationMapperFactory.PROVIDER_ID);
+ if (!isO1AttrMapper) {
+ if (isO2AttrMapper) {
+ return 1;
+ } else {
+ return 0;
+ }
+ } else if (!isO2AttrMapper) {
+ return -1;
+ }
+
+ // Mapper for "username" attribute first
+ String model1 = o1.getConfig().get(UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE);
+ String model2 = o2.getConfig().get(UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE);
+ boolean isO1UsernameMapper = model1 != null && model1.equalsIgnoreCase(UserModel.USERNAME);
+ boolean isO2UsernameMapper = model2 != null && model2.equalsIgnoreCase(UserModel.USERNAME);
+ if (!isO1UsernameMapper) {
+ if (isO2UsernameMapper) {
+ return 1;
+ } else {
+ return 0;
+ }
+ } else if (!isO2UsernameMapper) {
+ return -1;
+ }
+
+ // The username mapper corresponding to the same like configured username for federationProvider is first
+ String o1LdapAttr = o1.getConfig().get(UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE);
+ String o2LdapAttr = o2.getConfig().get(UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE);
+ boolean isO1LdapAttr = o1LdapAttr != null && ldapConfig.getUsernameLdapAttribute().equalsIgnoreCase(o1LdapAttr);
+ boolean isO2LdapAttr = o2LdapAttr != null && ldapConfig.getUsernameLdapAttribute().equalsIgnoreCase(o2LdapAttr);
+
+ if (!isO1LdapAttr) {
+ if (isO2LdapAttr) {
+ return 1;
+ } else {
+ return 0;
+ }
+ } else if (!isO2LdapAttr) {
+ return -1;
+ }
+
+ return 0;
+ }
+
+ }
+
+
+}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/group/GroupLDAPFederationMapper.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/group/GroupLDAPFederationMapper.java
index 2c1c048..cd220b2 100644
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/group/GroupLDAPFederationMapper.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/group/GroupLDAPFederationMapper.java
@@ -27,6 +27,7 @@ import java.util.Map;
import java.util.Set;
import org.jboss.logging.Logger;
+import org.keycloak.federation.ldap.LDAPConfig;
import org.keycloak.federation.ldap.LDAPFederationProvider;
import org.keycloak.federation.ldap.LDAPUtils;
import org.keycloak.federation.ldap.idm.model.LDAPDn;
@@ -41,9 +42,11 @@ import org.keycloak.federation.ldap.mappers.membership.LDAPGroupMapperMode;
import org.keycloak.federation.ldap.mappers.membership.MembershipType;
import org.keycloak.federation.ldap.mappers.membership.UserRolesRetrieveStrategy;
import org.keycloak.models.GroupModel;
+import org.keycloak.models.LDAPConstants;
import org.keycloak.models.ModelException;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserFederationMapperModel;
+import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.UserFederationSyncResult;
import org.keycloak.models.UserModel;
import org.keycloak.models.utils.KeycloakModelUtils;
@@ -149,8 +152,7 @@ public class GroupLDAPFederationMapper extends AbstractLDAPFederationMapper impl
logger.debugf("Syncing groups from LDAP into Keycloak DB. Mapper is [%s], LDAP provider is [%s]", mapperModel.getName(), ldapProvider.getModel().getDisplayName());
// Get all LDAP groups
- LDAPQuery ldapQuery = createGroupQuery();
- List<LDAPObject> ldapGroups = ldapQuery.getResultList();
+ List<LDAPObject> ldapGroups = getAllLDAPGroups();
// Convert to internal format
Map<String, LDAPObject> ldapGroupsMap = new HashMap<>();
@@ -286,29 +288,46 @@ public class GroupLDAPFederationMapper extends AbstractLDAPFederationMapper impl
}
}
- // Override if better effectivity or different algorithm is needed
+
protected GroupModel findKcGroupByLDAPGroup(LDAPObject ldapGroup) {
String groupNameAttr = config.getGroupNameLdapAttribute();
String groupName = ldapGroup.getAttributeAsString(groupNameAttr);
- List<GroupModel> groups = realm.getGroups();
- for (GroupModel group : groups) {
- if (group.getName().equals(groupName)) {
- return group;
+ if (config.isPreserveGroupsInheritance()) {
+ // Override if better effectivity or different algorithm is needed
+ List<GroupModel> groups = realm.getGroups();
+ for (GroupModel group : groups) {
+ if (group.getName().equals(groupName)) {
+ return group;
+ }
}
- }
- return null;
+ return null;
+ } else {
+ // Without preserved inheritance, it's always top-level group
+ return KeycloakModelUtils.findGroupByPath(realm, "/" + groupName);
+ }
}
protected GroupModel findKcGroupOrSyncFromLDAP(LDAPObject ldapGroup, UserModel user) {
GroupModel kcGroup = findKcGroupByLDAPGroup(ldapGroup);
if (kcGroup == null) {
- // Sync groups from LDAP
- if (!syncFromLDAPPerformedInThisTransaction) {
- syncDataFromFederationProviderToKeycloak();
- kcGroup = findKcGroupByLDAPGroup(ldapGroup);
+
+ if (config.isPreserveGroupsInheritance()) {
+
+ // Better to sync all groups from LDAP with preserved inheritance
+ if (!syncFromLDAPPerformedInThisTransaction) {
+ syncDataFromFederationProviderToKeycloak();
+ kcGroup = findKcGroupByLDAPGroup(ldapGroup);
+ }
+ } else {
+ String groupNameAttr = config.getGroupNameLdapAttribute();
+ String groupName = ldapGroup.getAttributeAsString(groupNameAttr);
+
+ kcGroup = realm.createGroup(groupName);
+ updateAttributesOfKCGroup(kcGroup, ldapGroup);
+ realm.moveGroup(kcGroup, null);
}
// Could theoretically happen on some LDAP servers if 'memberof' style is used and 'memberof' attribute of user references non-existing group
@@ -321,6 +340,12 @@ public class GroupLDAPFederationMapper extends AbstractLDAPFederationMapper impl
return kcGroup;
}
+ // Send LDAP query to retrieve all groups
+ protected List<LDAPObject> getAllLDAPGroups() {
+ LDAPQuery ldapGroupQuery = createGroupQuery();
+ return LDAPUtils.loadAllLDAPObjects(ldapGroupQuery, ldapProvider);
+ }
+
// Sync from Keycloak to LDAP
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/group/GroupLDAPFederationMapperFactory.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/group/GroupLDAPFederationMapperFactory.java
index 0304788..169875d 100644
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/group/GroupLDAPFederationMapperFactory.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/group/GroupLDAPFederationMapperFactory.java
@@ -26,6 +26,7 @@ import java.util.Map;
import org.keycloak.federation.ldap.LDAPConfig;
import org.keycloak.federation.ldap.LDAPFederationProvider;
+import org.keycloak.federation.ldap.LDAPUtils;
import org.keycloak.federation.ldap.mappers.AbstractLDAPFederationMapper;
import org.keycloak.federation.ldap.mappers.AbstractLDAPFederationMapperFactory;
import org.keycloak.federation.ldap.mappers.membership.CommonLDAPGroupMapperConfig;
@@ -33,7 +34,7 @@ import org.keycloak.federation.ldap.mappers.membership.LDAPGroupMapperMode;
import org.keycloak.federation.ldap.mappers.membership.MembershipType;
import org.keycloak.federation.ldap.mappers.membership.UserRolesRetrieveStrategy;
import org.keycloak.federation.ldap.mappers.membership.role.RoleMapperConfig;
-import org.keycloak.mappers.MapperConfigValidationException;
+import org.keycloak.mappers.FederationConfigValidationException;
import org.keycloak.models.LDAPConstants;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserFederationMapperModel;
@@ -87,9 +88,9 @@ public class GroupLDAPFederationMapperFactory extends AbstractLDAPFederationMapp
for (MembershipType membershipType : MembershipType.values()) {
membershipTypes.add(membershipType.toString());
}
- ProviderConfigProperty membershipType = createConfigProperty(RoleMapperConfig.MEMBERSHIP_ATTRIBUTE_TYPE, "Membership Attribute Type",
- "DN means that LDAP role has it's members declared in form of their full DN. For example 'member: uid=john,ou=users,dc=example,dc=com' . " +
- "UID means that LDAP role has it's members declared in form of pure user uids. For example 'memberUid: john' .",
+ ProviderConfigProperty membershipType = createConfigProperty(GroupMapperConfig.MEMBERSHIP_ATTRIBUTE_TYPE, "Membership Attribute Type",
+ "DN means that LDAP group has it's members declared in form of their full DN. For example 'member: uid=john,ou=users,dc=example,dc=com' . " +
+ "UID means that LDAP group has it's members declared in form of pure user uids. For example 'memberUid: john' .",
ProviderConfigProperty.LIST_TYPE, membershipTypes);
configProperties.add(membershipType);
@@ -164,6 +165,7 @@ public class GroupLDAPFederationMapperFactory extends AbstractLDAPFederationMapp
defaultValues.put(GroupMapperConfig.PRESERVE_GROUP_INHERITANCE, "true");
defaultValues.put(GroupMapperConfig.MEMBERSHIP_LDAP_ATTRIBUTE, LDAPConstants.MEMBER);
+ defaultValues.put(GroupMapperConfig.MEMBERSHIP_ATTRIBUTE_TYPE, MembershipType.DN.toString());
String mode = config.getEditMode() == UserFederationProvider.EditMode.WRITABLE ? LDAPGroupMapperMode.LDAP_ONLY.toString() : LDAPGroupMapperMode.READ_ONLY.toString();
defaultValues.put(GroupMapperConfig.MODE, mode);
@@ -185,7 +187,7 @@ public class GroupLDAPFederationMapperFactory extends AbstractLDAPFederationMapp
}
@Override
- public void validateConfig(RealmModel realm, UserFederationMapperModel mapperModel) throws MapperConfigValidationException {
+ public void validateConfig(RealmModel realm, UserFederationProviderModel fedProviderModel, UserFederationMapperModel mapperModel) throws FederationConfigValidationException {
checkMandatoryConfigAttribute(GroupMapperConfig.GROUPS_DN, "LDAP Groups DN", mapperModel);
checkMandatoryConfigAttribute(GroupMapperConfig.MODE, "Mode", mapperModel);
@@ -193,8 +195,10 @@ public class GroupLDAPFederationMapperFactory extends AbstractLDAPFederationMapp
MembershipType membershipType = mt==null ? MembershipType.DN : Enum.valueOf(MembershipType.class, mt);
boolean preserveGroupInheritance = Boolean.parseBoolean(mapperModel.getConfig().get(GroupMapperConfig.PRESERVE_GROUP_INHERITANCE));
if (preserveGroupInheritance && membershipType != MembershipType.DN) {
- throw new MapperConfigValidationException("Not possible to preserve group inheritance and use UID membership type together");
+ throw new FederationConfigValidationException("ldapErrorCantPreserveGroupInheritanceWithUIDMembershipType");
}
+
+ LDAPUtils.validateCustomLdapFilter(mapperModel.getConfig().get(GroupMapperConfig.GROUPS_LDAP_FILTER));
}
@Override
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/role/RoleLDAPFederationMapper.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/role/RoleLDAPFederationMapper.java
index cbafcc4..9dbeec9 100644
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/role/RoleLDAPFederationMapper.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/role/RoleLDAPFederationMapper.java
@@ -122,9 +122,9 @@ public class RoleLDAPFederationMapper extends AbstractLDAPFederationMapper imple
logger.debugf("Syncing roles from LDAP into Keycloak DB. Mapper is [%s], LDAP provider is [%s]", mapperModel.getName(), ldapProvider.getModel().getDisplayName());
- // Send LDAP query
- LDAPQuery ldapQuery = createRoleQuery();
- List<LDAPObject> ldapRoles = ldapQuery.getResultList();
+ // Send LDAP query to load all roles
+ LDAPQuery ldapRoleQuery = createRoleQuery();
+ List<LDAPObject> ldapRoles = LDAPUtils.loadAllLDAPObjects(ldapRoleQuery, ldapProvider);
RoleContainerModel roleContainer = getTargetRoleContainer();
String rolesRdnAttr = config.getRoleNameLdapAttribute();
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/role/RoleLDAPFederationMapperFactory.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/role/RoleLDAPFederationMapperFactory.java
index ee5140b..25a7ad1 100644
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/role/RoleLDAPFederationMapperFactory.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/role/RoleLDAPFederationMapperFactory.java
@@ -26,12 +26,14 @@ import java.util.Map;
import org.keycloak.federation.ldap.LDAPConfig;
import org.keycloak.federation.ldap.LDAPFederationProvider;
+import org.keycloak.federation.ldap.LDAPUtils;
import org.keycloak.federation.ldap.mappers.AbstractLDAPFederationMapper;
import org.keycloak.federation.ldap.mappers.AbstractLDAPFederationMapperFactory;
import org.keycloak.federation.ldap.mappers.membership.LDAPGroupMapperMode;
import org.keycloak.federation.ldap.mappers.membership.MembershipType;
import org.keycloak.federation.ldap.mappers.membership.UserRolesRetrieveStrategy;
-import org.keycloak.mappers.MapperConfigValidationException;
+import org.keycloak.federation.ldap.mappers.membership.group.GroupMapperConfig;
+import org.keycloak.mappers.FederationConfigValidationException;
import org.keycloak.models.LDAPConstants;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserFederationMapperModel;
@@ -178,7 +180,7 @@ public class RoleLDAPFederationMapperFactory extends AbstractLDAPFederationMappe
}
@Override
- public void validateConfig(RealmModel realm, UserFederationMapperModel mapperModel) throws MapperConfigValidationException {
+ public void validateConfig(RealmModel realm, UserFederationProviderModel fedProviderModel, UserFederationMapperModel mapperModel) throws FederationConfigValidationException {
checkMandatoryConfigAttribute(RoleMapperConfig.ROLES_DN, "LDAP Roles DN", mapperModel);
checkMandatoryConfigAttribute(RoleMapperConfig.MODE, "Mode", mapperModel);
@@ -187,14 +189,11 @@ public class RoleLDAPFederationMapperFactory extends AbstractLDAPFederationMappe
if (!useRealmMappings) {
String clientId = mapperModel.getConfig().get(RoleMapperConfig.CLIENT_ID);
if (clientId == null || clientId.trim().isEmpty()) {
- throw new MapperConfigValidationException("Client ID needs to be provided in config when Realm Roles Mapping is not used");
+ throw new FederationConfigValidationException("ldapErrorMissingClientId");
}
}
- String customLdapFilter = mapperModel.getConfig().get(RoleMapperConfig.ROLES_LDAP_FILTER);
- if ((customLdapFilter != null && customLdapFilter.trim().length() > 0) && (!customLdapFilter.startsWith("(") || !customLdapFilter.endsWith(")"))) {
- throw new MapperConfigValidationException("Custom Roles LDAP filter must starts with '(' and ends with ')'");
- }
+ LDAPUtils.validateCustomLdapFilter(mapperModel.getConfig().get(RoleMapperConfig.ROLES_LDAP_FILTER));
}
@Override
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/msad/MSADUserAccountControlMapper.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/msad/MSADUserAccountControlMapper.java
index 88f12fb..033b3f4 100644
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/msad/MSADUserAccountControlMapper.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/msad/MSADUserAccountControlMapper.java
@@ -30,6 +30,7 @@ import org.keycloak.federation.ldap.idm.model.LDAPObject;
import org.keycloak.federation.ldap.idm.query.internal.LDAPQuery;
import org.keycloak.federation.ldap.mappers.AbstractLDAPFederationMapper;
import org.keycloak.models.LDAPConstants;
+import org.keycloak.models.ModelException;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserFederationMapperModel;
@@ -48,6 +49,7 @@ public class MSADUserAccountControlMapper extends AbstractLDAPFederationMapper {
private static final Logger logger = Logger.getLogger(MSADUserAccountControlMapper.class);
private static final Pattern AUTH_EXCEPTION_REGEX = Pattern.compile(".*AcceptSecurityContext error, data ([0-9a-f]*), v.*");
+ private static final Pattern AUTH_INVALID_NEW_PASSWORD = Pattern.compile(".*error code ([0-9a-f]+) .*WILL_NOT_PERFORM.*");
public MSADUserAccountControlMapper(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, RealmModel realm) {
super(mapperModel, ldapProvider, realm);
@@ -94,7 +96,7 @@ public class MSADUserAccountControlMapper extends AbstractLDAPFederationMapper {
}
protected boolean processAuthErrorCode(String errorCode, UserModel user) {
- logger.debugf("MSAD Error code is '%s' after failed LDAP login of user", errorCode, user.getUsername());
+ logger.debugf("MSAD Error code is '%s' after failed LDAP login of user '%s'", errorCode, user.getUsername());
if (ldapProvider.getEditMode() == UserFederationProvider.EditMode.WRITABLE) {
if (errorCode.equals("532") || errorCode.equals("773")) {
@@ -105,6 +107,8 @@ public class MSADUserAccountControlMapper extends AbstractLDAPFederationMapper {
// User is disabled in MSAD. Set him to disabled in KC as well
user.setEnabled(false);
return true;
+ } else if (errorCode.equals("775")) {
+ logger.warnf("Locked user '%s' attempt to login", user.getUsername());
}
}
@@ -112,6 +116,22 @@ public class MSADUserAccountControlMapper extends AbstractLDAPFederationMapper {
}
+ protected ModelException processFailedPasswordUpdateException(ModelException e) {
+ String exceptionMessage = e.getCause().getMessage().replace('\n', ' ');
+ Matcher m = AUTH_INVALID_NEW_PASSWORD.matcher(exceptionMessage);
+ if (m.matches()) {
+ String errorCode = m.group(1);
+ if (errorCode.equals("53")) {
+ ModelException me = new ModelException("invalidPasswordRegexPatternMessage", e);
+ me.setParameters(new Object[]{"passwordConstraintViolation"});
+ return me;
+ }
+ }
+
+ return e;
+ }
+
+
public class MSADUserModelDelegate extends UserModelDelegate {
private final LDAPObject ldapUser;
@@ -156,7 +176,12 @@ public class MSADUserAccountControlMapper extends AbstractLDAPFederationMapper {
@Override
public void updateCredential(UserCredentialModel cred) {
// Update LDAP password first
- super.updateCredential(cred);
+ try {
+ super.updateCredential(cred);
+ } catch (ModelException me) {
+ me = processFailedPasswordUpdateException(me);
+ throw me;
+ }
if (ldapProvider.getEditMode() == UserFederationProvider.EditMode.WRITABLE && cred.getType().equals(UserCredentialModel.PASSWORD)) {
logger.debugf("Going to update userAccountControl for ldap user '%s' after successful password update", ldapUser.getDn().toString());
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/msad/MSADUserAccountControlMapperFactory.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/msad/MSADUserAccountControlMapperFactory.java
index 2ccc96e..36c494d 100644
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/msad/MSADUserAccountControlMapperFactory.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/msad/MSADUserAccountControlMapperFactory.java
@@ -25,7 +25,7 @@ import java.util.Map;
import org.keycloak.federation.ldap.LDAPFederationProvider;
import org.keycloak.federation.ldap.mappers.AbstractLDAPFederationMapper;
import org.keycloak.federation.ldap.mappers.AbstractLDAPFederationMapperFactory;
-import org.keycloak.mappers.MapperConfigValidationException;
+import org.keycloak.mappers.FederationConfigValidationException;
import org.keycloak.models.LDAPConstants;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserFederationMapperModel;
@@ -75,7 +75,7 @@ public class MSADUserAccountControlMapperFactory extends AbstractLDAPFederationM
}
@Override
- public void validateConfig(RealmModel realm, UserFederationMapperModel mapperModel) throws MapperConfigValidationException {
+ public void validateConfig(RealmModel realm, UserFederationProviderModel fedProviderModel, UserFederationMapperModel mapperModel) throws FederationConfigValidationException {
}
@Override
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/UserAttributeLDAPFederationMapperFactory.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/UserAttributeLDAPFederationMapperFactory.java
index 6a85623..b0a7faa 100755
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/UserAttributeLDAPFederationMapperFactory.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/UserAttributeLDAPFederationMapperFactory.java
@@ -24,7 +24,7 @@ import java.util.Map;
import org.keycloak.federation.ldap.LDAPConfig;
import org.keycloak.federation.ldap.LDAPFederationProvider;
-import org.keycloak.mappers.MapperConfigValidationException;
+import org.keycloak.mappers.FederationConfigValidationException;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserFederationMapperModel;
import org.keycloak.models.UserFederationProvider;
@@ -101,7 +101,7 @@ public class UserAttributeLDAPFederationMapperFactory extends AbstractLDAPFedera
}
@Override
- public void validateConfig(RealmModel realm, UserFederationMapperModel mapperModel) throws MapperConfigValidationException {
+ public void validateConfig(RealmModel realm, UserFederationProviderModel fedProviderModel, UserFederationMapperModel mapperModel) throws FederationConfigValidationException {
checkMandatoryConfigAttribute(UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, "User Model Attribute", mapperModel);
checkMandatoryConfigAttribute(UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, "LDAP Attribute", mapperModel);
}
diff --git a/federation/ldap/src/test/java/org/keycloak/federation/ldap/idm/model/LDAPMappersComparatorTest.java b/federation/ldap/src/test/java/org/keycloak/federation/ldap/idm/model/LDAPMappersComparatorTest.java
new file mode 100644
index 0000000..7ca7ff2
--- /dev/null
+++ b/federation/ldap/src/test/java/org/keycloak/federation/ldap/idm/model/LDAPMappersComparatorTest.java
@@ -0,0 +1,117 @@
+/*
+ * 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.federation.ldap.idm.model;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.keycloak.federation.ldap.LDAPConfig;
+import org.keycloak.federation.ldap.mappers.FullNameLDAPFederationMapper;
+import org.keycloak.federation.ldap.mappers.FullNameLDAPFederationMapperFactory;
+import org.keycloak.federation.ldap.mappers.LDAPMappersComparator;
+import org.keycloak.federation.ldap.mappers.UserAttributeLDAPFederationMapper;
+import org.keycloak.federation.ldap.mappers.UserAttributeLDAPFederationMapperFactory;
+import org.keycloak.models.LDAPConstants;
+import org.keycloak.models.UserFederationMapperModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.utils.KeycloakModelUtils;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class LDAPMappersComparatorTest {
+
+
+
+ @Test
+ public void testCompareWithCNUsername() {
+ Map<String, String> cfg = new HashMap<>();
+ cfg.put(LDAPConstants.USERNAME_LDAP_ATTRIBUTE, LDAPConstants.CN);
+ LDAPConfig config = new LDAPConfig(cfg);
+
+ List<UserFederationMapperModel> sorted = LDAPMappersComparator.sortAsc(config, getMappers());
+ assertOrder(sorted, "username-cn", "sAMAccountName", "first name", "full name");
+
+ sorted = LDAPMappersComparator.sortDesc(config, getMappers());
+ assertOrder(sorted, "full name", "first name", "sAMAccountName", "username-cn");
+ }
+
+ @Test
+ public void testCompareWithSAMAccountNameUsername() {
+ Map<String, String> cfg = new HashMap<>();
+ cfg.put(LDAPConstants.USERNAME_LDAP_ATTRIBUTE, LDAPConstants.SAM_ACCOUNT_NAME);
+ LDAPConfig config = new LDAPConfig(cfg);
+
+ List<UserFederationMapperModel> sorted = LDAPMappersComparator.sortAsc(config, getMappers());
+ assertOrder(sorted, "sAMAccountName", "username-cn", "first name", "full name");
+
+ sorted = LDAPMappersComparator.sortDesc(config, getMappers());
+ assertOrder(sorted, "full name", "first name", "username-cn", "sAMAccountName");
+ }
+
+ private void assertOrder(List<UserFederationMapperModel> result, String... names) {
+ Assert.assertEquals(result.size(), names.length);
+ for (int i=0 ; i<names.length ; i++) {
+ Assert.assertEquals(names[i], result.get(i).getName());
+ }
+ }
+
+ private Set<UserFederationMapperModel> getMappers() {
+ Set<UserFederationMapperModel> result = new HashSet<>();
+
+ UserFederationMapperModel mapperModel = KeycloakModelUtils.createUserFederationMapperModel("first name", "fed-provider", UserAttributeLDAPFederationMapperFactory.PROVIDER_ID,
+ UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, UserModel.FIRST_NAME,
+ UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, LDAPConstants.GIVENNAME,
+ UserAttributeLDAPFederationMapper.READ_ONLY, "true",
+ UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, "true",
+ UserAttributeLDAPFederationMapper.IS_MANDATORY_IN_LDAP, "true");
+ mapperModel.setId("idd1");
+ result.add(mapperModel);
+
+ mapperModel = KeycloakModelUtils.createUserFederationMapperModel("username-cn", "fed-provider", UserAttributeLDAPFederationMapperFactory.PROVIDER_ID,
+ UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, UserModel.USERNAME,
+ UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, LDAPConstants.CN,
+ UserAttributeLDAPFederationMapper.READ_ONLY, "true",
+ UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, "false",
+ UserAttributeLDAPFederationMapper.IS_MANDATORY_IN_LDAP, "true");
+ mapperModel.setId("idd2");
+ result.add(mapperModel);
+
+ mapperModel = KeycloakModelUtils.createUserFederationMapperModel("full name", "fed-provider", FullNameLDAPFederationMapperFactory.PROVIDER_ID,
+ FullNameLDAPFederationMapper.LDAP_FULL_NAME_ATTRIBUTE, LDAPConstants.CN,
+ UserAttributeLDAPFederationMapper.READ_ONLY, "true");
+ mapperModel.setId("idd3");
+ result.add(mapperModel);
+
+ mapperModel = KeycloakModelUtils.createUserFederationMapperModel("sAMAccountName", "fed-provider", UserAttributeLDAPFederationMapperFactory.PROVIDER_ID,
+ UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, UserModel.USERNAME,
+ UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, LDAPConstants.SAM_ACCOUNT_NAME,
+ UserAttributeLDAPFederationMapper.READ_ONLY, "false",
+ UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, "false",
+ UserAttributeLDAPFederationMapper.IS_MANDATORY_IN_LDAP, "true");
+ mapperModel.setId("idd4");
+ result.add(mapperModel);
+
+ return result;
+ }
+}
federation/pom.xml 2(+1 -1)
diff --git a/federation/pom.xml b/federation/pom.xml
index c60cd1c..14d3532 100755
--- a/federation/pom.xml
+++ b/federation/pom.xml
@@ -22,7 +22,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
integration/admin-client/pom.xml 7(+6 -1)
diff --git a/integration/admin-client/pom.xml b/integration/admin-client/pom.xml
index 00b5c27..3a36063 100755
--- a/integration/admin-client/pom.xml
+++ b/integration/admin-client/pom.xml
@@ -22,7 +22,7 @@
<parent>
<artifactId>keycloak-integration-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -57,6 +57,11 @@
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
+ <artifactId>resteasy-multipart-provider</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jackson2-provider</artifactId>
<scope>provided</scope>
</dependency>
diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ClientAttributeCertificateResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ClientAttributeCertificateResource.java
new file mode 100644
index 0000000..30d3bfb
--- /dev/null
+++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ClientAttributeCertificateResource.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2016 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * 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.admin.client.resource;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriInfo;
+import org.jboss.resteasy.annotations.cache.NoCache;
+import org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataInput;
+import org.keycloak.representations.KeyStoreConfig;
+import org.keycloak.representations.idm.CertificateRepresentation;
+
+/**
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc.
+ */
+public interface ClientAttributeCertificateResource {
+
+ /**
+ * Get key info
+ *
+ * @return
+ */
+ @GET
+ @NoCache
+ @Produces(MediaType.APPLICATION_JSON)
+ public CertificateRepresentation getKeyInfo();
+
+ /**
+ * Generate a new certificate with new key pair
+ *
+ * @return
+ */
+ @POST
+ @NoCache
+ @Path("generate")
+ @Produces(MediaType.APPLICATION_JSON)
+ public CertificateRepresentation generate();
+
+ /**
+ * Upload certificate and eventually private key
+ *
+ * @param uriInfo
+ * @param input
+ * @return
+ */
+ @POST
+ @Path("upload")
+ @Consumes(MediaType.MULTIPART_FORM_DATA)
+ @Produces(MediaType.APPLICATION_JSON)
+ public CertificateRepresentation uploadJks(@Context final UriInfo uriInfo, MultipartFormDataInput input);
+
+ /**
+ * Upload only certificate, not private key
+ *
+ * @param uriInfo
+ * @param input
+ * @return
+ */
+ @POST
+ @Path("upload-certificate")
+ @Consumes(MediaType.MULTIPART_FORM_DATA)
+ @Produces(MediaType.APPLICATION_JSON)
+ public CertificateRepresentation uploadJksCertificate(@Context final UriInfo uriInfo, MultipartFormDataInput input);
+
+ /**
+ * Get a keystore file for the client, containing private key and public certificate
+ *
+ * @param config Keystore configuration as JSON
+ * @return
+ */
+ @POST
+ @NoCache
+ @Path("/download")
+ @Produces(MediaType.APPLICATION_OCTET_STREAM)
+ @Consumes(MediaType.APPLICATION_JSON)
+ public byte[] getKeystore(final KeyStoreConfig config);
+
+ /**
+ * Generate a new keypair and certificate, and get the private key file
+ *
+ * Generates a keypair and certificate and serves the private key in a specified keystore format.
+ * Only generated public certificate is saved in Keycloak DB - the private key is not.
+ *
+ * @param config Keystore configuration as JSON
+ * @return
+ */
+ @POST
+ @NoCache
+ @Path("/generate-and-download")
+ @Produces(MediaType.APPLICATION_OCTET_STREAM)
+ @Consumes(MediaType.APPLICATION_JSON)
+ public byte[] generateAndGetKeystore(final KeyStoreConfig config);
+}
diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ClientResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ClientResource.java
index ee1c69a..644cc7a 100755
--- a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ClientResource.java
+++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ClientResource.java
@@ -34,7 +34,6 @@ import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import java.util.List;
import java.util.Map;
-import java.util.Set;
/**
* @author rodrigo.sasaki@icarros.com.br
@@ -55,21 +54,6 @@ public interface ClientResource {
@DELETE
public void remove();
- @GET
- @Path("allowed-origins")
- @Produces(MediaType.APPLICATION_JSON)
- public Set<String> getAllowedOrigins();
-
- @PUT
- @Path("allowed-origins")
- @Consumes(MediaType.APPLICATION_JSON)
- public void updateAllowedOrigins(Set<String> allowedOrigins);
-
- @DELETE
- @Path("allowed-origins")
- @Consumes(MediaType.APPLICATION_JSON)
- public void removeAllowedOrigins(Set<String> originsToRemove);
-
@POST
@Path("client-secret")
@Produces(MediaType.APPLICATION_JSON)
@@ -80,19 +64,31 @@ public interface ClientResource {
@Produces(MediaType.APPLICATION_JSON)
public CredentialRepresentation getSecret();
+ /**
+ * Generate a new registration access token for the client
+ *
+ * @return
+ */
+ @Path("registration-access-token")
+ @POST
+ @Produces(MediaType.APPLICATION_JSON)
+ @Consumes(MediaType.APPLICATION_JSON)
+ public ClientRepresentation regenerateRegistrationAccessToken();
+
+ /**
+ * Get representation of certificate resource
+ *
+ * @param attributePrefix
+ * @return
+ */
+ @Path("certificates/{attr}")
+ public ClientAttributeCertificateResource getCertficateResource(@PathParam("attr") String attributePrefix);
+
@GET
@NoCache
@Path("installation/providers/{providerId}")
public String getInstallationProvider(@PathParam("providerId") String providerId);
- @POST
- @Path("logout-all")
- public void logoutAllUsers();
-
- @POST
- @Path("logout-user/{username}")
- public void logoutUser(@PathParam("username") String username);
-
@Path("session-count")
@GET
@Produces(MediaType.APPLICATION_JSON)
diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RealmResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RealmResource.java
index 15ca073..86548ec 100644
--- a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RealmResource.java
+++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RealmResource.java
@@ -149,7 +149,7 @@ public interface RealmResource {
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Response partialImport(PartialImportRepresentation rep);
-
+
@Path("authentication")
@Consumes(MediaType.APPLICATION_JSON)
AuthenticationManagementResource flows();
diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/token/TokenManager.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/token/TokenManager.java
index ede1479..02056a7 100644
--- a/integration/admin-client/src/main/java/org/keycloak/admin/client/token/TokenManager.java
+++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/token/TokenManager.java
@@ -22,6 +22,7 @@ import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
import org.jboss.resteasy.client.jaxrs.ResteasyWebTarget;
import org.keycloak.admin.client.Config;
import org.keycloak.admin.client.resource.BasicAuthFilter;
+import org.keycloak.common.util.Time;
import org.keycloak.representations.AccessTokenResponse;
import javax.ws.rs.BadRequestException;
@@ -34,8 +35,11 @@ import java.util.Date;
*/
public class TokenManager {
+ private static final long DEFAULT_MIN_VALIDITY = 30;
+
private AccessTokenResponse currentToken;
- private Date expirationTime;
+ private long expirationTime;
+ private long minTokenValidity = DEFAULT_MIN_VALIDITY;
private final Config config;
private final ResteasyClient client;
@@ -73,10 +77,11 @@ public class TokenManager {
TokenService tokenService = target.proxy(TokenService.class);
- AccessTokenResponse response = tokenService.grantToken(config.getRealm(), form.asMap());
+ int requestTime = Time.currentTime();
+ currentToken = tokenService.grantToken(config.getRealm(), form.asMap());
+ expirationTime = requestTime + currentToken.getExpiresIn();
- defineCurrentToken(response);
- return response;
+ return currentToken;
}
public AccessTokenResponse refreshToken(){
@@ -95,27 +100,22 @@ public class TokenManager {
TokenService tokenService = target.proxy(TokenService.class);
try {
- AccessTokenResponse response = tokenService.refreshToken(config.getRealm(), form.asMap());
- defineCurrentToken(response);
- return response;
+ int requestTime = Time.currentTime();
+ currentToken = tokenService.refreshToken(config.getRealm(), form.asMap());
+ expirationTime = requestTime + currentToken.getExpiresIn();
+
+ return currentToken;
} catch (BadRequestException e) {
return grantToken();
}
}
- private void setExpirationTime() {
- Calendar cal = Calendar.getInstance();
- cal.add(Calendar.SECOND, (int) currentToken.getExpiresIn());
- expirationTime = cal.getTime();
+ public void setMinTokenValidity(long minTokenValidity) {
+ this.minTokenValidity = minTokenValidity;
}
private boolean tokenExpired() {
- return new Date().after(expirationTime);
- }
-
- private void defineCurrentToken(AccessTokenResponse accessTokenResponse){
- currentToken = accessTokenResponse;
- setExpirationTime();
+ return (Time.currentTime() + minTokenValidity) >= expirationTime;
}
}
diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/token/TokenService.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/token/TokenService.java
index 6acdc83..0cedec6 100755
--- a/integration/admin-client/src/main/java/org/keycloak/admin/client/token/TokenService.java
+++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/token/TokenService.java
@@ -36,10 +36,10 @@ public interface TokenService {
@POST
@Path("/realms/{realm}/protocol/openid-connect/token")
- public AccessTokenResponse grantToken(@PathParam("realm") String realm, MultivaluedMap<String, String> map);
+ AccessTokenResponse grantToken(@PathParam("realm") String realm, MultivaluedMap<String, String> map);
@POST
@Path("/realms/{realm}/protocol/openid-connect/token")
- public AccessTokenResponse refreshToken(@PathParam("realm") String realm, MultivaluedMap<String, String> map);
+ AccessTokenResponse refreshToken(@PathParam("realm") String realm, MultivaluedMap<String, String> map);
}
diff --git a/integration/client-registration/pom.xml b/integration/client-registration/pom.xml
index b0d72f6..5baf102 100755
--- a/integration/client-registration/pom.xml
+++ b/integration/client-registration/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-integration-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
diff --git a/integration/client-registration-cli/src/main/java/org/keycloak/client/registration/cli/Context.java b/integration/client-registration-cli/src/main/java/org/keycloak/client/registration/cli/Context.java
index 2d5dfe8..6f60c04 100644
--- a/integration/client-registration-cli/src/main/java/org/keycloak/client/registration/cli/Context.java
+++ b/integration/client-registration-cli/src/main/java/org/keycloak/client/registration/cli/Context.java
@@ -1,8 +1,8 @@
package org.keycloak.client.registration.cli;
+import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;
-import org.codehaus.jackson.map.SerializationConfig;
-import org.codehaus.jackson.map.annotate.JsonSerialize;
+import com.fasterxml.jackson.databind.SerializationFeature;
import org.keycloak.client.registration.ClientRegistration;
import org.keycloak.util.SystemPropertiesJsonParserFactory;
@@ -16,8 +16,8 @@ public class Context {
private static final ObjectMapper mapper = new ObjectMapper(new SystemPropertiesJsonParserFactory());
static {
- mapper.setSerializationInclusion(JsonSerialize.Inclusion.NON_NULL);
- mapper.enable(SerializationConfig.Feature.INDENT_OUTPUT);
+ mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
+ mapper.enable(SerializationFeature.INDENT_OUTPUT);
}
private ClientRegistration reg;
integration/pom.xml 2(+1 -1)
diff --git a/integration/pom.xml b/integration/pom.xml
index adf1219..2d1c939 100755
--- a/integration/pom.xml
+++ b/integration/pom.xml
@@ -20,7 +20,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<name>Keycloak Integration</name>
model/infinispan/pom.xml 2(+1 -1)
diff --git a/model/infinispan/pom.xml b/model/infinispan/pom.xml
index 87f1711..24634e6 100755
--- a/model/infinispan/pom.xml
+++ b/model/infinispan/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/CacheManager.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/CacheManager.java
new file mode 100755
index 0000000..e41913d
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/CacheManager.java
@@ -0,0 +1,217 @@
+package org.keycloak.models.cache.infinispan;
+
+import org.infinispan.Cache;
+import org.infinispan.notifications.cachelistener.annotation.CacheEntriesEvicted;
+import org.infinispan.notifications.cachelistener.annotation.CacheEntryInvalidated;
+import org.infinispan.notifications.cachelistener.event.CacheEntriesEvictedEvent;
+import org.infinispan.notifications.cachelistener.event.CacheEntryInvalidatedEvent;
+import org.jboss.logging.Logger;
+import org.keycloak.models.cache.infinispan.entities.AbstractRevisioned;
+import org.keycloak.models.cache.infinispan.entities.Revisioned;
+
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Predicate;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public abstract class CacheManager {
+ protected static final Logger logger = Logger.getLogger(CacheManager.class);
+ protected final Cache<String, Long> revisions;
+ protected final Cache<String, Revisioned> cache;
+ protected final UpdateCounter counter = new UpdateCounter();
+
+ public CacheManager(Cache<String, Revisioned> cache, Cache<String, Long> revisions) {
+ this.cache = cache;
+ this.revisions = revisions;
+ this.cache.addListener(this);
+ }
+
+ public Cache<String, Revisioned> getCache() {
+ return cache;
+ }
+
+ public long getCurrentCounter() {
+ return counter.current();
+ }
+
+ public Long getCurrentRevision(String id) {
+ Long revision = revisions.get(id);
+ if (revision == null) {
+ revision = counter.current();
+ }
+ // if you do cache.remove() on node 1 and the entry doesn't exist on node 2, node 2 never receives a invalidation event
+ // so, we do this to force this.
+ String invalidationKey = "invalidation.key" + id;
+ cache.putForExternalRead(invalidationKey, new AbstractRevisioned(-1L, invalidationKey));
+ return revision;
+ }
+
+ public void endRevisionBatch() {
+ try {
+ revisions.endBatch(true);
+ } catch (Exception e) {
+ }
+
+ }
+
+ public <T> T get(String id, Class<T> type) {
+ Revisioned o = (Revisioned)cache.get(id);
+ if (o == null) {
+ return null;
+ }
+ Long rev = revisions.get(id);
+ if (rev == null) {
+ RealmCacheManager.logger.tracev("get() missing rev");
+ return null;
+ }
+ long oRev = o.getRevision() == null ? -1L : o.getRevision().longValue();
+ if (rev > oRev) {
+ RealmCacheManager.logger.tracev("get() rev: {0} o.rev: {1}", rev.longValue(), oRev);
+ return null;
+ }
+ return o != null && type.isInstance(o) ? type.cast(o) : null;
+ }
+
+ public Object invalidateObject(String id) {
+ Revisioned removed = (Revisioned)cache.remove(id);
+ // if you do cache.remove() on node 1 and the entry doesn't exist on node 2, node 2 never receives a invalidation event
+ // so, we do this to force the event.
+ cache.remove("invalidation.key" + id);
+ bumpVersion(id);
+ return removed;
+ }
+
+ protected void bumpVersion(String id) {
+ long next = counter.next();
+ Object rev = revisions.put(id, next);
+ }
+
+ public void addRevisioned(Revisioned object, long startupRevision) {
+ //startRevisionBatch();
+ String id = object.getId();
+ try {
+ //revisions.getAdvancedCache().lock(id);
+ Long rev = revisions.get(id);
+ if (rev == null) {
+ if (id.endsWith("realm.clients")) RealmCacheManager.logger.trace("addRevisioned rev == null realm.clients");
+ rev = counter.current();
+ revisions.put(id, rev);
+ }
+ revisions.startBatch();
+ if (!revisions.getAdvancedCache().lock(id)) {
+ RealmCacheManager.logger.trace("Could not obtain version lock");
+ return;
+ }
+ rev = revisions.get(id);
+ if (rev == null) {
+ if (id.endsWith("realm.clients")) RealmCacheManager.logger.trace("addRevisioned rev2 == null realm.clients");
+ return;
+ }
+ if (rev > startupRevision) { // revision is ahead transaction start. Other transaction updated in the meantime. Don't cache
+ if (RealmCacheManager.logger.isTraceEnabled()) {
+ RealmCacheManager.logger.tracev("Skipped cache. Current revision {0}, Transaction start revision {1}", object.getRevision(), startupRevision);
+ }
+ return;
+ }
+ if (rev.equals(object.getRevision())) {
+ if (id.endsWith("realm.clients")) RealmCacheManager.logger.tracev("adding Object.revision {0} rev {1}", object.getRevision(), rev);
+ cache.putForExternalRead(id, object);
+ return;
+ }
+ if (rev > object.getRevision()) { // revision is ahead, don't cache
+ if (id.endsWith("realm.clients")) RealmCacheManager.logger.trace("addRevisioned revision is ahead realm.clients");
+ return;
+ }
+ // revisions cache has a lower value than the object.revision, so update revision and add it to cache
+ if (id.endsWith("realm.clients")) RealmCacheManager.logger.tracev("adding Object.revision {0} rev {1}", object.getRevision(), rev);
+ revisions.put(id, object.getRevision());
+ cache.putForExternalRead(id, object);
+ } finally {
+ endRevisionBatch();
+ }
+
+ }
+
+ public void clear() {
+ cache.clear();
+ }
+
+ public void addInvalidations(Predicate<Map.Entry<String, Revisioned>> predicate, Set<String> invalidations) {
+ Iterator<Map.Entry<String, Revisioned>> it = getEntryIterator(predicate);
+ while (it.hasNext()) {
+ invalidations.add(it.next().getKey());
+ }
+ }
+
+ private Iterator<Map.Entry<String, Revisioned>> getEntryIterator(Predicate<Map.Entry<String, Revisioned>> predicate) {
+ return cache
+ .entrySet()
+ .stream()
+ .filter(predicate).iterator();
+ }
+
+ @CacheEntryInvalidated
+ public void cacheInvalidated(CacheEntryInvalidatedEvent<String, Object> event) {
+ if (event.isPre()) {
+ String key = event.getKey();
+ if (key.startsWith("invalidation.key")) {
+ // if you do cache.remove() on node 1 and the entry doesn't exist on node 2, node 2 never receives a invalidation event
+ // so, we do this to force this.
+ String bump = key.substring("invalidation.key".length());
+ RealmCacheManager.logger.tracev("bumping invalidation key {0}", bump);
+ bumpVersion(bump);
+ return;
+ }
+
+ } else {
+ //if (!event.isPre()) {
+ String key = event.getKey();
+ if (key.startsWith("invalidation.key")) {
+ // if you do cache.remove() on node 1 and the entry doesn't exist on node 2, node 2 never receives a invalidation event
+ // so, we do this to force this.
+ String bump = key.substring("invalidation.key".length());
+ bumpVersion(bump);
+ RealmCacheManager.logger.tracev("bumping invalidation key {0}", bump);
+ return;
+ }
+ bumpVersion(key);
+ Object object = event.getValue();
+ if (object != null) {
+ bumpVersion(key);
+ Predicate<Map.Entry<String, Revisioned>> predicate = getInvalidationPredicate(object);
+ if (predicate != null) runEvictions(predicate);
+ RealmCacheManager.logger.tracev("invalidating: {0}" + object.getClass().getName());
+ }
+ }
+ }
+
+ @CacheEntriesEvicted
+ public void cacheEvicted(CacheEntriesEvictedEvent<String, Object> event) {
+ if (!event.isPre())
+ for (Map.Entry<String, Object> entry : event.getEntries().entrySet()) {
+ Object object = entry.getValue();
+ bumpVersion(entry.getKey());
+ if (object == null) continue;
+ RealmCacheManager.logger.tracev("evicting: {0}" + object.getClass().getName());
+ Predicate<Map.Entry<String, Revisioned>> predicate = getInvalidationPredicate(object);
+ if (predicate != null) runEvictions(predicate);
+ }
+ }
+
+ public void runEvictions(Predicate<Map.Entry<String, Revisioned>> current) {
+ Set<String> evictions = new HashSet<>();
+ addInvalidations(current, evictions);
+ RealmCacheManager.logger.tracev("running evictions size: {0}", evictions.size());
+ for (String key : evictions) {
+ cache.evict(key);
+ bumpVersion(key);
+ }
+ }
+
+ protected abstract Predicate<Map.Entry<String, Revisioned>> getInvalidationPredicate(Object object);
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedUser.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedUser.java
index c1bf4cd..af30000 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedUser.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedUser.java
@@ -34,8 +34,7 @@ import java.util.Set;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
-public class CachedUser implements Serializable {
- private String id;
+public class CachedUser extends AbstractRevisioned implements InRealm {
private String realm;
private String username;
private Long createdTimestamp;
@@ -53,8 +52,10 @@ public class CachedUser implements Serializable {
private Set<String> roleMappings = new HashSet<>();
private Set<String> groups = new HashSet<>();
- public CachedUser(RealmModel realm, UserModel user) {
- this.id = user.getId();
+
+
+ public CachedUser(Long revision, RealmModel realm, UserModel user) {
+ super(revision, user.getId());
this.realm = realm.getId();
this.username = user.getUsername();
this.createdTimestamp = user.getCreatedTimestamp();
@@ -80,10 +81,6 @@ public class CachedUser implements Serializable {
}
}
- public String getId() {
- return id;
- }
-
public String getRealm() {
return realm;
}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/UserListQuery.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/UserListQuery.java
new file mode 100755
index 0000000..c19e7aa
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/UserListQuery.java
@@ -0,0 +1,49 @@
+package org.keycloak.models.cache.infinispan.entities;
+
+import org.keycloak.models.RealmModel;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class UserListQuery extends AbstractRevisioned implements UserQuery {
+ private final Set<String> users;
+ private final String realm;
+ private final String realmName;
+
+ public UserListQuery(Long revisioned, String id, RealmModel realm, Set<String> users) {
+ super(revisioned, id);
+ this.realm = realm.getId();
+ this.realmName = realm.getName();
+ this.users = users;
+ }
+
+ public UserListQuery(Long revisioned, String id, RealmModel realm, String user) {
+ super(revisioned, id);
+ this.realm = realm.getId();
+ this.realmName = realm.getName();
+ this.users = new HashSet<>();
+ this.users.add(user);
+ }
+
+ @Override
+ public Set<String> getUsers() {
+ return users;
+ }
+
+ @Override
+ public String getRealm() {
+ return realm;
+ }
+
+ @Override
+ public String toString() {
+ return "UserListQuery{" +
+ "id='" + getId() + "'" +
+ "realmName='" + realmName + '\'' +
+ '}';
+ }
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/UserQuery.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/UserQuery.java
new file mode 100755
index 0000000..5f890a2
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/UserQuery.java
@@ -0,0 +1,11 @@
+package org.keycloak.models.cache.infinispan.entities;
+
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface UserQuery extends InRealm {
+ Set<String> getUsers();
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanCacheRealmProviderFactory.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanCacheRealmProviderFactory.java
index fb9946b..f32ac69 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanCacheRealmProviderFactory.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanCacheRealmProviderFactory.java
@@ -35,12 +35,12 @@ public class InfinispanCacheRealmProviderFactory implements CacheRealmProviderFa
private static final Logger log = Logger.getLogger(InfinispanCacheRealmProviderFactory.class);
- protected volatile StreamRealmCache realmCache;
+ protected volatile RealmCacheManager realmCache;
@Override
public CacheRealmProvider create(KeycloakSession session) {
lazyInit(session);
- return new StreamCacheRealmProvider(realmCache, session);
+ return new RealmCacheSession(realmCache, session);
}
private void lazyInit(KeycloakSession session) {
@@ -49,7 +49,7 @@ public class InfinispanCacheRealmProviderFactory implements CacheRealmProviderFa
if (realmCache == null) {
Cache<String, Revisioned> cache = session.getProvider(InfinispanConnectionProvider.class).getCache(InfinispanConnectionProvider.REALM_CACHE_NAME);
Cache<String, Long> revisions = session.getProvider(InfinispanConnectionProvider.class).getCache(InfinispanConnectionProvider.VERSION_CACHE_NAME);
- realmCache = new StreamRealmCache(cache, revisions);
+ realmCache = new RealmCacheManager(cache, revisions);
}
}
}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanCacheUserProviderFactory.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanCacheUserProviderFactory.java
index e8657ff..9e93d55 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanCacheUserProviderFactory.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanCacheUserProviderFactory.java
@@ -29,6 +29,7 @@ import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.cache.CacheUserProvider;
import org.keycloak.models.cache.CacheUserProviderFactory;
import org.keycloak.models.cache.infinispan.entities.CachedUser;
+import org.keycloak.models.cache.infinispan.entities.Revisioned;
import java.util.concurrent.ConcurrentHashMap;
@@ -39,25 +40,23 @@ public class InfinispanCacheUserProviderFactory implements CacheUserProviderFact
private static final Logger log = Logger.getLogger(InfinispanCacheUserProviderFactory.class);
- protected volatile InfinispanUserCache userCache;
+ protected volatile UserCacheManager userCache;
- protected final RealmLookup usernameLookup = new RealmLookup();
- protected final RealmLookup emailLookup = new RealmLookup();
@Override
public CacheUserProvider create(KeycloakSession session) {
lazyInit(session);
- return new DefaultCacheUserProvider(userCache, session);
+ return new UserCacheSession(userCache, session);
}
private void lazyInit(KeycloakSession session) {
if (userCache == null) {
synchronized (this) {
if (userCache == null) {
- Cache<String, CachedUser> cache = session.getProvider(InfinispanConnectionProvider.class).getCache(InfinispanConnectionProvider.USER_CACHE_NAME);
- cache.addListener(new CacheListener());
- userCache = new InfinispanUserCache(cache, usernameLookup, emailLookup);
+ Cache<String, Revisioned> cache = session.getProvider(InfinispanConnectionProvider.class).getCache(InfinispanConnectionProvider.USER_CACHE_NAME);
+ Cache<String, Long> revisions = session.getProvider(InfinispanConnectionProvider.class).getCache(InfinispanConnectionProvider.VERSION_CACHE_NAME);
+ userCache = new UserCacheManager(cache, revisions);
}
}
}
@@ -81,100 +80,5 @@ public class InfinispanCacheUserProviderFactory implements CacheUserProviderFact
return "default";
}
- @Listener
- public class CacheListener {
-
- @CacheEntryCreated
- public void userCreated(CacheEntryCreatedEvent<String, CachedUser> event) {
- if (!event.isPre()) {
- CachedUser user = event.getValue();
- if (user != null) {
- String realm = user.getRealm();
-
- usernameLookup.put(realm, user.getUsername(), user.getId());
- if (user.getEmail() != null) {
- emailLookup.put(realm, user.getEmail(), user.getId());
- }
-
- log.tracev("User added realm={0}, id={1}, username={2}", realm, user.getId(), user.getUsername());
- }
- }
- }
-
- @CacheEntryRemoved
- public void userRemoved(CacheEntryRemovedEvent<String, CachedUser> event) {
- if (event.isPre()) {
- CachedUser user = event.getValue();
- if (user != null) {
- removeUser(user);
-
- log.tracev("User invalidated realm={0}, id={1}, username={2}", user.getRealm(), user.getId(), user.getUsername());
- }
- }
- }
-
- @CacheEntryInvalidated
- public void userInvalidated(CacheEntryInvalidatedEvent<String, CachedUser> event) {
- if (event.isPre()) {
- CachedUser user = event.getValue();
- if (user != null) {
- removeUser(user);
-
- log.tracev("User invalidated realm={0}, id={1}, username={2}", user.getRealm(), user.getId(), user.getUsername());
- }
- }
- }
-
- @CacheEntriesEvicted
- public void userEvicted(CacheEntriesEvictedEvent<String, CachedUser> event) {
- for (CachedUser user : event.getEntries().values()) {
- removeUser(user);
-
- log.tracev("User evicted realm={0}, id={1}, username={2}", user.getRealm(), user.getId(), user.getUsername());
- }
- }
-
- private void removeUser(CachedUser cachedUser) {
- String realm = cachedUser.getRealm();
- usernameLookup.remove(realm, cachedUser.getUsername());
- if (cachedUser.getEmail() != null) {
- emailLookup.remove(realm, cachedUser.getEmail());
- }
- }
-
- }
-
- static class RealmLookup {
-
- protected final ConcurrentHashMap<String, ConcurrentHashMap<String, String>> lookup = new ConcurrentHashMap<>();
-
- public void put(String realm, String key, String value) {
- ConcurrentHashMap<String, String> map = lookup.get(realm);
- if(map == null) {
- map = new ConcurrentHashMap<>();
- ConcurrentHashMap<String, String> p = lookup.putIfAbsent(realm, map);
- if (p != null) {
- map = p;
- }
- }
- map.put(key, value);
- }
-
- public String get(String realm, String key) {
- ConcurrentHashMap<String, String> map = lookup.get(realm);
- return map != null ? map.get(key) : null;
- }
-
- public void remove(String realm, String key) {
- ConcurrentHashMap<String, String> map = lookup.get(realm);
- if (map != null) {
- map.remove(key);
- if (map.isEmpty()) {
- lookup.remove(realm);
- }
- }
- }
-
- }
}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmCacheManager.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmCacheManager.java
new file mode 100755
index 0000000..55e3b38
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmCacheManager.java
@@ -0,0 +1,162 @@
+/*
+ * 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.models.cache.infinispan;
+
+import org.infinispan.Cache;
+import org.infinispan.notifications.Listener;
+import org.jboss.logging.Logger;
+import org.keycloak.models.cache.infinispan.entities.CachedClient;
+import org.keycloak.models.cache.infinispan.entities.CachedClientTemplate;
+import org.keycloak.models.cache.infinispan.entities.CachedGroup;
+import org.keycloak.models.cache.infinispan.entities.CachedRealm;
+import org.keycloak.models.cache.infinispan.entities.CachedRole;
+import org.keycloak.models.cache.infinispan.entities.Revisioned;
+import org.keycloak.models.cache.infinispan.stream.ClientQueryPredicate;
+import org.keycloak.models.cache.infinispan.stream.ClientTemplateQueryPredicate;
+import org.keycloak.models.cache.infinispan.stream.GroupQueryPredicate;
+import org.keycloak.models.cache.infinispan.stream.HasRolePredicate;
+import org.keycloak.models.cache.infinispan.stream.InClientPredicate;
+import org.keycloak.models.cache.infinispan.stream.InRealmPredicate;
+import org.keycloak.models.cache.infinispan.stream.RealmQueryPredicate;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Predicate;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+@Listener
+public class RealmCacheManager extends CacheManager {
+
+ protected static final Logger logger = Logger.getLogger(RealmCacheManager.class);
+
+ public RealmCacheManager(Cache<String, Revisioned> cache, Cache<String, Long> revisions) {
+ super(cache, revisions);
+ }
+
+
+ public void realmInvalidation(String id, Set<String> invalidations) {
+ Predicate<Map.Entry<String, Revisioned>> predicate = getRealmInvalidationPredicate(id);
+ addInvalidations(predicate, invalidations);
+ }
+
+ public Predicate<Map.Entry<String, Revisioned>> getRealmInvalidationPredicate(String id) {
+ return RealmQueryPredicate.create().realm(id);
+ }
+
+ public void clientInvalidation(String id, Set<String> invalidations) {
+ addInvalidations(getClientInvalidationPredicate(id), invalidations);
+ }
+
+ public Predicate<Map.Entry<String, Revisioned>> getClientInvalidationPredicate(String id) {
+ return ClientQueryPredicate.create().client(id);
+ }
+
+ public void roleInvalidation(String id, Set<String> invalidations) {
+ addInvalidations(getRoleInvalidationPredicate(id), invalidations);
+
+ }
+
+ public Predicate<Map.Entry<String, Revisioned>> getRoleInvalidationPredicate(String id) {
+ return HasRolePredicate.create().role(id);
+ }
+
+ public void groupInvalidation(String id, Set<String> invalidations) {
+ addInvalidations(getGroupInvalidationPredicate(id), invalidations);
+
+ }
+
+ public Predicate<Map.Entry<String, Revisioned>> getGroupInvalidationPredicate(String id) {
+ return GroupQueryPredicate.create().group(id);
+ }
+
+ public void clientTemplateInvalidation(String id, Set<String> invalidations) {
+ addInvalidations(getClientTemplateInvalidationPredicate(id), invalidations);
+
+ }
+
+ public Predicate<Map.Entry<String, Revisioned>> getClientTemplateInvalidationPredicate(String id) {
+ return ClientTemplateQueryPredicate.create().template(id);
+ }
+
+ public void realmRemoval(String id, Set<String> invalidations) {
+ Predicate<Map.Entry<String, Revisioned>> predicate = getRealmRemovalPredicate(id);
+ addInvalidations(predicate, invalidations);
+ }
+
+ public Predicate<Map.Entry<String, Revisioned>> getRealmRemovalPredicate(String id) {
+ Predicate<Map.Entry<String, Revisioned>> predicate = null;
+ predicate = RealmQueryPredicate.create().realm(id)
+ .or(InRealmPredicate.create().realm(id));
+ return predicate;
+ }
+
+ public void clientAdded(String realmId, String id, Set<String> invalidations) {
+ addInvalidations(getClientAddedPredicate(realmId), invalidations);
+ }
+
+ public Predicate<Map.Entry<String, Revisioned>> getClientAddedPredicate(String realmId) {
+ return ClientQueryPredicate.create().inRealm(realmId);
+ }
+
+ public void clientRemoval(String realmId, String id, Set<String> invalidations) {
+ Predicate<Map.Entry<String, Revisioned>> predicate = null;
+ predicate = getClientRemovalPredicate(realmId, id);
+ addInvalidations(predicate, invalidations);
+ }
+
+ public Predicate<Map.Entry<String, Revisioned>> getClientRemovalPredicate(String realmId, String id) {
+ Predicate<Map.Entry<String, Revisioned>> predicate;
+ predicate = ClientQueryPredicate.create().inRealm(realmId)
+ .or(ClientQueryPredicate.create().client(id))
+ .or(InClientPredicate.create().client(id));
+ return predicate;
+ }
+
+ public void roleRemoval(String id, Set<String> invalidations) {
+ addInvalidations(getRoleRemovalPredicate(id), invalidations);
+
+ }
+
+ public Predicate<Map.Entry<String, Revisioned>> getRoleRemovalPredicate(String id) {
+ return getRoleInvalidationPredicate(id);
+ }
+
+ @Override
+ protected Predicate<Map.Entry<String, Revisioned>> getInvalidationPredicate(Object object) {
+ if (object instanceof CachedRealm) {
+ CachedRealm cached = (CachedRealm)object;
+ return getRealmRemovalPredicate(cached.getId());
+ } else if (object instanceof CachedClient) {
+ CachedClient cached = (CachedClient)object;
+ Predicate<Map.Entry<String, Revisioned>> predicate = getClientRemovalPredicate(cached.getRealm(), cached.getId());
+ return predicate;
+ } else if (object instanceof CachedRole) {
+ CachedRole cached = (CachedRole)object;
+ return getRoleRemovalPredicate(cached.getId());
+ } else if (object instanceof CachedGroup) {
+ CachedGroup cached = (CachedGroup)object;
+ return getGroupInvalidationPredicate(cached.getId());
+ } else if (object instanceof CachedClientTemplate) {
+ CachedClientTemplate cached = (CachedClientTemplate)object;
+ return getClientTemplateInvalidationPredicate(cached.getId());
+ }
+ return null;
+ }
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UpdateCounter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UpdateCounter.java
index 0c83dc8..966b1f4 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UpdateCounter.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UpdateCounter.java
@@ -3,17 +3,19 @@ package org.keycloak.models.cache.infinispan;
import java.util.concurrent.atomic.AtomicLong;
/**
+ * Used to track cache revisions
+ *
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class UpdateCounter {
- private static final AtomicLong counter = new AtomicLong();
+ private final AtomicLong counter = new AtomicLong();
- public static long current() {
+ public long current() {
return counter.get();
}
- public static long next() {
+ public long next() {
return counter.incrementAndGet();
}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java
index 6421ca2..6e53626 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java
@@ -31,11 +31,11 @@ import java.util.*;
public class UserAdapter implements UserModel {
protected UserModel updated;
protected CachedUser cached;
- protected CacheUserProvider userProviderCache;
+ protected UserCacheSession userProviderCache;
protected KeycloakSession keycloakSession;
protected RealmModel realm;
- public UserAdapter(CachedUser cached, CacheUserProvider userProvider, KeycloakSession keycloakSession, RealmModel realm) {
+ public UserAdapter(CachedUser cached, UserCacheSession userProvider, KeycloakSession keycloakSession, RealmModel realm) {
this.cached = cached;
this.userProviderCache = userProvider;
this.keycloakSession = keycloakSession;
@@ -44,7 +44,7 @@ public class UserAdapter implements UserModel {
protected void getDelegateForUpdate() {
if (updated == null) {
- userProviderCache.registerUserInvalidation(realm, getId());
+ userProviderCache.registerUserInvalidation(realm, cached);
updated = userProviderCache.getDelegate().getUserById(getId(), realm);
if (updated == null) throw new IllegalStateException("Not found in database");
}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java
index 7630225..10d6205 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java
@@ -270,7 +270,9 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
@Override
public void removeUserSession(RealmModel realm, UserSessionModel session) {
UserSessionEntity entity = getUserSessionEntity(session, false);
- removeUserSession(realm, entity, false);
+ if (entity != null) {
+ removeUserSession(realm, entity, false);
+ }
}
@Override
@@ -553,7 +555,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
return ((UserSessionAdapter) userSession).getEntity();
} else {
Cache<String, SessionEntity> cache = getCache(offline);
- return (UserSessionEntity) cache.get(userSession.getId());
+ return cache != null ? (UserSessionEntity) cache.get(userSession.getId()) : null;
}
}
@@ -578,7 +580,9 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
@Override
public void removeOfflineUserSession(RealmModel realm, UserSessionModel userSession) {
UserSessionEntity userSessionEntity = getUserSessionEntity(userSession, true);
- removeUserSession(realm, userSessionEntity, true);
+ if (userSessionEntity != null) {
+ removeUserSession(realm, userSessionEntity, true);
+ }
}
@Override
diff --git a/model/infinispan/src/test/java/org/keycloak/models/sessions/infinispan/initializer/ClusteredCacheBehaviorTest.java b/model/infinispan/src/test/java/org/keycloak/models/sessions/infinispan/initializer/ClusteredCacheBehaviorTest.java
index bc8b4a3..3dcc913 100755
--- a/model/infinispan/src/test/java/org/keycloak/models/sessions/infinispan/initializer/ClusteredCacheBehaviorTest.java
+++ b/model/infinispan/src/test/java/org/keycloak/models/sessions/infinispan/initializer/ClusteredCacheBehaviorTest.java
@@ -9,9 +9,11 @@ import org.infinispan.manager.DefaultCacheManager;
import org.infinispan.manager.EmbeddedCacheManager;
import org.infinispan.notifications.Listener;
import org.infinispan.notifications.cachelistener.annotation.CacheEntriesEvicted;
+import org.infinispan.notifications.cachelistener.annotation.CacheEntryCreated;
import org.infinispan.notifications.cachelistener.annotation.CacheEntryInvalidated;
import org.infinispan.notifications.cachelistener.annotation.CacheEntryRemoved;
import org.infinispan.notifications.cachelistener.event.CacheEntriesEvictedEvent;
+import org.infinispan.notifications.cachelistener.event.CacheEntryCreatedEvent;
import org.infinispan.notifications.cachelistener.event.CacheEntryInvalidatedEvent;
import org.infinispan.notifications.cachelistener.event.CacheEntryRemovedEvent;
import org.junit.Ignore;
@@ -61,6 +63,11 @@ public class ClusteredCacheBehaviorTest {
}
+ @CacheEntryCreated
+ public void created(CacheEntryCreatedEvent event) {
+
+ System.out.println("Listener '" + name + "' entry created " + event.getKey() + " isPre: " + event.isPre());
+ }
@CacheEntryRemoved
public void removed(CacheEntryRemovedEvent<String, Object> event) {
@@ -91,6 +98,8 @@ public class ClusteredCacheBehaviorTest {
System.out.println("node1 create entry");
node1Cache.put("key", "node1");
+ System.out.println("node1 create entry");
+ node1Cache.put("key", "node111");
System.out.println("node2 create entry");
node2Cache.put("key", "node2");
System.out.println("node1 remove entry");
model/jpa/pom.xml 2(+1 -1)
diff --git a/model/jpa/pom.xml b/model/jpa/pom.xml
index 57baeea..c1176f3 100755
--- a/model/jpa/pom.xml
+++ b/model/jpa/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
diff --git a/model/jpa/src/main/java/org/keycloak/connections/jpa/DefaultJpaConnectionProviderFactory.java b/model/jpa/src/main/java/org/keycloak/connections/jpa/DefaultJpaConnectionProviderFactory.java
index 144df70..444e891 100755
--- a/model/jpa/src/main/java/org/keycloak/connections/jpa/DefaultJpaConnectionProviderFactory.java
+++ b/model/jpa/src/main/java/org/keycloak/connections/jpa/DefaultJpaConnectionProviderFactory.java
@@ -121,12 +121,7 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide
}
}
- String driverDialect = config.get("driverDialect");
- if (driverDialect != null && driverDialect.length() > 0) {
- properties.put("hibernate.dialect", driverDialect);
- }
-
- String schema = config.get("schema");
+ String schema = getSchema();
if (schema != null) {
properties.put(JpaUtils.HIBERNATE_DEFAULT_SCHEMA, schema);
}
@@ -147,6 +142,11 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide
connection = getConnection();
try{
prepareOperationalInfo(connection);
+
+ String driverDialect = detectDialect(connection);
+ if (driverDialect != null) {
+ properties.put("hibernate.dialect", driverDialect);
+ }
if (databaseSchema != null) {
logger.trace("Updating database");
@@ -167,7 +167,7 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide
}
if (currentVersion == null || !JpaUpdaterProvider.LAST_VERSION.equals(currentVersion)) {
- updater.update(session, connection, schema);
+ updater.update(connection, schema);
} else {
logger.debug("Database is up to date");
}
@@ -184,13 +184,24 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide
emf = Persistence.createEntityManagerFactory(unitName, properties);
logger.trace("EntityManagerFactory created");
+ } catch (Exception e) {
+ // Safe rollback
+ if (connection != null) {
+ try {
+ connection.rollback();
+ } catch (SQLException e2) {
+ logger.warn("Can't rollback connection", e2);
+ }
+ }
+
+ throw e;
} finally {
// Close after creating EntityManagerFactory to prevent in-mem databases from closing
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
- logger.warn(e);
+ logger.warn("Can't close connection", e);
}
}
}
@@ -198,7 +209,7 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide
}
}
}
-
+
protected void prepareOperationalInfo(Connection connection) {
try {
operationalInfo = new LinkedHashMap<>();
@@ -207,12 +218,51 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide
operationalInfo.put("databaseUser", md.getUserName());
operationalInfo.put("databaseProduct", md.getDatabaseProductName() + " " + md.getDatabaseProductVersion());
operationalInfo.put("databaseDriver", md.getDriverName() + " " + md.getDriverVersion());
+
+ logger.debugf("Database info: %s", operationalInfo.toString());
} catch (SQLException e) {
logger.warn("Unable to prepare operational info due database exception: " + e.getMessage());
}
}
- private Connection getConnection() {
+
+ protected String detectDialect(Connection connection) {
+ String driverDialect = config.get("driverDialect");
+ if (driverDialect != null && driverDialect.length() > 0) {
+ return driverDialect;
+ } else {
+ try {
+ String dbProductName = connection.getMetaData().getDatabaseProductName();
+ String dbProductVersion = connection.getMetaData().getDatabaseProductVersion();
+
+ // For MSSQL2014, we may need to fix the autodetected dialect by hibernate
+ if (dbProductName.equals("Microsoft SQL Server")) {
+ String topVersionStr = dbProductVersion.split("\\.")[0];
+ boolean shouldSet2012Dialect = true;
+ try {
+ int topVersion = Integer.parseInt(topVersionStr);
+ if (topVersion < 12) {
+ shouldSet2012Dialect = false;
+ }
+ } catch (NumberFormatException nfe) {
+ }
+ if (shouldSet2012Dialect) {
+ String sql2012Dialect = "org.hibernate.dialect.SQLServer2012Dialect";
+ logger.debugf("Manually override hibernate dialect to %s", sql2012Dialect);
+ return sql2012Dialect;
+ }
+ }
+ } catch (SQLException e) {
+ logger.warnf("Unable to detect hibernate dialect due database exception : %s", e.getMessage());
+ }
+
+ return null;
+ }
+ }
+
+
+ @Override
+ public Connection getConnection() {
try {
String dataSourceLookup = config.get("dataSource");
if (dataSourceLookup != null) {
@@ -226,6 +276,11 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide
throw new RuntimeException("Failed to connect to database", e);
}
}
+
+ @Override
+ public String getSchema() {
+ return config.get("schema");
+ }
@Override
public Map<String,String> getOperationalInfo() {
diff --git a/model/jpa/src/main/java/org/keycloak/connections/jpa/JpaConnectionProviderFactory.java b/model/jpa/src/main/java/org/keycloak/connections/jpa/JpaConnectionProviderFactory.java
index edede60..8d687bc 100644
--- a/model/jpa/src/main/java/org/keycloak/connections/jpa/JpaConnectionProviderFactory.java
+++ b/model/jpa/src/main/java/org/keycloak/connections/jpa/JpaConnectionProviderFactory.java
@@ -17,6 +17,8 @@
package org.keycloak.connections.jpa;
+import java.sql.Connection;
+
import org.keycloak.provider.ProviderFactory;
/**
@@ -24,4 +26,9 @@ import org.keycloak.provider.ProviderFactory;
*/
public interface JpaConnectionProviderFactory extends ProviderFactory<JpaConnectionProvider> {
+ // Caller is responsible for closing connection
+ Connection getConnection();
+
+ String getSchema();
+
}
diff --git a/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/JpaUpdaterProvider.java b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/JpaUpdaterProvider.java
index 2e2c5d1..0e0dea2 100755
--- a/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/JpaUpdaterProvider.java
+++ b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/JpaUpdaterProvider.java
@@ -17,7 +17,6 @@
package org.keycloak.connections.jpa.updater;
-import org.keycloak.models.KeycloakSession;
import org.keycloak.provider.Provider;
import java.sql.Connection;
@@ -33,7 +32,7 @@ public interface JpaUpdaterProvider extends Provider {
public String getCurrentVersionSql(String defaultSchema);
- public void update(KeycloakSession session, Connection connection, String defaultSchema);
+ public void update(Connection connection, String defaultSchema);
public void validate(Connection connection, String defaultSchema);
diff --git a/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/conn/DefaultLiquibaseConnectionProvider.java b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/conn/DefaultLiquibaseConnectionProvider.java
new file mode 100644
index 0000000..a011b41
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/conn/DefaultLiquibaseConnectionProvider.java
@@ -0,0 +1,237 @@
+/*
+ * 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.connections.jpa.updater.liquibase.conn;
+
+import java.sql.Connection;
+
+import liquibase.Liquibase;
+import liquibase.changelog.ChangeSet;
+import liquibase.changelog.DatabaseChangeLog;
+import liquibase.database.Database;
+import liquibase.database.DatabaseFactory;
+import liquibase.database.core.DB2Database;
+import liquibase.database.jvm.JdbcConnection;
+import liquibase.exception.LiquibaseException;
+import liquibase.lockservice.LockService;
+import liquibase.lockservice.LockServiceFactory;
+import liquibase.logging.LogFactory;
+import liquibase.logging.LogLevel;
+import liquibase.resource.ClassLoaderResourceAccessor;
+import liquibase.servicelocator.ServiceLocator;
+import liquibase.sqlgenerator.SqlGeneratorFactory;
+import org.jboss.logging.Logger;
+import org.keycloak.Config;
+import org.keycloak.connections.jpa.updater.liquibase.LiquibaseJpaUpdaterProvider;
+import org.keycloak.connections.jpa.updater.liquibase.PostgresPlusDatabase;
+import org.keycloak.connections.jpa.updater.liquibase.lock.CustomInsertLockRecordGenerator;
+import org.keycloak.connections.jpa.updater.liquibase.lock.CustomLockService;
+import org.keycloak.connections.jpa.updater.liquibase.lock.DummyLockService;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class DefaultLiquibaseConnectionProvider implements LiquibaseConnectionProviderFactory, LiquibaseConnectionProvider {
+
+ private static final Logger logger = Logger.getLogger(DefaultLiquibaseConnectionProvider.class);
+
+ private volatile boolean initialized = false;
+
+ @Override
+ public LiquibaseConnectionProvider create(KeycloakSession session) {
+ if (!initialized) {
+ synchronized (this) {
+ if (!initialized) {
+ baseLiquibaseInitialization();
+ initialized = true;
+ }
+ }
+ }
+ return this;
+ }
+
+ protected void baseLiquibaseInitialization() {
+ ServiceLocator sl = ServiceLocator.getInstance();
+
+ if (!System.getProperties().containsKey("liquibase.scan.packages")) {
+ if (sl.getPackages().remove("liquibase.core")) {
+ sl.addPackageToScan("liquibase.core.xml");
+ }
+
+ if (sl.getPackages().remove("liquibase.parser")) {
+ sl.addPackageToScan("liquibase.parser.core.xml");
+ }
+
+ if (sl.getPackages().remove("liquibase.serializer")) {
+ sl.addPackageToScan("liquibase.serializer.core.xml");
+ }
+
+ sl.getPackages().remove("liquibase.ext");
+ sl.getPackages().remove("liquibase.sdk");
+ }
+
+ LogFactory.setInstance(new LogWrapper());
+
+ // Adding PostgresPlus support to liquibase
+ DatabaseFactory.getInstance().register(new PostgresPlusDatabase());
+
+ // Change command for creating lock and drop DELETE lock record from it
+ SqlGeneratorFactory.getInstance().register(new CustomInsertLockRecordGenerator());
+ }
+
+
+ @Override
+ public void init(Config.Scope config) {
+
+ }
+
+ @Override
+ public void postInit(KeycloakSessionFactory factory) {
+
+ }
+
+ @Override
+ public void close() {
+ }
+
+ @Override
+ public String getId() {
+ return "default";
+ }
+
+ @Override
+ public Liquibase getLiquibase(Connection connection, String defaultSchema) throws LiquibaseException {
+ Database database = DatabaseFactory.getInstance().findCorrectDatabaseImplementation(new JdbcConnection(connection));
+ if (defaultSchema != null) {
+ database.setDefaultSchemaName(defaultSchema);
+ }
+
+ String changelog = (database instanceof DB2Database) ? LiquibaseJpaUpdaterProvider.DB2_CHANGELOG : LiquibaseJpaUpdaterProvider.CHANGELOG;
+ logger.debugf("Using changelog file: %s", changelog);
+
+ // We wrap liquibase update in CustomLockService provided by DBLockProvider. No need to lock inside liquibase itself.
+ // NOTE: This can't be done in baseLiquibaseInitialization() as liquibase always restarts lock service
+ LockServiceFactory.getInstance().register(new DummyLockService());
+
+ return new Liquibase(changelog, new ClassLoaderResourceAccessor(getClass().getClassLoader()), database);
+ }
+
+ private static class LogWrapper extends LogFactory {
+
+ private liquibase.logging.Logger logger = new liquibase.logging.Logger() {
+ @Override
+ public void setName(String name) {
+ }
+
+ @Override
+ public void setLogLevel(String level) {
+ }
+
+ @Override
+ public void setLogLevel(LogLevel level) {
+ }
+
+ @Override
+ public void setLogLevel(String logLevel, String logFile) {
+ }
+
+ @Override
+ public void severe(String message) {
+ DefaultLiquibaseConnectionProvider.logger.error(message);
+ }
+
+ @Override
+ public void severe(String message, Throwable e) {
+ DefaultLiquibaseConnectionProvider.logger.error(message, e);
+ }
+
+ @Override
+ public void warning(String message) {
+ // Ignore this warning as cascaded drops doesn't work anyway with all DBs, which we need to support
+ if ("Database does not support drop with cascade".equals(message)) {
+ DefaultLiquibaseConnectionProvider.logger.debug(message);
+ } else {
+ DefaultLiquibaseConnectionProvider.logger.warn(message);
+ }
+ }
+
+ @Override
+ public void warning(String message, Throwable e) {
+ DefaultLiquibaseConnectionProvider.logger.warn(message, e);
+ }
+
+ @Override
+ public void info(String message) {
+ DefaultLiquibaseConnectionProvider.logger.debug(message);
+ }
+
+ @Override
+ public void info(String message, Throwable e) {
+ DefaultLiquibaseConnectionProvider.logger.debug(message, e);
+ }
+
+ @Override
+ public void debug(String message) {
+ if (DefaultLiquibaseConnectionProvider.logger.isTraceEnabled()) {
+ DefaultLiquibaseConnectionProvider.logger.trace(message);
+ }
+ }
+
+ @Override
+ public LogLevel getLogLevel() {
+ if (DefaultLiquibaseConnectionProvider.logger.isTraceEnabled()) {
+ return LogLevel.DEBUG;
+ } else if (DefaultLiquibaseConnectionProvider.logger.isDebugEnabled()) {
+ return LogLevel.INFO;
+ } else {
+ return LogLevel.WARNING;
+ }
+ }
+
+ @Override
+ public void debug(String message, Throwable e) {
+ DefaultLiquibaseConnectionProvider.logger.trace(message, e);
+ }
+
+ @Override
+ public void setChangeLog(DatabaseChangeLog databaseChangeLog) {
+ }
+
+ @Override
+ public void setChangeSet(ChangeSet changeSet) {
+ }
+
+ @Override
+ public int getPriority() {
+ return 0;
+ }
+ };
+
+ @Override
+ public liquibase.logging.Logger getLog(String name) {
+ return logger;
+ }
+
+ @Override
+ public liquibase.logging.Logger getLog() {
+ return logger;
+ }
+
+ }
+}
diff --git a/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/conn/LiquibaseConnectionProvider.java b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/conn/LiquibaseConnectionProvider.java
new file mode 100644
index 0000000..5aa81cc
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/conn/LiquibaseConnectionProvider.java
@@ -0,0 +1,33 @@
+/*
+ * 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.connections.jpa.updater.liquibase.conn;
+
+import java.sql.Connection;
+
+import liquibase.Liquibase;
+import liquibase.exception.LiquibaseException;
+import org.keycloak.provider.Provider;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public interface LiquibaseConnectionProvider extends Provider {
+
+ Liquibase getLiquibase(Connection connection, String defaultSchema) throws LiquibaseException;
+
+}
diff --git a/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/LiquibaseJpaUpdaterProvider.java b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/LiquibaseJpaUpdaterProvider.java
index 80bb467..61075c7 100755
--- a/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/LiquibaseJpaUpdaterProvider.java
+++ b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/LiquibaseJpaUpdaterProvider.java
@@ -20,18 +20,10 @@ package org.keycloak.connections.jpa.updater.liquibase;
import liquibase.Contexts;
import liquibase.Liquibase;
import liquibase.changelog.ChangeSet;
-import liquibase.changelog.DatabaseChangeLog;
import liquibase.changelog.RanChangeSet;
-import liquibase.database.Database;
-import liquibase.database.DatabaseFactory;
-import liquibase.database.core.DB2Database;
-import liquibase.database.jvm.JdbcConnection;
-import liquibase.logging.LogFactory;
-import liquibase.logging.LogLevel;
-import liquibase.resource.ClassLoaderResourceAccessor;
-import liquibase.servicelocator.ServiceLocator;
import org.jboss.logging.Logger;
import org.keycloak.connections.jpa.updater.JpaUpdaterProvider;
+import org.keycloak.connections.jpa.updater.liquibase.conn.LiquibaseConnectionProvider;
import org.keycloak.models.KeycloakSession;
import java.sql.Connection;
@@ -46,8 +38,14 @@ public class LiquibaseJpaUpdaterProvider implements JpaUpdaterProvider {
private static final Logger logger = Logger.getLogger(LiquibaseJpaUpdaterProvider.class);
- private static final String CHANGELOG = "META-INF/jpa-changelog-master.xml";
- private static final String DB2_CHANGELOG = "META-INF/db2-jpa-changelog-master.xml";
+ public static final String CHANGELOG = "META-INF/jpa-changelog-master.xml";
+ public static final String DB2_CHANGELOG = "META-INF/db2-jpa-changelog-master.xml";
+
+ private final KeycloakSession session;
+
+ public LiquibaseJpaUpdaterProvider(KeycloakSession session) {
+ this.session = session;
+ }
@Override
public String getCurrentVersionSql(String defaultSchema) {
@@ -55,7 +53,7 @@ public class LiquibaseJpaUpdaterProvider implements JpaUpdaterProvider {
}
@Override
- public void update(KeycloakSession session, Connection connection, String defaultSchema) {
+ public void update(Connection connection, String defaultSchema) {
logger.debug("Starting database update");
// Need ThreadLocal as liquibase doesn't seem to have API to inject custom objects into tasks
@@ -108,145 +106,14 @@ public class LiquibaseJpaUpdaterProvider implements JpaUpdaterProvider {
}
private Liquibase getLiquibase(Connection connection, String defaultSchema) throws Exception {
- ServiceLocator sl = ServiceLocator.getInstance();
-
- if (!System.getProperties().containsKey("liquibase.scan.packages")) {
- if (sl.getPackages().remove("liquibase.core")) {
- sl.addPackageToScan("liquibase.core.xml");
- }
-
- if (sl.getPackages().remove("liquibase.parser")) {
- sl.addPackageToScan("liquibase.parser.core.xml");
- }
-
- if (sl.getPackages().remove("liquibase.serializer")) {
- sl.addPackageToScan("liquibase.serializer.core.xml");
- }
-
- sl.getPackages().remove("liquibase.ext");
- sl.getPackages().remove("liquibase.sdk");
- }
-
- LogFactory.setInstance(new LogWrapper());
-
- // Adding PostgresPlus support to liquibase
- DatabaseFactory.getInstance().register(new PostgresPlusDatabase());
-
- Database database = DatabaseFactory.getInstance().findCorrectDatabaseImplementation(new JdbcConnection(connection));
- if (defaultSchema != null) {
- database.setDefaultSchemaName(defaultSchema);
- }
-
- String changelog = (database instanceof DB2Database) ? DB2_CHANGELOG : CHANGELOG;
- logger.debugf("Using changelog file: %s", changelog);
- return new Liquibase(changelog, new ClassLoaderResourceAccessor(getClass().getClassLoader()), database);
+ LiquibaseConnectionProvider liquibaseProvider = session.getProvider(LiquibaseConnectionProvider.class);
+ return liquibaseProvider.getLiquibase(connection, defaultSchema);
}
@Override
public void close() {
}
- private static class LogWrapper extends LogFactory {
-
- private liquibase.logging.Logger logger = new liquibase.logging.Logger() {
- @Override
- public void setName(String name) {
- }
-
- @Override
- public void setLogLevel(String level) {
- }
-
- @Override
- public void setLogLevel(LogLevel level) {
- }
-
- @Override
- public void setLogLevel(String logLevel, String logFile) {
- }
-
- @Override
- public void severe(String message) {
- LiquibaseJpaUpdaterProvider.logger.error(message);
- }
-
- @Override
- public void severe(String message, Throwable e) {
- LiquibaseJpaUpdaterProvider.logger.error(message, e);
- }
-
- @Override
- public void warning(String message) {
- // Ignore this warning as cascaded drops doesn't work anyway with all DBs, which we need to support
- if ("Database does not support drop with cascade".equals(message)) {
- LiquibaseJpaUpdaterProvider.logger.debug(message);
- } else {
- LiquibaseJpaUpdaterProvider.logger.warn(message);
- }
- }
-
- @Override
- public void warning(String message, Throwable e) {
- LiquibaseJpaUpdaterProvider.logger.warn(message, e);
- }
-
- @Override
- public void info(String message) {
- LiquibaseJpaUpdaterProvider.logger.debug(message);
- }
-
- @Override
- public void info(String message, Throwable e) {
- LiquibaseJpaUpdaterProvider.logger.debug(message, e);
- }
-
- @Override
- public void debug(String message) {
- LiquibaseJpaUpdaterProvider.logger.trace(message);
- }
-
- @Override
- public LogLevel getLogLevel() {
- if (LiquibaseJpaUpdaterProvider.logger.isTraceEnabled()) {
- return LogLevel.DEBUG;
- } else if (LiquibaseJpaUpdaterProvider.logger.isDebugEnabled()) {
- return LogLevel.INFO;
- } else {
- return LogLevel.WARNING;
- }
- }
-
- @Override
- public void debug(String message, Throwable e) {
- LiquibaseJpaUpdaterProvider.logger.trace(message, e);
- }
-
- @Override
- public void setChangeLog(DatabaseChangeLog databaseChangeLog) {
- }
-
- @Override
- public void setChangeSet(ChangeSet changeSet) {
- }
-
- @Override
- public int getPriority() {
- return 0;
- }
- };
-
- @Override
- public liquibase.logging.Logger getLog(String name) {
- return logger;
- }
-
- @Override
- public liquibase.logging.Logger getLog() {
- return logger;
- }
-
- }
-
public static String getTable(String table, String defaultSchema) {
return defaultSchema != null ? defaultSchema + "." + table : table;
}
diff --git a/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/LiquibaseJpaUpdaterProviderFactory.java b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/LiquibaseJpaUpdaterProviderFactory.java
index 28982b9..26eac9a 100755
--- a/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/LiquibaseJpaUpdaterProviderFactory.java
+++ b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/LiquibaseJpaUpdaterProviderFactory.java
@@ -30,7 +30,7 @@ public class LiquibaseJpaUpdaterProviderFactory implements JpaUpdaterProviderFac
@Override
public JpaUpdaterProvider create(KeycloakSession session) {
- return new LiquibaseJpaUpdaterProvider();
+ return new LiquibaseJpaUpdaterProvider(session);
}
@Override
diff --git a/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/lock/CustomInsertLockRecordGenerator.java b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/lock/CustomInsertLockRecordGenerator.java
new file mode 100644
index 0000000..3b1e2f9
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/lock/CustomInsertLockRecordGenerator.java
@@ -0,0 +1,64 @@
+/*
+ * 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.connections.jpa.updater.liquibase.lock;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import liquibase.database.Database;
+import liquibase.exception.ValidationErrors;
+import liquibase.sql.Sql;
+import liquibase.sqlgenerator.SqlGeneratorChain;
+import liquibase.sqlgenerator.core.AbstractSqlGenerator;
+import liquibase.statement.core.DeleteStatement;
+import liquibase.statement.core.InitializeDatabaseChangeLogLockTableStatement;
+
+/**
+ * We need to remove DELETE SQL command, which liquibase adds by default when inserting record to table lock. This is causing buggy behaviour
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class CustomInsertLockRecordGenerator extends AbstractSqlGenerator<InitializeDatabaseChangeLogLockTableStatement> {
+
+ @Override
+ public int getPriority() {
+ return super.getPriority() + 1; // Ensure bigger priority than InitializeDatabaseChangeLogLockTableGenerator
+ }
+
+ @Override
+ public ValidationErrors validate(InitializeDatabaseChangeLogLockTableStatement initializeDatabaseChangeLogLockTableStatement, Database database, SqlGeneratorChain sqlGeneratorChain) {
+ return new ValidationErrors();
+ }
+
+ @Override
+ public Sql[] generateSql(InitializeDatabaseChangeLogLockTableStatement statement, Database database, SqlGeneratorChain sqlGeneratorChain) {
+ // Generated by InitializeDatabaseChangeLogLockTableGenerator
+ Sql[] sqls = sqlGeneratorChain.generateSql(statement, database);
+
+ // Removing delete statement
+ List<Sql> result = new ArrayList<>();
+ for (Sql sql : sqls) {
+ String sqlCommand = sql.toSql();
+ if (!sqlCommand.toUpperCase().contains("DELETE")) {
+ result.add(sql);
+ }
+ }
+
+ return result.toArray(new Sql[result.size()]);
+ }
+}
diff --git a/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/lock/CustomLockService.java b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/lock/CustomLockService.java
new file mode 100644
index 0000000..0c246ca
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/lock/CustomLockService.java
@@ -0,0 +1,175 @@
+/*
+ * 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.connections.jpa.updater.liquibase.lock;
+
+import java.lang.reflect.Field;
+import java.text.DateFormat;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Date;
+
+import liquibase.database.Database;
+import liquibase.database.core.DerbyDatabase;
+import liquibase.exception.DatabaseException;
+import liquibase.exception.LockException;
+import liquibase.executor.Executor;
+import liquibase.executor.ExecutorService;
+import liquibase.lockservice.DatabaseChangeLogLock;
+import liquibase.lockservice.StandardLockService;
+import liquibase.logging.LogFactory;
+import liquibase.sql.visitor.AbstractSqlVisitor;
+import liquibase.sql.visitor.SqlVisitor;
+import liquibase.statement.core.CreateDatabaseChangeLogLockTableStatement;
+import liquibase.statement.core.DropTableStatement;
+import liquibase.statement.core.InitializeDatabaseChangeLogLockTableStatement;
+import liquibase.statement.core.RawSqlStatement;
+import org.jboss.logging.Logger;
+import org.keycloak.common.util.Time;
+import org.keycloak.common.util.reflections.Reflections;
+
+/**
+ * Liquibase lock service, which has some bugfixes and assumes timeouts to be configured in milliseconds
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class CustomLockService extends StandardLockService {
+
+ private static final Logger log = Logger.getLogger(CustomLockService.class);
+
+ private long changeLogLocRecheckTimeMillis = -1;
+
+ @Override
+ public void setChangeLogLockRecheckTime(long changeLogLocRecheckTime) {
+ super.setChangeLogLockRecheckTime(changeLogLocRecheckTime);
+ this.changeLogLocRecheckTimeMillis = changeLogLocRecheckTime;
+ }
+
+ // Bug in StandardLockService.getChangeLogLockRecheckTime()
+ @Override
+ public Long getChangeLogLockRecheckTime() {
+ if (changeLogLocRecheckTimeMillis == -1) {
+ return super.getChangeLogLockRecheckTime();
+ } else {
+ return changeLogLocRecheckTimeMillis;
+ }
+ }
+
+ @Override
+ public void init() throws DatabaseException {
+ boolean createdTable = false;
+ Executor executor = ExecutorService.getInstance().getExecutor(database);
+
+ if (!hasDatabaseChangeLogLockTable()) {
+
+ try {
+ if (log.isTraceEnabled()) {
+ log.trace("Create Database Lock Table");
+ }
+ executor.execute(new CreateDatabaseChangeLogLockTableStatement());
+ database.commit();
+ } catch (DatabaseException de) {
+ log.warn("Failed to create lock table. Maybe other transaction created in the meantime. Retrying...");
+ if (log.isDebugEnabled()) {
+ log.debug(de.getMessage(), de); //Log details at debug level
+ }
+ database.rollback();
+ throw new LockRetryException(de);
+ }
+
+ log.debugf("Created database lock table with name: %s", database.escapeTableName(database.getLiquibaseCatalogName(), database.getLiquibaseSchemaName(), database.getDatabaseChangeLogLockTableName()));
+
+ try {
+ Field field = Reflections.findDeclaredField(StandardLockService.class, "hasDatabaseChangeLogLockTable");
+ Reflections.setAccessible(field);
+ field.set(CustomLockService.this, true);
+ } catch (IllegalAccessException iae) {
+ throw new RuntimeException(iae);
+ }
+
+ createdTable = true;
+ }
+
+
+ if (!isDatabaseChangeLogLockTableInitialized(createdTable)) {
+ try {
+ if (log.isTraceEnabled()) {
+ log.trace("Initialize Database Lock Table");
+ }
+ executor.execute(new InitializeDatabaseChangeLogLockTableStatement());
+ database.commit();
+
+ } catch (DatabaseException de) {
+ log.warn("Failed to insert first record to the lock table. Maybe other transaction inserted in the meantime. Retrying...");
+ if (log.isDebugEnabled()) {
+ log.debug(de.getMessage(), de); // Log details at debug level
+ }
+ database.rollback();
+ throw new LockRetryException(de);
+ }
+
+ log.debug("Initialized record in the database lock table");
+ }
+
+
+ // Keycloak doesn't support Derby, but keep it for sure...
+ if (executor.updatesDatabase() && database instanceof DerbyDatabase && ((DerbyDatabase) database).supportsBooleanDataType()) { //check if the changelog table is of an old smallint vs. boolean format
+ String lockTable = database.escapeTableName(database.getLiquibaseCatalogName(), database.getLiquibaseSchemaName(), database.getDatabaseChangeLogLockTableName());
+ Object obj = executor.queryForObject(new RawSqlStatement("select min(locked) as test from " + lockTable + " fetch first row only"), Object.class);
+ if (!(obj instanceof Boolean)) { //wrong type, need to recreate table
+ executor.execute(new DropTableStatement(database.getLiquibaseCatalogName(), database.getLiquibaseSchemaName(), database.getDatabaseChangeLogLockTableName(), false));
+ executor.execute(new CreateDatabaseChangeLogLockTableStatement());
+ executor.execute(new InitializeDatabaseChangeLogLockTableStatement());
+ }
+ }
+
+ }
+
+ @Override
+ public void waitForLock() throws LockException {
+ boolean locked = false;
+ long startTime = Time.toMillis(Time.currentTime());
+ long timeToGiveUp = startTime + (getChangeLogLockWaitTime());
+
+ while (!locked && Time.toMillis(Time.currentTime()) < timeToGiveUp) {
+ locked = acquireLock();
+ if (!locked) {
+ int remainingTime = ((int)(timeToGiveUp / 1000)) - Time.currentTime();
+ log.debugf("Waiting for changelog lock... Remaining time: %d seconds", remainingTime);
+ try {
+ Thread.sleep(getChangeLogLockRecheckTime());
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ if (!locked) {
+ DatabaseChangeLogLock[] locks = listLocks();
+ String lockedBy;
+ if (locks.length > 0) {
+ DatabaseChangeLogLock lock = locks[0];
+ lockedBy = lock.getLockedBy() + " since " + DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT).format(lock.getLockGranted());
+ } else {
+ lockedBy = "UNKNOWN";
+ }
+ throw new LockException("Could not acquire change log lock. Currently locked by " + lockedBy);
+ }
+ }
+
+
+}
diff --git a/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/lock/LiquibaseDBLockProvider.java b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/lock/LiquibaseDBLockProvider.java
new file mode 100644
index 0000000..8769053
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/lock/LiquibaseDBLockProvider.java
@@ -0,0 +1,159 @@
+/*
+ * 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.connections.jpa.updater.liquibase.lock;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+
+import liquibase.Liquibase;
+import liquibase.exception.DatabaseException;
+import liquibase.exception.LiquibaseException;
+import liquibase.exception.LockException;
+import liquibase.lockservice.LockService;
+import org.jboss.logging.Logger;
+import org.keycloak.connections.jpa.JpaConnectionProvider;
+import org.keycloak.connections.jpa.JpaConnectionProviderFactory;
+import org.keycloak.connections.jpa.updater.liquibase.conn.LiquibaseConnectionProvider;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.dblock.DBLockProvider;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class LiquibaseDBLockProvider implements DBLockProvider {
+
+ private static final Logger logger = Logger.getLogger(LiquibaseDBLockProvider.class);
+
+ // 3 should be sufficient (Potentially one failure for createTable and one for insert record)
+ private int DEFAULT_MAX_ATTEMPTS = 3;
+
+
+ private final LiquibaseDBLockProviderFactory factory;
+ private final KeycloakSession session;
+
+ private LockService lockService;
+ private Connection dbConnection;
+
+ private int maxAttempts = DEFAULT_MAX_ATTEMPTS;
+
+ public LiquibaseDBLockProvider(LiquibaseDBLockProviderFactory factory, KeycloakSession session) {
+ this.factory = factory;
+ this.session = session;
+ init();
+ }
+
+ private void init() {
+ LiquibaseConnectionProvider liquibaseProvider = session.getProvider(LiquibaseConnectionProvider.class);
+ JpaConnectionProviderFactory jpaProviderFactory = (JpaConnectionProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(JpaConnectionProvider.class);
+
+ this.dbConnection = jpaProviderFactory.getConnection();
+ String defaultSchema = jpaProviderFactory.getSchema();
+
+ try {
+ Liquibase liquibase = liquibaseProvider.getLiquibase(dbConnection, defaultSchema);
+
+ this.lockService = new CustomLockService();
+ lockService.setChangeLogLockWaitTime(factory.getLockWaitTimeoutMillis());
+ lockService.setChangeLogLockRecheckTime(factory.getLockRecheckTimeMillis());
+ lockService.setDatabase(liquibase.getDatabase());
+ } catch (LiquibaseException exception) {
+ safeRollbackConnection();
+ safeCloseConnection();
+ throw new IllegalStateException(exception);
+ }
+ }
+
+ // Assumed transaction was rolled-back and we want to start with new DB connection
+ private void restart() {
+ safeCloseConnection();
+ this.dbConnection = null;
+ this.lockService = null;
+ init();
+ }
+
+
+ @Override
+ public void waitForLock() {
+ while (maxAttempts > 0) {
+ try {
+ lockService.waitForLock();
+ this.maxAttempts = DEFAULT_MAX_ATTEMPTS;
+ return;
+ } catch (LockException le) {
+ if (le.getCause() != null && le.getCause() instanceof LockRetryException) {
+ // Indicates we should try to acquire lock again in different transaction
+ restart();
+ maxAttempts--;
+ } else {
+ throw new IllegalStateException("Failed to retrieve lock", le);
+
+ // TODO: Possibility to forcefully retrieve lock after timeout instead of just give-up?
+ }
+ }
+ }
+ }
+
+
+ @Override
+ public void releaseLock() {
+ try {
+ lockService.releaseLock();
+ } catch (LockException e) {
+ logger.error("Could not release lock", e);
+ }
+ lockService.reset();
+ }
+
+ @Override
+ public void destroyLockInfo() {
+ try {
+ this.lockService.destroy();
+ dbConnection.commit();
+ logger.debug("Destroyed lock table");
+ } catch (DatabaseException | SQLException de) {
+ logger.error("Failed to destroy lock table");
+ safeRollbackConnection();
+ }
+ }
+
+ @Override
+ public void close() {
+ safeCloseConnection();
+ }
+
+ private void safeRollbackConnection() {
+ if (dbConnection != null) {
+ try {
+ this.dbConnection.rollback();
+ } catch (SQLException se) {
+ logger.warn("Failed to rollback connection after error", se);
+ }
+ }
+ }
+
+ private void safeCloseConnection() {
+ // Close after creating EntityManagerFactory to prevent in-mem databases from closing
+ if (dbConnection != null) {
+ try {
+ dbConnection.close();
+ } catch (SQLException e) {
+ logger.warn("Failed to close connection", e);
+ }
+ }
+ }
+}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java
index 115d4dd..adde0e4 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java
@@ -47,7 +47,7 @@ import java.util.Set;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
-public class ClientAdapter implements ClientModel {
+public class ClientAdapter implements ClientModel, JpaModel<ClientEntity> {
protected KeycloakSession session;
protected RealmModel realm;
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/ClientTemplateAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/ClientTemplateAdapter.java
index 826ed84..e38ab73 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/ClientTemplateAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/ClientTemplateAdapter.java
@@ -24,6 +24,7 @@ import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleContainerModel;
import org.keycloak.models.RoleModel;
+import org.keycloak.models.jpa.entities.ClientEntity;
import org.keycloak.models.jpa.entities.ClientTemplateEntity;
import org.keycloak.models.jpa.entities.ProtocolMapperEntity;
import org.keycloak.models.jpa.entities.RoleEntity;
@@ -42,7 +43,7 @@ import java.util.Set;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
-public class ClientTemplateAdapter implements ClientTemplateModel {
+public class ClientTemplateAdapter implements ClientTemplateModel , JpaModel<ClientTemplateEntity> {
protected KeycloakSession session;
protected RealmModel realm;
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java
index fc10217..efbd154 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java
@@ -50,6 +50,7 @@ import java.util.Set;
@Table(name="CLIENT", uniqueConstraints = {@UniqueConstraint(columnNames = {"REALM_ID", "CLIENT_ID"})})
@NamedQueries({
@NamedQuery(name="getClientsByRealm", query="select client from ClientEntity client where client.realm = :realm"),
+ @NamedQuery(name="getClientById", query="select client from ClientEntity client where client.id = :id and client.realm.id = :realm"),
@NamedQuery(name="getClientIdsByRealm", query="select client.id from ClientEntity client where client.realm.id = :realm"),
@NamedQuery(name="findClientIdByClientId", query="select client.id from ClientEntity client where client.clientId = :clientId and client.realm.id = :realm"),
@NamedQuery(name="findClientByClientId", query="select client from ClientEntity client where client.clientId = :clientId and client.realm.id = :realm"),
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
index 052ce6e..5ad023a 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
@@ -117,11 +117,11 @@ public class RealmEntity {
@Column(name="NOT_BEFORE")
protected int notBefore;
- @Column(name="PUBLIC_KEY", length = 2048)
+ @Column(name="PUBLIC_KEY", length = 4000)
protected String publicKeyPem;
- @Column(name="PRIVATE_KEY", length = 2048)
+ @Column(name="PRIVATE_KEY", length = 4000)
protected String privateKeyPem;
- @Column(name="CERTIFICATE", length = 2048)
+ @Column(name="CERTIFICATE", length = 4000)
protected String certificatePem;
@Column(name="CODE_SECRET", length = 255)
protected String codeSecret;
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/GroupAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/GroupAdapter.java
index a2d0fec..6ea87c5 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/GroupAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/GroupAdapter.java
@@ -41,7 +41,7 @@ import java.util.Set;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
-public class GroupAdapter implements GroupModel {
+public class GroupAdapter implements GroupModel , JpaModel<GroupEntity> {
protected GroupEntity group;
protected EntityManager em;
@@ -53,7 +53,7 @@ public class GroupAdapter implements GroupModel {
this.realm = realm;
}
- public GroupEntity getGroup() {
+ public GroupEntity getEntity() {
return group;
}
@@ -88,7 +88,7 @@ public class GroupAdapter implements GroupModel {
public static GroupEntity toEntity(GroupModel model, EntityManager em) {
if (model instanceof GroupAdapter) {
- return ((GroupAdapter)model).getGroup();
+ return ((GroupAdapter)model).getEntity();
}
return em.getReference(GroupEntity.class, model.getId());
}
@@ -233,7 +233,7 @@ public class GroupAdapter implements GroupModel {
protected TypedQuery<GroupRoleMappingEntity> getGroupRoleMappingEntityTypedQuery(RoleModel role) {
TypedQuery<GroupRoleMappingEntity> query = em.createNamedQuery("groupHasRole", GroupRoleMappingEntity.class);
- query.setParameter("group", getGroup());
+ query.setParameter("group", getEntity());
query.setParameter("roleId", role.getId());
return query;
}
@@ -242,7 +242,7 @@ public class GroupAdapter implements GroupModel {
public void grantRole(RoleModel role) {
if (hasRole(role)) return;
GroupRoleMappingEntity entity = new GroupRoleMappingEntity();
- entity.setGroup(getGroup());
+ entity.setGroup(getEntity());
entity.setRoleId(role.getId());
em.persist(entity);
em.flush();
@@ -269,7 +269,7 @@ public class GroupAdapter implements GroupModel {
// we query ids only as the role might be cached and following the @ManyToOne will result in a load
// even if we're getting just the id.
TypedQuery<String> query = em.createNamedQuery("groupRoleMappingIds", String.class);
- query.setParameter("group", getGroup());
+ query.setParameter("group", getEntity());
List<String> ids = query.getResultList();
Set<RoleModel> roles = new HashSet<RoleModel>();
for (String roleId : ids) {
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaModel.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaModel.java
new file mode 100755
index 0000000..7cc915f
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaModel.java
@@ -0,0 +1,9 @@
+package org.keycloak.models.jpa;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface JpaModel<T> {
+ T getEntity();
+}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java
index 635521e..8ed10be 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java
@@ -39,9 +39,11 @@ import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
+import java.util.Map;
import java.util.Set;
/**
@@ -53,7 +55,6 @@ public class JpaRealmProvider implements RealmProvider {
private final KeycloakSession session;
protected EntityManager em;
-
public JpaRealmProvider(KeycloakSession session, EntityManager em) {
this.session = session;
this.em = em;
@@ -76,21 +77,22 @@ public class JpaRealmProvider implements RealmProvider {
realm.setId(id);
em.persist(realm);
em.flush();
- final RealmModel model = new RealmAdapter(session, em, realm);
+ final RealmModel adapter = new RealmAdapter(session, em, realm);
session.getKeycloakSessionFactory().publish(new RealmModel.RealmCreationEvent() {
@Override
public RealmModel getCreatedRealm() {
- return model;
+ return adapter;
}
});
- return model;
+ return adapter;
}
@Override
public RealmModel getRealm(String id) {
RealmEntity realm = em.find(RealmEntity.class, id);
if (realm == null) return null;
- return new RealmAdapter(session, em, realm);
+ RealmAdapter adapter = new RealmAdapter(session, em, realm);
+ return adapter;
}
@Override
@@ -124,6 +126,7 @@ public class JpaRealmProvider implements RealmProvider {
if (realm == null) {
return false;
}
+ em.refresh(realm);
RealmAdapter adapter = new RealmAdapter(session, em, realm);
session.users().preRemove(adapter);
int num = em.createNamedQuery("deleteGroupRoleMappingsByRealm")
@@ -174,7 +177,8 @@ public class JpaRealmProvider implements RealmProvider {
entity.setRealmId(realm.getId());
em.persist(entity);
em.flush();
- return new RoleAdapter(session, realm, em, entity);
+ RoleAdapter adapter = new RoleAdapter(session, realm, em, entity);
+ return adapter;
}
@@ -203,7 +207,8 @@ public class JpaRealmProvider implements RealmProvider {
roleEntity.setRealmId(realm.getId());
em.persist(roleEntity);
em.flush();
- return new RoleAdapter(session, realm, em, roleEntity);
+ RoleAdapter adapter = new RoleAdapter(session, realm, em, roleEntity);
+ return adapter;
}
@Override
@@ -247,11 +252,11 @@ public class JpaRealmProvider implements RealmProvider {
@Override
public boolean removeRole(RealmModel realm, RoleModel role) {
session.users().preRemove(realm, role);
- RoleEntity roleEntity = em.getReference(RoleEntity.class, role.getId());
RoleContainerModel container = role.getContainer();
if (container.getDefaultRoles().contains(role.getName())) {
container.removeDefaultRoles(role.getName());
}
+ RoleEntity roleEntity = em.getReference(RoleEntity.class, role.getId());
String compositeRoleTable = JpaUtils.getTableNameForNativeQuery("COMPOSITE_ROLE", em);
em.createNativeQuery("delete from " + compositeRoleTable + " where CHILD_ROLE = :role").setParameter("role", roleEntity).executeUpdate();
em.createNamedQuery("deleteScopeMappingByRole").setParameter("role", roleEntity).executeUpdate();
@@ -260,7 +265,6 @@ public class JpaRealmProvider implements RealmProvider {
em.remove(roleEntity);
em.flush();
-
return true;
}
@@ -270,7 +274,8 @@ public class JpaRealmProvider implements RealmProvider {
RoleEntity entity = em.find(RoleEntity.class, id);
if (entity == null) return null;
if (!realm.getId().equals(entity.getRealmId())) return null;
- return new RoleAdapter(session, realm, em, entity);
+ RoleAdapter adapter = new RoleAdapter(session, realm, em, entity);
+ return adapter;
}
@Override
@@ -278,7 +283,8 @@ public class JpaRealmProvider implements RealmProvider {
GroupEntity groupEntity = em.find(GroupEntity.class, id);
if (groupEntity == null) return null;
if (!groupEntity.getRealm().getId().equals(realm.getId())) return null;
- return new GroupAdapter(realm, em, groupEntity);
+ GroupAdapter adapter = new GroupAdapter(realm, em, groupEntity);
+ return adapter;
}
@Override
@@ -361,7 +367,8 @@ public class JpaRealmProvider implements RealmProvider {
groupEntity.setRealm(realmEntity);
em.persist(groupEntity);
- return new GroupAdapter(realm, em, groupEntity);
+ GroupAdapter adapter = new GroupAdapter(realm, em, groupEntity);
+ return adapter;
}
@Override
@@ -391,6 +398,7 @@ public class JpaRealmProvider implements RealmProvider {
em.persist(entity);
em.flush();
final ClientModel resource = new ClientAdapter(realm, em, session, entity);
+
em.flush();
session.getKeycloakSessionFactory().publish(new RealmModel.ClientCreationEvent() {
@Override
@@ -416,14 +424,14 @@ public class JpaRealmProvider implements RealmProvider {
}
-
@Override
public ClientModel getClientById(String id, RealmModel realm) {
ClientEntity app = em.find(ClientEntity.class, id);
-
// Check if application belongs to this realm
if (app == null || !realm.getId().equals(app.getRealm().getId())) return null;
- return new ClientAdapter(realm, em, session, app);
+ ClientAdapter client = new ClientAdapter(realm, em, session, app);
+ return client;
+
}
@Override
@@ -468,6 +476,7 @@ public class JpaRealmProvider implements RealmProvider {
// Check if application belongs to this realm
if (app == null || !realm.getId().equals(app.getRealm().getId())) return null;
- return new ClientTemplateAdapter(realm, em, session, app);
+ ClientTemplateAdapter adapter = new ClientTemplateAdapter(realm, em, session, app);
+ return adapter;
}
}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
index 3dd9de7..f517782 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
@@ -65,7 +65,7 @@ import java.util.Set;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
-public class RealmAdapter implements RealmModel {
+public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
protected static final Logger logger = Logger.getLogger(RealmAdapter.class);
protected RealmEntity realm;
protected EntityManager em;
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RoleAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RoleAdapter.java
index 0ac1be9..61cf70c 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/RoleAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RoleAdapter.java
@@ -21,6 +21,7 @@ import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleContainerModel;
import org.keycloak.models.RoleModel;
+import org.keycloak.models.jpa.entities.RealmEntity;
import org.keycloak.models.jpa.entities.RoleEntity;
import org.keycloak.models.utils.KeycloakModelUtils;
@@ -33,7 +34,7 @@ import java.util.Set;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
-public class RoleAdapter implements RoleModel {
+public class RoleAdapter implements RoleModel, JpaModel<RoleEntity> {
protected RoleEntity role;
protected EntityManager em;
protected RealmModel realm;
@@ -46,7 +47,7 @@ public class RoleAdapter implements RoleModel {
this.session = session;
}
- public RoleEntity getRole() {
+ public RoleEntity getEntity() {
return role;
}
@@ -97,17 +98,17 @@ public class RoleAdapter implements RoleModel {
@Override
public void addCompositeRole(RoleModel role) {
RoleEntity entity = RoleAdapter.toRoleEntity(role, em);
- for (RoleEntity composite : getRole().getCompositeRoles()) {
+ for (RoleEntity composite : getEntity().getCompositeRoles()) {
if (composite.equals(entity)) return;
}
- getRole().getCompositeRoles().add(entity);
+ getEntity().getCompositeRoles().add(entity);
em.flush();
}
@Override
public void removeCompositeRole(RoleModel role) {
RoleEntity entity = RoleAdapter.toRoleEntity(role, em);
- Iterator<RoleEntity> it = getRole().getCompositeRoles().iterator();
+ Iterator<RoleEntity> it = getEntity().getCompositeRoles().iterator();
while (it.hasNext()) {
if (it.next().equals(entity)) it.remove();
}
@@ -117,7 +118,7 @@ public class RoleAdapter implements RoleModel {
public Set<RoleModel> getComposites() {
Set<RoleModel> set = new HashSet<RoleModel>();
- for (RoleEntity composite : getRole().getCompositeRoles()) {
+ for (RoleEntity composite : getEntity().getCompositeRoles()) {
set.add(new RoleAdapter(session, realm, em, composite));
// todo I want to do this, but can't as you get stack overflow
@@ -161,7 +162,7 @@ public class RoleAdapter implements RoleModel {
public static RoleEntity toRoleEntity(RoleModel model, EntityManager em) {
if (model instanceof RoleAdapter) {
- return ((RoleAdapter)model).getRole();
+ return ((RoleAdapter)model).getEntity();
}
return em.getReference(RoleEntity.class, model.getId());
}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java
index 9114af1..ef24368 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java
@@ -63,7 +63,7 @@ import java.util.Set;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
-public class UserAdapter implements UserModel {
+public class UserAdapter implements UserModel, JpaModel<UserEntity> {
protected UserEntity user;
protected EntityManager em;
@@ -77,7 +77,7 @@ public class UserAdapter implements UserModel {
this.session = session;
}
- public UserEntity getUser() {
+ public UserEntity getEntity() {
return user;
}
@@ -503,7 +503,7 @@ public class UserAdapter implements UserModel {
// we query ids only as the group might be cached and following the @ManyToOne will result in a load
// even if we're getting just the id.
TypedQuery<String> query = em.createNamedQuery("userGroupIds", String.class);
- query.setParameter("user", getUser());
+ query.setParameter("user", getEntity());
List<String> ids = query.getResultList();
Set<GroupModel> groups = new HashSet<>();
for (String groupId : ids) {
@@ -518,7 +518,7 @@ public class UserAdapter implements UserModel {
public void joinGroup(GroupModel group) {
if (isMemberOf(group)) return;
UserGroupMembershipEntity entity = new UserGroupMembershipEntity();
- entity.setUser(getUser());
+ entity.setUser(getEntity());
entity.setGroupId(group.getId());
em.persist(entity);
em.flush();
@@ -548,7 +548,7 @@ public class UserAdapter implements UserModel {
protected TypedQuery<UserGroupMembershipEntity> getUserGroupMappingQuery(GroupModel group) {
TypedQuery<UserGroupMembershipEntity> query = em.createNamedQuery("userMemberOf", UserGroupMembershipEntity.class);
- query.setParameter("user", getUser());
+ query.setParameter("user", getEntity());
query.setParameter("groupId", group.getId());
return query;
}
@@ -562,7 +562,7 @@ public class UserAdapter implements UserModel {
protected TypedQuery<UserRoleMappingEntity> getUserRoleMappingEntityTypedQuery(RoleModel role) {
TypedQuery<UserRoleMappingEntity> query = em.createNamedQuery("userHasRole", UserRoleMappingEntity.class);
- query.setParameter("user", getUser());
+ query.setParameter("user", getEntity());
query.setParameter("roleId", role.getId());
return query;
}
@@ -571,7 +571,7 @@ public class UserAdapter implements UserModel {
public void grantRole(RoleModel role) {
if (hasRole(role)) return;
UserRoleMappingEntity entity = new UserRoleMappingEntity();
- entity.setUser(getUser());
+ entity.setUser(getEntity());
entity.setRoleId(role.getId());
em.persist(entity);
em.flush();
@@ -598,7 +598,7 @@ public class UserAdapter implements UserModel {
// we query ids only as the role might be cached and following the @ManyToOne will result in a load
// even if we're getting just the id.
TypedQuery<String> query = em.createNamedQuery("userRoleMappingIds", String.class);
- query.setParameter("user", getUser());
+ query.setParameter("user", getEntity());
List<String> ids = query.getResultList();
Set<RoleModel> roles = new HashSet<RoleModel>();
for (String roleId : ids) {
diff --git a/model/jpa/src/main/resources/META-INF/db2-jpa-changelog-1.9.1.xml b/model/jpa/src/main/resources/META-INF/db2-jpa-changelog-1.9.1.xml
new file mode 100644
index 0000000..d0b387d
--- /dev/null
+++ b/model/jpa/src/main/resources/META-INF/db2-jpa-changelog-1.9.1.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!--
+ ~ 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.
+ -->
+
+<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
+ <changeSet author="keycloak" id="1.9.1">
+ <!-- Can't increase publicKey on DB2 due the DB2 SQL Error: SQLCODE=-670, SQLSTATE=54010, SQLERRMC=16293;USERSPACE1, DRIVER=4.19.26 . Need to find better solution -->
+ <modifyDataType tableName="REALM" columnName="PRIVATE_KEY" newDataType="VARCHAR(4000)"/>
+ <!--<modifyDataType tableName="REALM" columnName="PUBLIC_KEY" newDataType="VARCHAR(4000)"/>-->
+ <modifyDataType tableName="REALM" columnName="CERTIFICATE" newDataType="VARCHAR(4000)"/>
+ </changeSet>
+</databaseChangeLog>
\ No newline at end of file
diff --git a/model/jpa/src/main/resources/META-INF/db2-jpa-changelog-master.xml b/model/jpa/src/main/resources/META-INF/db2-jpa-changelog-master.xml
index ae4592d..a69e443 100644
--- a/model/jpa/src/main/resources/META-INF/db2-jpa-changelog-master.xml
+++ b/model/jpa/src/main/resources/META-INF/db2-jpa-changelog-master.xml
@@ -30,4 +30,5 @@
<include file="META-INF/jpa-changelog-1.7.0.xml"/>
<include file="META-INF/db2-jpa-changelog-1.8.0.xml"/>
<include file="META-INF/jpa-changelog-1.9.0.xml"/>
+ <include file="META-INF/db2-jpa-changelog-1.9.1.xml"/>
</databaseChangeLog>
diff --git a/model/jpa/src/main/resources/META-INF/jpa-changelog-1.9.1.xml b/model/jpa/src/main/resources/META-INF/jpa-changelog-1.9.1.xml
new file mode 100755
index 0000000..f801ddc
--- /dev/null
+++ b/model/jpa/src/main/resources/META-INF/jpa-changelog-1.9.1.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!--
+ ~ 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.
+ -->
+
+<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
+ <changeSet author="keycloak" id="1.9.1">
+ <modifyDataType tableName="REALM" columnName="PRIVATE_KEY" newDataType="VARCHAR(4000)"/>
+ <modifyDataType tableName="REALM" columnName="PUBLIC_KEY" newDataType="VARCHAR(4000)"/>
+ <modifyDataType tableName="REALM" columnName="CERTIFICATE" newDataType="VARCHAR(4000)"/>
+ </changeSet>
+</databaseChangeLog>
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 6a4d1dc..c107b8e 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
@@ -30,4 +30,5 @@
<include file="META-INF/jpa-changelog-1.7.0.xml"/>
<include file="META-INF/jpa-changelog-1.8.0.xml"/>
<include file="META-INF/jpa-changelog-1.9.0.xml"/>
+ <include file="META-INF/jpa-changelog-1.9.1.xml"/>
</databaseChangeLog>
diff --git a/model/jpa/src/main/resources/META-INF/services/org.keycloak.models.dblock.DBLockProviderFactory b/model/jpa/src/main/resources/META-INF/services/org.keycloak.models.dblock.DBLockProviderFactory
new file mode 100644
index 0000000..2b2ac02
--- /dev/null
+++ b/model/jpa/src/main/resources/META-INF/services/org.keycloak.models.dblock.DBLockProviderFactory
@@ -0,0 +1,18 @@
+#
+# 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.
+#
+
+org.keycloak.connections.jpa.updater.liquibase.lock.LiquibaseDBLockProviderFactory
\ No newline at end of file
diff --git a/model/jpa/src/main/resources/META-INF/services/org.keycloak.provider.Spi b/model/jpa/src/main/resources/META-INF/services/org.keycloak.provider.Spi
index 16b42ba..94c6512 100644
--- a/model/jpa/src/main/resources/META-INF/services/org.keycloak.provider.Spi
+++ b/model/jpa/src/main/resources/META-INF/services/org.keycloak.provider.Spi
@@ -16,4 +16,5 @@
#
org.keycloak.connections.jpa.JpaConnectionSpi
-org.keycloak.connections.jpa.updater.JpaUpdaterSpi
\ No newline at end of file
+org.keycloak.connections.jpa.updater.JpaUpdaterSpi
+org.keycloak.connections.jpa.updater.liquibase.conn.LiquibaseConnectionSpi
\ No newline at end of file
model/mongo/pom.xml 2(+1 -1)
diff --git a/model/mongo/pom.xml b/model/mongo/pom.xml
index be0bd9b..db24473 100755
--- a/model/mongo/pom.xml
+++ b/model/mongo/pom.xml
@@ -22,7 +22,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
diff --git a/model/mongo/src/main/java/org/keycloak/connections/mongo/DefaultMongoConnectionFactoryProvider.java b/model/mongo/src/main/java/org/keycloak/connections/mongo/DefaultMongoConnectionFactoryProvider.java
index 744d14e..73f4128 100755
--- a/model/mongo/src/main/java/org/keycloak/connections/mongo/DefaultMongoConnectionFactoryProvider.java
+++ b/model/mongo/src/main/java/org/keycloak/connections/mongo/DefaultMongoConnectionFactoryProvider.java
@@ -78,7 +78,13 @@ public class DefaultMongoConnectionFactoryProvider implements MongoConnectionPro
private static final Logger logger = Logger.getLogger(DefaultMongoConnectionFactoryProvider.class);
- private volatile MongoClient client;
+ private static final int STATE_BEFORE_INIT = 0; // Even before MongoClient is created
+ private static final int STATE_BEFORE_UPDATE = 1; // Mongo client was created, but DB is not yet updated to last version
+ private static final int STATE_AFTER_UPDATE = 2; // Mongo client was created and DB updated. DB is fully initialized now
+
+ private volatile int state = STATE_BEFORE_INIT;
+
+ private MongoClient client;
private MongoStore mongoStore;
private DB db;
@@ -87,15 +93,6 @@ public class DefaultMongoConnectionFactoryProvider implements MongoConnectionPro
private Map<String,String> operationalInfo;
@Override
- public MongoConnectionProvider create(KeycloakSession session) {
- lazyInit(session);
-
- TransactionMongoStoreInvocationContext invocationContext = new TransactionMongoStoreInvocationContext(mongoStore);
- session.getTransaction().enlist(new MongoKeycloakTransaction(invocationContext));
- return new DefaultMongoConnectionProvider(db, mongoStore, invocationContext);
- }
-
- @Override
public void init(Config.Scope config) {
this.config = config;
}
@@ -105,33 +102,51 @@ public class DefaultMongoConnectionFactoryProvider implements MongoConnectionPro
}
+ @Override
+ public DB getDBBeforeUpdate() {
+ lazyInitBeforeUpdate();
+ return db;
+ }
- private void lazyInit(KeycloakSession session) {
- if (client == null) {
+ private void lazyInitBeforeUpdate() {
+ if (state == STATE_BEFORE_INIT) {
synchronized (this) {
- if (client == null) {
+ if (state == STATE_BEFORE_INIT) {
try {
this.client = createMongoClient();
-
String dbName = config.get("db", "keycloak");
this.db = client.getDB(dbName);
- String databaseSchema = config.get("databaseSchema");
- if (databaseSchema != null) {
- if (databaseSchema.equals("update")) {
- MongoUpdaterProvider mongoUpdater = session.getProvider(MongoUpdaterProvider.class);
+ state = STATE_BEFORE_UPDATE;
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+ }
+ }
- if (mongoUpdater == null) {
- throw new RuntimeException("Can't update database: Mongo updater provider not found");
- }
- mongoUpdater.update(session, db);
- } else {
- throw new RuntimeException("Invalid value for databaseSchema: " + databaseSchema);
- }
- }
+ @Override
+ public MongoConnectionProvider create(KeycloakSession session) {
+ lazyInit(session);
+ TransactionMongoStoreInvocationContext invocationContext = new TransactionMongoStoreInvocationContext(mongoStore);
+ session.getTransaction().enlist(new MongoKeycloakTransaction(invocationContext));
+ return new DefaultMongoConnectionProvider(db, mongoStore, invocationContext);
+ }
+
+ private void lazyInit(KeycloakSession session) {
+ lazyInitBeforeUpdate();
+
+ if (state == STATE_BEFORE_UPDATE) {
+ synchronized (this) {
+ if (state == STATE_BEFORE_UPDATE) {
+ try {
+ update(session);
this.mongoStore = new MongoStoreImpl(db, getManagedEntities());
+
+ state = STATE_AFTER_UPDATE;
} catch (Exception e) {
throw new RuntimeException(e);
}
@@ -140,6 +155,24 @@ public class DefaultMongoConnectionFactoryProvider implements MongoConnectionPro
}
}
+ private void update(KeycloakSession session) {
+ String databaseSchema = config.get("databaseSchema");
+ if (databaseSchema != null) {
+ if (databaseSchema.equals("update")) {
+ MongoUpdaterProvider mongoUpdater = session.getProvider(MongoUpdaterProvider.class);
+
+ if (mongoUpdater == null) {
+ throw new RuntimeException("Can't update database: Mongo updater provider not found");
+ }
+
+ mongoUpdater.update(session, db);
+ } else {
+ throw new RuntimeException("Invalid value for databaseSchema: " + databaseSchema);
+ }
+ }
+ }
+
+
private Class[] getManagedEntities() throws ClassNotFoundException {
Class[] entityClasses = new Class[entities.length];
for (int i = 0; i < entities.length; i++) {
@@ -160,6 +193,7 @@ public class DefaultMongoConnectionFactoryProvider implements MongoConnectionPro
return "default";
}
+
/**
* Override this method if you want more possibility to configure Mongo client. It can be also used to inject mongo client
* from different source.
@@ -205,7 +239,7 @@ public class DefaultMongoConnectionFactoryProvider implements MongoConnectionPro
MongoClient client;
if (user != null && password != null) {
- MongoCredential credential = MongoCredential.createMongoCRCredential(user, dbName, password.toCharArray());
+ MongoCredential credential = MongoCredential.createCredential(user, dbName, password.toCharArray());
client = new MongoClient(new ServerAddress(host, port), Collections.singletonList(credential), clientOptions);
} else {
client = new MongoClient(new ServerAddress(host, port), clientOptions);
diff --git a/model/mongo/src/main/java/org/keycloak/connections/mongo/lock/MongoDBLockProvider.java b/model/mongo/src/main/java/org/keycloak/connections/mongo/lock/MongoDBLockProvider.java
new file mode 100644
index 0000000..e3383fe
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/connections/mongo/lock/MongoDBLockProvider.java
@@ -0,0 +1,137 @@
+/*
+ * 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.connections.mongo.lock;
+
+import com.mongodb.BasicDBObject;
+import com.mongodb.DB;
+import com.mongodb.DBCursor;
+import com.mongodb.DBObject;
+import com.mongodb.DuplicateKeyException;
+import com.mongodb.WriteResult;
+import org.jboss.logging.Logger;
+import org.keycloak.common.util.HostUtils;
+import org.keycloak.common.util.Time;
+import org.keycloak.models.dblock.DBLockProvider;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class MongoDBLockProvider implements DBLockProvider {
+
+ private static final String DB_LOCK_COLLECTION = "dblock";
+ private static final Logger logger = Logger.getLogger(MongoDBLockProvider .class);
+
+ private final MongoDBLockProviderFactory factory;
+ private final DB db;
+
+ public MongoDBLockProvider(MongoDBLockProviderFactory factory, DB db) {
+ this.factory = factory;
+ this.db = db;
+ }
+
+
+ @Override
+ public void waitForLock() {
+ boolean locked = false;
+ long startTime = Time.toMillis(Time.currentTime());
+ long timeToGiveUp = startTime + (factory.getLockWaitTimeoutMillis());
+
+ while (!locked && Time.toMillis(Time.currentTime()) < timeToGiveUp) {
+ locked = acquireLock();
+ if (!locked) {
+ int remainingTime = ((int)(timeToGiveUp / 1000)) - Time.currentTime();
+ logger.debugf("Waiting for changelog lock... Remaining time: %d seconds", remainingTime);
+ try {
+ Thread.sleep(factory.getLockRecheckTimeMillis());
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ if (!locked) {
+ DBObject query = new BasicDBObject("_id", 1);
+ DBCursor cursor = db.getCollection(DB_LOCK_COLLECTION).find(query);
+ String lockedBy;
+ if (cursor.hasNext()) {
+ DBObject dbObj = cursor.next();
+ lockedBy = dbObj.get("lockedBy") + " since " + Time.toDate(((int)((long) dbObj.get("lockedSince") / 1000)));
+ } else {
+ lockedBy = "UNKNOWN";
+ }
+ throw new IllegalStateException("Could not acquire change log lock. Currently locked by " + lockedBy);
+ }
+ }
+
+
+ private boolean acquireLock() {
+ DBObject query = new BasicDBObject("locked", false);
+
+ BasicDBObject update = new BasicDBObject("locked", true);
+ update.append("_id", 1);
+ update.append("lockedSince", Time.toMillis(Time.currentTime()));
+ update.append("lockedBy", HostUtils.getHostName()); // Maybe replace with something better, but doesn't matter for now
+
+ try {
+ WriteResult wr = db.getCollection(DB_LOCK_COLLECTION).update(query, update, true, false);
+ if (wr.getN() == 1) {
+ logger.debugf("Successfully acquired DB lock");
+ return true;
+ } else {
+ return false;
+ }
+ } catch (DuplicateKeyException dke) {
+ logger.debugf("Failed acquire lock. Reason: %s", dke.getMessage());
+ }
+
+ return false;
+ }
+
+
+ @Override
+ public void releaseLock() {
+ DBObject query = new BasicDBObject("locked", true);
+
+ BasicDBObject update = new BasicDBObject("locked", false);
+ update.append("_id", 1);
+ update.append("lockedBy", null);
+ update.append("lockedSince", null);
+
+ try {
+ WriteResult wr = db.getCollection(DB_LOCK_COLLECTION).update(query, update, true, false);
+ if (wr.getN() > 0) {
+ logger.debugf("Successfully released DB lock");
+ } else {
+ logger.warnf("Attempt to release DB lock, but nothing was released");
+ }
+ } catch (DuplicateKeyException dke) {
+ logger.debugf("Failed release lock. Reason: %s", dke.getMessage());
+ }
+ }
+
+ @Override
+ public void destroyLockInfo() {
+ db.getCollection(DB_LOCK_COLLECTION).remove(new BasicDBObject());
+ logger.debugf("Destroyed lock collection");
+ }
+
+ @Override
+ public void close() {
+
+ }
+}
diff --git a/model/mongo/src/main/java/org/keycloak/connections/mongo/lock/MongoDBLockProviderFactory.java b/model/mongo/src/main/java/org/keycloak/connections/mongo/lock/MongoDBLockProviderFactory.java
new file mode 100644
index 0000000..7bd6e02
--- /dev/null
+++ b/model/mongo/src/main/java/org/keycloak/connections/mongo/lock/MongoDBLockProviderFactory.java
@@ -0,0 +1,84 @@
+/*
+ * 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.connections.mongo.lock;
+
+import com.mongodb.DB;
+import org.jboss.logging.Logger;
+import org.keycloak.Config;
+import org.keycloak.common.util.Time;
+import org.keycloak.connections.mongo.MongoConnectionProvider;
+import org.keycloak.connections.mongo.MongoConnectionProviderFactory;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.dblock.DBLockProviderFactory;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class MongoDBLockProviderFactory implements DBLockProviderFactory {
+
+ private static final Logger logger = Logger.getLogger(MongoDBLockProviderFactory.class);
+
+ private long lockRecheckTimeMillis;
+ private long lockWaitTimeoutMillis;
+
+ protected long getLockRecheckTimeMillis() {
+ return lockRecheckTimeMillis;
+ }
+
+ protected long getLockWaitTimeoutMillis() {
+ return lockWaitTimeoutMillis;
+ }
+
+ @Override
+ public void init(Config.Scope config) {
+ int lockRecheckTime = config.getInt("lockRecheckTime", 2);
+ int lockWaitTimeout = config.getInt("lockWaitTimeout", 900);
+ this.lockRecheckTimeMillis = Time.toMillis(lockRecheckTime);
+ this.lockWaitTimeoutMillis = Time.toMillis(lockWaitTimeout);
+ logger.debugf("Mongo lock provider configured with lockWaitTime: %d seconds, lockRecheckTime: %d seconds", lockWaitTimeout, lockRecheckTime);
+ }
+
+ @Override
+ public void postInit(KeycloakSessionFactory factory) {
+
+ }
+
+ @Override
+ public MongoDBLockProvider create(KeycloakSession session) {
+ MongoConnectionProviderFactory mongoConnectionFactory = (MongoConnectionProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(MongoConnectionProvider.class);
+ DB db = mongoConnectionFactory.getDBBeforeUpdate();
+ return new MongoDBLockProvider(this, db);
+ }
+
+ @Override
+ public void setTimeouts(long lockRecheckTimeMillis, long lockWaitTimeoutMillis) {
+ this.lockRecheckTimeMillis = lockRecheckTimeMillis;
+ this.lockWaitTimeoutMillis = lockWaitTimeoutMillis;
+ }
+
+ @Override
+ public void close() {
+
+ }
+
+ @Override
+ public String getId() {
+ return "mongo";
+ }
+}
diff --git a/model/mongo/src/main/java/org/keycloak/connections/mongo/MongoConnectionProvider.java b/model/mongo/src/main/java/org/keycloak/connections/mongo/MongoConnectionProvider.java
index 0e9f84a..f13eaf7 100644
--- a/model/mongo/src/main/java/org/keycloak/connections/mongo/MongoConnectionProvider.java
+++ b/model/mongo/src/main/java/org/keycloak/connections/mongo/MongoConnectionProvider.java
@@ -27,6 +27,9 @@ import org.keycloak.provider.Provider;
*/
public interface MongoConnectionProvider extends Provider {
+ /**
+ * @return Fully updated and initialized DB
+ */
DB getDB();
MongoStore getMongoStore();
diff --git a/model/mongo/src/main/java/org/keycloak/connections/mongo/MongoConnectionProviderFactory.java b/model/mongo/src/main/java/org/keycloak/connections/mongo/MongoConnectionProviderFactory.java
index 2d5d4ca..42a1853 100644
--- a/model/mongo/src/main/java/org/keycloak/connections/mongo/MongoConnectionProviderFactory.java
+++ b/model/mongo/src/main/java/org/keycloak/connections/mongo/MongoConnectionProviderFactory.java
@@ -17,10 +17,17 @@
package org.keycloak.connections.mongo;
+import com.mongodb.DB;
import org.keycloak.provider.ProviderFactory;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public interface MongoConnectionProviderFactory extends ProviderFactory<MongoConnectionProvider> {
+
+ /**
+ * @return DB object, which may not be yet updated to current Keycloak version. Useful just if something needs to be done even before DB update (for example acquire DB lock)
+ */
+ DB getDBBeforeUpdate();
+
}
diff --git a/model/mongo/src/main/resources/META-INF/services/org.keycloak.models.dblock.DBLockProviderFactory b/model/mongo/src/main/resources/META-INF/services/org.keycloak.models.dblock.DBLockProviderFactory
new file mode 100644
index 0000000..4c6c4aa
--- /dev/null
+++ b/model/mongo/src/main/resources/META-INF/services/org.keycloak.models.dblock.DBLockProviderFactory
@@ -0,0 +1,35 @@
+#
+# 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.
+#
+
+#
+# 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.
+#
+
+org.keycloak.connections.mongo.lock.MongoDBLockProviderFactory
\ No newline at end of file
model/pom.xml 2(+1 -1)
diff --git a/model/pom.xml b/model/pom.xml
index 3aeb364..f33f76f 100755
--- a/model/pom.xml
+++ b/model/pom.xml
@@ -20,7 +20,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<name>Keycloak Model Parent</name>
pom.xml 2(+1 -1)
diff --git a/pom.xml b/pom.xml
index a736a45..f10c0b4 100644
--- a/pom.xml
+++ b/pom.xml
@@ -31,7 +31,7 @@
</description>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-parent</artifactId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<packaging>pom</packaging>
<properties>
proxy/launcher/pom.xml 2(+1 -1)
diff --git a/proxy/launcher/pom.xml b/proxy/launcher/pom.xml
index e1876eb..d584353 100755
--- a/proxy/launcher/pom.xml
+++ b/proxy/launcher/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
proxy/pom.xml 2(+1 -1)
diff --git a/proxy/pom.xml b/proxy/pom.xml
index cee6f92..e27a3bf 100755
--- a/proxy/pom.xml
+++ b/proxy/pom.xml
@@ -20,7 +20,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<name>Model Parent</name>
proxy/proxy-server/pom.xml 2(+1 -1)
diff --git a/proxy/proxy-server/pom.xml b/proxy/proxy-server/pom.xml
index 4d3227f..41967de 100755
--- a/proxy/proxy-server/pom.xml
+++ b/proxy/proxy-server/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
saml-core/pom.xml 2(+1 -1)
diff --git a/saml-core/pom.xml b/saml-core/pom.xml
index 365c7aa..4323a13 100755
--- a/saml-core/pom.xml
+++ b/saml-core/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
diff --git a/saml-core/src/main/java/org/keycloak/saml/common/parsers/AbstractParser.java b/saml-core/src/main/java/org/keycloak/saml/common/parsers/AbstractParser.java
index 612ead9..5cb19de 100755
--- a/saml-core/src/main/java/org/keycloak/saml/common/parsers/AbstractParser.java
+++ b/saml-core/src/main/java/org/keycloak/saml/common/parsers/AbstractParser.java
@@ -16,6 +16,7 @@
*/
package org.keycloak.saml.common.parsers;
+import org.keycloak.common.util.Environment;
import org.keycloak.saml.common.PicketLinkLogger;
import org.keycloak.saml.common.PicketLinkLoggerFactory;
import org.keycloak.saml.common.constants.GeneralConstants;
@@ -29,6 +30,8 @@ import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.Characters;
import javax.xml.stream.events.XMLEvent;
+import javax.xml.stream.util.EventReaderDelegate;
+
import java.io.InputStream;
import java.security.AccessController;
import java.security.PrivilegedAction;
@@ -83,28 +86,10 @@ public abstract class AbstractParser implements ParserNamespaceSupport {
if (configStream == null)
throw logger.nullArgumentError("InputStream");
- XMLInputFactory xmlInputFactory = getXMLInputFactory();
-
XMLEventReader xmlEventReader = StaxParserUtil.getXMLEventReader(configStream);
try {
- xmlEventReader = xmlInputFactory.createFilteredReader(xmlEventReader, new EventFilter() {
- public boolean accept(XMLEvent xmlEvent) {
- // We are going to disregard characters that are new line and whitespace
- if (xmlEvent.isCharacters()) {
- Characters chars = xmlEvent.asCharacters();
- String data = chars.getData();
- data = valid(data) ? data.trim() : null;
- return valid(data);
- } else {
- return xmlEvent.isStartElement() || xmlEvent.isEndElement();
- }
- }
-
- private boolean valid(String str) {
- return str != null && str.length() > 0;
- }
- });
+ xmlEventReader = filterWhitespaces(xmlEventReader);
} catch (XMLStreamException e) {
throw logger.parserException(e);
}
@@ -137,4 +122,48 @@ public abstract class AbstractParser implements ParserNamespaceSupport {
}
}
+ protected XMLEventReader filterWhitespaces(XMLEventReader xmlEventReader) throws XMLStreamException {
+ XMLInputFactory xmlInputFactory = getXMLInputFactory();
+
+ xmlEventReader = xmlInputFactory.createFilteredReader(xmlEventReader, new EventFilter() {
+ public boolean accept(XMLEvent xmlEvent) {
+ // We are going to disregard characters that are new line and whitespace
+ if (xmlEvent.isCharacters()) {
+ Characters chars = xmlEvent.asCharacters();
+ String data = chars.getData();
+ data = valid(data) ? data.trim() : null;
+ return valid(data);
+ } else {
+ return xmlEvent.isStartElement() || xmlEvent.isEndElement();
+ }
+ }
+
+ private boolean valid(String str) {
+ return str != null && str.length() > 0;
+ }
+
+ });
+
+ // Handle IBM JDK bug with Stax parsing when EventReader presented
+ if (Environment.IS_IBM_JAVA) {
+ final XMLEventReader origReader = xmlEventReader;
+
+ xmlEventReader = new EventReaderDelegate(origReader) {
+
+ @Override
+ public boolean hasNext() {
+ boolean hasNext = super.hasNext();
+ try {
+ return hasNext && (origReader.peek() != null);
+ } catch (XMLStreamException xse) {
+ throw new IllegalStateException(xse);
+ }
+ }
+
+ };
+ }
+
+ return xmlEventReader;
+ }
+
}
\ No newline at end of file
diff --git a/saml-core/src/main/java/org/keycloak/saml/common/util/TransformerUtil.java b/saml-core/src/main/java/org/keycloak/saml/common/util/TransformerUtil.java
index c8026fd..4e43c3d 100755
--- a/saml-core/src/main/java/org/keycloak/saml/common/util/TransformerUtil.java
+++ b/saml-core/src/main/java/org/keycloak/saml/common/util/TransformerUtil.java
@@ -27,6 +27,7 @@ import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
+import javax.xml.XMLConstants;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.util.JAXBSource;
@@ -108,6 +109,21 @@ public class TransformerUtil {
SecurityActions.setTCCL(TransformerUtil.class.getClassLoader());
}
transformerFactory = TransformerFactory.newInstance();
+ try {
+ transformerFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
+ } catch (TransformerConfigurationException ignored) {
+ // some platforms don't support this. For example our testsuite pulls Selenium which requires Xalan 2.7.1
+ logger.warn("XML External Entity switches are not supported. You may get XML injection vulnerabilities.");
+ }
+ try {
+ transformerFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
+
+ transformerFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "");
+ } catch (Exception ignored) {
+ // some platforms don't support this. For example our testsuite pulls Selenium which requires Xalan 2.7.1
+ logger.warn("XML External Entity switches are not supported. You may get XML injection vulnerabilities.");
+ }
+
} finally {
if (tccl_jaxp) {
SecurityActions.setTCCL(prevTCCL);
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/AbstractDescriptorParser.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/AbstractDescriptorParser.java
index 6b9db93..a58006c 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/AbstractDescriptorParser.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/metadata/AbstractDescriptorParser.java
@@ -35,28 +35,8 @@ import javax.xml.stream.events.XMLEvent;
public abstract class AbstractDescriptorParser extends AbstractParser {
protected XMLEventReader filterWhiteSpaceCharacters(XMLEventReader xmlEventReader) throws ParsingException {
-
- XMLInputFactory xmlInputFactory = getXMLInputFactory();
-
try {
- xmlEventReader = xmlInputFactory.createFilteredReader(xmlEventReader, new EventFilter() {
- public boolean accept(XMLEvent xmlEvent) {
- // We are going to disregard characters that are new line and whitespace
- if (xmlEvent.isCharacters()) {
- Characters chars = xmlEvent.asCharacters();
- String data = chars.getData();
- data = valid(data) ? data.trim() : null;
- return valid(data);
- } else {
- return xmlEvent.isStartElement() || xmlEvent.isEndElement();
- }
- }
-
- private boolean valid(String str) {
- return str != null && str.length() > 0;
- }
- });
- return xmlEventReader;
+ return filterWhitespaces(xmlEventReader);
} catch (XMLStreamException e) {
throw new ParsingException(e);
}
server-spi/pom.xml 2(+1 -1)
diff --git a/server-spi/pom.xml b/server-spi/pom.xml
index 69f1c3c..047a065 100755
--- a/server-spi/pom.xml
+++ b/server-spi/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
diff --git a/server-spi/src/main/java/org/keycloak/hash/Pbkdf2PasswordHashProvider.java b/server-spi/src/main/java/org/keycloak/hash/Pbkdf2PasswordHashProvider.java
index 7c1a3db..3a25c5c 100644
--- a/server-spi/src/main/java/org/keycloak/hash/Pbkdf2PasswordHashProvider.java
+++ b/server-spi/src/main/java/org/keycloak/hash/Pbkdf2PasswordHashProvider.java
@@ -86,7 +86,7 @@ public class Pbkdf2PasswordHashProvider implements PasswordHashProviderFactory,
byte[] key = getSecretKeyFactory().generateSecret(spec).getEncoded();
return Base64.encodeBytes(key);
} catch (InvalidKeySpecException e) {
- throw new RuntimeException("Credential could not be encoded");
+ throw new RuntimeException("Credential could not be encoded", e);
}
}
@@ -101,7 +101,7 @@ public class Pbkdf2PasswordHashProvider implements PasswordHashProviderFactory,
try {
return SecretKeyFactory.getInstance(PBKDF2_ALGORITHM);
} catch (NoSuchAlgorithmException e) {
- throw new RuntimeException("PBKDF2 algorithm not found");
+ throw new RuntimeException("PBKDF2 algorithm not found", e);
}
}
diff --git a/server-spi/src/main/java/org/keycloak/mappers/FederationConfigValidationException.java b/server-spi/src/main/java/org/keycloak/mappers/FederationConfigValidationException.java
new file mode 100644
index 0000000..cf58b0b
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/mappers/FederationConfigValidationException.java
@@ -0,0 +1,47 @@
+/*
+ * 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.mappers;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class FederationConfigValidationException extends Exception {
+
+ private Object[] parameters;
+
+ public FederationConfigValidationException(String message) {
+ super(message);
+ }
+
+ public FederationConfigValidationException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public FederationConfigValidationException(String message, Object ... parameters) {
+ super(message);
+ this.parameters = parameters;
+ }
+
+ public Object[] getParameters() {
+ return parameters;
+ }
+
+ public void setParameters(Object[] parameters) {
+ this.parameters = parameters;
+ }
+}
diff --git a/server-spi/src/main/java/org/keycloak/mappers/UserFederationMapperFactory.java b/server-spi/src/main/java/org/keycloak/mappers/UserFederationMapperFactory.java
index 80eec6a..661462c 100644
--- a/server-spi/src/main/java/org/keycloak/mappers/UserFederationMapperFactory.java
+++ b/server-spi/src/main/java/org/keycloak/mappers/UserFederationMapperFactory.java
@@ -52,10 +52,12 @@ public interface UserFederationMapperFactory extends ProviderFactory<UserFederat
/**
* Called when instance of mapperModel is created for this factory through admin endpoint
*
+ * @param realm
+ * @param fedProviderModel
* @param mapperModel
- * @throws MapperConfigValidationException if configuration provided in mapperModel is not valid
+ * @throws FederationConfigValidationException if configuration provided in mapperModel is not valid
*/
- void validateConfig(RealmModel realm, UserFederationMapperModel mapperModel) throws MapperConfigValidationException;
+ void validateConfig(RealmModel realm, UserFederationProviderModel fedProviderModel, UserFederationMapperModel mapperModel) throws FederationConfigValidationException;
/**
* Used to detect what are default values for ProviderConfigProperties specified during mapper creation
diff --git a/server-spi/src/main/java/org/keycloak/migration/MigrationModel.java b/server-spi/src/main/java/org/keycloak/migration/MigrationModel.java
index ec8137e..6196a29 100755
--- a/server-spi/src/main/java/org/keycloak/migration/MigrationModel.java
+++ b/server-spi/src/main/java/org/keycloak/migration/MigrationModel.java
@@ -26,7 +26,7 @@ public interface MigrationModel {
/**
* Must have the form of major.minor.micro as the version is parsed and numbers are compared
*/
- String LATEST_VERSION = "1.9.0";
+ String LATEST_VERSION = "1.9.2";
String getStoredVersion();
void setStoredVersion(String version);
diff --git a/server-spi/src/main/java/org/keycloak/migration/MigrationModelManager.java b/server-spi/src/main/java/org/keycloak/migration/MigrationModelManager.java
index 9b1442c..7a29804 100755
--- a/server-spi/src/main/java/org/keycloak/migration/MigrationModelManager.java
+++ b/server-spi/src/main/java/org/keycloak/migration/MigrationModelManager.java
@@ -25,6 +25,7 @@ import org.keycloak.migration.migrators.MigrateTo1_6_0;
import org.keycloak.migration.migrators.MigrateTo1_7_0;
import org.keycloak.migration.migrators.MigrateTo1_8_0;
import org.keycloak.migration.migrators.MigrateTo1_9_0;
+import org.keycloak.migration.migrators.MigrateTo1_9_2;
import org.keycloak.migration.migrators.MigrationTo1_2_0_CR1;
import org.keycloak.models.KeycloakSession;
@@ -92,6 +93,12 @@ public class MigrationModelManager {
}
new MigrateTo1_9_0().migrate(session);
}
+ if (stored == null || stored.lessThan(MigrateTo1_9_2.VERSION)) {
+ if (stored != null) {
+ logger.debug("Migrating older model to 1.9.2 updates");
+ }
+ new MigrateTo1_9_2().migrate(session);
+ }
model.setStoredVersion(MigrationModel.LATEST_VERSION);
}
diff --git a/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_9_2.java b/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_9_2.java
new file mode 100644
index 0000000..7c1f097
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_9_2.java
@@ -0,0 +1,37 @@
+/*
+ * 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.migration.migrators;
+
+import org.keycloak.migration.ModelVersion;
+import org.keycloak.models.BrowserSecurityHeaders;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+
+public class MigrateTo1_9_2 {
+
+ public static final ModelVersion VERSION = new ModelVersion("1.9.2");
+
+ public void migrate(KeycloakSession session) {
+ for (RealmModel realm : session.realms().getRealms()) {
+ if (realm.getBrowserSecurityHeaders() != null) {
+ realm.getBrowserSecurityHeaders().put("xFrameOptions", "nosniff");
+ }
+ }
+ }
+
+}
diff --git a/server-spi/src/main/java/org/keycloak/models/BrowserSecurityHeaders.java b/server-spi/src/main/java/org/keycloak/models/BrowserSecurityHeaders.java
index 4abc494..1056027 100755
--- a/server-spi/src/main/java/org/keycloak/models/BrowserSecurityHeaders.java
+++ b/server-spi/src/main/java/org/keycloak/models/BrowserSecurityHeaders.java
@@ -30,13 +30,15 @@ public class BrowserSecurityHeaders {
public static final Map<String, String> defaultHeaders;
static {
- Map<String, String> headerMap = new HashMap<String, String>();
+ Map<String, String> headerMap = new HashMap<>();
headerMap.put("xFrameOptions", "X-Frame-Options");
headerMap.put("contentSecurityPolicy", "Content-Security-Policy");
+ headerMap.put("xContentTypeOptions", "X-Content-Type-Options");
- Map<String, String> dh = new HashMap<String, String>();
+ Map<String, String> dh = new HashMap<>();
dh.put("xFrameOptions", "SAMEORIGIN");
dh.put("contentSecurityPolicy", "frame-src 'self'");
+ dh.put("xContentTypeOptions", "nosniff");
defaultHeaders = Collections.unmodifiableMap(dh);
headerAttributeMap = Collections.unmodifiableMap(headerMap);
diff --git a/server-spi/src/main/java/org/keycloak/models/cache/CacheUserProvider.java b/server-spi/src/main/java/org/keycloak/models/cache/CacheUserProvider.java
index d9c9e56..63c9706 100755
--- a/server-spi/src/main/java/org/keycloak/models/cache/CacheUserProvider.java
+++ b/server-spi/src/main/java/org/keycloak/models/cache/CacheUserProvider.java
@@ -27,5 +27,4 @@ import org.keycloak.models.UserProvider;
public interface CacheUserProvider extends UserProvider {
void clear();
UserProvider getDelegate();
- void registerUserInvalidation(RealmModel realm, String id);
}
diff --git a/server-spi/src/main/java/org/keycloak/models/dblock/DBLockProvider.java b/server-spi/src/main/java/org/keycloak/models/dblock/DBLockProvider.java
new file mode 100644
index 0000000..b5fb417
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/models/dblock/DBLockProvider.java
@@ -0,0 +1,44 @@
+/*
+ * 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.models.dblock;
+
+import org.keycloak.provider.Provider;
+
+/**
+ * Global database lock to ensure that some actions in DB can be done just be one cluster node at a time.
+ *
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public interface DBLockProvider extends Provider {
+
+
+ /**
+ * Try to retrieve DB lock or wait if retrieve was unsuccessful. Throw exception if lock can't be retrieved within specified timeout (900 seconds by default)
+ */
+ void waitForLock();
+
+
+ void releaseLock();
+
+
+ /**
+ * Will destroy whole state of DB lock (drop table/collection to track locking).
+ * */
+ void destroyLockInfo();
+}
diff --git a/server-spi/src/main/java/org/keycloak/models/dblock/DBLockSpi.java b/server-spi/src/main/java/org/keycloak/models/dblock/DBLockSpi.java
new file mode 100644
index 0000000..cd78f0f
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/models/dblock/DBLockSpi.java
@@ -0,0 +1,48 @@
+/*
+ * 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.models.dblock;
+
+import org.keycloak.provider.Provider;
+import org.keycloak.provider.ProviderFactory;
+import org.keycloak.provider.Spi;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class DBLockSpi implements Spi {
+
+ @Override
+ public boolean isInternal() {
+ return true;
+ }
+
+ @Override
+ public String getName() {
+ return "dblock";
+ }
+
+ @Override
+ public Class<? extends Provider> getProviderClass() {
+ return DBLockProvider.class;
+ }
+
+ @Override
+ public Class<? extends ProviderFactory> getProviderFactoryClass() {
+ return DBLockProviderFactory.class;
+ }
+}
diff --git a/server-spi/src/main/java/org/keycloak/models/UserFederationManager.java b/server-spi/src/main/java/org/keycloak/models/UserFederationManager.java
index 45077e2..ef8d182 100755
--- a/server-spi/src/main/java/org/keycloak/models/UserFederationManager.java
+++ b/server-spi/src/main/java/org/keycloak/models/UserFederationManager.java
@@ -55,9 +55,7 @@ public class UserFederationManager implements UserProvider {
}
protected UserFederationProvider getFederationProvider(UserFederationProviderModel model) {
- UserFederationProviderFactory factory = (UserFederationProviderFactory)session.getKeycloakSessionFactory().getProviderFactory(UserFederationProvider.class, model.getProviderName());
- return factory.getInstance(session, model);
-
+ return KeycloakModelUtils.getFederationProviderInstance(session, model);
}
@Override
@@ -98,7 +96,7 @@ public class UserFederationManager implements UserProvider {
boolean localRemoved = session.userStorage().removeUser(realm, user);
managedUsers.remove(user.getId());
if (!localRemoved) {
- logger.warn("User removed from federation provider, but failed to remove him from keycloak model");
+ logger.warn("User possibly removed from federation provider, but failed to remove him from keycloak model");
}
return localRemoved;
} else {
diff --git a/server-spi/src/main/java/org/keycloak/models/UserFederationSyncResult.java b/server-spi/src/main/java/org/keycloak/models/UserFederationSyncResult.java
index a9a32f9..380c654 100644
--- a/server-spi/src/main/java/org/keycloak/models/UserFederationSyncResult.java
+++ b/server-spi/src/main/java/org/keycloak/models/UserFederationSyncResult.java
@@ -96,7 +96,10 @@ public class UserFederationSyncResult {
if (ignored) {
return "Synchronization ignored as it's already in progress";
} else {
- String status = String.format("%d imported users, %d updated users, %d removed users", added, updated, removed);
+ String status = String.format("%d imported users, %d updated users", added, updated);
+ if (removed > 0) {
+ status += String.format(", %d removed users", removed);
+ }
if (failed != 0) {
status += String.format(", %d users failed sync! See server log for more details", failed);
}
diff --git a/server-spi/src/main/java/org/keycloak/models/UserFederationValidatingProviderFactory.java b/server-spi/src/main/java/org/keycloak/models/UserFederationValidatingProviderFactory.java
new file mode 100644
index 0000000..9624690
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/models/UserFederationValidatingProviderFactory.java
@@ -0,0 +1,37 @@
+/*
+ * 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.models;
+
+import org.keycloak.mappers.FederationConfigValidationException;
+
+/**
+ * TODO: Merge with UserFederationProviderFactory and add "default" method validateConfig with empty body once we move to source level 1.8
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public interface UserFederationValidatingProviderFactory extends UserFederationProviderFactory {
+
+ /**
+ * Called when instance of mapperModel is created for this factory through admin endpoint
+ *
+ * @param realm
+ * @param providerModel
+ * @throws FederationConfigValidationException if configuration provided in mapperModel is not valid
+ */
+ void validateConfig(RealmModel realm, UserFederationProviderModel providerModel) throws FederationConfigValidationException;
+}
diff --git a/server-spi/src/main/java/org/keycloak/models/utils/CredentialValidation.java b/server-spi/src/main/java/org/keycloak/models/utils/CredentialValidation.java
index 654c764..074949f 100755
--- a/server-spi/src/main/java/org/keycloak/models/utils/CredentialValidation.java
+++ b/server-spi/src/main/java/org/keycloak/models/utils/CredentialValidation.java
@@ -71,7 +71,7 @@ public class CredentialValidation {
public static boolean validateHashedCredential(KeycloakSession session, RealmModel realm, UserModel user, String unhashedCredValue, UserCredentialValueModel credential) {
- if(unhashedCredValue == null){
+ if (unhashedCredValue == null || unhashedCredValue.isEmpty()) {
return false;
}
diff --git a/server-spi/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java b/server-spi/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java
index ff5c38e..f75bf11 100755
--- a/server-spi/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java
+++ b/server-spi/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java
@@ -35,6 +35,8 @@ import org.keycloak.models.RoleModel;
import org.keycloak.models.ScopeContainerModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserFederationMapperModel;
+import org.keycloak.models.UserFederationProvider;
+import org.keycloak.models.UserFederationProviderFactory;
import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.UserModel;
import org.keycloak.representations.idm.CertificateRepresentation;
@@ -252,18 +254,21 @@ public final class KeycloakModelUtils {
}
/**
- * Try to find user by given username. If it fails, then fallback to find him by email
+ * Try to find user by username or email
*
* @param realm realm
* @param username username or email of user
* @return found user
*/
public static UserModel findUserByNameOrEmail(KeycloakSession session, RealmModel realm, String username) {
- UserModel user = session.users().getUserByUsername(username, realm);
- if (user == null && username.contains("@")) {
- user = session.users().getUserByEmail(username, realm);
+ if (username.indexOf('@') != -1) {
+ UserModel user = session.users().getUserByEmail(username, realm);
+ if (user != null) {
+ return user;
+ }
}
- return user;
+
+ return session.users().getUserByUsername(username, realm);
}
/**
@@ -406,6 +411,16 @@ public final class KeycloakModelUtils {
return mapperModel;
}
+ public static UserFederationProviderFactory getFederationProviderFactory(KeycloakSession session, UserFederationProviderModel model) {
+ return (UserFederationProviderFactory)session.getKeycloakSessionFactory().getProviderFactory(UserFederationProvider.class, model.getProviderName());
+ }
+
+ public static UserFederationProvider getFederationProviderInstance(KeycloakSession session, UserFederationProviderModel model) {
+ UserFederationProviderFactory factory = getFederationProviderFactory(session, model);
+ return factory.getInstance(session, model);
+
+ }
+
// END USER FEDERATION RELATED STUFF
public static String toLowerCaseSafe(String str) {
diff --git a/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
index d349f4e..970fd7e 100755
--- a/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
+++ b/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
@@ -695,6 +695,16 @@ public class RepresentationToModel {
if ("GENERATE".equals(rep.getPublicKey())) {
KeycloakModelUtils.generateRealmKeys(realm);
+ } else {
+ if (rep.getPrivateKey() != null && rep.getPublicKey() != null) {
+ realm.setPrivateKeyPem(rep.getPrivateKey());
+ realm.setPublicKeyPem(rep.getPublicKey());
+ realm.setCodeSecret(KeycloakModelUtils.generateCodeSecret());
+ }
+
+ if (rep.getCertificate() != null) {
+ realm.setCertificatePem(rep.getCertificate());
+ }
}
if(rep.isInternationalizationEnabled() != null){
diff --git a/server-spi/src/main/resources/META-INF/services/org.keycloak.provider.Spi b/server-spi/src/main/resources/META-INF/services/org.keycloak.provider.Spi
index 7bb58fc..ffbb0ac 100755
--- a/server-spi/src/main/resources/META-INF/services/org.keycloak.provider.Spi
+++ b/server-spi/src/main/resources/META-INF/services/org.keycloak.provider.Spi
@@ -21,6 +21,7 @@ org.keycloak.models.RealmSpi
org.keycloak.models.UserSessionSpi
org.keycloak.models.UserSpi
org.keycloak.models.session.UserSessionPersisterSpi
+org.keycloak.models.dblock.DBLockSpi
org.keycloak.migration.MigrationSpi
org.keycloak.hash.PasswordHashSpi
org.keycloak.events.EventListenerSpi
services/pom.xml 2(+1 -1)
diff --git a/services/pom.xml b/services/pom.xml
index ff4377a..2c98d39 100755
--- a/services/pom.xml
+++ b/services/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
diff --git a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
index fd6cd5a..68c0620 100755
--- a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
+++ b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
@@ -569,7 +569,7 @@ public class AuthenticationProcessor {
} else if (e.getError() == AuthenticationFlowError.USER_TEMPORARILY_DISABLED) {
logger.failedAuthentication(e);
event.error(Errors.USER_TEMPORARILY_DISABLED);
- return ErrorPage.error(session, Messages.ACCOUNT_TEMPORARILY_DISABLED);
+ return ErrorPage.error(session, Messages.INVALID_USER);
} else if (e.getError() == AuthenticationFlowError.INVALID_CLIENT_SESSION) {
logger.failedAuthentication(e);
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpCreateUserIfUniqueAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpCreateUserIfUniqueAuthenticator.java
index e16d759..6971ab5 100644
--- a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpCreateUserIfUniqueAuthenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpCreateUserIfUniqueAuthenticator.java
@@ -88,7 +88,7 @@ public class IdpCreateUserIfUniqueAuthenticator extends AbstractIdpAuthenticator
federatedUser.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
}
- // TODO: Event
+ userRegisteredSuccess(context, federatedUser, serializedCtx, brokerContext);
context.setUser(federatedUser);
context.getClientSession().setNote(BROKER_REGISTERED_NEW_USER, "true");
@@ -140,6 +140,12 @@ public class IdpCreateUserIfUniqueAuthenticator extends AbstractIdpAuthenticator
}
+ // Empty method by default. This exists, so subclass can override and add callback after new user is registered through social
+ protected void userRegisteredSuccess(AuthenticationFlowContext context, UserModel registeredUser, SerializedBrokeredIdentityContext serializedCtx, BrokeredIdentityContext brokerContext) {
+
+ }
+
+
@Override
public boolean requiresUser() {
return false;
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/browser/AbstractUsernameFormAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/browser/AbstractUsernameFormAuthenticator.java
index 77d004d..137e370 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/browser/AbstractUsernameFormAuthenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/browser/AbstractUsernameFormAuthenticator.java
@@ -65,7 +65,7 @@ public abstract class AbstractUsernameFormAuthenticator extends AbstractFormAuth
protected Response temporarilyDisabledUser(AuthenticationFlowContext context) {
return context.form()
- .setError(Messages.ACCOUNT_TEMPORARILY_DISABLED).createLogin();
+ .setError(Messages.INVALID_USER).createLogin();
}
protected Response invalidCredentials(AuthenticationFlowContext context) {
diff --git a/services/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java b/services/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java
index 11eba29..32d6426 100755
--- a/services/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java
+++ b/services/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java
@@ -425,7 +425,6 @@ public class SAMLEndpoint {
@Override
protected SAMLDocumentHolder extractResponseDocument(String response) {
byte[] samlBytes = PostBindingUtil.base64Decode(response);
- String xml = new String(samlBytes);
return SAMLRequestParser.parseResponseDocument(samlBytes);
}
diff --git a/services/src/main/java/org/keycloak/exportimport/dir/DirImportProvider.java b/services/src/main/java/org/keycloak/exportimport/dir/DirImportProvider.java
index dd2ee07..329b729 100755
--- a/services/src/main/java/org/keycloak/exportimport/dir/DirImportProvider.java
+++ b/services/src/main/java/org/keycloak/exportimport/dir/DirImportProvider.java
@@ -35,6 +35,7 @@ import java.io.FilenameFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@@ -118,26 +119,29 @@ public class DirImportProvider implements ImportProvider {
// Import realm first
FileInputStream is = new FileInputStream(realmFile);
final RealmRepresentation realmRep = JsonSerialization.readValue(is, RealmRepresentation.class);
+ final AtomicBoolean realmImported = new AtomicBoolean();
KeycloakModelUtils.runJobInTransaction(factory, new ExportImportSessionTask() {
@Override
public void runExportImportTask(KeycloakSession session) throws IOException {
- ImportUtils.importRealm(session, realmRep, strategy);
+ boolean imported = ImportUtils.importRealm(session, realmRep, strategy);
+ realmImported.set(imported);
}
});
- // Import users
- for (File userFile : userFiles) {
- final FileInputStream fis = new FileInputStream(userFile);
- KeycloakModelUtils.runJobInTransaction(factory, new ExportImportSessionTask() {
-
- @Override
- protected void runExportImportTask(KeycloakSession session) throws IOException {
- ImportUtils.importUsersFromStream(session, realmName, JsonSerialization.mapper, fis);
- }
- });
+ if (realmImported.get()) {
+ // Import users
+ for (File userFile : userFiles) {
+ final FileInputStream fis = new FileInputStream(userFile);
+ KeycloakModelUtils.runJobInTransaction(factory, new ExportImportSessionTask() {
+ @Override
+ protected void runExportImportTask(KeycloakSession session) throws IOException {
+ ImportUtils.importUsersFromStream(session, realmName, JsonSerialization.mapper, fis);
+ }
+ });
+ }
}
}
diff --git a/services/src/main/java/org/keycloak/exportimport/util/ImportUtils.java b/services/src/main/java/org/keycloak/exportimport/util/ImportUtils.java
index 06f6e54..2ee2e11 100755
--- a/services/src/main/java/org/keycloak/exportimport/util/ImportUtils.java
+++ b/services/src/main/java/org/keycloak/exportimport/util/ImportUtils.java
@@ -68,7 +68,7 @@ public class ImportUtils {
* @param strategy specifies whether to overwrite or ignore existing realm or user entries
* @return newly imported realm (or existing realm if ignoreExisting is true and realm of this name already exists)
*/
- public static void importRealm(KeycloakSession session, RealmRepresentation rep, Strategy strategy) {
+ public static boolean importRealm(KeycloakSession session, RealmRepresentation rep, Strategy strategy) {
String realmName = rep.getRealm();
RealmProvider model = session.realms();
RealmModel realm = model.getRealmByName(realmName);
@@ -76,7 +76,7 @@ public class ImportUtils {
if (realm != null) {
if (strategy == Strategy.IGNORE_EXISTING) {
logger.infof("Realm '%s' already exists. Import skipped", realmName);
- return;
+ return false;
} else {
logger.infof("Realm '%s' already exists. Removing it before import", realmName);
if (Config.getAdminRealm().equals(realm.getId())) {
@@ -91,13 +91,13 @@ public class ImportUtils {
}
RealmImporter realmManager = session.getContext().getRealmManager();
- realm = realmManager.importRealm(rep);
+ realmManager.importRealm(rep);
if (System.getProperty(ExportImportConfig.ACTION) != null) {
logger.infof("Realm '%s' imported", realmName);
}
- return;
+ return true;
}
/**
diff --git a/services/src/main/java/org/keycloak/forms/account/freemarker/FreeMarkerAccountProvider.java b/services/src/main/java/org/keycloak/forms/account/freemarker/FreeMarkerAccountProvider.java
index fd70124..893e816 100755
--- a/services/src/main/java/org/keycloak/forms/account/freemarker/FreeMarkerAccountProvider.java
+++ b/services/src/main/java/org/keycloak/forms/account/freemarker/FreeMarkerAccountProvider.java
@@ -27,7 +27,6 @@ import java.util.Map;
import java.util.Properties;
import javax.ws.rs.core.HttpHeaders;
-import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
@@ -64,6 +63,7 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.utils.FormMessage;
+import org.keycloak.utils.MediaType;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@@ -209,7 +209,7 @@ public class FreeMarkerAccountProvider implements AccountProvider {
try {
String result = freeMarker.processTemplate(attributes, Templates.getTemplate(page), theme);
- Response.ResponseBuilder builder = Response.status(status).type(MediaType.TEXT_HTML).entity(result);
+ Response.ResponseBuilder builder = Response.status(status).type(MediaType.TEXT_HTML_UTF_8_TYPE).language(locale).entity(result);
BrowserSecurityHeaderSetup.headers(builder, realm);
return builder.build();
} catch (FreeMarkerException e) {
diff --git a/services/src/main/java/org/keycloak/forms/account/freemarker/model/AccountFederatedIdentityBean.java b/services/src/main/java/org/keycloak/forms/account/freemarker/model/AccountFederatedIdentityBean.java
index ec02eba..a20810e 100755
--- a/services/src/main/java/org/keycloak/forms/account/freemarker/model/AccountFederatedIdentityBean.java
+++ b/services/src/main/java/org/keycloak/forms/account/freemarker/model/AccountFederatedIdentityBean.java
@@ -79,7 +79,7 @@ public class AccountFederatedIdentityBean {
this.identities = new LinkedList<FederatedIdentityEntry>(orderedSet);
// Removing last social provider is not possible if you don't have other possibility to authenticate
- this.removeLinkPossible = availableIdentities > 1 || user.getFederationLink() != null || AccountService.isPasswordSet(user);
+ this.removeLinkPossible = availableIdentities > 1 || user.getFederationLink() != null || AccountService.isPasswordSet(session, realm, user);
}
private FederatedIdentityModel getIdentity(Set<FederatedIdentityModel> identities, String providerId) {
diff --git a/services/src/main/java/org/keycloak/forms/login/freemarker/FreeMarkerLoginFormsProvider.java b/services/src/main/java/org/keycloak/forms/login/freemarker/FreeMarkerLoginFormsProvider.java
index bb2e518..1a81870 100755
--- a/services/src/main/java/org/keycloak/forms/login/freemarker/FreeMarkerLoginFormsProvider.java
+++ b/services/src/main/java/org/keycloak/forms/login/freemarker/FreeMarkerLoginFormsProvider.java
@@ -61,8 +61,8 @@ import org.keycloak.models.UserModel;
import org.keycloak.models.utils.FormMessage;
import org.keycloak.services.Urls;
import org.keycloak.services.messages.Messages;
+import org.keycloak.utils.MediaType;
-import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
@@ -312,7 +312,7 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
try {
String result = freeMarker.processTemplate(attributes, Templates.getTemplate(page), theme);
- Response.ResponseBuilder builder = Response.status(status).type(MediaType.TEXT_HTML).entity(result);
+ Response.ResponseBuilder builder = Response.status(status).type(MediaType.TEXT_HTML_UTF_8).entity(result);
BrowserSecurityHeaderSetup.headers(builder, realm);
for (Map.Entry<String, String> entry : httpResponseHeaders.entrySet()) {
builder.header(entry.getKey(), entry.getValue());
@@ -413,7 +413,7 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
}
try {
String result = freeMarker.processTemplate(attributes, form, theme);
- Response.ResponseBuilder builder = Response.status(status).type(MediaType.TEXT_HTML).entity(result);
+ Response.ResponseBuilder builder = Response.status(status).type(MediaType.TEXT_HTML_UTF_8_TYPE).language(locale).entity(result);
BrowserSecurityHeaderSetup.headers(builder, realm);
for (Map.Entry<String, String> entry : httpResponseHeaders.entrySet()) {
builder.header(entry.getKey(), entry.getValue());
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
index e49665c..1ff3da9 100644
--- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
@@ -222,6 +222,22 @@ public class TokenEndpoint {
accessCode.setAction(null);
UserSessionModel userSession = clientSession.getUserSession();
+
+ if (userSession == null) {
+ event.error(Errors.USER_SESSION_NOT_FOUND);
+ throw new ErrorResponseException("invalid_grant", "User session not found", Response.Status.BAD_REQUEST);
+ }
+
+ UserModel user = userSession.getUser();
+ if (user == null) {
+ event.error(Errors.USER_NOT_FOUND);
+ throw new ErrorResponseException("invalid_grant", "User not found", Response.Status.BAD_REQUEST);
+ }
+ if (!user.isEnabled()) {
+ event.error(Errors.USER_DISABLED);
+ throw new ErrorResponseException("invalid_grant", "User disabled", Response.Status.BAD_REQUEST);
+ }
+
event.user(userSession.getUser());
event.session(userSession.getId());
@@ -241,17 +257,6 @@ public class TokenEndpoint {
throw new ErrorResponseException("invalid_grant", "Client not allowed to exchange code", Response.Status.BAD_REQUEST);
}
- UserModel user = session.users().getUserById(userSession.getUser().getId(), realm);
- if (user == null) {
- event.error(Errors.USER_NOT_FOUND);
- throw new ErrorResponseException("invalid_grant", "User not found", Response.Status.BAD_REQUEST);
- }
-
- if (!user.isEnabled()) {
- event.error(Errors.USER_DISABLED);
- throw new ErrorResponseException("invalid_grant", "User disabled", Response.Status.BAD_REQUEST);
- }
-
if (!AuthenticationManager.isSessionValid(realm, userSession)) {
event.error(Errors.USER_SESSION_NOT_FOUND);
throw new ErrorResponseException("invalid_grant", "Session not active", Response.Status.BAD_REQUEST);
diff --git a/services/src/main/java/org/keycloak/services/DefaultKeycloakTransactionManager.java b/services/src/main/java/org/keycloak/services/DefaultKeycloakTransactionManager.java
index fca6a9e..53f92f4 100755
--- a/services/src/main/java/org/keycloak/services/DefaultKeycloakTransactionManager.java
+++ b/services/src/main/java/org/keycloak/services/DefaultKeycloakTransactionManager.java
@@ -18,7 +18,6 @@ package org.keycloak.services;
import org.keycloak.models.KeycloakTransaction;
import org.keycloak.models.KeycloakTransactionManager;
-import org.keycloak.services.ServicesLogger;
import java.util.LinkedList;
import java.util.List;
diff --git a/services/src/main/java/org/keycloak/services/filters/KeycloakSessionServletFilter.java b/services/src/main/java/org/keycloak/services/filters/KeycloakSessionServletFilter.java
index 530447e..d6a3f13 100755
--- a/services/src/main/java/org/keycloak/services/filters/KeycloakSessionServletFilter.java
+++ b/services/src/main/java/org/keycloak/services/filters/KeycloakSessionServletFilter.java
@@ -77,6 +77,12 @@ public class KeycloakSessionServletFilter implements Filter {
try {
filterChain.doFilter(servletRequest, servletResponse);
} finally {
+ // KeycloakTransactionCommitter is responsible for committing the transaction, but if an exception is thrown it's not invoked and transaction
+ // should be rolled back
+ if (session.getTransaction() != null && session.getTransaction().isActive()) {
+ session.getTransaction().rollback();
+ }
+
session.close();
ResteasyProviderFactory.clearContextData();
}
diff --git a/services/src/main/java/org/keycloak/services/managers/DBLockManager.java b/services/src/main/java/org/keycloak/services/managers/DBLockManager.java
new file mode 100644
index 0000000..1909c36
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/managers/DBLockManager.java
@@ -0,0 +1,87 @@
+/*
+ * 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.services.managers;
+
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.KeycloakSessionTask;
+import org.keycloak.models.RealmProvider;
+import org.keycloak.models.RealmProviderFactory;
+import org.keycloak.models.dblock.DBLockProvider;
+import org.keycloak.models.dblock.DBLockProviderFactory;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.services.ServicesLogger;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class DBLockManager {
+
+ protected static final ServicesLogger logger = ServicesLogger.ROOT_LOGGER;
+
+ public void waitForLock(KeycloakSessionFactory sessionFactory) {
+ KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
+
+ @Override
+ public void run(KeycloakSession session) {
+ DBLockProvider lock = getDBLock(session);
+ lock.waitForLock();
+ }
+
+ });
+ }
+
+
+ public void releaseLock(KeycloakSessionFactory sessionFactory) {
+ KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
+
+ @Override
+ public void run(KeycloakSession session) {
+ DBLockProvider lock = getDBLock(session);
+ lock.releaseLock();
+ }
+
+ });
+ }
+
+
+ public void checkForcedUnlock(KeycloakSessionFactory sessionFactory) {
+ if (Boolean.getBoolean("keycloak.dblock.forceUnlock")) {
+ logger.forcedReleaseDBLock();
+ releaseLock(sessionFactory);
+ }
+ }
+
+
+ // Try to detect ID from realmProvider
+ public DBLockProvider getDBLock(KeycloakSession session) {
+ String realmProviderId = getRealmProviderId(session);
+ return session.getProvider(DBLockProvider.class, realmProviderId);
+ }
+
+ public DBLockProviderFactory getDBLockFactory(KeycloakSession session) {
+ String realmProviderId = getRealmProviderId(session);
+ return (DBLockProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(DBLockProvider.class, realmProviderId);
+ }
+
+ private String getRealmProviderId(KeycloakSession session) {
+ RealmProviderFactory realmProviderFactory = (RealmProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(RealmProvider.class);
+ return realmProviderFactory.getId();
+ }
+
+}
diff --git a/services/src/main/java/org/keycloak/services/resources/AbstractSecuredLocalService.java b/services/src/main/java/org/keycloak/services/resources/AbstractSecuredLocalService.java
index 45df982..547424c 100755
--- a/services/src/main/java/org/keycloak/services/resources/AbstractSecuredLocalService.java
+++ b/services/src/main/java/org/keycloak/services/resources/AbstractSecuredLocalService.java
@@ -177,6 +177,8 @@ public abstract class AbstractSecuredLocalService {
oauth.setClientId(client.getClientId());
+ oauth.setSecure(realm.getSslRequired().isRequired(clientConnection));
+
UriBuilder uriBuilder = UriBuilder.fromUri(getBaseRedirectUri()).path("login-redirect");
if (path != null) {
@@ -247,8 +249,7 @@ public abstract class AbstractSecuredLocalService {
URI url = uriBuilder.build();
- // todo httpOnly!
- NewCookie cookie = new NewCookie(getStateCookieName(), state, getStateCookiePath(uriInfo), null, null, -1, isSecure);
+ NewCookie cookie = new NewCookie(getStateCookieName(), state, getStateCookiePath(uriInfo), null, null, -1, isSecure, true);
logger.debug("NewCookie: " + cookie.toString());
logger.debug("Oauth Redirect to: " + url);
return Response.status(302)
diff --git a/services/src/main/java/org/keycloak/services/resources/AccountService.java b/services/src/main/java/org/keycloak/services/resources/AccountService.java
index 653e266..4907707 100755
--- a/services/src/main/java/org/keycloak/services/resources/AccountService.java
+++ b/services/src/main/java/org/keycloak/services/resources/AccountService.java
@@ -30,16 +30,20 @@ import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.FederatedIdentityModel;
import org.keycloak.models.IdentityProviderModel;
+import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ModelDuplicateException;
import org.keycloak.models.ModelException;
import org.keycloak.models.ModelReadOnlyException;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserCredentialValueModel;
+import org.keycloak.models.UserFederationProvider;
+import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.utils.CredentialValidation;
import org.keycloak.models.utils.FormMessage;
+import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.models.utils.ModelToRepresentation;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.protocol.oidc.utils.RedirectUtils;
@@ -298,7 +302,7 @@ public class AccountService extends AbstractSecuredLocalService {
@GET
public Response passwordPage() {
if (auth != null) {
- account.setPasswordSet(isPasswordSet(auth.getUser()));
+ account.setPasswordSet(isPasswordSet(session, realm, auth.getUser()));
}
return forwardToPage("password", AccountPages.PASSWORD);
@@ -601,7 +605,7 @@ public class AccountService extends AbstractSecuredLocalService {
csrfCheck(formData);
UserModel user = auth.getUser();
- boolean requireCurrent = isPasswordSet(user);
+ boolean requireCurrent = isPasswordSet(session, realm, user);
account.setPasswordSet(requireCurrent);
String password = formData.getFirst("password");
@@ -621,7 +625,7 @@ public class AccountService extends AbstractSecuredLocalService {
}
}
- if (Validation.isEmpty(passwordNew)) {
+ if (Validation.isBlank(passwordNew)) {
setReferrerOnPage();
return account.setError(Messages.MISSING_PASSWORD).createResponse(AccountPages.PASSWORD);
}
@@ -723,7 +727,7 @@ public class AccountService extends AbstractSecuredLocalService {
if (link != null) {
// Removing last social provider is not possible if you don't have other possibility to authenticate
- if (session.users().getFederatedIdentities(user, realm).size() > 1 || user.getFederationLink() != null || isPasswordSet(user)) {
+ if (session.users().getFederatedIdentities(user, realm).size() > 1 || user.getFederationLink() != null || isPasswordSet(session, realm, user)) {
session.users().removeFederatedIdentity(realm, user, providerId);
logger.debugv("Social provider {0} removed successfully from user {1}", providerId, user.getUsername());
@@ -758,11 +762,25 @@ public class AccountService extends AbstractSecuredLocalService {
return Urls.accountBase(uriInfo.getBaseUri()).path("/").build(realm.getName());
}
- public static boolean isPasswordSet(UserModel user) {
+ public static boolean isPasswordSet(KeycloakSession session, RealmModel realm, UserModel user) {
boolean passwordSet = false;
+ // See if password is set for user on linked UserFederationProvider
if (user.getFederationLink() != null) {
- passwordSet = true;
+
+ UserFederationProvider federationProvider = null;
+ for (UserFederationProviderModel fedProviderModel : realm.getUserFederationProviders()) {
+ if (fedProviderModel.getId().equals(user.getFederationLink())) {
+ federationProvider = KeycloakModelUtils.getFederationProviderInstance(session, fedProviderModel);
+ }
+ }
+
+ if (federationProvider != null) {
+ Set<String> supportedCreds = federationProvider.getSupportedCredentialTypes(user);
+ if (supportedCreds.contains(UserCredentialModel.PASSWORD)) {
+ passwordSet = true;
+ }
+ }
}
for (UserCredentialValueModel c : user.getCredentialsDirectly()) {
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/AdminConsole.java b/services/src/main/java/org/keycloak/services/resources/admin/AdminConsole.java
index 044c7d5..9ed474b 100644
--- a/services/src/main/java/org/keycloak/services/resources/admin/AdminConsole.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/AdminConsole.java
@@ -27,7 +27,6 @@ import org.keycloak.theme.BrowserSecurityHeaderSetup;
import org.keycloak.theme.FreeMarkerException;
import org.keycloak.theme.FreeMarkerUtil;
import org.keycloak.theme.Theme;
-import org.keycloak.theme.ThemeProvider;
import org.keycloak.models.AdminRoles;
import org.keycloak.models.ClientModel;
import org.keycloak.models.Constants;
@@ -43,14 +42,13 @@ import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.resources.KeycloakApplication;
import org.keycloak.services.Urls;
+import org.keycloak.utils.MediaType;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
-import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
-import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import javax.ws.rs.ext.Providers;
@@ -281,7 +279,7 @@ public class AdminConsole {
if (!uriInfo.getRequestUri().getPath().endsWith("/")) {
return Response.status(302).location(uriInfo.getRequestUriBuilder().path("/").build()).build();
} else {
- Theme theme = getTheme();
+ Theme theme = AdminRoot.getTheme(session, realm);
Map<String, Object> map = new HashMap<>();
@@ -297,17 +295,12 @@ public class AdminConsole {
FreeMarkerUtil freeMarkerUtil = new FreeMarkerUtil();
String result = freeMarkerUtil.processTemplate(map, "index.ftl", theme);
- Response.ResponseBuilder builder = Response.status(Response.Status.OK).type(MediaType.TEXT_HTML).entity(result);
+ Response.ResponseBuilder builder = Response.status(Response.Status.OK).type(MediaType.TEXT_HTML_UTF_8).language(Locale.ENGLISH).entity(result);
BrowserSecurityHeaderSetup.headers(builder, realm);
return builder.build();
}
}
- private Theme getTheme() throws IOException {
- ThemeProvider themeProvider = session.getProvider(ThemeProvider.class, "extending");
- return themeProvider.getTheme(realm.getAdminTheme(), Theme.Type.ADMIN);
- }
-
@GET
@Path("{indexhtml: index.html}") // this expression is a hack to get around jaxdoclet generation bug. Doesn't like index.html
public Response getIndexHtmlRedirect() {
@@ -318,11 +311,7 @@ public class AdminConsole {
@Path("messages.json")
@Produces(MediaType.APPLICATION_JSON)
public Properties getMessages(@QueryParam("lang") String lang) {
- try {
- Locale locale = lang != null ? Locale.forLanguageTag(lang) : Locale.ENGLISH;
- return getTheme().getMessages("admin-messages", locale);
- } catch (IOException e) {
- throw new WebApplicationException("Failed to load message bundle", e);
- }
+ return AdminRoot.getMessages(session, realm, "admin-messages", lang);
}
+
}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/AdminRoot.java b/services/src/main/java/org/keycloak/services/resources/admin/AdminRoot.java
index 10b636b..0649b25 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/AdminRoot.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/AdminRoot.java
@@ -38,6 +38,8 @@ import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.resources.Cors;
import org.keycloak.services.resources.admin.info.ServerInfoAdminResource;
+import org.keycloak.theme.Theme;
+import org.keycloak.theme.ThemeProvider;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
@@ -47,6 +49,9 @@ import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
+import java.io.IOException;
+import java.util.Locale;
+import java.util.Properties;
/**
* Root resource for admin console and admin REST API
@@ -265,4 +270,31 @@ public class AdminRoot {
}
}
+ public static Theme getTheme(KeycloakSession session, RealmModel realm) throws IOException {
+ ThemeProvider themeProvider = session.getProvider(ThemeProvider.class, "extending");
+ return themeProvider.getTheme(realm.getAdminTheme(), Theme.Type.ADMIN);
+ }
+
+ public static Properties getMessages(KeycloakSession session, RealmModel realm, String lang) {
+ try {
+ Theme theme = getTheme(session, realm);
+ Locale locale = lang != null ? Locale.forLanguageTag(lang) : Locale.ENGLISH;
+ return theme.getMessages(locale);
+ } catch (IOException e) {
+ logger.error("Failed to load messages from theme", e);
+ return new Properties();
+ }
+ }
+
+ public static Properties getMessages(KeycloakSession session, RealmModel realm, String bundle, String lang) {
+ try {
+ Theme theme = getTheme(session, realm);
+ Locale locale = lang != null ? Locale.forLanguageTag(lang) : Locale.ENGLISH;
+ return theme.getMessages(bundle, locale);
+ } catch (IOException e) {
+ logger.error("Failed to load messages from theme", e);
+ return new Properties();
+ }
+ }
+
}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java b/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java
index 6c9bba9..54378bf 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java
@@ -581,7 +581,7 @@ public class AuthenticationManagementResource {
}
public List<AuthenticationExecutionModel> getSortedExecutions(AuthenticationFlowModel parentFlow) {
- List<AuthenticationExecutionModel> executions = realm.getAuthenticationExecutions(parentFlow.getId());
+ List<AuthenticationExecutionModel> executions = new LinkedList<>(realm.getAuthenticationExecutions(parentFlow.getId()));
Collections.sort(executions, AuthenticationExecutionModel.ExecutionComparator.SINGLETON);
return executions;
}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientAttributeCertificateResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientAttributeCertificateResource.java
index 6633a68..bea79a0 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/ClientAttributeCertificateResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientAttributeCertificateResource.java
@@ -28,6 +28,7 @@ import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.representations.KeyStoreConfig;
import org.keycloak.representations.idm.CertificateRepresentation;
import org.keycloak.services.ErrorResponseException;
import org.keycloak.common.util.PemUtils;
@@ -57,7 +58,7 @@ import java.util.Map;
* @version $Revision: 1 $
*/
public class ClientAttributeCertificateResource {
-
+
public static final String PRIVATE_KEY = "private.key";
public static final String X509CERTIFICATE = "certificate";
@@ -112,7 +113,7 @@ public class ClientAttributeCertificateResource {
client.setAttribute(privateAttribute, info.getPrivateKey());
client.setAttribute(certificateAttribute, info.getCertificate());
-
+
adminEvent.operation(OperationType.ACTION).resourcePath(session.getContext().getUri()).representation(info).success();
return info;
@@ -225,64 +226,6 @@ public class ClientAttributeCertificateResource {
return info;
}
-
- public static class KeyStoreConfig {
- protected Boolean realmCertificate;
- protected String storePassword;
- protected String keyPassword;
- protected String keyAlias;
- protected String realmAlias;
- protected String format;
-
- public Boolean isRealmCertificate() {
- return realmCertificate;
- }
-
- public void setRealmCertificate(Boolean realmCertificate) {
- this.realmCertificate = realmCertificate;
- }
-
- public String getStorePassword() {
- return storePassword;
- }
-
- public void setStorePassword(String storePassword) {
- this.storePassword = storePassword;
- }
-
- public String getKeyPassword() {
- return keyPassword;
- }
-
- public void setKeyPassword(String keyPassword) {
- this.keyPassword = keyPassword;
- }
-
- public String getKeyAlias() {
- return keyAlias;
- }
-
- public void setKeyAlias(String keyAlias) {
- this.keyAlias = keyAlias;
- }
-
- public String getRealmAlias() {
- return realmAlias;
- }
-
- public void setRealmAlias(String realmAlias) {
- this.realmAlias = realmAlias;
- }
-
- public String getFormat() {
- return format;
- }
-
- public void setFormat(String format) {
- this.format = format;
- }
- }
-
/**
* Get a keystore file for the client, containing private key and public certificate
*
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java
index 790dd2b..09c3dee 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java
@@ -45,7 +45,6 @@ import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.managers.ResourceAdminManager;
import org.keycloak.services.resources.KeycloakApplication;
import org.keycloak.services.ErrorResponse;
-import org.keycloak.util.JsonSerialization;
import org.keycloak.common.util.Time;
import javax.ws.rs.Consumes;
@@ -62,12 +61,11 @@ import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
-import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.Set;
+
import static java.lang.Boolean.TRUE;
@@ -172,54 +170,6 @@ public class ClientResource {
return provider.generateInstallation(session, realm, client, keycloak.getBaseUri(uriInfo));
}
-
- /**
- * Get keycloak.json file
- *
- * this method is deprecated, see getInstallationProvider
- *
- * Returns a keycloak.json file to be used to configure the adapter of the specified client.
- *
- * @return
- * @throws IOException
- */
- @Deprecated
- @GET
- @NoCache
- @Path("installation/json")
- @Produces(MediaType.APPLICATION_JSON)
- public String getInstallation() throws IOException {
- auth.requireView();
-
- ClientManager clientManager = new ClientManager(new RealmManager(session));
- Object rep = clientManager.toInstallationRepresentation(realm, client, getKeycloakApplication().getBaseUri(uriInfo));
-
- // TODO Temporary solution to pretty-print
- return JsonSerialization.mapper.writerWithDefaultPrettyPrinter().writeValueAsString(rep);
- }
-
- /**
- * Get adapter configuration XML for JBoss / Wildfly Keycloak subsystem
- *
- * this method is deprecated, see getInstallationProvider
- *
- * Returns XML that can be included in the JBoss / Wildfly Keycloak subsystem to configure the adapter of that client.
- *
- * @return
- * @throws IOException
- */
- @Deprecated
- @GET
- @NoCache
- @Path("installation/jboss")
- @Produces(MediaType.TEXT_PLAIN)
- public String getJBossInstallation() throws IOException {
- auth.requireView();
-
- ClientManager clientManager = new ClientManager(new RealmManager(session));
- return clientManager.toJBossSubsystemConfig(realm, client, getKeycloakApplication().getBaseUri(uriInfo));
- }
-
/**
* Delete the client
*
@@ -307,64 +257,6 @@ public class ClientResource {
}
/**
- * Get allowed origins
- *
- * This is used for CORS requests. Access tokens will have
- * their allowedOrigins claim set to this value for tokens created for this client.
- *
- * @return
- */
- @Path("allowed-origins")
- @GET
- @NoCache
- @Produces(MediaType.APPLICATION_JSON)
- public Set<String> getAllowedOrigins()
- {
- auth.requireView();
- return client.getWebOrigins();
- }
-
- /**
- * Update allowed origins
- *
- * This is used for CORS requests. Access tokens will have
- * their allowedOrigins claim set to this value for tokens created for this client.
- *
- * @param allowedOrigins
- */
- @Path("allowed-origins")
- @PUT
- @Consumes(MediaType.APPLICATION_JSON)
- public void updateAllowedOrigins(Set<String> allowedOrigins)
- {
- auth.requireManage();
-
- client.setWebOrigins(allowedOrigins);
- adminEvent.operation(OperationType.UPDATE).resourcePath(uriInfo).representation(client).success();
- }
-
- /**
- * Delete the specified origins from current allowed origins
- *
- * This is used for CORS requests. Access tokens will have
- * their allowedOrigins claim set to this value for tokens created for this client.
- *
- * @param allowedOrigins List of origins to delete
- */
- @Path("allowed-origins")
- @DELETE
- @Consumes(MediaType.APPLICATION_JSON)
- public void deleteAllowedOrigins(Set<String> allowedOrigins)
- {
- auth.requireManage();
-
- for (String origin : allowedOrigins) {
- client.removeWebOrigin(origin);
- }
- adminEvent.operation(OperationType.DELETE).resourcePath(uriInfo).success();
- }
-
- /**
* Get a user dedicated to the service account
*
* @return
@@ -507,41 +399,6 @@ public class ClientResource {
return sessions;
}
-
- /**
- * Logout all sessions
- *
- * If the client has an admin URL, invalidate all sessions associated with that client directly.
- *
- */
- @Path("logout-all")
- @POST
- public GlobalRequestResult logoutAll() {
- auth.requireManage();
- adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo).success();
- return new ResourceAdminManager(session).logoutClient(uriInfo.getRequestUri(), realm, client);
-
- }
-
- /**
- * Logout the user by username
- *
- * If the client has an admin URL, invalidate the sessions for a particular user directly.
- *
- */
- @Path("logout-user/{username}")
- @POST
- public void logout(final @PathParam("username") String username) {
- auth.requireManage();
- UserModel user = session.users().getUserByUsername(username, realm);
- if (user == null) {
- throw new NotFoundException("User not found");
- }
- adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo).success();
- new ResourceAdminManager(session).logoutUserFromClient(uriInfo.getRequestUri(), realm, client, user);
-
- }
-
/**
* Register a cluster node with the client
*
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/info/ServerInfoAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/info/ServerInfoAdminResource.java
index 419ea16..4454ccb 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/info/ServerInfoAdminResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/info/ServerInfoAdminResource.java
@@ -51,8 +51,14 @@ import org.keycloak.provider.Spi;
import org.keycloak.representations.idm.ConfigPropertyRepresentation;
import org.keycloak.representations.idm.ProtocolMapperRepresentation;
import org.keycloak.representations.idm.ProtocolMapperTypeRepresentation;
-import org.keycloak.representations.info.*;
import org.keycloak.broker.social.SocialIdentityProvider;
+import org.keycloak.representations.info.ClientInstallationRepresentation;
+import org.keycloak.representations.info.MemoryInfoRepresentation;
+import org.keycloak.representations.info.ProviderRepresentation;
+import org.keycloak.representations.info.ServerInfoRepresentation;
+import org.keycloak.representations.info.SpiInfoRepresentation;
+import org.keycloak.representations.info.SystemInfoRepresentation;
+import org.keycloak.representations.info.ThemeInfoRepresentation;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
index 0c9f4ce..b434cdf 100644
--- a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
@@ -20,7 +20,10 @@ import org.jboss.resteasy.annotations.cache.NoCache;
import org.jboss.resteasy.spi.BadRequestException;
import org.jboss.resteasy.spi.NotFoundException;
import org.jboss.resteasy.spi.ResteasyProviderFactory;
+import org.keycloak.KeyPairVerifier;
import org.keycloak.common.ClientConnection;
+import org.keycloak.common.VerificationException;
+import org.keycloak.common.util.PemUtils;
import org.keycloak.events.Event;
import org.keycloak.events.EventQuery;
import org.keycloak.events.EventStoreProvider;
@@ -30,6 +33,8 @@ import org.keycloak.events.admin.AdminEventQuery;
import org.keycloak.events.admin.OperationType;
import org.keycloak.exportimport.ClientDescriptionConverter;
import org.keycloak.exportimport.ClientDescriptionConverterFactory;
+import org.keycloak.jose.jws.JWSBuilder;
+import org.keycloak.jose.jws.JWSInput;
import org.keycloak.models.ClientModel;
import org.keycloak.models.GroupModel;
import org.keycloak.models.KeycloakSession;
@@ -74,8 +79,11 @@ import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.UriInfo;
+import java.security.PrivateKey;
+import java.security.PublicKey;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
@@ -236,6 +244,14 @@ public class RealmAdminResource {
logger.debug("updating realm: " + realm.getName());
try {
+ if (!"GENERATE".equals(rep.getPublicKey()) && (rep.getPrivateKey() != null && rep.getPublicKey() != null)) {
+ try {
+ KeyPairVerifier.verify(rep.getPrivateKey(), rep.getPublicKey());
+ } catch (VerificationException e) {
+ return ErrorResponse.error(e.getMessage(), Status.BAD_REQUEST);
+ }
+ }
+
RepresentationToModel.updateRealm(rep, realm);
// Refresh periodic sync tasks for configured federationProviders
@@ -253,7 +269,7 @@ public class RealmAdminResource {
throw e;
} catch (Exception e) {
logger.error(e.getMessage(), e);
- return ErrorResponse.error("Failed to update " + rep.getRealm() + " Realm.", Response.Status.INTERNAL_SERVER_ERROR);
+ return ErrorResponse.error("Failed to update realm", Response.Status.INTERNAL_SERVER_ERROR);
}
}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UserFederationProviderResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UserFederationProviderResource.java
index 0eb1748..29b4ae6 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/UserFederationProviderResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/UserFederationProviderResource.java
@@ -16,12 +16,14 @@
*/
package org.keycloak.services.resources.admin;
+import java.text.MessageFormat;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
+import java.util.Properties;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
@@ -40,7 +42,7 @@ import javax.ws.rs.core.UriInfo;
import org.jboss.resteasy.annotations.cache.NoCache;
import org.jboss.resteasy.spi.NotFoundException;
import org.keycloak.events.admin.OperationType;
-import org.keycloak.mappers.MapperConfigValidationException;
+import org.keycloak.mappers.FederationConfigValidationException;
import org.keycloak.mappers.UserFederationMapper;
import org.keycloak.mappers.UserFederationMapperFactory;
import org.keycloak.models.KeycloakSession;
@@ -63,7 +65,6 @@ import org.keycloak.representations.idm.UserFederationProviderRepresentation;
import org.keycloak.services.ErrorResponseException;
import org.keycloak.services.ServicesLogger;
import org.keycloak.services.managers.UsersSyncManager;
-import org.keycloak.timer.TimerProvider;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@@ -105,6 +106,9 @@ public class UserFederationProviderResource {
}
UserFederationProviderModel model = new UserFederationProviderModel(rep.getId(), rep.getProviderName(), rep.getConfig(), rep.getPriority(), displayName,
rep.getFullSyncPeriod(), rep.getChangedSyncPeriod(), rep.getLastSync());
+
+ UserFederationProvidersResource.validateFederationProviderConfig(session, auth, realm, model);
+
realm.updateUserFederationProvider(model);
new UsersSyncManager().notifyToRefreshPeriodicSync(session, realm, model, false);
boolean kerberosCredsAdded = UserFederationProvidersResource.checkKerberosCredential(session, realm, model);
@@ -369,9 +373,12 @@ public class UserFederationProviderResource {
private void validateModel(UserFederationMapperModel model) {
try {
UserFederationMapperFactory mapperFactory = (UserFederationMapperFactory) session.getKeycloakSessionFactory().getProviderFactory(UserFederationMapper.class, model.getFederationMapperType());
- mapperFactory.validateConfig(realm, model);
- } catch (MapperConfigValidationException ex) {
- throw new ErrorResponseException("Validation error", ex.getMessage(), Response.Status.BAD_REQUEST);
+ mapperFactory.validateConfig(realm, federationProviderModel, model);
+ } catch (FederationConfigValidationException ex) {
+ logger.error(ex.getMessage());
+ Properties messages = AdminRoot.getMessages(session, realm, auth.getAuth().getToken().getLocale());
+ throw new ErrorResponseException(ex.getMessage(), MessageFormat.format(messages.getProperty(ex.getMessage(), ex.getMessage()), ex.getParameters()),
+ Response.Status.BAD_REQUEST);
}
}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UserFederationProvidersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UserFederationProvidersResource.java
index 995cda4..9c9ac51 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/UserFederationProvidersResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/UserFederationProvidersResource.java
@@ -21,11 +21,13 @@ import org.jboss.resteasy.spi.NotFoundException;
import org.jboss.resteasy.spi.ResteasyProviderFactory;
import org.keycloak.common.constants.KerberosConstants;
import org.keycloak.events.admin.OperationType;
+import org.keycloak.mappers.FederationConfigValidationException;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserFederationProvider;
import org.keycloak.models.UserFederationProviderFactory;
import org.keycloak.models.UserFederationProviderModel;
+import org.keycloak.models.UserFederationValidatingProviderFactory;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.models.utils.ModelToRepresentation;
import org.keycloak.provider.ConfiguredProvider;
@@ -35,6 +37,8 @@ import org.keycloak.representations.idm.ConfigPropertyRepresentation;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.representations.idm.UserFederationProviderFactoryRepresentation;
import org.keycloak.representations.idm.UserFederationProviderRepresentation;
+import org.keycloak.services.ErrorResponse;
+import org.keycloak.services.ErrorResponseException;
import org.keycloak.services.ServicesLogger;
import org.keycloak.services.managers.UsersSyncManager;
import org.keycloak.timer.TimerProvider;
@@ -51,9 +55,11 @@ import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
+import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
+import java.util.Properties;
/**
* Base resource for managing users
@@ -101,6 +107,20 @@ public class UserFederationProvidersResource {
return false;
}
+ public static void validateFederationProviderConfig(KeycloakSession session, RealmAuth auth, RealmModel realm, UserFederationProviderModel model) {
+ UserFederationProviderFactory providerFactory = KeycloakModelUtils.getFederationProviderFactory(session, model);
+ if (providerFactory instanceof UserFederationValidatingProviderFactory) {
+ try {
+ ((UserFederationValidatingProviderFactory) providerFactory).validateConfig(realm, model);
+ } catch (FederationConfigValidationException fcve) {
+ logger.error(fcve.getMessage());
+ Properties messages = AdminRoot.getMessages(session, realm, auth.getAuth().getToken().getLocale());
+ throw new ErrorResponseException(fcve.getMessage(), MessageFormat.format(messages.getProperty(fcve.getMessage(), fcve.getMessage()), fcve.getParameters()),
+ Response.Status.BAD_REQUEST);
+ }
+ }
+ }
+
/**
* Get available provider factories
*
@@ -176,6 +196,10 @@ public class UserFederationProvidersResource {
if (displayName != null && displayName.trim().equals("")) {
displayName = null;
}
+
+ UserFederationProviderModel tempModel = new UserFederationProviderModel(null, rep.getProviderName(), rep.getConfig(), rep.getPriority(), displayName, rep.getFullSyncPeriod(), rep.getChangedSyncPeriod(), rep.getLastSync());
+ validateFederationProviderConfig(session, auth, realm, tempModel);
+
UserFederationProviderModel model = realm.addUserFederationProvider(rep.getProviderName(), rep.getConfig(), rep.getPriority(), displayName,
rep.getFullSyncPeriod(), rep.getChangedSyncPeriod(), rep.getLastSync());
new UsersSyncManager().notifyToRefreshPeriodicSync(session, realm, model, false);
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
index e8e38ba..9b03535 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
@@ -36,6 +36,7 @@ import org.keycloak.models.GroupModel;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ModelDuplicateException;
+import org.keycloak.models.ModelException;
import org.keycloak.models.ModelReadOnlyException;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserConsentModel;
@@ -54,6 +55,7 @@ import org.keycloak.representations.idm.GroupRepresentation;
import org.keycloak.representations.idm.UserConsentRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.representations.idm.UserSessionRepresentation;
+import org.keycloak.services.ErrorResponseException;
import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.managers.ClientSessionCode;
import org.keycloak.services.managers.UserManager;
@@ -74,11 +76,14 @@ import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
import javax.ws.rs.WebApplicationException;
+import java.io.IOException;
import java.net.URI;
+import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -86,6 +91,7 @@ import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
+import java.util.Properties;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.keycloak.models.UsernameLoginFailureModel;
@@ -93,6 +99,10 @@ import org.keycloak.services.managers.BruteForceProtector;
import org.keycloak.services.managers.UserSessionManager;
import org.keycloak.services.resources.AccountService;
import org.keycloak.common.util.Time;
+import org.keycloak.services.validation.Validation;
+import org.keycloak.theme.Theme;
+import org.keycloak.theme.Theme.Type;
+import org.keycloak.theme.ThemeProvider;
/**
* Base resource for managing users
@@ -707,6 +717,9 @@ public class UsersResource {
if (pass == null || pass.getValue() == null || !CredentialRepresentation.PASSWORD.equals(pass.getType())) {
throw new BadRequestException("No password provided");
}
+ if (Validation.isBlank(pass.getValue())) {
+ throw new BadRequestException("Empty password not allowed");
+ }
UserCredentialModel cred = RepresentationToModel.convertCredential(pass);
try {
@@ -715,6 +728,10 @@ public class UsersResource {
throw new BadRequestException("Resetting to N old passwords is not allowed.");
} catch (ModelReadOnlyException mre) {
throw new BadRequestException("Can't reset password as account is read only");
+ } catch (ModelException e) {
+ Properties messages = AdminRoot.getMessages(session, realm, auth.getAuth().getToken().getLocale());
+ throw new ErrorResponseException(e.getMessage(), MessageFormat.format(messages.getProperty(e.getMessage(), e.getMessage()), e.getParameters()),
+ Status.BAD_REQUEST);
}
if (pass.isTemporary() != null && pass.isTemporary()) user.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
diff --git a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
index 0ef806b..4de67ed 100644
--- a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
+++ b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
@@ -25,6 +25,7 @@ import org.keycloak.Config;
import org.keycloak.exportimport.ExportImportManager;
import org.keycloak.migration.MigrationModelManager;
import org.keycloak.models.*;
+import org.keycloak.services.managers.DBLockManager;
import org.keycloak.models.utils.PostMigrationEvent;
import org.keycloak.models.utils.RepresentationToModel;
import org.keycloak.representations.idm.RealmRepresentation;
@@ -39,7 +40,6 @@ import org.keycloak.services.resources.admin.AdminRoot;
import org.keycloak.services.scheduled.ClearExpiredEvents;
import org.keycloak.services.scheduled.ClearExpiredUserSessions;
import org.keycloak.services.scheduled.ClusterAwareScheduledTaskRunner;
-import org.keycloak.services.scheduled.ScheduledTaskRunner;
import org.keycloak.services.util.JsonConfigProvider;
import org.keycloak.services.util.ObjectMapperResolver;
import org.keycloak.timer.TimerProvider;
@@ -82,7 +82,6 @@ public class KeycloakApplication extends Application {
singletons.add(new ServerVersionResource());
singletons.add(new RealmsResource());
singletons.add(new AdminRoot());
- singletons.add(new ModelExceptionMapper());
classes.add(QRCodeResource.class);
classes.add(ThemeResource.class);
classes.add(JsResource.class);
@@ -91,44 +90,56 @@ public class KeycloakApplication extends Application {
singletons.add(new ObjectMapperResolver(Boolean.parseBoolean(System.getProperty("keycloak.jsonPrettyPrint", "false"))));
- migrateModel();
-
- boolean bootstrapAdminUser = false;
-
- KeycloakSession session = sessionFactory.create();
ExportImportManager exportImportManager;
+
+ DBLockManager dbLockManager = new DBLockManager();
+ dbLockManager.checkForcedUnlock(sessionFactory);
+ dbLockManager.waitForLock(sessionFactory);
try {
- session.getTransaction().begin();
+ migrateModel();
- ApplianceBootstrap applianceBootstrap = new ApplianceBootstrap(session);
- exportImportManager = new ExportImportManager(session);
+ KeycloakSession session = sessionFactory.create();
+ try {
+ session.getTransaction().begin();
+
+ ApplianceBootstrap applianceBootstrap = new ApplianceBootstrap(session);
+ exportImportManager = new ExportImportManager(session);
- boolean createMasterRealm = applianceBootstrap.isNewInstall();
- if (exportImportManager.isRunImport() && exportImportManager.isImportMasterIncluded()) {
- createMasterRealm = false;
+ boolean createMasterRealm = applianceBootstrap.isNewInstall();
+ if (exportImportManager.isRunImport() && exportImportManager.isImportMasterIncluded()) {
+ createMasterRealm = false;
+ }
+
+ if (createMasterRealm) {
+ applianceBootstrap.createMasterRealm(contextPath);
+ }
+ session.getTransaction().commit();
+ } catch (RuntimeException re) {
+ if (session.getTransaction().isActive()) {
+ session.getTransaction().rollback();
+ }
+ throw re;
+ } finally {
+ session.close();
}
- if (createMasterRealm) {
- applianceBootstrap.createMasterRealm(contextPath);
+ if (exportImportManager.isRunImport()) {
+ exportImportManager.runImport();
+ } else {
+ importRealms();
}
- session.getTransaction().commit();
- } finally {
- session.close();
- }
- if (exportImportManager.isRunImport()) {
- exportImportManager.runImport();
- } else {
- importRealms();
+ importAddUser();
+ } finally {
+ dbLockManager.releaseLock(sessionFactory);
}
- importAddUser();
-
if (exportImportManager.isRunExport()) {
exportImportManager.runExport();
}
- session = sessionFactory.create();
+ boolean bootstrapAdminUser = false;
+ KeycloakSession session = sessionFactory.create();
try {
session.getTransaction().begin();
bootstrapAdminUser = new ApplianceBootstrap(session).isNoMasterUser();
@@ -154,6 +165,7 @@ public class KeycloakApplication extends Application {
} catch (Exception e) {
session.getTransaction().rollback();
logger.migrationFailure(e);
+ throw e;
} finally {
session.close();
}
diff --git a/services/src/main/java/org/keycloak/services/resources/WelcomeResource.java b/services/src/main/java/org/keycloak/services/resources/WelcomeResource.java
index e2ee402..23e9baa 100755
--- a/services/src/main/java/org/keycloak/services/resources/WelcomeResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/WelcomeResource.java
@@ -17,17 +17,27 @@
package org.keycloak.services.resources;
import org.keycloak.Config;
-import org.keycloak.theme.FreeMarkerUtil;
-import org.keycloak.theme.Theme;
-import org.keycloak.theme.ThemeProvider;
-import org.keycloak.models.KeycloakSession;
import org.keycloak.common.util.MimeTypeUtil;
+import org.keycloak.models.KeycloakSession;
import org.keycloak.services.ServicesLogger;
import org.keycloak.services.managers.ApplianceBootstrap;
import org.keycloak.services.util.CacheControlUtil;
-
-import javax.ws.rs.*;
-import javax.ws.rs.core.*;
+import org.keycloak.theme.FreeMarkerUtil;
+import org.keycloak.theme.Theme;
+import org.keycloak.theme.ThemeProvider;
+import org.keycloak.utils.MediaType;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
@@ -64,7 +74,7 @@ public class WelcomeResource {
* @throws URISyntaxException
*/
@GET
- @Produces("text/html")
+ @Produces(MediaType.TEXT_HTML_UTF_8)
public Response getWelcomePage() throws URISyntaxException {
checkBootstrap();
@@ -127,7 +137,7 @@ public class WelcomeResource {
*/
@GET
@Path("/welcome-content/{path}")
- @Produces("text/html")
+ @Produces(MediaType.TEXT_HTML_UTF_8)
public Response getResource(@PathParam("path") String path) {
try {
InputStream resource = getTheme().getResourceAsStream(path);
diff --git a/services/src/main/java/org/keycloak/services/ServicesLogger.java b/services/src/main/java/org/keycloak/services/ServicesLogger.java
index 0927f38..8af67e5 100644
--- a/services/src/main/java/org/keycloak/services/ServicesLogger.java
+++ b/services/src/main/java/org/keycloak/services/ServicesLogger.java
@@ -401,4 +401,8 @@ public interface ServicesLogger extends BasicLogger {
@LogMessage(level = ERROR)
@Message(id=90, value="Failed to close ProviderSession")
void failedToCloseProviderSession(@Cause Throwable t);
+
+ @LogMessage(level = WARN)
+ @Message(id=91, value="Forced release of DB lock at startup requested by System property. Make sure to not use this in production environment! And especially when more cluster nodes are started concurrently.")
+ void forcedReleaseDBLock();
}
diff --git a/services/src/main/java/org/keycloak/services/util/LocaleHelper.java b/services/src/main/java/org/keycloak/services/util/LocaleHelper.java
index 63f38d0..1871193 100755
--- a/services/src/main/java/org/keycloak/services/util/LocaleHelper.java
+++ b/services/src/main/java/org/keycloak/services/util/LocaleHelper.java
@@ -61,16 +61,6 @@ public class LocaleHelper {
}
}
- // User profile
- if (user != null && user.getAttributes().containsKey(UserModel.LOCALE)) {
- String localeString = user.getFirstAttribute(UserModel.LOCALE);
- Locale locale = findLocale(realm.getSupportedLocales(), localeString);
- if (locale != null) {
- updateLocaleCookie(session, realm, localeString);
- return locale;
- }
- }
-
// Locale cookie
if (httpHeaders != null && httpHeaders.getCookies().containsKey(LOCALE_COOKIE)) {
String localeString = httpHeaders.getCookies().get(LOCALE_COOKIE).getValue();
@@ -83,6 +73,16 @@ public class LocaleHelper {
}
}
+ // User profile
+ if (user != null && user.getAttributes().containsKey(UserModel.LOCALE)) {
+ String localeString = user.getFirstAttribute(UserModel.LOCALE);
+ Locale locale = findLocale(realm.getSupportedLocales(), localeString);
+ if (locale != null) {
+ updateLocaleCookie(session, realm, localeString);
+ return locale;
+ }
+ }
+
// ui_locales query parameter
if (uriInfo != null && uriInfo.getQueryParameters().containsKey(UI_LOCALES_PARAM)) {
String localeString = uriInfo.getQueryParameters().getFirst(UI_LOCALES_PARAM);
diff --git a/services/src/main/java/org/keycloak/theme/BrowserSecurityHeaderSetup.java b/services/src/main/java/org/keycloak/theme/BrowserSecurityHeaderSetup.java
index 41fc584..dfcbf50 100755
--- a/services/src/main/java/org/keycloak/theme/BrowserSecurityHeaderSetup.java
+++ b/services/src/main/java/org/keycloak/theme/BrowserSecurityHeaderSetup.java
@@ -32,8 +32,9 @@ public class BrowserSecurityHeaderSetup {
public static Response.ResponseBuilder headers(Response.ResponseBuilder builder, RealmModel realm) {
for (Map.Entry<String, String> entry : realm.getBrowserSecurityHeaders().entrySet()) {
String headerName = BrowserSecurityHeaders.headerAttributeMap.get(entry.getKey());
- if (headerName == null) continue;
- builder.header(headerName, entry.getValue());
+ if (headerName != null && entry.getValue() != null && entry.getValue().length() > 0) {
+ builder.header(headerName, entry.getValue());
+ }
}
return builder;
}
diff --git a/services/src/main/java/org/keycloak/utils/MediaType.java b/services/src/main/java/org/keycloak/utils/MediaType.java
new file mode 100644
index 0000000..31ab972
--- /dev/null
+++ b/services/src/main/java/org/keycloak/utils/MediaType.java
@@ -0,0 +1,34 @@
+/*
+ * 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.utils;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class MediaType {
+
+ public static final String TEXT_HTML_UTF_8 = "text/html; charset=utf-8";
+ public static final javax.ws.rs.core.MediaType TEXT_HTML_UTF_8_TYPE = new javax.ws.rs.core.MediaType("text", "html", "utf-8");
+
+ public static final String APPLICATION_JSON = javax.ws.rs.core.MediaType.APPLICATION_JSON;
+ public static final javax.ws.rs.core.MediaType APPLICATION_JSON_TYPE = javax.ws.rs.core.MediaType.APPLICATION_JSON_TYPE;
+
+ public static final String APPLICATION_FORM_URLENCODED = javax.ws.rs.core.MediaType.APPLICATION_FORM_URLENCODED;
+ public static final javax.ws.rs.core.MediaType APPLICATION_FORM_URLENCODED_TYPE = javax.ws.rs.core.MediaType.APPLICATION_FORM_URLENCODED_TYPE;
+
+}
diff --git a/services/src/main/resources/META-INF/services/org.keycloak.provider.Spi b/services/src/main/resources/META-INF/services/org.keycloak.provider.Spi
index 7143e6c..50bb346 100755
--- a/services/src/main/resources/META-INF/services/org.keycloak.provider.Spi
+++ b/services/src/main/resources/META-INF/services/org.keycloak.provider.Spi
@@ -17,5 +17,4 @@
org.keycloak.exportimport.ClientDescriptionConverterSpi
org.keycloak.wellknown.WellKnownSpi
-org.keycloak.messages.MessagesSpi
org.keycloak.services.clientregistration.ClientRegistrationSpi
diff --git a/services/src/test/java/org/keycloak/test/broker/saml/SAMLParsingTest.java b/services/src/test/java/org/keycloak/test/broker/saml/SAMLParsingTest.java
new file mode 100644
index 0000000..d26895a
--- /dev/null
+++ b/services/src/test/java/org/keycloak/test/broker/saml/SAMLParsingTest.java
@@ -0,0 +1,41 @@
+/*
+ * 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.test.broker.saml;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.keycloak.saml.SAMLRequestParser;
+import org.keycloak.saml.processing.core.saml.v2.common.SAMLDocumentHolder;
+import org.keycloak.saml.processing.web.util.PostBindingUtil;
+
+/**
+ * This was failing on IBM JDK
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class SAMLParsingTest {
+
+ private static final String SAML_RESPONSE = "PHNhbWxwOkxvZ291dFJlc3BvbnNlIHhtbG5zOnNhbWxwPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2wiIHhtbG5zPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXNzZXJ0aW9uIiBEZXN0aW5hdGlvbj0iaHR0cDovL2xvY2FsaG9zdDo4MDgxL2F1dGgvcmVhbG1zL3JlYWxtLXdpdGgtYnJva2VyL2Jyb2tlci9rYy1zYW1sLWlkcC1iYXNpYy9lbmRwb2ludCIgSUQ9IklEXzlhMTcxZDIzLWM0MTctNDJmNS05YmNhLWMwOTMxMjNmZDY4YyIgSW5SZXNwb25zZVRvPSJJRF9iYzczMDcxMS0yMDM3LTQzZjMtYWQ3Ni03YmMzMzg0MmZiODciIElzc3VlSW5zdGFudD0iMjAxNi0wMi0yOVQxMjowMDoxNC4wNDRaIiBWZXJzaW9uPSIyLjAiPjxzYW1sOklzc3VlciB4bWxuczpzYW1sPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXNzZXJ0aW9uIj5odHRwOi8vbG9jYWxob3N0OjgwODIvYXV0aC9yZWFsbXMvcmVhbG0td2l0aC1zYW1sLWlkcC1iYXNpYzwvc2FtbDpJc3N1ZXI+PHNhbWxwOlN0YXR1cz48c2FtbHA6U3RhdHVzQ29kZSBWYWx1ZT0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnN0YXR1czpTdWNjZXNzIi8+PC9zYW1scDpTdGF0dXM+PC9zYW1scDpMb2dvdXRSZXNwb25zZT4=";
+
+ @Test
+ public void parseTest() throws Exception {
+ byte[] samlBytes = PostBindingUtil.base64Decode(SAML_RESPONSE);
+ SAMLDocumentHolder holder = SAMLRequestParser.parseResponseDocument(samlBytes);
+ Assert.assertNotNull(holder);
+ }
+}
testsuite/docker-cluster/pom.xml 2(+1 -1)
diff --git a/testsuite/docker-cluster/pom.xml b/testsuite/docker-cluster/pom.xml
index 30a6a56..1f67e12 100755
--- a/testsuite/docker-cluster/pom.xml
+++ b/testsuite/docker-cluster/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-testsuite-pom</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
testsuite/integration/pom.xml 47(+11 -36)
diff --git a/testsuite/integration/pom.xml b/testsuite/integration/pom.xml
index 531a2ae..8887ccf 100755
--- a/testsuite/integration/pom.xml
+++ b/testsuite/integration/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-testsuite-pom</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -237,6 +237,16 @@
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>mysql</groupId>
+ <artifactId>mysql-connector-java</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.postgresql</groupId>
+ <artifactId>postgresql</artifactId>
+ <version>${postgresql.version}</version>
+ </dependency>
+
</dependencies>
<build>
<plugins>
@@ -468,41 +478,6 @@
</profile>
- <!-- MySQL -->
- <profile>
- <activation>
- <property>
- <name>keycloak.connectionsJpa.driver</name>
- <value>com.mysql.jdbc.Driver</value>
- </property>
- </activation>
- <id>mysql</id>
- <dependencies>
- <dependency>
- <groupId>mysql</groupId>
- <artifactId>mysql-connector-java</artifactId>
- </dependency>
- </dependencies>
- </profile>
-
- <!-- PostgreSQL -->
- <profile>
- <activation>
- <property>
- <name>keycloak.connectionsJpa.driver</name>
- <value>org.postgresql.Driver</value>
- </property>
- </activation>
- <id>postgresql</id>
- <dependencies>
- <dependency>
- <groupId>org.postgresql</groupId>
- <artifactId>postgresql</artifactId>
- <version>${postgresql.version}</version>
- </dependency>
- </dependencies>
- </profile>
-
<profile>
<id>clean-jpa</id>
<build>
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java
index f03b55a..7097588 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java
@@ -59,6 +59,7 @@ import java.net.URI;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
+import org.keycloak.representations.idm.UserRepresentation;
/**
* Tests Undertow Adapter
@@ -138,6 +139,12 @@ public class AdapterTestStrategy extends ExternalResource {
String pageSource = driver.getPageSource();
System.out.println(pageSource);
Assert.assertTrue(pageSource.contains("parameter=hello"));
+ // test that user principal and KeycloakSecurityContext available
+ driver.navigate().to(APP_SERVER_BASE_URL + "/input-portal/insecure");
+ System.out.println("insecure: ");
+ System.out.println(driver.getPageSource());
+ Assert.assertTrue(driver.getPageSource().contains("Insecure Page"));
+ if (System.getProperty("insecure.user.principal.unsupported") == null) Assert.assertTrue(driver.getPageSource().contains("UserPrincipal"));
// test logout
@@ -592,7 +599,8 @@ public class AdapterTestStrategy extends ExternalResource {
// logout mposolda with admin client
Keycloak keycloakAdmin = Keycloak.getInstance(AUTH_SERVER_URL, "master", "admin", "admin", Constants.ADMIN_CLI_CLIENT_ID);
- ApiUtil.findClientByClientId(keycloakAdmin.realm("demo"), "session-portal").logoutUser("mposolda");
+ UserRepresentation mposolda = keycloakAdmin.realm("demo").users().search("mposolda", null, null, null, null, null).get(0);
+ keycloakAdmin.realm("demo").users().get(mposolda.getId()).logout();
// bburke should be still logged with original httpSession in our browser window
driver.navigate().to(APP_SERVER_BASE_URL + "/session-portal");
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/FilterAdapterTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/FilterAdapterTest.java
index 9637617..a533133 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/FilterAdapterTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/FilterAdapterTest.java
@@ -100,6 +100,7 @@ public class FilterAdapterTest {
@Test
public void testSavedPostRequest() throws Exception {
+ System.setProperty("insecure.user.principal.unsupported", "true");
testStrategy.testSavedPostRequest();
}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/InputServlet.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/InputServlet.java
index c0135ef..b6a0bd5 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/InputServlet.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/InputServlet.java
@@ -17,6 +17,9 @@
package org.keycloak.testsuite.adapter;
+import org.junit.Assert;
+import org.keycloak.KeycloakSecurityContext;
+
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
@@ -35,6 +38,17 @@ public class InputServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String appBase = System.getProperty("app.server.base.url", "http://localhost:8081");
+ if (req.getRequestURI().endsWith("insecure")) {
+ if (System.getProperty("insecure.user.principal.unsupported") == null) Assert.assertNotNull(req.getUserPrincipal());
+ if (System.getProperty("insecure.user.principal.unsupported") == null) Assert.assertNotNull(req.getAttribute(KeycloakSecurityContext.class.getName()));
+ resp.setContentType("text/html");
+ PrintWriter pw = resp.getWriter();
+ pw.printf("<html><head><title>%s</title></head><body>", "Insecure Page");
+ if (req.getUserPrincipal() != null) pw.printf("UserPrincipal: " + req.getUserPrincipal().getName());
+ pw.print("</body></html>");
+ pw.flush();
+ return;
+ }
String actionUrl = appBase + "/input-portal/secured/post";
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/ClusteredConcurrencyTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/ClusteredConcurrencyTest.java
new file mode 100755
index 0000000..7073797
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/ClusteredConcurrencyTest.java
@@ -0,0 +1,212 @@
+/*
+ * 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.admin;
+
+import org.jboss.logging.Logger;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.keycloak.admin.client.Keycloak;
+import org.keycloak.admin.client.resource.ClientResource;
+import org.keycloak.admin.client.resource.RealmResource;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.RealmModel;
+import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.representations.idm.GroupRepresentation;
+import org.keycloak.representations.idm.RoleRepresentation;
+import org.keycloak.services.DefaultKeycloakSessionFactory;
+import org.keycloak.services.resources.KeycloakApplication;
+
+import javax.ws.rs.NotFoundException;
+import javax.ws.rs.core.Response;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+@Ignore
+public class ClusteredConcurrencyTest {
+
+ private static final Logger log = Logger.getLogger(ClusteredConcurrencyTest.class);
+
+ private static final int DEFAULT_THREADS = 10;
+ private static final int DEFAULT_ITERATIONS = 100;
+
+ // If enabled only one request is allowed at the time. Useful for checking that test is working.
+ private static final boolean SYNCHRONIZED = false;
+
+ boolean passedCreateClient = false;
+ boolean passedCreateRole = false;
+
+ public static DefaultKeycloakSessionFactory node1factory;
+ public static DefaultKeycloakSessionFactory node2factory;
+ public static DefaultKeycloakSessionFactory[] nodes = new DefaultKeycloakSessionFactory[2];
+
+ @BeforeClass
+ public static void initKeycloak() throws Exception {
+ System.setProperty("keycloak.connectionsInfinispan.clustered", "true");
+ System.setProperty("keycloak.connectionsInfinispan.async", "false");
+ KeycloakApplication.loadConfig();
+ node1factory = new DefaultKeycloakSessionFactory();
+ node1factory.init();
+ nodes[0] = node1factory;
+ node2factory = new DefaultKeycloakSessionFactory();
+ node2factory.init();
+ nodes[1] = node2factory;
+
+ KeycloakSession session = nodes[0].create();
+ session.getTransaction().begin();
+ session.realms().createRealm("testrealm");
+ session.getTransaction().commit();
+
+ session = nodes[1].create();
+ session.getTransaction().begin();
+ RealmModel realm = session.realms().getRealmByName("testrealm");
+ Assert.assertNotNull(realm);
+ session.getTransaction().commit();
+
+ }
+
+ @Test
+ public void createClient() throws Throwable {
+ System.out.println("***************************");
+ long start = System.currentTimeMillis();
+ run(new KeycloakRunnable() {
+ @Override
+ public void run(int threadNum, int iterationNum) {
+ String name = "c-" + threadNum + "-" + iterationNum;
+ int node1 = threadNum % 2;
+ int node2 = 0;
+ if (node1 == 0) node2 = 1;
+
+ String id = null;
+ {
+ KeycloakSession session = nodes[node1].create();
+ session.getTransaction().begin();
+ RealmModel realm = session.realms().getRealmByName("testrealm");
+ ClientModel client = realm.addClient(name);
+ id = client.getId();
+ session.getTransaction().commit();
+ }
+ {
+ KeycloakSession session = nodes[node2].create();
+ session.getTransaction().begin();
+ RealmModel realm = session.realms().getRealmByName("testrealm");
+ boolean found = false;
+ for (ClientModel client : realm.getClients()) {
+ if (client.getId().equals(id)) {
+ found = true;
+ }
+ }
+ session.getTransaction().commit();
+ if (!found) {
+ fail("Client " + name + " not found in client list");
+ }
+ }
+ {
+ KeycloakSession session = nodes[node1].create();
+ session.getTransaction().begin();
+ RealmModel realm = session.realms().getRealmByName("testrealm");
+ boolean found = false;
+ for (ClientModel client : realm.getClients()) {
+ if (client.getId().equals(id)) {
+ found = true;
+ }
+ }
+ session.getTransaction().commit();
+ if (!found) {
+ fail("Client " + name + " not found in client list");
+ }
+ }
+ }
+ });
+ long end = System.currentTimeMillis() - start;
+ System.out.println("createClient took " + end);
+
+ }
+
+ private void run(final KeycloakRunnable runnable) throws Throwable {
+ run(runnable, DEFAULT_THREADS, DEFAULT_ITERATIONS);
+ }
+
+ private void run(final KeycloakRunnable runnable, final int numThreads, final int numIterationsPerThread) throws Throwable {
+ final CountDownLatch latch = new CountDownLatch(numThreads);
+ final AtomicReference<Throwable> failed = new AtomicReference();
+ final List<Thread> threads = new LinkedList<>();
+ final Lock lock = SYNCHRONIZED ? new ReentrantLock() : null;
+
+ for (int t = 0; t < numThreads; t++) {
+ final int threadNum = t;
+ Thread thread = new Thread() {
+ @Override
+ public void run() {
+ try {
+ if (lock != null) {
+ lock.lock();
+ }
+
+ for (int i = 0; i < numIterationsPerThread && latch.getCount() > 0; i++) {
+ log.infov("thread {0}, iteration {1}", threadNum, i);
+ runnable.run(threadNum, i);
+ }
+ latch.countDown();
+ } catch (Throwable t) {
+ failed.compareAndSet(null, t);
+ while (latch.getCount() > 0) {
+ latch.countDown();
+ }
+ } finally {
+ if (lock != null) {
+ lock.unlock();
+ }
+ }
+ }
+ };
+ thread.start();
+ threads.add(thread);
+ }
+
+ latch.await();
+
+ for (Thread t : threads) {
+ t.join();
+ }
+
+ if (failed.get() != null) {
+ throw failed.get();
+ }
+ }
+
+ interface KeycloakRunnable {
+
+ void run(int threadNum, int iterationNum);
+
+ }
+
+}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/ConcurrencyTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/ConcurrencyTest.java
index 9adebd3..daaf5a7 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/ConcurrencyTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/ConcurrencyTest.java
@@ -25,6 +25,7 @@ import org.keycloak.admin.client.Keycloak;
import org.keycloak.admin.client.resource.ClientResource;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.representations.idm.GroupRepresentation;
import org.keycloak.representations.idm.RoleRepresentation;
import javax.ws.rs.NotFoundException;
@@ -123,6 +124,39 @@ public class ConcurrencyTest extends AbstractClientTest {
}
@Test
+ public void createGroup() throws Throwable {
+ System.out.println("***************************");
+ long start = System.currentTimeMillis();
+ run(new KeycloakRunnable() {
+ @Override
+ public void run(Keycloak keycloak, RealmResource realm, int threadNum, int iterationNum) {
+ String name = "c-" + threadNum + "-" + iterationNum;
+ GroupRepresentation c = new GroupRepresentation();
+ c.setName(name);
+ Response response = realm.groups().add(c);
+ String id = ApiUtil.getCreatedId(response);
+ response.close();
+
+ c = realm.groups().group(id).toRepresentation();
+ assertNotNull(c);
+ boolean found = false;
+ for (GroupRepresentation r : realm.groups().groups()) {
+ if (r.getName().equals(name)) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ fail("Group " + name + " not found in group list");
+ }
+ }
+ });
+ long end = System.currentTimeMillis() - start;
+ System.out.println("createGroup took " + end);
+
+ }
+
+ @Test
@Ignore
public void createRemoveClient() throws Throwable {
// FYI< this will fail as HSQL seems to be trying to perform table locks.
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/RealmTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/RealmTest.java
index 156835a..511e6b5 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/RealmTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/RealmTest.java
@@ -25,6 +25,7 @@ import org.keycloak.admin.client.resource.ServerInfoResource;
import org.keycloak.models.Constants;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
+import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.RoleRepresentation;
@@ -32,6 +33,7 @@ import org.keycloak.services.managers.RealmManager;
import org.keycloak.testsuite.KeycloakServer;
import org.keycloak.util.JsonSerialization;
+import javax.ws.rs.BadRequestException;
import javax.ws.rs.NotFoundException;
import java.io.IOException;
import java.util.Collections;
@@ -51,6 +53,10 @@ import static org.junit.Assert.fail;
*/
public class RealmTest extends AbstractClientTest {
+ public static final String PRIVATE_KEY = "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y=";
+ public static final String PUBLIC_KEY = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB";
+ public static final String CERTIFICATE = "MIICsTCCAZkCBgFTLB5bhDANBgkqhkiG9w0BAQsFADAcMRowGAYDVQQDDBFhZG1pbi1jbGllbnQtdGVzdDAeFw0xNjAyMjkwODIwMDBaFw0yNjAyMjgwODIxNDBaMBwxGjAYBgNVBAMMEWFkbWluLWNsaWVudC10ZXN0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAquzJtpAlpTFnJzILjTOHW+SOWav1eIsCtlAqiFTvBskbod6b4BtVaR3FVrQm8rFiwDOIEWT3IG3ZIz0LKYxnqvuffyLHGHjiroqrR63kY9Wa9B790lSEWVaGeNOMnKleqKu5QUNfL3wVebUh/C/QfxZ29R1EIbxNe2ThN8yuIca8Ltn43D5VlyatptojffxpCYiYqAmIwQDaq1um2cQ+4rPBLxC5jM9UBvYOMUP4u0caNSaPI1o9lHVKgTtWcdQzUeMmAGsnLV26XGhA/OwRduUxksumR1kh/KSqowasjgSrpVqtF/uo5TY57s7drD+zKG58cdHLreclB9AQNvNwZwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBh4iwg8GnadeQP52pV5vKJ4Z8A1R2aYCzoW7Lc3FI/pXWX9Af5dKILX5O2j/daamPS+WtDWxIuwvZC5drrkvJn/r8e4KstnXQzPQggIJbI9v3wfIX3VlFvwvZVGiuE5PSLSWb0L57PEojZVpIU5bLchq4yRSD2zK4dWX8Y6I/D40a74KDvPOlEL8405/T1iW7ytKT9awNJW04N91owoI+kdUL+DMnnGzIxDAoYAeZI/1vcwoaH24zyTLGItkzpKxqLOdB05cnxn5jCWY2Hyd1zqtRkadhgZaqu4lcDHAHEMDp6dEjLZW8ym8bnlto+MD2y//CsyPCzyCLlA726vrli";
+
@Test
public void getRealms() {
List<RealmRepresentation> realms = keycloak.realms().findAll();
@@ -331,4 +337,80 @@ public class RealmTest extends AbstractClientTest {
}
+ @Test
+ public void uploadRealmKeys() throws Exception {
+ String originalPublicKey = realm.toRepresentation().getPublicKey();
+
+ RealmRepresentation rep = new RealmRepresentation();
+ rep.setPrivateKey("INVALID");
+ rep.setPublicKey(PUBLIC_KEY);
+
+ try {
+ realm.update(rep);
+ fail("Expected BadRequestException");
+ } catch (BadRequestException e) {
+ }
+
+ rep.setPrivateKey(PRIVATE_KEY);
+ rep.setPublicKey("INVALID");
+
+ try {
+ realm.update(rep);
+ fail("Expected BadRequestException");
+ } catch (BadRequestException e) {
+ }
+
+ assertEquals(originalPublicKey, realm.toRepresentation().getPublicKey());
+
+ rep.setPublicKey(PUBLIC_KEY);
+ realm.update(rep);
+
+ assertEquals(PUBLIC_KEY, rep.getPublicKey());
+
+ String privateKey2048 = IOUtils.toString(getClass().getResourceAsStream("/keys/private2048.pem"));
+ String publicKey2048 = IOUtils.toString(getClass().getResourceAsStream("/keys/public2048.pem"));
+
+ rep.setPrivateKey(privateKey2048);
+
+ try {
+ realm.update(rep);
+ fail("Expected BadRequestException");
+ } catch (BadRequestException e) {
+ }
+
+ assertEquals(PUBLIC_KEY, realm.toRepresentation().getPublicKey());
+
+ rep.setPublicKey(publicKey2048);
+
+ realm.update(rep);
+
+ assertEquals(publicKey2048, realm.toRepresentation().getPublicKey());
+
+ String privateKey4096 = IOUtils.toString(getClass().getResourceAsStream("/keys/private4096.pem"));
+ String publicKey4096 = IOUtils.toString(getClass().getResourceAsStream("/keys/public4096.pem"));
+ rep.setPrivateKey(privateKey4096);
+ rep.setPublicKey(publicKey4096);
+
+ realm.update(rep);
+
+ assertEquals(publicKey4096, realm.toRepresentation().getPublicKey());
+ }
+
+ @Test
+ public void uploadCertificate() throws IOException {
+ RealmRepresentation rep = new RealmRepresentation();
+ rep.setCertificate(CERTIFICATE);
+
+ realm.update(rep);
+
+ assertEquals(CERTIFICATE, rep.getCertificate());
+
+ String certificate = IOUtils.toString(getClass().getResourceAsStream("/keys/certificate.pem"));
+ rep.setCertificate(certificate);
+
+ realm.update(rep);
+
+ assertEquals(certificate, rep.getCertificate());
+ }
+
}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/UserTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/UserTest.java
index 07ed389..b38b362 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/UserTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/UserTest.java
@@ -646,6 +646,23 @@ public class UserTest extends AbstractClientTest {
assertEquals("Keycloak Account Management", driver.getTitle());
}
+ @Test
+ public void resetUserInvalidPassword() {
+ String userId = createUser("user1", "user1@localhost");
+
+ try {
+ CredentialRepresentation cred = new CredentialRepresentation();
+ cred.setType(CredentialRepresentation.PASSWORD);
+ cred.setValue(" ");
+ cred.setTemporary(false);
+ realm.users().get(userId).resetPassword(cred);
+ fail("Expected failure");
+ } catch (ClientErrorException e) {
+ assertEquals(400, e.getResponse().getStatus());
+ e.getResponse().close();
+ }
+ }
+
private void switchEditUsernameAllowedOn() {
RealmRepresentation rep = realm.toRepresentation();
rep.setEditUsernameAllowed(true);
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java
index 241116e..51a9044 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java
@@ -38,6 +38,7 @@ import org.keycloak.testsuite.broker.util.UserSessionStatusServlet;
import org.keycloak.testsuite.broker.util.UserSessionStatusServlet.UserSessionStatus;
import org.keycloak.testsuite.pages.AccountFederatedIdentityPage;
import org.keycloak.testsuite.pages.AccountPasswordPage;
+import org.keycloak.testsuite.pages.AccountUpdateProfilePage;
import org.keycloak.testsuite.pages.LoginPage;
import org.keycloak.testsuite.pages.LoginUpdateProfilePage;
import org.keycloak.testsuite.pages.OAuthGrantPage;
@@ -104,6 +105,9 @@ public abstract class AbstractIdentityProviderTest {
protected OAuthGrantPage grantPage;
@WebResource
+ AccountUpdateProfilePage accountUpdateProfilePage;
+
+ @WebResource
protected AccountPasswordPage changePasswordPage;
@WebResource
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractKeycloakIdentityProviderTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractKeycloakIdentityProviderTest.java
index 402f91e..76911d2 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractKeycloakIdentityProviderTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractKeycloakIdentityProviderTest.java
@@ -19,6 +19,7 @@ package org.keycloak.testsuite.broker;
import java.io.IOException;
import java.net.URI;
+import java.util.HashMap;
import java.util.Set;
import javax.mail.MessagingException;
@@ -39,9 +40,11 @@ import org.keycloak.models.FederatedIdentityModel;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
+import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.UserModel;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.services.Urls;
+import org.keycloak.testsuite.DummyUserFederationProviderFactory;
import org.keycloak.testsuite.broker.util.UserSessionStatusServlet;
import org.openqa.selenium.By;
import org.openqa.selenium.NoSuchElementException;
@@ -461,74 +464,146 @@ public abstract class AbstractKeycloakIdentityProviderTest extends AbstractIdent
setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_ON);
IdentityProviderModel identityProviderModel = getIdentityProviderModel();
- identityProviderModel.setStoreToken(true);
+ setStoreToken(identityProviderModel, true);
+ try {
+ authenticateWithIdentityProvider(identityProviderModel, "test-user", true);
- authenticateWithIdentityProvider(identityProviderModel, "test-user", true);
+ brokerServerRule.stopSession(session, true);
+ session = brokerServerRule.startSession();
- brokerServerRule.stopSession(session, true);
- session = brokerServerRule.startSession();
+ UserModel federatedUser = getFederatedUser();
+ RealmModel realm = getRealm();
+ Set<FederatedIdentityModel> federatedIdentities = this.session.users().getFederatedIdentities(federatedUser, realm);
- UserModel federatedUser = getFederatedUser();
- RealmModel realm = getRealm();
- Set<FederatedIdentityModel> federatedIdentities = this.session.users().getFederatedIdentities(federatedUser, realm);
+ assertFalse(federatedIdentities.isEmpty());
+ assertEquals(1, federatedIdentities.size());
- assertFalse(federatedIdentities.isEmpty());
- assertEquals(1, federatedIdentities.size());
+ FederatedIdentityModel identityModel = federatedIdentities.iterator().next();
- FederatedIdentityModel identityModel = federatedIdentities.iterator().next();
-
- assertNotNull(identityModel.getToken());
-
- UserSessionStatusServlet.UserSessionStatus userSessionStatus = retrieveSessionStatus();
- String accessToken = userSessionStatus.getAccessTokenString();
- URI tokenEndpointUrl = Urls.identityProviderRetrieveToken(BASE_URI, getProviderId(), realm.getName());
- final String authHeader = "Bearer " + accessToken;
- ClientRequestFilter authFilter = new ClientRequestFilter() {
- @Override
- public void filter(ClientRequestContext requestContext) throws IOException {
- requestContext.getHeaders().add(HttpHeaders.AUTHORIZATION, authHeader);
- }
- };
- Client client = ClientBuilder.newBuilder().register(authFilter).build();
- WebTarget tokenEndpoint = client.target(tokenEndpointUrl);
- Response response = tokenEndpoint.request().get();
- assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
- assertNotNull(response.readEntity(String.class));
- revokeGrant();
+ assertNotNull(identityModel.getToken());
+ UserSessionStatusServlet.UserSessionStatus userSessionStatus = retrieveSessionStatus();
+ String accessToken = userSessionStatus.getAccessTokenString();
+ URI tokenEndpointUrl = Urls.identityProviderRetrieveToken(BASE_URI, getProviderId(), realm.getName());
+ final String authHeader = "Bearer " + accessToken;
+ ClientRequestFilter authFilter = new ClientRequestFilter() {
+ @Override
+ public void filter(ClientRequestContext requestContext) throws IOException {
+ requestContext.getHeaders().add(HttpHeaders.AUTHORIZATION, authHeader);
+ }
+ };
+ Client client = ClientBuilder.newBuilder().register(authFilter).build();
+ WebTarget tokenEndpoint = client.target(tokenEndpointUrl);
+ Response response = tokenEndpoint.request().get();
+ assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
+ assertNotNull(response.readEntity(String.class));
+ revokeGrant();
- driver.navigate().to("http://localhost:8081/test-app/logout");
- String currentUrl = this.driver.getCurrentUrl();
- System.out.println("after logout currentUrl: " + currentUrl);
- assertTrue(currentUrl.startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth"));
-
- unconfigureUserRetrieveToken("test-user");
- loginIDP("test-user");
- //authenticateWithIdentityProvider(identityProviderModel, "test-user");
- assertEquals("http://localhost:8081/test-app", driver.getCurrentUrl());
-
- userSessionStatus = retrieveSessionStatus();
- accessToken = userSessionStatus.getAccessTokenString();
- final String authHeader2 = "Bearer " + accessToken;
- ClientRequestFilter authFilter2 = new ClientRequestFilter() {
- @Override
- public void filter(ClientRequestContext requestContext) throws IOException {
- requestContext.getHeaders().add(HttpHeaders.AUTHORIZATION, authHeader2);
- }
- };
- client = ClientBuilder.newBuilder().register(authFilter2).build();
- tokenEndpoint = client.target(tokenEndpointUrl);
- response = tokenEndpoint.request().get();
-
- assertEquals(Response.Status.FORBIDDEN.getStatusCode(), response.getStatus());
- revokeGrant();
- driver.navigate().to("http://localhost:8081/test-app/logout");
- driver.navigate().to("http://localhost:8081/test-app");
+ driver.navigate().to("http://localhost:8081/test-app/logout");
+ String currentUrl = this.driver.getCurrentUrl();
+ System.out.println("after logout currentUrl: " + currentUrl);
+ assertTrue(currentUrl.startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth"));
+
+ unconfigureUserRetrieveToken("test-user");
+ loginIDP("test-user");
+ //authenticateWithIdentityProvider(identityProviderModel, "test-user");
+ assertEquals("http://localhost:8081/test-app", driver.getCurrentUrl());
+
+ userSessionStatus = retrieveSessionStatus();
+ accessToken = userSessionStatus.getAccessTokenString();
+ final String authHeader2 = "Bearer " + accessToken;
+ ClientRequestFilter authFilter2 = new ClientRequestFilter() {
+ @Override
+ public void filter(ClientRequestContext requestContext) throws IOException {
+ requestContext.getHeaders().add(HttpHeaders.AUTHORIZATION, authHeader2);
+ }
+ };
+ client = ClientBuilder.newBuilder().register(authFilter2).build();
+ tokenEndpoint = client.target(tokenEndpointUrl);
+ response = tokenEndpoint.request().get();
+
+ assertEquals(Response.Status.FORBIDDEN.getStatusCode(), response.getStatus());
+
+ revokeGrant();
+ driver.navigate().to("http://localhost:8081/test-app/logout");
+ driver.navigate().to("http://localhost:8081/test-app");
- assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth"));
+ assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth"));
+ } finally {
+ setStoreToken(identityProviderModel, false);
+ }
+ }
+
+ private void setStoreToken(IdentityProviderModel identityProviderModel, boolean storeToken) {
+ identityProviderModel.setStoreToken(storeToken);
+ getRealm().updateIdentityProvider(identityProviderModel);
+
+ brokerServerRule.stopSession(session, storeToken);
+ session = brokerServerRule.startSession();
}
protected abstract void doAssertTokenRetrieval(String pageSource);
+ @Test
+ public void testWithLinkedFederationProvider() throws Exception {
+ setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_OFF);
+
+ // Add federationProvider to realm. It's configured with sync registrations
+ RealmModel realm = getRealm();
+ UserFederationProviderModel dummyModel = realm.addUserFederationProvider(DummyUserFederationProviderFactory.PROVIDER_NAME, new HashMap<String, String>(), 1, "test-dummy", -1, -1, 0);
+
+ brokerServerRule.stopSession(session, true);
+ session = brokerServerRule.startSession();
+
+ try {
+ // Login as user "test-user" to account management.
+ authenticateWithIdentityProvider(getIdentityProviderModel(), "test-user", false);
+ changePasswordPage.realm("realm-with-broker");
+ changePasswordPage.open();
+ assertTrue(changePasswordPage.isCurrent());
+
+ // Assert changing password with old password "secret" as this is the password from federationProvider (See DummyUserFederationProvider)
+ changePasswordPage.changePassword("new-password", "new-password");
+ Assert.assertEquals("Please specify password.", accountUpdateProfilePage.getError());
+
+ changePasswordPage.changePassword("bad", "new-password", "new-password");
+ Assert.assertEquals("Invalid existing password.", accountUpdateProfilePage.getError());
+
+ changePasswordPage.changePassword("secret", "new-password", "new-password");
+ Assert.assertEquals("Your password has been updated.", accountUpdateProfilePage.getSuccess());
+
+ // Logout
+ driver.navigate().to("http://localhost:8081/test-app/logout");
+
+
+ // Login as user "test-user-noemail" .
+ authenticateWithIdentityProvider(getIdentityProviderModel(), "test-user-noemail", false);
+ changePasswordPage.open();
+ assertTrue(changePasswordPage.isCurrent());
+
+ // Assert old password is not required as federationProvider doesn't have it for this user
+ changePasswordPage.changePassword("new-password", "new-password");
+ Assert.assertEquals("Your password has been updated.", accountUpdateProfilePage.getSuccess());
+
+ // Now it is required as it's set on model
+ changePasswordPage.changePassword("new-password2", "new-password2");
+ Assert.assertEquals("Please specify password.", accountUpdateProfilePage.getError());
+
+ changePasswordPage.changePassword("new-password", "new-password2", "new-password2");
+ Assert.assertEquals("Your password has been updated.", accountUpdateProfilePage.getSuccess());
+
+ // Logout
+ driver.navigate().to("http://localhost:8081/test-app/logout");
+ } finally {
+
+ // remove dummy federation provider for this realm
+ realm = getRealm();
+ realm.removeUserFederationProvider(dummyModel);
+
+ brokerServerRule.stopSession(session, true);
+ session = brokerServerRule.startSession();
+ }
+ }
+
}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/DummyUserFederationProvider.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/DummyUserFederationProvider.java
index 974e5c2..0669da4 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/DummyUserFederationProvider.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/DummyUserFederationProvider.java
@@ -25,8 +25,10 @@ import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserFederationProvider;
import org.keycloak.models.UserModel;
+import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -37,7 +39,11 @@ import java.util.Set;
*/
public class DummyUserFederationProvider implements UserFederationProvider {
- private static Map<String, UserModel> users = new HashMap<String, UserModel>();
+ private final Map<String, UserModel> users;
+
+ public DummyUserFederationProvider(Map<String, UserModel> users) {
+ this.users = users;
+ }
@Override
public UserModel validateAndProxy(RealmModel realm, UserModel local) {
@@ -97,27 +103,39 @@ public class DummyUserFederationProvider implements UserFederationProvider {
@Override
public boolean isValid(RealmModel realm, UserModel local) {
- return false;
+ String username = local.getUsername();
+ return users.containsKey(username);
}
@Override
public Set<String> getSupportedCredentialTypes(UserModel user) {
- return Collections.emptySet();
+ // Just user "test-user" is able to validate password with this federationProvider
+ if (user.getUsername().equals("test-user")) {
+ return Collections.singleton(UserCredentialModel.PASSWORD);
+ } else {
+ return Collections.emptySet();
+ }
}
@Override
public Set<String> getSupportedCredentialTypes() {
- return Collections.emptySet();
+ return Collections.singleton(UserCredentialModel.PASSWORD);
}
@Override
public boolean validCredentials(RealmModel realm, UserModel user, List<UserCredentialModel> input) {
+ if (user.getUsername().equals("test-user") && input.size() == 1) {
+ UserCredentialModel password = input.get(0);
+ if (password.getType().equals(UserCredentialModel.PASSWORD)) {
+ return "secret".equals(password.getValue());
+ }
+ }
return false;
}
@Override
public boolean validCredentials(RealmModel realm, UserModel user, UserCredentialModel... input) {
- return false;
+ return validCredentials(realm, user, Arrays.asList(input));
}
@Override
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/DummyUserFederationProviderFactory.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/DummyUserFederationProviderFactory.java
index 728a122..4b49499 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/DummyUserFederationProviderFactory.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/DummyUserFederationProviderFactory.java
@@ -25,6 +25,7 @@ import org.keycloak.models.UserFederationProvider;
import org.keycloak.models.UserFederationProviderFactory;
import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.UserFederationSyncResult;
+import org.keycloak.models.UserModel;
import org.keycloak.provider.ConfiguredProvider;
import org.keycloak.provider.ProviderConfigProperty;
@@ -43,9 +44,11 @@ public class DummyUserFederationProviderFactory implements UserFederationProvide
private AtomicInteger fullSyncCounter = new AtomicInteger();
private AtomicInteger changedSyncCounter = new AtomicInteger();
+ private Map<String, UserModel> users = new HashMap<String, UserModel>();
+
@Override
public UserFederationProvider getInstance(KeycloakSession session, UserFederationProviderModel model) {
- return new DummyUserFederationProvider();
+ return new DummyUserFederationProvider(users);
}
@Override
@@ -57,7 +60,7 @@ public class DummyUserFederationProviderFactory implements UserFederationProvide
@Override
public UserFederationProvider create(KeycloakSession session) {
- return new DummyUserFederationProvider();
+ return new DummyUserFederationProvider(users);
}
@Override
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/base/FederationProvidersIntegrationTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/base/FederationProvidersIntegrationTest.java
index 273a125..be8c97b 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/base/FederationProvidersIntegrationTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/base/FederationProvidersIntegrationTest.java
@@ -29,6 +29,7 @@ import org.keycloak.OAuth2Constants;
import org.keycloak.federation.ldap.LDAPConfig;
import org.keycloak.federation.ldap.LDAPFederationProvider;
import org.keycloak.federation.ldap.LDAPFederationProviderFactory;
+import org.keycloak.federation.ldap.LDAPUtils;
import org.keycloak.federation.ldap.idm.model.LDAPObject;
import org.keycloak.federation.ldap.mappers.FullNameLDAPFederationMapper;
import org.keycloak.federation.ldap.mappers.FullNameLDAPFederationMapperFactory;
@@ -499,6 +500,8 @@ public class FederationProvidersIntegrationTest {
}
}
+
+ // TODO: Rather separate test for fullNameMapper to better test all the possibilities
@Test
public void testFullNameMapper() {
KeycloakSession session = keycloakRule.startSession();
@@ -521,7 +524,7 @@ public class FederationProvidersIntegrationTest {
UserFederationMapperModel fullNameMapperModel = KeycloakModelUtils.createUserFederationMapperModel("full name", ldapModel.getId(), FullNameLDAPFederationMapperFactory.PROVIDER_ID,
FullNameLDAPFederationMapper.LDAP_FULL_NAME_ATTRIBUTE, ldapFirstNameAttributeName,
- UserAttributeLDAPFederationMapper.READ_ONLY, "false");
+ FullNameLDAPFederationMapper.READ_ONLY, "false");
appRealm.addUserFederationMapper(fullNameMapperModel);
} finally {
keycloakRule.stopSession(session, true);
@@ -534,6 +537,36 @@ public class FederationProvidersIntegrationTest {
// Assert user is successfully imported in Keycloak DB now with correct firstName and lastName
FederationTestUtils.assertUserImported(session.users(), appRealm, "fullname", "James", "Dee", "fullname@email.org", "4578");
+ // change mapper to writeOnly
+ UserFederationMapperModel fullNameMapperModel = appRealm.getUserFederationMapperByName(ldapModel.getId(), "full name");
+ fullNameMapperModel.getConfig().put(FullNameLDAPFederationMapper.WRITE_ONLY, "true");
+ appRealm.updateUserFederationMapper(fullNameMapperModel);
+ } finally {
+ keycloakRule.stopSession(session, true);
+ }
+
+
+ // Assert changing user in Keycloak will change him in LDAP too...
+ session = keycloakRule.startSession();
+ try {
+ RealmModel appRealm = new RealmManager(session).getRealmByName("test");
+
+ UserModel fullnameUser = session.users().getUserByUsername("fullname", appRealm);
+ fullnameUser.setFirstName("James2");
+ fullnameUser.setLastName("Dee2");
+ } finally {
+ keycloakRule.stopSession(session, true);
+ }
+
+
+ // Assert changed user available in Keycloak
+ session = keycloakRule.startSession();
+ try {
+ RealmModel appRealm = new RealmManager(session).getRealmByName("test");
+
+ // Assert user is successfully imported in Keycloak DB now with correct firstName and lastName
+ FederationTestUtils.assertUserImported(session.users(), appRealm, "fullname", "James2", "Dee2", "fullname@email.org", "4578");
+
// Remove "fullnameUser" to assert he is removed from LDAP. Revert mappers to previous state
UserModel fullnameUser = session.users().getUserByUsername("fullname", appRealm);
session.users().removeUser(appRealm, fullnameUser);
@@ -660,7 +693,7 @@ public class FederationProvidersIntegrationTest {
}
- Assert.assertFalse(session.users().removeUser(appRealm, user));
+ Assert.assertTrue(session.users().removeUser(appRealm, user));
} finally {
keycloakRule.stopSession(session, false);
}
@@ -795,8 +828,12 @@ public class FederationProvidersIntegrationTest {
LDAPObject ldapUser = ldapProvider.loadLDAPUserByUsername(appRealm, "johnkeycloak");
ldapProvider.getLdapIdentityStore().validatePassword(ldapUser, "Password1");
- // ATM it's not permitted to delete user in unsynced mode. Should be user deleted just locally instead?
- Assert.assertFalse(session.users().removeUser(appRealm, user));
+ // User is deleted just locally
+ Assert.assertTrue(session.users().removeUser(appRealm, user));
+
+ // Assert user not available locally, but will be reimported from LDAP once searched
+ Assert.assertNull(session.userStorage().getUserByUsername("johnkeycloak", appRealm));
+ Assert.assertNotNull(session.users().getUserByUsername("johnkeycloak", appRealm));
} finally {
keycloakRule.stopSession(session, false);
}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/base/LDAPGroupMapper2WaySyncTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/base/LDAPGroupMapper2WaySyncTest.java
index 45ad9e7..493996a 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/base/LDAPGroupMapper2WaySyncTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/base/LDAPGroupMapper2WaySyncTest.java
@@ -61,6 +61,7 @@ public class LDAPGroupMapper2WaySyncTest {
Map<String,String> ldapConfig = ldapRule.getConfig();
ldapConfig.put(LDAPConstants.SYNC_REGISTRATIONS, "true");
ldapConfig.put(LDAPConstants.EDIT_MODE, UserFederationProvider.EditMode.WRITABLE.toString());
+ ldapConfig.put(LDAPConstants.BATCH_SIZE_FOR_SYNC, "4"); // Issues with pagination on ApacheDS
ldapModel = appRealm.addUserFederationProvider(LDAPFederationProviderFactory.PROVIDER_NAME, ldapConfig, 0, "test-ldap", -1, -1, 0);
LDAPFederationProvider ldapFedProvider = FederationTestUtils.getLdapProvider(session, ldapModel);
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/base/LDAPGroupMapperSyncTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/base/LDAPGroupMapperSyncTest.java
index d218f26..9308e04 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/base/LDAPGroupMapperSyncTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/base/LDAPGroupMapperSyncTest.java
@@ -19,6 +19,7 @@ package org.keycloak.testsuite.federation.ldap.base;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import org.junit.Assert;
import org.junit.Before;
@@ -46,6 +47,7 @@ import org.keycloak.models.UserFederationMapperModel;
import org.keycloak.models.UserFederationProvider;
import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.UserFederationSyncResult;
+import org.keycloak.models.UserModel;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.testsuite.federation.ldap.FederationTestUtils;
@@ -258,4 +260,53 @@ public class LDAPGroupMapperSyncTest {
}
}
+
+
+ @Test
+ public void test04_syncNoPreserveGroupInheritanceWithLazySync() throws Exception {
+ KeycloakSession session = keycloakRule.startSession();
+ try {
+ RealmModel realm = session.realms().getRealmByName("test");
+ UserFederationMapperModel mapperModel = realm.getUserFederationMapperByName(ldapModel.getId(), "groupsMapper");
+ LDAPFederationProvider ldapProvider = FederationTestUtils.getLdapProvider(session, ldapModel);
+ GroupLDAPFederationMapper groupMapper = FederationTestUtils.getGroupMapper(mapperModel, ldapProvider, realm);
+
+ // Update group mapper to skip preserve inheritance
+ FederationTestUtils.updateGroupMapperConfigOptions(mapperModel, GroupMapperConfig.PRESERVE_GROUP_INHERITANCE, "false");
+ realm.updateUserFederationMapper(mapperModel);
+
+ // Add user to LDAP and put him as member of group11
+ FederationTestUtils.removeAllLDAPUsers(ldapProvider, realm);
+ LDAPObject johnLdap = FederationTestUtils.addLDAPUser(ldapProvider, realm, "johnkeycloak", "John", "Doe", "john@email.org", null, "1234");
+ FederationTestUtils.updateLDAPPassword(ldapProvider, johnLdap, "Password1");
+ groupMapper.addGroupMappingInLDAP("group11", johnLdap);
+
+ // Assert groups not yet imported to Keycloak DB
+ Assert.assertNull(KeycloakModelUtils.findGroupByPath(realm, "/group1"));
+ Assert.assertNull(KeycloakModelUtils.findGroupByPath(realm, "/group11"));
+ Assert.assertNull(KeycloakModelUtils.findGroupByPath(realm, "/group12"));
+
+ // Load user from LDAP to Keycloak DB
+ UserModel john = session.users().getUserByUsername("johnkeycloak", realm);
+ Set<GroupModel> johnGroups = john.getGroups();
+
+ // Assert just those groups, which john was memberOf exists because they were lazily created
+ GroupModel group1 = KeycloakModelUtils.findGroupByPath(realm, "/group1");
+ GroupModel group11 = KeycloakModelUtils.findGroupByPath(realm, "/group11");
+ GroupModel group12 = KeycloakModelUtils.findGroupByPath(realm, "/group12");
+ Assert.assertNull(group1);
+ Assert.assertNotNull(group11);
+ Assert.assertNull(group12);
+
+ Assert.assertEquals(1, johnGroups.size());
+ Assert.assertTrue(johnGroups.contains(group11));
+
+ // Delete group mapping
+ john.leaveGroup(group11);
+
+ } finally {
+ keycloakRule.stopSession(session, false);
+ }
+ }
+
}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/base/LDAPGroupMapperTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/base/LDAPGroupMapperTest.java
old mode 100644
new mode 100755
index 45afc47..43a9634
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/base/LDAPGroupMapperTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/base/LDAPGroupMapperTest.java
@@ -190,6 +190,7 @@ public class LDAPGroupMapperTest {
public void test02_readOnlyGroupMappings() {
KeycloakSession session = keycloakRule.startSession();
try {
+ System.out.println("starting test02_readOnlyGroupMappings");
RealmModel appRealm = session.realms().getRealmByName("test");
UserFederationMapperModel mapperModel = appRealm.getUserFederationMapperByName(ldapModel.getId(), "groupsMapper");
@@ -221,6 +222,7 @@ public class LDAPGroupMapperTest {
Assert.assertTrue(maryGroups.contains(group12));
// Assert that access through DB will have just DB mapped groups
+ System.out.println("******");
UserModel maryDB = session.userStorage().getUserByUsername("marykeycloak", appRealm);
Set<GroupModel> maryDBGroups = maryDB.getGroups();
Assert.assertFalse(maryDBGroups.contains(group1));
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/BruteForceTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/BruteForceTest.java
index 16d9804..481ea40 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/BruteForceTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/BruteForceTest.java
@@ -359,7 +359,7 @@ public class BruteForceTest {
loginPage.assertCurrent();
String src = driver.getPageSource();
- Assert.assertEquals("Account is temporarily disabled, contact admin or try again later.", loginPage.getError());
+ Assert.assertEquals("Invalid username or password.", loginPage.getError());
events.expectLogin().session((String) null).error(Errors.USER_TEMPORARILY_DISABLED)
.detail(Details.USERNAME, "test-user@localhost")
.removeDetail(Details.CONSENT)
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTest.java
index de3619d..777fd35 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTest.java
@@ -25,6 +25,7 @@ import org.keycloak.events.Details;
import org.keycloak.events.Event;
import org.keycloak.events.EventType;
import org.keycloak.models.BrowserSecurityHeaders;
+import org.keycloak.models.KeycloakSession;
import org.keycloak.models.PasswordPolicy;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserCredentialModel;
@@ -64,17 +65,13 @@ public class LoginTest {
@Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
- UserCredentialModel creds = new UserCredentialModel();
- creds.setType(CredentialRepresentation.PASSWORD);
- creds.setValue("password");
-
UserModel user = manager.getSession().users().addUser(appRealm, "login-test");
user.setEmail("login@test.com");
user.setEnabled(true);
userId = user.getId();
- user.updateCredential(creds);
+ user.updateCredential(UserCredentialModel.password("password"));
UserModel user2 = manager.getSession().users().addUser(appRealm, "login-test2");
user2.setEmail("login2@test.com");
@@ -82,7 +79,7 @@ public class LoginTest {
user2Id = user2.getId();
- user2.updateCredential(creds);
+ user2.updateCredential(UserCredentialModel.password("password"));
}
});
@@ -304,10 +301,30 @@ public class LoginTest {
}
@Test
+ // KEYCLOAK-2557
+ public void loginUserWithEmailAsUsername() {
+ KeycloakSession session = keycloakRule.startSession();
+
+ UserModel user = session.users().addUser(session.realms().getRealmByName("test"), "login@test.com");
+ user.setEnabled(true);
+ user.updateCredential(UserCredentialModel.password("password"));
+
+ keycloakRule.stopSession(session, true);
+
+ loginPage.open();
+ loginPage.login("login@test.com", "password");
+
+ Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
+ Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
+
+ events.expectLogin().user(userId).detail(Details.USERNAME, "login@test.com").assertEvent();
+ }
+
+ @Test
public void loginSuccess() {
loginPage.open();
loginPage.login("login-test", "password");
-
+
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/keycloaksaml/InputServlet.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/keycloaksaml/InputServlet.java
index b6eb54a..57c8e48 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/keycloaksaml/InputServlet.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/keycloaksaml/InputServlet.java
@@ -17,6 +17,9 @@
package org.keycloak.testsuite.keycloaksaml;
+import org.junit.Assert;
+import org.keycloak.KeycloakSecurityContext;
+
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
@@ -34,6 +37,16 @@ public class InputServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String appBase = System.getProperty("app.server.base.url", "http://localhost:8081");
+ if (req.getRequestURI().endsWith("insecure")) {
+ if (System.getProperty("insecure.user.principal.unsupported") == null) Assert.assertNotNull(req.getUserPrincipal());
+ resp.setContentType("text/html");
+ PrintWriter pw = resp.getWriter();
+ pw.printf("<html><head><title>%s</title></head><body>", "Insecure Page");
+ if (req.getUserPrincipal() != null) pw.printf("UserPrincipal: " + req.getUserPrincipal().getName());
+ pw.print("</body></html>");
+ pw.flush();
+ return;
+ }
String actionUrl = appBase + "/input-portal/secured/post";
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/keycloaksaml/SamlAdapterTestStrategy.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/keycloaksaml/SamlAdapterTestStrategy.java
index 948d9ca..a9e95bb 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/keycloaksaml/SamlAdapterTestStrategy.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/keycloaksaml/SamlAdapterTestStrategy.java
@@ -152,6 +152,12 @@ public class SamlAdapterTestStrategy extends ExternalResource {
String pageSource = driver.getPageSource();
System.out.println(pageSource);
Assert.assertTrue(pageSource.contains("parameter=hello"));
+ // test that user principal and KeycloakSecurityContext available
+ driver.navigate().to(APP_SERVER_BASE_URL + "/input-portal/insecure");
+ System.out.println("insecure: ");
+ System.out.println(driver.getPageSource());
+ Assert.assertTrue(driver.getPageSource().contains("Insecure Page"));
+ if (System.getProperty("insecure.user.principal.unsupported") == null) Assert.assertTrue(driver.getPageSource().contains("UserPrincipal"));
// test logout
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ConcurrentTransactionsTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ConcurrentTransactionsTest.java
new file mode 100755
index 0000000..93b6458
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ConcurrentTransactionsTest.java
@@ -0,0 +1,148 @@
+/*
+ * 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 java.util.concurrent.CountDownLatch;
+
+import org.junit.Assert;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.KeycloakSessionTask;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RealmProvider;
+import org.keycloak.models.utils.KeycloakModelUtils;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class ConcurrentTransactionsTest extends AbstractModelTest {
+
+ @Test
+ public void persistClient() throws Exception {
+ RealmModel realm = realmManager.createRealm("original");
+ KeycloakSession session = realmManager.getSession();
+
+ ClientModel client = session.realms().addClient(realm, "client");
+ client.setSecret("old");
+
+ String clientDBId = client.getId();
+
+ final KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
+ commit();
+
+ final CountDownLatch transactionsCounter = new CountDownLatch(2);
+ final CountDownLatch readLatch = new CountDownLatch(1);
+ final CountDownLatch updateLatch = new CountDownLatch(1);
+
+ Thread thread1 = new Thread() {
+
+ @Override
+ public void run() {
+ KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
+
+ @Override
+ public void run(KeycloakSession session) {
+ try {
+ // Wait until transaction in both threads started
+ transactionsCounter.countDown();
+ System.out.println("transaction1 started");
+ transactionsCounter.await();
+
+ // Read client
+ RealmModel realm = session.realms().getRealmByName("original");
+ ClientModel client = session.realms().getClientByClientId("client", realm);
+ System.out.println("transaction1: Read client finished");
+ readLatch.countDown();
+
+ // Wait until thread2 updates client and commits
+ updateLatch.await();
+ System.out.println("transaction1: Going to read client again");
+
+ client = session.realms().getClientByClientId("client", realm);
+ System.out.println("transaction1: secret: " + client.getSecret());
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ });
+ }
+
+ };
+
+ Thread thread2 = new Thread() {
+
+ @Override
+ public void run() {
+ KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
+
+ @Override
+ public void run(KeycloakSession session) {
+ try {
+ // Wait until transaction in both threads started
+ transactionsCounter.countDown();
+ System.out.println("transaction2 started");
+ transactionsCounter.await();
+
+
+ readLatch.await();
+ System.out.println("transaction2: Going to update client secret");
+
+ RealmModel realm = session.realms().getRealmByName("original");
+ ClientModel client = session.realms().getClientByClientId("client", realm);
+ client.setSecret("new");
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ });
+
+ System.out.println("transaction2: commited");
+ updateLatch.countDown();
+ }
+
+ };
+
+ thread1.start();
+ thread2.start();
+
+ thread1.join();
+ thread2.join();
+
+ System.out.println("after thread join");
+
+ commit();
+
+ session = realmManager.getSession();
+
+ realm = session.realms().getRealmByName("original");
+ ClientModel clientFromCache = session.realms().getClientById(clientDBId, realm);
+ ClientModel clientFromDB = session.getProvider(RealmProvider.class).getClientById(clientDBId, realm);
+
+ System.out.println("SECRET FROM DB : " + clientFromDB.getSecret());
+ System.out.println("SECRET FROM CACHE : " + clientFromCache.getSecret());
+
+ Assert.assertEquals("new", clientFromDB.getSecret());
+ Assert.assertEquals("new", clientFromCache.getSecret());
+ }
+
+}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/DBLockTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/DBLockTest.java
new file mode 100644
index 0000000..bc0cb99
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/DBLockTest.java
@@ -0,0 +1,174 @@
+/*
+ * 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 java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.jboss.logging.Logger;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.KeycloakSessionTask;
+import org.keycloak.services.managers.DBLockManager;
+import org.keycloak.models.dblock.DBLockProvider;
+import org.keycloak.models.dblock.DBLockProviderFactory;
+import org.keycloak.models.utils.KeycloakModelUtils;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class DBLockTest extends AbstractModelTest {
+
+ private static final Logger log = Logger.getLogger(DBLockTest.class);
+
+ private static final int SLEEP_TIME_MILLIS = 10;
+ private static final int THREADS_COUNT = 20;
+ private static final int ITERATIONS_PER_THREAD = 2;
+
+ private static final int LOCK_TIMEOUT_MILLIS = 240000; // Rather bigger to handle slow DB connections in testing env
+ private static final int LOCK_RECHECK_MILLIS = 10;
+
+ @Before
+ @Override
+ public void before() throws Exception {
+ super.before();
+
+ // Set timeouts for testing
+ DBLockManager lockManager = new DBLockManager();
+ DBLockProviderFactory lockFactory = lockManager.getDBLockFactory(session);
+ lockFactory.setTimeouts(LOCK_RECHECK_MILLIS, LOCK_TIMEOUT_MILLIS);
+
+ // Drop lock table, just to simulate racing threads for create lock table and insert lock record into it.
+ lockManager.getDBLock(session).destroyLockInfo();
+
+ commit();
+ }
+
+ // @Test // TODO: Running -Dtest=DBLockTest,UserModelTest might cause issues sometimes. Reenable this once DB lock is refactored.
+ public void testLockConcurrently() throws Exception {
+ long startupTime = System.currentTimeMillis();
+
+ final Semaphore semaphore = new Semaphore();
+ final KeycloakSessionFactory sessionFactory = realmManager.getSession().getKeycloakSessionFactory();
+
+ List<Thread> threads = new LinkedList<>();
+ for (int i=0 ; i<THREADS_COUNT ; i++) {
+ Thread thread = new Thread() {
+
+ @Override
+ public void run() {
+ for (int i=0 ; i<ITERATIONS_PER_THREAD ; i++) {
+ try {
+ KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
+
+ @Override
+ public void run(KeycloakSession session) {
+ lock(session, semaphore);
+ }
+
+ });
+ } catch (RuntimeException e) {
+ semaphore.setException(e);
+ throw e;
+ }
+ }
+ }
+
+ };
+ threads.add(thread);
+ }
+
+ for (Thread thread : threads) {
+ thread.start();
+ }
+ for (Thread thread : threads) {
+ thread.join();
+ }
+
+ long took = (System.currentTimeMillis() - startupTime);
+ log.infof("DBLockTest executed in %d ms with total counter %d. THREADS_COUNT=%d, ITERATIONS_PER_THREAD=%d", took, semaphore.getTotal(), THREADS_COUNT, ITERATIONS_PER_THREAD);
+ Assert.assertEquals(semaphore.getTotal(), THREADS_COUNT * ITERATIONS_PER_THREAD);
+ Assert.assertNull(semaphore.getException());
+ }
+
+ private void lock(KeycloakSession session, Semaphore semaphore) {
+ DBLockProvider dbLock = new DBLockManager().getDBLock(session);
+ dbLock.waitForLock();
+ try {
+ semaphore.increase();
+ Thread.sleep(SLEEP_TIME_MILLIS);
+ semaphore.decrease();
+ } catch (InterruptedException ie) {
+ throw new RuntimeException(ie);
+ } finally {
+ dbLock.releaseLock();
+ }
+ }
+
+
+ // Ensure just one thread is allowed to run at the same time
+ private class Semaphore {
+
+ private AtomicInteger counter = new AtomicInteger(0);
+ private AtomicInteger totalIncreases = new AtomicInteger(0);
+
+ private volatile Exception exception = null;
+
+ private void increase() {
+ int current = counter.incrementAndGet();
+ if (current != 1) {
+ IllegalStateException ex = new IllegalStateException("Counter has illegal value: " + current);
+ setException(ex);
+ throw ex;
+ }
+ totalIncreases.incrementAndGet();
+ }
+
+ private void decrease() {
+ int current = counter.decrementAndGet();
+ if (current != 0) {
+ IllegalStateException ex = new IllegalStateException("Counter has illegal value: " + current);
+ setException(ex);
+ throw ex;
+ }
+ }
+
+ private synchronized void setException(Exception exception) {
+ if (this.exception == null) {
+ this.exception = exception;
+ }
+ }
+
+ private synchronized Exception getException() {
+ return exception;
+ }
+
+ private int getTotal() {
+ return totalIncreases.get();
+ }
+ }
+
+
+
+
+}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ModelTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ModelTest.java
index 0db3146..c356eb5 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ModelTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ModelTest.java
@@ -83,7 +83,7 @@ public class ModelTest extends AbstractModelTest {
Assert.assertEquals(expected.getPublicKeyPem(), actual.getPublicKeyPem());
Assert.assertEquals(expected.getPrivateKeyPem(), actual.getPrivateKeyPem());
- Assert.assertEquals(expected.getDefaultRoles(), actual.getDefaultRoles());
+ Assert.assertEquals(new HashSet<>(expected.getDefaultRoles()), new HashSet<>(actual.getDefaultRoles()));
Assert.assertEquals(expected.getSmtpConfig(), actual.getSmtpConfig());
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserModelTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserModelTest.java
index 53c313f..0841c1f 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserModelTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserModelTest.java
@@ -212,6 +212,17 @@ public class UserModelTest extends AbstractModelTest {
}
@Test
+ public void testSearchByString() {
+ RealmModel realm = realmManager.createRealm("original");
+ UserModel user1 = session.users().addUser(realm, "user1");
+
+ commit();
+
+ List<UserModel> users = session.users().searchForUser("user", realm, 0, 7);
+ Assert.assertTrue(users.contains(user1));
+ }
+
+ @Test
public void testSearchByUserAttribute() throws Exception {
RealmModel realm = realmManager.createRealm("original");
UserModel user1 = session.users().addUser(realm, "user1");
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/ResourceOwnerPasswordCredentialsGrantTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/ResourceOwnerPasswordCredentialsGrantTest.java
index 0c93c0f..5d86085 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/ResourceOwnerPasswordCredentialsGrantTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/ResourceOwnerPasswordCredentialsGrantTest.java
@@ -29,8 +29,10 @@ import org.keycloak.events.Details;
import org.keycloak.events.Errors;
import org.keycloak.models.*;
import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.models.utils.TimeBasedOTP;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.RefreshToken;
+import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.OAuthClient;
@@ -63,9 +65,24 @@ public class ResourceOwnerPasswordCredentialsGrantTest {
user.setEmail("direct-login@localhost");
user.setEnabled(true);
+ session.users().updateCredential(appRealm, user, UserCredentialModel.password("password"));
+
userId = user.getId();
- session.users().updateCredential(appRealm, user, UserCredentialModel.password("password"));
+ UserModel user2 = session.users().addUser(appRealm, "direct-login-otp");
+ user2.setEnabled(true);
+
+ UserCredentialModel credentials = new UserCredentialModel();
+ credentials.setType(CredentialRepresentation.TOTP);
+ credentials.setValue("totpSecret");
+ user2.updateCredential(credentials);
+
+ user2.setOtpEnabled(true);
+
+ session.users().updateCredential(appRealm, user2, UserCredentialModel.password("password"));
+
+ userId2 = user2.getId();
+
}
});
@@ -83,6 +100,10 @@ public class ResourceOwnerPasswordCredentialsGrantTest {
private static String userId;
+ private static String userId2;
+
+ private TimeBasedOTP totp = new TimeBasedOTP();
+
@Test
public void grantAccessTokenUsername() throws Exception {
grantAccessToken("direct-login", "resource-owner");
@@ -98,11 +119,57 @@ public class ResourceOwnerPasswordCredentialsGrantTest {
grantAccessToken("direct-login", "resource-owner-public");
}
+ @Test
+ public void grantAccessTokenWithTotp() throws Exception {
+ grantAccessToken(userId2, "direct-login-otp", "resource-owner", totp.generateTOTP("totpSecret"));
+ }
+
+ @Test
+ public void grantAccessTokenMissingTotp() throws Exception {
+ oauth.clientId("resource-owner");
+
+ OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("secret", "direct-login-otp", "password");
+
+ assertEquals(401, response.getStatusCode());
+
+ assertEquals("invalid_grant", response.getError());
+
+ events.expectLogin()
+ .client("resource-owner")
+ .session((String) null)
+ .clearDetails()
+ .error(Errors.INVALID_USER_CREDENTIALS)
+ .user(userId2)
+ .assertEvent();
+ }
+
+ @Test
+ public void grantAccessTokenInvalidTotp() throws Exception {
+ oauth.clientId("resource-owner");
+
+ OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("secret", "direct-login-otp", "password", totp.generateTOTP("totpSecret2"));
+
+ assertEquals(401, response.getStatusCode());
+
+ assertEquals("invalid_grant", response.getError());
+
+ events.expectLogin()
+ .client("resource-owner")
+ .session((String) null)
+ .clearDetails()
+ .error(Errors.INVALID_USER_CREDENTIALS)
+ .user(userId2)
+ .assertEvent();
+ }
private void grantAccessToken(String login, String clientId) throws Exception {
+ grantAccessToken(userId, login, clientId, null);
+ }
+
+ private void grantAccessToken(String userId, String login, String clientId, String otp) throws Exception {
oauth.clientId(clientId);
- OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("secret", login, "password");
+ OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("secret", login, "password", otp);
assertEquals(200, response.getStatusCode());
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/OAuthClient.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/OAuthClient.java
index 46235ed..7ccd8d7 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/OAuthClient.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/OAuthClient.java
@@ -93,7 +93,7 @@ public class OAuthClient {
public AuthorizationCodeResponse doLogin(String username, String password) {
openLoginForm();
-
+ String src = driver.getPageSource();
driver.findElement(By.id("username")).sendKeys(username);
driver.findElement(By.id("password")).sendKeys(password);
driver.findElement(By.name("login")).click();
@@ -205,6 +205,10 @@ public class OAuthClient {
return doGrantAccessTokenRequest(realm, username, password, null, clientId, clientSecret);
}
+ public AccessTokenResponse doGrantAccessTokenRequest(String clientSecret, String username, String password, String otp) throws Exception {
+ return doGrantAccessTokenRequest(realm, username, password, otp, clientId, clientSecret);
+ }
+
public AccessTokenResponse doGrantAccessTokenRequest(String realm, String username, String password, String totp,
String clientId, String clientSecret) throws Exception {
CloseableHttpClient client = new DefaultHttpClient();
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/saml/SamlPicketlinkSPTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/saml/SamlPicketlinkSPTest.java
index 2c13fdd..6ce33ec 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/saml/SamlPicketlinkSPTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/saml/SamlPicketlinkSPTest.java
@@ -22,8 +22,13 @@ import org.junit.Assert;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.RuleChain;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
import org.keycloak.admin.client.Keycloak;
import org.keycloak.admin.client.resource.RealmResource;
+import org.keycloak.common.util.Environment;
import org.keycloak.models.ClientModel;
import org.keycloak.models.Constants;
import org.keycloak.models.ProtocolMapperModel;
@@ -66,7 +71,28 @@ import static org.junit.Assert.assertEquals;
*/
public class SamlPicketlinkSPTest {
- @ClassRule
+ // This test is ignored in IBM JDK due to the IBM JDK bug, which is handled in Keycloak SP ( org.keycloak.saml.common.parsers.AbstractParser ) but not in Picketlink SP
+ public static TestRule ignoreIBMJDK = new TestRule() {
+
+ @Override
+ public Statement apply(final Statement base, final Description description) {
+ return new Statement() {
+
+ @Override
+ public void evaluate() throws Throwable {
+ if (Environment.IS_IBM_JAVA) {
+ System.err.println("Ignore " + description.getDisplayName() + " because executing on IBM JDK");
+ } else {
+ base.evaluate();
+ }
+ }
+
+ };
+ }
+
+ };
+
+
public static SamlKeycloakRule keycloakRule = new SamlKeycloakRule() {
@Override
public void initWars() {
@@ -97,6 +123,12 @@ public class SamlPicketlinkSPTest {
}
};
+ @ClassRule
+ public static TestRule chain = RuleChain
+ .outerRule(ignoreIBMJDK)
+ .around(keycloakRule);
+
+
public static class SamlSPFacade extends HttpServlet {
public static String samlResponse;
public static String RELAY_STATE = "http://test.com/foo/bar";
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/util/cli/AbstractOfflineCacheCommand.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/util/cli/AbstractOfflineCacheCommand.java
index ae677b8..59be8aa 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/util/cli/AbstractOfflineCacheCommand.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/util/cli/AbstractOfflineCacheCommand.java
@@ -103,6 +103,34 @@ public abstract class AbstractOfflineCacheCommand extends AbstractCommand {
}
}
+ // Just to check performance of multiple get calls. And comparing what's the change between the case when item is available locally or not.
+ public static class GetMultipleCommand extends AbstractOfflineCacheCommand {
+
+ @Override
+ public String getName() {
+ return "getMulti";
+ }
+
+ @Override
+ protected void doRunCacheCommand(KeycloakSession session, Cache<String, SessionEntity> cache) {
+ String id = getArg(0);
+ int count = getIntArg(1);
+
+ long start = System.currentTimeMillis();
+ for (int i=0 ; i<count ; i++) {
+ UserSessionEntity userSession = (UserSessionEntity) cache.get(id);
+ //printSession(id, userSession);
+ }
+ long took = System.currentTimeMillis() - start;
+ log.infof("Took %d milliseconds", took);
+ }
+
+ @Override
+ public String printUsage() {
+ return getName() + " <user-session-id> <count-of-gets>";
+ }
+ }
+
public static class RemoveCommand extends AbstractOfflineCacheCommand {
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/util/cli/TestsuiteCLI.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/util/cli/TestsuiteCLI.java
index 534e84a..59f3490 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/util/cli/TestsuiteCLI.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/util/cli/TestsuiteCLI.java
@@ -45,6 +45,7 @@ public class TestsuiteCLI {
HelpCommand.class,
AbstractOfflineCacheCommand.PutCommand.class,
AbstractOfflineCacheCommand.GetCommand.class,
+ AbstractOfflineCacheCommand.GetMultipleCommand.class,
AbstractOfflineCacheCommand.GetLocalCommand.class,
AbstractOfflineCacheCommand.RemoveCommand.class,
AbstractOfflineCacheCommand.SizeCommand.class,
diff --git a/testsuite/integration/src/test/resources/keys/certificate.pem b/testsuite/integration/src/test/resources/keys/certificate.pem
new file mode 100644
index 0000000..a526c93
--- /dev/null
+++ b/testsuite/integration/src/test/resources/keys/certificate.pem
@@ -0,0 +1,21 @@
+-----BEGIN CERTIFICATE-----
+MIIDXTCCAkWgAwIBAgIJAIzE3vQp7EQWMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV
+BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
+aWRnaXRzIFB0eSBMdGQwHhcNMTYwMjI5MDgzMDU0WhcNNDMwNzE2MDgzMDU0WjBF
+MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50
+ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
+CgKCAQEAp1+GzdEkt2FZbISXYO12503FL6Oh8s4+tJ2fE66N8IezhugP8xiySDfW
+TEMaO5Z2TaTnQQoF9SSZ9Edq1GPxpBX0cdkCOBopEGdlb3hUYDeMaDMs18KGemUc
+Fj+CWB5VVcbmWMJ36WCz7FC+Oe38tmujR1AJpJL3pwqazyWIZzPqX8rW+rrNPGKP
+C96oBPZMb4RJWivLBJi/o5MGSpo1sJNtxyF4zUUI00LX0wZAV1HH1XErd1Vz41on
+nmB+tj9nevVRR4rDV280IELp9Ud0PIb3w843uJtwfSAwVG0pT6hv1VBDrBxTS08N
+dPU8CtkQAXzCCr8nqfAbUFOhcWRQgQIDAQABo1AwTjAdBgNVHQ4EFgQUFE+uUZAI
+n57ArEylqhCmHkAenTEwHwYDVR0jBBgwFoAUFE+uUZAIn57ArEylqhCmHkAenTEw
+DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEApkgD3OCtw3+sk7GR1YaJ
+xNd8HT+fxXmnLqGnCQWX8lRIg5vj1PDMRev6vlIK3JfQV3zajcpKFfpy96klWsJy
+ZLYBVW2QOtMzDdQ9I8dS4Pn/SJ/Vo/M/ucfY4ttcuUL3oQCrI/c/u9tcamGMfbwd
+658MlXrUvt4B6qXY5AbgUvYR25P86uw7hSFMq5tQftNQsLbOh2FEeIiKhpgI7w8S
+SPajaWjUXsfHc5H7f9MciE2NS1Vd3AViGrVWP1rgQ1Iv0UyQVQrnjmIs12ENJmTd
+5lDqra5FJhaO7+RUG6er8n8HwXzhHkPmezGqtxWKikjitqvDY9prB3omJSa4Led+
+AQ==
+-----END CERTIFICATE-----
diff --git a/testsuite/integration/src/test/resources/keys/private2048.pem b/testsuite/integration/src/test/resources/keys/private2048.pem
new file mode 100644
index 0000000..e2baad3
--- /dev/null
+++ b/testsuite/integration/src/test/resources/keys/private2048.pem
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEogIBAAKCAQEApnZ/E2BUULHjsRiSnEgZ4vGe15BRqZPdkHR+NcvVYpThc7Jq
+Y6nZrdrwO9sOjlMC5e2Q18Fypi4KbJpGSe9r0DPgcbPsHSoe2xFO3M8XBE0DyoRb
+laQFhe6p/sj3ak32k2zn+fMZUmlx/MTNQh1ICki7So0NDCBXt8XGZNnEyvKeXOUZ
+P5qicP9KxVAQiWJvlkaTjc8rrRTmf+HWw/QfgQC0tzBRpa7T+RpW9O+rnWfOaNfT
+kTb9itIc+ZOa2Z4iidZ7+ifMOp9cNT641Wb6iYqJ2ufqY+msxI54tYM1tPgGS7r4
+SnCwmnqTaO383wXUl8TQ7qStmAWIepV3nNyuAQIDAQABAoIBAEj90rDrX21W43Fn
+RfpTP06dBjqdpMFH/jJ2clUigPnOMKGrzSzQcIvkYczNPC+6RJ4PsqB4yc4GiDmg
+2EtZOZw88yDIdTNAofELQNpf0Ebpgk0OBp6yIl3dDhuTgbHSZ9mzOnEGYMcbR4k/
+voVME6e2xrFk8iCsGeqSRXE5cCpQzXLFQGRrGGoJH8hggteyQhei39pxo8IM87A0
+ptdZj8YJ0YUUpztyshR5KAzZeJPjKRXE1wPiZoqDzQ0OCC+k9IGCeJIAAGVB1Zs7
+4ga4gEkczYfSbhRRGl7Uls4XJHL1pNsU75sRpQZGuGm9as6PprYJiDH5Eoz+EdzO
++oxs0FUCgYEA1NFbnyjlTEB9liVAMSd+BNRL7c78+Tsbfn/y6b1pEcSZ5+R79tNw
+NTAmk0GSrW64MRQS2gYe871w9QBwpSlNoGMN5Zjzl8WIZ+AFzTCVnEyBt0DOTj/w
+ZU2/o56C+/CYj6kUjP88HRIX/Grr7rY20uGcDKVLFVeYCBGUI/m0WOsCgYEAyD1F
+TLZea00ZBl0xb/ju0KKGHxw63BldfoKCXjTarz1M7Nl9vBdGuOBH+H3a2wSPhJfb
+7Le9LY+tU53aCJql5xshsDNtduTMdHPamKXkHH038Tbn1vb8HWPzAzYcAY3oAPVJ
+OPPWtTmVeitl0Lo5kPYFbx4yBykOOAokLE/qGcMCgYBZu4CnRkYQday+TyyWzTEM
+djshpUHzEGISX36b4ZpYvI2sQiGmvBY2xvus4VwoNmQBhZZBSY1pdjoXg7z7VsP9
+WWa1pV0oZEiUi9fGYbLjeTrEetXCFqGVBUhFhAN0mUiqYj9hCAlftI5ahva96ySI
+nEoA5v0WnZ1j4Y2V8aaCSwKBgA2bBCtrNM0rpuikymgmTOvGL0DL5T/xRUYETiFi
+i/1eN+zb9kwidL65ForO2mEJVUsYGmxiK6t92LQWxKrS/zTNxiM3y1dJwo6jFJZM
+p0w8QeHU7jnP/F9u3CM6uPpuDvaJtBj3kH4t1HdBnaBqFuE/pizfq1yLMJkkL0MH
+hwuLAoGAEwOTV+mkM6Fv+JGDwRkfFlz95XfQsTXT3xBgGy9sGmtn2b4tPCzO/Fxd
+eLD9dMeI8M3yB4Vc1gRhK8jmmgvcYhsyLn+DreLGWnwQjQn+vAMCpkcPMGJjp3bz
+UooIHTCHq7aNirxPrzgi+F5PcGR074qPFrdVaL031CPTAGmMqzE=
+-----END RSA PRIVATE KEY-----
diff --git a/testsuite/integration/src/test/resources/keys/private4096.pem b/testsuite/integration/src/test/resources/keys/private4096.pem
new file mode 100644
index 0000000..e854f45
--- /dev/null
+++ b/testsuite/integration/src/test/resources/keys/private4096.pem
@@ -0,0 +1,51 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIJKAIBAAKCAgEAni4pAISeCrzLSEuTJPx51Uwh0LIQuNHRTah1UuWk5NP4W1ct
+l1PuCoddtZNiPL2tnEw0Js1m7vlI3+miynNy8ze2LCtaA98oPUVsD9C13hKH3nQ1
+Zf/IYCzHMC7BAJ7pFCgo2TM0S2ic2eHyr+/vQd2ha8mnj6hzwJ3O9KaIINDLA1Eu
+cJNZDxmlbzJfap5mFH1Uj2RhDiYM4m8445fR7QDv3Zhiv0HMP0dCaVnPot+jT6ar
+195XDDLSIrK+sc8SFLdlc4BlGitDyZviKVzI5E4nn4hkHLBK6jHzNd+afJLPyZIm
+t0N9NPj6y3h3gaGAVMWQpK2bgKEdF6417CPGbcMuGsBbpmDQzl9T4EtcN/Pq741t
+yCEXUWORHOEU+bBiEO1RAWsah13LxhYQ38YclTkr7BGZfQ4sfEZDKEknr7UuQw1e
+E7eZLmiAb+N72kxO5u50OtAmqUhw8LN5iQ/gD5dcItsdi643nKTFUB+bEIqqWaXB
+vRsIn3BmNJiPrgUFJoMm94vrAC1WKjKU4QfERSD/SUuFL3FisP3c33VP1VX33rfM
+6PIWyffIx43XoSp7MwFL3zPvTNLBP33LIBPD83ZT1Af46ky8TIA98CEkNNQdBOQE
+trSsm0snYuku1xNQrdccw3jFvroVQGGPS4fLb0ZveUzvWlTXfPwcF/I+UX8CAwEA
+AQKCAgAtBL4uo4/HRowkez3ZnPGfvxcwqj0QHMcQ7wzVFv6jEaDxfhI/q2doH7aP
+4u5mkyVCAn4lPPg6s0AGWhw2ujzQ894pG/12dKCjL4rdxWy9U1DQAwrfiLiihpv6
+HpRXdOKpFfTqPCR6AcACUsgbQpI5kLZMUwwZWrv26fLotiywCjx9KTGqYIVMQuRO
+jWuSkJpE4paaaNsmh7XLDBSI0cCxTz71NlWEUVmr+L/x89mgZT25gZoOyfzEQAKG
+VZDoUiK3OqrpAHXFyOJ2EqN/WiTitUuZn9u+Pn5TwPuTLZxuL+prV+kk51RPKz1V
+g2efwTMg7UmJVM+ZAEGjZ7V5C4WBbR4kxpCVe2vs2x6OUkhu84MFrB3WQ5oHbR7C
+EZc15FVZNux7mHokwBYsosgtNUtdRtWN3mIIjZKQhbQfw5U8LwLGf94Gc5sxAu8O
+E/KQ2kynqs6OU0PixVMRxkLnVJeT3x4lS8VIuiEx6Wa8+R5774tMmVPPghr2Y2Fx
+IY/nF0fwU+5wn5Bu2Td5m/+g/lFfWZslTdTRNPtAPHfEuQCVxgtolpS2is9rqJw7
+0ymmck+XdM18IGVf1e+AH01w8FMlngU7JqO2jkqNE0B+s+FRkYkg/ldTJjI3e8HT
+kFBIynUfgzNgvulL06Re9TSq5QG9pahpT3FyUILbe808fV+jgQKCAQEAz4bBUHSH
+TAsgUSvAQ/KDJAaoaEFLAoy1B/u2qk/4e01b1FrEVGO4tPhGR5fsWYew7ktTMHPp
+ywra1sTdUUKEVm51YHiymT1T5w5S1gYJn6eElhfcf3tTk1zDW+WBQ0lrn5FVe4Kt
+guFledpGqssGymqrxwKqKnGY5viI+jUmBZATIo6yeNDJuBd2W2kXO40wUn7M5/yE
+a1jUxc6SPC2Z4CsAZyL5YuwbwW/K2dP97MX2snyPlnLzm9etBsMa52N/ODBThgxE
++pwST+0DMbi9RtSCEQfeTEmJvuXkANhLnQZq7oW+WwEpWTjNQMzdaOYyIBk4vlmP
+8mjI+KdplCtNcQKCAQEAwyC1OH1iASdJ7ihvSx8+nM6vk835Am2xERvcduAh7yX6
+0S8Uq0AYlFz1qg/7T0HK1+6kWsCSxgaXRnlU6Mit62sEQ89HEs6kPCkjGvT0BLF1
+dTU1f8xbWG/Ra7ejjuVAV4K2ZhpI2TwHChQt94mkNmeW99SHTVZmsKEo6igfstRx
+Xk5PwK69YVbR4NEGRWcKjeeokfNML6WKa+G0zzaP6mRpdcIvsHbZJNX9NKAU1IvC
++QrrgZNnR0gjFrb77QVK/yED8MidYz1VSOPP1TtozKfyqc7flC28wTd1U1c+Sh+9
+02nkh2xEKwBRnf3+3qdGYZnZPJTHOnLuOegv84LV7wKCAQEApaE1lNMMUOLobiBv
+GUvq4sv1iQ/joCtRKQf7KD8fYLnDOt8epwPYHYex/93/Iw2rZuTzhk5dIFKPiq4g
+vYRLPvh18gMi+C78UgMalfrHn4cByRFOSOjTMV/uA+BOpLdqkDZcdXE8rqLabMCL
+ejEEQHWWmAVGbw2vLVjbamcU64er3f7p7oclGCqRqi9b/YmYMZ9GRzlBLrP9Tcqq
+6CC1GTb44VgGlq8/D8n7qpMJrPnrBVVo/HjOeWlPjYAWbur4VI4te2U7gJEkBGp0
+DDEXz/o3vQP8pgJjT9sHeK0o3DCNE8XmwZdRuwYcu0VGyTxAcWHv6exteNms3Ngw
+6bMN4QKCAQABFK5MSM2BKiGLsyeip+Kl5bMtQ2fMrqTbbmcTNXyaoYA5JmSb6jf4
+omct8Pa4Yqnn9kdsxUJK3IB8AHIK9AmakzYr1fsTzJc2ShgKry6m1ADNjGTmd1BO
+NhhX22WJhhWMJooyGJUstttnH+N9SoLhVkOMzd2N/RuGgO4EFgLO78RM/GwOqikc
+X+m7sAyz17VEQfM6E7npTaZtoItq1meHqdS3tUKkXJQpUxIa94QGBVwoGvpg9lsN
+FwYyuwK8NlpK/XjTHZlZkl5lj/V3veN/trJuZFnyrSote8wnkQUkTfa0NBLy+ROL
+lW3eTSjbPNvz8HE2l7Bez4IoSfPyClh7AoIBAAyWkCZU/57BWfWQVM+74Czjfh9l
+fYrri16POhlDe7oZn/nAaKxWGoNTOEFkKeM0uag+mDUspxXn/ZREV+xb3WXTNTjV
+hLM7HiKMx9rMOIQedO4heyJWT5fIzGfxmPVok9cO8XMG+Ox3SLpn1S0N5+5fkvQj
+S4OX8v4U0AAhfwyxd1zvvsMWj9lPEOpFIWpjhFtnxvevCYCu62JKYKwI0kSqFeWr
+nm1EgcpZVKvLZjScWiW+f20KOBeg/WFBeqh0BFEM7eCOg1EyGJnHLegYabcpWIYH
+hLJWQdXkUbXkJBql3Sbd3o3ZTiknahO4bc1Kcxm29Kns8Y02cEd+Ahwuhh4=
+-----END RSA PRIVATE KEY-----
diff --git a/testsuite/integration/src/test/resources/keys/public2048.pem b/testsuite/integration/src/test/resources/keys/public2048.pem
new file mode 100644
index 0000000..565df1e
--- /dev/null
+++ b/testsuite/integration/src/test/resources/keys/public2048.pem
@@ -0,0 +1,9 @@
+-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApnZ/E2BUULHjsRiSnEgZ
+4vGe15BRqZPdkHR+NcvVYpThc7JqY6nZrdrwO9sOjlMC5e2Q18Fypi4KbJpGSe9r
+0DPgcbPsHSoe2xFO3M8XBE0DyoRblaQFhe6p/sj3ak32k2zn+fMZUmlx/MTNQh1I
+Cki7So0NDCBXt8XGZNnEyvKeXOUZP5qicP9KxVAQiWJvlkaTjc8rrRTmf+HWw/Qf
+gQC0tzBRpa7T+RpW9O+rnWfOaNfTkTb9itIc+ZOa2Z4iidZ7+ifMOp9cNT641Wb6
+iYqJ2ufqY+msxI54tYM1tPgGS7r4SnCwmnqTaO383wXUl8TQ7qStmAWIepV3nNyu
+AQIDAQAB
+-----END PUBLIC KEY-----
diff --git a/testsuite/integration/src/test/resources/keys/public4096.pem b/testsuite/integration/src/test/resources/keys/public4096.pem
new file mode 100644
index 0000000..1d1fa1b
--- /dev/null
+++ b/testsuite/integration/src/test/resources/keys/public4096.pem
@@ -0,0 +1,14 @@
+-----BEGIN PUBLIC KEY-----
+MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAni4pAISeCrzLSEuTJPx5
+1Uwh0LIQuNHRTah1UuWk5NP4W1ctl1PuCoddtZNiPL2tnEw0Js1m7vlI3+miynNy
+8ze2LCtaA98oPUVsD9C13hKH3nQ1Zf/IYCzHMC7BAJ7pFCgo2TM0S2ic2eHyr+/v
+Qd2ha8mnj6hzwJ3O9KaIINDLA1EucJNZDxmlbzJfap5mFH1Uj2RhDiYM4m8445fR
+7QDv3Zhiv0HMP0dCaVnPot+jT6ar195XDDLSIrK+sc8SFLdlc4BlGitDyZviKVzI
+5E4nn4hkHLBK6jHzNd+afJLPyZImt0N9NPj6y3h3gaGAVMWQpK2bgKEdF6417CPG
+bcMuGsBbpmDQzl9T4EtcN/Pq741tyCEXUWORHOEU+bBiEO1RAWsah13LxhYQ38Yc
+lTkr7BGZfQ4sfEZDKEknr7UuQw1eE7eZLmiAb+N72kxO5u50OtAmqUhw8LN5iQ/g
+D5dcItsdi643nKTFUB+bEIqqWaXBvRsIn3BmNJiPrgUFJoMm94vrAC1WKjKU4QfE
+RSD/SUuFL3FisP3c33VP1VX33rfM6PIWyffIx43XoSp7MwFL3zPvTNLBP33LIBPD
+83ZT1Af46ky8TIA98CEkNNQdBOQEtrSsm0snYuku1xNQrdccw3jFvroVQGGPS4fL
+b0ZveUzvWlTXfPwcF/I+UX8CAwEAAQ==
+-----END PUBLIC KEY-----
diff --git a/testsuite/integration/src/test/resources/log4j.properties b/testsuite/integration/src/test/resources/log4j.properties
index a199a41..fd730de 100755
--- a/testsuite/integration/src/test/resources/log4j.properties
+++ b/testsuite/integration/src/test/resources/log4j.properties
@@ -39,7 +39,7 @@ log4j.logger.org.keycloak.testsuite=${keycloak.testsuite.logging.level}
# Liquibase updates logged with "info" by default. Logging level can be changed by system property "keycloak.liquibase.logging.level"
keycloak.liquibase.logging.level=info
-log4j.logger.org.keycloak.connections.jpa.updater.liquibase.LiquibaseJpaUpdaterProvider=${keycloak.liquibase.logging.level}
+log4j.logger.org.keycloak.connections.jpa.updater.liquibase=${keycloak.liquibase.logging.level}
# Enable to view infinispan initialization
# log4j.logger.org.keycloak.models.sessions.infinispan.initializer=trace
diff --git a/testsuite/integration/src/test/resources/META-INF/keycloak-server.json b/testsuite/integration/src/test/resources/META-INF/keycloak-server.json
index 950fd22..e9bb97f 100755
--- a/testsuite/integration/src/test/resources/META-INF/keycloak-server.json
+++ b/testsuite/integration/src/test/resources/META-INF/keycloak-server.json
@@ -86,7 +86,7 @@
"connectionsInfinispan": {
"default": {
"clustered": "${keycloak.connectionsInfinispan.clustered:false}",
- "async": "${keycloak.connectionsInfinispan.async:true}",
+ "async": "${keycloak.connectionsInfinispan.async:false}",
"sessionsOwners": "${keycloak.connectionsInfinispan.sessionsOwners:2}"
}
}
diff --git a/testsuite/integration-arquillian/pom.xml b/testsuite/integration-arquillian/pom.xml
index ffc8bfc..e008b74 100644
--- a/testsuite/integration-arquillian/pom.xml
+++ b/testsuite/integration-arquillian/pom.xml
@@ -24,7 +24,7 @@
<parent>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-testsuite-pom</artifactId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
diff --git a/testsuite/integration-arquillian/README.md b/testsuite/integration-arquillian/README.md
index 39168de..1adc054 100644
--- a/testsuite/integration-arquillian/README.md
+++ b/testsuite/integration-arquillian/README.md
@@ -152,6 +152,7 @@ integration-arquillian
│
├──console (activated by -Pconsole-ui-tests)
├──mod_auth_mellon (activated by -Pmod_auth_mellon)
+ ├──console_no_users (activated by -Pconsole-ui-no-users-tests)
└──...
```
testsuite/integration-arquillian/servers/eap7/pom.xml 172(+122 -50)
diff --git a/testsuite/integration-arquillian/servers/eap7/pom.xml b/testsuite/integration-arquillian/servers/eap7/pom.xml
index 47bec1b..a02b37d 100644
--- a/testsuite/integration-arquillian/servers/eap7/pom.xml
+++ b/testsuite/integration-arquillian/servers/eap7/pom.xml
@@ -1,27 +1,27 @@
<?xml version="1.0"?>
<!--
- ~ 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.
- -->
+~ 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.
+-->
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<parent>
<groupId>org.keycloak.testsuite</groupId>
<artifactId>integration-arquillian-servers</artifactId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -118,6 +118,68 @@
</profile>
<profile>
+ <id>ssl</id>
+ <activation>
+ <property>
+ <name>auth.server.ssl.required</name>
+ </property>
+ </activation>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>xml-maven-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>configure-adapter-subsystem-security</id>
+ <phase>process-resources</phase>
+ <goals>
+ <goal>transform</goal>
+ </goals>
+ <configuration>
+ <transformationSets>
+ <transformationSet>
+ <dir>${keycloak.server.home}/standalone/configuration</dir>
+ <includes>
+ <include>standalone.xml</include>
+ </includes>
+ <stylesheet>src/main/xslt/security.xsl</stylesheet>
+ <outputDir>${keycloak.server.home}/standalone/configuration</outputDir>
+ </transformationSet>
+ </transformationSets>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <artifactId>maven-resources-plugin</artifactId>
+ <version>2.7</version>
+ <executions>
+ <execution>
+ <id>copy-keystore</id>
+ <phase>process-resources</phase>
+ <goals>
+ <goal>copy-resources</goal>
+ </goals>
+ <configuration>
+ <outputDirectory>${keycloak.server.home}/standalone/configuration</outputDirectory>
+ <resources>
+ <resource>
+ <directory>src/main/keystore</directory>
+ <includes>
+ <include>keycloak.jks</include>
+ <include>keycloak.truststore</include>
+ </includes>
+ </resource>
+ </resources>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ <profile>
<id>jpa</id>
<properties>
<jdbc.mvn.driver.deployment.dir>${keycloak.server.home}/modules/system/layers/base/com/${jdbc.mvn.artifactId}/main</jdbc.mvn.driver.deployment.dir>
@@ -255,13 +317,14 @@
</plugins>
</build>
</profile>
+
<profile>
- <id>ssl</id>
- <activation>
- <property>
- <name>auth.server.ssl.required</name>
- </property>
- </activation>
+ <id>auth-server-eap7-cluster</id>
+ <properties>
+ <session.cache.owners>1</session.cache.owners>
+ <offline.session.cache.owners>1</offline.session.cache.owners>
+ <login.failure.cache.owners>1</login.failure.cache.owners>
+ </properties>
<build>
<plugins>
<plugin>
@@ -269,53 +332,62 @@
<artifactId>xml-maven-plugin</artifactId>
<executions>
<execution>
- <id>configure-adapter-subsystem-security</id>
+ <id>configure-wildfly-datasource</id>
<phase>process-resources</phase>
<goals>
<goal>transform</goal>
</goals>
<configuration>
<transformationSets>
+ <!-- point KeycloakDS datasource to H2 running on TCP port -->
<transformationSet>
<dir>${keycloak.server.home}/standalone/configuration</dir>
<includes>
- <include>standalone.xml</include>
+ <include>standalone-ha.xml</include>
</includes>
- <stylesheet>src/main/xslt/security.xsl</stylesheet>
+ <stylesheet>src/main/xslt/datasource-jdbc-url.xsl</stylesheet>
<outputDir>${keycloak.server.home}/standalone/configuration</outputDir>
+ <parameters>
+ <parameter>
+ <name>pool.name</name>
+ <value>KeycloakDS</value>
+ </parameter>
+ <parameter>
+ <name>jdbc.url</name>
+ <value>jdbc:h2:tcp://${jboss.bind.address:localhost}:9092/mem:keycloak;DB_CLOSE_DELAY=-1</value>
+ </parameter>
+ </parameters>
+ </transformationSet>
+ <transformationSet>
+ <dir>${keycloak.server.home}/standalone/configuration</dir>
+ <includes>
+ <include>standalone-ha.xml</include>
+ </includes>
+ <stylesheet>src/main/xslt/ispn-cache-owners.xsl</stylesheet>
+ <outputDir>${keycloak.server.home}/standalone/configuration</outputDir>
+ <parameters>
+ <parameter>
+ <name>sessionCacheOwners</name>
+ <value>${session.cache.owners}</value>
+ </parameter>
+ <parameter>
+ <name>offlineSessionCacheOwners</name>
+ <value>${offline.session.cache.owners}</value>
+ </parameter>
+ <parameter>
+ <name>loginFailureCacheOwners</name>
+ <value>${login.failure.cache.owners}</value>
+ </parameter>
+ </parameters>
</transformationSet>
</transformationSets>
</configuration>
</execution>
</executions>
</plugin>
- <plugin>
- <artifactId>maven-resources-plugin</artifactId>
- <version>2.7</version>
- <executions>
- <execution>
- <id>copy-keystore</id>
- <phase>process-resources</phase>
- <goals>
- <goal>copy-resources</goal>
- </goals>
- <configuration>
- <outputDirectory>${keycloak.server.home}/standalone/configuration</outputDirectory>
- <resources>
- <resource>
- <directory>src/main/keystore</directory>
- <includes>
- <include>keycloak.jks</include>
- <include>keycloak.truststore</include>
- </includes>
- </resource>
- </resources>
- </configuration>
- </execution>
- </executions>
- </plugin>
</plugins>
</build>
</profile>
+
</profiles>
</project>
diff --git a/testsuite/integration-arquillian/servers/eap7/src/main/xslt/datasource-jdbc-url.xsl b/testsuite/integration-arquillian/servers/eap7/src/main/xslt/datasource-jdbc-url.xsl
new file mode 100644
index 0000000..589ee4c
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/eap7/src/main/xslt/datasource-jdbc-url.xsl
@@ -0,0 +1,36 @@
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:xalan="http://xml.apache.org/xalan"
+ xmlns:j="urn:jboss:domain:4.0"
+ xmlns:ds="urn:jboss:domain:datasources:4.0"
+ xmlns:k="urn:jboss:domain:keycloak:1.1"
+ xmlns:sec="urn:jboss:domain:security:1.2"
+ version="2.0"
+ exclude-result-prefixes="xalan j ds k sec">
+
+ <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" xalan:indent-amount="4" standalone="no"/>
+ <xsl:strip-space elements="*"/>
+
+
+ <xsl:variable name="nsDS" select="'urn:jboss:domain:datasources:'"/>
+
+ <xsl:param name="pool.name" select="'KeycloakDS'"/>
+ <xsl:param name="jdbc.url" />
+
+ <!-- replace JDBC URL -->
+ <xsl:template match="//*[local-name()='subsystem' and starts-with(namespace-uri(), $nsDS)]
+ /*[local-name()='datasources' and starts-with(namespace-uri(), $nsDS)]
+ /*[local-name()='datasource' and starts-with(namespace-uri(), $nsDS) and @pool-name=$pool.name]
+ /*[local-name()='connection-url' and starts-with(namespace-uri(), $nsDS)]">
+ <connection-url>
+ <xsl:value-of select="$jdbc.url"/>
+ </connection-url>
+ </xsl:template>
+
+ <!-- Copy everything else. -->
+ <xsl:template match="@*|node()">
+ <xsl:copy>
+ <xsl:apply-templates select="@*|node()" />
+ </xsl:copy>
+ </xsl:template>
+
+</xsl:stylesheet>
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/servers/eap7/src/main/xslt/ispn-cache-owners.xsl b/testsuite/integration-arquillian/servers/eap7/src/main/xslt/ispn-cache-owners.xsl
new file mode 100644
index 0000000..7237d89
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/eap7/src/main/xslt/ispn-cache-owners.xsl
@@ -0,0 +1,40 @@
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:xalan="http://xml.apache.org/xalan"
+ xmlns:j="urn:jboss:domain:4.0"
+ xmlns:i="urn:jboss:domain:infinispan:4.0"
+ version="2.0"
+ exclude-result-prefixes="xalan i">
+
+ <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" xalan:indent-amount="4" standalone="no"/>
+ <xsl:strip-space elements="*"/>
+
+ <xsl:variable name="nsDS" select="'urn:jboss:domain:datasources:'"/>
+
+ <xsl:param name="sessionCacheOwners" select="'1'"/>
+ <xsl:param name="offlineSessionCacheOwners" select="'1'"/>
+ <xsl:param name="loginFailureCacheOwners" select="'1'"/>
+
+ <xsl:template match="//i:cache-container/i:distributed-cache[@name='sessions']/@owners">
+ <xsl:attribute name="owners">
+ <xsl:value-of select="$sessionCacheOwners"/>
+ </xsl:attribute>
+ </xsl:template>
+ <xsl:template match="//i:cache-container/i:distributed-cache[@name='offlineSessions']/@owners">
+ <xsl:attribute name="owners">
+ <xsl:value-of select="$offlineSessionCacheOwners"/>
+ </xsl:attribute>
+ </xsl:template>
+ <xsl:template match="//i:cache-container/i:distributed-cache[@name='loginFailures']/@owners">
+ <xsl:attribute name="owners">
+ <xsl:value-of select="$loginFailureCacheOwners"/>
+ </xsl:attribute>
+ </xsl:template>
+
+ <!-- Copy everything else. -->
+ <xsl:template match="@*|node()">
+ <xsl:copy>
+ <xsl:apply-templates select="@*|node()" />
+ </xsl:copy>
+ </xsl:template>
+
+</xsl:stylesheet>
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/servers/pom.xml b/testsuite/integration-arquillian/servers/pom.xml
index 1546e35..81affa2 100644
--- a/testsuite/integration-arquillian/servers/pom.xml
+++ b/testsuite/integration-arquillian/servers/pom.xml
@@ -21,7 +21,7 @@
<parent>
<groupId>org.keycloak.testsuite</groupId>
<artifactId>integration-arquillian</artifactId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -54,6 +54,13 @@
<module>eap7</module>
</modules>
</profile>
+ <profile>
+ <id>auth-server-eap7-cluster</id>
+ <modules>
+ <module>eap7</module>
+ <module>wildfly-balancer</module>
+ </modules>
+ </profile>
</profiles>
</project>
diff --git a/testsuite/integration-arquillian/servers/wildfly/pom.xml b/testsuite/integration-arquillian/servers/wildfly/pom.xml
index bd822a1..5607d94 100644
--- a/testsuite/integration-arquillian/servers/wildfly/pom.xml
+++ b/testsuite/integration-arquillian/servers/wildfly/pom.xml
@@ -21,7 +21,7 @@
<parent>
<groupId>org.keycloak.testsuite</groupId>
<artifactId>integration-arquillian-servers</artifactId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
diff --git a/testsuite/integration-arquillian/servers/wildfly-balancer/pom.xml b/testsuite/integration-arquillian/servers/wildfly-balancer/pom.xml
index 4a03364..84d6638 100644
--- a/testsuite/integration-arquillian/servers/wildfly-balancer/pom.xml
+++ b/testsuite/integration-arquillian/servers/wildfly-balancer/pom.xml
@@ -21,7 +21,7 @@
<parent>
<groupId>org.keycloak.testsuite</groupId>
<artifactId>integration-arquillian-servers</artifactId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
diff --git a/testsuite/integration-arquillian/tests/base/pom.xml b/testsuite/integration-arquillian/tests/base/pom.xml
index 61b3604..70bf0df 100644
--- a/testsuite/integration-arquillian/tests/base/pom.xml
+++ b/testsuite/integration-arquillian/tests/base/pom.xml
@@ -21,7 +21,7 @@
<parent>
<groupId>org.keycloak.testsuite</groupId>
<artifactId>integration-arquillian-tests</artifactId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/admin/ApiUtil.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/admin/ApiUtil.java
index 275fd9c..99fda36 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/admin/ApiUtil.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/admin/ApiUtil.java
@@ -30,6 +30,8 @@ import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import javax.ws.rs.core.Response.Status;
+import javax.ws.rs.core.Response.StatusType;
import org.apache.commons.lang.builder.EqualsBuilder;
import static org.keycloak.representations.idm.CredentialRepresentation.PASSWORD;
@@ -44,6 +46,11 @@ public class ApiUtil {
public static String getCreatedId(Response response) {
URI location = response.getLocation();
+ if (!response.getStatusInfo().equals(Status.CREATED)) {
+ StatusType statusInfo = response.getStatusInfo();
+ throw new RuntimeException("Create method returned status " +
+ statusInfo.getReasonPhrase() + " (Code: " + statusInfo.getStatusCode() + "); expected status: Created (201)");
+ }
if (location == null) {
return null;
}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/KeycloakArquillianExtension.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/KeycloakArquillianExtension.java
index 275974f..63f5436 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/KeycloakArquillianExtension.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/KeycloakArquillianExtension.java
@@ -23,6 +23,7 @@ import org.jboss.arquillian.container.test.impl.enricher.resource.URLResourcePro
import org.jboss.arquillian.container.test.spi.client.deployment.ApplicationArchiveProcessor;
import org.jboss.arquillian.container.test.spi.client.deployment.DeploymentScenarioGenerator;
import org.jboss.arquillian.core.spi.LoadableExtension;
+import org.jboss.arquillian.graphene.location.ContainerCustomizableURLResourceProvider;
import org.jboss.arquillian.graphene.location.CustomizableURLResourceProvider;
import org.jboss.arquillian.test.spi.enricher.resource.ResourceProvider;
import org.jboss.arquillian.test.spi.execution.TestExecutionDecider;
@@ -60,7 +61,8 @@ public class KeycloakArquillianExtension implements LoadableExtension {
builder
.override(ResourceProvider.class, URLResourceProvider.class, URLProvider.class)
- .override(ResourceProvider.class, CustomizableURLResourceProvider.class, URLProvider.class);
+ .override(ResourceProvider.class, CustomizableURLResourceProvider.class, URLProvider.class)
+ .override(ResourceProvider.class, ContainerCustomizableURLResourceProvider.class, URLProvider.class);
}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/TestContext.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/TestContext.java
index 673bc7c..fc63b22 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/TestContext.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/TestContext.java
@@ -17,7 +17,9 @@
package org.keycloak.testsuite.arquillian;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
/**
*
@@ -33,6 +35,8 @@ public final class TestContext {
private final List<ContainerInfo> appServerBackendsInfo = new ArrayList<>();
private boolean adminLoggedIn;
+
+ private final Map customContext = new HashMap<>();
public TestContext(SuiteContext suiteContext, Class testClass) {
this.suiteContext = suiteContext;
@@ -88,4 +92,12 @@ public final class TestContext {
+ (isAdapterTest() ? "App server container: " + getAppServerInfo() + "\n" : "");
}
+ public Object getCustomValue(Object key) {
+ return customContext.get(key);
+ }
+
+ public void setCustomValue(Object key, Object value) {
+ customContext.put(key, value);
+ }
+
}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/account/AccountManagement.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/account/AccountManagement.java
index ba45448..0de5299 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/account/AccountManagement.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/account/AccountManagement.java
@@ -18,8 +18,8 @@ package org.keycloak.testsuite.auth.page.account;
import javax.ws.rs.core.UriBuilder;
import org.jboss.arquillian.graphene.findby.FindByJQuery;
-import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.testsuite.auth.page.AuthRealm;
+import org.keycloak.testsuite.page.PageWithLogOutAction;
import static org.keycloak.testsuite.util.WaitUtils.waitUntilElement;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
@@ -29,7 +29,7 @@ import org.openqa.selenium.support.FindBy;
* @author <a href="mailto:pmensik@redhat.com">Petr Mensik</a>
* @author tkyjovsk
*/
-public class AccountManagement extends AuthRealm {
+public class AccountManagement extends AuthRealm implements PageWithLogOutAction {
@Override
public UriBuilder createUriBuilder() {
@@ -76,7 +76,12 @@ public class AccountManagement extends AuthRealm {
public void signOut() {
signOutLink.click();
}
-
+
+ @Override
+ public void logOut() {
+ signOut();
+ }
+
public void account() {
accountLink.click();
}
@@ -108,4 +113,5 @@ public class AccountManagement extends AuthRealm {
public void waitForAccountLinkPresent() {
waitUntilElement(accountLink, "account link should be present").is().present();
}
+
}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/login/Login.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/login/Login.java
index f489f00..dcee089 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/login/Login.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/login/Login.java
@@ -42,7 +42,7 @@ public abstract class Login extends AuthRealm {
@Override
public UriBuilder createUriBuilder() {
return super.createUriBuilder()
- .path((getProtocol().equals(OIDC) || getProtocol().equals(SAML)) ? "protocol/" : "" + "{" + PROTOCOL + "}" + (getProtocol().equals(OIDC) ? "/auth" : ""));
+ .path(((getProtocol().equals(OIDC) || getProtocol().equals(SAML)) ? "protocol/" : "") + "{" + PROTOCOL + "}" + (getProtocol().equals(OIDC) ? "/auth" : ""));
}
public void setProtocol(String protocol) {
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/login/SAMLIDPInitiatedLogin.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/login/SAMLIDPInitiatedLogin.java
new file mode 100644
index 0000000..66cf075
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/login/SAMLIDPInitiatedLogin.java
@@ -0,0 +1,19 @@
+package org.keycloak.testsuite.auth.page.login;
+
+import javax.ws.rs.core.UriBuilder;
+
+/**
+ * @author mhajas
+ */
+public class SAMLIDPInitiatedLogin extends SAMLRedirectLogin {
+
+ public void setUrlName(String urlName) {
+ setUriParameter("clientUrlName", urlName);
+ }
+
+ @Override
+ public UriBuilder createUriBuilder() {
+ return super.createUriBuilder().path("clients/{clientUrlName}");
+ }
+}
+
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/login/TermsAndConditions.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/login/TermsAndConditions.java
new file mode 100644
index 0000000..a4c425e
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/login/TermsAndConditions.java
@@ -0,0 +1,60 @@
+/*
+ * 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.auth.page.login;
+
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class TermsAndConditions extends LoginActions {
+
+ @FindBy(id = "kc-accept")
+ private WebElement acceptButton;
+
+ @FindBy(id = "kc-decline")
+ private WebElement declineButton;
+
+ @FindBy(id = "kc-terms-text")
+ private WebElement textElem;
+
+ @Override
+ public boolean isCurrent() {
+ return driver.getTitle().equals("Terms and Conditions");
+ }
+
+ public void acceptTerms() {
+ acceptButton.click();
+ }
+ public void declineTerms() {
+ declineButton.click();
+ }
+
+ public String getAcceptButtonText() {
+ return acceptButton.getAttribute("value");
+ }
+
+ public String getDeclineButtonText() {
+ return declineButton.getAttribute("value");
+ }
+
+ public String getText() {
+ return textElem.getText();
+ }
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/WelcomePage.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/WelcomePage.java
index 72d389c..9f34a40 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/WelcomePage.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/WelcomePage.java
@@ -17,6 +17,7 @@
package org.keycloak.testsuite.auth.page;
+import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
@@ -38,7 +39,8 @@ public class WelcomePage extends AuthServer {
private WebElement createButton;
public boolean isPasswordSet() {
- return !driver.getPageSource().contains("Please create an initial admin user to get started.");
+ return !(driver.getPageSource().contains("Please create an initial admin user to get started.") ||
+ driver.getPageSource().contains("You need local access to create the initial admin user."));
}
public void setPassword(String username, String password) {
@@ -58,4 +60,8 @@ public class WelcomePage extends AuthServer {
}
}
+ public void navigateToAdminConsole() {
+ driver.findElement(By.linkText("Administration Console")).click();
+ }
+
}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/AdminConsole.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/AdminConsole.java
index 18a5352..c46b59f 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/AdminConsole.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/AdminConsole.java
@@ -25,6 +25,7 @@ import static org.keycloak.testsuite.auth.page.AuthRealm.MASTER;
import org.keycloak.testsuite.auth.page.login.PageWithLoginUrl;
import org.keycloak.testsuite.console.page.fragment.Menu;
import org.keycloak.testsuite.console.page.fragment.ModalDialog;
+import org.keycloak.testsuite.page.PageWithLogOutAction;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
@@ -32,8 +33,8 @@ import org.openqa.selenium.support.FindBy;
*
* @author Petr Mensik
*/
-public class AdminConsole extends AuthServer implements PageWithLoginUrl {
-
+public class AdminConsole extends AuthServer implements PageWithLoginUrl, PageWithLogOutAction {
+
public static final String ADMIN_REALM = "adminRealm";
public AdminConsole() {
@@ -56,7 +57,7 @@ public class AdminConsole extends AuthServer implements PageWithLoginUrl {
@Page
private Menu menu;
-
+
@FindBy(xpath = "//div[@class='modal-dialog']")
protected ModalDialog modalDialog;
@@ -79,7 +80,8 @@ public class AdminConsole extends AuthServer implements PageWithLoginUrl {
@FindBy(css = "navbar-brand")
protected WebElement brandLink;
-
+
+ @Override
public void logOut() {
menu.logOut();
}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/page/PageWithLogOutAction.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/page/PageWithLogOutAction.java
new file mode 100644
index 0000000..33fa4fc
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/page/PageWithLogOutAction.java
@@ -0,0 +1,11 @@
+package org.keycloak.testsuite.page;
+
+/**
+ *
+ * @author tkyjovsk
+ */
+public interface PageWithLogOutAction {
+
+ public void logOut();
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/IOUtil.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/IOUtil.java
index 23103d3..d872914 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/IOUtil.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/IOUtil.java
@@ -43,6 +43,8 @@ public class IOUtil {
private static final Logger log = Logger.getLogger(IOUtil.class);
+ public static final File PROJECT_BUILD_DIRECTORY = new File(System.getProperty("project.build.directory", "target"));
+
public static <T> T loadJson(InputStream is, Class<T> type) {
try {
return JsonSerialization.readValue(is, type);
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/LogChecker.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/LogChecker.java
index 8e16b34..ac1b059 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/LogChecker.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/LogChecker.java
@@ -1,10 +1,11 @@
package org.keycloak.testsuite.util;
-import java.io.File;
-import java.io.IOException;
import org.apache.commons.io.FileUtils;
import org.jboss.logging.Logger;
+import java.io.File;
+import java.io.IOException;
+
/**
*
* @author vramik
@@ -14,20 +15,30 @@ public class LogChecker {
private static final Logger log = Logger.getLogger(LogChecker.class);
+ private static final String[] IGNORED = new String[] { ".*Jetty ALPN support not found.*" };
+
public static void checkServerLog(File logFile) throws IOException {
log.info(String.format("Checking server log: '%s'", logFile.getAbsolutePath()));
- String logContent = FileUtils.readFileToString(logFile);
-
- boolean containsError
- = logContent.contains("ERROR")
- || logContent.contains("SEVERE")
- || logContent.contains("Exception ");
-
- //There is expected string "Exception" in server log: Adding provider
- //singleton org.keycloak.services.resources.ModelExceptionMapper
- if (containsError) {
- throw new RuntimeException(String.format("Server log file contains ERROR: '%s'", logFile.getPath()));
+ String[] logContent = FileUtils.readFileToString(logFile).split("\n");
+
+ for (String log : logContent) {
+ boolean containsError = log.contains("ERROR") || log.contains("SEVERE") || log.contains("Exception ");
+ //There is expected string "Exception" in server log: Adding provider
+ //singleton org.keycloak.services.resources.ModelExceptionMapper
+ if (containsError) {
+ boolean ignore = false;
+ for (String i : IGNORED) {
+ if (log.matches(i)) {
+ ignore = true;
+ break;
+ }
+ }
+ if (!ignore) {
+ throw new RuntimeException(String.format("Server log file contains ERROR: '%s'", log));
+ }
+ }
}
+
}
public static void checkJBossServerLog(String jbossHome) throws IOException {
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/Timer.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/Timer.java
index 1b2ec65..082a2a4 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/Timer.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/Timer.java
@@ -14,15 +14,28 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package org.keycloak.testsuite.util;
-import java.text.MessageFormat;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Date;
-import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.TreeMap;
+import org.apache.commons.io.IOUtils;
+import org.jboss.logging.Logger;
+import org.jfree.chart.ChartFactory;
+import org.jfree.chart.ChartUtilities;
+import org.jfree.chart.JFreeChart;
+import org.jfree.chart.plot.PlotOrientation;
+import org.jfree.data.xy.XYSeries;
+import org.jfree.data.xy.XYSeriesCollection;
+import static org.keycloak.testsuite.util.IOUtil.PROJECT_BUILD_DIRECTORY;
+import static org.jgroups.util.Util.assertTrue;
/**
*
@@ -30,45 +43,129 @@ import java.util.Map;
*/
public class Timer {
- private static Long time;
+ public static final Timer DEFAULT = new Timer();
- private static final Map<String, List<Long>> stats = new HashMap<>();
+ protected final Logger log = Logger.getLogger(Timer.class);
- public static void time() {
- time = new Date().getTime();
- }
+ protected static final File DATA_DIR = new File(PROJECT_BUILD_DIRECTORY, "stats/data");
+ protected static final File CHARTS_DIR = new File(PROJECT_BUILD_DIRECTORY, "stats/charts");
+
+ public static final String DEFAULT_OPERATION = "DEFAULT_OPERATION";
- public static void time(String operation) {
+ private Long time;
+ private String operation = DEFAULT_OPERATION;
+ private final Map<String, List<Long>> stats = new TreeMap<>();
+
+ public long elapsedTime() {
+ long elapsedTime = 0;
if (time == null) {
- System.out.println(MessageFormat.format("Starting timer for operation {0}", operation));
- time();
} else {
- long timeOrig = time;
- time();
- logOperation(operation, time - timeOrig);
- System.out.println(MessageFormat.format("Operation {0} took {1} ms", operation, time - timeOrig));
+ elapsedTime = new Date().getTime() - time;
}
+ return elapsedTime;
+ }
+
+ public void reset() {
+ reset(operation); // log last operation
}
- private static void logOperation(String operation, long delta) {
+ public void reset(String operation) {
+ reset(operation, true);
+ }
+
+ public void reset(String newOperation, boolean logOperationOnChange) {
+ if (time != null) {
+ if (operation.equals(newOperation) || logOperationOnChange) {
+ logOperation(operation, elapsedTime());
+ }
+ }
+ time = new Date().getTime();
+ if (!operation.equals(newOperation)) {
+ operation = newOperation;
+ log.info(String.format("Operation '%s' started.", newOperation));
+ }
+ }
+
+ private void logOperation(String operation, long duration) {
if (!stats.containsKey(operation)) {
stats.put(operation, new ArrayList<Long>());
}
- stats.get(operation).add(delta);
+ stats.get(operation).add(duration);
+ log.info(String.format("Operation '%s' took: %s ms", operation, duration));
+ }
+
+ public void clearStats() {
+ clearStats(true, true, true);
}
- public static void printStats() {
- if (!stats.isEmpty()) {
- System.out.println("OPERATION STATS:");
+ public void clearStats(boolean logStats, boolean saveData, boolean saveCharts) {
+ if (logStats) {
+ log.info("Timer Statistics:");
+ for (String op : stats.keySet()) {
+ long sum = 0;
+ for (Long duration : stats.get(op)) {
+ sum += duration;
+ }
+ log.info(String.format("Operation '%s' average: %s ms", op, sum / stats.get(op).size()));
+ }
}
- for (String op : stats.keySet()) {
- long sum = 0;
- for (Long t : stats.get(op)) {
- sum += t;
+ if (PROJECT_BUILD_DIRECTORY.exists()) {
+ DATA_DIR.mkdirs();
+ CHARTS_DIR.mkdirs();
+ for (String op : stats.keySet()) {
+ if (saveData) {
+ saveData(op);
+ }
+ if (saveCharts) {
+ saveChart(op);
+ }
}
- System.out.println(MessageFormat.format("Operation {0} average time: {1,number,#} ms", op, sum / stats.get(op).size()));
}
stats.clear();
}
+ private void saveData(String op) {
+ try {
+ File f = new File(DATA_DIR, op.replace(" ", "_") + ".txt");
+ if (!f.createNewFile()) {
+ throw new IOException("Couldn't create file: " + f);
+ }
+ OutputStream stream = new BufferedOutputStream(new FileOutputStream(f));
+ for (Long duration : stats.get(op)) {
+ IOUtils.write(duration.toString(), stream);
+ IOUtils.write("\n", stream);
+ }
+ stream.flush();
+ IOUtils.closeQuietly(stream);
+ } catch (IOException ex) {
+ log.error("Unable to save data for operation '" + op + "'", ex);
+ }
+ }
+
+ private void saveChart(String op) {
+ XYSeries series = new XYSeries(op);
+ int i = 0;
+ for (Long duration : stats.get(op)) {
+ series.add(++i, duration);
+ }
+ final XYSeriesCollection data = new XYSeriesCollection(series);
+ final JFreeChart chart = ChartFactory.createXYLineChart(
+ op,
+ "Operations",
+ "Duration (ms)",
+ data,
+ PlotOrientation.VERTICAL,
+ true,
+ true,
+ false
+ );
+ try {
+ ChartUtilities.saveChartAsPNG(
+ new File(CHARTS_DIR, op.replace(" ", "_") + ".png"),
+ chart, 640, 480);
+ } catch (IOException ex) {
+ log.warn("Unable to save chart for operation '" + op + "'.");
+ }
+ }
+
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java
index 989b105..5c3a906 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java
@@ -20,6 +20,7 @@ import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.PropertiesConfiguration;
import org.keycloak.testsuite.arquillian.TestContext;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
import javax.ws.rs.NotFoundException;
@@ -33,12 +34,16 @@ import org.junit.After;
import org.junit.Before;
import org.junit.runner.RunWith;
import org.keycloak.admin.client.Keycloak;
+import org.keycloak.admin.client.resource.AuthenticationManagementResource;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.admin.client.resource.RealmsResource;
+import org.keycloak.admin.client.resource.UserResource;
+import org.keycloak.admin.client.resource.UsersResource;
import org.keycloak.models.Constants;
import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.representations.idm.RequiredActionProviderRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
-import static org.keycloak.testsuite.admin.Users.setPasswordFor;
+import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.arquillian.AuthServerTestEnricher;
import org.keycloak.testsuite.arquillian.SuiteContext;
import org.keycloak.testsuite.auth.page.WelcomePage;
@@ -52,8 +57,8 @@ import static org.keycloak.testsuite.auth.page.AuthRealm.MASTER;
import org.keycloak.testsuite.auth.page.account.Account;
import org.keycloak.testsuite.auth.page.login.OIDCLogin;
import org.keycloak.testsuite.auth.page.login.UpdatePassword;
-import org.keycloak.testsuite.util.Timer;
import org.keycloak.testsuite.util.WaitUtils;
+import static org.keycloak.testsuite.admin.Users.setPasswordFor;
/**
*
@@ -128,7 +133,6 @@ public abstract class AbstractKeycloakTest {
public void afterAbstractKeycloakTest() {
// removeTestRealms(); // keeping test realms after test to be able to inspect failures, instead deleting existing realms before import
// keycloak.close(); // keeping admin connection open
- Timer.printStats();
}
private void updateMasterAdminPassword() {
@@ -209,6 +213,65 @@ public abstract class AbstractKeycloakTest {
return adminClient.realms();
}
+ public void createRealm(String realm) {
+ try {
+ RealmResource realmResource = adminClient.realms().realm(realm);
+ // Throws NotFoundException in case the realm does not exist! Ugly but there
+ // does not seem to be a way to this just by asking.
+ RealmRepresentation realmRepresentation = realmResource.toRepresentation();
+ } catch (NotFoundException ex) {
+ RealmRepresentation realmRepresentation = new RealmRepresentation();
+ realmRepresentation.setRealm(realm);
+ realmRepresentation.setEnabled(true);
+ realmRepresentation.setRegistrationAllowed(true);
+ adminClient.realms().create(realmRepresentation);
+
+// List<RequiredActionProviderRepresentation> requiredActions = adminClient.realm(realm).flows().getRequiredActions();
+// for (RequiredActionProviderRepresentation a : requiredActions) {
+// a.setEnabled(false);
+// a.setDefaultAction(false);
+// adminClient.realm(realm).flows().updateRequiredAction(a.getAlias(), a);
+// }
+ }
+ }
+
+ public String createUser(String realm, String username, String password, String ... requiredActions) {
+ List<String> requiredUserActions = Arrays.asList(requiredActions);
+
+ UserRepresentation homer = new UserRepresentation();
+ homer.setEnabled(true);
+ homer.setUsername(username);
+ homer.setRequiredActions(requiredUserActions);
+
+ return ApiUtil.createUserAndResetPasswordWithAdminClient(adminClient.realm(realm), homer, password);
+ }
+
+ public void setRequiredActionEnabled(String realm, String requiredAction, boolean enabled, boolean defaultAction) {
+ AuthenticationManagementResource managementResource = adminClient.realm(realm).flows();
+
+ RequiredActionProviderRepresentation action = managementResource.getRequiredAction(requiredAction);
+ action.setEnabled(enabled);
+ action.setDefaultAction(defaultAction);
+
+ managementResource.updateRequiredAction(requiredAction, action);
+ }
+
+ public void setRequiredActionEnabled(String realm, String userId, String requiredAction, boolean enabled) {
+ UsersResource usersResource = adminClient.realm(realm).users();
+
+ UserResource userResource = usersResource.get(userId);
+ UserRepresentation userRepresentation = userResource.toRepresentation();
+
+ List<String> requiredActions = userRepresentation.getRequiredActions();
+ if (enabled && !requiredActions.contains(requiredAction)) {
+ requiredActions.add(requiredAction);
+ } else if (!enabled && requiredActions.contains(requiredAction)) {
+ requiredActions.remove(requiredAction);
+ }
+
+ userResource.update(userRepresentation);
+ }
+
private void loadConstantsProperties() throws ConfigurationException {
constantsProperties = new PropertiesConfiguration("test-constants.properties");
constantsProperties.setThrowExceptionOnMissing(true);
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/ChangePasswordTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/ChangePasswordTest.java
index 8e85307..ce1900d 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/ChangePasswordTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/ChangePasswordTest.java
@@ -62,6 +62,9 @@ public class ChangePasswordTest extends AbstractAccountManagementTest {
testRealmChangePasswordPage.changePasswords(correctPassword, NEW_PASSWORD, NEW_PASSWORD + "-mismatch");
assertAlertError();
+
+ testRealmChangePasswordPage.changePasswords(correctPassword, " ", " ");
+ assertAlertError();
}
@Test
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/RegistrationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/RegistrationTest.java
index a49d823..1e3e916 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/RegistrationTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/RegistrationTest.java
@@ -17,10 +17,13 @@
package org.keycloak.testsuite.account;
import org.jboss.arquillian.graphene.page.Page;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
import org.junit.Test;
import org.keycloak.testsuite.auth.page.login.Registration;
-import static org.junit.Assert.*;
import org.junit.Before;
import static org.keycloak.representations.idm.CredentialRepresentation.PASSWORD;
import org.keycloak.representations.idm.RealmRepresentation;
@@ -58,6 +61,7 @@ public class RegistrationTest extends AbstractAccountManagementTest {
setPasswordFor(newUser, PASSWORD);
testRealmAccountManagementPage.navigateTo();
+ assertTrue("Registration should be allowed.", testRealmResource().toRepresentation().isRegistrationAllowed());
testRealmLoginPage.form().register();
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/ResetCredentialsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/ResetCredentialsTest.java
old mode 100755
new mode 100644
index 00c0549..83a4b0c
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/ResetCredentialsTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/ResetCredentialsTest.java
@@ -19,6 +19,7 @@ package org.keycloak.testsuite.account;
import org.jboss.arquillian.graphene.page.Page;
import org.junit.AfterClass;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
import org.junit.Before;
import org.junit.Test;
import org.keycloak.representations.idm.RealmRepresentation;
@@ -61,6 +62,7 @@ public class ResetCredentialsTest extends AbstractAccountManagementTest {
}
testRealmAccountManagementPage.navigateTo();
+ assertTrue("Reset password should be allowed.", testRealmResource().toRepresentation().isResetPasswordAllowed());
testRealmLoginPage.form().forgotPassword();
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractDemoExampleAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractDemoExampleAdapterTest.java
index 5619885..fafbf29 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractDemoExampleAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractDemoExampleAdapterTest.java
@@ -260,7 +260,7 @@ public abstract class AbstractDemoExampleAdapterTest extends AbstractExampleAdap
resultList.get(0).findElement(By.xpath(".//td[text()='REVOKE_GRANT']"));
resultList.get(0).findElement(By.xpath(".//td[text()='Client']/../td[text()='account']"));
- resultList.get(0).findElement(By.xpath(".//td[text()='IP Address']/../td[text()='127.0.0.1']"));
+ resultList.get(0).findElement(By.xpath(".//td[text()='IP Address']/../td[text()='127.0.0.1' or text()='0:0:0:0:0:0:0:1']"));
resultList.get(0).findElement(By.xpath(".//td[text()='revoked_client']/../td[text()='customer-portal']"));
loginEventsPage.table().reset();
@@ -272,7 +272,7 @@ public abstract class AbstractDemoExampleAdapterTest extends AbstractExampleAdap
resultList.get(0).findElement(By.xpath(".//td[text()='LOGIN']"));
resultList.get(0).findElement(By.xpath(".//td[text()='Client']/../td[text()='customer-portal']"));
- resultList.get(0).findElement(By.xpath(".//td[text()='IP Address']/../td[text()='127.0.0.1']"));
+ resultList.get(0).findElement(By.xpath(".//td[text()='IP Address']/../td[text()='127.0.0.1' or text()='0:0:0:0:0:0:0:1']"));
resultList.get(0).findElement(By.xpath(".//td[text()='username']/../td[text()='bburke@redhat.com']"));
resultList.get(0).findElement(By.xpath(".//td[text()='consent']/../td[text()='consent_granted']"));
}
@@ -317,7 +317,7 @@ public abstract class AbstractDemoExampleAdapterTest extends AbstractExampleAdap
resultList.get(0).findElement(By.xpath(".//td[text()='LOGOUT']"));
resultList.get(0).findElement(By.xpath(".//td[text()='Client']/../td[text()='']"));
- resultList.get(0).findElement(By.xpath(".//td[text()='IP Address']/../td[text()='127.0.0.1']"));
+ resultList.get(0).findElement(By.xpath(".//td[text()='IP Address']/../td[text()='127.0.0.1' or text()='0:0:0:0:0:0:0:1']"));
loginEventsPage.table().reset();
loginEventsPage.table().filterForm().addEventType("LOGIN");
@@ -328,7 +328,7 @@ public abstract class AbstractDemoExampleAdapterTest extends AbstractExampleAdap
resultList.get(0).findElement(By.xpath(".//td[text()='LOGIN']"));
resultList.get(0).findElement(By.xpath(".//td[text()='Client']/../td[text()='customer-portal']"));
- resultList.get(0).findElement(By.xpath(".//td[text()='IP Address']/../td[text()='127.0.0.1']"));
+ resultList.get(0).findElement(By.xpath(".//td[text()='IP Address']/../td[text()='127.0.0.1' or text()='0:0:0:0:0:0:0:1']"));
resultList.get(0).findElement(By.xpath(".//td[text()='username']/../td[text()='bburke@redhat.com']"));
loginEventsPage.table().reset();
@@ -339,7 +339,7 @@ public abstract class AbstractDemoExampleAdapterTest extends AbstractExampleAdap
assertEquals(1, resultList.size());
resultList.get(0).findElement(By.xpath(".//td[text()='CODE_TO_TOKEN']"));
resultList.get(0).findElement(By.xpath(".//td[text()='Client']/../td[text()='customer-portal']"));
- resultList.get(0).findElement(By.xpath(".//td[text()='IP Address']/../td[text()='127.0.0.1']"));
+ resultList.get(0).findElement(By.xpath(".//td[text()='IP Address']/../td[text()='127.0.0.1' or text()='0:0:0:0:0:0:0:1']"));
resultList.get(0).findElement(By.xpath(".//td[text()='refresh_token_type']/../td[text()='Refresh']"));
String serverLogPath = null;
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractDemoServletsAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractDemoServletsAdapterTest.java
old mode 100755
new mode 100644
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLServletsAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLServletsAdapterTest.java
index e07b02f..62ac226 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLServletsAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLServletsAdapterTest.java
@@ -27,6 +27,7 @@ import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.testsuite.adapter.AbstractServletsAdapterTest;
import org.keycloak.testsuite.adapter.page.*;
import org.keycloak.testsuite.admin.ApiUtil;
+import org.keycloak.testsuite.auth.page.login.SAMLIDPInitiatedLogin;
import org.keycloak.testsuite.util.IOUtil;
import org.w3c.dom.Document;
@@ -81,6 +82,9 @@ public abstract class AbstractSAMLServletsAdapterTest extends AbstractServletsAd
@Page
private SalesPostSigTransientServlet salesPostSigTransientServletPage;
+ @Page
+ private SAMLIDPInitiatedLogin samlidpInitiatedLogin;
+
@Deployment(name = BadClientSalesPostSigServlet.DEPLOYMENT_NAME)
protected static WebArchive badClientSalesPostSig() {
return samlServletDeployment(BadClientSalesPostSigServlet.DEPLOYMENT_NAME, SendUsernameServlet.class);
@@ -220,7 +224,7 @@ public abstract class AbstractSAMLServletsAdapterTest extends AbstractServletsAd
assertCurrentUrlStartsWith(testRealmSAMLRedirectLoginPage);
salesPostPassiveServletPage.navigateTo();
- assertTrue(driver.getPageSource().contains("Forbidden") || driver.getPageSource().contains("<body></body>"));
+ assertTrue(driver.getPageSource().contains("Forbidden") || driver.getPageSource().contains("<body></body>") || driver.getPageSource().contains("<body><pre></pre></body>"));
salesPostSigEmailServletPage.navigateTo();
assertCurrentUrlStartsWith(testRealmSAMLPostLoginPage);
@@ -367,7 +371,7 @@ public abstract class AbstractSAMLServletsAdapterTest extends AbstractServletsAd
public void salesPostPassiveTest() {
salesPostPassiveServletPage.navigateTo();
//Different 403 status page on EAP and Wildfly
- assertTrue(driver.getPageSource().contains("Forbidden") || driver.getPageSource().contains("<body></body>"));
+ assertTrue(driver.getPageSource().contains("Forbidden") || driver.getPageSource().contains("<body></body>") || driver.getPageSource().contains("<body><pre></pre></body>"));
salesPostServletPage.navigateTo();
testRealmSAMLRedirectLoginPage.form().login(bburkeUser);
@@ -378,7 +382,7 @@ public abstract class AbstractSAMLServletsAdapterTest extends AbstractServletsAd
salesPostPassiveServletPage.logout();
salesPostPassiveServletPage.navigateTo();
//Different 403 status page on EAP and Wildfly
- assertTrue(driver.getPageSource().contains("Forbidden") || driver.getPageSource().contains("<body></body>"));
+ assertTrue(driver.getPageSource().contains("Forbidden") || driver.getPageSource().contains("<body></body>") || driver.getPageSource().contains("<body><pre></pre></body>"));
salesPostServletPage.navigateTo();
testRealmSAMLRedirectLoginPage.form().login("unauthorized", "password");
@@ -458,4 +462,37 @@ public abstract class AbstractSAMLServletsAdapterTest extends AbstractServletsAd
assertTrue(driver.getPageSource().contains("Forbidden") || driver.getPageSource().contains("Status 403"));
salesPostSigTransientServletPage.logout();
}
+
+ @Test
+ public void idpInitiatedLogin() {
+ samlidpInitiatedLogin.setAuthRealm(SAMLSERVLETDEMO);
+ samlidpInitiatedLogin.setUrlName("employee2");
+ samlidpInitiatedLogin.navigateTo();
+ samlidpInitiatedLogin.form().login(bburkeUser);
+
+ employee2ServletPage.navigateTo();
+ assertTrue(driver.getPageSource().contains("principal=bburke"));
+
+ salesPostSigServletPage.navigateTo();
+ assertTrue(driver.getPageSource().contains("principal=bburke"));
+
+ employee2ServletPage.logout();
+ }
+
+ @Test
+ public void idpInitiatedUnauthorizedLoginTest() {
+ samlidpInitiatedLogin.setAuthRealm(SAMLSERVLETDEMO);
+ samlidpInitiatedLogin.setUrlName("employee2");
+ samlidpInitiatedLogin.navigateTo();
+ samlidpInitiatedLogin.form().login("unauthorized","password");
+
+ assertFalse(driver.getPageSource().contains("principal="));
+ assertTrue(driver.getPageSource().contains("Forbidden") || driver.getPageSource().contains("Status 403"));
+
+ employee2ServletPage.navigateTo();
+ assertFalse(driver.getPageSource().contains("principal="));
+ assertTrue(driver.getPageSource().contains("Forbidden") || driver.getPageSource().contains("Status 403"));
+
+ employee2ServletPage.logout();
+ }
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSessionServletAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSessionServletAdapterTest.java
index a027234..0bce8a6 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSessionServletAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSessionServletAdapterTest.java
@@ -23,22 +23,29 @@ import org.jboss.arquillian.drone.api.annotation.Drone;
import org.jboss.arquillian.graphene.page.Page;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.After;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
+
import org.junit.Test;
import org.keycloak.OAuth2Constants;
import org.keycloak.admin.client.resource.ClientResource;
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testsuite.adapter.page.SessionPortal;
+
import static org.keycloak.testsuite.auth.page.AuthRealm.DEMO;
+
import org.keycloak.testsuite.auth.page.account.Sessions;
import org.keycloak.testsuite.auth.page.login.Login;
+
import static org.keycloak.testsuite.admin.ApiUtil.findClientResourceByClientId;
import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlEquals;
import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWithLoginUrlOf;
+
import org.keycloak.testsuite.util.SecondBrowser;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
@@ -156,9 +163,11 @@ public abstract class AbstractSessionServletAdapterTest extends AbstractServlets
public void testAdminApplicationLogout() {
// login as bburke
loginAndCheckSession(driver, testRealmLoginPage);
+
// logout mposolda with admin client
- findClientResourceByClientId(testRealmResource(), "session-portal")
- .logoutUser("mposolda");
+ UserRepresentation mposolda = testRealmResource().users().search("mposolda", null, null, null, null, null).get(0);
+ testRealmResource().users().get(mposolda.getId()).logout();
+
// bburke should be still logged with original httpSession in our browser window
sessionPortalPage.navigateTo();
assertEquals(driver.getCurrentUrl(), sessionPortalPage.toString());
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/AbstractAuthenticationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/AbstractAuthenticationTest.java
index 70013c5..fb7e966 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/AbstractAuthenticationTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/AbstractAuthenticationTest.java
@@ -21,6 +21,7 @@ import org.junit.Assert;
import org.junit.Before;
import org.keycloak.admin.client.resource.AuthenticationManagementResource;
import org.keycloak.admin.client.resource.RealmResource;
+import org.keycloak.representations.idm.AuthenticationExecutionExportRepresentation;
import org.keycloak.representations.idm.AuthenticationExecutionInfoRepresentation;
import org.keycloak.representations.idm.AuthenticationFlowRepresentation;
import org.keycloak.representations.idm.AuthenticatorConfigRepresentation;
@@ -92,14 +93,41 @@ public abstract class AbstractAuthenticationTest extends AbstractKeycloakTest {
Assert.assertEquals("Execution requirement choices - " + actual.getProviderId(), expected.getRequirementChoices(), actual.getRequirementChoices());
}
+ void compareExecution(AuthenticationExecutionExportRepresentation expected, AuthenticationExecutionExportRepresentation actual) {
+ Assert.assertEquals("Execution flowAlias - " + actual.getAuthenticator(), expected.getFlowAlias(), actual.getFlowAlias());
+ Assert.assertEquals("Execution authenticator - " + actual.getAuthenticator(), expected.getAuthenticator(), actual.getAuthenticator());
+ Assert.assertEquals("Execution userSetupAllowed - " + actual.getAuthenticator(), expected.isUserSetupAllowed(), actual.isUserSetupAllowed());
+ Assert.assertEquals("Execution authenticatorFlow - " + actual.getAuthenticator(), expected.isAutheticatorFlow(), actual.isAutheticatorFlow());
+ Assert.assertEquals("Execution authenticatorConfig - " + actual.getAuthenticator(), expected.getAuthenticatorConfig(), actual.getAuthenticatorConfig());
+ Assert.assertEquals("Execution priority - " + actual.getAuthenticator(), expected.getPriority(), actual.getPriority());
+ Assert.assertEquals("Execution requirement - " + actual.getAuthenticator(), expected.getRequirement(), actual.getRequirement());
+ }
+
+ void compareExecutions(List<AuthenticationExecutionExportRepresentation> expected, List<AuthenticationExecutionExportRepresentation> actual) {
+ Assert.assertNotNull("Executions should not be null", actual);
+ Assert.assertEquals("Size", expected.size(), actual.size());
+
+ for (int i = 0; i < expected.size(); i++) {
+ compareExecution(expected.get(i), actual.get(i));
+ }
+ }
+
void compareFlows(AuthenticationFlowRepresentation expected, AuthenticationFlowRepresentation actual) {
Assert.assertEquals("Flow alias", expected.getAlias(), actual.getAlias());
Assert.assertEquals("Flow description", expected.getDescription(), actual.getDescription());
Assert.assertEquals("Flow providerId", expected.getProviderId(), actual.getProviderId());
Assert.assertEquals("Flow top level", expected.isTopLevel(), actual.isTopLevel());
Assert.assertEquals("Flow built-in", expected.isBuiltIn(), actual.isBuiltIn());
- }
+ List<AuthenticationExecutionExportRepresentation> expectedExecs = expected.getAuthenticationExecutions();
+ List<AuthenticationExecutionExportRepresentation> actualExecs = actual.getAuthenticationExecutions();
+
+ if (expectedExecs == null) {
+ Assert.assertTrue("Executions should be null or empty", actualExecs == null || actualExecs.size() == 0);
+ } else {
+ compareExecutions(expectedExecs, actualExecs);
+ }
+ }
AuthenticationFlowRepresentation newFlow(String alias, String description,
String providerId, boolean topLevel, boolean builtIn) {
@@ -112,8 +140,8 @@ public abstract class AbstractAuthenticationTest extends AbstractKeycloakTest {
return flow;
}
- AuthenticationExecutionInfoRepresentation newExecution(String displayName, String providerId, Boolean configurable,
- int level, int index, String requirement, Boolean authFlow, String[] choices) {
+ AuthenticationExecutionInfoRepresentation newExecInfo(String displayName, String providerId, Boolean configurable,
+ int level, int index, String requirement, Boolean authFlow, String[] choices) {
AuthenticationExecutionInfoRepresentation execution = new AuthenticationExecutionInfoRepresentation();
execution.setRequirement(requirement);
@@ -129,6 +157,12 @@ public abstract class AbstractAuthenticationTest extends AbstractKeycloakTest {
return execution;
}
+ void addExecInfo(List<AuthenticationExecutionInfoRepresentation> target, String displayName, String providerId, Boolean configurable,
+ int level, int index, String requirement, Boolean authFlow, String[] choices) {
+
+ AuthenticationExecutionInfoRepresentation exec = newExecInfo(displayName, providerId, configurable, level, index, requirement, authFlow, choices);
+ target.add(exec);
+ }
AuthenticatorConfigRepresentation newConfig(String alias, String[] keyvalues) {
AuthenticatorConfigRepresentation config = new AuthenticatorConfigRepresentation();
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ExecutionTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ExecutionTest.java
index f9c0bba..afe702f 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ExecutionTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ExecutionTest.java
@@ -83,7 +83,7 @@ public class ExecutionTest extends AbstractAuthenticationTest {
response.close();
}
- compareExecution(newExecution("Review Profile", "idp-review-profile", true, 0, 3, DISABLED, null, new String[]{REQUIRED, DISABLED}), exec);
+ compareExecution(newExecInfo("Review Profile", "idp-review-profile", true, 0, 3, DISABLED, null, new String[]{REQUIRED, DISABLED}), exec);
// remove execution
authMgmtResource.removeExecution(exec.getId());
@@ -143,7 +143,7 @@ public class ExecutionTest extends AbstractAuthenticationTest {
// Note: there is no checking in addExecution if requirement is one of requirementChoices
// Thus we can have OPTIONAL which is neither ALTERNATIVE, nor DISABLED
- compareExecution(newExecution("Cookie", "auth-cookie", false, 0, 2, OPTIONAL, null, new String[]{ALTERNATIVE, DISABLED}), exec);
+ compareExecution(newExecInfo("Cookie", "auth-cookie", false, 0, 2, OPTIONAL, null, new String[]{ALTERNATIVE, DISABLED}), exec);
}
@Test
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/FlowTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/FlowTest.java
index 63c262a..d381bec 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/FlowTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/FlowTest.java
@@ -19,12 +19,15 @@ package org.keycloak.testsuite.admin.authentication;
import org.junit.Assert;
import org.junit.Test;
+import org.keycloak.representations.idm.AuthenticationExecutionExportRepresentation;
import org.keycloak.representations.idm.AuthenticationFlowRepresentation;
+import org.keycloak.testsuite.admin.ApiUtil;
import javax.ws.rs.BadRequestException;
import javax.ws.rs.core.Response;
import java.util.HashMap;
import java.util.List;
+import java.util.Map;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
@@ -62,13 +65,52 @@ public class FlowTest extends AbstractAuthenticationTest {
response.close();
}
- // check that new flow is returned
+ // check that new flow is returned in a children list
flows = authMgmtResource.getFlows();
AuthenticationFlowRepresentation found = findFlowByAlias("browser-2", flows);
- Assert.assertNotNull("created flow visible", found);
+ Assert.assertNotNull("created flow visible in parent", found);
compareFlows(newFlow, found);
+ // check that new flow is returned individually
+ AuthenticationFlowRepresentation found2 = authMgmtResource.getFlow(found.getId());
+ Assert.assertNotNull("created flow visible directly", found2);
+ compareFlows(newFlow, found2);
+
+
+ // add execution flow using a different method
+ Map<String, String> data = new HashMap<>();
+ data.put("alias", "SomeFlow");
+ data.put("type", "basic-flow");
+ data.put("description", "Test flow");
+ data.put("provider", "registration-page-form");
+
+ try {
+ authMgmtResource.addExecutionFlow("inexistent-parent-flow-alias", data);
+ Assert.fail("addExecutionFlow for inexistent parent should have failed");
+ } catch (Exception expected) {
+ }
+
+ authMgmtResource.addExecutionFlow("browser-2", data);
+
+ // check that new flow is returned in a children list
+ flows = authMgmtResource.getFlows();
+ found2 = findFlowByAlias("browser-2", flows);
+ Assert.assertNotNull("created flow visible in parent", found2);
+
+ List<AuthenticationExecutionExportRepresentation> execs = found2.getAuthenticationExecutions();
+ Assert.assertNotNull(execs);
+ Assert.assertEquals("Size one", 1, execs.size());
+
+ AuthenticationExecutionExportRepresentation expected = new AuthenticationExecutionExportRepresentation();
+ expected.setFlowAlias("SomeFlow");
+ expected.setUserSetupAllowed(false);
+ expected.setAuthenticator("registration-page-form");
+ expected.setAutheticatorFlow(true);
+ expected.setRequirement("DISABLED");
+ expected.setPriority(0);
+ compareExecution(expected, execs.get(0));
+
// delete non-built-in flow
authMgmtResource.deleteFlow(found.getId());
@@ -122,7 +164,31 @@ public class FlowTest extends AbstractAuthenticationTest {
// adjust expected values before comparing
browser.setAlias("Copy of browser");
browser.setBuiltIn(false);
+ browser.getAuthenticationExecutions().get(2).setFlowAlias("Copy of browser forms");
compareFlows(browser, copyOfBrowser);
+
+ // get new flow directly and compare
+ copyOfBrowser = authMgmtResource.getFlow(copyOfBrowser.getId());
+ Assert.assertNotNull(copyOfBrowser);
+ compareFlows(browser, copyOfBrowser);
+ }
+
+ @Test
+ // KEYCLOAK-2580
+ public void addExecutionFlow() {
+ HashMap<String, String> params = new HashMap<>();
+ params.put("newName", "parent");
+ Response response = authMgmtResource.copy("browser", params);
+ Assert.assertEquals(201, response.getStatus());
+ response.close();
+
+ params = new HashMap<>();
+ params.put("alias", "child");
+ params.put("description", "Description");
+ params.put("provider", "registration-page-form");
+ params.put("type", "basic-flow");
+
+ authMgmtResource.addExecutionFlow("parent", params);
}
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/InitialFlowsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/InitialFlowsTest.java
index 1f42069..45f538f 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/InitialFlowsTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/InitialFlowsTest.java
@@ -19,6 +19,7 @@ package org.keycloak.testsuite.admin.authentication;
import org.junit.Assert;
import org.junit.Test;
+import org.keycloak.representations.idm.AuthenticationExecutionExportRepresentation;
import org.keycloak.representations.idm.AuthenticationExecutionInfoRepresentation;
import org.keycloak.representations.idm.AuthenticationFlowRepresentation;
import org.keycloak.representations.idm.AuthenticatorConfigRepresentation;
@@ -81,12 +82,12 @@ public class InitialFlowsTest extends AbstractAuthenticationTest {
FlowExecutions fe2 = it2.next();
compareFlows(fe1.flow, fe2.flow);
- compareExecutions(fe1.executions, fe2.executions);
+ compareExecutionsInfo(fe1.executions, fe2.executions);
}
}
- private void compareExecutions(List<AuthenticationExecutionInfoRepresentation> expected, List<AuthenticationExecutionInfoRepresentation> actual) {
+ private void compareExecutionsInfo(List<AuthenticationExecutionInfoRepresentation> expected, List<AuthenticationExecutionInfoRepresentation> actual) {
Assert.assertEquals("Executions count", expected.size(), actual.size());
Iterator<AuthenticationExecutionInfoRepresentation> it1 = expected.iterator();
Iterator<AuthenticationExecutionInfoRepresentation> it2 = actual.iterator();
@@ -124,66 +125,117 @@ public class InitialFlowsTest extends AbstractAuthenticationTest {
LinkedList<FlowExecutions> expected = new LinkedList<>();
AuthenticationFlowRepresentation flow = newFlow("browser", "browser based authentication", "basic-flow", true, true);
- List<AuthenticationExecutionInfoRepresentation> executions = new LinkedList<>();
- executions.add(newExecution("Cookie", "auth-cookie", false, 0, 0, ALTERNATIVE, null, new String[]{ALTERNATIVE, DISABLED}));
- executions.add(newExecution("Kerberos", "auth-spnego", false, 0, 1, DISABLED, null, new String[]{ALTERNATIVE, REQUIRED, DISABLED}));
- executions.add(newExecution("forms", null, false, 0, 2, ALTERNATIVE, true, new String[]{ALTERNATIVE, REQUIRED, DISABLED}));
- executions.add(newExecution("Username Password Form", "auth-username-password-form", false, 1, 0, REQUIRED, null, new String[]{REQUIRED}));
- executions.add(newExecution("OTP Form", "auth-otp-form", false, 1, 1, OPTIONAL, null, new String[]{REQUIRED, OPTIONAL, DISABLED}));
- expected.add(new FlowExecutions(flow, executions));
+ addExecExport(flow, null, false, "auth-cookie", false, null, ALTERNATIVE, 10);
+ addExecExport(flow, null, false, "auth-spnego", false, null, DISABLED, 20);
+ addExecExport(flow, "forms", false, null, true, null, ALTERNATIVE, 30);
+
+ List<AuthenticationExecutionInfoRepresentation> execs = new LinkedList<>();
+ addExecInfo(execs, "Cookie", "auth-cookie", false, 0, 0, ALTERNATIVE, null, new String[]{ALTERNATIVE, DISABLED});
+ addExecInfo(execs, "Kerberos", "auth-spnego", false, 0, 1, DISABLED, null, new String[]{ALTERNATIVE, REQUIRED, DISABLED});
+ addExecInfo(execs, "forms", null, false, 0, 2, ALTERNATIVE, true, new String[]{ALTERNATIVE, REQUIRED, DISABLED});
+ addExecInfo(execs, "Username Password Form", "auth-username-password-form", false, 1, 0, REQUIRED, null, new String[]{REQUIRED});
+ addExecInfo(execs, "OTP Form", "auth-otp-form", false, 1, 1, OPTIONAL, null, new String[]{REQUIRED, OPTIONAL, DISABLED});
+ expected.add(new FlowExecutions(flow, execs));
flow = newFlow("clients", "Base authentication for clients", "client-flow", true, true);
- executions = new LinkedList<>();
- executions.add(newExecution("Client Id and Secret", "client-secret", false, 0, 0, ALTERNATIVE, null, new String[]{ALTERNATIVE, DISABLED}));
- executions.add(newExecution("Signed Jwt", "client-jwt", false, 0, 1, ALTERNATIVE, null, new String[]{ALTERNATIVE, DISABLED}));
- expected.add(new FlowExecutions(flow, executions));
+ addExecExport(flow, null, false, "client-secret", false, null, ALTERNATIVE, 10);
+ addExecExport(flow, null, false, "client-jwt", false, null, ALTERNATIVE, 20);
+
+ execs = new LinkedList<>();
+ addExecInfo(execs, "Client Id and Secret", "client-secret", false, 0, 0, ALTERNATIVE, null, new String[]{ALTERNATIVE, DISABLED});
+ addExecInfo(execs, "Signed Jwt", "client-jwt", false, 0, 1, ALTERNATIVE, null, new String[]{ALTERNATIVE, DISABLED});
+ expected.add(new FlowExecutions(flow, execs));
flow = newFlow("direct grant", "OpenID Connect Resource Owner Grant", "basic-flow", true, true);
- executions = new LinkedList<>();
- executions.add(newExecution("Username Validation", "direct-grant-validate-username", false, 0, 0, REQUIRED, null, new String[]{REQUIRED}));
- executions.add(newExecution("Password", "direct-grant-validate-password", false, 0, 1, REQUIRED, null, new String[]{REQUIRED, DISABLED}));
- executions.add(newExecution("OTP", "direct-grant-validate-otp", false, 0, 2, OPTIONAL, null, new String[]{REQUIRED, OPTIONAL, DISABLED}));
- expected.add(new FlowExecutions(flow, executions));
+ addExecExport(flow, null, false, "direct-grant-validate-username", false, null, REQUIRED, 10);
+ addExecExport(flow, null, false, "direct-grant-validate-password", false, null, REQUIRED, 20);
+ addExecExport(flow, null, false, "direct-grant-validate-otp", false, null, OPTIONAL, 30);
+
+ execs = new LinkedList<>();
+ addExecInfo(execs, "Username Validation", "direct-grant-validate-username", false, 0, 0, REQUIRED, null, new String[]{REQUIRED});
+ addExecInfo(execs, "Password", "direct-grant-validate-password", false, 0, 1, REQUIRED, null, new String[]{REQUIRED, DISABLED});
+ addExecInfo(execs, "OTP", "direct-grant-validate-otp", false, 0, 2, OPTIONAL, null, new String[]{REQUIRED, OPTIONAL, DISABLED});
+ expected.add(new FlowExecutions(flow, execs));
flow = newFlow("first broker login", "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account",
"basic-flow", true, true);
- executions = new LinkedList<>();
- executions.add(newExecution("Review Profile", "idp-review-profile", true, 0, 0, REQUIRED, null, new String[]{REQUIRED, DISABLED}));
- executions.add(newExecution("Create User If Unique", "idp-create-user-if-unique", true, 0, 1, ALTERNATIVE, null, new String[]{ALTERNATIVE, REQUIRED, DISABLED}));
- executions.add(newExecution("Handle Existing Account", null, false, 0, 2, ALTERNATIVE, true, new String[]{ALTERNATIVE, REQUIRED, DISABLED}));
- executions.add(newExecution("Confirm link existing account", "idp-confirm-link", false, 1, 0, REQUIRED, null, new String[]{REQUIRED, DISABLED}));
- executions.add(newExecution("Verify existing account by Email", "idp-email-verification", false, 1, 1, ALTERNATIVE, null, new String[]{ALTERNATIVE, REQUIRED, DISABLED}));
- executions.add(newExecution("Verify Existing Account by Re-authentication", null, false, 1, 2, ALTERNATIVE, true, new String[]{ALTERNATIVE, REQUIRED, DISABLED}));
- executions.add(newExecution("Username Password Form for identity provider reauthentication", "idp-username-password-form", false, 2, 0, REQUIRED, null, new String[]{REQUIRED}));
- executions.add(newExecution("OTP Form", "auth-otp-form", false, 2, 1, OPTIONAL, null, new String[]{REQUIRED, OPTIONAL, DISABLED}));
- expected.add(new FlowExecutions(flow, executions));
+ addExecExport(flow, null, false, "idp-review-profile", false, "review profile config", REQUIRED, 10);
+ addExecExport(flow, null, false, "idp-create-user-if-unique", false, "create unique user config", ALTERNATIVE, 20);
+ addExecExport(flow, "Handle Existing Account", false, null, true, null, ALTERNATIVE, 30);
+
+ execs = new LinkedList<>();
+ addExecInfo(execs, "Review Profile", "idp-review-profile", true, 0, 0, REQUIRED, null, new String[]{REQUIRED, DISABLED});
+ addExecInfo(execs, "Create User If Unique", "idp-create-user-if-unique", true, 0, 1, ALTERNATIVE, null, new String[]{ALTERNATIVE, REQUIRED, DISABLED});
+ addExecInfo(execs, "Handle Existing Account", null, false, 0, 2, ALTERNATIVE, true, new String[]{ALTERNATIVE, REQUIRED, DISABLED});
+ addExecInfo(execs, "Confirm link existing account", "idp-confirm-link", false, 1, 0, REQUIRED, null, new String[]{REQUIRED, DISABLED});
+ addExecInfo(execs, "Verify existing account by Email", "idp-email-verification", false, 1, 1, ALTERNATIVE, null, new String[]{ALTERNATIVE, REQUIRED, DISABLED});
+ addExecInfo(execs, "Verify Existing Account by Re-authentication", null, false, 1, 2, ALTERNATIVE, true, new String[]{ALTERNATIVE, REQUIRED, DISABLED});
+ addExecInfo(execs, "Username Password Form for identity provider reauthentication", "idp-username-password-form", false, 2, 0, REQUIRED, null, new String[]{REQUIRED});
+ addExecInfo(execs, "OTP Form", "auth-otp-form", false, 2, 1, OPTIONAL, null, new String[]{REQUIRED, OPTIONAL, DISABLED});
+ expected.add(new FlowExecutions(flow, execs));
flow = newFlow("registration", "registration flow", "basic-flow", true, true);
- executions = new LinkedList<>();
- executions.add(newExecution("registration form", "registration-page-form", false, 0, 0, REQUIRED, true, new String[]{REQUIRED, DISABLED}));
- executions.add(newExecution("Registration User Creation", "registration-user-creation", false, 1, 0, REQUIRED, null, new String[]{REQUIRED, DISABLED}));
- executions.add(newExecution("Profile Validation", "registration-profile-action", false, 1, 1, REQUIRED, null, new String[]{REQUIRED, DISABLED}));
- executions.add(newExecution("Password Validation", "registration-password-action", false, 1, 2, REQUIRED, null, new String[]{REQUIRED, DISABLED}));
- executions.add(newExecution("Recaptcha", "registration-recaptcha-action", true, 1, 3, DISABLED, null, new String[]{REQUIRED, DISABLED}));
- expected.add(new FlowExecutions(flow, executions));
+ addExecExport(flow, "registration form", false, "registration-page-form", true, null, REQUIRED, 10);
+
+ execs = new LinkedList<>();
+ addExecInfo(execs, "registration form", "registration-page-form", false, 0, 0, REQUIRED, true, new String[]{REQUIRED, DISABLED});
+ addExecInfo(execs, "Registration User Creation", "registration-user-creation", false, 1, 0, REQUIRED, null, new String[]{REQUIRED, DISABLED});
+ addExecInfo(execs, "Profile Validation", "registration-profile-action", false, 1, 1, REQUIRED, null, new String[]{REQUIRED, DISABLED});
+ addExecInfo(execs, "Password Validation", "registration-password-action", false, 1, 2, REQUIRED, null, new String[]{REQUIRED, DISABLED});
+ addExecInfo(execs, "Recaptcha", "registration-recaptcha-action", true, 1, 3, DISABLED, null, new String[]{REQUIRED, DISABLED});
+ expected.add(new FlowExecutions(flow, execs));
flow = newFlow("reset credentials", "Reset credentials for a user if they forgot their password or something", "basic-flow", true, true);
- executions = new LinkedList<>();
- executions.add(newExecution("Choose User", "reset-credentials-choose-user", false, 0, 0, REQUIRED, null, new String[]{REQUIRED}));
- executions.add(newExecution("Send Reset Email", "reset-credential-email", false, 0, 1, REQUIRED, null, new String[]{REQUIRED}));
- executions.add(newExecution("Reset Password", "reset-password", false, 0, 2, REQUIRED, null, new String[]{REQUIRED, OPTIONAL, DISABLED}));
- executions.add(newExecution("Reset OTP", "reset-otp", false, 0, 3, OPTIONAL, null, new String[]{REQUIRED, OPTIONAL, DISABLED}));
- expected.add(new FlowExecutions(flow, executions));
+ addExecExport(flow, null, false, "reset-credentials-choose-user", false, null, REQUIRED, 10);
+ addExecExport(flow, null, false, "reset-credential-email", false, null, REQUIRED, 20);
+ addExecExport(flow, null, false, "reset-password", false, null, REQUIRED, 30);
+ addExecExport(flow, null, false, "reset-otp", false, null, OPTIONAL, 40);
+
+ execs = new LinkedList<>();
+ addExecInfo(execs, "Choose User", "reset-credentials-choose-user", false, 0, 0, REQUIRED, null, new String[]{REQUIRED});
+ addExecInfo(execs, "Send Reset Email", "reset-credential-email", false, 0, 1, REQUIRED, null, new String[]{REQUIRED});
+ addExecInfo(execs, "Reset Password", "reset-password", false, 0, 2, REQUIRED, null, new String[]{REQUIRED, OPTIONAL, DISABLED});
+ addExecInfo(execs, "Reset OTP", "reset-otp", false, 0, 3, OPTIONAL, null, new String[]{REQUIRED, OPTIONAL, DISABLED});
+ expected.add(new FlowExecutions(flow, execs));
flow = newFlow("saml ecp", "SAML ECP Profile Authentication Flow", "basic-flow", true, true);
- executions = new LinkedList<>();
- executions.add(newExecution(null, "http-basic-authenticator", false, 0, 0, REQUIRED, null, new String[]{}));
- expected.add(new FlowExecutions(flow, executions));
+ addExecExport(flow, null, false, "http-basic-authenticator", false, null, REQUIRED, 10);
+
+ execs = new LinkedList<>();
+ addExecInfo(execs, null, "http-basic-authenticator", false, 0, 0, REQUIRED, null, new String[]{});
+ expected.add(new FlowExecutions(flow, execs));
return expected;
}
- static class FlowExecutions implements Comparable<FlowExecutions> {
+ private void addExecExport(AuthenticationFlowRepresentation flow, String flowAlias, boolean userSetupAllowed,
+ String authenticator, boolean authenticatorFlow, String authenticatorConfig,
+ String requirement, int priority) {
+
+ AuthenticationExecutionExportRepresentation rep = newExecutionExportRepresentation(flowAlias, userSetupAllowed,
+ authenticator, authenticatorFlow, authenticatorConfig, requirement, priority);
+
+ List<AuthenticationExecutionExportRepresentation> execs = flow.getAuthenticationExecutions();
+ if (execs == null) {
+ execs = new ArrayList<>();
+ flow.setAuthenticationExecutions(execs);
+ }
+ execs.add(rep);
+ }
+
+ private AuthenticationExecutionExportRepresentation newExecutionExportRepresentation(String flowAlias, boolean userSetupAllowed, String authenticator, boolean authenticatorFlow, String authenticatorConfig, String requirement, int priority) {
+ AuthenticationExecutionExportRepresentation rep = new AuthenticationExecutionExportRepresentation();
+ rep.setFlowAlias(flowAlias);
+ rep.setUserSetupAllowed(userSetupAllowed);
+ rep.setAuthenticator(authenticator);
+ rep.setAutheticatorFlow(authenticatorFlow);
+ rep.setAuthenticatorConfig(authenticatorConfig);
+ rep.setRequirement(requirement);
+ rep.setPriority(priority);
+ return rep;
+ }
+
+ private static class FlowExecutions implements Comparable<FlowExecutions> {
AuthenticationFlowRepresentation flow;
List<AuthenticationExecutionInfoRepresentation> executions;
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ProvidersTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ProvidersTest.java
new file mode 100644
index 0000000..9f679e3
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ProvidersTest.java
@@ -0,0 +1,159 @@
+/*
+ * 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.admin.authentication;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
+ */
+public class ProvidersTest extends AbstractAuthenticationTest {
+
+ @Test
+ public void testFormProviders() {
+ List<Map<String, Object>> result = authMgmtResource.getFormProviders();
+
+ Assert.assertNotNull("null result", result);
+ Assert.assertEquals("size", 1, result.size());
+ Map<String, Object> item = result.get(0);
+
+ Assert.assertEquals("id", "registration-page-form", item.get("id"));
+ Assert.assertEquals("displayName", "Registration Page", item.get("displayName"));
+ Assert.assertEquals("description", "This is the controller for the registration page", item.get("description"));
+ }
+
+ @Test
+ public void testFormActionProviders() {
+ List<Map<String, Object>> result = authMgmtResource.getFormActionProviders();
+
+ List<Map<String, Object>> expected = new LinkedList<>();
+ addProviderInfo(expected, "registration-profile-action", "Profile Validation",
+ "Validates email, first name, and last name attributes and stores them in user data.");
+ addProviderInfo(expected, "registration-recaptcha-action", "Recaptcha",
+ "Adds Google Recaptcha button. Recaptchas verify that the entity that is registering is a human. " +
+ "This can only be used on the internet and must be configured after you add it.");
+ addProviderInfo(expected, "registration-password-action", "Password Validation",
+ "Validates that password matches password confirmation field. It also will store password in user's credential store.");
+ addProviderInfo(expected, "registration-user-creation", "Registration User Creation",
+ "This action must always be first! Validates the username of the user in validation phase. " +
+ "In success phase, this will create the user in the database.");
+
+ compareProviders(expected, result);
+ }
+
+ @Test
+ public void testClientAuthenticatorProviders() {
+ List<Map<String, Object>> result = authMgmtResource.getClientAuthenticatorProviders();
+
+ List<Map<String, Object>> expected = new LinkedList<>();
+ addProviderInfo(expected, "client-jwt", "Signed Jwt",
+ "Validates client based on signed JWT issued by client and signed with the Client private key");
+ addProviderInfo(expected, "client-secret", "Client Id and Secret", "Validates client based on 'client_id' and " +
+ "'client_secret' sent either in request parameters or in 'Authorization: Basic' header");
+
+ compareProviders(expected, result);
+ }
+
+
+ @Test
+ public void testInitialAuthenticationProviders() {
+
+ List<Map<String, Object>> providers = authMgmtResource.getAuthenticatorProviders();
+ providers = sortProviders(providers);
+
+ compareProviders(expectedAuthProviders(), providers);
+ }
+
+ private List<Map<String, Object>> expectedAuthProviders() {
+ ArrayList<Map<String, Object>> result = new ArrayList<>();
+ addProviderInfo(result, "auth-conditional-otp-form", "Conditional OTP Form",
+ "Validates a OTP on a separate OTP form. Only shown if required based on the configured conditions.");
+ addProviderInfo(result, "auth-cookie", "Cookie", "Validates the SSO cookie set by the auth server.");
+ addProviderInfo(result, "auth-otp-form", "OTP Form", "Validates a OTP on a separate OTP form.");
+ addProviderInfo(result, "auth-spnego", "Kerberos", "Initiates the SPNEGO protocol. Most often used with Kerberos.");
+ addProviderInfo(result, "auth-username-password-form", "Username Password Form",
+ "Validates a username and password from login form.");
+ addProviderInfo(result, "direct-grant-validate-otp", "OTP", "Validates the one time password supplied as a 'totp' form parameter in direct grant request");
+ addProviderInfo(result, "direct-grant-validate-password", "Password",
+ "Validates the password supplied as a 'password' form parameter in direct grant request");
+ addProviderInfo(result, "direct-grant-validate-username", "Username Validation",
+ "Validates the username supplied as a 'username' form parameter in direct grant request");
+ addProviderInfo(result, "http-basic-authenticator", null, null);
+ addProviderInfo(result, "idp-confirm-link", "Confirm link existing account", "Show the form where user confirms if he wants " +
+ "to link identity provider with existing account or rather edit user profile data retrieved from identity provider to avoid conflict");
+ addProviderInfo(result, "idp-create-user-if-unique", "Create User If Unique", "Detect if there is existing Keycloak account " +
+ "with same email like identity provider. If no, create new user");
+ addProviderInfo(result, "idp-email-verification", "Verify existing account by Email", "Email verification of existing Keycloak " +
+ "user, that wants to link his user account with identity provider");
+ addProviderInfo(result, "idp-review-profile", "Review Profile",
+ "User reviews and updates profile data retrieved from Identity Provider in the displayed form");
+ addProviderInfo(result, "idp-username-password-form", "Username Password Form for identity provider reauthentication",
+ "Validates a password from login form. Username is already known from identity provider authentication");
+ addProviderInfo(result, "reset-credential-email", "Send Reset Email", "Send email to user and wait for response.");
+ addProviderInfo(result, "reset-credentials-choose-user", "Choose User", "Choose a user to reset credentials for");
+ addProviderInfo(result, "reset-otp", "Reset OTP", "Sets the Configure OTP required action if execution is REQUIRED. " +
+ "Will also set it if execution is OPTIONAL and the OTP is currently configured for it.");
+ addProviderInfo(result, "reset-password", "Reset Password", "Sets the Update Password required action if execution is REQUIRED. " +
+ "Will also set it if execution is OPTIONAL and the password is currently configured for it.");
+ return result;
+ }
+
+ private List<Map<String, Object>> sortProviders(List<Map<String, Object>> providers) {
+ ArrayList<Map<String, Object>> sorted = new ArrayList<>(providers);
+ Collections.sort(sorted, new ProviderComparator());
+ return sorted;
+ }
+
+ private void compareProviders(List<Map<String, Object>> expected, List<Map<String, Object>> actual) {
+ Assert.assertEquals("Providers count", expected.size(), actual.size());
+ // compare ignoring list and map impl types
+ Assert.assertEquals(normalizeResults(expected), normalizeResults(actual));
+ }
+
+ private List<Map<String, Object>> normalizeResults(List<Map<String, Object>> list) {
+ ArrayList<Map<String, Object>> result = new ArrayList();
+ for (Map<String, Object> item: list) {
+ result.add(new HashMap(item));
+ }
+ return result;
+ }
+
+ private void addProviderInfo(List<Map<String, Object>> list, String id, String displayName, String description) {
+ HashMap<String, Object> item = new HashMap<>();
+ item.put("id", id);
+ item.put("displayName", displayName);
+ item.put("description", description);
+ list.add(item);
+ }
+
+ private static class ProviderComparator implements Comparator<Map<String, Object>> {
+ @Override
+ public int compare(Map<String, Object> o1, Map<String, Object> o2) {
+ return String.valueOf(o1.get("id")).compareTo(String.valueOf(o2.get("id")));
+ }
+ }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/RequiredActionsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/RequiredActionsTest.java
new file mode 100644
index 0000000..0ac6864
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/RequiredActionsTest.java
@@ -0,0 +1,121 @@
+/*
+ * 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.admin.authentication;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.keycloak.representations.idm.RequiredActionProviderRepresentation;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
+ */
+public class RequiredActionsTest extends AbstractAuthenticationTest {
+
+ @Test
+ public void testRequiredActions() {
+ List<RequiredActionProviderRepresentation> result = authMgmtResource.getRequiredActions();
+
+ List<RequiredActionProviderRepresentation> expected = new ArrayList<>();
+ addRequiredAction(expected, "CONFIGURE_TOTP", "Configure Totp", true, false, null);
+ addRequiredAction(expected, "UPDATE_PASSWORD", "Update Password", true, false, null);
+ addRequiredAction(expected, "UPDATE_PROFILE", "Update Profile", true, false, null);
+ addRequiredAction(expected, "VERIFY_EMAIL", "Verify Email", true, false, null);
+ addRequiredAction(expected, "terms_and_conditions", "Terms and Conditions", false, false, null);
+
+ compareRequiredActions(expected, sort(result));
+
+ RequiredActionProviderRepresentation forUpdate = newRequiredAction("VERIFY_EMAIL", "Verify Email", false, false, null);
+ try {
+ authMgmtResource.updateRequiredAction(forUpdate.getAlias(), forUpdate);
+ Assert.fail("updateRequiredAction should fail due to null config");
+ } catch (Exception ignored) {
+ }
+
+ forUpdate.setConfig(Collections.<String, String>emptyMap());
+ authMgmtResource.updateRequiredAction(forUpdate.getAlias(), forUpdate);
+
+ result = authMgmtResource.getRequiredActions();
+ RequiredActionProviderRepresentation updated = findRequiredActionByAlias(forUpdate.getAlias(), result);
+
+ Assert.assertNotNull("Required Action still there", updated);
+ compareRequiredAction(forUpdate, updated);
+ }
+
+
+ private RequiredActionProviderRepresentation findRequiredActionByAlias(String alias, List<RequiredActionProviderRepresentation> list) {
+ for (RequiredActionProviderRepresentation a: list) {
+ if (alias.equals(a.getAlias())) {
+ return a;
+ }
+ }
+ return null;
+ }
+
+ private List<RequiredActionProviderRepresentation> sort(List<RequiredActionProviderRepresentation> list) {
+ ArrayList<RequiredActionProviderRepresentation> sorted = new ArrayList<>(list);
+ Collections.sort(sorted, new RequiredActionProviderComparator());
+ return sorted;
+ }
+
+ private void compareRequiredActions(List<RequiredActionProviderRepresentation> expected, List<RequiredActionProviderRepresentation> actual) {
+ Assert.assertNotNull("Actual null", actual);
+ Assert.assertEquals("Required actions count", expected.size(), actual.size());
+
+ Iterator<RequiredActionProviderRepresentation> ite = expected.iterator();
+ Iterator<RequiredActionProviderRepresentation> ita = actual.iterator();
+ while (ite.hasNext()) {
+ compareRequiredAction(ite.next(), ita.next());
+ }
+ }
+
+ private void compareRequiredAction(RequiredActionProviderRepresentation expected, RequiredActionProviderRepresentation actual) {
+ Assert.assertEquals("alias - " + expected.getAlias(), expected.getAlias(), actual.getAlias());
+ Assert.assertEquals("name - " + expected.getAlias(), expected.getName(), actual.getName());
+ Assert.assertEquals("enabled - " + expected.getAlias(), expected.isEnabled(), actual.isEnabled());
+ Assert.assertEquals("defaultAction - " + expected.getAlias(), expected.isDefaultAction(), actual.isDefaultAction());
+ Assert.assertEquals("config - " + expected.getAlias(), expected.getConfig() != null ? expected.getConfig() : Collections.emptyMap(), actual.getConfig());
+ }
+
+ private void addRequiredAction(List<RequiredActionProviderRepresentation> target, String alias, String name, boolean enabled, boolean defaultAction, Map conf) {
+ target.add(newRequiredAction(alias, name, enabled, defaultAction, conf));
+ }
+
+ private RequiredActionProviderRepresentation newRequiredAction(String alias, String name, boolean enabled, boolean defaultAction, Map conf) {
+ RequiredActionProviderRepresentation action = new RequiredActionProviderRepresentation();
+ action.setAlias(alias);
+ action.setName(name);
+ action.setEnabled(enabled);
+ action.setDefaultAction(defaultAction);
+ action.setConfig(conf);
+ return action;
+ }
+
+ private static class RequiredActionProviderComparator implements Comparator<RequiredActionProviderRepresentation> {
+ @Override
+ public int compare(RequiredActionProviderRepresentation o1, RequiredActionProviderRepresentation o2) {
+ return o1.getAlias().compareTo(o2.getAlias());
+ }
+ }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/AbstractClientTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/AbstractClientTest.java
index 7059318..9913c17 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/AbstractClientTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/AbstractClientTest.java
@@ -17,10 +17,12 @@
package org.keycloak.testsuite.admin.client;
+import java.util.List;
import javax.ws.rs.core.Response;
import org.keycloak.admin.client.resource.ClientResource;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testsuite.AbstractAuthTest;
import org.keycloak.testsuite.admin.ApiUtil;
@@ -34,29 +36,35 @@ public abstract class AbstractClientTest extends AbstractAuthTest {
return testRealmResource().toRepresentation();
}
- protected void createOidcClient(String name) {
+ // returns UserRepresentation retrieved from server, with all fields, including id
+ protected UserRepresentation getFullUserRep(String userName) {
+ List<UserRepresentation> results = testRealmResource().users().search(userName, null, null, null, null, null);
+ if (results.size() != 1) throw new RuntimeException("Did not find single user with username " + userName);
+ return results.get(0);
+ }
+
+ protected String createOidcClient(String name) {
ClientRepresentation clientRep = new ClientRepresentation();
clientRep.setClientId(name);
clientRep.setName(name);
clientRep.setRootUrl("foo");
clientRep.setProtocol("openid-connect");
- createClient(clientRep);
+ return createClient(clientRep);
}
- protected void createSamlClient(String name) {
+ protected String createSamlClient(String name) {
ClientRepresentation clientRep = new ClientRepresentation();
clientRep.setClientId(name);
clientRep.setName(name);
clientRep.setProtocol("saml");
clientRep.setAdminUrl("samlEndpoint");
- createClient(clientRep);
+ return createClient(clientRep);
}
- protected void createClient(ClientRepresentation clientRep) {
+ protected String createClient(ClientRepresentation clientRep) {
Response resp = testRealmResource().clients().create(clientRep);
- // for some reason, findAll() will later fail unless readEntity is called here
- resp.readEntity(String.class);
- //testRealmResource().clients().findAll();
+ resp.close();
+ return ApiUtil.getCreatedId(resp);
}
protected ClientRepresentation findClientRepresentation(String name) {
@@ -69,4 +77,8 @@ public abstract class AbstractClientTest extends AbstractAuthTest {
return ApiUtil.findClientResourceByName(testRealmResource(), name);
}
+ protected ClientResource findClientResourceById(String id) {
+ return ApiUtil.findClientResourceByClientId(testRealmResource(), id);
+ }
+
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/ClientProtocolMapperTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/ClientProtocolMapperTest.java
new file mode 100644
index 0000000..acf2329
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/ClientProtocolMapperTest.java
@@ -0,0 +1,266 @@
+/*
+ * Copyright 2016 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * 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.admin.client;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import javax.ws.rs.NotFoundException;
+import javax.ws.rs.core.Response;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.keycloak.admin.client.resource.ClientResource;
+import org.keycloak.admin.client.resource.ProtocolMappersResource;
+import org.keycloak.representations.idm.ProtocolMapperRepresentation;
+import org.keycloak.testsuite.admin.ApiUtil;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc.
+ */
+public class ClientProtocolMapperTest extends AbstractClientTest {
+
+ private ClientResource oidcClientRsc;
+ private ProtocolMappersResource oidcMappersRsc;
+ private ClientResource samlClientRsc;
+ private ProtocolMappersResource samlMappersRsc;
+
+ private Map<String, List<ProtocolMapperRepresentation>> builtinMappers = null;
+
+ @Before
+ public void init() {
+ createOidcClient("oidcMapperClient");
+ oidcClientRsc = findClientResource("oidcMapperClient");
+ oidcMappersRsc = oidcClientRsc.getProtocolMappers();
+
+ createSamlClient("samlMapperClient");
+ samlClientRsc = findClientResource("samlMapperClient");
+ samlMappersRsc = samlClientRsc.getProtocolMappers();
+
+ builtinMappers = adminClient.serverInfo().getInfo().getBuiltinProtocolMappers();
+ }
+
+ @After
+ public void tearDown() {
+ oidcClientRsc.remove();
+ samlClientRsc.remove();
+ }
+
+ private ProtocolMapperRepresentation makeMapper(String protocol, String name, String mapperType, Map<String, String> config) {
+ ProtocolMapperRepresentation rep = new ProtocolMapperRepresentation();
+ rep.setProtocol(protocol);
+ rep.setName(name);
+ rep.setProtocolMapper(mapperType);
+ rep.setConfig(config);
+ rep.setConsentRequired(true);
+ rep.setConsentText("Test Consent Text");
+ return rep;
+ }
+
+ private ProtocolMapperRepresentation makeSamlMapper(String name) {
+ Map<String, String> config = new HashMap<>();
+ config.put("role", "account.view-profile");
+ config.put("new.role.name", "new-role-name");
+ return makeMapper("saml", name, "saml-role-name-mapper", config);
+ }
+
+ private ProtocolMapperRepresentation makeOidcMapper(String name) {
+ Map<String, String> config = new HashMap<>();
+ config.put("role", "myrole");
+ return makeMapper("openid-connect", name, "oidc-hardcoded-role-mapper", config);
+ }
+
+ private void assertEqualMappers(ProtocolMapperRepresentation original, ProtocolMapperRepresentation created) {
+ assertNotNull(created);
+ assertEquals(original.getName(), created.getName());
+ assertEquals(original.getConfig(), created.getConfig());
+ assertEquals(original.getConsentText(), created.getConsentText());
+ assertEquals(original.isConsentRequired(), created.isConsentRequired());
+ assertEquals(original.getProtocol(), created.getProtocol());
+ assertEquals(original.getProtocolMapper(), created.getProtocolMapper());
+ }
+
+ @Test
+ public void testGetMappersList() {
+ assertFalse(oidcMappersRsc.getMappers().isEmpty());
+ assertFalse(samlMappersRsc.getMappers().isEmpty());
+ }
+
+ private boolean containsMapper(List<ProtocolMapperRepresentation> mappers, ProtocolMapperRepresentation mapper) {
+ for (ProtocolMapperRepresentation listedMapper : mappers) {
+ if (listedMapper.getName().equals(mapper.getName())) return true;
+ }
+
+ return false;
+ }
+
+ private List<ProtocolMapperRepresentation> mappersToAdd(List<ProtocolMapperRepresentation> oldMappers,
+ List<ProtocolMapperRepresentation> builtins) {
+ List<ProtocolMapperRepresentation> mappersToAdd = new ArrayList<>();
+ for (ProtocolMapperRepresentation builtin : builtins) {
+ if (!containsMapper(oldMappers, builtin)) mappersToAdd.add(builtin);
+ }
+
+ return mappersToAdd;
+ }
+
+ private void testAddAllBuiltinMappers(ProtocolMappersResource resource, String resourceName) {
+ List<ProtocolMapperRepresentation> oldMappers = resource.getMappersPerProtocol(resourceName);
+ List<ProtocolMapperRepresentation> builtins = builtinMappers.get(resourceName);
+
+ List<ProtocolMapperRepresentation> mappersToAdd = mappersToAdd(oldMappers, builtins);
+
+ // This is used by admin console to add builtin mappers
+ resource.createMapper(mappersToAdd);
+
+ List<ProtocolMapperRepresentation> newMappers = resource.getMappersPerProtocol(resourceName);
+ assertEquals(oldMappers.size() + mappersToAdd.size(), newMappers.size());
+
+ for (ProtocolMapperRepresentation rep : mappersToAdd) {
+ assertTrue(containsMapper(newMappers, rep));
+ }
+ }
+
+ @Test
+ public void testCreateOidcMappersFromList() {
+ testAddAllBuiltinMappers(oidcMappersRsc, "openid-connect");
+ }
+
+ @Test
+ public void testCreateSamlMappersFromList() {
+ testAddAllBuiltinMappers(samlMappersRsc, "saml");
+ }
+
+ @Test
+ public void testCreateSamlProtocolMapper() {
+
+ //{"protocol":"saml",
+ // "config":{"role":"account.view-profile","new.role.name":"new-role-name"},
+ // "consentRequired":true,
+ // "consentText":"My consent text",
+ // "name":"saml-role-name-maper",
+ // "protocolMapper":"saml-role-name-mapper"}
+ ProtocolMapperRepresentation rep = makeSamlMapper("saml-role-name-mapper");
+
+ int totalMappers = samlMappersRsc.getMappers().size();
+ int totalSamlMappers = samlMappersRsc.getMappersPerProtocol("saml").size();
+ Response resp = samlMappersRsc.createMapper(rep);
+ resp.close();
+ assertEquals(totalMappers + 1, samlMappersRsc.getMappers().size());
+ assertEquals(totalSamlMappers + 1, samlMappersRsc.getMappersPerProtocol("saml").size());
+
+ String createdId = ApiUtil.getCreatedId(resp);
+ ProtocolMapperRepresentation created = samlMappersRsc.getMapperById(createdId);
+ assertEqualMappers(rep, created);
+ }
+
+ @Test
+ public void testCreateOidcProtocolMapper() {
+ //{"protocol":"openid-connect",
+ // "config":{"role":"myrole"},
+ // "consentRequired":true,
+ // "consentText":"My consent text",
+ // "name":"oidc-hardcoded-role-mapper",
+ // "protocolMapper":"oidc-hardcoded-role-mapper"}
+ ProtocolMapperRepresentation rep = makeOidcMapper("oidc-hardcoded-role-mapper");
+
+ int totalMappers = oidcMappersRsc.getMappers().size();
+ int totalOidcMappers = oidcMappersRsc.getMappersPerProtocol("openid-connect").size();
+ Response resp = oidcMappersRsc.createMapper(rep);
+ resp.close();
+ assertEquals(totalMappers + 1, oidcMappersRsc.getMappers().size());
+ assertEquals(totalOidcMappers + 1, oidcMappersRsc.getMappersPerProtocol("openid-connect").size());
+
+ String createdId = ApiUtil.getCreatedId(resp);
+ ProtocolMapperRepresentation created = oidcMappersRsc.getMapperById(createdId);//findByName(samlMappersRsc, "saml-role-name-mapper");
+ assertEqualMappers(rep, created);
+ }
+
+ @Test
+ public void testUpdateSamlMapper() {
+ ProtocolMapperRepresentation rep = makeSamlMapper("saml-role-name-mapper2");
+
+ Response resp = samlMappersRsc.createMapper(rep);
+ resp.close();
+
+ String createdId = ApiUtil.getCreatedId(resp);
+
+ rep.getConfig().put("role", "account.manage-account");
+ rep.setId(createdId);
+ rep.setConsentRequired(false);
+ samlMappersRsc.update(createdId, rep);
+
+ ProtocolMapperRepresentation updated = samlMappersRsc.getMapperById(createdId);
+ assertEqualMappers(rep, updated);
+ }
+
+ @Test
+ public void testUpdateOidcMapper() {
+ ProtocolMapperRepresentation rep = makeOidcMapper("oidc-hardcoded-role-mapper2");
+
+ Response resp = oidcMappersRsc.createMapper(rep);
+ resp.close();
+
+ String createdId = ApiUtil.getCreatedId(resp);
+
+ rep.getConfig().put("role", "myotherrole");
+ rep.setId(createdId);
+ rep.setConsentRequired(false);
+ oidcMappersRsc.update(createdId, rep);
+
+ ProtocolMapperRepresentation updated = oidcMappersRsc.getMapperById(createdId);
+ assertEqualMappers(rep, updated);
+ }
+
+ @Test (expected = NotFoundException.class)
+ public void testDeleteSamlMapper() {
+ ProtocolMapperRepresentation rep = makeSamlMapper("saml-role-name-mapper3");
+
+ Response resp = samlMappersRsc.createMapper(rep);
+ resp.close();
+
+ String createdId = ApiUtil.getCreatedId(resp);
+
+ samlMappersRsc.delete(createdId);
+
+ samlMappersRsc.getMapperById(createdId);
+ }
+
+ @Test (expected = NotFoundException.class)
+ public void testDeleteOidcMapper() {
+ ProtocolMapperRepresentation rep = makeOidcMapper("oidc-hardcoded-role-mapper3");
+
+ Response resp = oidcMappersRsc.createMapper(rep);
+ resp.close();
+
+ String createdId = ApiUtil.getCreatedId(resp);
+
+ oidcMappersRsc.delete(createdId);
+
+ oidcMappersRsc.getMapperById(createdId);
+ }
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/ClientRolesTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/ClientRolesTest.java
index 9180f15..5fb7c7d 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/ClientRolesTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/ClientRolesTest.java
@@ -17,6 +17,7 @@
package org.keycloak.testsuite.admin.client;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.keycloak.admin.client.resource.ClientResource;
@@ -42,16 +43,17 @@ public class ClientRolesTest extends AbstractClientTest {
rolesRsc = clientRsc.roles();
}
+ @After
+ public void tearDown() {
+ clientRsc.remove();
+ }
+
private RoleRepresentation makeRole(String name) {
RoleRepresentation role = new RoleRepresentation();
role.setName(name);
return role;
}
- /* private boolean hasRole(RolesResource rolesRsc, String name) {
- return rolesRsc.get(name) != null;
- }*/
-
private boolean hasRole(RolesResource rolesRsc, String name) {
for (RoleRepresentation role : rolesRsc.list()) {
if (role.getName().equals(name)) return true;
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/CredentialsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/CredentialsTest.java
new file mode 100644
index 0000000..2b79e50
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/CredentialsTest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2016 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * 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.admin.client;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.keycloak.admin.client.resource.ClientAttributeCertificateResource;
+import org.keycloak.admin.client.resource.ClientResource;
+import org.keycloak.representations.idm.CertificateRepresentation;
+import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.representations.idm.CredentialRepresentation;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNull;
+
+/**
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc.
+ */
+public class CredentialsTest extends AbstractClientTest {
+
+ private ClientResource accountClient;
+
+ @Before
+ public void init() {
+ accountClient = findClientResourceById("account");
+ }
+
+ @Test
+ public void testGetAndRegenerateSecret() {
+ CredentialRepresentation oldCredential = accountClient.getSecret();
+ CredentialRepresentation newCredential = accountClient.generateNewSecret();
+ assertNotNull(oldCredential);
+ assertNotNull(newCredential);
+ assertNotEquals(newCredential.getValue(), oldCredential.getValue());
+ assertEquals(newCredential.getValue(), accountClient.getSecret().getValue());
+ }
+
+ @Test
+ public void testGetAndRegenerateRegistrationAccessToken() {
+ ClientRepresentation rep = accountClient.toRepresentation();
+ String oldToken = rep.getRegistrationAccessToken();
+ String newToken = accountClient.regenerateRegistrationAccessToken().getRegistrationAccessToken();
+ assertNull(oldToken); // registration access token not saved in ClientRep
+ assertNotNull(newToken); // it's only available via regenerateRegistrationAccessToken()
+ assertNull(accountClient.toRepresentation().getRegistrationAccessToken());
+ }
+
+ @Test
+ public void testGetCertificateResource() {
+ ClientAttributeCertificateResource certRsc = accountClient.getCertficateResource("jwt.credential");
+ CertificateRepresentation cert = certRsc.generate();
+ CertificateRepresentation certFromGet = certRsc.getKeyInfo();
+ assertEquals(cert.getCertificate(), certFromGet.getCertificate());
+ assertEquals(cert.getPrivateKey(), certFromGet.getPrivateKey());
+ }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/InstallationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/InstallationTest.java
index 86927e4..7137198 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/InstallationTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/InstallationTest.java
@@ -17,6 +17,7 @@
package org.keycloak.testsuite.admin.client;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.keycloak.admin.client.resource.ClientResource;
@@ -47,6 +48,12 @@ public class InstallationTest extends AbstractClientTest {
samlClient = findClientResource(SAML_NAME);
}
+ @After
+ public void tearDown() {
+ oidcClient.remove();
+ samlClient.remove();
+ }
+
private String authServerUrl() {
return AuthServerTestEnricher.getAuthServerContextRoot() + "/auth";
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/SessionTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/SessionTest.java
new file mode 100644
index 0000000..cf15f96
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/SessionTest.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2016 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * 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.admin.client;
+
+import java.util.List;
+import org.jboss.arquillian.graphene.page.Page;
+import org.junit.Before;
+import org.junit.Test;
+import org.keycloak.admin.client.resource.ClientResource;
+import org.keycloak.representations.idm.UserRepresentation;
+import org.keycloak.representations.idm.UserSessionRepresentation;
+import org.keycloak.testsuite.auth.page.account.AccountManagement;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+/**
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc.
+ */
+public class SessionTest extends AbstractClientTest {
+ private static boolean testUserCreated = false;
+
+ @Page
+ protected AccountManagement testRealmAccountManagementPage;
+
+ @Before
+ public void init() {
+ // make user test user exists in test realm
+ if (!testUserCreated) createTestUserWithAdminClient();
+ testUserCreated = true;
+ }
+
+ @Test
+ public void testGetAppSessionCount() {
+ ClientResource accountClient = findClientResourceById("account");
+ int sessionCount = accountClient.getApplicationSessionCount().get("count");
+ assertEquals(0, sessionCount);
+
+ testRealmAccountManagementPage.navigateTo();
+ loginPage.form().login(testUser);
+
+ sessionCount = accountClient.getApplicationSessionCount().get("count");
+ assertEquals(1, sessionCount);
+
+ testRealmAccountManagementPage.signOut();
+
+ sessionCount = accountClient.getApplicationSessionCount().get("count");
+ assertEquals(0, sessionCount);
+ }
+
+ @Test
+ public void testGetUserSessions() {
+ //List<java.util.Map<String, String>> stats = this.testRealmResource().getClientSessionStats();
+ ClientResource account = findClientResourceById("account");
+
+ testRealmAccountManagementPage.navigateTo();
+ loginPage.form().login(testUser);
+
+ List<UserSessionRepresentation> sessions = account.getUserSessions(0, 5);
+ assertEquals(1, sessions.size());
+
+ UserSessionRepresentation rep = sessions.get(0);
+
+ UserRepresentation testUserRep = getFullUserRep(testUser.getUsername());
+ assertEquals(testUserRep.getId(), rep.getUserId());
+ assertEquals(testUserRep.getUsername(), rep.getUsername());
+
+ String clientId = account.toRepresentation().getId();
+ assertEquals("account", rep.getClients().get(clientId));
+ assertNotNull(rep.getIpAddress());
+ assertNotNull(rep.getLastAccess());
+ assertNotNull(rep.getStart());
+
+ testRealmAccountManagementPage.signOut();
+ }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cluster/SessionFailoverClusterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cluster/SessionFailoverClusterTest.java
index 3597b75..f15444d 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cluster/SessionFailoverClusterTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cluster/SessionFailoverClusterTest.java
@@ -1,17 +1,19 @@
package org.keycloak.testsuite.cluster;
import java.util.List;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import org.junit.Ignore;
import org.junit.Test;
import org.keycloak.representations.idm.RealmRepresentation;
import static org.keycloak.testsuite.auth.page.AuthRealm.ADMIN;
-import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlDoesntStartWith;
-import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWith;
import static org.keycloak.testsuite.util.WaitUtils.pause;
import org.openqa.selenium.Cookie;
+import org.keycloak.testsuite.page.AbstractPage;
+import org.keycloak.testsuite.page.PageWithLogOutAction;
+import org.junit.Before;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWith;
/**
*
@@ -20,77 +22,131 @@ import org.openqa.selenium.Cookie;
public class SessionFailoverClusterTest extends AbstractClusterTest {
public static final String KEYCLOAK_SESSION_COOKIE = "KEYCLOAK_SESSION";
- public static final String KEYCLOAK_IDENTITY_COOKIE = "KEYCLOAK_IDENTITY";
+
+ public static final Integer SESSION_CACHE_OWNERS = Integer.parseInt(System.getProperty("session.cache.owners", "1"));
+ public static final Integer OFFLINE_SESSION_CACHE_OWNERS = Integer.parseInt(System.getProperty("offline.session.cache.owners", "1"));
+ public static final Integer LOGIN_FAILURES_CACHE_OWNERS = Integer.parseInt(System.getProperty("login.failure.cache.owners", "1"));
+
+ public static final Integer REBALANCE_WAIT = Integer.parseInt(System.getProperty("rebalance.wait", "5000"));
@Override
public void addTestRealms(List<RealmRepresentation> testRealms) {
}
+ @Before
+ public void beforeSessionFailover() {
+ log.info("Initial node failure");
+ failure();
+ pause(REBALANCE_WAIT);
+ }
+
@Test
- @Ignore("work in progress") // only works with owners="2" at the moment
public void sessionFailover() {
+
+ boolean expectSuccessfulFailover = SESSION_CACHE_OWNERS >= getClusterSize();
+
+ log.info("SESSION FAILOVER TEST: cluster size = " + getClusterSize() + ", session-cache owners = " + SESSION_CACHE_OWNERS
+ + " --> Testsing for " + (expectSuccessfulFailover ? "" : "UN") + "SUCCESSFUL session failover.");
+
+ assertEquals(2, getClusterSize());
+ sessionFailover(expectSuccessfulFailover);
+ }
+
+ protected void sessionFailover(boolean expectSuccessfulFailover) {
+
// LOGIN
- accountPage.navigateTo();
- driver.navigate().refresh();
- pause(3000);
- loginPage.form().login(ADMIN, ADMIN);
- assertCurrentUrlStartsWith(accountPage);
-
- Cookie sessionCookie = driver.manage().getCookieNamed(KEYCLOAK_SESSION_COOKIE);
- assertNotNull(sessionCookie);
+ Cookie sessionCookie = login(accountPage);
- failure();
+ switchFailedNode();
- // check if session survived backend failure
-
- driver.navigate().refresh();
- pause(3000);
-
- assertCurrentUrlStartsWith(accountPage);
- Cookie sessionCookieAfterFailover = driver.manage().getCookieNamed(KEYCLOAK_SESSION_COOKIE);
- assertNotNull(sessionCookieAfterFailover);
- assertEquals(sessionCookieAfterFailover.getValue(), sessionCookie.getValue());
+ // VERIFY
+ if (expectSuccessfulFailover) {
+ verifyLoggedIn(accountPage, sessionCookie);
+ } else {
+ verifyLoggedOut(accountPage);
+ // FIXME test fails if I put re-login here
+ }
- failback();
- iterateCurrentFailNode();
+ switchFailedNode();
- // check if session survived backend failback
- driver.navigate().refresh();
- pause(3000);
- assertCurrentUrlStartsWith(accountPage);
- Cookie sessionCookieAfterFailback = driver.manage().getCookieNamed(KEYCLOAK_SESSION_COOKIE);
- assertNotNull(sessionCookieAfterFailback);
- assertEquals(sessionCookieAfterFailover.getValue(), sessionCookie.getValue());
+ // VERIFY again
+ if (expectSuccessfulFailover) {
+ verifyLoggedIn(accountPage, sessionCookie);
+ } else {
+ verifyLoggedOut(accountPage);
+ login(accountPage);
+ }
// LOGOUT
- accountPage.navigateTo();
- accountPage.signOut();
+ logout(accountPage);
+ verifyLoggedOut(accountPage);
- assertCurrentUrlDoesntStartWith(accountPage);
- masterRealmPage.navigateTo();
- sessionCookie = driver.manage().getCookieNamed(KEYCLOAK_SESSION_COOKIE);
- assertNull(sessionCookie);
+ switchFailedNode();
+
+ // VERIFY
+ verifyLoggedOut(accountPage);
+ }
+
+ /**
+ * failure --> failback --> failure of next node
+ */
+ protected void switchFailedNode() {
+ assertFalse(controller.isStarted(getCurrentFailNode().getQualifier()));
+
+ failback();
+ pause(REBALANCE_WAIT);
+
+ iterateCurrentFailNode();
+
failure();
+ pause(REBALANCE_WAIT);
- // check if session survived backend failure
- driver.navigate().refresh();
- pause(3000);
- assertCurrentUrlDoesntStartWith(accountPage);
+ assertFalse(controller.isStarted(getCurrentFailNode().getQualifier()));
+ }
+
+ protected Cookie login(AbstractPage targetPage) {
+ targetPage.navigateTo();
+ assertCurrentUrlStartsWith(loginPage);
+ loginPage.form().login(ADMIN, ADMIN);
+ assertCurrentUrlStartsWith(targetPage);
+ Cookie sessionCookie = driver.manage().getCookieNamed(KEYCLOAK_SESSION_COOKIE);
+ assertNotNull(sessionCookie);
+ return sessionCookie;
+ }
+
+ protected void logout(AbstractPage targetPage) {
+ if (!(targetPage instanceof PageWithLogOutAction)) {
+ throw new IllegalArgumentException(targetPage.getClass().getSimpleName() + " must implement PageWithLogOutAction interface");
+ }
+ targetPage.navigateTo();
+ assertCurrentUrlStartsWith(targetPage);
+ ((PageWithLogOutAction) targetPage).logOut();
+ }
+
+ protected Cookie verifyLoggedIn(AbstractPage targetPage, Cookie sessionCookieForVerification) {
+ // verify on realm path
masterRealmPage.navigateTo();
- sessionCookieAfterFailover = driver.manage().getCookieNamed(KEYCLOAK_SESSION_COOKIE);
- assertNull(sessionCookieAfterFailover);
-
- failback();
-
- // check if session survived backend failback
+ Cookie sessionCookieOnRealmPath = driver.manage().getCookieNamed(KEYCLOAK_SESSION_COOKIE);
+ assertNotNull(sessionCookieOnRealmPath);
+ assertEquals(sessionCookieOnRealmPath.getValue(), sessionCookieForVerification.getValue());
+ // verify on target page
+ targetPage.navigateTo();
+ assertCurrentUrlStartsWith(targetPage);
+ Cookie sessionCookie = driver.manage().getCookieNamed(KEYCLOAK_SESSION_COOKIE);
+ assertNotNull(sessionCookie);
+ assertEquals(sessionCookie.getValue(), sessionCookieForVerification.getValue());
+ return sessionCookie;
+ }
+
+ protected void verifyLoggedOut(AbstractPage targetPage) {
+ // verify on target page
+ targetPage.navigateTo();
driver.navigate().refresh();
- pause(3000);
- assertCurrentUrlDoesntStartWith(accountPage);
- masterRealmPage.navigateTo();
- sessionCookieAfterFailback = driver.manage().getCookieNamed(KEYCLOAK_SESSION_COOKIE);
- assertNull(sessionCookieAfterFailback);
+ assertCurrentUrlStartsWith(loginPage);
+ Cookie sessionCookie = driver.manage().getCookieNamed(KEYCLOAK_SESSION_COOKIE);
+ assertNull(sessionCookie);
}
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/user/AbstractUserTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/user/AbstractUserTest.java
new file mode 100644
index 0000000..d7dbbdc
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/user/AbstractUserTest.java
@@ -0,0 +1,52 @@
+package org.keycloak.testsuite.user;
+
+import javax.ws.rs.core.Response;
+import static javax.ws.rs.core.Response.Status.CREATED;
+import org.keycloak.admin.client.resource.UserResource;
+import org.keycloak.admin.client.resource.UsersResource;
+import org.keycloak.representations.idm.UserRepresentation;
+import static org.keycloak.testsuite.admin.ApiUtil.getCreatedId;
+import static org.junit.Assert.assertEquals;
+import org.keycloak.testsuite.AbstractAuthTest;
+
+/**
+ *
+ * @author tkyjovsk
+ */
+public abstract class AbstractUserTest extends AbstractAuthTest {
+
+ protected UsersResource users() {
+ return testRealmResource().users();
+ }
+
+ protected UserResource user(UserRepresentation user) {
+ if (user.getId()==null) {
+ throw new IllegalStateException("User id cannot be null.");
+ }
+ return user(user.getId());
+ }
+
+ protected UserResource user(String id) {
+ return users().get(id);
+ }
+
+ public static UserRepresentation createUserRep(String username) {
+ UserRepresentation user = new UserRepresentation();
+ user.setUsername(username);
+ user.setEmail(username + "@email.test");
+ return user;
+ }
+
+ public UserRepresentation createUser(UserRepresentation user) {
+ return createUser(users(), user);
+ }
+
+ public UserRepresentation createUser(UsersResource users, UserRepresentation user) {
+ Response response = users.create(user);
+ assertEquals(CREATED.getStatusCode(), response.getStatus());
+ user.setId(getCreatedId(response));
+ response.close();
+ return user;
+ }
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/testsaml.json b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/testsaml.json
index 031edc7..5a1d2c6 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/testsaml.json
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/testsaml.json
@@ -299,7 +299,8 @@
],
"adminUrl": "http://localhost:8080/employee2",
"attributes": {
- "saml.authnstatement": "true"
+ "saml.authnstatement": "true",
+ "saml_idp_initiated_sso_url_name" : "employee2"
},
"protocolMappers": [
{
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/arquillian.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/arquillian.xml
index d267f06..7c9e96c 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/arquillian.xml
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/arquillian.xml
@@ -60,9 +60,10 @@
<property name="enabled">${auth.server.wildfly}</property>
<property name="adapterImplClass">org.jboss.as.arquillian.container.managed.ManagedDeployableContainer</property>
<property name="jbossHome">${keycloak.home}</property>
- <property name="javaVmArguments">-Djboss.socket.binding.port-offset=${auth.server.port.offset} -Xms64m -Xmx512m -XX:MaxPermSize=256m ${adapter.test.props}</property>
+ <property name="javaVmArguments">-Djboss.socket.binding.port-offset=${auth.server.port.offset} -Djboss.bind.address=0.0.0.0 -Xms64m -Xmx512m -XX:MaxPermSize=256m ${adapter.test.props}</property>
<property name="managementPort">${auth.server.management.port}</property>
<property name="startupTimeoutInSeconds">${startup.timeout.sec}</property>
+ <property name="javaHome">${auth.server.java.home}</property>
</configuration>
</container>
@@ -131,12 +132,73 @@
<property name="enabled">${auth.server.eap7}</property>
<property name="adapterImplClass">org.jboss.as.arquillian.container.managed.ManagedDeployableContainer</property>
<property name="jbossHome">${keycloak.home}</property>
- <property name="javaVmArguments">-Djboss.socket.binding.port-offset=${auth.server.port.offset} -Xms64m -Xmx512m -XX:MaxPermSize=256m ${adapter.test.props}</property>
+ <property name="javaVmArguments">-Djboss.socket.binding.port-offset=${auth.server.port.offset} -Djboss.bind.address=0.0.0.0 -Xms64m -Xmx512m -XX:MaxPermSize=256m ${adapter.test.props}</property>
<property name="startupTimeoutInSeconds">${startup.timeout.sec}</property>
<property name="managementPort">${auth.server.management.port}</property>
+ <property name="javaHome">${auth.server.java.home}</property>
</configuration>
</container>
+ <group qualifier="auth-server-eap7-cluster">
+ <container qualifier="auth-server-eap7-balancer" mode="suite" >
+ <configuration>
+ <property name="enabled">${auth.server.eap7.cluster}</property>
+ <property name="adapterImplClass">org.jboss.as.arquillian.container.managed.ManagedDeployableContainer</property>
+ <property name="jbossHome">${keycloak.balancer.home}</property>
+ <property name="jbossArguments">
+ -Djboss.socket.binding.port-offset=${auth.server.port.offset}
+ </property>
+ <property name="javaVmArguments">
+ -Xms64m -Xmx512m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m
+ -Djava.net.preferIPv4Stack=true
+ </property>
+ <property name="outputToConsole">${frontend.console.output}</property>
+ <property name="managementPort">${auth.server.management.port}</property>
+ <property name="startupTimeoutInSeconds">${startup.timeout.sec}</property>
+ </configuration>
+ </container>
+ <container qualifier="auth-server-eap7-backend1" mode="manual" >
+ <configuration>
+ <property name="enabled">${auth.server.eap7.cluster}</property>
+ <property name="adapterImplClass">org.jboss.as.arquillian.container.managed.ManagedDeployableContainer</property>
+ <property name="jbossHome">${keycloak.backend1.home}</property>
+ <property name="serverConfig">standalone-ha.xml</property>
+ <property name="jbossArguments">
+ -Djboss.socket.binding.port-offset=${auth.server.backend1.port.offset}
+ -Djboss.node.name=node1
+ ${adapter.test.props}
+ </property>
+ <property name="javaVmArguments">
+ -Xms64m -Xmx512m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m
+ -Djava.net.preferIPv4Stack=true
+ </property>
+ <property name="outputToConsole">${backends.console.output}</property>
+ <property name="managementPort">${auth.server.backend1.management.port}</property>
+ <property name="startupTimeoutInSeconds">${startup.timeout.sec}</property>
+ </configuration>
+ </container>
+ <container qualifier="auth-server-eap7-backend2" mode="manual" >
+ <configuration>
+ <property name="enabled">${auth.server.eap7.cluster}</property>
+ <property name="adapterImplClass">org.jboss.as.arquillian.container.managed.ManagedDeployableContainer</property>
+ <property name="jbossHome">${keycloak.backend2.home}</property>
+ <property name="serverConfig">standalone-ha.xml</property>
+ <property name="jbossArguments">
+ -Djboss.socket.binding.port-offset=${auth.server.backend2.port.offset}
+ -Djboss.node.name=node2
+ ${adapter.test.props}
+ </property>
+ <property name="javaVmArguments">
+ -Xms64m -Xmx512m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m
+ -Djava.net.preferIPv4Stack=true
+ </property>
+ <property name="outputToConsole">${backends.console.output}</property>
+ <property name="managementPort">${auth.server.backend2.management.port}</property>
+ <property name="startupTimeoutInSeconds">${startup.timeout.sec}</property>
+ </configuration>
+ </container>
+ </group>
+
<!-- PREVIOUS VERSIONS OF KEYCLOAK FOR MIGRATION TESTS -->
<container qualifier="auth-server-wildfly-kc16" mode="manual" >
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/log4j.properties b/testsuite/integration-arquillian/tests/base/src/test/resources/log4j.properties
index 858ed87..566ed61 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/log4j.properties
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/log4j.properties
@@ -40,7 +40,8 @@ log4j.additivity.org.keycloak.testsuite=false
# Liquibase updates logged with "info" by default. Logging level can be changed by system property "keycloak.liquibase.logging.level"
keycloak.liquibase.logging.level=info
-log4j.logger.org.keycloak.connections.jpa.updater.liquibase.LiquibaseJpaUpdaterProvider=${keycloak.liquibase.logging.level}
+log4j.logger.org.keycloak.connections.jpa.updater.liquibase=${keycloak.liquibase.logging.level}
+log4j.logger.org.keycloak.connections.jpa.DefaultJpaConnectionProviderFactory=debug
# Enable to view database updates
# log4j.logger.org.keycloak.connections.mongo.updater.DefaultMongoUpdaterProvider=debug
diff --git a/testsuite/integration-arquillian/tests/other/adapters/as7/pom.xml b/testsuite/integration-arquillian/tests/other/adapters/as7/pom.xml
index e585365..eb8ff75 100644
--- a/testsuite/integration-arquillian/tests/other/adapters/as7/pom.xml
+++ b/testsuite/integration-arquillian/tests/other/adapters/as7/pom.xml
@@ -21,7 +21,7 @@
<parent>
<groupId>org.keycloak.testsuite</groupId>
<artifactId>integration-arquillian-tests-adapters</artifactId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
diff --git a/testsuite/integration-arquillian/tests/other/adapters/eap6/pom.xml b/testsuite/integration-arquillian/tests/other/adapters/eap6/pom.xml
index 6a11a45..4a7a4a1 100644
--- a/testsuite/integration-arquillian/tests/other/adapters/eap6/pom.xml
+++ b/testsuite/integration-arquillian/tests/other/adapters/eap6/pom.xml
@@ -21,7 +21,7 @@
<parent>
<groupId>org.keycloak.testsuite</groupId>
<artifactId>integration-arquillian-tests-adapters</artifactId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -92,6 +92,32 @@
</execution>
</executions>
</plugin>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>xml-maven-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>configure-adapter-debug-log</id>
+ <phase>process-resources</phase>
+ <goals>
+ <goal>transform</goal>
+ </goals>
+ <configuration>
+ <transformationSets>
+ <transformationSet>
+ <dir>${app.server.eap6.home}/standalone/configuration</dir>
+ <includes>
+ <include>standalone.xml</include>
+ </includes>
+ <stylesheet>src/main/xslt/add-adapter-log-level.xsl</stylesheet>
+ <outputDir>${app.server.eap6.home}/standalone/configuration</outputDir>
+ </transformationSet>
+ </transformationSets>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+
</plugins>
</build>
diff --git a/testsuite/integration-arquillian/tests/other/adapters/eap6/src/main/xslt/add-adapter-log-level.xsl b/testsuite/integration-arquillian/tests/other/adapters/eap6/src/main/xslt/add-adapter-log-level.xsl
new file mode 100644
index 0000000..39cec8b
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/other/adapters/eap6/src/main/xslt/add-adapter-log-level.xsl
@@ -0,0 +1,50 @@
+<!--
+ ~ 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.
+ -->
+
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:xalan="http://xml.apache.org/xalan"
+ xmlns:j="urn:jboss:domain:4.0"
+ xmlns:ds="urn:jboss:domain:datasources:4.0"
+ xmlns:k="urn:jboss:domain:keycloak:1.1"
+ xmlns:sec="urn:jboss:domain:security:1.2"
+ version="2.0"
+ exclude-result-prefixes="xalan j ds k sec">
+
+ <xsl:param name="config"/>
+
+ <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" xalan:indent-amount="4" standalone="no"/>
+ <xsl:strip-space elements="*"/>
+
+ <xsl:variable name="nsDS" select="'urn:jboss:domain:logging:'"/>
+
+ <xsl:template match="//*[local-name()='subsystem' and starts-with(namespace-uri(), $nsDS)]
+ /*[local-name()='root-logger' and starts-with(namespace-uri(), $nsDS)]">
+ <logger category="org.keycloak.adapters">
+ <level name="DEBUG"/>
+ </logger>
+ <xsl:copy>
+ <xsl:apply-templates select="@* | node()" />
+ </xsl:copy>
+ </xsl:template>
+
+ <xsl:template match="@*|node()">
+ <xsl:copy>
+ <xsl:apply-templates select="@*|node()" />
+ </xsl:copy>
+ </xsl:template>
+
+</xsl:stylesheet>
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/tests/other/adapters/eap7/pom.xml b/testsuite/integration-arquillian/tests/other/adapters/eap7/pom.xml
index 187a7c1..3f04a57 100644
--- a/testsuite/integration-arquillian/tests/other/adapters/eap7/pom.xml
+++ b/testsuite/integration-arquillian/tests/other/adapters/eap7/pom.xml
@@ -21,7 +21,7 @@
<parent>
<groupId>org.keycloak.testsuite</groupId>
<artifactId>integration-arquillian-tests-adapters</artifactId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -129,6 +129,32 @@
</systemPropertyVariables>
</configuration>
</plugin>
+
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>xml-maven-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>configure-adapter-debug-log</id>
+ <phase>process-resources</phase>
+ <goals>
+ <goal>transform</goal>
+ </goals>
+ <configuration>
+ <transformationSets>
+ <transformationSet>
+ <dir>${app.server.eap7.home}/standalone/configuration</dir>
+ <includes>
+ <include>standalone.xml</include>
+ </includes>
+ <stylesheet>src/main/xslt/add-adapter-log-level.xsl</stylesheet>
+ <outputDir>${app.server.eap7.home}/standalone/configuration</outputDir>
+ </transformationSet>
+ </transformationSets>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
</plugins>
</build>
</profile>
diff --git a/testsuite/integration-arquillian/tests/other/adapters/eap7/src/main/xslt/add-adapter-log-level.xsl b/testsuite/integration-arquillian/tests/other/adapters/eap7/src/main/xslt/add-adapter-log-level.xsl
new file mode 100644
index 0000000..39cec8b
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/other/adapters/eap7/src/main/xslt/add-adapter-log-level.xsl
@@ -0,0 +1,50 @@
+<!--
+ ~ 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.
+ -->
+
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:xalan="http://xml.apache.org/xalan"
+ xmlns:j="urn:jboss:domain:4.0"
+ xmlns:ds="urn:jboss:domain:datasources:4.0"
+ xmlns:k="urn:jboss:domain:keycloak:1.1"
+ xmlns:sec="urn:jboss:domain:security:1.2"
+ version="2.0"
+ exclude-result-prefixes="xalan j ds k sec">
+
+ <xsl:param name="config"/>
+
+ <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" xalan:indent-amount="4" standalone="no"/>
+ <xsl:strip-space elements="*"/>
+
+ <xsl:variable name="nsDS" select="'urn:jboss:domain:logging:'"/>
+
+ <xsl:template match="//*[local-name()='subsystem' and starts-with(namespace-uri(), $nsDS)]
+ /*[local-name()='root-logger' and starts-with(namespace-uri(), $nsDS)]">
+ <logger category="org.keycloak.adapters">
+ <level name="DEBUG"/>
+ </logger>
+ <xsl:copy>
+ <xsl:apply-templates select="@* | node()" />
+ </xsl:copy>
+ </xsl:template>
+
+ <xsl:template match="@*|node()">
+ <xsl:copy>
+ <xsl:apply-templates select="@*|node()" />
+ </xsl:copy>
+ </xsl:template>
+
+</xsl:stylesheet>
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/tests/other/adapters/karaf/pom.xml b/testsuite/integration-arquillian/tests/other/adapters/karaf/pom.xml
index da9b783..ec06e25 100644
--- a/testsuite/integration-arquillian/tests/other/adapters/karaf/pom.xml
+++ b/testsuite/integration-arquillian/tests/other/adapters/karaf/pom.xml
@@ -21,7 +21,7 @@
<parent>
<groupId>org.keycloak.testsuite</groupId>
<artifactId>integration-arquillian-tests-adapters</artifactId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
diff --git a/testsuite/integration-arquillian/tests/other/adapters/pom.xml b/testsuite/integration-arquillian/tests/other/adapters/pom.xml
index 22f7336..23ef642 100644
--- a/testsuite/integration-arquillian/tests/other/adapters/pom.xml
+++ b/testsuite/integration-arquillian/tests/other/adapters/pom.xml
@@ -24,7 +24,7 @@
<parent>
<groupId>org.keycloak.testsuite</groupId>
<artifactId>integration-arquillian-tests-other</artifactId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<artifactId>integration-arquillian-tests-adapters</artifactId>
diff --git a/testsuite/integration-arquillian/tests/other/adapters/tomcat/pom.xml b/testsuite/integration-arquillian/tests/other/adapters/tomcat/pom.xml
index 5553475..36d2815 100644
--- a/testsuite/integration-arquillian/tests/other/adapters/tomcat/pom.xml
+++ b/testsuite/integration-arquillian/tests/other/adapters/tomcat/pom.xml
@@ -21,7 +21,7 @@
<parent>
<groupId>org.keycloak.testsuite</groupId>
<artifactId>integration-arquillian-tests-adapters</artifactId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
diff --git a/testsuite/integration-arquillian/tests/other/adapters/wildfly/pom.xml b/testsuite/integration-arquillian/tests/other/adapters/wildfly/pom.xml
index 63b4082..3fda9e0 100644
--- a/testsuite/integration-arquillian/tests/other/adapters/wildfly/pom.xml
+++ b/testsuite/integration-arquillian/tests/other/adapters/wildfly/pom.xml
@@ -24,7 +24,7 @@
<parent>
<groupId>org.keycloak.testsuite</groupId>
<artifactId>integration-arquillian-tests-adapters</artifactId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<artifactId>integration-arquillian-adapters-wildfly</artifactId>
diff --git a/testsuite/integration-arquillian/tests/other/adapters/wildfly8/pom.xml b/testsuite/integration-arquillian/tests/other/adapters/wildfly8/pom.xml
index 1e3ca88..0503fb2 100644
--- a/testsuite/integration-arquillian/tests/other/adapters/wildfly8/pom.xml
+++ b/testsuite/integration-arquillian/tests/other/adapters/wildfly8/pom.xml
@@ -21,7 +21,7 @@
<parent>
<groupId>org.keycloak.testsuite</groupId>
<artifactId>integration-arquillian-tests-adapters</artifactId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
diff --git a/testsuite/integration-arquillian/tests/other/adapters/wildfly-relative/pom.xml b/testsuite/integration-arquillian/tests/other/adapters/wildfly-relative/pom.xml
index fb919f6..bf38c82 100644
--- a/testsuite/integration-arquillian/tests/other/adapters/wildfly-relative/pom.xml
+++ b/testsuite/integration-arquillian/tests/other/adapters/wildfly-relative/pom.xml
@@ -21,7 +21,7 @@
<parent>
<groupId>org.keycloak.testsuite</groupId>
<artifactId>integration-arquillian-tests-adapters</artifactId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
diff --git a/testsuite/integration-arquillian/tests/other/console/pom.xml b/testsuite/integration-arquillian/tests/other/console/pom.xml
index b681814..3f02409 100644
--- a/testsuite/integration-arquillian/tests/other/console/pom.xml
+++ b/testsuite/integration-arquillian/tests/other/console/pom.xml
@@ -24,11 +24,39 @@
<parent>
<groupId>org.keycloak.testsuite</groupId>
<artifactId>integration-arquillian-tests-other</artifactId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<artifactId>integration-arquillian-tests-console</artifactId>
<name>Admin Console UI Tests</name>
-
+
+ <build>
+ <pluginManagement>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-resources-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>copy-theme-files</id>
+ <phase>process-resources</phase>
+ <goals>
+ <goal>copy-resources</goal>
+ </goals>
+ <configuration>
+ <outputDirectory>${keycloak.home}/themes</outputDirectory>
+ <resources>
+ <resource>
+ <directory>src/main/resources/themes</directory>
+ </resource>
+ </resources>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </pluginManagement>
+ </build>
+
</project>
diff --git a/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/clients/CreateClientForm.java b/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/clients/CreateClientForm.java
index d622f63..0269e03 100644
--- a/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/clients/CreateClientForm.java
+++ b/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/clients/CreateClientForm.java
@@ -72,9 +72,9 @@ public class CreateClientForm extends Form {
}
public void setProtocol(String protocol) {
- Timer.time();
+ Timer.DEFAULT.reset();
protocolSelect.selectByVisibleText(protocol);
- Timer.time("clientSettings.setProtocol()");
+ Timer.DEFAULT.reset("clientSettings.setProtocol()");
}
public class SAMLClientSettingsForm extends Form {
diff --git a/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/clients/settings/ClientSettingsForm.java b/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/clients/settings/ClientSettingsForm.java
index 4f284bf..1e89e98 100644
--- a/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/clients/settings/ClientSettingsForm.java
+++ b/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/clients/settings/ClientSettingsForm.java
@@ -187,19 +187,19 @@ public class ClientSettingsForm extends CreateClientForm {
}
public void setRedirectUris(List<String> redirectUris) {
- Timer.time();
+ Timer.DEFAULT.reset();
while (!deleteRedirectUriIcons.isEmpty()) {
deleteRedirectUriIcons.get(0).click();
pause(100);
}
- Timer.time("deleteRedirectUris");
+ Timer.DEFAULT.reset("deleteRedirectUris");
if (redirectUris != null) {
for (String redirectUri : redirectUris) {
addRedirectUri(redirectUri);
pause(100);
}
}
- Timer.time("addRedirectUris");
+ Timer.DEFAULT.reset("addRedirectUris");
}
public boolean isStandardFlowEnabled() {
diff --git a/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/federation/LdapUserProviderForm.java b/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/federation/LdapUserProviderForm.java
index efad976..2d927a9 100644
--- a/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/federation/LdapUserProviderForm.java
+++ b/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/federation/LdapUserProviderForm.java
@@ -52,6 +52,9 @@ public class LdapUserProviderForm extends Form {
@FindBy(id = "ldapBindCredential")
private WebElement ldapBindCredentialInput;
+ @FindBy(id = "customUserSearchFilter")
+ private WebElement customUserSearchFilterInput;
+
@FindBy(id = "searchScope")
private Select searchScopeSelect;
@@ -158,6 +161,10 @@ public class LdapUserProviderForm extends Form {
setInputValue(ldapBindCredentialInput, ldapBindCredential);
}
+ public void setCustomUserSearchFilter(String customUserSearchFilter) {
+ setInputValue(customUserSearchFilterInput, customUserSearchFilter);
+ }
+
public void setKerberosRealmInput(String kerberosRealm) {
setInputValue(kerberosRealmInput, kerberosRealm);
}
diff --git a/testsuite/integration-arquillian/tests/other/console/src/main/resources/themes/qe/login/messages/messages_en.properties b/testsuite/integration-arquillian/tests/other/console/src/main/resources/themes/qe/login/messages/messages_en.properties
new file mode 100644
index 0000000..b9060b4
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/other/console/src/main/resources/themes/qe/login/messages/messages_en.properties
@@ -0,0 +1,4 @@
+doAccept=Yes
+doDecline=No
+
+termsText=<p>See <a href="https://en.wikipedia.org/wiki/Quality_assurance">QA</a> for more information.</p>
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/tests/other/console/src/main/resources/themes/qe/login/theme.properties b/testsuite/integration-arquillian/tests/other/console/src/main/resources/themes/qe/login/theme.properties
new file mode 100644
index 0000000..f1dbb72
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/other/console/src/main/resources/themes/qe/login/theme.properties
@@ -0,0 +1 @@
+parent=base
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/authentication/actions/TermsAndConditionsTest.java b/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/authentication/actions/TermsAndConditionsTest.java
new file mode 100644
index 0000000..f518784
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/authentication/actions/TermsAndConditionsTest.java
@@ -0,0 +1,166 @@
+/*
+ * JBoss, Home of Professional Open Source
+ *
+ * Copyright 2013 Red Hat, Inc. and/or its affiliates.
+ *
+ * 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.console.authentication.actions;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.jboss.arquillian.graphene.page.Page;
+import org.junit.Assert;
+import org.junit.Test;
+import org.keycloak.admin.client.resource.RealmResource;
+import org.keycloak.representations.idm.CredentialRepresentation;
+import org.keycloak.representations.idm.RealmRepresentation;
+
+import org.keycloak.representations.idm.UserRepresentation;
+import org.keycloak.testsuite.auth.page.login.Registration;
+import org.keycloak.testsuite.auth.page.login.TermsAndConditions;
+import org.keycloak.testsuite.console.AbstractConsoleTest;
+import org.keycloak.testsuite.console.page.authentication.RequiredActions;
+
+/**
+ *
+ */
+public class TermsAndConditionsTest extends AbstractConsoleTest {
+
+ private static final String TERMS_TEXT = "Terms and conditions to be defined";
+
+ private static final String REALM = "TermsAndConditions";
+
+ private static final String BART = "Bart";
+
+ private static final String BART_PASS = "Ay caramba!";
+
+ private static final String HOMER = "Homer";
+
+ private static final String HOMER_PASS = "Mmm donuts.";
+
+ @Page
+ private TermsAndConditions termsAndConditionsPage;
+
+ @Page
+ private Registration registrationPage;
+
+ @Override
+ public void beforeConsoleTest() {
+ // no operation - we don't need 'admin' user for this test.
+ }
+
+ @Override
+ public void setDefaultPageUriParameters() {
+ super.setDefaultPageUriParameters();
+ testRealmPage.setAuthRealm(REALM);
+ testRealmAdminConsolePage.setAdminRealm(REALM);
+ }
+
+ @Override
+ public void addTestRealms(List<RealmRepresentation> testRealms) {
+ RealmRepresentation testRealmRep = new RealmRepresentation();
+ testRealmRep.setRealm(REALM);
+ testRealmRep.setEnabled(true);
+ testRealms.add(testRealmRep);
+ }
+
+ @Test
+ public void testExistingUser() {
+ // create user
+ String userId = createUser(REALM, HOMER, HOMER_PASS);
+
+ // test t&c - log in and make sure t&c is not displayed
+ testRealmAdminConsolePage.navigateTo();
+ testRealmLoginPage.form().login(HOMER, HOMER_PASS);
+ testRealmAdminConsolePage.logOut();
+
+ // enable terms
+ setRequiredActionEnabled(REALM, RequiredActions.TERMS_AND_CONDITIONS, true, false);
+ setRequiredActionEnabled(REALM, userId, RequiredActions.TERMS_AND_CONDITIONS, true);
+
+ // test t&c - log in and accept
+ testRealmLoginPage.form().login(HOMER, HOMER_PASS);
+ Assert.assertEquals(TERMS_TEXT, termsAndConditionsPage.getText());
+ termsAndConditionsPage.declineTerms();
+
+ testRealmLoginPage.form().login(HOMER, HOMER_PASS);
+ Assert.assertEquals(TERMS_TEXT, termsAndConditionsPage.getText());
+
+ termsAndConditionsPage.acceptTerms();
+ testRealmAdminConsolePage.logOut();
+
+ testRealmLoginPage.form().login(HOMER, HOMER_PASS);
+ testRealmAdminConsolePage.logOut();
+
+ // disable terms
+ setRequiredActionEnabled(REALM, RequiredActions.TERMS_AND_CONDITIONS, false, false);
+ }
+
+ @Test
+ public void testAdminCreatedUser() {
+ // enable terms
+ setRequiredActionEnabled(REALM, RequiredActions.TERMS_AND_CONDITIONS, true, false);
+
+ // create user
+ String userId = createUser(REALM, BART, BART_PASS);
+ setRequiredActionEnabled(REALM, userId, RequiredActions.TERMS_AND_CONDITIONS, true);
+
+ // test t&c
+ testRealmAdminConsolePage.navigateTo();
+ testRealmLoginPage.form().login(BART, BART_PASS);
+ Assert.assertTrue(termsAndConditionsPage.isCurrent());
+
+ // disable terms
+ setRequiredActionEnabled(REALM, RequiredActions.TERMS_AND_CONDITIONS, false, false);
+ }
+
+ @Test
+ public void testSelfRegisteredUser() {
+ // enable self-registration
+ RealmResource realmResource = adminClient.realm(REALM);
+ RealmRepresentation realmRepresentation = realmResource.toRepresentation();
+ realmRepresentation.setRegistrationAllowed(true);
+ realmResource.update(realmRepresentation);
+
+ // enable terms
+ setRequiredActionEnabled(REALM, RequiredActions.TERMS_AND_CONDITIONS, true, true);
+
+ // self-register
+ CredentialRepresentation mrBurnsPassword = new CredentialRepresentation();
+ mrBurnsPassword.setType(CredentialRepresentation.PASSWORD);
+ mrBurnsPassword.setValue("Excellent.");
+
+ List<CredentialRepresentation> credentials = new ArrayList<CredentialRepresentation>();
+ credentials.add(mrBurnsPassword);
+
+ UserRepresentation mrBurns = new UserRepresentation();
+ mrBurns.setUsername("mrburns");
+ mrBurns.setFirstName("Montgomery");
+ mrBurns.setLastName("Burns");
+ mrBurns.setEmail("mburns@keycloak.org");
+ mrBurns.setCredentials(credentials);
+
+ testRealmAdminConsolePage.navigateTo();
+ testRealmLoginPage.form().register();
+
+ registrationPage.register(mrBurns);
+
+ // test t&c
+ Assert.assertTrue(termsAndConditionsPage.isCurrent());
+
+ // disable terms
+ setRequiredActionEnabled(REALM, RequiredActions.TERMS_AND_CONDITIONS, false, false);
+ }
+
+}
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/clients/ClientSettingsTest.java b/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/clients/ClientSettingsTest.java
index 14fff17..557a2db 100644
--- a/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/clients/ClientSettingsTest.java
+++ b/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/clients/ClientSettingsTest.java
@@ -166,10 +166,10 @@ public class ClientSettingsTest extends AbstractClientTest {
for (int i = 0; i < count; i++) {
String clientId = String.format("%s%02d", clientIdPrefix, i);
ClientRepresentation cr = createOidcClientRep(CONFIDENTIAL, clientId, "http://example.test/*");
- Timer.time();
+ Timer.DEFAULT.reset();
Response r = testRealmResource().clients().create(cr);
r.close();
- Timer.time("create client");
+ Timer.DEFAULT.reset("create client");
}
}
diff --git a/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/events/LoginEventsTest.java b/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/events/LoginEventsTest.java
index b5cecd5..f53beef 100644
--- a/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/events/LoginEventsTest.java
+++ b/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/events/LoginEventsTest.java
@@ -6,7 +6,6 @@ import org.junit.Test;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.testsuite.admin.Users;
import org.keycloak.testsuite.console.AbstractConsoleTest;
-import org.keycloak.testsuite.console.page.clients.Client;
import org.keycloak.testsuite.console.page.events.Config;
import org.keycloak.testsuite.console.page.events.LoginEvents;
import org.openqa.selenium.By;
@@ -16,6 +15,7 @@ import java.util.Arrays;
import java.util.List;
import static org.junit.Assert.assertEquals;
+import org.junit.Ignore;
import static org.keycloak.representations.idm.CredentialRepresentation.PASSWORD;
/**
diff --git a/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/federation/LdapUserFederationTest.java b/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/federation/LdapUserFederationTest.java
index c81c26e..d0d2a27 100644
--- a/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/federation/LdapUserFederationTest.java
+++ b/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/federation/LdapUserFederationTest.java
@@ -109,8 +109,25 @@ public class LdapUserFederationTest extends AbstractConsoleTest {
createLdapUserProvider.form().save();
assertAlertDanger();
createLdapUserProvider.form().setLdapBindCredentialInput("secret");
+
+ createLdapUserProvider.form().setCustomUserSearchFilter("foo");
+ createLdapUserProvider.form().save();
+ assertAlertDanger();
+ createLdapUserProvider.form().setCustomUserSearchFilter("");
createLdapUserProvider.form().save();
assertAlertSuccess();
+
+ // Try updating invalid Custom LDAP Filter
+ createLdapUserProvider.form().setCustomUserSearchFilter("(foo=bar");
+ createLdapUserProvider.form().save();
+ assertAlertDanger();
+ createLdapUserProvider.form().setCustomUserSearchFilter("foo=bar)");
+ createLdapUserProvider.form().save();
+ assertAlertDanger();
+ createLdapUserProvider.form().setCustomUserSearchFilter("(foo=bar)");
+ createLdapUserProvider.form().save();
+ assertAlertSuccess();
+
}
@Test
diff --git a/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/realm/SecurityDefensesTest.java b/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/realm/SecurityDefensesTest.java
index 5792a73..9537661 100644
--- a/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/realm/SecurityDefensesTest.java
+++ b/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/realm/SecurityDefensesTest.java
@@ -41,7 +41,7 @@ import static org.junit.Assert.*;
public class SecurityDefensesTest extends AbstractRealmTest {
public static final String INVALID_PWD_MSG = "Invalid username or password.";
- public static final String ACC_DISABLED_MSG = "Account is temporarily disabled, contact admin or try again later.";
+ public static final String ACC_DISABLED_MSG = "Invalid username or password.";
public static final short ATTEMPTS_BAD_PWD = 2;
public static final short ATTEMPTS_GOOD_PWD = 1;
diff --git a/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/roles/RealmRolesTest.java b/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/roles/RealmRolesTest.java
index d0b54bc..76a2bf8 100644
--- a/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/roles/RealmRolesTest.java
+++ b/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/roles/RealmRolesTest.java
@@ -121,13 +121,13 @@ public class RealmRolesTest extends AbstractRolesTest {
}
public void createTestRoles(String namePrefix, int count) {
- Timer.time();
+ Timer.DEFAULT.reset();
for (int i = 0; i < count; i++) {
String roleName = String.format("%s%02d", namePrefix, i);
RoleRepresentation rr = new RoleRepresentation(roleName, "", false);
testRealmResource().roles().create(rr);
}
- Timer.time("create " + count + " roles");
+ Timer.DEFAULT.reset("create " + count + " roles");
}
// @Test
diff --git a/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/themes/TermsAndConditionsThemeTest.java b/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/themes/TermsAndConditionsThemeTest.java
new file mode 100644
index 0000000..69e0c8a
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/themes/TermsAndConditionsThemeTest.java
@@ -0,0 +1,83 @@
+/*
+ * 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.console.themes;
+
+import java.util.List;
+import org.jboss.arquillian.graphene.page.Page;
+import org.junit.Assert;
+import org.junit.Test;
+import org.keycloak.admin.client.resource.RealmResource;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.testsuite.auth.page.login.TermsAndConditions;
+import org.keycloak.testsuite.console.AbstractConsoleTest;
+import org.keycloak.testsuite.console.page.authentication.RequiredActions;
+
+/**
+ *
+ */
+public class TermsAndConditionsThemeTest extends AbstractConsoleTest {
+
+ private static final String REALM = "CustomLook";
+
+ private static final String HOMER = "Homer";
+
+ private static final String HOMER_PASS = "Mmm donuts.";
+
+ @Page
+ private TermsAndConditions termsAndConditionsPage;
+
+ @Override
+ public void setDefaultPageUriParameters() {
+ super.setDefaultPageUriParameters();
+ testRealmPage.setAuthRealm(REALM);
+ testRealmAdminConsolePage.setAdminRealm(REALM);
+ }
+
+ @Override
+ public void addTestRealms(List<RealmRepresentation> testRealms) {
+ RealmRepresentation testRealmRep = new RealmRepresentation();
+ testRealmRep.setRealm(REALM);
+ testRealmRep.setEnabled(true);
+ testRealms.add(testRealmRep);
+ }
+
+ @Override
+ public void beforeConsoleTest() {
+ // no operation - we don't need 'admin' user for this test.
+ }
+
+ @Test
+ public void testTermsAndConditions() {
+ String userId = createUser(REALM, HOMER, HOMER_PASS);
+ setRequiredActionEnabled(REALM, RequiredActions.TERMS_AND_CONDITIONS, true, false);
+ setRequiredActionEnabled(REALM, userId, RequiredActions.TERMS_AND_CONDITIONS, true);
+
+ RealmResource realmResource = adminClient.realm(REALM);
+ RealmRepresentation realmRepresentation = realmResource.toRepresentation();
+ realmRepresentation.setLoginTheme("qe");
+ realmResource.update(realmRepresentation);
+
+ testRealmAdminConsolePage.navigateTo();
+ testRealmLoginPage.form().login(HOMER, HOMER_PASS);
+
+ Assert.assertTrue(termsAndConditionsPage.isCurrent());
+ Assert.assertTrue(termsAndConditionsPage.getText().contains("See QA for more information."));
+ Assert.assertEquals("Yes", termsAndConditionsPage.getAcceptButtonText());
+ Assert.assertEquals("No", termsAndConditionsPage.getDeclineButtonText());
+ }
+
+}
diff --git a/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/users/UsersTest.java b/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/users/UsersTest.java
index ed4000d..d0e95f5 100644
--- a/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/users/UsersTest.java
+++ b/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/users/UsersTest.java
@@ -21,7 +21,7 @@ public class UsersTest extends AbstractUserTest {
}
public void createTestUsers(String usernamePrefix, int count) {
-// Timer.time();
+// Timer.DEFAULT.reset();
for (int i = 0; i < count; i++) {
String username = String.format("%s%03d", usernamePrefix, i);
UserRepresentation u = createUserRepresentation(
@@ -30,13 +30,13 @@ public class UsersTest extends AbstractUserTest {
"First",
"Last",
true);
- Timer.time();
+ Timer.DEFAULT.reset();
Response r = testRealmResource().users().create(u);
String id = getCreatedId(r);
r.close();
- Timer.time("create user");
+ Timer.DEFAULT.reset("create user");
}
-// Timer.time("create " + count + " users");
+// Timer.DEFAULT.reset("create " + count + " users");
}
@Test
diff --git a/testsuite/integration-arquillian/tests/other/console_no_users/pom.xml b/testsuite/integration-arquillian/tests/other/console_no_users/pom.xml
new file mode 100644
index 0000000..0ae2e64
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/other/console_no_users/pom.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0"?>
+<!--
+ ~ 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.
+ -->
+
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.keycloak.testsuite</groupId>
+ <artifactId>integration-arquillian-tests-other</artifactId>
+ <version>2.0.0.CR1-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>integration-arquillian-tests-console-no-users</artifactId>
+
+ <name>Admin Console UI Tests - Without pre-configured accounts</name>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-resources-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>copy-admin-user-json-file</id>
+ <phase>none</phase>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
diff --git a/testsuite/integration-arquillian/tests/other/console_no_users/src/test/java/org/keycloak/testsuite/console/pages/WelcomePageTest.java b/testsuite/integration-arquillian/tests/other/console_no_users/src/test/java/org/keycloak/testsuite/console/pages/WelcomePageTest.java
new file mode 100644
index 0000000..1051bb1
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/other/console_no_users/src/test/java/org/keycloak/testsuite/console/pages/WelcomePageTest.java
@@ -0,0 +1,112 @@
+package org.keycloak.testsuite.console.pages;
+
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.URL;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.List;
+import org.jboss.arquillian.graphene.page.Page;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.testsuite.AbstractKeycloakTest;
+import org.keycloak.testsuite.auth.page.WelcomePage;
+import org.keycloak.testsuite.auth.page.login.OIDCLogin;
+
+/**
+ *
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class WelcomePageTest extends AbstractKeycloakTest {
+
+ @Page
+ private WelcomePage welcomePage;
+
+ @Page
+ protected OIDCLogin loginPage;
+
+ @Override
+ public void addTestRealms(List<RealmRepresentation> testRealms) {
+ // no operation
+ }
+
+ /*
+ * Leave out client initialization and creation of a user account. We
+ * don't need those.
+ */
+ @Before
+ @Override
+ public void beforeAbstractKeycloakTest() {
+ setDefaultPageUriParameters();
+ driverSettings();
+ }
+
+ /**
+ * Attempt to resolve the floating IP address. This is where EAP/WildFly
+ * will be accessible. See "-Djboss.bind.address=0.0.0.0".
+ *
+ * @return
+ * @throws Exception
+ */
+ private String getFloatingIpAddress() throws Exception {
+ Enumeration<NetworkInterface> netInterfaces = NetworkInterface.getNetworkInterfaces();
+ for (NetworkInterface ni : Collections.list(netInterfaces)) {
+ Enumeration<InetAddress> inetAddresses = ni.getInetAddresses();
+ for (InetAddress a : Collections.list(inetAddresses)) {
+ if (!a.isLoopbackAddress() && a.isSiteLocalAddress()) {
+ return a.getHostAddress();
+ }
+ }
+ }
+ return null;
+ }
+
+ private URL getPublicServerUrl() throws Exception {
+ String floatingIp = getFloatingIpAddress();
+ if (floatingIp == null) {
+ throw new RuntimeException("Could not determine floating IP address.");
+ }
+ return new URL("http", floatingIp, welcomePage.getInjectedUrl().getPort(), "");
+ }
+
+ @Test
+ public void test_1_LocalAccessNoAdmin() throws Exception {
+ welcomePage.navigateTo();
+ Assert.assertFalse("Welcome page did not ask to create a new admin user.", welcomePage.isPasswordSet());
+ }
+
+ @Test
+ public void test_2_RemoteAccessNoAdmin() throws Exception {
+ driver.navigate().to(getPublicServerUrl());
+ Assert.assertFalse("Welcome page did not ask to create a new admin user.", welcomePage.isPasswordSet());
+ }
+
+ @Test
+ public void test_3_LocalAccessWithAdmin() throws Exception {
+ welcomePage.navigateTo();
+ welcomePage.setPassword("admin", "admin");
+ Assert.assertTrue(driver.getPageSource().contains("User created"));
+
+ welcomePage.navigateTo();
+ Assert.assertTrue("Welcome page asked to set admin password.", welcomePage.isPasswordSet());
+ }
+
+ @Test
+ public void test_4_RemoteAccessWithAdmin() throws Exception {
+ driver.navigate().to(getPublicServerUrl());
+ Assert.assertTrue("Welcome page asked to set admin password.", welcomePage.isPasswordSet());
+ }
+
+ @Test
+ public void test_5_AccessCreatedAdminAccount() throws Exception {
+ welcomePage.navigateToAdminConsole();
+ loginPage.form().login("admin", "admin");
+ Assert.assertFalse("Login with 'admin:admin' failed",
+ driver.getPageSource().contains("Invalid username or password."));
+ }
+
+}
diff --git a/testsuite/integration-arquillian/tests/other/jpa-performance/pom.xml b/testsuite/integration-arquillian/tests/other/jpa-performance/pom.xml
new file mode 100644
index 0000000..95bdc57
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/other/jpa-performance/pom.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0"?>
+<!--
+ ~ 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.
+ -->
+
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.keycloak.testsuite</groupId>
+ <artifactId>integration-arquillian-tests-other</artifactId>
+ <version>2.0.0.CR1-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>integration-arquillian-tests-jpa-performance</artifactId>
+
+ <name>Keycloak JPA Performance Tests</name>
+
+</project>
diff --git a/testsuite/integration-arquillian/tests/other/jpa-performance/README.md b/testsuite/integration-arquillian/tests/other/jpa-performance/README.md
new file mode 100644
index 0000000..238bd4c
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/other/jpa-performance/README.md
@@ -0,0 +1,59 @@
+# Keycloak JPA Performance Tests
+
+## Test Phases
+
+1. Create individual users
+2. Delete realm **Optional**
+3. Re-import realm **Optional**
+4. Delete individual users
+
+Phases 2 and 3 are activated by property `many.users.reimport=true|false`.
+
+
+## How to run
+
+1. Build the Arquilian Base Testsuite module: `/testsuite/integration-arquillian/base`
+2. Run the test from this module using `mvn test` or `mvn clean test`.
+
+Optional parameters:
+* `many.users.count` - Number of users to add/delete. Default: *10000*.
+* `many.users.batch` - Measurement batch size. Default: *1000*.
+* `many.users.reimport` - Switch for phases 2 and 3. Default: *false*.
+* `many.users.minTokenValidity` - Minimum validity of admin-client's access token. Default: *10000*. (ms)
+
+
+### With MySQL
+
+Start dockerized MySQL:
+```
+docker run --name mysql-keycloak -e MYSQL_ROOT_PASSWORD=keycloak -e MYSQL_DATABASE=keycloak -e MYSQL_USER=keycloak -e MYSQL_PASSWORD=keycloak -d -p 3306:3306 mysql
+```
+
+Additional test parameters:
+```
+-Pclean-jpa
+-Dkeycloak.connectionsJpa.url=jdbc:mysql://localhost/keycloak
+-Dkeycloak.connectionsJpa.driver=com.mysql.jdbc.Driver
+-Dkeycloak.connectionsJpa.user=keycloak
+-Dkeycloak.connectionsJpa.password=keycloak
+```
+
+### With PostgreSQL
+
+Start dockerized PostgreSQL:
+```
+docker run --name postgres-keycloak -e POSTGRES_PASSWORD=keycloak -d -p 5432:5432 postgres
+```
+
+Additional test parameters:
+```
+-Pclean-jpa
+-Dkeycloak.connectionsJpa.url=jdbc:postgresql://localhost/postgres
+-Dkeycloak.connectionsJpa.driver=org.postgresql.Driver
+-Dkeycloak.connectionsJpa.user=postgres
+-Dkeycloak.connectionsJpa.password=keycloak
+```
+
+## Reports
+
+Test creates reports in `target/stats`.
diff --git a/testsuite/integration-arquillian/tests/other/jpa-performance/src/test/java/org/keycloak/testsuite/user/ManyUsersTest.java b/testsuite/integration-arquillian/tests/other/jpa-performance/src/test/java/org/keycloak/testsuite/user/ManyUsersTest.java
new file mode 100644
index 0000000..914f748
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/other/jpa-performance/src/test/java/org/keycloak/testsuite/user/ManyUsersTest.java
@@ -0,0 +1,148 @@
+package org.keycloak.testsuite.user;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.LinkedList;
+import java.util.List;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.keycloak.representations.idm.UserRepresentation;
+import org.keycloak.testsuite.util.Timer;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.util.JsonSerialization;
+import org.keycloak.admin.client.resource.RealmResource;
+import static org.keycloak.testsuite.util.IOUtil.PROJECT_BUILD_DIRECTORY;
+import static org.junit.Assert.fail;
+
+/**
+ *
+ * @author tkyjovsk
+ */
+public class ManyUsersTest extends AbstractUserTest {
+
+ private static final int COUNT = Integer.parseInt(System.getProperty("many.users.count", "10000"));
+ private static final int BATCH = Integer.parseInt(System.getProperty("many.users.batch", "1000"));
+ private static final boolean REIMPORT = Boolean.parseBoolean(System.getProperty("many.users.reimport", "false"));
+
+ private static final String REALM = "realm_with_many_users";
+
+ private List<UserRepresentation> users;
+
+ private final Timer realmTimer = new Timer();
+ private final Timer usersTimer = new Timer();
+
+ private static final long MIN_TOKEN_VALIDITY = Long.parseLong(System.getProperty("many.users.minTokenValidity", "10000"));
+ long tokenExpirationTime = 0;
+
+ protected boolean tokenMinValidityExpired() {
+ return System.currentTimeMillis() >= tokenExpirationTime - MIN_TOKEN_VALIDITY;
+ }
+
+ protected void refreshToken() {
+ long requestTime = System.currentTimeMillis();
+ adminClient.tokenManager().refreshToken();
+ tokenExpirationTime = requestTime + adminClient.tokenManager().getAccessToken().getExpiresIn() * 1000;
+ }
+
+ protected void refreshTokenIfMinValidityExpired() {
+ if (tokenMinValidityExpired()) {
+ log.info(String.format("Minimum access token validity (%s ms) expired --> refreshing", MIN_TOKEN_VALIDITY));
+ refreshToken();
+ }
+ }
+
+ protected RealmResource realmResource() {
+ return realmsResouce().realm(REALM);
+ }
+
+ @Before
+ public void before() {
+ users = new LinkedList<>();
+ for (int i = 0; i < COUNT; i++) {
+ users.add(createUserRep("user" + i));
+ }
+
+ realmTimer.reset("create realm before test");
+ RealmRepresentation realm = new RealmRepresentation();
+ realm.setRealm(REALM);
+ realmsResouce().create(realm);
+
+ refreshToken();
+ }
+
+ @After
+ public void after() {
+ realmTimer.clearStats(true, true, false);
+ usersTimer.clearStats();
+ }
+
+ @Test
+ public void manyUsers() throws IOException {
+ RealmRepresentation realm = realmResource().toRepresentation();
+ realm.setUsers(users);
+
+ // CREATE
+ realmTimer.reset("create " + users.size() + " users");
+ usersTimer.reset("create " + BATCH + " users");
+ int i = 0;
+ for (UserRepresentation user : users) {
+ refreshTokenIfMinValidityExpired();
+ createUser(realmResource().users(), user);
+ if (++i % BATCH == 0) {
+ usersTimer.reset();
+ log.info("Created users: " + i + " / " + users.size());
+ }
+ }
+ if (i % BATCH != 0) {
+ usersTimer.reset();
+ log.info("Created users: " + i + " / " + users.size());
+ }
+
+ if (REIMPORT) {
+
+ // SAVE REALM
+ realmTimer.reset("save realm with " + users.size() + " users");
+ File realmFile = new File(PROJECT_BUILD_DIRECTORY, REALM + ".json");
+ JsonSerialization.writeValueToStream(new BufferedOutputStream(new FileOutputStream(realmFile)), realm);
+
+ // DELETE REALM
+ realmTimer.reset("delete realm with " + users.size() + " users");
+ realmResource().remove();
+ try {
+ realmResource().toRepresentation();
+ fail("realm not deleted");
+ } catch (Exception ex) {
+ log.debug("realm deleted");
+ }
+
+ // RE-IMPORT SAVED REALM
+ realmTimer.reset("re-import realm with " + realm.getUsers().size() + " users");
+ realmsResouce().create(realm);
+ realmTimer.reset("load " + realm.getUsers().size() + " users");
+ users = realmResource().users().search("", 0, Integer.MAX_VALUE);
+
+ }
+
+ // DELETE INDIVIDUAL USERS
+ realmTimer.reset("delete " + users.size() + " users");
+ usersTimer.reset("delete " + BATCH + " users", false);
+ i = 0;
+ for (UserRepresentation user : users) {
+ refreshTokenIfMinValidityExpired();
+ realmResource().users().get(user.getId()).remove();
+ if (++i % BATCH == 0) {
+ usersTimer.reset();
+ log.info("Deleted users: " + i + " / " + users.size());
+ }
+ }
+ if (i % BATCH != 0) {
+ usersTimer.reset();
+ log.info("Deleted users: " + i + " / " + users.size());
+ }
+ realmTimer.reset();
+ }
+
+}
diff --git a/testsuite/integration-arquillian/tests/other/mod_auth_mellon/pom.xml b/testsuite/integration-arquillian/tests/other/mod_auth_mellon/pom.xml
index 78ef296..4327228 100644
--- a/testsuite/integration-arquillian/tests/other/mod_auth_mellon/pom.xml
+++ b/testsuite/integration-arquillian/tests/other/mod_auth_mellon/pom.xml
@@ -24,7 +24,7 @@
<parent>
<groupId>org.keycloak.testsuite</groupId>
<artifactId>integration-arquillian-tests-other</artifactId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<artifactId>integration-arquillian-tests-other-mod_auth_mellon</artifactId>
diff --git a/testsuite/integration-arquillian/tests/other/pom.xml b/testsuite/integration-arquillian/tests/other/pom.xml
index 19648b7..092e105 100644
--- a/testsuite/integration-arquillian/tests/other/pom.xml
+++ b/testsuite/integration-arquillian/tests/other/pom.xml
@@ -24,7 +24,7 @@
<parent>
<groupId>org.keycloak.testsuite</groupId>
<artifactId>integration-arquillian-tests</artifactId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<artifactId>integration-arquillian-tests-other</artifactId>
@@ -135,11 +135,23 @@
</modules>
</profile>
<profile>
+ <id>console-ui-no-users-tests</id>
+ <modules>
+ <module>console_no_users</module>
+ </modules>
+ </profile>
+ <profile>
<id>mod_auth_mellon</id>
<modules>
<module>mod_auth_mellon</module>
</modules>
</profile>
+ <profile>
+ <id>jpa-performance</id>
+ <modules>
+ <module>jpa-performance</module>
+ </modules>
+ </profile>
</profiles>
</project>
testsuite/integration-arquillian/tests/pom.xml 202(+195 -7)
diff --git a/testsuite/integration-arquillian/tests/pom.xml b/testsuite/integration-arquillian/tests/pom.xml
index b2438dd..0527852 100644
--- a/testsuite/integration-arquillian/tests/pom.xml
+++ b/testsuite/integration-arquillian/tests/pom.xml
@@ -24,7 +24,7 @@
<parent>
<groupId>org.keycloak.testsuite</groupId>
<artifactId>integration-arquillian</artifactId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<packaging>pom</packaging>
@@ -57,16 +57,18 @@
<browser>phantomjs</browser>
<firefox_binary>/usr/bin/firefox</firefox_binary>
- <arquillian-core.version>1.1.8.Final</arquillian-core.version>
- <selenium.version>2.45.0</selenium.version>
- <arquillian-drone.version>2.0.0.Alpha4</arquillian-drone.version>
- <arquillian-graphene.version>2.1.0.Alpha2</arquillian-graphene.version>
+ <arquillian-core.version>1.1.11.Final</arquillian-core.version>
+ <selenium.version>2.52.0</selenium.version>
+ <arquillian-drone.version>2.0.0.Beta1</arquillian-drone.version>
+ <arquillian-graphene.version>2.1.0.Alpha3</arquillian-graphene.version>
<arquillian-wildfly-container.version>8.2.0.Final</arquillian-wildfly-container.version>
- <version.shrinkwrap.resolvers>2.1.1</version.shrinkwrap.resolvers>
+ <version.shrinkwrap.resolvers>2.2.2</version.shrinkwrap.resolvers>
<frontend.console.output>true</frontend.console.output>
<backends.console.output>true</backends.console.output>
+ <auth.server.java.home>${java.home}</auth.server.java.home>
+
</properties>
<dependencyManagement>
@@ -108,6 +110,7 @@
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<systemPropertyVariables>
+ <project.build.directory>${project.build.directory}</project.build.directory>
<browser>${browser}</browser>
<firefox_binary>${firefox_binary}</firefox_binary>
<shouldDeploy>false</shouldDeploy>
@@ -119,6 +122,7 @@
<auth.server.management.port>${auth.server.management.port}</auth.server.management.port>
<auth.server.management.port.jmx>${auth.server.management.port.jmx}</auth.server.management.port.jmx>
<auth.server.ssl.required>${auth.server.ssl.required}</auth.server.ssl.required>
+ <auth.server.java.home>${auth.server.java.home}</auth.server.java.home>
<startup.timeout.sec>${startup.timeout.sec}</startup.timeout.sec>
<jboss.server.config.dir>${jboss.server.config.dir}</jboss.server.config.dir>
<frontend.console.output>${frontend.console.output}</frontend.console.output>
@@ -220,7 +224,19 @@
<dependency>
<groupId>org.jboss.arquillian.graphene</groupId>
<artifactId>arquillian-browser-screenshooter</artifactId>
- <version>2.1.0.Alpha3</version><!-- TODO upgrade <arquillian-graphene.version> and use ${arquillian-graphene.version} -->
+ <version>${arquillian-graphene.version}</version>
+ <exclusions>
+ <exclusion>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-io</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+
+ <dependency>
+ <groupId>jfree</groupId>
+ <artifactId>jfreechart</artifactId>
+ <version>1.0.13</version>
</dependency>
<!-- <dependency>
@@ -395,6 +411,16 @@
<groupId>org.infinispan</groupId>
<artifactId>infinispan-core</artifactId>
</dependency>
+
+ <dependency>
+ <groupId>mysql</groupId>
+ <artifactId>mysql-connector-java</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.postgresql</groupId>
+ <artifactId>postgresql</artifactId>
+ <version>${postgresql.version}</version>
+ </dependency>
</dependencies>
<build>
@@ -411,11 +437,49 @@
<groupId>org.codehaus.mojo</groupId>
<artifactId>xml-maven-plugin</artifactId>
</plugin>
+ <plugin>
+ <groupId>org.liquibase</groupId>
+ <artifactId>liquibase-maven-plugin</artifactId>
+ </plugin>
</plugins>
</build>
</profile>
<profile>
+ <id>clean-jpa</id>
+ <build>
+ <pluginManagement>
+ <plugins>
+ <plugin>
+ <groupId>org.liquibase</groupId>
+ <artifactId>liquibase-maven-plugin</artifactId>
+ <configuration>
+ <changeLogFile>META-INF/jpa-changelog-master.xml</changeLogFile>
+
+ <url>${keycloak.connectionsJpa.url}</url>
+ <driver>${keycloak.connectionsJpa.driver}</driver>
+ <username>${keycloak.connectionsJpa.user}</username>
+ <password>${keycloak.connectionsJpa.password}</password>
+
+ <promptOnNonLocalDatabase>false</promptOnNonLocalDatabase>
+ <databaseClass>${keycloak.connectionsJpa.liquibaseDatabaseClass}</databaseClass>
+ </configuration>
+ <executions>
+ <execution>
+ <id>clean-jpa</id>
+ <phase>clean</phase>
+ <goals>
+ <goal>dropAll</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </pluginManagement>
+ </build>
+ </profile>
+
+ <profile>
<id>auth-server-wildfly</id>
<properties>
<auth.server.container>auth-server-wildfly</auth.server.container>
@@ -581,6 +645,7 @@
</build>
</profile>
+
<profile>
<id>auth-server-eap7</id>
<properties>
@@ -660,6 +725,129 @@
</build>
</profile>
+ <profile>
+ <id>auth-server-eap7-cluster</id>
+ <properties>
+ <!--disable exclusion pattern for cluster test which is enabled by default in base/pom.xml-->
+ <exclude.cluster>-</exclude.cluster>
+
+ <auth.server.container>auth-server-eap7-cluster</auth.server.container>
+ <startup.timeout.sec>300</startup.timeout.sec>
+ <adapter.test.props/>
+ <h2.version>1.3.173</h2.version>
+
+ <keycloak.balancer.home>${containers.home}/balancer/wildfly-balancer-${project.version}</keycloak.balancer.home>
+ <keycloak.backend1.home>${containers.home}/node1/keycloak-${version.server.dist}</keycloak.backend1.home>
+ <keycloak.backend2.home>${containers.home}/node2/keycloak-${version.server.dist}</keycloak.backend2.home>
+
+ <keycloak.home>${keycloak.backend1.home}</keycloak.home>
+ <jboss.server.config.dir>${keycloak.home}/standalone/configuration</jboss.server.config.dir>
+ </properties>
+ <dependencies>
+ <dependency>
+ <groupId>org.wildfly</groupId>
+ <artifactId>wildfly-arquillian-container-managed</artifactId>
+ </dependency>
+ </dependencies>
+ <build>
+ <pluginManagement>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-enforcer-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>enforce-properties</id>
+ <goals>
+ <goal>enforce</goal>
+ </goals>
+ <configuration>
+ <rules>
+ <requireProperty>
+ <property>version.server.dist</property>
+ </requireProperty>
+ </rules>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <systemPropertyVariables>
+ <run.h2>true</run.h2>
+
+ <auth.server.eap7.cluster>true</auth.server.eap7.cluster>
+ <auth.server.undertow>false</auth.server.undertow>
+ <adapter.test.props>${adapter.test.props}</adapter.test.props>
+
+ <keycloak.balancer.home>${keycloak.balancer.home}</keycloak.balancer.home>
+ <keycloak.backend1.home>${keycloak.backend1.home}</keycloak.backend1.home>
+ <keycloak.backend2.home>${keycloak.backend2.home}</keycloak.backend2.home>
+
+ <!--100-->
+ <auth.server.backend1.port.offset>101</auth.server.backend1.port.offset>
+ <auth.server.backend2.port.offset>102</auth.server.backend2.port.offset>
+ <!--8180-->
+ <auth.server.backend1.http.port>8181</auth.server.backend1.http.port>
+ <auth.server.backend2.http.port>8182</auth.server.backend2.http.port>
+ <!--8543-->
+ <auth.server.backend1.https.port>8544</auth.server.backend1.https.port>
+ <auth.server.backend2.https.port>8545</auth.server.backend2.https.port>
+ <!--10090-->
+ <auth.server.backend1.management.port>10091</auth.server.backend1.management.port>
+ <auth.server.backend2.management.port>10092</auth.server.backend2.management.port>
+ <!--10099-->
+ <auth.server.backend1.management.port.jmx>10100</auth.server.backend1.management.port.jmx>
+ <auth.server.backend2.management.port.jmx>10101</auth.server.backend2.management.port.jmx>
+ </systemPropertyVariables>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-dependency-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>unpack-auth-server-wildfly</id>
+ <phase>generate-test-resources</phase>
+ <goals>
+ <goal>unpack</goal>
+ </goals>
+ <configuration>
+ <artifactItems>
+ <artifactItem>
+ <groupId>org.keycloak.testsuite</groupId>
+ <artifactId>integration-arquillian-server-wildfly-balancer</artifactId>
+ <version>${project.version}</version>
+ <type>zip</type>
+ <outputDirectory>${containers.home}/balancer</outputDirectory>
+ </artifactItem>
+ <artifactItem>
+ <groupId>org.keycloak.testsuite</groupId>
+ <artifactId>integration-arquillian-server-eap7</artifactId>
+ <version>${project.version}</version>
+ <type>zip</type>
+ <outputDirectory>${containers.home}/node1</outputDirectory>
+ </artifactItem>
+ <artifactItem>
+ <groupId>org.keycloak.testsuite</groupId>
+ <artifactId>integration-arquillian-server-eap7</artifactId>
+ <version>${project.version}</version>
+ <type>zip</type>
+ <outputDirectory>${containers.home}/node2</outputDirectory>
+ </artifactItem>
+ </artifactItems>
+ <overWriteIfNewer>true</overWriteIfNewer>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </pluginManagement>
+ </build>
+ </profile>
+
<!-- Profiles for migration tests-->
<profile>
testsuite/jetty/jetty81/pom.xml 2(+1 -1)
diff --git a/testsuite/jetty/jetty81/pom.xml b/testsuite/jetty/jetty81/pom.xml
index 74289d6..6985589 100755
--- a/testsuite/jetty/jetty81/pom.xml
+++ b/testsuite/jetty/jetty81/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-testsuite-pom</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
testsuite/jetty/jetty91/pom.xml 2(+1 -1)
diff --git a/testsuite/jetty/jetty91/pom.xml b/testsuite/jetty/jetty91/pom.xml
index 97ee5f1..b4a465f 100755
--- a/testsuite/jetty/jetty91/pom.xml
+++ b/testsuite/jetty/jetty91/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-testsuite-pom</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
testsuite/jetty/jetty92/pom.xml 2(+1 -1)
diff --git a/testsuite/jetty/jetty92/pom.xml b/testsuite/jetty/jetty92/pom.xml
index 5e67dd1..d109be0 100755
--- a/testsuite/jetty/jetty92/pom.xml
+++ b/testsuite/jetty/jetty92/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-testsuite-pom</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
testsuite/jetty/pom.xml 2(+1 -1)
diff --git a/testsuite/jetty/pom.xml b/testsuite/jetty/pom.xml
index 474ce25..e6951dc 100755
--- a/testsuite/jetty/pom.xml
+++ b/testsuite/jetty/pom.xml
@@ -20,7 +20,7 @@
<parent>
<artifactId>keycloak-testsuite-pom</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<name>Keycloak SAML Jetty Testsuite Integration</name>
testsuite/performance/pom.xml 2(+1 -1)
diff --git a/testsuite/performance/pom.xml b/testsuite/performance/pom.xml
index 169e17a..2a6c79a 100755
--- a/testsuite/performance/pom.xml
+++ b/testsuite/performance/pom.xml
@@ -22,7 +22,7 @@
<parent>
<artifactId>keycloak-testsuite-pom</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
testsuite/pom.xml 2(+1 -1)
diff --git a/testsuite/pom.xml b/testsuite/pom.xml
index 329fe7f..4e8d2f5 100755
--- a/testsuite/pom.xml
+++ b/testsuite/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
testsuite/proxy/pom.xml 2(+1 -1)
diff --git a/testsuite/proxy/pom.xml b/testsuite/proxy/pom.xml
index 6ec2ef1..a8a50c4 100755
--- a/testsuite/proxy/pom.xml
+++ b/testsuite/proxy/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-testsuite-pom</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
testsuite/stress/pom.xml 2(+1 -1)
diff --git a/testsuite/stress/pom.xml b/testsuite/stress/pom.xml
index 7bd00ae..0c60ce4 100755
--- a/testsuite/stress/pom.xml
+++ b/testsuite/stress/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-testsuite-pom</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
testsuite/tomcat6/pom.xml 2(+1 -1)
diff --git a/testsuite/tomcat6/pom.xml b/testsuite/tomcat6/pom.xml
index e8146dd..1e8f51c 100755
--- a/testsuite/tomcat6/pom.xml
+++ b/testsuite/tomcat6/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-testsuite-pom</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
testsuite/tomcat7/pom.xml 2(+1 -1)
diff --git a/testsuite/tomcat7/pom.xml b/testsuite/tomcat7/pom.xml
index b7f871a..a424eeb 100755
--- a/testsuite/tomcat7/pom.xml
+++ b/testsuite/tomcat7/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-testsuite-pom</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
testsuite/tomcat8/pom.xml 2(+1 -1)
diff --git a/testsuite/tomcat8/pom.xml b/testsuite/tomcat8/pom.xml
index 54cd055..64643f7 100755
--- a/testsuite/tomcat8/pom.xml
+++ b/testsuite/tomcat8/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-testsuite-pom</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
themes/pom.xml 2(+1 -1)
diff --git a/themes/pom.xml b/themes/pom.xml
index 2a50591..48e9831 100755
--- a/themes/pom.xml
+++ b/themes/pom.xml
@@ -4,7 +4,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
diff --git a/themes/src/main/resources/theme/base/account/password.ftl b/themes/src/main/resources/theme/base/account/password.ftl
index 053fdbd..5d2369a 100755
--- a/themes/src/main/resources/theme/base/account/password.ftl
+++ b/themes/src/main/resources/theme/base/account/password.ftl
@@ -18,7 +18,7 @@
</div>
<div class="col-sm-10 col-md-10">
- <input type="password" class="form-control" id="password" name="password" autofocus>
+ <input type="password" class="form-control" id="password" name="password" autofocus autocomplete="off">
</div>
</div>
</#if>
@@ -31,7 +31,7 @@
</div>
<div class="col-sm-10 col-md-10">
- <input type="password" class="form-control" id="password-new" name="password-new">
+ <input type="password" class="form-control" id="password-new" name="password-new" autocomplete="off">
</div>
</div>
@@ -41,7 +41,7 @@
</div>
<div class="col-sm-10 col-md-10">
- <input type="password" class="form-control" id="password-confirm" name="password-confirm">
+ <input type="password" class="form-control" id="password-confirm" name="password-confirm" autocomplete="off">
</div>
</div>
diff --git a/themes/src/main/resources/theme/base/account/totp.ftl b/themes/src/main/resources/theme/base/account/totp.ftl
index 3fa4ae9..1cfefba 100755
--- a/themes/src/main/resources/theme/base/account/totp.ftl
+++ b/themes/src/main/resources/theme/base/account/totp.ftl
@@ -48,7 +48,7 @@
</div>
<div class="col-sm-10 col-md-10">
- <input type="text" class="form-control" id="totp" name="totp" autocomplete="off" autofocus>
+ <input type="text" class="form-control" id="totp" name="totp" autocomplete="off" autofocus autocomplete="off">
<input type="hidden" id="totpSecret" name="totpSecret" value="${totp.totpSecret}" />
</div>
</div>
diff --git a/themes/src/main/resources/theme/base/admin/messages/admin-messages_de.properties b/themes/src/main/resources/theme/base/admin/messages/admin-messages_de.properties
index c3332d7..e69de29 100644
--- a/themes/src/main/resources/theme/base/admin/messages/admin-messages_de.properties
+++ b/themes/src/main/resources/theme/base/admin/messages/admin-messages_de.properties
@@ -1,901 +0,0 @@
-# Common messages
-enabled=de Enabled
-name=de Name
-displayName=de Display name
-displayNameHtml=de HTML Display name
-save=de Save
-cancel=de Cancel
-onText=AN
-offText=AUS
-client=de Client
-clients=de Clients
-clear=de Clear
-selectOne=de Select One...
-
-true=de True
-false=de False
-
-
-# Realm settings
-realm-detail.enabled.tooltip=de Users and clients can only access a realm if it's enabled
-registrationAllowed=de User registration
-registrationAllowed.tooltip=de Enable/disable the registration page. A link for registration will show on login page too.
-registrationEmailAsUsername=de Email as username
-registrationEmailAsUsername.tooltip=de If enabled then username field is hidden from registration form and email is used as username for new user.
-editUsernameAllowed=de Edit username
-editUsernameAllowed.tooltip=de If enabled, the username field is editable, readonly otherwise.
-resetPasswordAllowed=de Forgot password
-resetPasswordAllowed.tooltip=de Show a link on login page for user to click on when they have forgotten their credentials.
-rememberMe=de Remember Me
-rememberMe.tooltip=de Show checkbox on login page to allow user to remain logged in between browser restarts until session expires.
-verifyEmail=de Verify email
-verifyEmail.tooltip=de Require the user to verify their email address the first time they login.
-sslRequired=de Require SSL
-sslRequired.option.all=de all requests
-sslRequired.option.external=de external requests
-sslRequired.option.none=de none
-sslRequired.tooltip=de Is HTTPS required? 'None' means HTTPS is not required for any client IP address. 'External requests' means localhost and private IP addresses can access without HTTPS. 'All requests' means HTTPS is required for all IP addresses.
-publicKey=de Public key
-gen-new-keys=de Generate new keys
-certificate=de Certificate
-host=de Host
-smtp-host=de SMTP Host
-port=de Port
-smtp-port=de SMTP Port (defaults to 25)
-from=de From
-sender-email-addr=de Sender Email Address
-enable-ssl=de Enable SSL
-enable-start-tls=de Enable StartTLS
-enable-auth=de Enable Authentication
-username=de Username
-login-username=de Login Username
-password=de Password
-login-password=de Login Password
-login-theme=de Login Theme
-login-theme.tooltip=de Select theme for login, TOTP, grant, registration, and forgot password pages.
-account-theme=de Account Theme
-account-theme.tooltip=de Select theme for user account management pages.
-admin-console-theme=de Admin Console Theme
-select-theme-admin-console=de Select theme for admin console.
-email-theme=de Email Theme
-select-theme-email=de Select theme for emails that are sent by the server.
-i18n-enabled=de Internationalization Enabled
-supported-locales=de Supported Locales
-supported-locales.placeholder=de Type a locale and enter
-default-locale=de Default Locale
-realm-cache-clear=de Realm Cache
-realm-cache-clear.tooltip=de Clears all entries from the realm cache (this will clear entries for all realms)
-user-cache-clear=de User Cache
-user-cache-clear.tooltip=de Clears all entries from the user cache (this will clear entries for all realms)
-revoke-refresh-token=de Revoke Refresh Token
-revoke-refresh-token.tooltip=de If enabled refresh tokens can only be used once. Otherwise refresh tokens are not revoked when used and can be used multiple times.
-sso-session-idle=de SSO Session Idle
-seconds=de Seconds
-minutes=de Minutes
-hours=de Hours
-days=de Days
-sso-session-max=de SSO Session Max
-sso-session-idle.tooltip=de Time a session is allowed to be idle before it expires. Tokens and browser sessions are invalidated when a session is expired.
-sso-session-max.tooltip=de Max time before a session is expired. Tokens and browser sessions are invalidated when a session is expired.
-offline-session-idle=de Offline Session Idle
-offline-session-idle.tooltip=de Time an offline session is allowed to be idle before it expires. You need to use offline token to refresh at least once within this period, otherwise offline session will expire.
-access-token-lifespan=de Access Token Lifespan
-access-token-lifespan.tooltip=de Max time before an access token is expired. This value is recommended to be short relative to the SSO timeout.
-access-token-lifespan-for-implicit-flow=de Access Token Lifespan For Implicit Flow
-access-token-lifespan-for-implicit-flow.tooltip=de Max time before an access token issued during OpenID Connect Implicit Flow is expired. This value is recommended to be shorter than SSO timeout. There is no possibility to refresh token during implicit flow, that's why there is separate timeout different to 'Access Token Lifespan'.
-client-login-timeout=de Client login timeout
-client-login-timeout.tooltip=de Max time an client has to finish the access token protocol. This should normally be 1 minute.
-login-timeout=de Login timeout
-login-timeout.tooltip=de Max time a user has to complete a login. This is recommended to be relatively long. 30 minutes or more.
-login-action-timeout=de Login action timeout
-login-action-timeout.tooltip=de Max time a user has to complete login related actions like update password or configure totp. This is recommended to be relatively long. 5 minutes or more.
-headers=de Headers
-brute-force-detection=de Brute Force Detection
-x-frame-options=de X-Frame-Options
-click-label-for-info=de Click on label link for more information. The default value prevents pages from being included via non-origin iframes.
-content-sec-policy=de Content-Security-Policy
-max-login-failures=de Max Login Failures
-max-login-failures.tooltip=de How many failures before wait is triggered.
-wait-increment=de Wait Increment
-wait-increment.tooltip=de When failure threshold has been met, how much time should the user be locked out?
-quick-login-check-millis=de Quick Login Check Milli Seconds
-quick-login-check-millis.tooltip=de If a failure happens concurrently too quickly, lock out the user.
-min-quick-login-wait=de Minimum Quick Login Wait
-min-quick-login-wait.tooltip=de How long to wait after a quick login failure.
-max-wait=de Max Wait
-max-wait.tooltip=de Max time a user will be locked out.
-failure-reset-time=de Failure Reset Time
-failure-reset-time.tooltip=de When will failure count be reset?
-realm-tab-login=de Login
-realm-tab-keys=de Keys
-realm-tab-email=de Email
-realm-tab-themes=de Themes
-realm-tab-cache=de Cache
-realm-tab-tokens=de Tokens
-realm-tab-client-initial-access=de Initial Access Tokens
-realm-tab-security-defenses=de Security Defenses
-realm-tab-general=de General
-add-realm=de Add realm
-
-#Session settings
-realm-sessions=de Realm Sessions
-revocation=de Revocation
-logout-all=de Logout all
-active-sessions=de Active Sessions
-sessions=de Sessions
-not-before=de Not Before
-not-before.tooltip=de Revoke any tokens issued before this date.
-set-to-now=de Set to now
-push=de Push
-push.tooltip=de For every client that has an admin URL, notify them of the new revocation policy.
-
-#Protocol Mapper
-usermodel.prop.label=de Property
-usermodel.prop.tooltip=de Name of the property method in the UserModel interface. For example, a value of 'email' would reference the UserModel.getEmail() method.
-usermodel.attr.label=de User Attribute
-usermodel.attr.tooltip=de Name of stored user attribute which is the name of an attribute within the UserModel.attribute map.
-userSession.modelNote.label=de User Session Note
-userSession.modelNote.tooltip=de Name of stored user session note within the UserSessionModel.note map.
-multivalued.label=de Multivalued
-multivalued.tooltip=de Indicates if attribute supports multiple values. If true, then the list of all values of this attribute will be set as claim. If false, then just first value will be set as claim
-selectRole.label=de Select Role
-selectRole.tooltip=de Enter role in the textbox to the left, or click this button to browse and select the role you want
-tokenClaimName.label=de Token Claim Name
-tokenClaimName.tooltip=de Name of the claim to insert into the token. This can be a fully qualified name like 'address.street'. In this case, a nested json object will be created.
-jsonType.label=de Claim JSON Type
-jsonType.tooltip=de JSON type that should be used to populate the json claim in the token. long, int, boolean, and String are valid values.
-includeInIdToken.label=de Add to ID token
-includeInIdToken.tooltip=de Should the claim be added to the ID token?
-includeInAccessToken.label=de Add to access token
-includeInAccessToken.tooltip=de Should the claim be added to the access token?
-
-
-# client details
-clients.tooltip=de Clients are trusted browser apps and web services in a realm. These clients can request a login. You can also define client specific roles.
-search.placeholder=de Search...
-create=de Create
-import=de Import
-client-id=de Client ID
-base-url=de Base URL
-actions=de Actions
-not-defined=de Not defined
-edit=de Edit
-delete=de Delete
-no-results=de No results
-no-clients-available=de No clients available
-add-client=de Add Client
-select-file=de Select file
-view-details=de View details
-clear-import=de Clear import
-client-id.tooltip=de Specifies ID referenced in URI and tokens. For example 'my-client'
-client.name.tooltip=de Specifies display name of the client. For example 'My Client'. Supports keys for localized values as well. For example\: ${my_client}
-client.enabled.tooltip=de Disabled clients cannot initiate a login or have obtain access tokens.
-consent-required=de Consent Required
-consent-required.tooltip=de If enabled users have to consent to client access.
-direct-grants-only=de Direct Grants Only
-direct-grants-only.tooltip=de When enabled, client can only obtain grants from grant REST API.
-client-protocol=de Client Protocol
-client-protocol.tooltip=de 'OpenID connect' allows Clients to verify the identity of the End-User based on the authentication performed by an Authorization Server.'SAML' enables web-based authentication and authorization scenarios including cross-domain single sign-on (SSO) and uses security tokens containing assertions to pass information.
-access-type=de Access Type
-access-type.tooltip=de 'Confidential' clients require a secret to initiate login protocol. 'Public' clients do not require a secret. 'Bearer-only' clients are web services that never initiate a login.
-standard-flow-enabled=de Standard Flow Enabled
-standard-flow-enabled.tooltip=de This enables standard OpenID Connect redirect based authentication with authorization code. In terms of OpenID Connect or OAuth2 specifications, this enables support of 'Authorization Code Flow' for this client.
-implicit-flow-enabled=de Implicit Flow Enabled
-implicit-flow-enabled.tooltip=de This enables support for OpenID Connect redirect based authentication without authorization code. In terms of OpenID Connect or OAuth2 specifications, this enables support of 'Implicit Flow' for this client.
-direct-access-grants-enabled=de Direct Access Grants Enabled
-direct-access-grants-enabled.tooltip=de This enables support for Direct Access Grants, which means that client has access to username/password of user and exchange it directly with Keycloak server for access token. In terms of OAuth2 specification, this enables support of 'Resource Owner Password Credentials Grant' for this client.
-service-accounts-enabled=de Service Accounts Enabled
-service-accounts-enabled.tooltip=de Allows you to authenticate this client to Keycloak and retrieve access token dedicated to this client.
-include-authnstatement=de Include AuthnStatement
-include-authnstatement.tooltip=de Should a statement specifying the method and timestamp be included in login responses?
-sign-documents=de Sign Documents
-sign-documents.tooltip=de Should SAML documents be signed by the realm?
-sign-assertions=de Sign Assertions
-sign-assertions.tooltip=de Should assertions inside SAML documents be signed? This setting isn't needed if document is already being signed.
-signature-algorithm=de Signature Algorithm
-signature-algorithm.tooltip=de The signature algorithm to use to sign documents.
-canonicalization-method=de Canonicalization Method
-canonicalization-method.tooltip=de Canonicalization Method for XML signatures.
-encrypt-assertions=de Encrypt Assertions
-encrypt-assertions.tooltip=de Should SAML assertions be encrypted with client's public key using AES?
-client-signature-required=de Client Signature Required
-client-signature-required.tooltip=de Will the client sign their saml requests and responses? And should they be validated?
-force-post-binding=de Force POST Binding
-force-post-binding.tooltip=de Always use POST binding for responses.
-front-channel-logout=de Front Channel Logout
-front-channel-logout.tooltip=de When true, logout requires a browser redirect to client. When false, server performs a background invocation for logout.
-force-name-id-format=de Force Name ID Format
-force-name-id-format.tooltip=de Ignore requested NameID subject format and use admin console configured one.
-name-id-format=de Name ID Format
-name-id-format.tooltip=de The name ID format to use for the subject.
-root-url=de Root URL
-root-url.tooltip=de Root URL appended to relative URLs
-valid-redirect-uris=de Valid Redirect URIs
-valid-redirect-uris.tooltip=de Valid URI pattern a browser can redirect to after a successful login or logout. Simple wildcards are allowed i.e. 'http://example.com/*'. Relative path can be specified too i.e. /my/relative/path/*. Relative paths will generate a redirect URI using the request's host and port. For SAML, you must set valid URI patterns if you are relying on the consumer service URL embedded with the login request.
-base-url.tooltip=de Default URL to use when the auth server needs to redirect or link back to the client.
-admin-url=de Admin URL
-admin-url.tooltip=de URL to the admin interface of the client. Set this if the client supports the adapter REST API. This REST API allows the auth server to push revocation policies and other adminstrative tasks. Usually this is set to the base URL of the client.
-master-saml-processing-url=de Master SAML Processing URL
-master-saml-processing-url.tooltip=de If configured, this URL will be used for every binding to both the SP's Assertion Consumer and Single Logout Services. This can be individually overiden for each binding and service in the Fine Grain SAML Endpoint Configuration.
-idp-sso-url-ref=de IDP Initiated SSO URL Name
-idp-sso-url-ref.tooltip=de URL fragment name to reference client when you want to do IDP Initiated SSO. Leaving this empty will disable IDP Initiated SSO. The URL you will reference from your browser will be: {server-root}/realms/{realm}/protocol/saml/clients/{client-url-name}
-idp-sso-relay-state=de IDP Initiated SSO Relay State
-idp-sso-relay-state.tooltip=de Relay state you want to send with SAML request when you want to do IDP Initiated SSO.
-web-origins=de Web Origins
-web-origins.tooltip=de Allowed CORS origins. To permit all origins of Valid Redirect URIs add '+'. To permit all origins add '*'.
-fine-saml-endpoint-conf=de Fine Grain SAML Endpoint Configuration
-fine-saml-endpoint-conf.tooltip=de Expand this section to configure exact URLs for Assertion Consumer and Single Logout Service.
-assertion-consumer-post-binding-url=de Assertion Consumer Service POST Binding URL
-assertion-consumer-post-binding-url.tooltip=de SAML POST Binding URL for the client's assertion consumer service (login responses). You can leave this blank if you do not have a URL for this binding.
-assertion-consumer-redirect-binding-url=de Assertion Consumer Service Redirect Binding URL
-assertion-consumer-redirect-binding-url.tooltip=de SAML Redirect Binding URL for the client's assertion consumer service (login responses). You can leave this blank if you do not have a URL for this binding.
-logout-service-binding-post-url=de Logout Service POST Binding URL
-logout-service-binding-post-url.tooltip=de SAML POST Binding URL for the client's single logout service. You can leave this blank if you are using a different binding
-logout-service-redir-binding-url=de Logout Service Redirect Binding URL
-logout-service-redir-binding-url.tooltip=de SAML Redirect Binding URL for the client's single logout service. You can leave this blank if you are using a different binding.
-
-# client import
-import-client=de Import Client
-format-option=de Format Option
-select-format=de Select a Format
-import-file=de Import File
-
-# client tabs
-settings=de Settings
-credentials=de Credentials
-saml-keys=de SAML Keys
-roles=de Roles
-mappers=de Mappers
-mappers.tooltip=de Protocol mappers perform transformation on tokens and documents. They an do things like map user data into protocol claims, or just transform any requests going between the client and auth server.
-scope=de Scope
-scope.tooltip=de Scope mappings allow you to restrict which user role mappings are included within the access token requested by the client.
-sessions.tooltip=de View active sessions for this client. Allows you to see which users are active and when they logged in.
-offline-access=de Offline Access
-offline-access.tooltip=de View offline sessions for this client. Allows you to see which users retrieve offline token and when they retrieve it. To revoke all tokens for the client, go to Revocation tab and set not before value to now.
-clustering=de Clustering
-installation=de Installation
-installation.tooltip=de Helper utility for generating various client adapter configuration formats which you can download or cut and paste to configure your clients.
-service-account-roles=de Service Account Roles
-service-account-roles.tooltip=de Allows you to authenticate role mappings for the service account dedicated to this client.
-
-# client credentials
-client-authenticator=de Client Authenticator
-client-authenticator.tooltip=de Client Authenticator used for authentication this client against Keycloak server
-certificate.tooltip=de Client Certificate for validate JWT issued by client and signed by Client private key from your keystore.
-no-client-certificate-configured=de No client certificate configured
-gen-new-keys-and-cert=de Generate new keys and certificate
-import-certificate=de Import Certificate
-gen-client-private-key=de Generate Client Private Key
-generate-private-key=de Generate Private Key
-archive-format=de Archive Format
-archive-format.tooltip=de Java keystore or PKCS12 archive format.
-key-alias=de Key Alias
-key-alias.tooltip=de Archive alias for your private key and certificate.
-key-password=de Key Password
-key-password.tooltip=de Password to access the private key in the archive
-store-password=de Store Password
-store-password.tooltip=de Password to access the archive itself
-generate-and-download=de Generate and Download
-client-certificate-import=de Client Certificate Import
-import-client-certificate=de Import Client Certificate
-jwt-import.key-alias.tooltip=de Archive alias for your certificate.
-secret=de Secret
-regenerate-secret=de Regenerate Secret
-registrationAccessToken=de Registration access token
-registrationAccessToken.regenerate=de Regenerate registration access token
-registrationAccessToken.tooltip=de The registration access token provides access for clients to the client registration service.
-add-role=de Add Role
-role-name=de Role Name
-composite=de Composite
-description=de Description
-no-client-roles-available=de No client roles available
-scope-param-required=de Scope Param Required
-scope-param-required.tooltip=de This role will only be granted if scope parameter with role name is used during authorization/token request.
-composite-roles=de Composite Roles
-composite-roles.tooltip=de When this role is (un)assigned to a user any role associated with it will be (un)assigned implicitly.
-realm-roles=de Realm Roles
-available-roles=de Available Roles
-add-selected=de Add selected
-associated-roles=de Associated Roles
-composite.associated-realm-roles.tooltip=de Realm level roles associated with this composite role.
-composite.available-realm-roles.tooltip=de Realm level roles associated with this composite role.
-remove-selected=de Remove selected
-client-roles=de Client Roles
-select-client-to-view-roles=de Select client to view roles for client
-available-roles.tooltip=de Roles from this client that you can associate to this composite role.
-client.associated-roles.tooltip=de Client roles associated with this composite role.
-add-builtin=de Add Builtin
-category=de Category
-type=de Type
-no-mappers-available=de No mappers available
-add-builtin-protocol-mappers=de Add Builtin Protocol Mappers
-add-builtin-protocol-mapper=de Add Builtin Protocol Mapper
-scope-mappings=de Scope Mappings
-full-scope-allowed=de Full Scope Allowed
-full-scope-allowed.tooltip=de Allows you to disable all restrictions.
-scope.available-roles.tooltip=de Realm level roles that can be assigned to scope.
-assigned-roles=de Assigned Roles
-assigned-roles.tooltip=de Realm level roles assigned to scope.
-effective-roles=de Effective Roles
-realm.effective-roles.tooltip=de Assigned realm level roles that may have been inherited from a composite role.
-select-client-roles.tooltip=de Select client to view roles for client
-assign.available-roles.tooltip=de Client roles available to be assigned.
-client.assigned-roles.tooltip=de Assigned client roles.
-client.effective-roles.tooltip=de Assigned client roles that may have been inherited from a composite role.
-basic-configuration=de Basic configuration
-node-reregistration-timeout=de Node Re-registration Timeout
-node-reregistration-timeout.tooltip=de Interval to specify max time for registered clients cluster nodes to re-register. If cluster node won't send re-registration request to Keycloak within this time, it will be unregistered from Keycloak
-registered-cluster-nodes=de Registered cluster nodes
-register-node-manually=de Register node manually
-test-cluster-availability=de Test cluster availability
-last-registration=de Last registration
-node-host=de Node host
-no-registered-cluster-nodes=de No registered cluster nodes available
-cluster-nodes=de Cluster Nodes
-add-node=de Add Node
-active-sessions.tooltip=de Total number of active user sessions for this client.
-show-sessions=de Show Sessions
-show-sessions.tooltip=de Warning, this is a potentially expensive operation depending on number of active sessions.
-user=de User
-from-ip=de From IP
-session-start=de Session Start
-first-page=de First Page
-previous-page=de Previous Page
-next-page=de Next Page
-client-revoke.not-before.tooltip=de Revoke any tokens issued before this date for this client.
-client-revoke.push.tooltip=de If admin URL is configured for this client, push this policy to that client.
-select-a-format=de Select a Format
-download=de Download
-offline-tokens=de Offline Tokens
-offline-tokens.tooltip=de Total number of offline tokens for this client.
-show-offline-tokens=de Show Offline Tokens
-show-offline-tokens.tooltip=de Warning, this is a potentially expensive operation depending on number of offline tokens.
-token-issued=de Token Issued
-last-access=de Last Access
-last-refresh=de Last Refresh
-key-export=de Key Export
-key-import=de Key Import
-export-saml-key=de Export SAML Key
-import-saml-key=de Import SAML Key
-realm-certificate-alias=de Realm Certificate Alias
-realm-certificate-alias.tooltip=de Realm certificate is stored in archive too. This is the alias to it.
-signing-key=de Signing Key
-saml-signing-key=de SAML Signing Key.
-private-key=de Private Key
-generate-new-keys=de Generate new keys
-export=de Export
-encryption-key=de Encryption Key
-saml-encryption-key.tooltip=de SAML Encryption Key.
-service-accounts=de Service Accounts
-service-account.available-roles.tooltip=de Realm level roles that can be assigned to service account.
-service-account.assigned-roles.tooltip=de Realm level roles assigned to service account.
-service-account-is-not-enabled-for=de Service account is not enabled for {{client}}
-create-protocol-mappers=de Create Protocol Mappers
-create-protocol-mapper=de Create Protocol Mapper
-protocol=de Protocol
-protocol.tooltip=de Protocol...
-id=de ID
-mapper.name.tooltip=de Name of the mapper.
-mapper.consent-required.tooltip=de When granting temporary access, must the user consent to providing this data to the client?
-consent-text=de Consent Text
-consent-text.tooltip=de Text to display on consent page.
-mapper-type=de Mapper Type
-mapper-type.tooltip=de Type of the mapper
-select-role=de Select role
-select-role.tooltip=de Enter role in the textbox to the left, or click this button to browse and select the role you want.
-
-# realm identity providers
-identity-providers=de Identity Providers
-table-of-identity-providers=de Table of identity providers
-add-provider.placeholder=de Add provider...
-provider=de Provider
-gui-order=de GUI order
-first-broker-login-flow=de First Login Flow
-post-broker-login-flow=de Post Login Flow
-redirect-uri=de Redirect URI
-redirect-uri.tooltip=de The redirect uri to use when configuring the identity provider.
-alias=de Alias
-identity-provider.alias.tooltip=de The alias uniquely identifies an identity provider and it is also used to build the redirect uri.
-identity-provider.enabled.tooltip=de Enable/disable this identity provider.
-authenticate-by-default=de Authenticate by Default
-identity-provider.authenticate-by-default.tooltip=de Indicates if this provider should be tried by default for authentication even before displaying login screen.
-store-tokens=de Store Tokens
-identity-provider.store-tokens.tooltip=de Enable/disable if tokens must be stored after authenticating users.
-stored-tokens-readable=de Stored Tokens Readable
-identity-provider.stored-tokens-readable.tooltip=de Enable/disable if new users can read any stored tokens. This assigns the broker.read-token role.
-update-profile-on-first-login=de Update Profile on First Login
-on=de On
-on-missing-info=de On missing info
-off=de Off
-update-profile-on-first-login.tooltip=de Define conditions under which a user has to update their profile during first-time login.
-trust-email=de Trust Email
-trust-email.tooltip=de If enabled then email provided by this provider is not verified even if verification is enabled for the realm.
-gui-order.tooltip=de Number defining order of the provider in GUI (eg. on Login page).
-first-broker-login-flow.tooltip=de Alias of authentication flow, which is triggered after first login with this identity provider. Term 'First Login' means that there is not yet existing Keycloak account linked with the authenticated identity provider account.
-post-broker-login-flow.tooltip=de Alias of authentication flow, which is triggered after each login with this identity provider. Useful if you want additional verification of each user authenticated with this identity provider (for example OTP). Leave this empty if you don't want any additional authenticators to be triggered after login with this identity provider. Also note, that authenticator implementations must assume that user is already set in ClientSession as identity provider already set it.
-openid-connect-config=de OpenID Connect Config
-openid-connect-config.tooltip=de OIDC SP and external IDP configuration.
-authorization-url=de Authorization URL
-authorization-url.tooltip=de The Authorization Url.
-token-url=de Token URL
-token-url.tooltip=de The Token URL.
-logout-url=de Logout URL
-identity-provider.logout-url.tooltip=de End session endpoint to use to logout user from external IDP.
-backchannel-logout=de Backchannel Logout
-backchannel-logout.tooltip=de Does the external IDP support backchannel logout?
-user-info-url=de User Info URL
-user-info-url.tooltip=de The User Info Url. This is optional.
-identity-provider.client-id.tooltip=de The client or client identifier registered within the identity provider.
-client-secret=de Client Secret
-show-secret=de Show secret
-hide-secret=de Hide secret
-client-secret.tooltip=de The client or client secret registered within the identity provider.
-issuer=de Issuer
-issuer.tooltip=de The issuer identifier for the issuer of the response. If not provided, no validation will be performed.
-default-scopes=de Default Scopes
-identity-provider.default-scopes.tooltip=de The scopes to be sent when asking for authorization. It can be a space-separated list of scopes. Defaults to 'openid'.
-prompt=de Prompt
-unspecified.option=de unspecified
-none.option=de none
-consent.option=de consent
-login.option=de login
-select-account.option=de select_account
-prompt.tooltip=de Specifies whether the Authorization Server prompts the End-User for reauthentication and consent.
-validate-signatures=de Validate Signatures
-identity-provider.validate-signatures.tooltip=de Enable/disable signature validation of external IDP signatures.
-validating-public-key=de Validating Public Key
-identity-provider.validating-public-key.tooltip=de The public key in PEM format that must be used to verify external IDP signatures.
-import-external-idp-config=de Import External IDP Config
-import-external-idp-config.tooltip=de Allows you to load external IDP metadata from a config file or to download it from a URL.
-import-from-url=de Import from URL
-identity-provider.import-from-url.tooltip=de Import metadata from a remote IDP discovery descriptor.
-import-from-file=de Import from file
-identity-provider.import-from-file.tooltip=de Import metadata from a downloaded IDP discovery descriptor.
-saml-config=de SAML Config
-identity-provider.saml-config.tooltip=de SAML SP and external IDP configuration.
-single-signon-service-url=de Single Sign-On Service URL
-saml.single-signon-service-url.tooltip=de The Url that must be used to send authentication requests (SAML AuthnRequest).
-single-logout-service-url=de Single Logout Service URL
-saml.single-logout-service-url.tooltip=de The Url that must be used to send logout requests.
-nameid-policy-format=de NameID Policy Format
-nameid-policy-format.tooltip=de Specifies the URI reference corresponding to a name identifier format. Defaults to urn:oasis:names:tc:SAML:2.0:nameid-format:persistent.
-http-post-binding-response=de HTTP-POST Binding Response
-http-post-binding-response.tooltip=de Indicates whether to respond to requests using HTTP-POST binding. If false, HTTP-REDIRECT binding will be used.
-http-post-binding-for-authn-request=de HTTP-POST Binding for AuthnRequest
-http-post-binding-for-authn-request.tooltip=de Indicates whether the AuthnRequest must be sent using HTTP-POST binding. If false, HTTP-REDIRECT binding will be used.
-want-authn-requests-signed=de Want AuthnRequests Signed
-want-authn-requests-signed.tooltip=de Indicates whether the identity provider expects signed a AuthnRequest.
-force-authentication=de Force Authentication
-identity-provider.force-authentication.tooltip=de Indicates whether the identity provider must authenticate the presenter directly rather than rely on a previous security context.
-validate-signature=de Validate Signature
-saml.validate-signature.tooltip=de Enable/disable signature validation of SAML responses.
-validating-x509-certificate=de Validating X509 Certificate
-validating-x509-certificate.tooltip=de The certificate in PEM format that must be used to check for signatures.
-saml.import-from-url.tooltip=de Import metadata from a remote IDP SAML entity descriptor.
-social.client-id.tooltip=de The client identifier registered with the identity provider.
-social.client-secret.tooltip=de The client secret registered with the identity provider.
-social.default-scopes.tooltip=de The scopes to be sent when asking for authorization. See documentation for possible values, separator and default value'.
-key=de Key
-stackoverflow.key.tooltip=de The Key obtained from Stack Overflow client registration.
-
-realms=de Realms
-realm=de Realm
-
-identity-provider-mappers=de Identity Provider Mappers
-create-identity-provider-mapper=de Create Identity Provider Mapper
-add-identity-provider-mapper=de Add Identity Provider Mapper
-client.description.tooltip=de Specifies description of the client. For example 'My Client for TimeSheets'. Supports keys for localized values as well. For example\: ${my_client_description}
-
-expires=de Expires
-expiration=de Expiration
-expiration.tooltip=de Specifies how long the token should be valid
-count=de Count
-count.tooltip=de Specifies how many clients can be created using the token
-remainingCount=de Remaining Count
-created=de Created
-back=de Back
-initial-access-tokens=de Initial Access Tokens
-add-initial-access-tokens=de Add Initial Access Token
-initial-access-token=de Initial Access Token
-initial-access.copyPaste.tooltip=de Copy/paste the initial access token before navigating away from this page as it's not posible to retrieve later
-continue=de Continue
-initial-access-token.confirm.title=de Copy Initial Access Token
-initial-access-token.confirm.text=de Please copy and paste the initial access token before confirming as it can't be retrieved later
-
-client-templates=de Client Templates
-client-templates.tooltip=de Client templates allow you to define common configuration that is shared between multiple clients
-
-groups=de Groups
-
-group.add-selected.tooltip=de Realm roles that can be assigned to the group.
-group.assigned-roles.tooltip=de Realm roles mapped to the group
-group.effective-roles.tooltip=de All realm role mappings. Some roles here might be inherited from a mapped composite role.
-group.available-roles.tooltip=de Assignable roles from this client.
-group.assigned-roles-client.tooltip=de Role mappings for this client.
-group.effective-roles-client.tooltip=de Role mappings for this client. Some roles here might be inherited from a mapped composite role.
-
-default-roles=de Default Roles
-no-realm-roles-available=de No realm roles available
-
-users=de Users
-user.add-selected.tooltip=de Realm roles that can be assigned to the user.
-user.assigned-roles.tooltip=de Realm roles mapped to the user
-user.effective-roles.tooltip=de All realm role mappings. Some roles here might be inherited from a mapped composite role.
-user.available-roles.tooltip=de Assignable roles from this client.
-user.assigned-roles-client.tooltip=de Role mappings for this client.
-user.effective-roles-client.tooltip=de Role mappings for this client. Some roles here might be inherited from a mapped composite role.
-default.available-roles.tooltip=de Realm level roles that can be assigned.
-realm-default-roles=de Realm Default Roles
-realm-default-roles.tooltip=de Realm level roles assigned to new users.
-default.available-roles-client.tooltip=de Roles from this client that are assignable as a default.
-client-default-roles=de Client Default Roles
-client-default-roles.tooltip=de Roles from this client assigned as a default role.
-composite.available-roles.tooltip=de Realm level roles associated with this composite role.
-composite.associated-roles.tooltip=de Realm level roles associated with this composite role.
-composite.available-roles-client.tooltip=de Roles from this client that you can associate to this composite role.
-composite.associated-roles-client.tooltip=de Client roles associated with this composite role.
-partial-import=de Partial Import
-
-file=de File
-import-from-realm=de Import from realm
-import-users=de Import users
-import-clients=de Import clients
-import-identity-providers=de Import identity providers
-import-realm-roles=de Import realm roles
-import-client-roles=de Import client roles
-if-resource-exists=de If a resource exists
-fail=de Fail
-skip=de Skip
-overwrite=de Overwrite
-if-resource-exists.tooltip=de Specify what should be done if you try to import a resource that already exists.
-
-action=de Action
-role-selector=de Role Selector
-realm-roles.tooltip=de Realm roles that can be selected.
-
-select-a-role=de Select a role
-select-realm-role=de Select realm role
-client-roles.tooltip=de Client roles that can be selected.
-select-client-role=de Select client role
-
-client-template=de Client Template
-client-template.tooltip=de Client template this client inherits configuration from
-client-saml-endpoint=de Client SAML Endpoint
-add-client-template=de Add client template
-
-manage=de Manage
-authentication=de Authentication
-user-federation=de User Federation
-events=de Events
-realm-settings=de Realm Settings
-configure=de Configure
-select-realm=de Select realm
-add=de Add
-
-client-template.name.tooltip=de Name of the client template. Must be unique in the realm
-client-template.description.tooltip=de Description of the client template
-client-template.protocol.tooltip=de Which SSO protocol configuration is being supplied by this client template
-
-add-user-federation-provider=de Add user federation provider
-required-settings=de Required Settings
-provider-id=de Provider ID
-console-display-name=de Console Display Name
-console-display-name.tooltip=de Display name of provider when linked in admin console.
-priority=de Priority
-priority.tooltip=de Priority of provider when doing a user lookup. Lowest first.
-sync-settings=de Sync Settings
-periodic-full-sync=de Periodic Full Sync
-periodic-full-sync.tooltip=de Does periodic full synchronization of provider users to Keycloak should be enabled or not
-full-sync-period=de Full Sync Period
-full-sync-period.tooltip=de Period for full synchronization in seconds
-periodic-changed-users-sync=de Periodic Changed Users Sync
-periodic-changed-users-sync.tooltip=de Does periodic synchronization of changed or newly created provider users to Keycloak should be enabled or not
-changed-users-sync-period=de Changed Users Sync Period
-changed-users-sync-period.tooltip=de Period for synchronization of changed or newly created provider users in seconds
-synchronize-changed-users=de Synchronize changed users
-synchronize-all-users=de Synchronize all users
-kerberos-realm=de Kerberos Realm
-kerberos-realm.tooltip=de Name of kerberos realm. For example FOO.ORG
-server-principal=de Server Principal
-server-principal.tooltip=de Full name of server principal for HTTP service including server and domain name. For example HTTP/host.foo.org@FOO.ORG
-keytab=de KeyTab
-keytab.tooltip=de Location of Kerberos KeyTab file containing the credentials of server principal. For example /etc/krb5.keytab
-debug=de Debug
-debug.tooltip=de Enable/disable debug logging to standard output for Krb5LoginModule.
-allow-password-authentication=de Allow Password Authentication
-allow-password-authentication.tooltip=de Enable/disable possibility of username/password authentication against Kerberos database
-edit-mode=de Edit Mode
-edit-mode.tooltip=de READ_ONLY means that password updates are not allowed and user always authenticates with Kerberos password. UNSYNCED means user can change his password in Keycloak database and this one will be used instead of Kerberos password then
-ldap.edit-mode.tooltip=de READ_ONLY is a read only LDAP store. WRITABLE means data will be synced back to LDAP on demand. UNSYNCED means user data will be imported, but not synced back to LDAP.
-update-profile-first-login=de Update Profile First Login
-update-profile-first-login.tooltip=de Update profile on first login
-sync-registrations=de Sync Registrations
-ldap.sync-registrations.tooltip=de Should newly created users be created within LDAP store? Priority effects which provider is chose to sync the new user.
-vendor=de Vendor
-ldap.vendor.tooltip=de LDAP vendor (provider)
-username-ldap-attribute=de Username LDAP attribute
-ldap-attribute-name-for-username=de LDAP attribute name for username
-username-ldap-attribute.tooltip=de Name of LDAP attribute, which is mapped as Keycloak username. For many LDAP server vendors it can be 'uid'. For Active directory it can be 'sAMAccountName' or 'cn'. The attribute should be filled for all LDAP user records you want to import from LDAP to Keycloak.
-rdn-ldap-attribute=de RDN LDAP attribute
-ldap-attribute-name-for-user-rdn=de LDAP attribute name for user RDN
-rdn-ldap-attribute.tooltip=de Name of LDAP attribute, which is used as RDN (top attribute) of typical user DN. Usually it's the same as Username LDAP attribute, however it's not required. For example for Active directory it's common to use 'cn' as RDN attribute when username attribute might be 'sAMAccountName'.
-uuid-ldap-attribute=de UUID LDAP attribute
-ldap-attribute-name-for-uuid=de LDAP attribute name for UUID
-uuid-ldap-attribute.tooltip=de Name of LDAP attribute, which is used as unique object identifier (UUID) for objects in LDAP. For many LDAP server vendors it's 'entryUUID' however some are different. For example for Active directory it should be 'objectGUID'. If your LDAP server really doesn't support the notion of UUID, you can use any other attribute, which is supposed to be unique among LDAP users in tree. For example 'uid' or 'entryDN'.
-user-object-classes=de User Object Classes
-ldap-user-object-classes.placeholder=de LDAP User Object Classes (div. by comma)
-
-ldap-connection-url=de LDAP connection URL
-ldap-users-dn=de LDAP Users DN
-ldap-bind-dn=de LDAP Bind DN
-ldap-bind-credentials=de LDAP Bind Credentials
-ldap-filter=de LDAP Filter
-ldap.user-object-classes.tooltip=de All values of LDAP objectClass attribute for users in LDAP divided by comma. For example: 'inetOrgPerson, organizationalPerson' . Newly created Keycloak users will be written to LDAP with all those object classes and existing LDAP user records are found just if they contain all those object classes.\
-
-connection-url=de Connection URL
-ldap.connection-url.tooltip=de Connection URL to your LDAP server
-test-connection=de Test connection
-users-dn=de Users DN
-ldap.users-dn.tooltip=de Full DN of LDAP tree where your users are. This DN is parent of LDAP users. It could be for example 'ou=users,dc=example,dc=com' assuming that your typical user will have DN like 'uid=john,ou=users,dc=example,dc=com'
-authentication-type=de Authentication Type
-ldap.authentication-type.tooltip=de LDAP Authentication type. Right now just 'none' (anonymous LDAP authentication) or 'simple' (Bind credential + Bind password authentication) mechanisms are available
-bind-dn=de Bind DN
-ldap.bind-dn.tooltip=de DN of LDAP admin, which will be used by Keycloak to access LDAP server
-bind-credential=de Bind Credential
-ldap.bind-credential.tooltip=de Password of LDAP admin
-test-authentication=de Test authentication
-custom-user-ldap-filter=de Custom User LDAP Filter
-ldap.custom-user-ldap-filter.tooltip=de Additional LDAP Filter for filtering searched users. Leave this empty if you don't need additional filter. Make sure that it starts with '(' and ends with ')'
-search-scope=de Search Scope
-ldap.search-scope.tooltip=de For one level, we search for users just in DNs specified by User DNs. For subtree, we search in whole of their subtree. See LDAP documentation for more details
-connection-pooling=de Connection Pooling
-ldap.connection-pooling.tooltip=de Does Keycloak should use connection pooling for accessing LDAP server
-ldap.pagination.tooltip=de Does the LDAP server support pagination.
-kerberos-integration=de Kerberos Integration
-allow-kerberos-authentication=de Allow Kerberos authentication
-ldap.allow-kerberos-authentication.tooltip=de Enable/disable HTTP authentication of users with SPNEGO/Kerberos tokens. The data about authenticated users will be provisioned from this LDAP server
-use-kerberos-for-password-authentication=de Use Kerberos For Password Authentication
-ldap.use-kerberos-for-password-authentication.tooltip=de Use Kerberos login module for authenticate username/password against Kerberos server instead of authenticating against LDAP server with Directory Service API
-batch-size=de Batch Size
-ldap.batch-size.tooltip=de Count of LDAP users to be imported from LDAP to Keycloak within single transaction.
-ldap.periodic-full-sync.tooltip=de Does periodic full synchronization of LDAP users to Keycloak should be enabled or not
-ldap.periodic-changed-users-sync.tooltip=de Does periodic synchronization of changed or newly created LDAP users to Keycloak should be enabled or not
-ldap.changed-users-sync-period.tooltip=de Period for synchronization of changed or newly created LDAP users in seconds
-user-federation-mappers=de User Federation Mappers
-create-user-federation-mapper=de Create user federation mapper
-add-user-federation-mapper=de Add user federation mapper
-provider-name=de Provider Name
-no-user-federation-providers-configured=de No user federation providers configured
-add-identity-provider=de Add identity provider
-add-identity-provider-link=de Add identity provider link
-identity-provider=de Identity Provider
-identity-provider-user-id=de Identity Provider User ID
-identity-provider-user-id.tooltip=de Unique ID of the user on the Identity Provider side
-identity-provider-username=de Identity Provider Username
-identity-provider-username.tooltip=de Username on the Identity Provider side
-pagination=de Pagination
-
-browser-flow=de Browser Flow
-browser-flow.tooltip=de Select the flow you want to use for browser authentication.
-registration-flow=de Registration Flow
-registration-flow.tooltip=de Select the flow you want to use for registration.
-direct-grant-flow=de Direct Grant Flow
-direct-grant-flow.tooltip=de Select the flow you want to use for direct grant authentication.
-reset-credentials=de Reset Credentials
-reset-credentials.tooltip=de Select the flow you want to use when the user has forgotten their credentials.
-client-authentication=de Client Authentication
-client-authentication.tooltip=de Select the flow you want to use for authentication of clients.
-new=de New
-copy=de Copy
-add-execution=de Add execution
-add-flow=de Add flow
-auth-type=de Auth Type
-requirement=de Requirement
-config=de Config
-no-executions-available=de No executions available
-authentication-flows=de Authentication Flows
-create-authenticator-config=de Create authenticator config
-authenticator.alias.tooltip=de Name of the configuration
-otp-type=de OTP Type
-time-based=de Time Based
-counter-based=de Counter Based
-otp-type.tooltip=de totp is Time-Based One Time Password. 'hotp' is a counter base one time password in which the server keeps a counter to hash against.
-otp-hash-algorithm=de OTP Hash Algorithm
-otp-hash-algorithm.tooltip=de What hashing algorithm should be used to generate the OTP.
-number-of-digits=de Number of Digits
-otp.number-of-digits.tooltip=de How many digits should the OTP have?
-look-ahead-window=de Look Ahead Window
-otp.look-ahead-window.tooltip=de How far ahead should the server look just in case the token generator and server are out of time sync or counter sync?
-initial-counter=de Initial Counter
-otp.initial-counter.tooltip=de What should the initial counter value be?
-otp-token-period=de OTP Token Period
-otp-token-period.tooltip=de How many seconds should an OTP token be valid? Defaults to 30 seconds.
-table-of-password-policies=de Table of Password Policies
-add-policy.placeholder=de Add policy...
-policy-type=de Policy Type
-policy-value=de Policy Value
-admin-events=de Admin Events
-admin-events.tooltip=de Displays saved admin events for the realm. Events are related to admin account, for example a realm creation. To enable persisted events go to config.
-login-events=de Login Events
-filter=de Filter
-update=de Update
-reset=de Reset
-operation-types=de Operation Types
-select-operations.placeholder=de Select operations...
-resource-path=de Resource Path
-resource-path.tooltip=de Filter by resource path. Supports wildcards '*' to match a single part of the path and '**' matches multiple parts. For example 'realms/*/clients/asbc' matches client with id asbc in any realm, while or 'realms/master/**' matches anything in the master realm.
-date-(from)=de Date (From)
-date-(to)=de Date (To)
-authentication-details=de Authentication Details
-ip-address=de IP Address
-time=de Time
-operation-type=de Operation Type
-auth=de Auth
-representation=de Representation
-register=de Register
-required-action=de Required Action
-default-action=de Default Action
-auth.default-action.tooltip=de If enabled, any new user will have this required action assigned to it.
-no-required-actions-configured=de No required actions configured
-defaults-to-id=de Defaults to id
-flows=de Flows
-bindings=de Bindings
-required-actions=de Required Actions
-password-policy=de Password Policy
-otp-policy=de OTP Policy
-user-groups=de User Groups
-default-groups=de Default Groups
-groups.default-groups.tooltip=de Set of groups that new users will automatically join.
-cut=de Cut
-paste=de Paste
-
-create-group=de Create group
-create-authenticator-execution=de Create Authenticator Execution
-create-form-action-execution=de Create Form Action Execution
-create-top-level-form=de Create Top Level Form
-flow.alias.tooltip=de Specifies display name for the flow.
-top-level-flow-type=de Top Level Flow Type
-flow.generic=de generic
-flow.client=de client
-top-level-flow-type.tooltip=de What kind of top level flow is it? Type 'client' is used for authentication of clients (applications) when generic is for users and everything else
-create-execution-flow=de Create Execution Flow
-flow-type=de Flow Type
-flow.form.type=de form
-flow-type.tooltip=de What kind of form is it
-form-provider=de Form Provider
-default-groups.tooltip=de Newly created or registered users will automatically be added to these groups
-select-a-type.placeholder=de select a type
-available-groups=de Available Groups
-available-groups.tooltip=de Select a group you want to add as a default.
-value=de Value
-table-of-group-members=de Table of group members
-last-name=de Last Name
-first-name=de First Name
-email=de Email
-toggle-navigation=de Toggle navigation
-manage-account=de Manage account
-sign-out=de Sign Out
-server-info=de Server Info
-resource-not-found=de Resource <strong>not found</strong>...
-resource-not-found.instruction=de We could not find the resource you are looking for. Please make sure the URL you entered is correct.
-go-to-the-home-page=de Go to the home page »
-page-not-found=de Page <strong>not found</strong>...
-page-not-found.instruction=de We could not find the page you are looking for. Please make sure the URL you entered is correct.
-events.tooltip=de Displays saved events for the realm. Events are related to user accounts, for example a user login. To enable persisted events go to config.
-select-event-types.placeholder=de Select event types...
-events-config.tooltip=de Displays configuration options to enable persistence of user and admin events.
-select-an-action.placeholder=de Select an action...
-event-listeners.tooltip=de Configure what listeners receive events for the realm.
-login.save-events.tooltip=de If enabled login events are saved to the database which makes events available to the admin and account management consoles.
-clear-events.tooltip=de Deletes all events in the database.
-events.expiration.tooltip=de Sets the expiration for events. Expired events are periodically deleted from the database.
-admin-events-settings=de Admin Events Settings
-save-events=de Save events
-admin.save-events.tooltip=de If enabled admin events are saved to the database which makes events available to the admin console.
-saved-types.tooltip=de Configure what event types are saved.
-include-representation=de Include Representation
-include-representation.tooltip=de Include JSON representation for create and update requests.
-clear-admin-events.tooltip=de Deletes all admin events in the database.
-server-version=de Server Version
-info=de Info
-providers=de Providers
-server-time=de Server Time
-server-uptime=de Server Uptime
-memory=de Memory
-total-memory=de Total Memory
-free-memory=de Free Memory
-used-memory=de Used Memory
-system=de System
-current-working-directory=de Current Working Directory
-java-version=de Java Version
-java-vendor=de Java Vendor
-java-runtime=de Java Runtime
-java-vm=de Java VM
-java-vm-version=de Java VM Version
-java-home=de Java Home
-user-name=de User Name
-user-timezone=de User Timezone
-user-locale=de User Locale
-system-encoding=de System Encoding
-operating-system=de Operating System
-os-architecture=de OS Architecture
-spi=de SPI
-granted-roles=de Granted Roles
-granted-protocol-mappers=de Granted Protocol Mappers
-additional-grants=de Additional Grants
-revoke=de Revoke
-new-password=de New Password
-password-confirmation=de Password Confirmation
-credentials.temporary.tooltip=de If enabled user is required to change password on next login
-remove-totp=de Remove TOTP
-credentials.remove-totp.tooltip=de Remove one time password generator for user.
-reset-actions=de Reset Actions
-credentials.reset-actions.tooltip=de Set of actions to execute when sending the user a Reset Actions Email. 'Verify email' sends an email to the user to verify their email address. 'Update profile' requires user to enter in new personal information. 'Update password' requires user to enter in a new password. 'Configure TOTP' requires setup of a mobile password generator.
-reset-actions-email=de Reset Actions Email
-send-email=de Send email
-credentials.reset-actions-email.tooltip=de Sends an email to user with an embedded link. Clicking on link will allow the user to execute the reset actions. They will not have to login prior to this. For example, set the action to update password, click this button, and the user will be able to change their password without logging in.
-add-user=de Add user
-created-at=de Created At
-user-enabled=de User Enabled
-user-enabled.tooltip=de A disabled user cannot login.
-user-temporarily-locked=de User Temporarily Locked
-user-temporarily-locked.tooltip=de The user may have been locked due to failing to login too many times.
-unlock-user=de Unlock user
-federation-link=de Federation Link
-email-verified=de Email Verified
-email-verified.tooltip=de Has the user's email been verified?
-required-user-actions=de Required User Actions
-required-user-actions.tooltip=de Require an action when the user logs in. 'Verify email' sends an email to the user to verify their email address. 'Update profile' requires user to enter in new personal information. 'Update password' requires user to enter in a new password. 'Configure TOTP' requires setup of a mobile password generator.
-locale=de Locale
-select-one.placeholder=de Select one...
-impersonate=de Impersonate
-impersonate-user=de Impersonate user
-impersonate-user.tooltip=de Login as this user. If user is in same realm as you, your current login session will be logged out before you are logged in as this user.
-identity-provider-alias=de Identity Provider Alias
-provider-user-id=de Provider User ID
-provider-username=de Provider Username
-no-identity-provider-links-available=de No identity provider links available
-group-membership=de Group Membership
-leave=de Leave
-group-membership.tooltip=de Groups user is a member of. Select a listed group and click the Leave button to leave the group.
-membership.available-groups.tooltip=de Groups a user can join. Select a group and click the join button.
-table-of-realm-users=de Table of Realm Users
-view-all-users=de View all users
-unlock-users=de Unlock users
-no-users-available=de No users available
-users.instruction=de Please enter a search, or click on view all users
-consents=de Consents
-started=de Started
-logout-all-sessions=de Logout all sessions
-logout=de Logout
-new-name=de New Name
-ok=de Ok
-attributes=de Attributes
-role-mappings=de Role Mappings
-members=de Members
-details=de Details
-identity-provider-links=de Identity Provider Links
-register-required-action=de Register required action
-gender=de Gender
-address=de Address
-phone=de Phone
-profile-url=de Profile URL
-picture-url=de Picture URL
-website=de Website
-import-keys-and-cert=de Import keys and cert
-import-keys-and-cert.tooltip=de Upload the client's key pair and cert.
-upload-keys=de Upload Keys
-download-keys-and-cert=de Download keys and cert
-no-value-assigned.placeholder=de No value assigned
-remove=de Remove
-no-group-members=de No group members
-temporary=de Temporary
-join=de Join
-event-type=de Event Type
-events-config=de Events Config
-event-listeners=de Event Listeners
-login-events-settings=de Login Events Settings
-clear-events=de Clear Events
-saved-types=de Saved Types
-clear-admin-events=de Clear admin events
-clear-changes=de Clear changes
diff --git a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
index 57836f5..a86792a 100644
--- a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
+++ b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
@@ -36,8 +36,9 @@ sslRequired=Require SSL
sslRequired.option.all=all requests
sslRequired.option.external=external requests
sslRequired.option.none=none
-sslRequired.tooltip=Is HTTPS required? 'None' means HTTPS is not required for any client IP address. 'External requests' means localhost and private IP addresses can access without HTTPS. 'All requests' means HTTPS is required for all IP addresses.
+sslRequired.tooltip=Is HTTPS required? 'None' means HTTPS is not required for any client IP address. 'External requests' means localhost and private IP addresses can access without HTTPS. 'All requests' means HTTPS is required for all IP addresses.
publicKey=Public key
+privateKey=Private key
gen-new-keys=Generate new keys
certificate=Certificate
host=Host
@@ -94,8 +95,11 @@ login-action-timeout.tooltip=Max time a user has to complete login related actio
headers=Headers
brute-force-detection=Brute Force Detection
x-frame-options=X-Frame-Options
-click-label-for-info=Click on label link for more information. The default value prevents pages from being included via non-origin iframes.
+x-frame-options-tooltip=Default value prevents pages from being included via non-origin iframes (click label for more information)
content-sec-policy=Content-Security-Policy
+content-sec-policy-tooltip=Default value prevents pages from being included via non-origin iframes (click label for more information)
+content-type-options=X-Content-Type-Options
+content-type-options-tooltip=Default value prevents Internet Explorer and Google Chrome from MIME-sniffing a response away from the declared content-type (click label for more information)
max-login-failures=Max Login Failures
max-login-failures.tooltip=How many failures before wait is triggered.
wait-increment=Wait Increment
@@ -133,7 +137,7 @@ push.tooltip=For every client that has an admin URL, notify them of the new revo
#Protocol Mapper
usermodel.prop.label=Property
-usermodel.prop.tooltip=Name of the property method in the UserModel interface. For example, a value of 'email' would reference the UserModel.getEmail() method.
+usermodel.prop.tooltip=Name of the property method in the UserModel interface. For example, a value of 'email' would reference the UserModel.getEmail() method.
usermodel.attr.label=User Attribute
usermodel.attr.tooltip=Name of stored user attribute which is the name of an attribute within the UserModel.attribute map.
userSession.modelNote.label=User Session Note
@@ -143,9 +147,9 @@ multivalued.tooltip=Indicates if attribute supports multiple values. If true, th
selectRole.label=Select Role
selectRole.tooltip=Enter role in the textbox to the left, or click this button to browse and select the role you want.
tokenClaimName.label=Token Claim Name
-tokenClaimName.tooltip=Name of the claim to insert into the token. This can be a fully qualified name like 'address.street'. In this case, a nested json object will be created.
+tokenClaimName.tooltip=Name of the claim to insert into the token. This can be a fully qualified name like 'address.street'. In this case, a nested json object will be created.
jsonType.label=Claim JSON Type
-jsonType.tooltip=JSON type that should be used to populate the json claim in the token. long, int, boolean, and String are valid values.
+jsonType.tooltip=JSON type that should be used to populate the json claim in the token. long, int, boolean, and String are valid values.
includeInIdToken.label=Add to ID token
includeInIdToken.tooltip=Should the claim be added to the ID token?
includeInAccessToken.label=Add to access token
@@ -153,7 +157,7 @@ includeInAccessToken.tooltip=Should the claim be added to the access token?
# client details
-clients.tooltip=Clients are trusted browser apps and web services in a realm. These clients can request a login. You can also define client specific roles.
+clients.tooltip=Clients are trusted browser apps and web services in a realm. These clients can request a login. You can also define client specific roles.
search.placeholder=Search...
create=Create
import=Import
@@ -169,7 +173,7 @@ add-client=Add Client
select-file=Select file
view-details=View details
clear-import=Clear import
-client-id.tooltip=Specifies ID referenced in URI and tokens. For example 'my-client'. For SAML this is also the expected issuer value from authn requests
+client-id.tooltip=Specifies ID referenced in URI and tokens. For example 'my-client'. For SAML this is also the expected issuer value from authn requests
client.name.tooltip=Specifies display name of the client. For example 'My Client'. Supports keys for localized values as well. For example\\: ${my_client}
client.enabled.tooltip=Disabled clients cannot initiate a login or have obtain access tokens.
consent-required=Consent Required
@@ -177,7 +181,7 @@ consent-required.tooltip=If enabled users have to consent to client access.
client-protocol=Client Protocol
client-protocol.tooltip='OpenID connect' allows Clients to verify the identity of the End-User based on the authentication performed by an Authorization Server.'SAML' enables web-based authentication and authorization scenarios including cross-domain single sign-on (SSO) and uses security tokens containing assertions to pass information.
access-type=Access Type
-access-type.tooltip='Confidential' clients require a secret to initiate login protocol. 'Public' clients do not require a secret. 'Bearer-only' clients are web services that never initiate a login.
+access-type.tooltip='Confidential' clients require a secret to initiate login protocol. 'Public' clients do not require a secret. 'Bearer-only' clients are web services that never initiate a login.
standard-flow-enabled=Standard Flow Enabled
standard-flow-enabled.tooltip=This enables standard OpenID Connect redirect based authentication with authorization code. In terms of OpenID Connect or OAuth2 specifications, this enables support of 'Authorization Code Flow' for this client.
implicit-flow-enabled=Implicit Flow Enabled
@@ -191,7 +195,7 @@ include-authnstatement.tooltip=Should a statement specifying the method and time
sign-documents=Sign Documents
sign-documents.tooltip=Should SAML documents be signed by the realm?
sign-assertions=Sign Assertions
-sign-assertions.tooltip=Should assertions inside SAML documents be signed? This setting isn't needed if document is already being signed.
+sign-assertions.tooltip=Should assertions inside SAML documents be signed? This setting isn't needed if document is already being signed.
signature-algorithm=Signature Algorithm
signature-algorithm.tooltip=The signature algorithm to use to sign documents.
canonicalization-method=Canonicalization Method
@@ -199,11 +203,11 @@ canonicalization-method.tooltip=Canonicalization Method for XML signatures.
encrypt-assertions=Encrypt Assertions
encrypt-assertions.tooltip=Should SAML assertions be encrypted with client's public key using AES?
client-signature-required=Client Signature Required
-client-signature-required.tooltip=Will the client sign their saml requests and responses? And should they be validated?
+client-signature-required.tooltip=Will the client sign their saml requests and responses? And should they be validated?
force-post-binding=Force POST Binding
force-post-binding.tooltip=Always use POST binding for responses.
front-channel-logout=Front Channel Logout
-front-channel-logout.tooltip=When true, logout requires a browser redirect to client. When false, server performs a background invocation for logout.
+front-channel-logout.tooltip=When true, logout requires a browser redirect to client. When false, server performs a background invocation for logout.
force-name-id-format=Force Name ID Format
force-name-id-format.tooltip=Ignore requested NameID subject format and use admin console configured one.
name-id-format=Name ID Format
@@ -211,14 +215,14 @@ name-id-format.tooltip=The name ID format to use for the subject.
root-url=Root URL
root-url.tooltip=Root URL appended to relative URLs
valid-redirect-uris=Valid Redirect URIs
-valid-redirect-uris.tooltip=Valid URI pattern a browser can redirect to after a successful login or logout. Simple wildcards are allowed i.e. 'http://example.com/*'. Relative path can be specified too i.e. /my/relative/path/*. Relative paths will generate a redirect URI using the request's host and port. For SAML, you must set valid URI patterns if you are relying on the consumer service URL embedded with the login request.
+valid-redirect-uris.tooltip=Valid URI pattern a browser can redirect to after a successful login or logout. Simple wildcards are allowed i.e. 'http://example.com/*'. Relative path can be specified too i.e. /my/relative/path/*. Relative paths are relative to the client root URL, or if none is specified the auth server root URL is used. For SAML, you must set valid URI patterns if you are relying on the consumer service URL embedded with the login request.
base-url.tooltip=Default URL to use when the auth server needs to redirect or link back to the client.
admin-url=Admin URL
-admin-url.tooltip=URL to the admin interface of the client. Set this if the client supports the adapter REST API. This REST API allows the auth server to push revocation policies and other adminstrative tasks. Usually this is set to the base URL of the client.
+admin-url.tooltip=URL to the admin interface of the client. Set this if the client supports the adapter REST API. This REST API allows the auth server to push revocation policies and other adminstrative tasks. Usually this is set to the base URL of the client.
master-saml-processing-url=Master SAML Processing URL
-master-saml-processing-url.tooltip=If configured, this URL will be used for every binding to both the SP's Assertion Consumer and Single Logout Services. This can be individually overiden for each binding and service in the Fine Grain SAML Endpoint Configuration.
+master-saml-processing-url.tooltip=If configured, this URL will be used for every binding to both the SP's Assertion Consumer and Single Logout Services. This can be individually overiden for each binding and service in the Fine Grain SAML Endpoint Configuration.
idp-sso-url-ref=IDP Initiated SSO URL Name
-idp-sso-url-ref.tooltip=URL fragment name to reference client when you want to do IDP Initiated SSO. Leaving this empty will disable IDP Initiated SSO. The URL you will reference from your browser will be: {server-root}/realms/{realm}/protocol/saml/clients/{client-url-name}
+idp-sso-url-ref.tooltip=URL fragment name to reference client when you want to do IDP Initiated SSO. Leaving this empty will disable IDP Initiated SSO. The URL you will reference from your browser will be: {server-root}/realms/{realm}/protocol/saml/clients/{client-url-name}
idp-sso-relay-state=IDP Initiated SSO Relay State
idp-sso-relay-state.tooltip=Relay state you want to send with SAML request when you want to do IDP Initiated SSO.
web-origins=Web Origins
@@ -226,13 +230,13 @@ web-origins.tooltip=Allowed CORS origins. To permit all origins of Valid Redirec
fine-saml-endpoint-conf=Fine Grain SAML Endpoint Configuration
fine-saml-endpoint-conf.tooltip=Expand this section to configure exact URLs for Assertion Consumer and Single Logout Service.
assertion-consumer-post-binding-url=Assertion Consumer Service POST Binding URL
-assertion-consumer-post-binding-url.tooltip=SAML POST Binding URL for the client's assertion consumer service (login responses). You can leave this blank if you do not have a URL for this binding.
+assertion-consumer-post-binding-url.tooltip=SAML POST Binding URL for the client's assertion consumer service (login responses). You can leave this blank if you do not have a URL for this binding.
assertion-consumer-redirect-binding-url=Assertion Consumer Service Redirect Binding URL
-assertion-consumer-redirect-binding-url.tooltip=SAML Redirect Binding URL for the client's assertion consumer service (login responses). You can leave this blank if you do not have a URL for this binding.
+assertion-consumer-redirect-binding-url.tooltip=SAML Redirect Binding URL for the client's assertion consumer service (login responses). You can leave this blank if you do not have a URL for this binding.
logout-service-binding-post-url=Logout Service POST Binding URL
-logout-service-binding-post-url.tooltip=SAML POST Binding URL for the client's single logout service. You can leave this blank if you are using a different binding
+logout-service-binding-post-url.tooltip=SAML POST Binding URL for the client's single logout service. You can leave this blank if you are using a different binding
logout-service-redir-binding-url=Logout Service Redirect Binding URL
-logout-service-redir-binding-url.tooltip=SAML Redirect Binding URL for the client's single logout service. You can leave this blank if you are using a different binding.
+logout-service-redir-binding-url.tooltip=SAML Redirect Binding URL for the client's single logout service. You can leave this blank if you are using a different binding.
# client import
import-client=Import Client
@@ -246,12 +250,12 @@ credentials=Credentials
saml-keys=SAML Keys
roles=Roles
mappers=Mappers
-mappers.tooltip=Protocol mappers perform transformation on tokens and documents. They can do things like map user data into protocol claims, or just transform any requests going between the client and auth server.
+mappers.tooltip=Protocol mappers perform transformation on tokens and documents. They can do things like map user data into protocol claims, or just transform any requests going between the client and auth server.
scope=Scope
scope.tooltip=Scope mappings allow you to restrict which user role mappings are included within the access token requested by the client.
-sessions.tooltip=View active sessions for this client. Allows you to see which users are active and when they logged in.
+sessions.tooltip=View active sessions for this client. Allows you to see which users are active and when they logged in.
offline-access=Offline Access
-offline-access.tooltip=View offline sessions for this client. Allows you to see which users retrieve offline token and when they retrieve it. To revoke all tokens for the client, go to Revocation tab and set not before value to now.
+offline-access.tooltip=View offline sessions for this client. Allows you to see which users retrieve offline token and when they retrieve it. To revoke all tokens for the client, go to Revocation tab and set not before value to now.
clustering=Clustering
installation=Installation
installation.tooltip=Helper utility for generating various client adapter configuration formats which you can download or cut and paste to configure your clients.
@@ -358,7 +362,7 @@ key-import=Key Import
export-saml-key=Export SAML Key
import-saml-key=Import SAML Key
realm-certificate-alias=Realm Certificate Alias
-realm-certificate-alias.tooltip=Realm certificate is stored in archive too. This is the alias to it.
+realm-certificate-alias.tooltip=Realm certificate is stored in archive too. This is the alias to it.
signing-key=Signing Key
saml-signing-key=SAML Signing Key.
private-key=Private Key
@@ -421,7 +425,7 @@ identity-provider.logout-url.tooltip=End session endpoint to use to logout user
backchannel-logout=Backchannel Logout
backchannel-logout.tooltip=Does the external IDP support backchannel logout?
user-info-url=User Info URL
-user-info-url.tooltip=The User Info Url. This is optional.
+user-info-url.tooltip=The User Info Url. This is optional.
identity-provider.client-id.tooltip=The client or client identifier registered within the identity provider.
client-secret=Client Secret
show-secret=Show secret
@@ -512,10 +516,10 @@ groups=Groups
group.add-selected.tooltip=Realm roles that can be assigned to the group.
group.assigned-roles.tooltip=Realm roles mapped to the group
-group.effective-roles.tooltip=All realm role mappings. Some roles here might be inherited from a mapped composite role.
+group.effective-roles.tooltip=All realm role mappings. Some roles here might be inherited from a mapped composite role.
group.available-roles.tooltip=Assignable roles from this client.
group.assigned-roles-client.tooltip=Role mappings for this client.
-group.effective-roles-client.tooltip=Role mappings for this client. Some roles here might be inherited from a mapped composite role.
+group.effective-roles-client.tooltip=Role mappings for this client. Some roles here might be inherited from a mapped composite role.
default-roles=Default Roles
no-realm-roles-available=No realm roles available
@@ -523,10 +527,10 @@ no-realm-roles-available=No realm roles available
users=Users
user.add-selected.tooltip=Realm roles that can be assigned to the user.
user.assigned-roles.tooltip=Realm roles mapped to the user
-user.effective-roles.tooltip=All realm role mappings. Some roles here might be inherited from a mapped composite role.
+user.effective-roles.tooltip=All realm role mappings. Some roles here might be inherited from a mapped composite role.
user.available-roles.tooltip=Assignable roles from this client.
user.assigned-roles-client.tooltip=Role mappings for this client.
-user.effective-roles-client.tooltip=Role mappings for this client. Some roles here might be inherited from a mapped composite role.
+user.effective-roles-client.tooltip=Role mappings for this client. Some roles here might be inherited from a mapped composite role.
default.available-roles.tooltip=Realm level roles that can be assigned.
realm-default-roles=Realm Default Roles
realm-default-roles.tooltip=Realm level roles assigned to new users.
@@ -577,7 +581,7 @@ configure=Configure
select-realm=Select realm
add=Add
-client-template.name.tooltip=Name of the client template. Must be unique in the realm
+client-template.name.tooltip=Name of the client template. Must be unique in the realm
client-template.description.tooltip=Description of the client template
client-template.protocol.tooltip=Which SSO protocol configuration is being supplied by this client template
@@ -587,7 +591,7 @@ provider-id=Provider ID
console-display-name=Console Display Name
console-display-name.tooltip=Display name of provider when linked in admin console.
priority=Priority
-priority.tooltip=Priority of provider when doing a user lookup. Lowest first.
+priority.tooltip=Priority of provider when doing a user lookup. Lowest first.
sync-settings=Sync Settings
periodic-full-sync=Periodic Full Sync
periodic-full-sync.tooltip=Does periodic full synchronization of provider users to Keycloak should be enabled or not
@@ -611,11 +615,11 @@ allow-password-authentication=Allow Password Authentication
allow-password-authentication.tooltip=Enable/disable possibility of username/password authentication against Kerberos database
edit-mode=Edit Mode
edit-mode.tooltip=READ_ONLY means that password updates are not allowed and user always authenticates with Kerberos password. UNSYNCED means user can change his password in Keycloak database and this one will be used instead of Kerberos password then
-ldap.edit-mode.tooltip=READ_ONLY is a read only LDAP store. WRITABLE means data will be synced back to LDAP on demand. UNSYNCED means user data will be imported, but not synced back to LDAP.
+ldap.edit-mode.tooltip=READ_ONLY is a read only LDAP store. WRITABLE means data will be synced back to LDAP on demand. UNSYNCED means user data will be imported, but not synced back to LDAP.
update-profile-first-login=Update Profile First Login
update-profile-first-login.tooltip=Update profile on first login
sync-registrations=Sync Registrations
-ldap.sync-registrations.tooltip=Should newly created users be created within LDAP store? Priority effects which provider is chose to sync the new user.
+ldap.sync-registrations.tooltip=Should newly created users be created within LDAP store? Priority effects which provider is chose to sync the new user.
vendor=Vendor
ldap.vendor.tooltip=LDAP vendor (provider)
username-ldap-attribute=Username LDAP attribute
@@ -706,7 +710,7 @@ authenticator.alias.tooltip=Name of the configuration
otp-type=OTP Type
time-based=Time Based
counter-based=Counter Based
-otp-type.tooltip=totp is Time-Based One Time Password. 'hotp' is a counter base one time password in which the server keeps a counter to hash against.
+otp-type.tooltip=totp is Time-Based One Time Password. 'hotp' is a counter base one time password in which the server keeps a counter to hash against.
otp-hash-algorithm=OTP Hash Algorithm
otp-hash-algorithm.tooltip=What hashing algorithm should be used to generate the OTP.
number-of-digits=Number of Digits
@@ -838,10 +842,10 @@ credentials.temporary.tooltip=If enabled user is required to change password on
remove-totp=Remove TOTP
credentials.remove-totp.tooltip=Remove one time password generator for user.
reset-actions=Reset Actions
-credentials.reset-actions.tooltip=Set of actions to execute when sending the user a Reset Actions Email. 'Verify email' sends an email to the user to verify their email address. 'Update profile' requires user to enter in new personal information. 'Update password' requires user to enter in a new password. 'Configure TOTP' requires setup of a mobile password generator.
+credentials.reset-actions.tooltip=Set of actions to execute when sending the user a Reset Actions Email. 'Verify email' sends an email to the user to verify their email address. 'Update profile' requires user to enter in new personal information. 'Update password' requires user to enter in a new password. 'Configure TOTP' requires setup of a mobile password generator.
reset-actions-email=Reset Actions Email
send-email=Send email
-credentials.reset-actions-email.tooltip=Sends an email to user with an embedded link. Clicking on link will allow the user to execute the reset actions. They will not have to login prior to this. For example, set the action to update password, click this button, and the user will be able to change their password without logging in.
+credentials.reset-actions-email.tooltip=Sends an email to user with an embedded link. Clicking on link will allow the user to execute the reset actions. They will not have to login prior to this. For example, set the action to update password, click this button, and the user will be able to change their password without logging in.
add-user=Add user
created-at=Created At
user-enabled=User Enabled
@@ -853,20 +857,20 @@ federation-link=Federation Link
email-verified=Email Verified
email-verified.tooltip=Has the user's email been verified?
required-user-actions=Required User Actions
-required-user-actions.tooltip=Require an action when the user logs in. 'Verify email' sends an email to the user to verify their email address. 'Update profile' requires user to enter in new personal information. 'Update password' requires user to enter in a new password. 'Configure TOTP' requires setup of a mobile password generator.
+required-user-actions.tooltip=Require an action when the user logs in. 'Verify email' sends an email to the user to verify their email address. 'Update profile' requires user to enter in new personal information. 'Update password' requires user to enter in a new password. 'Configure TOTP' requires setup of a mobile password generator.
locale=Locale
select-one.placeholder=Select one...
impersonate=Impersonate
impersonate-user=Impersonate user
-impersonate-user.tooltip=Login as this user. If user is in same realm as you, your current login session will be logged out before you are logged in as this user.
+impersonate-user.tooltip=Login as this user. If user is in same realm as you, your current login session will be logged out before you are logged in as this user.
identity-provider-alias=Identity Provider Alias
provider-user-id=Provider User ID
provider-username=Provider Username
no-identity-provider-links-available=No identity provider links available
group-membership=Group Membership
leave=Leave
-group-membership.tooltip=Groups user is a member of. Select a listed group and click the Leave button to leave the group.
-membership.available-groups.tooltip=Groups a user can join. Select a group and click the join button.
+group-membership.tooltip=Groups user is a member of. Select a listed group and click the Leave button to leave the group.
+membership.available-groups.tooltip=Groups a user can join. Select a group and click the join button.
table-of-realm-users=Table of Realm Users
view-all-users=View all users
unlock-users=Unlock users
@@ -907,4 +911,4 @@ clear-events=Clear events
saved-types=Saved Types
clear-admin-events=Clear admin events
clear-changes=Clear changes
-
+error=Error
diff --git a/themes/src/main/resources/theme/base/admin/messages/messages_en.properties b/themes/src/main/resources/theme/base/admin/messages/messages_en.properties
index ab297dd..95e16db 100644
--- a/themes/src/main/resources/theme/base/admin/messages/messages_en.properties
+++ b/themes/src/main/resources/theme/base/admin/messages/messages_en.properties
@@ -5,4 +5,10 @@ invalidPasswordMinUpperCaseCharsMessage=Invalid password: must contain at least
invalidPasswordMinSpecialCharsMessage=Invalid password: must contain at least {0} special characters.
invalidPasswordNotUsernameMessage=Invalid password: must not be equal to the username.
invalidPasswordRegexPatternMessage=Invalid password: fails to match regex pattern(s).
-invalidPasswordHistoryMessage=Invalid password: must not be equal to any of last {0} passwords.
\ No newline at end of file
+invalidPasswordHistoryMessage=Invalid password: must not be equal to any of last {0} passwords.
+
+ldapErrorInvalidCustomFilter=Custom configured LDAP filter does not start with "(" or does not end with ")".
+ldapErrorMissingClientId=Client ID needs to be provided in config when Realm Roles Mapping is not used.
+ldapErrorCantPreserveGroupInheritanceWithUIDMembershipType=Not possible to preserve group inheritance and use UID membership type together.
+ldapErrorCantWriteOnlyForReadOnlyLdap=Can't set write only when LDAP provider mode is not WRITABLE
+ldapErrorCantWriteOnlyAndReadOnly=Can't set write-only and read-only together
diff --git a/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js b/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js
index 150f57f..3112a27 100755
--- a/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js
+++ b/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js
@@ -1171,17 +1171,8 @@ module.controller('CreateClientCtrl', function($scope, realm, client, templates,
$scope.save = function() {
-
$scope.client.protocol = $scope.protocol;
- if ($scope.client.protocol == 'openid-connect' && !$scope.client.rootUrl) {
- Notifications.error("You must specify the root URL of application");
- }
-
- if ($scope.client.protocol == 'saml' && !$scope.client.adminUrl) {
- Notifications.error("You must specify the SAML Endpoint URL");
- }
-
Client.save({
realm: realm.realm,
client: ''
@@ -1599,8 +1590,8 @@ module.controller('ClientProtocolMapperListCtrl', function($scope, realm, client
});
module.controller('ClientProtocolMapperCtrl', function($scope, realm, serverInfo, client, mapper, ClientProtocolMapper, Notifications, Dialog, $location) {
- /*
$scope.realm = realm;
+ /*
$scope.client = client;
$scope.create = false;
$scope.protocol = client.protocol;
@@ -1674,12 +1665,13 @@ module.controller('ClientProtocolMapperCtrl', function($scope, realm, serverInfo
});
module.controller('ClientProtocolMapperCreateCtrl', function($scope, realm, serverInfo, client, ClientProtocolMapper, Notifications, Dialog, $location) {
+ $scope.realm = realm;
+
if (client.protocol == null) {
client.protocol = 'openid-connect';
}
var protocol = client.protocol;
/*
- $scope.realm = realm;
$scope.client = client;
$scope.create = true;
$scope.protocol = protocol;
@@ -1738,7 +1730,7 @@ module.controller('ClientTemplateTabCtrl', function(Dialog, $scope, Current, Not
-module.controller('ClientTemplateListCtrl', function($scope, realm, templates, ClientTemplate, serverInfo, $route, Dialog, Notifications) {
+module.controller('ClientTemplateListCtrl', function($scope, realm, templates, ClientTemplate, serverInfo, $route, Dialog, Notifications, $location) {
$scope.realm = realm;
$scope.templates = templates;
diff --git a/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js b/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js
index c93e8c6..94b550d 100755
--- a/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js
+++ b/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js
@@ -990,8 +990,21 @@ module.controller('RealmTokenDetailCtrl', function($scope, Realm, realm, $http,
};
});
-module.controller('RealmKeysDetailCtrl', function($scope, Realm, realm, $http, $location, Dialog, Notifications) {
- $scope.realm = realm;
+module.controller('RealmKeysDetailCtrl', function($scope, Realm, realm, $http, $route, $location, Dialog, Notifications) {
+ $scope.realm = angular.copy(realm);
+ $scope.enableUpload = false;
+
+ $scope.$watch('realm', function () {
+ if (!angular.equals($scope.realm, realm)) {
+ if ($scope.realm.privateKey && $scope.realm.publicKey != realm.publicKey) {
+ $scope.enableUpload = true;
+ } else if ($scope.realm.certificate != realm.certificate) {
+ $scope.enableUpload = true;
+ } else {
+ $scope.enableUpload = false;
+ }
+ }
+ }, true);
$scope.generate = function() {
Dialog.confirmGenerateKeys($scope.realm.realm, 'realm', function() {
@@ -1003,6 +1016,34 @@ module.controller('RealmKeysDetailCtrl', function($scope, Realm, realm, $http, $
});
});
};
+
+ $scope.cancel = function() {
+ $route.reload();
+ }
+
+ $scope.save = function() {
+ var title = 'Upload keys for realm';
+ var msg = 'Are you sure you want to upload keys for ' + $scope.realm.realm + '?';
+ var btns = {
+ ok: {
+ label: 'Upload Keys',
+ cssClass: 'btn btn-danger'
+ },
+ cancel: {
+ label: 'Cancel',
+ cssClass: 'btn btn-default'
+ }
+ };
+
+ Dialog.open(title, msg, btns, function() {
+ Realm.update($scope.realm, function () {
+ Notifications.success('Keys uploaded for realm.');
+ Realm.get({ id : realm.realm }, function(updated) {
+ $scope.realm = updated;
+ })
+ });
+ });
+ };
});
module.controller('RealmSessionStatsCtrl', function($scope, realm, stats, RealmClientSessionStats, RealmLogoutAll, Notifications) {
diff --git a/themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js b/themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js
index 0fc333a..4ca5bf8 100755
--- a/themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js
+++ b/themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js
@@ -506,8 +506,8 @@ module.controller('UserCredentialsCtrl', function($scope, realm, user, RequiredA
$scope.password = null;
$scope.confirmPassword = null;
}, function(response) {
- if (response.data && response.data.errorMessage) {
- Notifications.error(response.data.errorMessage);
+ if (response.data && response.data['error_description']) {
+ Notifications.error(response.data['error_description']);
} else {
Notifications.error("Failed to reset user password");
}
@@ -705,6 +705,10 @@ module.controller('GenericUserFederationCtrl', function($scope, $location, Notif
$location.url("/realms/" + realm.realm + "/user-federation/providers/" + $scope.instance.providerName + "/" + id);
Notifications.success("The provider has been created.");
+ }, function (errorResponse) {
+ if (errorResponse.data && errorResponse.data['error_description']) {
+ Notifications.error(errorResponse.data['error_description']);
+ }
});
} else {
UserFederationInstances.update({realm: realm.realm,
@@ -713,6 +717,10 @@ module.controller('GenericUserFederationCtrl', function($scope, $location, Notif
$scope.instance, function () {
$route.reload();
Notifications.success("The provider has been updated.");
+ }, function (errorResponse) {
+ if (errorResponse.data && errorResponse.data['error_description']) {
+ Notifications.error(errorResponse.data['error_description']);
+ }
});
}
};
@@ -909,6 +917,10 @@ module.controller('LDAPCtrl', function($scope, $location, $route, Notifications,
$location.url("/realms/" + realm.realm + "/user-federation/providers/" + $scope.instance.providerName + "/" + id);
Notifications.success("The provider has been created.");
+ }, function (errorResponse) {
+ if (errorResponse.data && errorResponse.data['error_description']) {
+ Notifications.error(errorResponse.data['error_description']);
+ }
});
} else {
UserFederationInstances.update({realm: realm.realm,
@@ -917,8 +929,11 @@ module.controller('LDAPCtrl', function($scope, $location, $route, Notifications,
$scope.instance, function () {
$route.reload();
Notifications.success("The provider has been updated.");
+ }, function (errorResponse) {
+ if (errorResponse.data && errorResponse.data['error_description']) {
+ Notifications.error(errorResponse.data['error_description']);
+ }
});
-
}
};
@@ -1041,7 +1056,7 @@ module.controller('UserFederationMapperCtrl', function($scope, realm, provider,
Notifications.success("Your changes have been saved.");
}, function(error) {
if (error.status == 400 && error.data.error_description) {
- Notifications.error('Error in configuration of mapper: ' + error.data.error_description);
+ Notifications.error(error.data.error_description);
} else {
Notifications.error('Unexpected error when creating mapper');
}
@@ -1113,7 +1128,7 @@ module.controller('UserFederationMapperCreateCtrl', function($scope, realm, prov
Notifications.success("Mapper has been created.");
}, function(error) {
if (error.status == 400 && error.data.error_description) {
- Notifications.error('Error in configuration of mapper: ' + error.data.error_description);
+ Notifications.error(error.data.error_description);
} else {
Notifications.error('Unexpected error when creating mapper');
}
diff --git a/themes/src/main/resources/theme/base/admin/resources/js/services.js b/themes/src/main/resources/theme/base/admin/resources/js/services.js
index 3c9d645..868ec4d 100755
--- a/themes/src/main/resources/theme/base/admin/resources/js/services.js
+++ b/themes/src/main/resources/theme/base/admin/resources/js/services.js
@@ -952,19 +952,6 @@ module.factory('ClientOfflineSessions', function($resource) {
});
});
-module.factory('ClientLogoutAll', function($resource) {
- return $resource(authUrl + '/admin/realms/:realm/clients/:client/logout-all', {
- realm : '@realm',
- client : "@client"
- });
-});
-module.factory('ClientLogoutUser', function($resource) {
- return $resource(authUrl + '/admin/realms/:realm/clients/:client/logout-user/:user', {
- realm : '@realm',
- client : "@client",
- user : "@user"
- });
-});
module.factory('RealmLogoutAll', function($resource) {
return $resource(authUrl + '/admin/realms/:realm/logout-all', {
realm : '@realm'
@@ -1315,10 +1302,9 @@ module.factory('PasswordPolicy', function() {
if (!policies || policies.length == 0) {
return "";
}
-
var policyString = "";
- for (var i in policies){
+ for (var i = 0; i < policies.length; i++) {
policyString += policies[i].name;
if ( policies[i].value ){
policyString += '(' + policies[i].value + ')';
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-clustering.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-clustering.html
index 085562d..3b6c48c 100644
--- a/themes/src/main/resources/theme/base/admin/resources/partials/client-clustering.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-clustering.html
@@ -61,12 +61,8 @@
<tr ng-repeat="node in nodeRegistrations">
<td><a href="#/realms/{{realm.realm}}/clients/{{client.id}}/clustering/{{node.host}}">{{node.host}}</a></td>
<td>{{node.lastRegistration}}</td>
- <td class="kc-action-cell">
- <button class="btn btn-default btn-block btn-sm" kc-open="/realms/{{realm.realm}}/clients/{{client.id}}/clustering/{{node.host}}">{{:: 'edit' | translate}}</button>
- </td>
- <td class="kc-action-cell">
- <button class="btn btn-default btn-block btn-sm" data-ng-click="removeNode(node)">{{:: 'delete' | translate}}</button>
- </td>
+ <td class="kc-action-cell" kc-open="/realms/{{realm.realm}}/clients/{{client.id}}/clustering/{{node.host}}">{{:: 'edit' | translate}}</td>
+ <td class="kc-action-cell" data-ng-click="removeNode(node)">{{:: 'delete' | translate}}</td>
</tr>
<tr data-ng-show="!nodeRegistrations || nodeRegistrations.length == 0">
<td class="text-muted">{{:: 'no-registered-cluster-nodes' | translate}}</td>
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html
index 77810f5..bbbb843 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html
@@ -220,14 +220,14 @@
<div class="input-group" ng-repeat="(i, redirectUri) in client.redirectUris track by $index">
<input class="form-control" ng-model="client.redirectUris[i]">
<div class="input-group-addon">
- <i class="pficon pficon-remove" style="width: 10px;" data-ng-click="deleteRedirectUri($index)"></i>
+ <i class="fa fa-minus" style="width: 10px;" data-ng-click="deleteRedirectUri($index)"></i>
</div>
</div>
<div class="input-group">
<input class="form-control" ng-model="newRedirectUri" id="newRedirectUri">
<div class="input-group-addon">
- <i class="pficon pficon-add" style="width: 10px;" data-ng-click="newRedirectUri.length > 0 && addRedirectUri()"></i>
+ <i class="fa fa-plus" style="width: 10px;" data-ng-click="newRedirectUri.length > 0 && addRedirectUri()"></i>
</div>
</div>
</div>
@@ -279,14 +279,14 @@
<div class="input-group" ng-repeat="(i, webOrigin) in client.webOrigins track by $index">
<input class="form-control" ng-model="client.webOrigins[i]">
<div class="input-group-addon">
- <i class="pficon pficon-remove" style="width: 10px;" data-ng-click="deleteWebOrigin($index)"></i>
+ <i class="fa fa-minus" style="width: 10px;" data-ng-click="deleteWebOrigin($index)"></i>
</div>
</div>
<div class="input-group">
<input class="form-control" ng-model="newWebOrigin" id="newWebOrigin">
<div class="input-group-addon">
- <i class="pficon pficon-add" style="width: 10px;" data-ng-click="newWebOrigin.length > 0 && addWebOrigin()"></i>
+ <i class="fa fa-plus" style="width: 10px;" data-ng-click="newWebOrigin.length > 0 && addWebOrigin()"></i>
</div>
</div>
</div>
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-initial-access.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-initial-access.html
index 7b7c90e..97c1fbb 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/client-initial-access.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-initial-access.html
@@ -37,9 +37,7 @@
<td><span data-ng-show="ia.expiration > 0">{{((ia.timestamp + ia.expiration) * 1000)|date:'shortDate'}} {{((ia.timestamp + ia.expiration) * 1000)|date:'mediumTime'}}</span></td>
<td>{{ia.count}}</td>
<td>{{ia.remainingCount}}</td>
- <td class="kc-action-cell">
- <button class="btn btn-default btn-block btn-sm" data-ng-click="remove(ia.id)">{{:: 'delete' | translate}}</button>
- </td>
+ <td class="kc-action-cell" data-ng-click="remove(ia.id)">{{:: 'delete' | translate}}</td>
</tr>
<tr data-ng-show="(clients | filter:search).length == 0">
<td class="text-muted" colspan="3" data-ng-show="search.clientId">{{:: 'no-results' | translate}}</td>
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-list.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-list.html
index 9bc8c95..9347274 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/client-list.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-list.html
@@ -40,15 +40,9 @@
<a href="{{client.rootUrl}}{{client.baseUrl}}" target="_blank" data-ng-show="client.baseUrl">{{client.rootUrl}}{{client.baseUrl}}</a>
<span data-ng-hide="client.baseUrl">{{:: 'not-defined' | translate}}</span>
</td>
- <td class="kc-action-cell">
- <button class="btn btn-default btn-block btn-sm" kc-open="/realms/{{realm.realm}}/clients/{{client.id}}">{{:: 'edit' | translate}}</button>
- </td>
- <td class="kc-action-cell">
- <button class="btn btn-default btn-block btn-sm" data-ng-click="exportClient(client)">{{:: 'export' | translate}}</button>
- </td>
- <td class="kc-action-cell">
- <button class="btn btn-default btn-block btn-sm" data-ng-click="removeClient(client)">{{:: 'delete' | translate}}</button>
- </td>
+ <td class="kc-action-cell" kc-open="/realms/{{realm.realm}}/clients/{{client.id}}">{{:: 'edit' | translate}}</td>
+ <td class="kc-action-cell" data-ng-click="exportClient(client)">{{:: 'export' | translate}}</td>
+ <td class="kc-action-cell" data-ng-click="removeClient(client)">{{:: 'delete' | translate}}</td>
</tr>
<tr data-ng-show="(clients | filter:search).length == 0">
<td class="text-muted" colspan="3" data-ng-show="search.clientId">{{:: 'no-results' | translate}}</td>
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-mappers.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-mappers.html
index 86b52e1..c07e732 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/client-mappers.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-mappers.html
@@ -54,12 +54,8 @@
<td><a href="#/realms/{{realm.realm}}/clients/{{client.id}}/mappers/{{mapper.id}}">{{mapper.name}}</a></td>
<td>{{mapperTypes[mapper.protocolMapper].category}}</td>
<td>{{mapperTypes[mapper.protocolMapper].name}}</td>
- <td class="kc-action-cell">
- <button class="btn btn-default btn-block btn-sm" kc-open="/realms/{{realm.realm}}/clients/{{client.id}}/mappers/{{mapper.id}}">{{:: 'edit' | translate}}</button>
- </td>
- <td class="kc-action-cell">
- <button class="btn btn-default btn-block btn-sm" data-ng-click="removeMapper(mapper)">{{:: 'delete' | translate}}</button>
- </td>
+ <td class="kc-action-cell" kc-open="/realms/{{realm.realm}}/clients/{{client.id}}/mappers/{{mapper.id}}">{{:: 'edit' | translate}}</td>
+ <td class="kc-action-cell" data-ng-click="removeMapper(mapper)">{{:: 'delete' | translate}}</td>
</tr>
<tr data-ng-show="mappers.length == 0">
<td>{{:: 'no-mappers-available' | translate}}</td>
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-role-list.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-role-list.html
index 39be3b2..52154d7 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/client-role-list.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-role-list.html
@@ -28,12 +28,8 @@
<td><a href="#/realms/{{realm.realm}}/clients/{{client.id}}/roles/{{role.id}}">{{role.name}}</a></td>
<td translate="{{role.composite}}"></td>
<td>{{role.description}}</td>
- <td class="kc-action-cell">
- <button class="btn btn-default btn-block btn-sm" kc-open="/realms/{{realm.realm}}/clients/{{client.id}}/roles/{{role.id}}">{{:: 'edit' | translate}}</button>
- </td>
- <td class="kc-action-cell">
- <button class="btn btn-default btn-block btn-sm" data-ng-click="removeRole(role)">{{:: 'delete' | translate}}</button>
- </td>
+ <td class="kc-action-cell" kc-open="/realms/{{realm.realm}}/clients/{{client.id}}/roles/{{role.id}}">{{:: 'edit' | translate}}</td>
+ <td class="kc-action-cell" data-ng-click="removeRole(role)">{{:: 'delete' | translate}}</td>
</tr>
<tr data-ng-show="!roles || roles.length == 0">
<td>{{:: 'no-client-roles-available' | translate}}</td>
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-template-list.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-template-list.html
index a16f533..94df5f6 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/client-template-list.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-template-list.html
@@ -26,18 +26,14 @@
</tr>
<tr data-ng-hide="clients.length == 0">
<th>{{:: 'name' | translate}}</th>
- <th colspan="2">{{:: 'actions' | translate}}</th>
+ <th colspan="2" class="w-25">{{:: 'actions' | translate}}</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="template in templates | filter:search | orderBy:'name'">
<td><a href="#/realms/{{realm.realm}}/client-templates/{{template.id}}">{{template.name}}</a></td>
- <td class="kc-action-cell">
- <button class="btn btn-default btn-block btn-sm" kc-open="/realms/{{realm.realm}}/client-templates/{{template.id}}">{{:: 'edit' | translate}}</button>
- </td>
- <td class="kc-action-cell">
- <button class="btn btn-default btn-block btn-sm" data-ng-click="removeClientTemplate(template)">{{:: 'delete' | translate}}</button>
- </td>
+ <td class="kc-action-cell" kc-open="/realms/{{realm.realm}}/client-templates/{{template.id}}">{{:: 'edit' | translate}}</td>
+ <td class="kc-action-cell" data-ng-click="removeClientTemplate(template)">{{:: 'delete' | translate}}</td>
</tr>
<tr data-ng-show="(clients | filter:search).length == 0">
<td class="text-muted" colspan="3" data-ng-show="search.name">{{:: 'no-results' | translate}}</td>
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-template-mappers.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-template-mappers.html
index 66c29e3..3b6fa9e 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/client-template-mappers.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-template-mappers.html
@@ -40,12 +40,8 @@
<td><a href="#/realms/{{realm.realm}}/client-templates/{{template.id}}/mappers/{{mapper.id}}">{{mapper.name}}</a></td>
<td>{{mapperTypes[mapper.protocolMapper].category}}</td>
<td>{{mapperTypes[mapper.protocolMapper].name}}</td>
- <td class="kc-action-cell">
- <button class="btn btn-default btn-block btn-sm" kc-open="/realms/{{realm.realm}}/client-templates/{{template.id}}/mappers/{{mapper.id}}">{{:: 'edit' | translate}}</button>
- </td>
- <td class="kc-action-cell">
- <button class="btn btn-default btn-block btn-sm" data-ng-click="removeMapper(mapper)">{{:: 'delete' | translate}}</button>
- </td>
+ <td class="kc-action-cell" kc-open="/realms/{{realm.realm}}/client-templates/{{template.id}}/mappers/{{mapper.id}}">{{:: 'edit' | translate}}</td>
+ <td class="kc-action-cell" data-ng-click="removeMapper(mapper)">{{:: 'delete' | translate}}</td>
</tr>
<tr data-ng-show="mappers.length == 0">
<td>{{:: 'no-mappers-available' | translate}}</td>
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/create-client.html b/themes/src/main/resources/theme/base/admin/resources/partials/create-client.html
index 757f077..98609c8 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/create-client.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/create-client.html
@@ -18,8 +18,8 @@
</div>
<div class="col-md-6" data-ng-show="importing">
- <button class="btn btn-default" data-ng-click="viewImportDetails()">{{:: 'view-details' | translate}}</button>
- <button class="btn btn-default" data-ng-click="reset()">{{:: 'clear-import' | translate}}</button>
+ <button class="btn btn-default" type="button" data-ng-click="viewImportDetails()">{{:: 'view-details' | translate}}</button>
+ <button class="btn btn-default" type="button" data-ng-click="reset()">{{:: 'clear-import' | translate}}</button>
</div>
</div>
@@ -58,14 +58,14 @@
<kc-tooltip>{{:: 'client-template.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group" data-ng-hide="protocol == 'saml'">
- <label class="col-md-2 control-label" for="rootUrl">{{:: 'root-url' | translate}} <span class="required">*</span></label>
+ <label class="col-md-2 control-label" for="rootUrl">{{:: 'root-url' | translate}}</label>
<div class="col-sm-6">
<input class="form-control" type="text" name="rootUrl" id="rootUrl" data-ng-model="client.rootUrl">
</div>
<kc-tooltip>{{:: 'root-url.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group" data-ng-show="protocol == 'saml'">
- <label class="col-md-2 control-label" for="masterSamlUrl">{{:: 'client-saml-endpoint' | translate}} <span class="required">*</span></label>
+ <label class="col-md-2 control-label" for="masterSamlUrl">{{:: 'client-saml-endpoint' | translate}}</label>
<div class="col-sm-6">
<input class="form-control" type="text" name="masterSamlUrl" id="masterSamlUrl"
data-ng-model="client.adminUrl">
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/defense-headers.html b/themes/src/main/resources/theme/base/admin/resources/partials/defense-headers.html
index aa1dc4e..1dc08a1 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/defense-headers.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/defense-headers.html
@@ -9,18 +9,25 @@
<form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageRealm">
<fieldset class="border-top">
<div class="form-group">
- <label class="col-md-2 control-label" for="xFrameOptions"><a href="http://tools.ietf.org/html/rfc7034">{{:: 'x-frame-options' | translate}}</a></label>
+ <label class="col-md-2 control-label" for="xFrameOptions"><a href="http://tools.ietf.org/html/rfc7034" target="_blank">{{:: 'x-frame-options' | translate}}</a></label>
<div class="col-sm-6">
<input class="form-control" id="xFrameOptions" type="text" ng-model="realm.browserSecurityHeaders.xFrameOptions">
</div>
- <kc-tooltip>{{:: 'click-label-for-info' | translate}}</kc-tooltip>
+ <kc-tooltip>{{:: 'x-frame-options-tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group">
- <label class="col-md-2 control-label" for="contentSecurityPolicy"><a href="http://www.w3.org/TR/CSP/">{{:: 'content-sec-policy' | translate}}</a></label>
+ <label class="col-md-2 control-label" for="contentSecurityPolicy"><a href="http://www.w3.org/TR/CSP/" target="_blank">{{:: 'content-sec-policy' | translate}}</a></label>
<div class="col-sm-6">
<input class="form-control" id="contentSecurityPolicy" type="text" ng-model="realm.browserSecurityHeaders.contentSecurityPolicy">
</div>
- <kc-tooltip>{{:: 'click-label-for-info' | translate}}</kc-tooltip>
+ <kc-tooltip>{{:: 'content-sec-policy-tooltip' | translate}}</kc-tooltip>
+ </div>
+ <div class="form-group">
+ <label class="col-md-2 control-label" for="xContentTypeOptions"><a href="https://www.owasp.org/index.php/List_of_useful_HTTP_headers">{{:: 'content-type-options' | translate}}</a></label>
+ <div class="col-sm-6">
+ <input class="form-control" id="xContentTypeOptions" type="text" ng-model="realm.browserSecurityHeaders.xContentTypeOptions">
+ </div>
+ <kc-tooltip>{{:: 'content-type-options-tooltip' | translate}}</kc-tooltip>
</div>
</fieldset>
<div class="form-group" data-ng-show="access.manageRealm">
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/group-attributes.html b/themes/src/main/resources/theme/base/admin/resources/partials/group-attributes.html
index c074dc4..c3962c5 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/group-attributes.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/group-attributes.html
@@ -19,16 +19,12 @@
<tr ng-repeat="(key, value) in group.attributes">
<td>{{key}}</td>
<td><input ng-model="group.attributes[key]" class="form-control" type="text" name="{{key}}" id="attribute-{{key}}" /></td>
- <td class="kc-action-cell">
- <button type="button" class="btn btn-default btn-block btn-sm" data-ng-click="removeAttribute(key)">{{:: 'delete' | translate}}</button>
- </td>
+ <td class="kc-action-cell" data-ng-click="removeAttribute(key)">{{:: 'delete' | translate}}</td>
</tr>
<tr>
<td><input ng-model="newAttribute.key" class="form-control" type="text" id="newAttributeKey" /></td>
<td><input ng-model="newAttribute.value" class="form-control" type="text" id="newAttributeValue" /></td>
- <td class="kc-action-cell">
- <button class="btn btn-default btn-block btn-sm" data-ng-click="addAttribute()" data-ng-disabled="!newAttribute.key.length || !newAttribute.value.length">{{:: 'add' | translate}}</button>
- </td>
+ <td class="kc-action-cell" data-ng-click="addAttribute()" data-ng-disabled="!newAttribute.key.length || !newAttribute.value.length">{{:: 'add' | translate}}</td>
</tr>
</tbody>
</table>
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/group-members.html b/themes/src/main/resources/theme/base/admin/resources/partials/group-members.html
index cb0d165..ffdf362 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/group-members.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/group-members.html
@@ -35,9 +35,7 @@
<td>{{user.lastName}}</td>
<td>{{user.firstName}}</td>
<td>{{user.email}}</td>
- <td class="kc-action-cell">
- <button class="btn btn-default btn-block btn-sm" kc-open="/realms/{{realm.realm}}/users/{{user.id}}">{{:: 'edit' | translate}}</button>
- </td>
+ <td class="kc-action-cell" kc-open="/realms/{{realm.realm}}/users/{{user.id}}">{{:: 'edit' | translate}}</td>
</tr>
<tr data-ng-show="!users || users.length == 0">
<td class="text-muted" data-ng-show="searchLoaded && users.length == 0 && lastSearch != null">{{:: 'no-group-members' | translate}}</td>
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/password-policy.html b/themes/src/main/resources/theme/base/admin/resources/partials/password-policy.html
index 81d8f1d..926ee80 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/password-policy.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/password-policy.html
@@ -33,9 +33,7 @@
<input class="form-control" ng-model="p.value" ng-show="p.name != 'notUsername' "
placeholder="{{:: 'no-value-assigned.placeholder' | translate}}" min="1" required>
</td>
- <td class="kc-action-cell">
- <button type="button" class="btn btn-default btn-block btn-sm" ng-click="removePolicy($index)">{{:: 'delete' | translate}}</button>
- </td>
+ <td class="kc-action-cell" ng-click="removePolicy($index)">{{:: 'delete' | translate}}</td>
</tr>
</tbody>
</table>
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider.html b/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider.html
index 37256af..71a8c5a 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider.html
@@ -34,12 +34,8 @@
<td>{{identityProvider.providerId}}</td>
<td translate="{{identityProvider.enabled}}"></td>
<td>{{identityProvider.config.guiOrder}}</td>
- <td class="kc-action-cell">
- <button class="btn btn-default btn-block btn-sm" kc-open="/realms/{{realm.realm}}/identity-provider-settings/provider/{{identityProvider.providerId}}/{{identityProvider.alias}}">{{:: 'edit' | translate}}</button>
- </td>
- <td class="kc-action-cell">
- <button class="btn btn-default btn-block btn-sm" data-ng-click="removeIdentityProvider(identityProvider)">{{:: 'delete' | translate}}</button>
- </td>
+ <td class="kc-action-cell" kc-open="/realms/{{realm.realm}}/identity-provider-settings/provider/{{identityProvider.providerId}}/{{identityProvider.alias}}">{{:: 'edit' | translate}}</td>
+ <td class="kc-action-cell" data-ng-click="removeIdentityProvider(identityProvider)">{{:: 'delete' | translate}}</td>
</tr>
</tbody>
</table>
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/realm-keys.html b/themes/src/main/resources/theme/base/admin/resources/partials/realm-keys.html
index 93301af..b8a16c9 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/realm-keys.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/realm-keys.html
@@ -4,25 +4,38 @@
<form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageRealm">
<fieldset class="border-top">
<div class="form-group">
+ <label class="col-md-2 control-label" for="privateKey">{{:: 'privateKey' | translate}}</label>
+
+ <div class="col-md-10">
+ <textarea type="password" id="privateKey" name="privateKey" class="form-control" rows="{{!realm.privateKey ? 1 : 8}}" data-ng-model="realm.privateKey"></textarea>
+ </div>
+ </div>
+ <div class="form-group">
<label class="col-md-2 control-label" for="publicKey">{{:: 'publicKey' | translate}}</label>
<div class="col-md-10">
<textarea type="text" id="publicKey" name="publicKey" class="form-control" rows="4"
- kc-select-action="click" readonly>{{realm.publicKey}}</textarea>
+ kc-select-action="click" data-ng-model="realm.publicKey"></textarea>
</div>
</div>
<div class="form-group">
<label class="col-md-2 control-label" for="certificate">{{:: 'certificate' | translate}}</label>
<div class="col-md-10">
- <textarea type="text" id="certificate" name="certificate" class="form-control" rows="8" kc-select-action="click" readonly>{{realm.certificate}}</textarea>
+ <textarea type="text" id="certificate" name="certificate" class="form-control" rows="8" kc-select-action="click" data-ng-model="realm.certificate"></textarea>
</div>
</div>
</fieldset>
<div class="form-group" data-ng-show="access.manageRealm">
<div class="col-md-10 col-md-offset-2">
- <button class="btn btn-danger" type="submit" data-ng-click="generate()">{{:: 'gen-new-keys' | translate}}</button>
+ <button class="btn btn-danger" type="button" data-ng-click="generate()" data-ng-disabled="enableUpload">{{:: 'gen-new-keys' | translate}}</button>
+ </div>
+ </div>
+ <div class="form-group" data-ng-show="access.manageRealm">
+ <div class="col-md-10 col-md-offset-2">
+ <button class="btn btn-danger" type="button" data-ng-click="save()" data-ng-disabled="!enableUpload">{{:: 'upload-keys' | translate}}</button>
+ <button class="btn btn-default" type="button" data-ng-click="cancel()" data-ng-disabled="!enableUpload">{{:: 'cancel' | translate}}</button>
</div>
</div>
</form>
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/required-actions.html b/themes/src/main/resources/theme/base/admin/resources/partials/required-actions.html
index 24415e3..45a5be6 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/required-actions.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/required-actions.html
@@ -18,7 +18,7 @@
</tr>
</thead>
<tbody>
- <tr ng-repeat="requiredAction in requiredActions" data-ng-show="requiredActions.length > 0">
+ <tr ng-repeat="requiredAction in requiredActions | orderBy : 'name'" data-ng-show="requiredActions.length > 0">
<td>{{requiredAction.name}}</td>
<td><input type="checkbox" ng-model="requiredAction.enabled" ng-change="updateRequiredAction(requiredAction)" id="{{requiredAction.alias}}.enabled"></td>
<td><input type="checkbox" ng-model="requiredAction.defaultAction" ng-change="updateRequiredAction(requiredAction)" id="{{requiredAction.alias}}.defaultAction"></td>
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/role-list.html b/themes/src/main/resources/theme/base/admin/resources/partials/role-list.html
index 62a3c39..8b2bd26 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/role-list.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/role-list.html
@@ -38,12 +38,8 @@
<td><a href="#/realms/{{realm.realm}}/roles/{{role.id}}">{{role.name}}</a></td>
<td translate="{{role.composite}}"></td>
<td>{{role.description}}</td>
- <td class="kc-action-cell">
- <button class="btn btn-default btn-block btn-sm" kc-open="/realms/{{realm.realm}}/roles/{{role.id}}">{{:: 'edit' | translate}}</button>
- </td>
- <td class="kc-action-cell">
- <button class="btn btn-default btn-block btn-sm" data-ng-click="removeRole(role)">{{:: 'delete' | translate}}</button>
- </td>
+ <td class="kc-action-cell" kc-open="/realms/{{realm.realm}}/roles/{{role.id}}">{{:: 'edit' | translate}}</td>
+ <td class="kc-action-cell" data-ng-click="removeRole(role)">{{:: 'delete' | translate}}</td>
</tr>
<tr data-ng-show="(roles | filter:{name: searchQuery}).length == 0">
<td class="text-muted" colspan="3" data-ng-show="searchQuery">{{:: 'no-results' | translate}}</td>
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/user-attributes.html b/themes/src/main/resources/theme/base/admin/resources/partials/user-attributes.html
index 1716db6..26d30e1 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/user-attributes.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/user-attributes.html
@@ -19,16 +19,12 @@
<tr ng-repeat="(key, value) in user.attributes | toOrderedMapSortedByKey">
<td>{{key}}</td>
<td><input ng-model="user.attributes[key]" class="form-control" type="text" name="{{key}}" id="attribute-{{key}}" /></td>
- <td class="kc-action-cell">
- <button type="button" class="btn btn-default btn-block btn-sm" data-ng-click="removeAttribute(key)">{{:: 'delete' | translate}}</button>
- </td>
+ <td class="kc-action-cell" data-ng-click="removeAttribute(key)">{{:: 'delete' | translate}}</td>
</tr>
<tr>
<td><input ng-model="newAttribute.key" class="form-control" type="text" id="newAttributeKey" /></td>
<td><input ng-model="newAttribute.value" class="form-control" type="text" id="newAttributeValue" /></td>
- <td class="kc-action-cell">
- <button class="btn btn-default btn-block btn-sm" data-ng-click="addAttribute()" data-ng-disabled="!newAttribute.key.length || !newAttribute.value.length">{{:: 'add' | translate}}</button>
- </td>
+ <td class="kc-action-cell" data-ng-click="addAttribute()" data-ng-disabled="!newAttribute.key.length || !newAttribute.value.length">{{:: 'add' | translate}}</td>
</tr>
</tbody>
</table>
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/user-consents.html b/themes/src/main/resources/theme/base/admin/resources/partials/user-consents.html
index 766d4aa..63bfa7e 100644
--- a/themes/src/main/resources/theme/base/admin/resources/partials/user-consents.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/user-consents.html
@@ -41,11 +41,7 @@
<span ng-if="!$first">, </span><a href="#/realms/{{realm.realm}}/users/{{user.id}}/offline-sessions/{{additionalGrant.client}}">{{additionalGrant.key}}</a>
</span>
</td>
- <td class="kc-action-cell">
- <button class="btn btn-default btn-block btn-sm" ng-click="revokeConsent(consent.clientId)">
- <i class="pficon pficon-delete"></i> {{:: 'revoke' | translate}}
- </button>
- </td>
+ <td class="kc-action-cell" ng-click="revokeConsent(consent.clientId)">{{:: 'revoke' | translate}}</td>
</tr>
</tbody>
</table>
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/user-federated-identity-list.html b/themes/src/main/resources/theme/base/admin/resources/partials/user-federated-identity-list.html
index 1695ea4..82f96f6 100644
--- a/themes/src/main/resources/theme/base/admin/resources/partials/user-federated-identity-list.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/user-federated-identity-list.html
@@ -29,9 +29,7 @@
<td>{{identity.identityProvider}}</td>
<td>{{identity.userId}}</td>
<td>{{identity.userName}}</td>
- <td class="kc-action-cell">
- <button class="btn btn-default btn-block btn-sm" ng-click="removeProviderLink(identity)">{{:: 'remove' | translate}}</button>
- </td>
+ <td class="kc-action-cell" ng-click="removeProviderLink(identity)">{{:: 'remove' | translate}}</td>
</tr>
<tr data-ng-show="federatedIdentities.length == 0">
<td>{{:: 'no-identity-provider-links-available' | translate}}</td>
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/user-federation.html b/themes/src/main/resources/theme/base/admin/resources/partials/user-federation.html
index 36a65c0..fe8b1be 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/user-federation.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/user-federation.html
@@ -30,12 +30,8 @@
<td><a href="#/realms/{{realm.realm}}/user-federation/providers/{{instance.providerName}}/{{instance.id}}">{{instance.displayName}}</a></td>
<td>{{instance.providerName|capitalize}}</td>
<td>{{instance.priority}}</td>
- <td class="kc-action-cell">
- <button class="btn btn-default btn-block btn-sm" kc-open="/realms/{{realm.realm}}/user-federation/providers/{{instance.providerName}}/{{instance.id}}">{{:: 'edit' | translate}}</button>
- </td>
- <td class="kc-action-cell">
- <button class="btn btn-default btn-block btn-sm" data-ng-click="removeUserFederation(instance)">{{:: 'delete' | translate}}</button>
- </td>
+ <td class="kc-action-cell" kc-open="/realms/{{realm.realm}}/user-federation/providers/{{instance.providerName}}/{{instance.id}}">{{:: 'edit' | translate}}</td>
+ <td class="kc-action-cell" data-ng-click="removeUserFederation(instance)">{{:: 'delete' | translate}}</td>
</tr>
<tr data-ng-show="!instances || instances.length == 0">
<td class="text-muted">{{:: 'no-user-federation-providers-configured' | translate}}</td>
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/user-list.html b/themes/src/main/resources/theme/base/admin/resources/partials/user-list.html
index c57d482..f0cd97e 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/user-list.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/user-list.html
@@ -53,15 +53,9 @@
<td class="clip">{{user.email}}</td>
<td class="clip">{{user.lastName}}</td>
<td class="clip">{{user.firstName}}</td>
- <td class="kc-action-cell">
- <button class="btn btn-default btn-block btn-sm" kc-open="/realms/{{realm.realm}}/users/{{user.id}}">{{:: 'edit' | translate}}</button>
- </td>
- <td data-ng-show="access.impersonation" class="kc-action-cell">
- <button class="btn btn-default btn-block btn-sm" data-ng-click="impersonate(user.id)">{{:: 'impersonate' | translate}}</button>
- </td>
- <td data-ng-show="access.manageUsers" class="kc-action-cell">
- <button class="btn btn-default btn-block btn-sm" data-ng-click="removeUser(user)">{{:: 'delete' | translate}}</button>
- </td>
+ <td class="kc-action-cell" kc-open="/realms/{{realm.realm}}/users/{{user.id}}">{{:: 'edit' | translate}}</td>
+ <td data-ng-show="access.impersonation" class="kc-action-cell" data-ng-click="impersonate(user.id)">{{:: 'impersonate' | translate}}</td>
+ <td data-ng-show="access.manageUsers" class="kc-action-cell" data-ng-click="removeUser(user)">{{:: 'delete' | translate}}</td>
</tr>
<tr data-ng-show="!users || users.length == 0">
<td class="text-muted" data-ng-show="!users">{{:: 'users.instruction' | translate}}</td>
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/user-sessions.html b/themes/src/main/resources/theme/base/admin/resources/partials/user-sessions.html
index 4273dcd..1fe11e9 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/user-sessions.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/user-sessions.html
@@ -34,9 +34,7 @@
</div>
</ul>
</td>
- <td class="kc-action-cell" data-ng-show="access.manageUsers">
- <button class="btn btn-default btn-block btn-sm" ng-click="logoutSession(session.id)">{{:: 'logout' | translate}}</button>
- </td>
+ <td class="kc-action-cell" data-ng-show="access.manageUsers" ng-click="logoutSession(session.id)">{{:: 'logout' | translate}}</td>
</tr>
</tbody>
</table>
diff --git a/themes/src/main/resources/theme/base/email/messages/messages_de.properties b/themes/src/main/resources/theme/base/email/messages/messages_de.properties
index ca5c776..c997261 100755
--- a/themes/src/main/resources/theme/base/email/messages/messages_de.properties
+++ b/themes/src/main/resources/theme/base/email/messages/messages_de.properties
@@ -1,10 +1,5 @@
emailVerificationSubject=E-Mail verifizieren
passwordResetSubject=Passwort zur\u00FCckzusetzen
-passwordResetBody=Someone just requested to change your {2} account''s credentials. If this was you, click on the link below to reset them.\n\n{0}\n\nThis link and code will expire within {1} minutes.\n\nIf you don''t want to reset your credentials, just ignore this message and nothing will be changed.
-passwordResetBodyHtml=<p>Someone just requested to change your {2} account''s credentials. If this was you, click on the link below to reset them.</p><p><a href="{0}">{0}</a></p><p>This link will expire within {1} minutes.</p><p>If you don''t want to reset your credentials, just ignore this message and nothing will be changed.</p>
-executeActionsSubject=Update Your Account
-executeActionsBody=Your adminstrator has just requested that you update your {2} account. Click on the link below to start this process.\n\n{0}\n\nThis link will expire within {1} minutes.\n\nIf you are unaware that your admin has requested this, just ignore this message and nothing will be changed.
-executeActionsBodyHtml=<p>Your adminstrator has just requested that you update your {2} account. Click on the link below to start this process.</p><p><a href="{0}">{0}</a></p><p>This link will expire within {1} minutes.</p><p>If you are unaware that your admin has requested this, just ignore this message and nothing will be changed.</p>
emailVerificationBody=Jemand hat ein {2} Konto mit dieser E-Mail Adresse erstellt. Fall das Sie waren, dann klicken Sie auf den Link um die E-Mail Adresse zu verifizieren.\n\n{0}\n\nDieser Link wird in {1} Minuten ablaufen.\n\nFalls Sie dieses Konto nicht erstellt haben, dann k\u00F6nnen sie diese Nachricht ignorieren.
emailVerificationBodyHtml=<p>Jemand hat ein {2} Konto mit dieser E-Mail Adresse erstellt. Fall das Sie waren, dann klicken Sie auf den Link um die E-Mail Adresse zu verifizieren.</p><p><a href="{0}">{0}</a></p><p>Dieser Link wird in {1} Minuten ablaufen.</p><p>Falls Sie dieses Konto nicht erstellt haben, dann k\u00F6nnen sie diese Nachricht ignorieren.</p>
eventLoginErrorSubject=Fehlgeschlagene Anmeldung
diff --git a/themes/src/main/resources/theme/base/email/messages/messages_fr.properties b/themes/src/main/resources/theme/base/email/messages/messages_fr.properties
index 8c66385..08af3a6 100755
--- a/themes/src/main/resources/theme/base/email/messages/messages_fr.properties
+++ b/themes/src/main/resources/theme/base/email/messages/messages_fr.properties
@@ -1,9 +1,6 @@
emailVerificationSubject=V\u00e9rification du courriel
emailVerificationBody=Quelqu''un vient de cr\u00e9er un compte "{2}" avec votre courriel. Si c''est bien vous, veuillez cliquer sur le lien ci-dessous afin de v\u00e9rifier votre adresse de courriel\n\n{0}\n\nCe lien expire dans {1} minute(s).\n\nSinon ignorer ce message.
emailVerificationBodyHtml=<p>Quelqu''un a cr\u00e9er un compte "{2}" avec votre courriel. Si c''est bien vous, veuillez cliquer sur le lien ci-dessous afin de v\u00e9rifier votre adresse de courriel</p><p><a href="{0}">{0}</a></p><p>Ce lien expire dans {1} minute(s).</p><p>Sinon veuillez ignorer ce message.</p>
-identityProviderLinkSubject=Link {0}
-identityProviderLinkBody=Someone wants to link your "{1}" account with "{0}" account of user {2} . If this was you, click the link below to link accounts\n\n{3}\n\nThis link will expire within {4} minutes.\n\nIf you don''t want to link account, just ignore this message. If you link accounts, you will be able to login to {1} through {0}.
-identityProviderLinkBodyHtml=<p>Someone wants to link your <b>{1}</b> account with <b>{0}</b> account of user {2} . If this was you, click the link below to link accounts</p><p><a href="{3}">{3}</a></p><p>This link will expire within {4} minutes.</p><p>If you don''t want to link account, just ignore this message. If you link accounts, you will be able to login to {1} through {0}.</p>
passwordResetSubject=R\u00e9initialiser le mot de passe
passwordResetBody=Quelqu''un vient de demander une r\u00e9initialisation de mot de passe pour votre compte {2}. Si c''est bien vous, veuillez cliquer sur le lien ci-dessous pour le mettre \u00e0 jour .\n\n{0}\n\nCe lien expire dans {1} minute(s).\n\nSinon ignorer ce message, aucun changement ne sera effectu\u00e9 sur votre compte.
passwordResetBodyHtml=<p>Quelqu''un vient de demander une reinitialisation de mot de passe pour votre compte {2}. Si c''est bien vous, veuillez cliquer sur le lien ci-dessous pour le mettre \u00e0 jour.</p><p><a href="{0}">{0}</a></p><p>Ce lien expire dans {1} minute(s).</p><p>Sinon ignorer ce message, aucun changement ne sera effectu\u00e9 sur votre compte.</p>
diff --git a/themes/src/main/resources/theme/base/email/messages/messages_pt_BR.properties b/themes/src/main/resources/theme/base/email/messages/messages_pt_BR.properties
index 6596fba..cfe9737 100755
--- a/themes/src/main/resources/theme/base/email/messages/messages_pt_BR.properties
+++ b/themes/src/main/resources/theme/base/email/messages/messages_pt_BR.properties
@@ -5,11 +5,6 @@ identityProviderLinkSubject=Vincular {0}
identityProviderLinkBody=Algu\u00E9m quer vincular sua conta "{1}" com a conta "{0}" do usu\u00E1rio {2} . Se foi voc\u00EA, clique no link abaixo para vincular as contas.\n\n{3}\n\nEste link ir\u00E1 expirar em {4} minutos.\n\nSe voc\u00EA n\u00E3o quer vincular a conta, apenas ignore esta mensagem. Se voc\u00EA vincular as contas, voc\u00EA ser\u00E1 capaz de logar em {1} atr\u00E1v\u00E9s de {0}.
identityProviderLinkBodyHtml=<p>Algu\u00E9m quer vincular sua conta <b>{1}</b> com a conta <b>{0}</b> do usu\u00E1rio {2} . Se foi voc\u00EA, clique no link abaixo para vincular as contas.</p><p><a href="{3}">{3}</a></p><p>Este link ir\u00E1 expirar em {4} minutos.</p><p>Se voc\u00EA n\u00E3o quer vincular a conta, apenas ignore esta mensagem. Se voc\u00EA vincular as contas, voc\u00EA ser\u00E1 capaz de logar em {1} atr\u00E1v\u00E9s de {0}.</p>
passwordResetSubject=Redefini\u00E7\u00E3o de senha
-passwordResetBody=Someone just requested to change your {2} account''s credentials. If this was you, click on the link below to reset them.\n\n{0}\n\nThis link and code will expire within {1} minutes.\n\nIf you don''t want to reset your credentials, just ignore this message and nothing will be changed.
-passwordResetBodyHtml=<p>Someone just requested to change your {2} account''s credentials. If this was you, click on the link below to reset them.</p><p><a href="{0}">{0}</a></p><p>This link will expire within {1} minutes.</p><p>If you don''t want to reset your credentials, just ignore this message and nothing will be changed.</p>
-executeActionsSubject=Update Your Account
-executeActionsBody=Your adminstrator has just requested that you update your {2} account. Click on the link below to start this process.\n\n{0}\n\nThis link will expire within {1} minutes.\n\nIf you are unaware that your admin has requested this, just ignore this message and nothing will be changed.
-executeActionsBodyHtml=<p>Your adminstrator has just requested that you update your {2} account. Click on the link below to start this process.</p><p><a href="{0}">{0}</a></p><p>This link will expire within {1} minutes.</p><p>If you are unaware that your admin has requested this, just ignore this message and nothing will be changed.</p>
eventLoginErrorSubject=Erro de login
eventLoginErrorBody=Uma tentativa de login mal sucedida para a sua conta foi detectada em {0} de {1}. Se n\u00E3o foi voc\u00EA, por favor, entre em contato com um administrador.
eventLoginErrorBodyHtml=<p>Uma tentativa de login mal sucedida para a sua conta foi detectada em {0} de {1}. Se n\u00E3o foi voc\u00EA, por favor, entre em contato com um administrador.</p>
diff --git a/themes/src/main/resources/theme/base/login/login.ftl b/themes/src/main/resources/theme/base/login/login.ftl
index 840bf4e..3f70f0c 100755
--- a/themes/src/main/resources/theme/base/login/login.ftl
+++ b/themes/src/main/resources/theme/base/login/login.ftl
@@ -27,7 +27,7 @@
</div>
<div class="${properties.kcInputWrapperClass!}">
- <input id="password" class="${properties.kcInputClass!}" name="password" type="password" />
+ <input id="password" class="${properties.kcInputClass!}" name="password" type="password" autocomplete="off" />
</div>
</div>
diff --git a/themes/src/main/resources/theme/base/login/login-update-password.ftl b/themes/src/main/resources/theme/base/login/login-update-password.ftl
index bad990c..bf0594d 100755
--- a/themes/src/main/resources/theme/base/login/login-update-password.ftl
+++ b/themes/src/main/resources/theme/base/login/login-update-password.ftl
@@ -11,7 +11,7 @@
<label for="password-new" class="${properties.kcLabelClass!}">${msg("passwordNew")}</label>
</div>
<div class="${properties.kcInputWrapperClass!}">
- <input type="password" id="password-new" name="password-new" class="${properties.kcInputClass!}" autofocus />
+ <input type="password" id="password-new" name="password-new" class="${properties.kcInputClass!}" autofocus autocomplete="off" />
</div>
</div>
@@ -20,7 +20,7 @@
<label for="password-confirm" class="${properties.kcLabelClass!}">${msg("passwordConfirm")}</label>
</div>
<div class="${properties.kcInputWrapperClass!}">
- <input type="password" id="password-confirm" name="password-confirm" class="${properties.kcInputClass!}" />
+ <input type="password" id="password-confirm" name="password-confirm" class="${properties.kcInputClass!}" autocomplete="off" />
</div>
</div>
diff --git a/themes/src/main/resources/theme/keycloak/admin/resources/css/styles.css b/themes/src/main/resources/theme/keycloak/admin/resources/css/styles.css
old mode 100644
new mode 100755
index 11ed88e..a4eead1
--- a/themes/src/main/resources/theme/keycloak/admin/resources/css/styles.css
+++ b/themes/src/main/resources/theme/keycloak/admin/resources/css/styles.css
@@ -336,23 +336,23 @@ h1 i {
/* Action cell */
.kc-action-cell {
- position: relative;
- width: 100px;
+ background-color: #eeeeee;
+ background-image: linear-gradient(to bottom, #fafafa 0%, #ededed 100%);
+ background-repeat: repeat-x;
+
+ text-align: center;
+ vertical-align: middle;
+
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
+
+ cursor:pointer;
}
-.kc-action-cell .btn {
- border: none;
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
+.kc-action-cell:hover {
+ background-color: #eeeeee;
+ background-image: none;
}
.kc-sorter span {
util/embedded-ldap/pom.xml 2(+1 -1)
diff --git a/util/embedded-ldap/pom.xml b/util/embedded-ldap/pom.xml
index e62d23a..3b66403 100644
--- a/util/embedded-ldap/pom.xml
+++ b/util/embedded-ldap/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
util/pom.xml 2(+1 -1)
diff --git a/util/pom.xml b/util/pom.xml
index c7bfddd..065f701 100644
--- a/util/pom.xml
+++ b/util/pom.xml
@@ -20,7 +20,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
wildfly/adduser/pom.xml 2(+1 -1)
diff --git a/wildfly/adduser/pom.xml b/wildfly/adduser/pom.xml
index 98e9557..e417c3c 100755
--- a/wildfly/adduser/pom.xml
+++ b/wildfly/adduser/pom.xml
@@ -21,7 +21,7 @@
<parent>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-wildfly-parent</artifactId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<artifactId>keycloak-wildfly-adduser</artifactId>
wildfly/extensions/pom.xml 2(+1 -1)
diff --git a/wildfly/extensions/pom.xml b/wildfly/extensions/pom.xml
index 03873a5..a581cfe 100755
--- a/wildfly/extensions/pom.xml
+++ b/wildfly/extensions/pom.xml
@@ -21,7 +21,7 @@
<parent>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-wildfly-parent</artifactId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<artifactId>keycloak-wildfly-extensions</artifactId>
wildfly/pom.xml 2(+1 -1)
diff --git a/wildfly/pom.xml b/wildfly/pom.xml
index d3dee35..efb8706 100755
--- a/wildfly/pom.xml
+++ b/wildfly/pom.xml
@@ -20,7 +20,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<name>Keycloak WildFly Integration</name>
wildfly/server-subsystem/pom.xml 2(+1 -1)
diff --git a/wildfly/server-subsystem/pom.xml b/wildfly/server-subsystem/pom.xml
index ff2d413..a98aa58 100755
--- a/wildfly/server-subsystem/pom.xml
+++ b/wildfly/server-subsystem/pom.xml
@@ -21,7 +21,7 @@
<parent>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-wildfly-parent</artifactId>
- <version>1.9.1.Final-SNAPSHOT</version>
+ <version>2.0.0.CR1-SNAPSHOT</version>
</parent>
<artifactId>keycloak-wildfly-server-subsystem</artifactId>
diff --git a/wildfly/server-subsystem/src/main/resources/subsystem-templates/keycloak-infinispan.xml b/wildfly/server-subsystem/src/main/resources/subsystem-templates/keycloak-infinispan.xml
index 79f741a..837407a 100755
--- a/wildfly/server-subsystem/src/main/resources/subsystem-templates/keycloak-infinispan.xml
+++ b/wildfly/server-subsystem/src/main/resources/subsystem-templates/keycloak-infinispan.xml
@@ -26,7 +26,9 @@
<replacement placeholder="CACHE-CONTAINERS">
<cache-container name="keycloak" jndi-name="infinispan/Keycloak">
<local-cache name="realms"/>
- <local-cache name="users"/>
+ <local-cache name="users">
+ <eviction max-entries="10000" strategy="LRU"/>
+ </local-cache>
<local-cache name="sessions"/>
<local-cache name="offlineSessions"/>
<local-cache name="loginFailures"/>
@@ -87,7 +89,9 @@
<cache-container name="keycloak" jndi-name="infinispan/Keycloak">
<transport lock-timeout="60000"/>
<invalidation-cache name="realms" mode="SYNC"/>
- <invalidation-cache name="users" mode="SYNC"/>
+ <invalidation-cache name="users" mode="SYNC">
+ <eviction max-entries="10000" strategy="LRU"/>
+ </invalidation-cache>
<distributed-cache name="sessions" mode="SYNC" owners="1"/>
<distributed-cache name="offlineSessions" mode="SYNC" owners="1"/>
<distributed-cache name="loginFailures" mode="SYNC" owners="1"/>